Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

Commit ef802a8

Browse files
author
Roman Petrov
committed
feat: add prefer-immediate-return rule
1 parent ff611af commit ef802a8

File tree

8 files changed

+271
-0
lines changed

8 files changed

+271
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* **Breaking Change:** cli arguments `--fatal-unused` and `--fatal-warnings` activate by default.
66
* chore: restrict `analyzer` version to `>=3.0.0 <3.4.0`.
77
* chore: restrict `analyzer_plugin` version to `>=0.9.0 <0.10.0`.
8+
* feat: add [prefer-immediate-return](https://dartcodemetrics.dev/docs/rules/common/prefer-immediate-return) rule
89

910
## 4.12.0
1011

lib/src/analyzers/lint_analyzer/rules/rules_factory.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import 'rules_list/prefer_correct_identifier_length/prefer_correct_identifier_le
3939
import 'rules_list/prefer_correct_type_name/prefer_correct_type_name_rule.dart';
4040
import 'rules_list/prefer_extracting_callbacks/prefer_extracting_callbacks_rule.dart';
4141
import 'rules_list/prefer_first/prefer_first_rule.dart';
42+
import 'rules_list/prefer_immediate_return/prefer_immediate_return_rule.dart';
4243
import 'rules_list/prefer_intl_name/prefer_intl_name_rule.dart';
4344
import 'rules_list/prefer_last/prefer_last_rule.dart';
4445
import 'rules_list/prefer_match_file_name/prefer_match_file_name_rule.dart';
@@ -112,6 +113,8 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
112113
PreferExtractingCallbacksRule.ruleId: (config) =>
113114
PreferExtractingCallbacksRule(config),
114115
PreferFirstRule.ruleId: (config) => PreferFirstRule(config),
116+
PreferImmediateReturnRule.ruleId: (config) =>
117+
PreferImmediateReturnRule(config),
115118
PreferIntlNameRule.ruleId: (config) => PreferIntlNameRule(config),
116119
PreferLastRule.ruleId: (config) => PreferLastRule(config),
117120
PreferMatchFileNameRule.ruleId: (config) => PreferMatchFileNameRule(config),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// ignore_for_file: public_member_api_docs
2+
3+
import 'package:analyzer/dart/ast/ast.dart';
4+
import 'package:analyzer/dart/ast/visitor.dart';
5+
6+
import '../../../../../utils/node_utils.dart';
7+
import '../../../lint_utils.dart';
8+
import '../../../models/internal_resolved_unit_result.dart';
9+
import '../../../models/issue.dart';
10+
import '../../../models/replacement.dart';
11+
import '../../../models/severity.dart';
12+
import '../../models/common_rule.dart';
13+
import '../../rule_utils.dart';
14+
15+
part 'visitor.dart';
16+
17+
class PreferImmediateReturnRule extends CommonRule {
18+
static const ruleId = 'prefer-immediate-return';
19+
static const _warningMessage =
20+
'Prefer returning the result immediately instead of declaring an intermediate variable right before the return statement.';
21+
static const _replaceComment = 'Replace with immediate return.';
22+
23+
PreferImmediateReturnRule([Map<String, Object> config = const {}])
24+
: super(
25+
id: ruleId,
26+
severity: readSeverity(config, Severity.style),
27+
excludes: readExcludes(config),
28+
);
29+
30+
@override
31+
Iterable<Issue> check(InternalResolvedUnitResult source) {
32+
final visitor = _Visitor();
33+
source.unit.visitChildren(visitor);
34+
35+
return visitor.issues
36+
.map(
37+
(issue) => createIssue(
38+
rule: this,
39+
location: nodeLocation(
40+
node: issue.returnStatement,
41+
source: source,
42+
withCommentOrMetadata: true,
43+
),
44+
message: _warningMessage,
45+
replacement: Replacement(
46+
comment: _replaceComment,
47+
replacement: 'return ${issue.variableDeclarationInitializer};',
48+
),
49+
),
50+
)
51+
.toList(growable: false);
52+
}
53+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
part of 'prefer_immediate_return_rule.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
final _issues = <_IssueDetails>[];
5+
6+
Iterable<_IssueDetails> get issues => _issues;
7+
8+
@override
9+
void visitBlockFunctionBody(BlockFunctionBody node) {
10+
super.visitBlockFunctionBody(node);
11+
12+
if (node.block.statements.length < 2) {
13+
return;
14+
}
15+
16+
final variableDeclarationStatement =
17+
node.block.statements[node.block.statements.length - 2];
18+
final returnStatement = node.block.statements.last;
19+
if (variableDeclarationStatement is! VariableDeclarationStatement ||
20+
returnStatement is! ReturnStatement) {
21+
return;
22+
}
23+
24+
final returnIdentifier = returnStatement.expression;
25+
if (returnIdentifier is! Identifier) {
26+
return;
27+
}
28+
29+
final lastDeclaredVariable =
30+
variableDeclarationStatement.variables.variables.last;
31+
if (returnIdentifier.name != lastDeclaredVariable.name.name) {
32+
return;
33+
}
34+
35+
_issues.add(_IssueDetails(
36+
lastDeclaredVariable.initializer,
37+
returnStatement,
38+
));
39+
}
40+
}
41+
42+
class _IssueDetails {
43+
const _IssueDetails(
44+
this.variableDeclarationInitializer,
45+
this.returnStatement,
46+
);
47+
48+
final Expression? variableDeclarationInitializer;
49+
final ReturnStatement returnStatement;
50+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
int calculateSum(int a, int b) {
2+
final sum = a + b;
3+
4+
return sum; // LINT
5+
}
6+
7+
int calculateSum(int a, int b) {
8+
final delta = a - b, sum = a + b;
9+
10+
return sum; // LINT
11+
}
12+
13+
final calculateSum = (int a, int b) {
14+
final delta = a - b, sum = a + b;
15+
16+
return sum; // LINT
17+
};
18+
19+
class Geometry {
20+
static void calculateRectangleArea(int width, int height) {
21+
final result = width * height;
22+
23+
return result; // LINT
24+
}
25+
}
26+
27+
void returnNull() {
28+
final String? x;
29+
30+
return x; // LINT
31+
}
32+
33+
int calculateSomething(int a, int b) {
34+
final x = a * b;
35+
36+
return x * x; // OK
37+
}
38+
39+
int calculateSum(int a, int b) {
40+
final sum = a + b, delta = a - b;
41+
42+
return sum; // OK, "sum" variable not immediately preceding return statement
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart';
2+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/prefer_immediate_return/prefer_immediate_return_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'prefer_immediate_return/examples/example.dart';
8+
9+
void main() {
10+
group(
11+
'PreferImmediateReturnRule',
12+
() {
13+
test('initialization', () async {
14+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
15+
final issues = PreferImmediateReturnRule().check(unit);
16+
17+
RuleTestHelper.verifyInitialization(
18+
issues: issues,
19+
ruleId: 'prefer-immediate-return',
20+
severity: Severity.style,
21+
);
22+
});
23+
24+
test('reports about found issues', () async {
25+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
26+
final issues = PreferImmediateReturnRule().check(unit);
27+
28+
RuleTestHelper.verifyIssues(
29+
issues: issues,
30+
startLines: [
31+
4,
32+
10,
33+
16,
34+
23,
35+
30,
36+
],
37+
startColumns: [
38+
3,
39+
3,
40+
3,
41+
5,
42+
3,
43+
],
44+
messages: [
45+
'Prefer returning the result immediately instead of declaring an intermediate variable right before the return statement.',
46+
'Prefer returning the result immediately instead of declaring an intermediate variable right before the return statement.',
47+
'Prefer returning the result immediately instead of declaring an intermediate variable right before the return statement.',
48+
'Prefer returning the result immediately instead of declaring an intermediate variable right before the return statement.',
49+
'Prefer returning the result immediately instead of declaring an intermediate variable right before the return statement.',
50+
],
51+
replacementComments: [
52+
'Replace with immediate return.',
53+
'Replace with immediate return.',
54+
'Replace with immediate return.',
55+
'Replace with immediate return.',
56+
'Replace with immediate return.',
57+
],
58+
replacements: [
59+
'return a + b;',
60+
'return a + b;',
61+
'return a + b;',
62+
'return width * height;',
63+
'return null;',
64+
],
65+
locationTexts: [
66+
'return sum;',
67+
'return sum;',
68+
'return sum;',
69+
'return result;',
70+
'return x;',
71+
],
72+
);
73+
});
74+
},
75+
);
76+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Prefer immediate return
2+
3+
![Has auto-fix](https://img.shields.io/badge/-has%20auto--fix-success)
4+
5+
## Rule id {#rule-id}
6+
7+
prefer-immediate-return
8+
9+
## Severity {#severity}
10+
11+
Style
12+
13+
## Description {#description}
14+
15+
Declaring a local variable only to immediately return it might be considered a bad practice. The name of a function or a class method with its return type should give enough information about what should be returned.
16+
17+
### Example {#example}
18+
19+
Bad:
20+
21+
```dart
22+
void calculateSum(int a, int b) {
23+
final sum = a + b;
24+
return sum; // LINT
25+
}
26+
27+
void calculateArea(int width, int height) {
28+
final result = width * height;
29+
return result; // LINT
30+
}
31+
```
32+
33+
Good:
34+
35+
```dart
36+
void calculateSum(int a, int b) {
37+
return a + b;
38+
}
39+
40+
void calculateArea(int width, int height) => width * height;
41+
```

website/docs/rules/overview.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ Rules configuration is [described here](../getting-started/configuration#configu
135135

136136
Use `first` to gets the first element.
137137

138+
- [prefer-immediate-return](./common/prefer-immediate-return.md) &nbsp; ![Has auto-fix](https://img.shields.io/badge/-has%20auto--fix-success)
139+
140+
Warns when a method or a function returns a variable declared right before the return statement.
141+
138142
- [prefer-last](./common/prefer-last.md) &nbsp; ![Has auto-fix](https://img.shields.io/badge/-has%20auto--fix-success)
139143

140144
Use `last` to gets the last element.

0 commit comments

Comments
 (0)