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

Commit ad87db1

Browse files
authored
feat: add static code diagnostic avoid-initializing-in-on-mount (#1165)
* feat: add new rule correct-game-instantiating * feat: add static code diagnostic avoid-initializing-in-on-mount * chore: remove implementation import * chore: update comments
1 parent c3a124a commit ad87db1

File tree

9 files changed

+160
-7
lines changed

9 files changed

+160
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
* docs: remove old website
66
* feat: add static code diagnostic [`correct-game-instantiating`](https://dcm.dev/docs/individuals/rules/flame/correct-game-instantiating).
7+
* feat: add static code diagnostic [`avoid-initializing-in-on-mount`](https://dcm.dev/docs/individuals/rules/flame/avoid-initializing-in-on-mount).
78

89
## 5.5.1
910

lib/src/analyzers/lint_analyzer/rules/models/flame_rule.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import 'rule.dart';
22
import 'rule_type.dart';
33

4-
/// Represents a base class for Flutter-specific rules.
4+
/// Represents a base class for Flame-specific rules.
5+
/// link: https://pub.dev/packages/flame
56
abstract class FlameRule extends Rule {
6-
static const link = 'https://pub.dev/packages/flame';
7-
87
const FlameRule({
98
required super.id,
109
required super.severity,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'rules_list/avoid_dynamic/avoid_dynamic_rule.dart';
1111
import 'rules_list/avoid_expanded_as_spacer/avoid_expanded_as_spacer_rule.dart';
1212
import 'rules_list/avoid_global_state/avoid_global_state_rule.dart';
1313
import 'rules_list/avoid_ignoring_return_values/avoid_ignoring_return_values_rule.dart';
14+
import 'rules_list/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule.dart';
1415
import 'rules_list/avoid_late_keyword/avoid_late_keyword_rule.dart';
1516
import 'rules_list/avoid_missing_enum_constant_in_map/avoid_missing_enum_constant_in_map_rule.dart';
1617
import 'rules_list/avoid_nested_conditional_expressions/avoid_nested_conditional_expressions_rule.dart';
@@ -88,6 +89,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
8889
AvoidDynamicRule.ruleId: AvoidDynamicRule.new,
8990
AvoidGlobalStateRule.ruleId: AvoidGlobalStateRule.new,
9091
AvoidIgnoringReturnValuesRule.ruleId: AvoidIgnoringReturnValuesRule.new,
92+
AvoidInitializingInOnMountRule.ruleId: AvoidInitializingInOnMountRule.new,
9193
AvoidLateKeywordRule.ruleId: AvoidLateKeywordRule.new,
9294
AvoidMissingEnumConstantInMapRule.ruleId:
9395
AvoidMissingEnumConstantInMapRule.new,

lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_global_state/avoid_global_state_rule.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@ class AvoidGlobalStateRule extends CommonRule {
3737
return visitor.declarations
3838
.map((declaration) => createIssue(
3939
rule: this,
40-
location: nodeLocation(
41-
node: declaration,
42-
source: source,
43-
),
40+
location: nodeLocation(node: declaration, source: source),
4441
message: _warning,
4542
))
4643
.toList(growable: false);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
import 'package:analyzer/dart/element/element.dart';
6+
import 'package:collection/collection.dart';
7+
8+
import '../../../../../utils/flame_type_utils.dart';
9+
import '../../../../../utils/node_utils.dart';
10+
import '../../../lint_utils.dart';
11+
import '../../../models/internal_resolved_unit_result.dart';
12+
import '../../../models/issue.dart';
13+
import '../../../models/severity.dart';
14+
import '../../models/flame_rule.dart';
15+
import '../../rule_utils.dart';
16+
17+
part 'visitor.dart';
18+
19+
class AvoidInitializingInOnMountRule extends FlameRule {
20+
static const String ruleId = 'avoid-initializing-in-on-mount';
21+
22+
static const _warningMessage =
23+
'Avoid initializing final late variables in onMount.';
24+
25+
AvoidInitializingInOnMountRule([Map<String, Object> config = const {}])
26+
: super(
27+
id: ruleId,
28+
severity: readSeverity(config, Severity.warning),
29+
excludes: readExcludes(config),
30+
includes: readIncludes(config),
31+
);
32+
33+
@override
34+
Iterable<Issue> check(InternalResolvedUnitResult source) {
35+
final visitor = _Visitor();
36+
37+
source.unit.visitChildren(visitor);
38+
39+
return visitor.expressions
40+
.map((expression) => createIssue(
41+
rule: this,
42+
location: nodeLocation(node: expression, source: source),
43+
message: _warningMessage,
44+
))
45+
.toList(growable: false);
46+
}
47+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
part of 'avoid_initializing_in_on_mount_rule.dart';
2+
3+
class _Visitor extends SimpleAstVisitor<void> {
4+
final _expressions = <AssignmentExpression>[];
5+
6+
Iterable<AssignmentExpression> get expressions => _expressions;
7+
8+
@override
9+
void visitClassDeclaration(ClassDeclaration node) {
10+
super.visitClassDeclaration(node);
11+
12+
final type = node.extendsClause?.superclass.type;
13+
if (type == null || !isComponentOrSubclass(type)) {
14+
return;
15+
}
16+
17+
final onMountMethod = node.members.firstWhereOrNull((member) =>
18+
member is MethodDeclaration &&
19+
member.name.lexeme == 'onMount' &&
20+
isOverride(member.metadata));
21+
22+
if (onMountMethod is MethodDeclaration) {
23+
final visitor = _AssignmentExpressionVisitor();
24+
onMountMethod.visitChildren(visitor);
25+
26+
_expressions.addAll(visitor.wrongAssignments);
27+
}
28+
}
29+
}
30+
31+
class _AssignmentExpressionVisitor extends RecursiveAstVisitor<void> {
32+
final wrongAssignments = <AssignmentExpression>{};
33+
34+
@override
35+
void visitAssignmentExpression(AssignmentExpression node) {
36+
super.visitAssignmentExpression(node);
37+
38+
final element = node.writeElement;
39+
if (element is PropertyAccessorElement &&
40+
element.variable.isFinal &&
41+
element.variable.isLate) {
42+
wrongAssignments.add(node);
43+
}
44+
}
45+
}

lib/src/utils/flame_type_utils.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import 'package:analyzer/dart/element/type.dart';
2+
3+
bool isComponentOrSubclass(DartType? type) =>
4+
_isComponent(type) || _isSubclassOfComponent(type);
5+
6+
bool _isComponent(DartType? type) =>
7+
type?.getDisplayString(withNullability: false) == 'Component';
8+
9+
bool _isSubclassOfComponent(DartType? type) =>
10+
type is InterfaceType && type.allSupertypes.any(_isComponent);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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/avoid_initializing_in_on_mount/avoid_initializing_in_on_mount_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'avoid_initializing_in_on_mount/examples/example.dart';
8+
9+
void main() {
10+
group('AvoidInitializingInOnMountRule', () {
11+
test('initialization', () async {
12+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
13+
final issues = AvoidInitializingInOnMountRule().check(unit);
14+
15+
RuleTestHelper.verifyInitialization(
16+
issues: issues,
17+
ruleId: 'avoid-initializing-in-on-mount',
18+
severity: Severity.warning,
19+
);
20+
});
21+
22+
test('reports about found issues', () async {
23+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
24+
final issues = AvoidInitializingInOnMountRule().check(unit);
25+
26+
RuleTestHelper.verifyIssues(
27+
issues: issues,
28+
startLines: [8],
29+
startColumns: [5],
30+
locationTexts: ['x = 1'],
31+
messages: [
32+
'Avoid initializing final late variables in onMount.',
33+
],
34+
);
35+
});
36+
});
37+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class MyComponent extends Component {
2+
late final int x;
3+
4+
int y;
5+
6+
@override
7+
void onMount() {
8+
x = 1; // LINT
9+
y = 2;
10+
}
11+
}
12+
13+
class Component {
14+
void onMount() {}
15+
}

0 commit comments

Comments
 (0)