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

Commit be6a8f5

Browse files
committed
feat: add prefer-define-hero-tag rule
1 parent 14ffd46 commit be6a8f5

File tree

6 files changed

+229
-0
lines changed

6 files changed

+229
-0
lines changed

CHANGELOG.md

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

55
* fix: correctly support dartdoc tags for [`format-comment`](https://dcm.dev/docs/individuals/rules/common/format-comment).
6+
* feat: add static code diagnostic [prefer-define-hero-tag](https://github.com/dart-code-checker/dart-code-metrics/issues/1027).
67

78
## 5.6.0-dev.1
89

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import 'rules_list/prefer_correct_edge_insets_constructor/prefer_correct_edge_in
5959
import 'rules_list/prefer_correct_identifier_length/prefer_correct_identifier_length_rule.dart';
6060
import 'rules_list/prefer_correct_test_file_name/prefer_correct_test_file_name_rule.dart';
6161
import 'rules_list/prefer_correct_type_name/prefer_correct_type_name_rule.dart';
62+
import 'rules_list/prefer_define_hero_tag/prefer_define_hero_tag_rule.dart';
6263
import 'rules_list/prefer_enums_by_name/prefer_enums_by_name_rule.dart';
6364
import 'rules_list/prefer_extracting_callbacks/prefer_extracting_callbacks_rule.dart';
6465
import 'rules_list/prefer_first/prefer_first_rule.dart';
@@ -149,6 +150,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
149150
PreferCorrectIdentifierLengthRule.new,
150151
PreferCorrectTestFileNameRule.ruleId: PreferCorrectTestFileNameRule.new,
151152
PreferCorrectTypeNameRule.ruleId: PreferCorrectTypeNameRule.new,
153+
PreferDefineHeroTagRule.ruleId: PreferDefineHeroTagRule.new,
152154
PreferEnumsByNameRule.ruleId: PreferEnumsByNameRule.new,
153155
PreferExtractingCallbacksRule.ruleId: PreferExtractingCallbacksRule.new,
154156
PreferFirstRule.ruleId: PreferFirstRule.new,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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/severity.dart';
11+
import '../../models/flutter_rule.dart';
12+
import '../../rule_utils.dart';
13+
14+
part 'visitor.dart';
15+
16+
class PreferDefineHeroTagRule extends FlutterRule {
17+
static const ruleId = 'prefer-define-hero-tag';
18+
static const _issueMessage = 'Prefer define heroTag property.';
19+
20+
PreferDefineHeroTagRule([Map<String, Object> config = const {}])
21+
: super(
22+
id: ruleId,
23+
severity: readSeverity(config, Severity.performance),
24+
excludes: readExcludes(config),
25+
includes: readIncludes(config),
26+
);
27+
28+
@override
29+
Iterable<Issue> check(InternalResolvedUnitResult source) {
30+
final visitor = _Visitor();
31+
source.unit.visitChildren(visitor);
32+
33+
return visitor.invocations
34+
.map((invocation) => createIssue(
35+
rule: this,
36+
location: nodeLocation(node: invocation, source: source),
37+
message: _issueMessage,
38+
))
39+
.toList(growable: false);
40+
}
41+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
part of 'prefer_define_hero_tag_rule.dart';
2+
3+
const _floatingActionButtonClassName = 'FloatingActionButton';
4+
const _constructorExtendedName = 'extended';
5+
const _constructorLargeName = 'large';
6+
const _constructorSmallName = 'small';
7+
const _heroTagPropertyName = 'heroTag';
8+
9+
class _Visitor extends RecursiveAstVisitor<void> {
10+
final _invocations = <MethodInvocation>[];
11+
12+
Iterable<MethodInvocation> get invocations => _invocations;
13+
14+
@override
15+
void visitMethodInvocation(MethodInvocation node) {
16+
super.visitMethodInvocation(node);
17+
18+
final methodName = node.methodName.name;
19+
if (methodName == _floatingActionButtonClassName ||
20+
node.beginToken.lexeme == _floatingActionButtonClassName &&
21+
(methodName == _constructorExtendedName ||
22+
methodName == _constructorLargeName ||
23+
methodName == _constructorSmallName)) {
24+
if (!node.argumentList.arguments
25+
.cast<NamedExpression>()
26+
.any((arg) => arg.name.label.name == _heroTagPropertyName)) {
27+
_invocations.add(node);
28+
}
29+
}
30+
}
31+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
class MyWidget extends StatelessWidget {
2+
const MyWidget({super.key});
3+
4+
@override
5+
Widget build(BuildContext context) => Scaffold(
6+
floatingActionButton: FloatingActionButton(
7+
onPressed: () {},
8+
),
9+
);
10+
}
11+
12+
class MyWidget2 extends StatelessWidget {
13+
const MyWidget2({super.key});
14+
15+
@override
16+
Widget build(BuildContext context) => Scaffold(
17+
floatingActionButton: FloatingActionButton.extended(
18+
label: const Text('label'),
19+
onPressed: () {},
20+
),
21+
);
22+
}
23+
24+
class MyWidget3 extends StatelessWidget {
25+
const MyWidget3({super.key});
26+
27+
@override
28+
Widget build(BuildContext context) => Scaffold(
29+
floatingActionButton: FloatingActionButton.large(
30+
onPressed: () {},
31+
),
32+
);
33+
}
34+
35+
class MyWidget4 extends StatelessWidget {
36+
const MyWidget4({super.key});
37+
38+
@override
39+
Widget build(BuildContext context) => Scaffold(
40+
floatingActionButton: FloatingActionButton.small(
41+
onPressed: () {},
42+
),
43+
);
44+
}
45+
46+
class MyWidget6 extends StatelessWidget {
47+
const MyWidget6({super.key});
48+
49+
@override
50+
Widget build(BuildContext context) => Scaffold(
51+
floatingActionButton: FloatingActionButton(
52+
heroTag: 'heroTag',
53+
onPressed: () {},
54+
),
55+
);
56+
}
57+
58+
class MyWidget7 extends StatelessWidget {
59+
const MyWidget7({super.key});
60+
61+
@override
62+
Widget build(BuildContext context) => Scaffold(
63+
floatingActionButton: FloatingActionButton.extended(
64+
heroTag: 'heroTag',
65+
label: const Text('label'),
66+
onPressed: () {},
67+
),
68+
);
69+
}
70+
71+
class MyWidget8 extends StatelessWidget {
72+
const MyWidget8({super.key});
73+
74+
@override
75+
Widget build(BuildContext context) => Scaffold(
76+
floatingActionButton: FloatingActionButton.large(
77+
heroTag: 'heroTag',
78+
onPressed: () {},
79+
),
80+
);
81+
}
82+
83+
class MyWidget9 extends StatelessWidget {
84+
const MyWidget9({super.key});
85+
86+
@override
87+
Widget build(BuildContext context) => Scaffold(
88+
floatingActionButton: FloatingActionButton.small(
89+
heroTag: 'heroTag',
90+
onPressed: () {},
91+
),
92+
);
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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_define_hero_tag/prefer_define_hero_tag_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'prefer_define_hero_tag/examples/example.dart';
8+
9+
void main() {
10+
group(
11+
'PreferDefineHeroTagRule',
12+
() {
13+
test('initialization', () async {
14+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
15+
final issues = PreferDefineHeroTagRule().check(unit);
16+
17+
RuleTestHelper.verifyInitialization(
18+
issues: issues,
19+
ruleId: 'prefer-define-hero-tag',
20+
severity: Severity.performance,
21+
);
22+
});
23+
24+
test('reports about found issues', () async {
25+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
26+
final issues = PreferDefineHeroTagRule().check(unit);
27+
28+
RuleTestHelper.verifyIssues(
29+
issues: issues,
30+
startLines: [6, 17, 29, 40],
31+
startColumns: [31, 31, 31, 31],
32+
messages: [
33+
'Prefer define heroTag property.',
34+
'Prefer define heroTag property.',
35+
'Prefer define heroTag property.',
36+
'Prefer define heroTag property.',
37+
],
38+
locationTexts: [
39+
'''
40+
FloatingActionButton(
41+
onPressed: () {},
42+
)''',
43+
'''
44+
FloatingActionButton.extended(
45+
label: const Text('label'),
46+
onPressed: () {},
47+
)''',
48+
'''
49+
FloatingActionButton.large(
50+
onPressed: () {},
51+
)''',
52+
'''
53+
FloatingActionButton.small(
54+
onPressed: () {},
55+
)''',
56+
],
57+
);
58+
});
59+
},
60+
);
61+
}

0 commit comments

Comments
 (0)