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

Commit 85be501

Browse files
authored
feat: add avoid-substring rule (#1206)
1 parent 1709f2d commit 85be501

File tree

6 files changed

+116
-0
lines changed

6 files changed

+116
-0
lines changed

CHANGELOG.md

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

55
* feat: add static code diagnostic [prefer-define-hero-tag](https://github.com/dart-code-checker/dart-code-metrics/issues/1027).
66
* chore: restrict `analyzer` version to `>=5.1.0 <5.8.0`.
7+
* feat: add avoid-substring rule
78

89
## 5.6.0
910

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import 'rules_list/avoid_redundant_async/avoid_redundant_async_rule.dart';
2424
import 'rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule.dart';
2525
import 'rules_list/avoid_returning_widgets/avoid_returning_widgets_rule.dart';
2626
import 'rules_list/avoid_shrink_wrap_in_lists/avoid_shrink_wrap_in_lists_rule.dart';
27+
import 'rules_list/avoid_substring/avoid_substring_rule.dart';
2728
import 'rules_list/avoid_throw_in_catch_block/avoid_throw_in_catch_block_rule.dart';
2829
import 'rules_list/avoid_top_level_members_in_tests/avoid_top_level_members_in_tests_rule.dart';
2930
import 'rules_list/avoid_unnecessary_conditionals/avoid_unnecessary_conditionals_rule.dart';
@@ -108,6 +109,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
108109
AvoidRedundantAsyncOnLoadRule.ruleId: AvoidRedundantAsyncOnLoadRule.new,
109110
AvoidReturningWidgetsRule.ruleId: AvoidReturningWidgetsRule.new,
110111
AvoidShrinkWrapInListsRule.ruleId: AvoidShrinkWrapInListsRule.new,
112+
AvoidSubstringRule.ruleId: AvoidSubstringRule.new,
111113
AvoidThrowInCatchBlockRule.ruleId: AvoidThrowInCatchBlockRule.new,
112114
AvoidTopLevelMembersInTestsRule.ruleId: AvoidTopLevelMembersInTestsRule.new,
113115
AvoidUnnecessaryConditionalsRule.ruleId: AvoidUnnecessaryConditionalsRule.new,
Lines changed: 47 additions & 0 deletions
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 'visitor.dart';
15+
16+
class AvoidSubstringRule extends CommonRule {
17+
static const String ruleId = 'avoid-substring';
18+
19+
static const _warning =
20+
'Avoid using substring if you are having emojis in the string. Consider using characters.getRange instead.';
21+
22+
AvoidSubstringRule([Map<String, Object> config = const {}])
23+
: super(
24+
id: ruleId,
25+
severity: readSeverity(config, Severity.warning),
26+
excludes: readExcludes(config),
27+
includes: readIncludes(config),
28+
);
29+
30+
@override
31+
Iterable<Issue> check(InternalResolvedUnitResult source) {
32+
final visitor = _Visitor();
33+
34+
source.unit.visitChildren(visitor);
35+
36+
return visitor.expressions
37+
.map((expression) => createIssue(
38+
rule: this,
39+
location: nodeLocation(
40+
node: expression,
41+
source: source,
42+
),
43+
message: _warning,
44+
))
45+
.toList(growable: false);
46+
}
47+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
part of 'avoid_substring_rule.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
final _expressions = <Expression>[];
5+
6+
Iterable<Expression> get expressions => _expressions;
7+
8+
@override
9+
void visitMethodInvocation(MethodInvocation node) {
10+
super.visitMethodInvocation(node);
11+
if (_isNotSubstringMethod(node)) {
12+
return;
13+
}
14+
_expressions.add(node);
15+
}
16+
17+
bool _isNotSubstringMethod(MethodInvocation node) =>
18+
node.methodName.name != 'substring';
19+
}
Lines changed: 41 additions & 0 deletions
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/avoid_substring/avoid_substring_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'avoid_substring/examples/example.dart';
8+
9+
void main() {
10+
group('AvoidSubstringRule', () {
11+
test('initialization', () async {
12+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
13+
final issues = AvoidSubstringRule().check(unit);
14+
15+
RuleTestHelper.verifyInitialization(
16+
issues: issues,
17+
ruleId: 'avoid-substring',
18+
severity: Severity.warning,
19+
);
20+
});
21+
22+
test('reports about found issues', () async {
23+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
24+
final issues = AvoidSubstringRule().check(unit);
25+
26+
RuleTestHelper.verifyIssues(
27+
issues: issues,
28+
startLines: [3, 5],
29+
startColumns: [3, 3],
30+
locationTexts: [
31+
's.substring(14, 15)',
32+
'''"It's a smiley 😀 smile".substring(14, 15)''',
33+
],
34+
messages: [
35+
'Avoid using substring if you are having emojis in the string. Consider using characters.getRange instead.',
36+
'Avoid using substring if you are having emojis in the string. Consider using characters.getRange instead.',
37+
],
38+
);
39+
});
40+
});
41+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
void main() {
2+
final s = "It's a smiley 😀 smile";
3+
s.substring(14, 15); // LINT
4+
5+
"It's a smiley 😀 smile".substring(14, 15); // LINT
6+
}

0 commit comments

Comments
 (0)