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

Commit 9fd3b58

Browse files
feat: add avoid-top-level-members-in-tests (#920)
Co-authored-by: Dmitry Krutskikh <dmitry.krutskikh@gmail.com>
1 parent b6a3946 commit 9fd3b58

File tree

8 files changed

+249
-0
lines changed

8 files changed

+249
-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
* feat: add static code diagnostic [`avoid-duplicate-exports`](https://dartcodemetrics.dev/docs/rules/common/avoid-duplicate-exports).
6+
* feat: add static code diagnostic [`avoid-top-level-members-in-tests`](https://dartcodemetrics.dev/docs/rules/common/avoid-top-level-members-in-tests).
67
* feat: add static code diagnostic [`prefer-enums-by-name`](https://dartcodemetrics.dev/docs/rules/common/prefer-enums-by-name).
78

89
## 4.17.0-dev.1

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'rules_list/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
1616
import 'rules_list/avoid_preserve_whitespace_false/avoid_preserve_whitespace_false_rule.dart';
1717
import 'rules_list/avoid_returning_widgets/avoid_returning_widgets_rule.dart';
1818
import 'rules_list/avoid_throw_in_catch_block/avoid_throw_in_catch_block_rule.dart';
19+
import 'rules_list/avoid_top_level_members_in_tests/avoid_top_level_members_in_tests_rule.dart';
1920
import 'rules_list/avoid_unnecessary_setstate/avoid_unnecessary_setstate_rule.dart';
2021
import 'rules_list/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart';
2122
import 'rules_list/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart';
@@ -76,6 +77,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
7677
AvoidPreserveWhitespaceFalseRule.ruleId: AvoidPreserveWhitespaceFalseRule.new,
7778
AvoidReturningWidgetsRule.ruleId: AvoidReturningWidgetsRule.new,
7879
AvoidThrowInCatchBlockRule.ruleId: AvoidThrowInCatchBlockRule.new,
80+
AvoidTopLevelMembersInTestsRule.ruleId: AvoidTopLevelMembersInTestsRule.new,
7981
AvoidUnnecessarySetStateRule.ruleId: AvoidUnnecessarySetStateRule.new,
8082
AvoidUnnecessaryTypeAssertionsRule.ruleId:
8183
AvoidUnnecessaryTypeAssertionsRule.new,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 'visitor.dart';
15+
16+
class AvoidTopLevelMembersInTestsRule extends CommonRule {
17+
static const String ruleId = 'avoid-top-level-members-in-tests';
18+
19+
static const _warning = 'Avoid declaring top-level members in tests.';
20+
21+
AvoidTopLevelMembersInTestsRule([Map<String, Object> config = const {}])
22+
: super(
23+
id: ruleId,
24+
severity: readSeverity(config, Severity.warning),
25+
excludes: ['!test/**'],
26+
);
27+
28+
@override
29+
Iterable<Issue> check(InternalResolvedUnitResult source) {
30+
final visitor = _Visitor();
31+
32+
source.unit.visitChildren(visitor);
33+
34+
return visitor.declarations
35+
.map((declaration) => createIssue(
36+
rule: this,
37+
location: nodeLocation(
38+
node: declaration,
39+
source: source,
40+
),
41+
message: _warning,
42+
))
43+
.toList(growable: false);
44+
}
45+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
part of 'avoid_top_level_members_in_tests_rule.dart';
2+
3+
class _Visitor extends GeneralizingAstVisitor<void> {
4+
final _declarations = <AstNode>[];
5+
6+
Iterable<AstNode> get declarations => _declarations;
7+
8+
@override
9+
void visitClassDeclaration(ClassDeclaration node) {
10+
if (!Identifier.isPrivateName(node.name.name)) {
11+
_declarations.add(node);
12+
}
13+
}
14+
15+
@override
16+
void visitMixinDeclaration(MixinDeclaration node) {
17+
if (!Identifier.isPrivateName(node.name.name)) {
18+
_declarations.add(node);
19+
}
20+
}
21+
22+
@override
23+
void visitExtensionDeclaration(ExtensionDeclaration node) {
24+
final name = node.name?.name;
25+
if (name != null && !Identifier.isPrivateName(name)) {
26+
_declarations.add(node);
27+
}
28+
}
29+
30+
@override
31+
void visitEnumDeclaration(EnumDeclaration node) {
32+
if (!Identifier.isPrivateName(node.name.name)) {
33+
_declarations.add(node);
34+
}
35+
}
36+
37+
@override
38+
void visitTypeAlias(TypeAlias node) {
39+
if (!Identifier.isPrivateName(node.name.name)) {
40+
_declarations.add(node);
41+
}
42+
}
43+
44+
@override
45+
void visitFunctionDeclaration(FunctionDeclaration node) {
46+
final name = node.name.name;
47+
if (isEntrypoint(name, node.metadata)) {
48+
return;
49+
}
50+
51+
if (!Identifier.isPrivateName(name)) {
52+
_declarations.add(node);
53+
}
54+
}
55+
56+
@override
57+
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
58+
final variables = node.variables.variables;
59+
60+
if (variables.isNotEmpty &&
61+
!Identifier.isPrivateName(variables.first.name.name)) {
62+
_declarations.add(variables.first);
63+
}
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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_top_level_members_in_tests/avoid_top_level_members_in_tests_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'avoid_top_level_members_in_tests/examples/example.dart';
8+
9+
void main() {
10+
group('AvoidTopLevelMembersInTestsRule', () {
11+
test('initialization', () async {
12+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
13+
final issues = AvoidTopLevelMembersInTestsRule().check(unit);
14+
15+
RuleTestHelper.verifyInitialization(
16+
issues: issues,
17+
ruleId: 'avoid-top-level-members-in-tests',
18+
severity: Severity.warning,
19+
);
20+
});
21+
22+
test('reports about found issues', () async {
23+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
24+
final issues = AvoidTopLevelMembersInTestsRule().check(unit);
25+
26+
RuleTestHelper.verifyIssues(
27+
issues: issues,
28+
startLines: [1, 6, 10, 14, 18, 22, 26],
29+
startColumns: [7, 1, 1, 1, 1, 1, 1],
30+
locationTexts: [
31+
'public = 1',
32+
'void function() {}',
33+
'class Class {}',
34+
'mixin Mixin {}',
35+
'extension Extension on String {}',
36+
'enum Enum { first, second }',
37+
'typedef Public = String;',
38+
],
39+
messages: [
40+
'Avoid declaring top-level members in tests.',
41+
'Avoid declaring top-level members in tests.',
42+
'Avoid declaring top-level members in tests.',
43+
'Avoid declaring top-level members in tests.',
44+
'Avoid declaring top-level members in tests.',
45+
'Avoid declaring top-level members in tests.',
46+
'Avoid declaring top-level members in tests.',
47+
],
48+
);
49+
});
50+
});
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
final public = 1; // LINT
2+
final _private = 2;
3+
4+
void main() {}
5+
6+
void function() {} // LINT
7+
8+
void _function() {}
9+
10+
class Class {} // LINT
11+
12+
class _Class {}
13+
14+
mixin Mixin {} // LINT
15+
16+
mixin _Mixin {}
17+
18+
extension Extension on String {} // LINT
19+
20+
extension _Extension on String {}
21+
22+
enum Enum { first, second } // LINT
23+
24+
enum _Enum { first, second }
25+
26+
typedef Public = String; // LINT
27+
28+
typedef _Private = String;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Avoid top-level members in tests
2+
3+
## Rule id {#rule-id}
4+
5+
avoid-top-level-members-in-tests
6+
7+
## Severity {#severity}
8+
9+
Warning
10+
11+
## Description {#description}
12+
13+
Warns when a public top-level member (expect the entrypoint) is declared inside a test file.
14+
15+
It helps reduce code bloat and find unused declarations in test files.
16+
17+
### Example {#example}
18+
19+
Bad:
20+
21+
```dart
22+
final public = 1; // LINT
23+
24+
void function() {} // LINT
25+
26+
class Class {} // LINT
27+
28+
mixin Mixin {} // LINT
29+
30+
extension Extension on String {} // LINT
31+
32+
enum Enum { first, second } // LINT
33+
34+
typedef Public = String; // LINT
35+
```
36+
37+
Good:
38+
39+
```dart
40+
final _private = 2;
41+
42+
void _function() {}
43+
44+
class _Class {}
45+
46+
mixin _Mixin {}
47+
48+
extension _Extension on String {}
49+
50+
enum _Enum { first, second }
51+
52+
typedef _Private = String;
53+
```

website/docs/rules/overview.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ Rules configuration is [described here](../getting-started/configuration#configu
5959

6060
Warns when call `throw` in a catch block.
6161

62+
- [avoid-top-level-members-in-tests](./common/avoid-top-level-members-in-tests.md)
63+
64+
Warns when a public top-level member (expect the entrypoint) is declared inside a test file.
65+
6266
- [avoid-unnecessary-type-assertions](./common/avoid-unnecessary-type-assertions.md)
6367

6468
Warns about unnecessary usage of 'is' and 'whereType' operators.

0 commit comments

Comments
 (0)