Skip to content

Commit 9a330c0

Browse files
committed
Issue #148: work in progress (do not merge), new rule avoid_initstate_field_initialization
1 parent 7a96dd6 commit 9a330c0

File tree

6 files changed

+321
-0
lines changed

6 files changed

+321
-0
lines changed

lib/analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ custom_lint:
4343
ignored_types:
4444
- AnimationController
4545

46+
- avoid_initstate_field_initialization
4647
- avoid_non_null_assertion
4748
- avoid_returning_widgets
4849
- avoid_unnecessary_return_variable

lib/solid_lints.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:custom_lint_builder/custom_lint_builder.dart';
44
import 'package:solid_lints/src/lints/avoid_debug_print_in_release/avoid_debug_print_in_release_rule.dart';
55
import 'package:solid_lints/src/lints/avoid_final_with_getter/avoid_final_with_getter_rule.dart';
66
import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule.dart';
7+
import 'package:solid_lints/src/lints/avoid_initstate_field_initialization/avoid_initstate_field_initialization_rule.dart';
78
import 'package:solid_lints/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart';
89
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
910
import 'package:solid_lints/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart';
@@ -69,6 +70,7 @@ class _SolidLints extends PluginBase {
6970
AvoidFinalWithGetterRule.createRule(configs),
7071
NamedParametersOrderingRule.createRule(configs),
7172
AvoidUnnecessaryReturnVariableRule.createRule(configs),
73+
AvoidInitstateFieldInitializationRule.createRule(configs),
7274
];
7375

7476
// Return only enabled rules
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import 'package:analyzer/dart/ast/ast.dart';
2+
import 'package:analyzer/dart/ast/visitor.dart';
3+
import 'package:analyzer/error/listener.dart';
4+
import 'package:collection/collection.dart';
5+
import 'package:custom_lint_builder/custom_lint_builder.dart';
6+
import 'package:solid_lints/src/lints/avoid_initstate_field_initialization/visitors/avoid_initstate_field_initialization_visitor.dart';
7+
import 'package:solid_lints/src/models/rule_config.dart';
8+
import 'package:solid_lints/src/models/solid_lint_rule.dart';
9+
10+
/// An `avoid_initstate_field_initialization` rule which suggests using
11+
/// declaration initializer over initializing field from `initState()`
12+
/// if possible.
13+
///
14+
/// See more here: https://github.com/solid-software/solid_lints/issues/148
15+
///
16+
/// ### Example
17+
///
18+
/// #### BAD:
19+
///
20+
/// ```dart
21+
/// class SomeState extends State<Some> {
22+
/// late final X? x;
23+
/// void initState() {
24+
/// x = widget.x;
25+
/// }
26+
/// }
27+
/// ```
28+
///
29+
/// #### GOOD:
30+
///
31+
/// ```dart
32+
/// class SomeState extends State<Some> {
33+
/// late final x = widget.x;
34+
/// }
35+
/// ```
36+
///
37+
class AvoidInitstateFieldInitializationRule extends SolidLintRule {
38+
/// This lint rule represents the error when field
39+
/// is initialized from `initState()`
40+
/// when declaration initializer is possible.
41+
static const lintName = 'avoid_initstate_field_initialization';
42+
43+
/// Name of `initState()` method.
44+
static const initState = 'initState';
45+
46+
AvoidInitstateFieldInitializationRule._(super.config);
47+
48+
/// Creates a new instance of [AvoidInitstateFieldInitializationRule]
49+
/// based on the lint configuration.
50+
factory AvoidInitstateFieldInitializationRule.createRule(
51+
CustomLintConfigs configs,
52+
) {
53+
final rule = RuleConfig(
54+
configs: configs,
55+
name: lintName,
56+
problemMessage: (_) => """
57+
Avoid initialization final fields in initState().
58+
Use declaration initializer instead.""",
59+
);
60+
61+
return AvoidInitstateFieldInitializationRule._(rule);
62+
}
63+
64+
@override
65+
void run(
66+
CustomLintResolver resolver,
67+
ErrorReporter reporter,
68+
CustomLintContext context,
69+
) {
70+
context.registry.addClassDeclaration((classDec) {
71+
_checkClass(
72+
classDec: classDec,
73+
reporter: reporter,
74+
context: context,
75+
);
76+
});
77+
// context.registry.addMethodDeclaration(
78+
// (method) {
79+
// if (method.name.toString() != initState) return;
80+
81+
// _checkInitStateMethod(
82+
// method: method,
83+
// reporter: reporter,
84+
// context: context,
85+
// );
86+
// },
87+
// );
88+
}
89+
90+
void _checkClass({
91+
required ClassDeclaration classDec,
92+
required ErrorReporter reporter,
93+
required CustomLintContext context,
94+
}) {
95+
final classElement = classDec.declaredFragment?.element;
96+
if (classElement == null) return;
97+
98+
//shortcut by checking class info without any visitor
99+
final initStateMethod = classElement.methods2.firstWhereOrNull(
100+
(method) => method.name3 == initState,
101+
);
102+
if (initStateMethod == null) return;
103+
104+
//TODO REMOVE THIS
105+
//For test purposes ignoring all classes except in test file.
106+
if (!classElement.name3.toString().startsWith("_TestWidgetState")) return;
107+
print("\n${classElement.name3}");
108+
//TODO END REMOVE
109+
110+
final visitor = AvoidInitstateFieldInitializationVisitor();
111+
112+
classDec.accept(visitor);
113+
}
114+
115+
void _checkInitStateMethod({
116+
required MethodDeclaration method,
117+
required ErrorReporter reporter,
118+
required CustomLintContext context,
119+
}) {
120+
final classDeclaration = method.thisOrAncestorOfType<ClassDeclaration>();
121+
final classElement = classDeclaration?.declaredFragment?.element;
122+
if (classElement == null) return;
123+
124+
//TODO REMOVE THIS
125+
//For test purposes ignoring all classes except in test file.
126+
if (!classElement.name3.toString().startsWith("_TestWidgetState")) return;
127+
print("\n${classElement.name3}");
128+
//TODO END REMOVE
129+
130+
//fields are clearly recognizeable here
131+
print(classElement.fields2);
132+
133+
AstNode body = method.body;
134+
while (body.childEntities.firstOrNull != null) {
135+
if (body.childEntities.first is! AstNode) break;
136+
body = body.childEntities.first as AstNode;
137+
}
138+
139+
for (final child in body.childEntities) {
140+
// print(child);
141+
// print(child.runtimeType);
142+
if (child is! ExpressionStatement) continue;
143+
144+
final expr = child.expression;
145+
if (expr is! AssignmentExpression) continue;
146+
147+
final leftHand = expr.leftHandSide;
148+
if (leftHand is! SimpleIdentifier) continue;
149+
print(leftHand.element);
150+
151+
if (child.expression case final SimpleIdentifier id) {
152+
print(id.element);
153+
}
154+
}
155+
156+
//TODO: if restoring this method, visitor should become Recursive again
157+
final visitor = AvoidInitstateFieldInitializationVisitor();
158+
method.visitChildren(visitor);
159+
}
160+
}
161+
162+
//HACK: Failed attempt to use separate visitor, didn't help
163+
class _VisitorWrapper extends SimpleAstVisitor<void> {
164+
@override
165+
void visitMethodDeclaration(MethodDeclaration node) {
166+
if (node.name.toString() !=
167+
AvoidInitstateFieldInitializationRule.initState) {
168+
return;
169+
}
170+
final visitor = AvoidInitstateFieldInitializationVisitor();
171+
node.visitChildren(visitor);
172+
}
173+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import 'package:analyzer/dart/ast/ast.dart';
2+
import 'package:analyzer/dart/ast/visitor.dart';
3+
import 'package:solid_lints/src/lints/avoid_initstate_field_initialization/avoid_initstate_field_initialization_rule.dart';
4+
// import 'package:analyzer/dart/element/element2.dart';
5+
// import 'package:collection/collection.dart';
6+
7+
/// TODO DESCRIPTION
8+
class AvoidInitstateFieldInitializationVisitor extends SimpleAstVisitor<void> {
9+
@override
10+
void visitClassDeclaration(ClassDeclaration node) {
11+
super.visitClassDeclaration(node);
12+
13+
node.visitChildren(this);
14+
}
15+
16+
@override
17+
void visitMethodDeclaration(MethodDeclaration node) {
18+
super.visitMethodDeclaration(node);
19+
20+
if (node.name.toString() !=
21+
AvoidInitstateFieldInitializationRule.initState) {
22+
return;
23+
}
24+
25+
AstNode body = node.body;
26+
while (body.childEntities.firstOrNull != null) {
27+
if (body.childEntities.first is! AstNode) break;
28+
body = body.childEntities.first as AstNode;
29+
}
30+
31+
body.visitChildren(this);
32+
}
33+
34+
@override
35+
void visitExpressionStatement(ExpressionStatement node) {
36+
super.visitExpressionStatement(node);
37+
node.visitChildren(this);
38+
}
39+
40+
@override
41+
void visitAssignmentExpression(AssignmentExpression node) {
42+
super.visitAssignmentExpression(node);
43+
44+
if (node.leftHandSide case final SimpleIdentifier field
45+
//when field.element is FieldElement2
46+
) {
47+
_processVisitedVariable(node, field);
48+
}
49+
}
50+
51+
void _processVisitedVariable(
52+
AssignmentExpression node,
53+
SimpleIdentifier identifier,
54+
) {
55+
//TODO: why identifier.element is this null?
56+
print("$node ; $identifier ; ${identifier.element}");
57+
}
58+
}

lint_test/analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,4 @@ custom_lint:
9090
- nullable
9191
- default
9292
- avoid_unnecessary_return_variable
93+
- avoid_initstate_field_initialization
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// ignore_for_file: unused_local_variable, prefer_match_file_name, avoid_late_keyword, member_ordering
2+
3+
import 'package:flutter/widgets.dart';
4+
5+
/// Widget to test avoid_initstate_field_initialization rule.
6+
class TestWidget extends StatefulWidget {
7+
static const int xConst = 0;
8+
//ignore: avoid_global_state
9+
static int xStaticVariable = 0;
10+
final int xFinal = 0;
11+
@override
12+
State<TestWidget> createState() => _TestWidgetStateBad();
13+
}
14+
15+
class _TestWidgetStateBad extends State<TestWidget> {
16+
late final int? a;
17+
late final int? b;
18+
19+
void initState() {
20+
super.initState();
21+
//expect_lint: avoid_initstate_field_initialization
22+
a = widget.xFinal;
23+
//expect_lint: avoid_initstate_field_initialization
24+
b = (widget.xFinal + TestWidget.xConst) * 1;
25+
}
26+
27+
@override
28+
Widget build(BuildContext context) {
29+
throw UnimplementedError();
30+
}
31+
}
32+
33+
// ignore: unused_element
34+
class _TestWidgetStateGood extends State<TestWidget> {
35+
late final a = widget.xFinal;
36+
late final int? b;
37+
late final int? c;
38+
late final int? d;
39+
late final int? e;
40+
late final int? f;
41+
late final g = widget.xFinal + TestWidget.xConst;
42+
late final int? h;
43+
int? k;
44+
45+
void initState() {
46+
super.initState();
47+
48+
//init from local variable
49+
final bLocal = 1;
50+
b = bLocal;
51+
52+
//init from external mutable
53+
c = TestWidget.xStaticVariable;
54+
55+
//init from method result
56+
d = _evaluateSomething();
57+
58+
//init from expression with variable
59+
e = widget.xFinal + TestWidget.xStaticVariable;
60+
61+
//init more than once by arbitrary logic
62+
f = widget.xFinal;
63+
if (widget.xFinal <= 0) {
64+
f = TestWidget.xConst;
65+
}
66+
67+
//init conditionally (possible null value)
68+
if (bLocal <= TestWidget.xConst) {
69+
h = widget.xFinal;
70+
}
71+
72+
//init variable, not final
73+
k = widget.xFinal;
74+
75+
//init local variable
76+
int localInit;
77+
localInit = 0;
78+
}
79+
80+
@override
81+
Widget build(BuildContext context) {
82+
throw UnimplementedError();
83+
}
84+
85+
int _evaluateSomething() => 1;
86+
}

0 commit comments

Comments
 (0)