join-return-with-assignment

Same as join_return_with_assignment in Dart lints

Path
lint/complexity/join-return-with-assignment
Default
Off, opt in
Origin
Dart lints
Rule
249 of 512

Flags an assignment immediately followed by return of the same variable.

When a block ends with x = e; return x; the two statements can be joined into return x = e; (or simply return e; for a local). The rule looks at the last two statements of every block: the second-to-last must be an assignment (any operator), increment, decrement, or other prefix/postfix expression on the same variable or property chain that the final return yields. A run of assignments to that same target (x = a; x += b; return x;) is left alone, as are switch-case statement lists, which are not blocks. Diagnostics cover the expression statement, including its semicolon.

int f(int a) {
  a += 1; // flagged
  return a;
}

Invalid

example.dartdart
class A {
  int _a = 0;
  int b = 0;
  late A next;

  int incrementA() {
    _a++;
    return _a;
  }

  int plusEquals(int value) {
    _a += value;
    return _a;
  }

  int assignB(int value) {
    b = value;
    return b;
  }

  int? chained(A other) {
    other.b = 3;
    return other.b;
  }

  int? propertyChain(A other) {
    print('x');
    other.next.b = 4;
    return other.next.b;
  }
}

int prefix(int x) {
  ++x;
  return x;
}

int afterOtherStatement(int x, int y) {
  y = 2;
  x = y + 1;
  return x;
}

int nested(bool c, int x) {
  if (c) {
    x *= 2;
    return x;
  }
  return 0;
}

int? nullable(int? x) {
  x ??= 1;
  return x;
}

int Function() closure(int x) {
  return () {
    x--;
    return x;
  };
}

// Parentheses around the returned name do not matter.
int parenthesizedReturn(int x) {
  x = 1;
  return (x);
}

class StateBase {
  int get state => 0;
  set state(int value) {}
}

class InheritedState extends StateBase {}

int pairedAccessors(InheritedState notifier) {
  notifier.state = 1;
  return notifier.state;
}

int pairedAccessorChain(List<InheritedState> notifiers) {
  notifiers.first.state = 1;
  return notifiers.first.state;
}

dynamic unresolvedMembers(dynamic receiver) {
  // Unresolved dynamic properties have the same null canonical element in Dart.
  receiver.first = 1;
  return receiver.second;
}
Assignment could be inlined in `return` statement.
6 int incrementA() {
7 _a++;
Assignment could be inlined in `return` statement.
11 int plusEquals(int value) {
12 _a += value;
Assignment could be inlined in `return` statement.
16 int assignB(int value) {
17 b = value;
Assignment could be inlined in `return` statement.
21 int? chained(A other) {
22 other.b = 3;
Assignment could be inlined in `return` statement.
27 print('x');
28 other.next.b = 4;
Assignment could be inlined in `return` statement.
33int prefix(int x) {
34 ++x;
Assignment could be inlined in `return` statement.
39 y = 2;
40 x = y + 1;
Assignment could be inlined in `return` statement.
45 if (c) {
46 x *= 2;
Assignment could be inlined in `return` statement.
52int? nullable(int? x) {
53 x ??= 1;
Assignment could be inlined in `return` statement.
58 return () {
59 x--;
Assignment could be inlined in `return` statement.
65int parenthesizedReturn(int x) {
66 x = 1;
Assignment could be inlined in `return` statement.
77int pairedAccessors(InheritedState notifier) {
78 notifier.state = 1;
Assignment could be inlined in `return` statement.
88 // Unresolved dynamic properties have the same null canonical element in Dart.
89 receiver.first = 1;

Valid

example.dartdart
class A {
  int _a = 0;
  int b = 0;
  A? next;

  int joined() {
    return ++_a;
  }

  // `this.b` is not an identifier chain, matching the analyzer.
  int viaThis(int value) {
    this.b = value;
    return this.b;
  }

  // Mixing `b` and `this.b` is not provably the same element.
  int mixed(int value) {
    b = value;
    return this.b;
  }

  int? nullAsserted(A other) {
    other.next!.b = 4;
    return other.next!.b;
  }
}

// A preceding assignment to the same variable keeps the pair apart.
int runOfAssignments(int x) {
  x = 1;
  x += 2;
  return x;
}

int differentVariable(int x, int y) {
  x = 2;
  return y;
}

int notLast(int x) {
  x = 1;
  print(x);
  return x;
}

int indexed(List<int> list) {
  list[0] = 1;
  return list[0];
}

void noValue(int x) {
  x = 1;
  return;
}

// Switch-case statement lists are not blocks.
int inSwitch(int kind, int x) {
  switch (kind) {
    case 1:
      x = 3;
      return x;
    default:
      return 0;
  }
}

int callStatement(List<int> list) {
  list.add(1);
  return list.length;
}

class StateBase {
  int get state => 0;
  set state(int value) {}
}

class ReadOverride extends StateBase {
  @override
  int get state => super.state;
}

// The getter and setter canonicalize to variables on different declarations.
int splitAccessors(ReadOverride notifier) {
  notifier.state = 1;
  return notifier.state;
}

int splitAccessorChain(List<ReadOverride> notifiers) {
  notifiers.first.state = 1;
  return notifiers.first.state;
}

int differentReceivers(StateBase first, StateBase second) {
  first.state = 1;
  return second.state;
}

int differentAccessSyntax(StateBase notifier) {
  notifier.state = 1;
  return (notifier).state;
}

Configure join-return-with-assignment

Set its severity in falcon.json. See the configuration guide.

falcon.jsonjson
{
  "linter": {
    "rules": {
      "complexity": {
        "join-return-with-assignment": "error"
      }
    }
  }
}

Or silence one occurrence with a suppression comment; the reason is required.

example.dartdart
// falcon-ignore lint/complexity/join-return-with-assignment: <reason>
  • max-lines-for-filemax_lines_for_file

    Flags a file longer than the configured line limit.

  • max-lines-for-functionmax_lines_for_function

    Flags a function or method longer than the configured line limit.

  • max-parameters-for-functionmax_parameters_for_function

    Flags callable declarations and function types with more than the configured number of parameters.

  • max-statements

    Reports function bodies with too many statements.

  • max-switch-casesmax_switch_cases

    Flags a switch statement with more than the configured number of cases.

  • maximum-nesting-levelmaximum_nesting_level

    Flags a function whose control-flow nesting runs deeper than the configured level.

  • no-boolean-literal-compareno-boolean-literal-comparerecommended

    Don't compare boolean values to boolean literals.

  • one-member-abstractsone_member_abstracts

    Flags abstract classes whose only member is a single abstract method.