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

Commit b6a3946

Browse files
feat: add prefer-enums-by-name rule (#919)
* feat: add prefer-enums-by-name rule * chore: fix analyzer issues * docs: add rule docs * docs: update docs Co-authored-by: Dmitry Krutskikh <dmitry.krutskikh@gmail.com>
1 parent 64d39b4 commit b6a3946

File tree

10 files changed

+168
-2
lines changed

10 files changed

+168
-2
lines changed

CHANGELOG.md

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

55
* feat: add static code diagnostic [`avoid-duplicate-exports`](https://dartcodemetrics.dev/docs/rules/common/avoid-duplicate-exports).
6+
* feat: add static code diagnostic [`prefer-enums-by-name`](https://dartcodemetrics.dev/docs/rules/common/prefer-enums-by-name).
67

78
## 4.17.0-dev.1
89

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import 'rules_list/prefer_conditional_expressions/prefer_conditional_expressions
4242
import 'rules_list/prefer_const_border_radius/prefer_const_border_radius_rule.dart';
4343
import 'rules_list/prefer_correct_identifier_length/prefer_correct_identifier_length_rule.dart';
4444
import 'rules_list/prefer_correct_type_name/prefer_correct_type_name_rule.dart';
45+
import 'rules_list/prefer_enums_by_name/prefer_enums_by_name_rule.dart';
4546
import 'rules_list/prefer_extracting_callbacks/prefer_extracting_callbacks_rule.dart';
4647
import 'rules_list/prefer_first/prefer_first_rule.dart';
4748
import 'rules_list/prefer_immediate_return/prefer_immediate_return_rule.dart';
@@ -105,6 +106,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
105106
PreferCorrectIdentifierLengthRule.ruleId:
106107
PreferCorrectIdentifierLengthRule.new,
107108
PreferCorrectTypeNameRule.ruleId: PreferCorrectTypeNameRule.new,
109+
PreferEnumsByNameRule.ruleId: PreferEnumsByNameRule.new,
108110
PreferExtractingCallbacksRule.ruleId: PreferExtractingCallbacksRule.new,
109111
PreferFirstRule.ruleId: PreferFirstRule.new,
110112
PreferImmediateReturnRule.ruleId: PreferImmediateReturnRule.new,
Lines changed: 46 additions & 0 deletions
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+
import 'package:analyzer/dart/element/element.dart';
6+
7+
import '../../../../../utils/node_utils.dart';
8+
import '../../../lint_utils.dart';
9+
import '../../../models/internal_resolved_unit_result.dart';
10+
import '../../../models/issue.dart';
11+
import '../../../models/severity.dart';
12+
import '../../models/common_rule.dart';
13+
import '../../rule_utils.dart';
14+
15+
part 'visitor.dart';
16+
17+
class PreferEnumsByNameRule extends CommonRule {
18+
static const ruleId = 'prefer-enums-by-name';
19+
static const _warningMessage = 'Prefer using values.byName';
20+
21+
PreferEnumsByNameRule([Map<String, Object> config = const {}])
22+
: super(
23+
id: ruleId,
24+
severity: readSeverity(config, Severity.style),
25+
excludes: readExcludes(config),
26+
);
27+
28+
@override
29+
Iterable<Issue> check(InternalResolvedUnitResult source) {
30+
final visitor = _Visitor();
31+
32+
source.unit.visitChildren(visitor);
33+
34+
return visitor.invocations
35+
.map((invocation) => createIssue(
36+
rule: this,
37+
location: nodeLocation(
38+
node: invocation,
39+
source: source,
40+
withCommentOrMetadata: false,
41+
),
42+
message: _warningMessage,
43+
))
44+
.toList(growable: false);
45+
}
46+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
part of 'prefer_enums_by_name_rule.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
final _invocations = <MethodInvocation>[];
5+
6+
Iterable<MethodInvocation> get invocations => _invocations;
7+
8+
@override
9+
void visitMethodInvocation(MethodInvocation node) {
10+
super.visitMethodInvocation(node);
11+
12+
final target = node.target;
13+
if (target is PrefixedIdentifier) {
14+
if (_isEnum(target.prefix) && _hasValuesTarget(target.identifier)) {
15+
if (node.methodName.name == 'firstWhere') {
16+
_invocations.add(node);
17+
}
18+
}
19+
}
20+
}
21+
22+
bool _isEnum(SimpleIdentifier prefix) =>
23+
prefix.staticElement?.kind == ElementKind.ENUM;
24+
25+
bool _hasValuesTarget(SimpleIdentifier identifier) =>
26+
identifier.name == 'values';
27+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import 'dart:async';
2+
3+
void main() async {
4+
SomeEnums.values.byName('first');
5+
// LINT
6+
SomeEnums.values.firstWhere((element) => element.name == 'first');
7+
// LINT
8+
SomeEnums.values
9+
.firstWhere((element) => element.name == 'second', orElse: () => null);
10+
}
11+
12+
enum SomeEnums { first, second }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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_enums_by_name/prefer_enums_by_name_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'prefer_enums_by_name/examples/example.dart';
8+
9+
void main() {
10+
group('PreferEnumsByNameRule', () {
11+
test('initialization', () async {
12+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
13+
final issues = PreferEnumsByNameRule().check(unit);
14+
15+
RuleTestHelper.verifyInitialization(
16+
issues: issues,
17+
ruleId: 'prefer-enums-by-name',
18+
severity: Severity.style,
19+
);
20+
});
21+
22+
test('reports about found issues', () async {
23+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
24+
final issues = PreferEnumsByNameRule().check(unit);
25+
26+
RuleTestHelper.verifyIssues(
27+
issues: issues,
28+
startLines: [6, 8],
29+
startColumns: [3, 3],
30+
locationTexts: [
31+
"SomeEnums.values.firstWhere((element) => element.name == 'first')",
32+
'SomeEnums.values\n'
33+
" .firstWhere((element) => element.name == 'second', orElse: () => null)",
34+
],
35+
messages: [
36+
'Prefer using values.byName',
37+
'Prefer using values.byName',
38+
],
39+
);
40+
});
41+
});
42+
}

website/docs/rules/common/no-magic-number.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Warning
1414

1515
Warns against using number literals outside of named constants or variables. Exceptions are made for common constants (by default: -1, 0 and 1) and for literals inside `DateTime` constructor as there is no way to create `const DateTime` and extracting each `int` argument to separate named constant is far too inconvenient.
1616

17-
## Example {#example}
17+
### Example {#example}
1818

1919
Bad:
2020

website/docs/rules/common/no-object-declaration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Style
1212

1313
Warns when a class member is declared with Object type.
1414

15-
## Example {#example}
15+
### Example {#example}
1616

1717
Bad:
1818

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Prefer enums byName
2+
3+
## Rule id {#rule-id}
4+
5+
prefer-enums-by-name
6+
7+
## Severity {#severity}
8+
9+
Style
10+
11+
## Description {#description}
12+
13+
Since Dart 2.15 it's possible to use `byName` method on enum `values` prop instead of searching the value with `firstWhere`.
14+
15+
**Note:** `byName` will throw an exception if the enum does not contain a value for the given name.
16+
17+
### Example {#example}
18+
19+
Bad:
20+
21+
```dart
22+
// LINT
23+
final styleDefinition = StyleDefinition.values.firstWhere(
24+
(def) => def.name == json['styleDefinition'],
25+
);
26+
```
27+
28+
Good:
29+
30+
```dart
31+
final styleDefinition = StyleDefinition.values.byName(json['styleDefinition']);
32+
```

website/docs/rules/overview.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ Rules configuration is [described here](../getting-started/configuration#configu
147147

148148
Type name should only contain alphanumeric characters, start with an uppercase character and span between min-length and max-length characters in length.
149149

150+
- [prefer-enums-by-name](./common/prefer-enums-by-name.md)
151+
152+
Since Dart 2.15 it's possible to use `byName` method on enum `values` prop instead of searching the value with `firstWhere`.
153+
150154
- [prefer-first](./common/prefer-first.md) &nbsp; ![Has auto-fix](https://img.shields.io/badge/-has%20auto--fix-success)
151155

152156
Use `first` to gets the first element.

0 commit comments

Comments
 (0)