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

Commit e743903

Browse files
authored
feat: support mixins, extensions and enums for prefer-match-file-name (#573)
* feat: add support mixins, extensions and enums for `prefer-match-file-name` rule. * chore: add missed calls super functions
1 parent dfb85a3 commit e743903

File tree

11 files changed

+138
-23
lines changed

11 files changed

+138
-23
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
* feat: add alphabetical sorting by type for `member-ordering-extended` rule.
6+
* feat: add support mixins, extensions and enums for `prefer-match-file-name` rule.
67
* fix: prefer conditional expressions rule breaks code with increment / decrement operators
78

89
## 4.7.0
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import 'package:analyzer/dart/ast/ast.dart';
2+
3+
String humanReadableNodeType(AstNode? node) {
4+
if (node is ClassDeclaration) {
5+
return 'Class';
6+
} else if (node is EnumDeclaration) {
7+
return 'Enum';
8+
} else if (node is ExtensionDeclaration) {
9+
return 'Extension';
10+
} else if (node is MixinDeclaration) {
11+
return 'Mixin';
12+
}
13+
14+
return 'Node';
15+
}

lib/src/analyzers/lint_analyzer/rules/rules_list/avoid-unnecessary-type-casts/visitor.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class _Visitor extends RecursiveAstVisitor<void> {
77

88
@override
99
void visitAsExpression(AsExpression node) {
10+
super.visitAsExpression(node);
11+
1012
final objectType = node.expression.staticType;
1113
final castedType = node.type.type;
1214
if (_isUselessTypeCheck(objectType, castedType)) {

lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_unnecessary_type_assertions/visitor.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class _Visitor extends RecursiveAstVisitor<void> {
3636

3737
@override
3838
void visitIsExpression(IsExpression node) {
39+
super.visitIsExpression(node);
40+
3941
final objectType = node.expression.staticType;
4042
final castedType = node.type.type;
4143
if (_isUselessTypeCheck(objectType, castedType)) {

lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_match_file_name/prefer_match_file_name_rule.dart

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,21 @@
22

33
import 'package:analyzer/dart/ast/ast.dart';
44
import 'package:analyzer/dart/ast/visitor.dart';
5-
import 'package:path/path.dart';
5+
import 'package:path/path.dart' as p;
66

77
import '../../../../../utils/node_utils.dart';
88
import '../../../lint_utils.dart';
99
import '../../../models/internal_resolved_unit_result.dart';
1010
import '../../../models/issue.dart';
1111
import '../../../models/severity.dart';
1212
import '../../models/common_rule.dart';
13+
import '../../node_utils.dart';
1314
import '../../rule_utils.dart';
1415

1516
part 'visitor.dart';
1617

1718
class PreferMatchFileNameRule extends CommonRule {
1819
static const String ruleId = 'prefer-match-file-name';
19-
static const _notMatchNameFailure =
20-
'File name does not match with first class name.';
2120
static final _onlySymbolsRegex = RegExp('[^a-zA-Z0-9]');
2221

2322
PreferMatchFileNameRule([Map<String, Object> config = const {}])
@@ -27,18 +26,6 @@ class PreferMatchFileNameRule extends CommonRule {
2726
excludes: readExcludes(config),
2827
);
2928

30-
bool _hasMatchName(String path, String className) {
31-
final classNameFormatted =
32-
className.replaceAll(_onlySymbolsRegex, '').toLowerCase();
33-
34-
return classNameFormatted ==
35-
basename(path)
36-
.split('.')
37-
.first
38-
.replaceAll(_onlySymbolsRegex, '')
39-
.toLowerCase();
40-
}
41-
4229
@override
4330
Iterable<Issue> check(InternalResolvedUnitResult source) {
4431
final visitor = _Visitor();
@@ -48,19 +35,32 @@ class PreferMatchFileNameRule extends CommonRule {
4835

4936
if (visitor.declaration.isNotEmpty &&
5037
!_hasMatchName(source.path, visitor.declaration.first.name)) {
38+
final node = visitor.declaration.first;
39+
final nodeType = humanReadableNodeType(node.parent).toLowerCase();
40+
5141
final issue = createIssue(
5242
rule: this,
53-
location: nodeLocation(
54-
node: visitor.declaration.first,
55-
source: source,
56-
withCommentOrMetadata: true,
57-
),
58-
message: _notMatchNameFailure,
43+
location: nodeLocation(node: node, source: source),
44+
message: 'File name does not match with first $nodeType name.',
5945
);
6046

6147
_issue.add(issue);
6248
}
6349

6450
return _issue;
6551
}
52+
53+
bool _hasMatchName(String path, String identifierName) {
54+
final identifierNameFormatted =
55+
identifierName.replaceAll(_onlySymbolsRegex, '').toLowerCase();
56+
57+
final fileNameFormatted = p
58+
.basename(path)
59+
.split('.')
60+
.first
61+
.replaceAll(_onlySymbolsRegex, '')
62+
.toLowerCase();
63+
64+
return identifierNameFormatted == fileNameFormatted;
65+
}
6666
}

lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_match_file_name/visitor.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,30 @@ class _Visitor extends RecursiveAstVisitor<void> {
1313
_declarations.add(node.name);
1414
}
1515

16+
@override
17+
void visitExtensionDeclaration(ExtensionDeclaration node) {
18+
super.visitExtensionDeclaration(node);
19+
20+
final name = node.name;
21+
if (name != null) {
22+
_declarations.add(name);
23+
}
24+
}
25+
26+
@override
27+
void visitMixinDeclaration(MixinDeclaration node) {
28+
super.visitMixinDeclaration(node);
29+
30+
_declarations.add(node.name);
31+
}
32+
33+
@override
34+
void visitEnumDeclaration(EnumDeclaration node) {
35+
super.visitEnumDeclaration(node);
36+
37+
_declarations.add(node.name);
38+
}
39+
1640
int _compareByPrivateType(SimpleIdentifier a, SimpleIdentifier b) {
1741
final isAPrivate = Identifier.isPrivateName(a.name);
1842
final isBPrivate = Identifier.isPrivateName(b.name);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
enum _MultiEnum { a, b, c }
2+
3+
enum MultipleEnumExample { a, b, c }
4+
5+
enum Test { a, b, c }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
extension _MultiExtension on String {}
2+
3+
extension MultipleExtensionExample on String {}
4+
5+
extension Test on String {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mixin _MultiMixin {}
2+
3+
mixin MultipleMixinExample {}
4+
5+
mixin Test {}

test/src/analyzers/lint_analyzer/rules/rules_list/prefer_match_file_name/prefer_match_file_name_rule_test.dart

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ const _withIssue = '$_examplePath/example_with_issue.dart';
1212
const _emptyFile = '$_examplePath/empty_file.dart';
1313
const _privateClass = '$_examplePath/private_class.dart';
1414
const _multiClass = '$_examplePath/multiple_classes_example.dart';
15+
const _multipleEnums = '$_examplePath/multiple_enums.dart';
16+
const _multipleExtensions = '$_examplePath/multiple_extensions.dart';
17+
const _multipleMixins = '$_examplePath/multiple_mixins.dart';
1518
const _codegenFile = '$_examplePath/some_widget.codegen.dart';
1619

1720
void main() {
@@ -77,6 +80,60 @@ void main() {
7780
RuleTestHelper.verifyNoIssues(issues);
7881
});
7982

83+
test(
84+
'reports about found issue about incorrect file name with enums',
85+
() async {
86+
final unit = await RuleTestHelper.resolveFromFile(_multipleEnums);
87+
final issues = PreferMatchFileNameRule().check(unit);
88+
89+
RuleTestHelper.verifyIssues(
90+
issues: issues,
91+
startOffsets: [34],
92+
startLines: [3],
93+
startColumns: [6],
94+
endOffsets: [53],
95+
messages: ['File name does not match with first enum name.'],
96+
locationTexts: ['MultipleEnumExample'],
97+
);
98+
},
99+
);
100+
101+
test(
102+
'reports about found issue about incorrect file name with extensions',
103+
() async {
104+
final unit = await RuleTestHelper.resolveFromFile(_multipleExtensions);
105+
final issues = PreferMatchFileNameRule().check(unit);
106+
107+
RuleTestHelper.verifyIssues(
108+
issues: issues,
109+
startOffsets: [50],
110+
startLines: [3],
111+
startColumns: [11],
112+
endOffsets: [74],
113+
messages: ['File name does not match with first extension name.'],
114+
locationTexts: ['MultipleExtensionExample'],
115+
);
116+
},
117+
);
118+
119+
test(
120+
'reports about found issue about incorrect file name with mixins',
121+
() async {
122+
final unit = await RuleTestHelper.resolveFromFile(_multipleMixins);
123+
final issues = PreferMatchFileNameRule().check(unit);
124+
125+
RuleTestHelper.verifyIssues(
126+
issues: issues,
127+
startOffsets: [28],
128+
startLines: [3],
129+
startColumns: [7],
130+
endOffsets: [48],
131+
messages: ['File name does not match with first mixin name.'],
132+
locationTexts: ['MultipleMixinExample'],
133+
);
134+
},
135+
);
136+
80137
test('reports no issues for codegen file', () async {
81138
final unit = await RuleTestHelper.resolveFromFile(_codegenFile);
82139
final issues = PreferMatchFileNameRule().check(unit);

website/docs/rules/common/prefer-match-file-name.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ Warning
1010

1111
## Description {#description}
1212

13-
Warns if the file name does not match the name of the first public class in the file or a private class if there are no
14-
public classes.
13+
Warns if the file name does not match the name of the first public class / mixin / extension / enum in the file or a private one if there are no public entries.
1514

1615
### Config example {#config-example}
1716

0 commit comments

Comments
 (0)