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

Commit 628fc92

Browse files
committed
feat: introduce prefer-provide-intl-description rule
1 parent 7652d0f commit 628fc92

File tree

6 files changed

+374
-0
lines changed

6 files changed

+374
-0
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import 'rules_list/prefer_last/prefer_last_rule.dart';
6565
import 'rules_list/prefer_match_file_name/prefer_match_file_name_rule.dart';
6666
import 'rules_list/prefer_moving_to_variable/prefer_moving_to_variable_rule.dart';
6767
import 'rules_list/prefer_on_push_cd_strategy/prefer_on_push_cd_strategy_rule.dart';
68+
import 'rules_list/prefer_provide_intl_description/prefer_provide_intl_description_rule.dart';
6869
import 'rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_rule.dart';
6970
import 'rules_list/prefer_static_class/prefer_static_class_rule.dart';
7071
import 'rules_list/prefer_trailing_comma/prefer_trailing_comma_rule.dart';
@@ -150,6 +151,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
150151
PreferMatchFileNameRule.ruleId: PreferMatchFileNameRule.new,
151152
PreferMovingToVariableRule.ruleId: PreferMovingToVariableRule.new,
152153
PreferOnPushCdStrategyRule.ruleId: PreferOnPushCdStrategyRule.new,
154+
PreferProvideIntlDescriptionRule.ruleId: PreferProvideIntlDescriptionRule.new,
153155
PreferSingleWidgetPerFileRule.ruleId: PreferSingleWidgetPerFileRule.new,
154156
PreferStaticClassRule.ruleId: PreferStaticClassRule.new,
155157
PreferTrailingCommaRule.ruleId: PreferTrailingCommaRule.new,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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/intl_rule.dart';
12+
import '../../rule_utils.dart';
13+
14+
part 'visitor.dart';
15+
16+
class PreferProvideIntlDescriptionRule extends IntlRule {
17+
static const String ruleId = 'prefer-provide-intl-description';
18+
19+
static const _warning = 'Prefer provide description for translated message';
20+
21+
PreferProvideIntlDescriptionRule([Map<String, Object> config = const {}])
22+
: super(
23+
id: ruleId,
24+
severity: readSeverity(config, Severity.warning),
25+
excludes: readExcludes(config),
26+
includes: readIncludes(config),
27+
);
28+
29+
@override
30+
Iterable<Issue> check(InternalResolvedUnitResult source) {
31+
final visitor = _Visitor();
32+
33+
source.unit.visitChildren(visitor);
34+
35+
return visitor.declarations
36+
.map((declaration) => createIssue(
37+
rule: this,
38+
location: nodeLocation(
39+
node: declaration,
40+
source: source,
41+
),
42+
message: _warning,
43+
))
44+
.toList(growable: false);
45+
}
46+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
part of 'prefer_provide_intl_description_rule.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
static const _supportedMethods = {'message', 'plural', 'gender', 'select'};
5+
6+
final _declarations = <MethodInvocation>[];
7+
8+
Iterable<MethodInvocation> get declarations => _declarations;
9+
10+
@override
11+
void visitMethodInvocation(MethodInvocation node) {
12+
super.visitMethodInvocation(node);
13+
14+
final target = node.realTarget;
15+
if (target != null &&
16+
target is SimpleIdentifier &&
17+
target.name == 'Intl' &&
18+
_supportedMethods.contains(node.methodName.name) &&
19+
_withEmptyDescription(node.argumentList)) {
20+
_declarations.add(node);
21+
}
22+
}
23+
24+
bool _withEmptyDescription(ArgumentList args) =>
25+
args.arguments.any((argument) =>
26+
argument is NamedExpression &&
27+
argument.name.label.name == 'desc' &&
28+
argument.expression is SimpleStringLiteral &&
29+
(argument.expression as SimpleStringLiteral).value.isEmpty) ||
30+
args.arguments.every((argument) =>
31+
argument is! NamedExpression || argument.name.label.name != 'desc');
32+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import 'package:intl/intl.dart';
2+
3+
class SomeClassI18n {
4+
static final String message = Intl.message(
5+
'message',
6+
name: 'SomeClassI18n_message',
7+
desc: 'Message description',
8+
);
9+
10+
static String plural = Intl.plural(
11+
1,
12+
one: 'one',
13+
other: 'other',
14+
name: 'SomeClassI18n_plural',
15+
desc: 'Plural description',
16+
);
17+
18+
static String gender = Intl.gender(
19+
'other',
20+
female: 'female',
21+
male: 'male',
22+
other: 'other',
23+
name: 'SomeClassI18n_gender',
24+
desc: 'Gender description',
25+
);
26+
27+
static String select = Intl.select(
28+
true,
29+
{true: 'true', false: 'false'},
30+
name: 'SomeClassI18n_select',
31+
desc: 'Select description',
32+
);
33+
}
34+
35+
class Intl {
36+
static String message(String messageText,
37+
{String? desc = '',
38+
Map<String, Object>? examples,
39+
String? locale,
40+
String? name,
41+
List<Object>? args,
42+
String? meaning,
43+
bool? skip}) =>
44+
'';
45+
46+
static String plural(num howMany,
47+
{String? zero,
48+
String? one,
49+
String? two,
50+
String? few,
51+
String? many,
52+
required String other,
53+
String? desc,
54+
Map<String, Object>? examples,
55+
String? locale,
56+
int? precision,
57+
String? name,
58+
List<Object>? args,
59+
String? meaning,
60+
bool? skip}) =>
61+
'';
62+
63+
static String gender(String targetGender,
64+
{String? female,
65+
String? male,
66+
required String other,
67+
String? desc,
68+
Map<String, Object>? examples,
69+
String? locale,
70+
String? name,
71+
List<Object>? args,
72+
String? meaning,
73+
bool? skip}) =>
74+
'';
75+
76+
static String select(Object choice, Map<Object, String> cases,
77+
{String? desc,
78+
Map<String, Object>? examples,
79+
String? locale,
80+
String? name,
81+
List<Object>? args,
82+
String? meaning,
83+
bool? skip}) =>
84+
'';
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// import 'package:intl/intl.dart';
2+
3+
class SomeClassI18n {
4+
static final String message = Intl.message(
5+
'message',
6+
name: 'SomeClassI18n_message',
7+
desc: '',
8+
);
9+
10+
static final String message2 = Intl.message(
11+
'message2',
12+
name: 'SomeClassI18n_message2',
13+
);
14+
15+
static String plural = Intl.plural(
16+
1,
17+
one: 'one',
18+
other: 'other',
19+
name: 'SomeClassI18n_plural',
20+
desc: '',
21+
);
22+
23+
static String plural2 = Intl.plural(
24+
2,
25+
one: 'one',
26+
other: 'other',
27+
name: 'SomeClassI18n_plural2',
28+
);
29+
30+
static String gender = Intl.gender(
31+
'other',
32+
female: 'female',
33+
male: 'male',
34+
other: 'other',
35+
name: 'SomeClassI18n_gender',
36+
desc: '',
37+
);
38+
39+
static String gender2 = Intl.gender(
40+
'other',
41+
female: 'female',
42+
male: 'male',
43+
other: 'other',
44+
name: 'SomeClassI18n_gender2',
45+
);
46+
47+
static String select = Intl.select(
48+
true,
49+
{true: 'true', false: 'false'},
50+
name: 'SomeClassI18n_select',
51+
desc: '',
52+
);
53+
54+
static String select2 = Intl.select(
55+
false,
56+
{true: 'true', false: 'false'},
57+
name: 'SomeClassI18n_select',
58+
);
59+
}
60+
61+
class Intl {
62+
Intl();
63+
64+
static String message(String messageText,
65+
{String? desc = '',
66+
Map<String, Object>? examples,
67+
String? locale,
68+
String? name,
69+
List<Object>? args,
70+
String? meaning,
71+
bool? skip}) =>
72+
'';
73+
74+
static String plural(num howMany,
75+
{String? zero,
76+
String? one,
77+
String? two,
78+
String? few,
79+
String? many,
80+
required String other,
81+
String? desc,
82+
Map<String, Object>? examples,
83+
String? locale,
84+
int? precision,
85+
String? name,
86+
List<Object>? args,
87+
String? meaning,
88+
bool? skip}) =>
89+
'';
90+
91+
static String gender(String targetGender,
92+
{String? female,
93+
String? male,
94+
required String other,
95+
String? desc,
96+
Map<String, Object>? examples,
97+
String? locale,
98+
String? name,
99+
List<Object>? args,
100+
String? meaning,
101+
bool? skip}) =>
102+
'';
103+
104+
static String select(Object choice, Map<Object, String> cases,
105+
{String? desc,
106+
Map<String, Object>? examples,
107+
String? locale,
108+
String? name,
109+
List<Object>? args,
110+
String? meaning,
111+
bool? skip}) =>
112+
'';
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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_provide_intl_description/prefer_provide_intl_description_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'avoid_empty_intl_description/examples/example.dart';
8+
const _incorrectExamplePath =
9+
'avoid_empty_intl_description/examples/incorrect_example.dart';
10+
11+
void main() {
12+
group('$PreferProvideIntlDescriptionRule', () {
13+
test('initialization', () async {
14+
final unit = await RuleTestHelper.resolveFromFile(_incorrectExamplePath);
15+
final issues = PreferProvideIntlDescriptionRule().check(unit);
16+
17+
RuleTestHelper.verifyInitialization(
18+
issues: issues,
19+
ruleId: 'avoid-empty-intl-description',
20+
severity: Severity.warning,
21+
);
22+
});
23+
24+
test('reports no issues', () async {
25+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
26+
final issues = PreferProvideIntlDescriptionRule().check(unit);
27+
28+
RuleTestHelper.verifyNoIssues(issues);
29+
});
30+
31+
test('reports about found issues for incorrect names', () async {
32+
final unit = await RuleTestHelper.resolveFromFile(_incorrectExamplePath);
33+
final issues = PreferProvideIntlDescriptionRule().check(unit);
34+
35+
RuleTestHelper.verifyIssues(
36+
issues: issues,
37+
startLines: [4, 10, 15, 23, 30, 39, 47, 54],
38+
startColumns: [33, 34, 26, 27, 26, 27, 26, 27],
39+
locationTexts: [
40+
'Intl.message(\n'
41+
" 'message',\n"
42+
" name: 'SomeClassI18n_message',\n"
43+
" desc: '',\n"
44+
' )',
45+
'Intl.message(\n'
46+
" 'message2',\n"
47+
" name: 'SomeClassI18n_message2',\n"
48+
' )',
49+
'Intl.plural(\n'
50+
' 1,\n'
51+
" one: 'one',\n"
52+
" other: 'other',\n"
53+
" name: 'SomeClassI18n_plural',\n"
54+
" desc: '',\n"
55+
' )',
56+
'Intl.plural(\n'
57+
' 2,\n'
58+
" one: 'one',\n"
59+
" other: 'other',\n"
60+
" name: 'SomeClassI18n_plural2',\n"
61+
' )',
62+
'Intl.gender(\n'
63+
" 'other',\n"
64+
" female: 'female',\n"
65+
" male: 'male',\n"
66+
" other: 'other',\n"
67+
" name: 'SomeClassI18n_gender',\n"
68+
" desc: '',\n"
69+
' )',
70+
'Intl.gender(\n'
71+
" 'other',\n"
72+
" female: 'female',\n"
73+
" male: 'male',\n"
74+
" other: 'other',\n"
75+
" name: 'SomeClassI18n_gender2',\n"
76+
' )',
77+
'Intl.select(\n'
78+
' true,\n'
79+
" {true: 'true', false: 'false'},\n"
80+
" name: 'SomeClassI18n_select',\n"
81+
" desc: '',\n"
82+
' )',
83+
'Intl.select(\n'
84+
' false,\n'
85+
" {true: 'true', false: 'false'},\n"
86+
" name: 'SomeClassI18n_select',\n"
87+
' )',
88+
],
89+
messages: List.filled(
90+
issues.length,
91+
'Prefer provide description for translated message',
92+
),
93+
);
94+
});
95+
});
96+
}

0 commit comments

Comments
 (0)