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

Commit 1d8e535

Browse files
feat: add static code diagnostic prefer-correct-test-file-name (#1000)
* feat: add static code diagnostic prefer-correct-test-file-name * chore: fix admotion * chore: replace usage of name2 with name Co-authored-by: Dmitry Krutskikh <[email protected]>
1 parent a254d6e commit 1d8e535

File tree

14 files changed

+210
-1
lines changed

14 files changed

+210
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
* feat: add static code diagnostic [`prefer-correct-test-file-name`](https://dartcodemetrics.dev/docs/rules/common/prefer-correct-test-file-name).
56
* feat: add static code diagnostic [`prefer-iterable-of`](https://dartcodemetrics.dev/docs/rules/common/prefer-iterable-of).
67

78
## 4.18.3

lib/src/analyzers/lint_analyzer/lint_utils.dart

+6
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,11 @@ Iterable<String> readExcludes(Map<String, Object> config) {
1313
: const <String>[];
1414
}
1515

16+
bool hasExcludes(Map<String, Object> config) {
17+
final data = config['exclude'];
18+
19+
return _isIterableOfStrings(data);
20+
}
21+
1622
bool _isIterableOfStrings(Object? object) =>
1723
object is Iterable<Object> && object.every((node) => node is String);

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

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import 'rules_list/prefer_conditional_expressions/prefer_conditional_expressions
4545
import 'rules_list/prefer_const_border_radius/prefer_const_border_radius_rule.dart';
4646
import 'rules_list/prefer_correct_edge_insets_constructor/prefer_correct_edge_insets_constructor_rule.dart';
4747
import 'rules_list/prefer_correct_identifier_length/prefer_correct_identifier_length_rule.dart';
48+
import 'rules_list/prefer_correct_test_file_name/prefer_correct_test_file_name_rule.dart';
4849
import 'rules_list/prefer_correct_type_name/prefer_correct_type_name_rule.dart';
4950
import 'rules_list/prefer_enums_by_name/prefer_enums_by_name_rule.dart';
5051
import 'rules_list/prefer_extracting_callbacks/prefer_extracting_callbacks_rule.dart';
@@ -116,6 +117,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
116117
PreferCorrectEdgeInsetsConstructorRule.new,
117118
PreferCorrectIdentifierLengthRule.ruleId:
118119
PreferCorrectIdentifierLengthRule.new,
120+
PreferCorrectTestFileNameRule.ruleId: PreferCorrectTestFileNameRule.new,
119121
PreferCorrectTypeNameRule.ruleId: PreferCorrectTypeNameRule.new,
120122
PreferEnumsByNameRule.ruleId: PreferEnumsByNameRule.new,
121123
PreferExtractingCallbacksRule.ruleId: PreferExtractingCallbacksRule.new,

lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_top_level_members_in_tests/avoid_top_level_members_in_tests_rule.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class AvoidTopLevelMembersInTestsRule extends CommonRule {
2222
: super(
2323
id: ruleId,
2424
severity: readSeverity(config, Severity.warning),
25-
excludes: ['/**', '!test/**'],
25+
excludes:
26+
hasExcludes(config) ? readExcludes(config) : ['/**', '!test/**'],
2627
);
2728

2829
@override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
part of 'prefer_correct_test_file_name_rule.dart';
2+
3+
class _ConfigParser {
4+
static const _namePatternConfig = 'name-pattern';
5+
6+
static String parseNamePattern(Map<String, Object> config) {
7+
final raw = config[_namePatternConfig];
8+
9+
return raw is String ? raw : '_test.dart';
10+
}
11+
}
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+
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/common_rule.dart';
12+
import '../../rule_utils.dart';
13+
14+
part 'config_parser.dart';
15+
part 'visitor.dart';
16+
17+
class PreferCorrectTestFileNameRule extends CommonRule {
18+
static const String ruleId = 'prefer-correct-test-file-name';
19+
20+
static const _warningMessage = 'Test file name should end with ';
21+
22+
final String _fileNamePattern;
23+
24+
PreferCorrectTestFileNameRule([Map<String, Object> config = const {}])
25+
: _fileNamePattern = _ConfigParser.parseNamePattern(config),
26+
super(
27+
id: ruleId,
28+
severity: readSeverity(config, Severity.warning),
29+
excludes:
30+
hasExcludes(config) ? readExcludes(config) : ['/**', '!test/**'],
31+
);
32+
33+
@override
34+
Iterable<Issue> check(InternalResolvedUnitResult source) {
35+
final visitor = _Visitor(source.path, _fileNamePattern);
36+
37+
source.unit.visitChildren(visitor);
38+
39+
return visitor.declaration
40+
.map((declaration) => createIssue(
41+
rule: this,
42+
location: nodeLocation(node: declaration, source: source),
43+
message: '$_warningMessage$_fileNamePattern',
44+
))
45+
.toList(growable: false);
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
part of 'prefer_correct_test_file_name_rule.dart';
2+
3+
class _Visitor extends GeneralizingAstVisitor<void> {
4+
final String path;
5+
final String pattern;
6+
7+
final _declarations = <FunctionDeclaration>[];
8+
9+
Iterable<FunctionDeclaration> get declaration => _declarations;
10+
11+
_Visitor(this.path, this.pattern);
12+
13+
@override
14+
void visitFunctionDeclaration(FunctionDeclaration node) {
15+
// ignore: deprecated_member_use
16+
if (node.name.name != 'main' || _matchesTestName(path)) {
17+
return;
18+
}
19+
20+
_declarations.add(node);
21+
}
22+
23+
bool _matchesTestName(String path) => path.endsWith(pattern);
24+
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class PreferMatchFileNameRule extends CommonRule {
2929
@override
3030
Iterable<Issue> check(InternalResolvedUnitResult source) {
3131
final visitor = _Visitor();
32+
3233
source.unit.visitChildren(visitor);
3334

3435
final issues = <Issue>[];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
void main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// LINT
2+
void main() {
3+
print('Hello');
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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_correct_test_file_name/prefer_correct_test_file_name_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'prefer_correct_test_file_name/examples/example.dart';
8+
const _correctExamplePath =
9+
'prefer_correct_test_file_name/examples/correct_example.dart';
10+
11+
void main() {
12+
group('PreferCorrectTestFileNameRule', () {
13+
test('initialization', () async {
14+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
15+
final issues = PreferCorrectTestFileNameRule().check(unit);
16+
17+
RuleTestHelper.verifyInitialization(
18+
issues: issues,
19+
ruleId: 'prefer-correct-test-file-name',
20+
severity: Severity.warning,
21+
);
22+
});
23+
24+
test('reports about found issues', () async {
25+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
26+
final config = {'name-pattern': 'correct_example.dart'};
27+
28+
final issues = PreferCorrectTestFileNameRule(config).check(unit);
29+
30+
RuleTestHelper.verifyIssues(
31+
issues: issues,
32+
startLines: [2],
33+
startColumns: [1],
34+
locationTexts: [
35+
'void main() {\n'
36+
" print('Hello');\n"
37+
'}',
38+
],
39+
messages: [
40+
'Test file name should end with correct_example.dart',
41+
],
42+
);
43+
});
44+
45+
test('reports no found issues', () async {
46+
final unit = await RuleTestHelper.resolveFromFile(_correctExamplePath);
47+
final config = {'name-pattern': 'correct_example.dart'};
48+
49+
final issues = PreferCorrectTestFileNameRule(config).check(unit);
50+
51+
RuleTestHelper.verifyNoIssues(issues);
52+
});
53+
});
54+
}

website/docs/rules/common/avoid-top-level-members-in-tests.mdx

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ Warns when a public top-level member (expect the entrypoint) is declared inside
66

77
It helps reduce code bloat and find unused declarations in test files.
88

9+
::: note
10+
11+
If you want to set `exclude` config for this rule, the default `['/**', '!test/**']` will be overriden.
12+
13+
:::
14+
915
### Example {#example}
1016

1117
Bad:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import RuleDetails from '@site/src/components/RuleDetails';
2+
3+
<RuleDetails version="4.19.0" severity="warning" />
4+
5+
Warns if the file within `/test` contains a `main`, but the file name doesn't end with `_test.dart`.
6+
7+
:::note
8+
9+
If you want to set `exclude` config for this rule, the default `['/**', '!test/**']` will be overriden.
10+
11+
:::
12+
13+
### Example {#example}
14+
15+
**❌ Bad:**
16+
17+
File name: **some_file.dart**
18+
19+
```dart
20+
void main() {
21+
...
22+
}
23+
```
24+
25+
**✅ Good:**
26+
27+
File name: **some_file_test.dart**
28+
29+
```dart
30+
void main() {
31+
...
32+
}
33+
```
34+
35+
File name: **some_other_file.dart**
36+
37+
```dart
38+
void helperFunction() {
39+
...
40+
}
41+
```

website/docs/rules/index.mdx

+10
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,16 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
355355
Warns when an identifier name length is very short or long.
356356
</RuleEntry>
357357

358+
<RuleEntry
359+
name="prefer-correct-test-file-name"
360+
type="common"
361+
severity="warning"
362+
version="4.19.0"
363+
>
364+
Warns if the file within <code>/test</code> contains a <code>main</code>, but
365+
the file name doesn't end with <code>_test.dart</code>.
366+
</RuleEntry>
367+
358368
<RuleEntry
359369
name="prefer-correct-type-name"
360370
type="common"

0 commit comments

Comments
 (0)