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

Commit

Permalink
feat: avoid-non-ascii-symbols rule (#825)
Browse files Browse the repository at this point in the history
* 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 <dmitry.krutskikh@gmail.com>
  • Loading branch information
incendial and dkrutskikh committed May 3, 2022
1 parent 374f392 commit ded9d0e
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions lib/src/analyzers/lint_analyzer/rules/rules_factory.dart
Expand Up @@ -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';
Expand Down Expand Up @@ -65,6 +66,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
AvoidMissingEnumConstantInMapRule(config),
AvoidNestedConditionalExpressionsRule.ruleId: (config) =>
AvoidNestedConditionalExpressionsRule(config),
AvoidNonAsciiSymbolsRule.ruleId: (config) => AvoidNonAsciiSymbolsRule(config),
AvoidNonNullAssertionRule.ruleId: (config) =>
AvoidNonNullAssertionRule(config),
AvoidPreserveWhitespaceFalseRule.ruleId: (config) =>
Expand Down
@@ -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<String, Object> config = const {}])
: super(
id: ruleId,
severity: readSeverity(config, Severity.warning),
excludes: readExcludes(config),
);

@override
Iterable<Issue> 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);
}
}
@@ -0,0 +1,31 @@
part of 'avoid_non_ascii_symbols_rule.dart';

final _onlyAsciiSymbolsRegExp = RegExp(r'^[\u0000-\u007f]*$');

class _Visitor extends RecursiveAstVisitor<void> {
final _literals = <SingleStringLiteral>[];

Iterable<SingleStringLiteral> 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;
}
}
}
}
@@ -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.',
],
);
});
});
}
@@ -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
}
31 changes: 31 additions & 0 deletions 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 ='!@#$%^';
```
4 changes: 4 additions & 0 deletions website/docs/rules/overview.md
Expand Up @@ -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.
Expand Down

0 comments on commit ded9d0e

Please sign in to comment.