|
1 | 1 | import 'package:analyzer/dart/ast/ast.dart'; |
2 | 2 | import 'package:analyzer/dart/ast/visitor.dart'; |
3 | 3 | import 'package:analyzer/dart/element/element.dart'; |
4 | | -import 'package:collection/collection.dart'; |
| 4 | +import 'package:solid_lints/src/common/visitors/select_expression_identifiers_visitor.dart'; |
| 5 | +import 'package:solid_lints/src/lints/avoid_unnecessary_return_variable/avoid_unnecessary_return_variable_rule.dart'; |
| 6 | +import 'package:solid_lints/src/lints/avoid_unnecessary_return_variable/visitors/return_variable_usage_visitor.dart'; |
5 | 7 |
|
6 | | -/// This visitor is searches all uses of a single local variable, |
7 | | -/// including variable declaration. |
8 | | -class AvoidUnnecessaryReturnVariableVisitor extends RecursiveAstVisitor<void> { |
9 | | - /// The problem expects that exactly 1 mention of return variable. |
10 | | - /// VariableDeclarationStatement doesn't count when visiting SimpleIdentifier. |
11 | | - /// Any other amount of variable mentions implies that it is used somewhere |
12 | | - /// except return, so its existence is justified. |
13 | | - static const _badStatementCount = 1; |
| 8 | +/// Visitor for [AvoidUnnecessaryReturnVariableRule]. |
| 9 | +class AvoidUnnecessaryReturnVariableVisitor extends SimpleAstVisitor<void> { |
| 10 | + /// The rule associated with this visitor. |
| 11 | + final AvoidUnnecessaryReturnVariableRule _rule; |
14 | 12 |
|
15 | | - final LocalVariableElement _returnVariableElement; |
| 13 | + /// Creates a new instance of [AvoidUnnecessaryReturnVariableVisitor]. |
| 14 | + AvoidUnnecessaryReturnVariableVisitor(this._rule); |
16 | 15 |
|
17 | | - bool _foundTokensBetweenDeclarationAndReturn = false; |
18 | | - VariableDeclaration? _variableDeclaration; |
19 | | - int _variableStatementCounter = 0; |
| 16 | + @override |
| 17 | + void visitReturnStatement(ReturnStatement node) { |
| 18 | + final expr = node.expression; |
| 19 | + if (expr is! SimpleIdentifier) return; |
20 | 20 |
|
21 | | - final ReturnStatement _returnStatement; |
| 21 | + final element = expr.element; |
| 22 | + if (element is! LocalVariableElement) return; |
22 | 23 |
|
23 | | - /// After visiting holds info about whether there are any tokens |
24 | | - /// between variable declaration and return statement |
25 | | - bool get foundTokensBetweenDeclarationAndReturn => |
26 | | - _foundTokensBetweenDeclarationAndReturn; |
| 24 | + if (!element.isFinal && !element.isConst) return; |
27 | 25 |
|
28 | | - /// Returns statement of local variable declaration |
29 | | - VariableDeclaration? get variableDeclaration => _variableDeclaration; |
| 26 | + final block = node.parent; |
| 27 | + if (block == null) return; |
30 | 28 |
|
31 | | - /// Creates a new instance of [AvoidUnnecessaryReturnVariableVisitor]. |
32 | | - AvoidUnnecessaryReturnVariableVisitor( |
33 | | - this._returnVariableElement, |
34 | | - this._returnStatement, |
35 | | - ); |
| 29 | + final returnVariableUsageVisitor = ReturnVariableUsageVisitor( |
| 30 | + node, |
| 31 | + element, |
| 32 | + ); |
36 | 33 |
|
37 | | - /// Defines whether the variables is used in return statement only. |
38 | | - bool hasBadStatementCount() => |
39 | | - _variableStatementCounter == _badStatementCount; |
| 34 | + block.visitChildren(returnVariableUsageVisitor); |
| 35 | + if (!returnVariableUsageVisitor.hasBadStatementCount) return; |
40 | 36 |
|
41 | | - @override |
42 | | - void visitVariableDeclarationStatement(VariableDeclarationStatement node) { |
43 | | - if (_collectVariableDeclaration(node)) { |
44 | | - _checkTokensInBetween(node, _returnStatement); |
| 37 | + //it is 100% bad if return statement follows declaration |
| 38 | + if (!returnVariableUsageVisitor.foundTokensBetweenDeclarationAndReturn) { |
| 39 | + _rule.reportAtNode(node); |
| 40 | + return; |
45 | 41 | } |
46 | | - super.visitVariableDeclarationStatement(node); |
47 | | - } |
48 | 42 |
|
49 | | - @override |
50 | | - void visitSimpleIdentifier(SimpleIdentifier node) { |
51 | | - if (node.element?.id == _returnVariableElement.id) { |
52 | | - _variableStatementCounter++; |
53 | | - } |
| 43 | + final declaration = returnVariableUsageVisitor.variableDeclaration; |
54 | 44 |
|
55 | | - super.visitSimpleIdentifier(node); |
56 | | - } |
| 45 | + //check if immutable |
| 46 | + final initializer = declaration?.initializer; |
| 47 | + if (initializer == null) return; |
57 | 48 |
|
58 | | - bool _collectVariableDeclaration(VariableDeclarationStatement node) { |
59 | | - final targetVariable = node.variables.variables.firstWhereOrNull( |
60 | | - (v) => v.declaredFragment?.element.id == _returnVariableElement.id, |
61 | | - ); |
62 | | - if (targetVariable == null) return false; |
| 49 | + if (!_isExpressionImmutable(initializer)) return; |
63 | 50 |
|
64 | | - _variableDeclaration = targetVariable; |
65 | | - return true; |
| 51 | + _rule.reportAtNode(node); |
66 | 52 | } |
67 | 53 |
|
68 | | - void _checkTokensInBetween( |
69 | | - VariableDeclarationStatement variableDeclaration, |
70 | | - ReturnStatement returnStatement, |
71 | | - ) { |
72 | | - final tokenBeforeReturn = |
73 | | - _returnStatement.findPrevious(_returnStatement.beginToken); |
| 54 | + bool _isExpressionImmutable(Expression expr) { |
| 55 | + final visitor = SelectExpressionIdentifiersVisitor(); |
| 56 | + expr.accept(visitor); |
74 | 57 |
|
75 | | - if (tokenBeforeReturn != variableDeclaration.endToken) { |
76 | | - _foundTokensBetweenDeclarationAndReturn = true; |
77 | | - } |
| 58 | + return visitor.identifiers.every(_isSimpleIdentifierImmutable); |
| 59 | + } |
| 60 | + |
| 61 | + bool _isSimpleIdentifierImmutable(SimpleIdentifier identifier) { |
| 62 | + return switch (identifier.element) { |
| 63 | + ClassElement _ => true, |
| 64 | + final VariableElement variable => variable.isFinal || variable.isConst, |
| 65 | + GetterElement(:final PropertyInducingElement variable) => |
| 66 | + variable.isFinal || variable.isConst, |
| 67 | + _ => false, |
| 68 | + }; |
78 | 69 | } |
79 | 70 | } |
0 commit comments