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

Commit

Permalink
feat: add static code diagnostic prefer-correct-test-file-name (#1000)
Browse files Browse the repository at this point in the history
* 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 <dmitry.krutskikh@gmail.com>
  • Loading branch information
incendial and dkrutskikh committed Sep 19, 2022
1 parent a254d6e commit 1d8e535
Show file tree
Hide file tree
Showing 14 changed files with 210 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

## Unreleased

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

## 4.18.3
Expand Down
6 changes: 6 additions & 0 deletions lib/src/analyzers/lint_analyzer/lint_utils.dart
Expand Up @@ -13,5 +13,11 @@ Iterable<String> readExcludes(Map<String, Object> config) {
: const <String>[];
}

bool hasExcludes(Map<String, Object> config) {
final data = config['exclude'];

return _isIterableOfStrings(data);
}

bool _isIterableOfStrings(Object? object) =>
object is Iterable<Object> && object.every((node) => node is String);
2 changes: 2 additions & 0 deletions lib/src/analyzers/lint_analyzer/rules/rules_factory.dart
Expand Up @@ -45,6 +45,7 @@ import 'rules_list/prefer_conditional_expressions/prefer_conditional_expressions
import 'rules_list/prefer_const_border_radius/prefer_const_border_radius_rule.dart';
import 'rules_list/prefer_correct_edge_insets_constructor/prefer_correct_edge_insets_constructor_rule.dart';
import 'rules_list/prefer_correct_identifier_length/prefer_correct_identifier_length_rule.dart';
import 'rules_list/prefer_correct_test_file_name/prefer_correct_test_file_name_rule.dart';
import 'rules_list/prefer_correct_type_name/prefer_correct_type_name_rule.dart';
import 'rules_list/prefer_enums_by_name/prefer_enums_by_name_rule.dart';
import 'rules_list/prefer_extracting_callbacks/prefer_extracting_callbacks_rule.dart';
Expand Down Expand Up @@ -116,6 +117,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
PreferCorrectEdgeInsetsConstructorRule.new,
PreferCorrectIdentifierLengthRule.ruleId:
PreferCorrectIdentifierLengthRule.new,
PreferCorrectTestFileNameRule.ruleId: PreferCorrectTestFileNameRule.new,
PreferCorrectTypeNameRule.ruleId: PreferCorrectTypeNameRule.new,
PreferEnumsByNameRule.ruleId: PreferEnumsByNameRule.new,
PreferExtractingCallbacksRule.ruleId: PreferExtractingCallbacksRule.new,
Expand Down
Expand Up @@ -22,7 +22,8 @@ class AvoidTopLevelMembersInTestsRule extends CommonRule {
: super(
id: ruleId,
severity: readSeverity(config, Severity.warning),
excludes: ['/**', '!test/**'],
excludes:
hasExcludes(config) ? readExcludes(config) : ['/**', '!test/**'],
);

@override
Expand Down
@@ -0,0 +1,11 @@
part of 'prefer_correct_test_file_name_rule.dart';

class _ConfigParser {
static const _namePatternConfig = 'name-pattern';

static String parseNamePattern(Map<String, Object> config) {
final raw = config[_namePatternConfig];

return raw is String ? raw : '_test.dart';
}
}
@@ -0,0 +1,47 @@
// ignore_for_file: public_member_api_docs

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';

import '../../../../../utils/node_utils.dart';
import '../../../lint_utils.dart';
import '../../../models/internal_resolved_unit_result.dart';
import '../../../models/issue.dart';
import '../../../models/severity.dart';
import '../../models/common_rule.dart';
import '../../rule_utils.dart';

part 'config_parser.dart';
part 'visitor.dart';

class PreferCorrectTestFileNameRule extends CommonRule {
static const String ruleId = 'prefer-correct-test-file-name';

static const _warningMessage = 'Test file name should end with ';

final String _fileNamePattern;

PreferCorrectTestFileNameRule([Map<String, Object> config = const {}])
: _fileNamePattern = _ConfigParser.parseNamePattern(config),
super(
id: ruleId,
severity: readSeverity(config, Severity.warning),
excludes:
hasExcludes(config) ? readExcludes(config) : ['/**', '!test/**'],
);

@override
Iterable<Issue> check(InternalResolvedUnitResult source) {
final visitor = _Visitor(source.path, _fileNamePattern);

source.unit.visitChildren(visitor);

return visitor.declaration
.map((declaration) => createIssue(
rule: this,
location: nodeLocation(node: declaration, source: source),
message: '$_warningMessage$_fileNamePattern',
))
.toList(growable: false);
}
}
@@ -0,0 +1,24 @@
part of 'prefer_correct_test_file_name_rule.dart';

class _Visitor extends GeneralizingAstVisitor<void> {
final String path;
final String pattern;

final _declarations = <FunctionDeclaration>[];

Iterable<FunctionDeclaration> get declaration => _declarations;

_Visitor(this.path, this.pattern);

@override
void visitFunctionDeclaration(FunctionDeclaration node) {
// ignore: deprecated_member_use
if (node.name.name != 'main' || _matchesTestName(path)) {
return;
}

_declarations.add(node);
}

bool _matchesTestName(String path) => path.endsWith(pattern);
}
Expand Up @@ -29,6 +29,7 @@ class PreferMatchFileNameRule extends CommonRule {
@override
Iterable<Issue> check(InternalResolvedUnitResult source) {
final visitor = _Visitor();

source.unit.visitChildren(visitor);

final issues = <Issue>[];
Expand Down
@@ -0,0 +1 @@
void main() {}
@@ -0,0 +1,4 @@
// LINT
void main() {
print('Hello');
}
@@ -0,0 +1,54 @@
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart';
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/prefer_correct_test_file_name/prefer_correct_test_file_name_rule.dart';
import 'package:test/test.dart';

import '../../../../../helpers/rule_test_helper.dart';

const _examplePath = 'prefer_correct_test_file_name/examples/example.dart';
const _correctExamplePath =
'prefer_correct_test_file_name/examples/correct_example.dart';

void main() {
group('PreferCorrectTestFileNameRule', () {
test('initialization', () async {
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
final issues = PreferCorrectTestFileNameRule().check(unit);

RuleTestHelper.verifyInitialization(
issues: issues,
ruleId: 'prefer-correct-test-file-name',
severity: Severity.warning,
);
});

test('reports about found issues', () async {
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
final config = {'name-pattern': 'correct_example.dart'};

final issues = PreferCorrectTestFileNameRule(config).check(unit);

RuleTestHelper.verifyIssues(
issues: issues,
startLines: [2],
startColumns: [1],
locationTexts: [
'void main() {\n'
" print('Hello');\n"
'}',
],
messages: [
'Test file name should end with correct_example.dart',
],
);
});

test('reports no found issues', () async {
final unit = await RuleTestHelper.resolveFromFile(_correctExamplePath);
final config = {'name-pattern': 'correct_example.dart'};

final issues = PreferCorrectTestFileNameRule(config).check(unit);

RuleTestHelper.verifyNoIssues(issues);
});
});
}
Expand Up @@ -6,6 +6,12 @@ Warns when a public top-level member (expect the entrypoint) is declared inside

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

::: note

If you want to set `exclude` config for this rule, the default `['/**', '!test/**']` will be overriden.

:::

### Example {#example}

Bad:
Expand Down
41 changes: 41 additions & 0 deletions website/docs/rules/common/prefer-correct-test-file-name.mdx
@@ -0,0 +1,41 @@
import RuleDetails from '@site/src/components/RuleDetails';

<RuleDetails version="4.19.0" severity="warning" />

Warns if the file within `/test` contains a `main`, but the file name doesn't end with `_test.dart`.

:::note

If you want to set `exclude` config for this rule, the default `['/**', '!test/**']` will be overriden.

:::

### Example {#example}

**❌ Bad:**

File name: **some_file.dart**

```dart
void main() {
...
}
```

**✅ Good:**

File name: **some_file_test.dart**

```dart
void main() {
...
}
```

File name: **some_other_file.dart**

```dart
void helperFunction() {
...
}
```
10 changes: 10 additions & 0 deletions website/docs/rules/index.mdx
Expand Up @@ -355,6 +355,16 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
Warns when an identifier name length is very short or long.
</RuleEntry>

<RuleEntry
name="prefer-correct-test-file-name"
type="common"
severity="warning"
version="4.19.0"
>
Warns if the file within <code>/test</code> contains a <code>main</code>, but
the file name doesn't end with <code>_test.dart</code>.
</RuleEntry>

<RuleEntry
name="prefer-correct-type-name"
type="common"
Expand Down

0 comments on commit 1d8e535

Please sign in to comment.