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

Commit 728b979

Browse files
feat: add static code diagnostic prefer-commenting-analyzer-ignores (#776)
* feat: add static code diagnostic prefer-commenting-analyzer-ignores * fix: add new rule to factory * test: update suppression test Co-authored-by: Dmitry Krutskikh <dmitry.krutskikh@gmail.com>
1 parent d4a081e commit 728b979

File tree

10 files changed

+217
-8
lines changed

10 files changed

+217
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
* feat: add static code diagnostic [`prefer-commenting-analyzer-ignores`](https://dartcodemetrics.dev/docs/rules/common/prefer-commenting-analyzer-ignores).
56
* fix: add check for supertypes for [`avoid-non-null-assertions`](https://dartcodemetrics.dev/docs/rules/common/avoid-non-null-assertion) rule.
67
* fix: cover more cases in [`prefer-immediate-return`](https://dartcodemetrics.dev/docs/rules/common/prefer-immediate-return) rule
78
* fix: support index expressions for [`no-magic-number`](https://dartcodemetrics.dev/docs/rules/common/no-magic-number) rule.

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import 'rules_list/no_equal_then_else/no_equal_then_else_rule.dart';
3333
import 'rules_list/no_magic_number/no_magic_number_rule.dart';
3434
import 'rules_list/no_object_declaration/no_object_declaration_rule.dart';
3535
import 'rules_list/prefer_async_await/prefer_async_await_rule.dart';
36+
import 'rules_list/prefer_commenting_analyzer_ignores/prefer_commenting_analyzer_ignores.dart';
3637
import 'rules_list/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart';
3738
import 'rules_list/prefer_const_border_radius/prefer_const_border_radius_rule.dart';
3839
import 'rules_list/prefer_correct_identifier_length/prefer_correct_identifier_length_rule.dart';
@@ -102,6 +103,8 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
102103
NoMagicNumberRule.ruleId: (config) => NoMagicNumberRule(config),
103104
NoObjectDeclarationRule.ruleId: (config) => NoObjectDeclarationRule(config),
104105
PreferAsyncAwaitRule.ruleId: (config) => PreferAsyncAwaitRule(config),
106+
PreferCommentingAnalyzerIgnores.ruleId: (config) =>
107+
PreferCommentingAnalyzerIgnores(config),
105108
PreferConditionalExpressionsRule.ruleId: (config) =>
106109
PreferConditionalExpressionsRule(config),
107110
PreferConstBorderRadiusRule.ruleId: (config) =>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import 'package:analyzer/dart/ast/ast.dart';
2+
import 'package:analyzer/dart/ast/token.dart';
3+
import 'package:analyzer/dart/ast/visitor.dart';
4+
import 'package:analyzer/source/line_info.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 PreferCommentingAnalyzerIgnores extends CommonRule {
17+
static const String ruleId = 'prefer-commenting-analyzer-ignores';
18+
19+
static const _warning = 'Prefer commenting analyzer ignores.';
20+
21+
PreferCommentingAnalyzerIgnores([Map<String, Object> config = const {}])
22+
: super(
23+
id: ruleId,
24+
severity: readSeverity(config, Severity.warning),
25+
excludes: readExcludes(config),
26+
);
27+
28+
@override
29+
Iterable<Issue> check(InternalResolvedUnitResult source) {
30+
final visitor = _Visitor(source.lineInfo)..checkComments(source.unit.root);
31+
32+
return visitor.comments.map((comment) => createIssue(
33+
rule: this,
34+
location: nodeLocation(
35+
node: comment,
36+
source: source,
37+
),
38+
message: _warning,
39+
));
40+
}
41+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
part of 'prefer_commenting_analyzer_ignores.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
final LineInfo _lineInfo;
5+
6+
final _comments = <Token>[];
7+
8+
Iterable<Token> get comments => _comments;
9+
10+
_Visitor(this._lineInfo);
11+
12+
void checkComments(AstNode node) {
13+
Token? token = node.beginToken;
14+
while (token != null) {
15+
Token? commentToken = token.precedingComments;
16+
while (commentToken != null) {
17+
_visitCommentToken(commentToken);
18+
commentToken = commentToken.next;
19+
}
20+
21+
if (token == token.next) {
22+
break;
23+
}
24+
25+
token = token.next;
26+
}
27+
}
28+
29+
void _visitCommentToken(Token node) {
30+
if (node.type == TokenType.SINGLE_LINE_COMMENT) {
31+
final comment = node.toString();
32+
33+
final matches = _getIgnoreMatches(comment);
34+
if (_hasNoDescription(matches, comment) && _hasNoPreviousComment(node)) {
35+
_comments.add(node);
36+
}
37+
}
38+
}
39+
40+
bool _hasNoDescription(Iterable<Match> matches, String comment) =>
41+
matches.isNotEmpty &&
42+
matches.first.groupCount > 0 &&
43+
comment.trim().endsWith(matches.first.group(0)?.trim() ?? '');
44+
45+
bool _hasNoPreviousComment(Token node) {
46+
final previous = node.previous;
47+
48+
return previous == null ||
49+
(previous.type != TokenType.SINGLE_LINE_COMMENT ||
50+
_lineInfo.getLocation(node.offset).lineNumber - 1 !=
51+
_lineInfo.getLocation(previous.offset).lineNumber);
52+
}
53+
54+
Iterable<Match> _getIgnoreMatches(String comment) {
55+
final regExp = RegExp(r'ignore:[a-z_,-\s]*([a-z]*(-|_)[a-z]*)+');
56+
57+
return regExp.allMatches(comment);
58+
}
59+
}

test/resources/suppression_example.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ void main() {
44
// ignore: rule_id4
55
const a = 5; // ignore: RULE_ID5
66

7-
// ignore:rule_id6 ,rule_id7
7+
// ignore comment
8+
// ignore:rule_id6 ,rule_id7, some comment about ignores
89
const b = a + 5; // ignore: RULE_ID8, rule_id9, unused_local_variable
910
}
1011

test/src/analyzers/lint_analyzer/models/suppression_test.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ void main() {
1919
expect(suppression.isSuppressed('rule_id3'), isTrue);
2020
expect(suppression.isSuppressed('rule_id4'), isFalse);
2121

22-
expect(suppression.isSuppressedAt('rule_id1', 5), isTrue);
23-
expect(suppression.isSuppressedAt('rule_id2', 8), isTrue);
24-
expect(suppression.isSuppressedAt('rule_id3', 2), isTrue);
22+
expect(suppression.isSuppressedAt('rule_id1', null), isTrue);
23+
expect(suppression.isSuppressedAt('rule_id2', null), isTrue);
24+
expect(suppression.isSuppressedAt('rule_id3', null), isTrue);
2525
expect(suppression.isSuppressedAt('rule_id4', 5), isTrue);
2626
expect(suppression.isSuppressedAt('rule_id5', 5), isTrue);
27-
expect(suppression.isSuppressedAt('rule_id6', 8), isTrue);
28-
expect(suppression.isSuppressedAt('rule_id7', 8), isTrue);
29-
expect(suppression.isSuppressedAt('rule_id8', 8), isTrue);
30-
expect(suppression.isSuppressedAt('rule_id9', 8), isTrue);
27+
expect(suppression.isSuppressedAt('rule_id6', 9), isTrue);
28+
expect(suppression.isSuppressedAt('rule_id7', 9), isTrue);
29+
expect(suppression.isSuppressedAt('rule_id8', 9), isTrue);
30+
expect(suppression.isSuppressedAt('rule_id9', 9), isTrue);
3131
});
3232
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// ignore_for_file: some_rule
2+
void main() {
3+
// ignore: deprecated_member_use
4+
final map = Map(); // LINT
5+
6+
// ignore: deprecated_member_use, long-method
7+
final set = Set(); // LINT
8+
9+
// Ignored for some reasons
10+
// ignore: deprecated_member_use
11+
final list = List();
12+
13+
// ignore: deprecated_member_use same line ignore
14+
final queue = Queue();
15+
16+
// ignore: deprecated_member_use, long-method multiple same line ignore
17+
final linkedList = LinkedList();
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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_commenting_analyzer_ignores/prefer_commenting_analyzer_ignores.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'prefer_commenting_analyzer_ignores/examples/example.dart';
8+
9+
void main() {
10+
group('PreferCommentingAnalyzerIgnores', () {
11+
test('initialization', () async {
12+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
13+
final issues = PreferCommentingAnalyzerIgnores().check(unit);
14+
15+
RuleTestHelper.verifyInitialization(
16+
issues: issues,
17+
ruleId: 'prefer-commenting-analyzer-ignores',
18+
severity: Severity.warning,
19+
);
20+
});
21+
22+
test('reports about found issues', () async {
23+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
24+
final issues = PreferCommentingAnalyzerIgnores().check(unit);
25+
26+
RuleTestHelper.verifyIssues(
27+
issues: issues,
28+
startLines: [3, 6],
29+
startColumns: [3, 3],
30+
locationTexts: [
31+
'// ignore: deprecated_member_use',
32+
'// ignore: deprecated_member_use, long-method',
33+
],
34+
messages: [
35+
'Prefer commenting analyzer ignores.',
36+
'Prefer commenting analyzer ignores.',
37+
],
38+
);
39+
});
40+
});
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Prefer commenting analyzer ignores
2+
3+
## Rule id
4+
5+
prefer-commenting-analyzer-ignores
6+
7+
## Severity {#severity}
8+
9+
Warning
10+
11+
## Description
12+
13+
Warns when `// ignore:` comments are left without any additional description why this ignore is applied.
14+
15+
This rule doesn't trigger on global `ignore_for_file:` comments.
16+
17+
### Example
18+
19+
Bad:
20+
21+
```dart
22+
// ignore: deprecated_member_use
23+
final map = Map(); // LINT
24+
25+
// ignore: deprecated_member_use, long-method
26+
final set = Set(); // LINT
27+
```
28+
29+
Good:
30+
31+
```dart
32+
// Ignored for some reasons
33+
// ignore: deprecated_member_use
34+
final list = List();
35+
36+
// ignore: deprecated_member_use same line ignore
37+
final queue = Queue();
38+
39+
// ignore: deprecated_member_use, long-method multiple same line ignore
40+
final linkedList = LinkedList();
41+
```

website/docs/rules/overview.md

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

120120
Recommends to use async/await syntax to handle Futures result instead of `.then()` invocation.
121121

122+
- [prefer-commenting-analyzer-ignores](./common/prefer-commenting-analyzer-ignores.md)
123+
124+
Warns when `// ignore:` comments are left without any additional description why this ignore is applied.
125+
122126
- [prefer-conditional-expressions](./common/prefer-conditional-expressions.md) &nbsp; ![Has auto-fix](https://img.shields.io/badge/-has%20auto--fix-success)
123127

124128
Recommends to use a conditional expression instead of assigning to the same thing or return statement in each branch of an if statement.

0 commit comments

Comments
 (0)