Skip to content

Commit 0ac062d

Browse files
applied code review suggestions
1 parent b126a49 commit 0ac062d

File tree

3 files changed

+203
-75
lines changed

3 files changed

+203
-75
lines changed

lib/src/lints/proper_super_calls/proper_super_calls_rule.dart

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,49 +44,43 @@ import 'package:solid_lints/src/lints/proper_super_calls/visitors/proper_super_c
4444
/// }
4545
/// ```
4646
class ProperSuperCallsRule extends AnalysisRule {
47-
/// This lint rule name.
47+
/// Lint rule name.
4848
static const String lintName = 'proper_super_calls';
4949

50-
/// Error code for when super.initState() is not the first statement.
51-
static const _superInitStateCode = LintCode(
50+
/// Error code for invalid `super.initState()` placement.
51+
static const LintCode superInitStateCode = LintCode(
5252
lintName,
53-
"super.initState() should be first",
53+
'super.initState() should be first',
5454
);
5555

56-
/// Error code for when super.dispose() is not the last statement.
57-
static const _superDisposeCode = LintCode(
56+
/// Error code for invalid `super.dispose()` placement.
57+
static const LintCode superDisposeCode = LintCode(
5858
lintName,
59-
"super.dispose() should be last",
59+
'super.dispose() should be last',
6060
);
6161

62-
/// Creates a new instance of [ProperSuperCallsRule].
62+
/// Creates an instance of [ProperSuperCallsRule].
6363
ProperSuperCallsRule()
6464
: super(
6565
name: lintName,
6666
description:
67-
'Ensures that `super` calls are made in the correct order ',
67+
'Ensures proper ordering of Flutter lifecycle super calls.',
6868
);
6969

7070
@override
71-
LintCode get diagnosticCode => _superInitStateCode;
71+
LintCode get diagnosticCode => superInitStateCode;
7272

7373
@override
7474
void registerNodeProcessors(
7575
RuleVisitorRegistry registry,
7676
RuleContext context,
7777
) {
78-
final visitor = ProperSuperCallsVisitor(
79-
onViolation: (nameToken, {required bool isInitState}) {
80-
// Access the reporter from the currentUnit
81-
final reporter = context.currentUnit?.diagnosticReporter;
82-
83-
reporter?.atToken(
84-
nameToken,
85-
isInitState ? _superInitStateCode : _superDisposeCode,
86-
);
87-
},
78+
registry.addMethodDeclaration(
79+
this,
80+
ProperSuperCallsVisitor(
81+
rule: this,
82+
context: context,
83+
),
8884
);
89-
90-
registry.addMethodDeclaration(this, visitor);
9185
}
9286
}
Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,112 @@
1+
import 'package:analyzer/analysis_rule/rule_context.dart';
12
import 'package:analyzer/dart/ast/ast.dart';
2-
import 'package:analyzer/dart/ast/token.dart';
33
import 'package:analyzer/dart/ast/visitor.dart';
4+
import 'package:analyzer/dart/element/element.dart';
5+
import 'package:analyzer/dart/element/type.dart';
46
import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart';
57

6-
/// Visitor for the proper [ProperSuperCallsRule].
8+
/// Visitor for [ProperSuperCallsRule].
79
class ProperSuperCallsVisitor extends SimpleAstVisitor<void> {
8-
/// Callback to report violations.
9-
final void Function(Token name, {required bool isInitState}) onViolation;
10+
/// The rule associated with this visitor.
11+
final ProperSuperCallsRule rule;
12+
13+
/// The context associated with this visitor.
14+
final RuleContext context;
1015

1116
static const _initState = 'initState';
1217
static const _dispose = 'dispose';
13-
static const _override = 'override';
18+
static const _flutterStateClass = 'State';
1419

1520
/// Creates a new instance of [ProperSuperCallsVisitor].
16-
ProperSuperCallsVisitor({required this.onViolation});
21+
ProperSuperCallsVisitor({
22+
required this.rule,
23+
required this.context,
24+
});
1725

1826
@override
1927
void visitMethodDeclaration(MethodDeclaration node) {
2028
final methodName = node.name.lexeme;
2129
final body = node.body;
2230

23-
if ((methodName == _initState || methodName == _dispose) &&
24-
body is BlockFunctionBody) {
25-
final hasOverride = node.metadata.any(
26-
(annotation) => annotation.name.name == _override,
31+
if ((methodName != _initState && methodName != _dispose) ||
32+
body is! BlockFunctionBody) {
33+
return;
34+
}
35+
36+
if (!_overridesFlutterStateMethod(node)) {
37+
return;
38+
}
39+
40+
final statements = body.block.statements;
41+
final reporter = context.currentUnit?.diagnosticReporter;
42+
43+
if (reporter == null) return;
44+
45+
if (methodName == _initState &&
46+
!_isSuperCallFirst(statements, _initState)) {
47+
reporter.atToken(
48+
node.name,
49+
ProperSuperCallsRule.superInitStateCode,
50+
);
51+
}
52+
53+
if (methodName == _dispose && !_isSuperCallLast(statements, _dispose)) {
54+
reporter.atToken(
55+
node.name,
56+
ProperSuperCallsRule.superDisposeCode,
2757
);
28-
if (!hasOverride) return;
58+
}
59+
}
2960

30-
final statements = body.block.statements;
61+
bool _overridesFlutterStateMethod(MethodDeclaration node) {
62+
final classElement = node.declaredFragment?.element.enclosingElement;
3163

32-
// Logic for initState: super.initState() must be the very first statement.
33-
if (methodName == _initState &&
34-
!_isSuperCallFirst(statements, _initState)) {
35-
onViolation(node.name, isInitState: true);
36-
}
64+
if (classElement is! ClassElement) {
65+
return false;
66+
}
67+
68+
final methodName = node.name.lexeme;
69+
70+
final supertype = classElement.supertype;
71+
72+
if (supertype == null) {
73+
return false;
74+
}
3775

38-
// Logic for dispose: super.dispose() must be the very last statement.
39-
if (methodName == _dispose && !_isSuperCallLast(statements, _dispose)) {
40-
onViolation(node.name, isInitState: false);
41-
}
76+
final isStateSubclass = _isStateSubclass(supertype);
77+
78+
if (!isStateSubclass) {
79+
return false;
4280
}
81+
82+
return methodName == _initState || methodName == _dispose;
83+
}
84+
85+
bool _isStateSubclass(InterfaceType supertype) {
86+
final isStateSubclass = supertype.element.name == _flutterStateClass ||
87+
supertype.allSupertypes.any(
88+
(t) => t.element.name == _flutterStateClass,
89+
);
90+
return isStateSubclass;
4391
}
4492

45-
/// Returns true if the first statement is the expected super call.
4693
bool _isSuperCallFirst(List<Statement> statements, String name) {
4794
return statements.isNotEmpty && _isTargetSuperCall(statements.first, name);
4895
}
4996

50-
/// Returns true if the last statement is the expected super call.
5197
bool _isSuperCallLast(List<Statement> statements, String name) {
5298
return statements.isNotEmpty && _isTargetSuperCall(statements.last, name);
5399
}
54100

55-
/// Checks if a statement is a [MethodInvocation] on [SuperExpression].
56101
bool _isTargetSuperCall(Statement statement, String name) {
57-
if (statement is ExpressionStatement) {
58-
final expr = statement.expression;
59-
return expr is MethodInvocation &&
60-
expr.target is SuperExpression &&
61-
expr.methodName.name == name;
102+
if (statement is! ExpressionStatement) {
103+
return false;
62104
}
63-
return false;
105+
106+
final expression = statement.expression;
107+
108+
return expression is MethodInvocation &&
109+
expression.target is SuperExpression &&
110+
expression.methodName.name == name;
64111
}
65112
}

test/proper_super_calls_rule_test.dart

Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,59 +12,131 @@ void main() {
1212
class ProperSuperCallsRuleTest extends AnalysisRuleTest {
1313
@override
1414
void setUp() {
15+
final flutter = newPackage('flutter');
16+
flutter.addFile(
17+
'lib/src/widgets/framework.dart',
18+
r'''
19+
abstract class StatefulWidget {}
20+
21+
abstract class State<T extends StatefulWidget> {
22+
void initState() {}
23+
void dispose() {}
24+
}
25+
''',
26+
);
27+
1528
rule = ProperSuperCallsRule();
1629
super.setUp();
1730
}
1831

1932
@override
2033
String get analysisRule => ProperSuperCallsRule.lintName;
2134

22-
/// Mocks the Flutter framework parts relevant to this lint.
23-
String get _flutterBase => r'''
24-
abstract class StatefulWidget {}
25-
abstract class State<T extends StatefulWidget> {
26-
void initState() {}
27-
void dispose() {}
35+
void test_initState_reports_when_super_not_first() async {
36+
await assertDiagnostics(
37+
r'''
38+
import 'package:flutter/src/widgets/framework.dart';
39+
40+
class MyWidgetState extends State<StatefulWidget> {
41+
@override
42+
void initState() {
43+
print('Bad');
44+
super.initState();
45+
}
2846
}
29-
''';
47+
''',
48+
[lint(125, 9)],
49+
);
50+
}
3051

31-
void test_initState_reports_when_not_first() async {
52+
void test_dispose_reports_when_super_not_last() async {
3253
await assertDiagnostics(
33-
_flutterBase +
34-
r'''
54+
r'''
55+
import 'package:flutter/src/widgets/framework.dart';
56+
3557
class MyWidgetState extends State<StatefulWidget> {
3658
@override
59+
void dispose() {
60+
super.dispose();
61+
print('Bad');
62+
}
63+
}
64+
''',
65+
[lint(125, 7)],
66+
);
67+
}
68+
69+
void test_reports_even_without_explicit_override_annotation() async {
70+
await assertDiagnostics(
71+
r'''
72+
import 'package:flutter/src/widgets/framework.dart';
73+
74+
class MyWidgetState extends State<StatefulWidget> {
3775
void initState() {
3876
print('Bad');
3977
super.initState();
4078
}
4179
}
4280
''',
43-
[
44-
lint(197, 9), // Highlights 'initState'
45-
]);
81+
[lint(113, 9)],
82+
);
83+
}
84+
85+
void test_reports_empty_body_missing_super() async {
86+
await assertDiagnostics(
87+
r'''
88+
import 'package:flutter/src/widgets/framework.dart';
89+
90+
class MyWidgetState extends State<StatefulWidget> {
91+
@override
92+
void initState() {}
93+
}
94+
''',
95+
[lint(125, 9)],
96+
);
4697
}
4798

48-
void test_dispose_reports_when_not_last() async {
99+
void test_reports_when_wrong_super_method_is_called() async {
49100
await assertDiagnostics(
50-
_flutterBase +
51-
r'''
101+
r'''
102+
import 'package:flutter/src/widgets/framework.dart';
103+
52104
class MyWidgetState extends State<StatefulWidget> {
105+
@override
106+
void initState() {
107+
super.dispose();
108+
print('Bad');
109+
}
110+
}
111+
''',
112+
[lint(125, 9)],
113+
);
114+
}
115+
116+
void test_reports_in_deep_inheritance() async {
117+
await assertDiagnostics(
118+
r'''
119+
import 'package:flutter/src/widgets/framework.dart';
120+
121+
abstract class BaseState extends State<StatefulWidget> {}
122+
123+
class MyWidgetState extends BaseState {
53124
@override
54125
void dispose() {
55126
super.dispose();
56127
print('Bad');
57128
}
58129
}
59130
''',
60-
[
61-
lint(197, 7), // Highlights 'dispose'
62-
]);
131+
[lint(172, 7)],
132+
);
63133
}
64134

65-
void test_no_report_for_correct_flutter_usage() async {
66-
await assertNoDiagnostics(_flutterBase +
67-
r'''
135+
void test_no_report_for_correct_usage() async {
136+
await assertNoDiagnostics(
137+
r'''
138+
import 'package:flutter/src/widgets/framework.dart';
139+
68140
class MyWidgetState extends State<StatefulWidget> {
69141
@override
70142
void initState() {
@@ -78,6 +150,21 @@ class MyWidgetState extends State<StatefulWidget> {
78150
super.dispose();
79151
}
80152
}
81-
''');
153+
''',
154+
);
155+
}
156+
157+
void test_no_report_for_other_methods() async {
158+
await assertNoDiagnostics(
159+
r'''
160+
import 'package:flutter/src/widgets/framework.dart';
161+
162+
class MyWidgetState extends State<StatefulWidget> {
163+
void build() {
164+
super.initState();
165+
}
166+
}
167+
''',
168+
);
82169
}
83170
}

0 commit comments

Comments
 (0)