diff --git a/CHANGELOG.md b/CHANGELOG.md index 27c8f1e272..1bafe1089e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * chore: restrict `analyzer` version to `>=5.1.0 <5.8.0`. +* feat: add avoid-substring rule ## 5.6.0 diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart index 0be7acfd64..c19630b3f2 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart @@ -24,6 +24,7 @@ import 'rules_list/avoid_redundant_async/avoid_redundant_async_rule.dart'; import 'rules_list/avoid_redundant_async_on_load/avoid_redundant_async_on_load_rule.dart'; import 'rules_list/avoid_returning_widgets/avoid_returning_widgets_rule.dart'; import 'rules_list/avoid_shrink_wrap_in_lists/avoid_shrink_wrap_in_lists_rule.dart'; +import 'rules_list/avoid_substring/avoid_substring_rule.dart'; import 'rules_list/avoid_throw_in_catch_block/avoid_throw_in_catch_block_rule.dart'; import 'rules_list/avoid_top_level_members_in_tests/avoid_top_level_members_in_tests_rule.dart'; import 'rules_list/avoid_unnecessary_conditionals/avoid_unnecessary_conditionals_rule.dart'; @@ -107,6 +108,7 @@ final _implementedRules = )>{ AvoidRedundantAsyncOnLoadRule.ruleId: AvoidRedundantAsyncOnLoadRule.new, AvoidReturningWidgetsRule.ruleId: AvoidReturningWidgetsRule.new, AvoidShrinkWrapInListsRule.ruleId: AvoidShrinkWrapInListsRule.new, + AvoidSubstringRule.ruleId: AvoidSubstringRule.new, AvoidThrowInCatchBlockRule.ruleId: AvoidThrowInCatchBlockRule.new, AvoidTopLevelMembersInTestsRule.ruleId: AvoidTopLevelMembersInTestsRule.new, AvoidUnnecessaryConditionalsRule.ruleId: AvoidUnnecessaryConditionalsRule.new, diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/avoid_substring_rule.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/avoid_substring_rule.dart new file mode 100644 index 0000000000..23088cf34f --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/avoid_substring_rule.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 'visitor.dart'; + +class AvoidSubstringRule extends CommonRule { + static const String ruleId = 'avoid-substring'; + + static const _warning = + 'Avoid using substring if you are having emojis in the string. Consider using characters.getRange instead.'; + + AvoidSubstringRule([Map config = const {}]) + : super( + id: ruleId, + severity: readSeverity(config, Severity.warning), + excludes: readExcludes(config), + includes: readIncludes(config), + ); + + @override + Iterable check(InternalResolvedUnitResult source) { + final visitor = _Visitor(); + + source.unit.visitChildren(visitor); + + return visitor.expressions + .map((expression) => createIssue( + rule: this, + location: nodeLocation( + node: expression, + source: source, + ), + message: _warning, + )) + .toList(growable: false); + } +} diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/visitor.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/visitor.dart new file mode 100644 index 0000000000..dd1b53c2e7 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/visitor.dart @@ -0,0 +1,19 @@ +part of 'avoid_substring_rule.dart'; + +class _Visitor extends RecursiveAstVisitor { + final _expressions = []; + + Iterable get expressions => _expressions; + + @override + void visitMethodInvocation(MethodInvocation node) { + super.visitMethodInvocation(node); + if (_isNotSubstringMethod(node)) { + return; + } + _expressions.add(node); + } + + bool _isNotSubstringMethod(MethodInvocation node) => + node.methodName.name != 'substring'; +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/avoid_substring_rule_test.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/avoid_substring_rule_test.dart new file mode 100644 index 0000000000..37bd326663 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/avoid_substring_rule_test.dart @@ -0,0 +1,41 @@ +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart'; +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/avoid_substring_rule.dart'; +import 'package:test/test.dart'; + +import '../../../../../helpers/rule_test_helper.dart'; + +const _examplePath = 'avoid_substring/examples/example.dart'; + +void main() { + group('AvoidSubstringRule', () { + test('initialization', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = AvoidSubstringRule().check(unit); + + RuleTestHelper.verifyInitialization( + issues: issues, + ruleId: 'avoid-substring', + severity: Severity.warning, + ); + }); + + test('reports about found issues', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = AvoidSubstringRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [3, 5], + startColumns: [3, 3], + locationTexts: [ + 's.substring(14, 15)', + '''"It's a smiley 😀 smile".substring(14, 15)''', + ], + messages: [ + 'Avoid using substring if you are having emojis in the string. Consider using characters.getRange instead.', + 'Avoid using substring if you are having emojis in the string. Consider using characters.getRange instead.', + ], + ); + }); + }); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/examples/example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/examples/example.dart new file mode 100644 index 0000000000..d9d6cea0cd --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_substring/examples/example.dart @@ -0,0 +1,6 @@ +void main() { + final s = "It's a smiley 😀 smile"; + s.substring(14, 15); // LINT + + "It's a smiley 😀 smile".substring(14, 15); // LINT +}