From ded9d0eced3ddaf5e4b5f0f0f67e7cf3fcd61ec2 Mon Sep 17 00:00:00 2001 From: Dmitry Zhifarsky Date: Tue, 3 May 2022 15:04:53 +0400 Subject: [PATCH] feat: avoid-non-ascii-symbols rule (#825) * feat: avoid-non-ascii-symbols rule * test: update test case * fix: add break in order not to have multiple highlights Co-authored-by: Dmitry Krutskikh --- CHANGELOG.md | 3 +- .../lint_analyzer/rules/rules_factory.dart | 2 + .../avoid_non_ascii_symbols_rule.dart | 45 +++++++++++++++++++ .../avoid_non_ascii_symbols/visitor.dart | 31 +++++++++++++ .../avoid_non_ascii_symbols_rule_test.dart | 45 +++++++++++++++++++ .../examples/example.dart | 8 ++++ .../rules/common/avoid-non-ascii-symbols.md | 31 +++++++++++++ website/docs/rules/overview.md | 4 ++ 8 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/avoid_non_ascii_symbols_rule.dart create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/visitor.dart create mode 100644 test/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/avoid_non_ascii_symbols_rule_test.dart create mode 100644 test/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/examples/example.dart create mode 100644 website/docs/rules/common/avoid-non-ascii-symbols.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d4910ed8d0..bd262aca4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ ## Unreleased +* feat: add static code diagnostic [`avoid-non-ascii-symbols`](https://dartcodemetrics.dev/docs/rules/common/avoid-non-ascii-symbols). +* feat: remove declaration in [`prefer-immediate-return`](https://dartcodemetrics.dev/docs/rules/common/prefer-immediate-return). * fix: correctly handle disabling rules with false. * fix: dart-code-metrics crash saying `Bad state: No element` when running command. -* feat: remove declaration in prefer-immediate-return ## 4.14.0 diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart index d42a755268..364f283b8a 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart @@ -8,6 +8,7 @@ import 'rules_list/avoid_ignoring_return_values/avoid_ignoring_return_values_rul import 'rules_list/avoid_late_keyword/avoid_late_keyword_rule.dart'; import 'rules_list/avoid_missing_enum_constant_in_map/avoid_missing_enum_constant_in_map_rule.dart'; import 'rules_list/avoid_nested_conditional_expressions/avoid_nested_conditional_expressions_rule.dart'; +import 'rules_list/avoid_non_ascii_symbols/avoid_non_ascii_symbols_rule.dart'; import 'rules_list/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart'; import 'rules_list/avoid_preserve_whitespace_false/avoid_preserve_whitespace_false_rule.dart'; import 'rules_list/avoid_returning_widgets/avoid_returning_widgets_rule.dart'; @@ -65,6 +66,7 @@ final _implementedRules = )>{ AvoidMissingEnumConstantInMapRule(config), AvoidNestedConditionalExpressionsRule.ruleId: (config) => AvoidNestedConditionalExpressionsRule(config), + AvoidNonAsciiSymbolsRule.ruleId: (config) => AvoidNonAsciiSymbolsRule(config), AvoidNonNullAssertionRule.ruleId: (config) => AvoidNonNullAssertionRule(config), AvoidPreserveWhitespaceFalseRule.ruleId: (config) => diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/avoid_non_ascii_symbols_rule.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/avoid_non_ascii_symbols_rule.dart new file mode 100644 index 0000000000..adcbb539d5 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/avoid_non_ascii_symbols_rule.dart @@ -0,0 +1,45 @@ +// 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 AvoidNonAsciiSymbolsRule extends CommonRule { + static const String ruleId = 'avoid-non-ascii-symbols'; + + static const _warning = 'Avoid using non ascii symbols in string literals.'; + + AvoidNonAsciiSymbolsRule([Map config = const {}]) + : super( + id: ruleId, + severity: readSeverity(config, Severity.warning), + excludes: readExcludes(config), + ); + + @override + Iterable check(InternalResolvedUnitResult source) { + final visitor = _Visitor(); + + source.unit.visitChildren(visitor); + + return visitor.literals + .map((literal) => createIssue( + rule: this, + location: nodeLocation( + node: literal, + source: source, + ), + message: _warning, + )) + .toList(growable: false); + } +} diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/visitor.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/visitor.dart new file mode 100644 index 0000000000..ae03ef23f9 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/visitor.dart @@ -0,0 +1,31 @@ +part of 'avoid_non_ascii_symbols_rule.dart'; + +final _onlyAsciiSymbolsRegExp = RegExp(r'^[\u0000-\u007f]*$'); + +class _Visitor extends RecursiveAstVisitor { + final _literals = []; + + Iterable get literals => _literals; + + @override + void visitSimpleStringLiteral(SimpleStringLiteral node) { + super.visitSimpleStringLiteral(node); + + if (!_onlyAsciiSymbolsRegExp.hasMatch(node.value)) { + _literals.add(node); + } + } + + @override + void visitStringInterpolation(StringInterpolation node) { + super.visitStringInterpolation(node); + + for (final element in node.elements) { + if (element is InterpolationString && + !_onlyAsciiSymbolsRegExp.hasMatch(element.value)) { + _literals.add(node); + break; + } + } + } +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/avoid_non_ascii_symbols_rule_test.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/avoid_non_ascii_symbols_rule_test.dart new file mode 100644 index 0000000000..6b29ead349 --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/avoid_non_ascii_symbols_rule_test.dart @@ -0,0 +1,45 @@ +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_non_ascii_symbols/avoid_non_ascii_symbols_rule.dart'; +import 'package:test/test.dart'; + +import '../../../../../helpers/rule_test_helper.dart'; + +const _examplePath = 'avoid_non_ascii_symbols/examples/example.dart'; + +void main() { + group('AvoidNonAsciiSymbolsRule', () { + test('initialization', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = AvoidNonAsciiSymbolsRule().check(unit); + + RuleTestHelper.verifyInitialization( + issues: issues, + ruleId: 'avoid-non-ascii-symbols', + severity: Severity.warning, + ); + }); + + test('reports about found issues', () async { + final unit = await RuleTestHelper.resolveFromFile(_examplePath); + final issues = AvoidNonAsciiSymbolsRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startLines: [3, 4, 6, 7], + startColumns: [19, 19, 27, 23], + locationTexts: [ + "'hello 汉字'", + "'hello привет'", + r"'#!$_&- éè ;∞¥₤€'", + "'inform@tiv€'", + ], + messages: [ + 'Avoid using non ascii symbols in string literals.', + 'Avoid using non ascii symbols in string literals.', + 'Avoid using non ascii symbols in string literals.', + 'Avoid using non ascii symbols in string literals.', + ], + ); + }); + }); +} diff --git a/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/examples/example.dart b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/examples/example.dart new file mode 100644 index 0000000000..c7a85ec3bb --- /dev/null +++ b/test/src/analyzers/lint_analyzer/rules/rules_list/avoid_non_ascii_symbols/examples/example.dart @@ -0,0 +1,8 @@ +void main() { + final english = 'hello'; + final chinese = 'hello 汉字'; // LINT + final russian = 'hello привет'; // LINT + final someGenericSymbols = '!@#${english}%^'; + final nonAsciiSymbols = '#!$_&- éè ;∞¥₤€'; // LINT + final misspelling = 'inform@tiv€'; // LINT +} diff --git a/website/docs/rules/common/avoid-non-ascii-symbols.md b/website/docs/rules/common/avoid-non-ascii-symbols.md new file mode 100644 index 0000000000..7107d3fba4 --- /dev/null +++ b/website/docs/rules/common/avoid-non-ascii-symbols.md @@ -0,0 +1,31 @@ +# Avoid non ascii symbols + +## Rule id {#rule-id} + +avoid-non-ascii-symbols + +## Severity {#severity} + +Warning + +## Description {#description} + +Warns when a string literal contains non ascii characters. This might indicate that the string was not localized. + +### Example {#example} + +Bad: + +```dart +final chinese = 'hello 汉字'; // LINT +final russian = 'hello привет'; // LINT +final withSomeNonAsciiSymbols = '#!$_&- éè ;∞¥₤€'; // LINT +final misspelling = 'inform@tiv€'; // LINT +``` + +Good: + +```dart +final english = 'hello'; +final someGenericSymbols ='!@#$%^'; +``` diff --git a/website/docs/rules/overview.md b/website/docs/rules/overview.md index b013c8afdb..35580e8897 100644 --- a/website/docs/rules/overview.md +++ b/website/docs/rules/overview.md @@ -39,6 +39,10 @@ Rules configuration is [described here](../getting-started/configuration#configu Warns about nested conditional expressions. +- [avoid-non-ascii-symbols](./common/avoid-non-ascii-symbols.md) + + Warns when a string literal contains non ascii characters. + - [avoid-non-null-assertion](./common/avoid-non-null-assertion.md) Warns when non null assertion operator (or “bang” operator) is used for a property access or method invocation. The operator check works at runtime and it may fail and throw a runtime exception.