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

Commit 6dbbc52

Browse files
author
Konoshenko Vlad
authored
feat: add static code diagnostic prefer-correct-type-name. (#501)
* feat: add static code diagnostic `prefer-correct-type-name`. * added test cases * review suggestions changed
1 parent 91091c9 commit 6dbbc52

File tree

11 files changed

+466
-0
lines changed

11 files changed

+466
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
* feat: add static code diagnostic `prefer-correct-type-name`.
56
* chore: deprecate documentation in Github repo.
67
* chore: restrict `analyzer` version to `>=2.4.0 <2.7.0`.
78

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import 'rules_list/no_object_declaration/no_object_declaration.dart';
2323
import 'rules_list/prefer_conditional_expressions/prefer_conditional_expressions.dart';
2424
import 'rules_list/prefer_const_border_radius/prefer_const_border_radius.dart';
2525
import 'rules_list/prefer_correct_identifier_length/prefer_correct_identifier_length.dart';
26+
import 'rules_list/prefer_correct_type_name/prefer_correct_type_name.dart';
2627
import 'rules_list/prefer_extracting_callbacks/prefer_extracting_callbacks.dart';
2728
import 'rules_list/prefer_first/prefer_first.dart';
2829
import 'rules_list/prefer_intl_name/prefer_intl_name.dart';
@@ -71,6 +72,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
7172
PreferConstBorderRadiusRule(config),
7273
PreferCorrectIdentifierLength.ruleId: (config) =>
7374
PreferCorrectIdentifierLength(config),
75+
PreferCorrectTypeName.ruleId: (config) => PreferCorrectTypeName(config),
7476
PreferExtractingCallbacksRule.ruleId: (config) =>
7577
PreferExtractingCallbacksRule(config),
7678
PreferFirstRule.ruleId: (config) => PreferFirstRule(config),
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import 'package:analyzer/dart/ast/ast.dart';
2+
3+
import '../../../../../utils/node_utils.dart';
4+
import '../../../lint_utils.dart';
5+
import '../../../metrics/scope_visitor.dart';
6+
import '../../../models/internal_resolved_unit_result.dart';
7+
import '../../../models/issue.dart';
8+
import '../../../models/severity.dart';
9+
import '../../models/common_rule.dart';
10+
import '../../models/rule_documentation.dart';
11+
import '../../rule_utils.dart';
12+
13+
part 'utils/config_parser.dart';
14+
15+
part 'validator.dart';
16+
17+
part 'visitor.dart';
18+
19+
class PreferCorrectTypeName extends CommonRule {
20+
static const String ruleId = 'prefer-correct-type-name';
21+
final _Validator _validator;
22+
23+
PreferCorrectTypeName([Map<String, Object> config = const {}])
24+
: _validator = _Validator(
25+
_ConfigParser.readMaxIdentifierLength(config),
26+
_ConfigParser.readMinIdentifierLength(config),
27+
_ConfigParser.readExcludes(config),
28+
),
29+
super(
30+
id: ruleId,
31+
documentation: const RuleDocumentation(
32+
name: 'Prefer correct type name',
33+
brief:
34+
'Type name should only contain alphanumeric characters, start with an uppercase character and span between min-length and max-length characters in length.',
35+
),
36+
severity: readSeverity(config, Severity.style),
37+
excludes: readExcludes(config),
38+
);
39+
40+
@override
41+
Iterable<Issue> check(InternalResolvedUnitResult source) {
42+
final visitor = _Visitor(_validator);
43+
44+
source.unit.visitChildren(visitor);
45+
46+
return visitor.nodes
47+
.map(
48+
(node) => createIssue(
49+
rule: this,
50+
location: nodeLocation(node: node, source: source),
51+
message:
52+
"The '$node' name should only contain alphanumeric characters, start with an uppercase character and span between ${_validator.minLength} and ${_validator.maxLength} characters in length",
53+
),
54+
)
55+
.toList(growable: false);
56+
}
57+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
part of '../prefer_correct_type_name.dart';
2+
3+
const _defaultMinIdentifierLength = 3;
4+
const _defaultMaxIdentifierLength = 40;
5+
const _defaultExclude = <String>[];
6+
7+
const _minIdentifierLengthLabel = 'min-length';
8+
const _maxIdentifierLengthLabel = 'max-length';
9+
const _excludeLabel = 'excluded';
10+
11+
/// Parser for rule configuration
12+
class _ConfigParser {
13+
/// Read min identifier length from config
14+
static int readMinIdentifierLength(Map<String, Object> config) =>
15+
_parseIntConfig(config[_minIdentifierLengthLabel]) ??
16+
_defaultMinIdentifierLength;
17+
18+
/// Read max identifier length from config
19+
static int readMaxIdentifierLength(Map<String, Object> config) =>
20+
_parseIntConfig(config[_maxIdentifierLengthLabel]) ??
21+
_defaultMaxIdentifierLength;
22+
23+
/// Read excludes list from config
24+
static Iterable<String> readExcludes(Map<String, Object> config) =>
25+
_isIterableOfStrings(config[_excludeLabel])
26+
? (config[_excludeLabel] as Iterable).cast<String>()
27+
: _defaultExclude;
28+
29+
static int? _parseIntConfig(Object? value) =>
30+
value != null ? int.tryParse(value.toString()) : null;
31+
32+
static bool _isIterableOfStrings(Object? object) =>
33+
object is Iterable<Object> && object.every((node) => node is String);
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
part of 'prefer_correct_type_name.dart';
2+
3+
class _Validator {
4+
final int maxLength;
5+
final int minLength;
6+
final Iterable<String> exceptions;
7+
8+
_Validator(this.maxLength, this.minLength, this.exceptions);
9+
10+
bool isValid(String name) =>
11+
exceptions.contains(name) ||
12+
(isUpperCase(name) &&
13+
withoutUnderscore(name).length >= minLength &&
14+
withoutUnderscore(name).length <= maxLength);
15+
16+
bool isUpperCase(String name) {
17+
final className = withoutUnderscore(name);
18+
return className[0] == className[0].toUpperCase();
19+
}
20+
21+
String withoutUnderscore(String name) =>
22+
name.startsWith('_') ? name.replaceFirst('_', '') : name;
23+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
part of 'prefer_correct_type_name.dart';
2+
3+
class _Visitor extends ScopeVisitor {
4+
final _nodes = <SimpleIdentifier>[];
5+
final _Validator validator;
6+
7+
_Visitor(this.validator);
8+
9+
Iterable<SimpleIdentifier> get nodes => _nodes;
10+
11+
@override
12+
void visitEnumDeclaration(EnumDeclaration node) {
13+
super.visitEnumDeclaration(node);
14+
15+
if (!validator.isValid(node.name.name)) {
16+
_nodes.add(node.name);
17+
}
18+
}
19+
20+
@override
21+
void visitExtensionDeclaration(ExtensionDeclaration node) {
22+
super.visitExtensionDeclaration(node);
23+
24+
if (node.name != null && !validator.isValid(node.name!.name)) {
25+
_nodes.add(node.name!);
26+
}
27+
}
28+
29+
@override
30+
void visitMixinDeclaration(MixinDeclaration node) {
31+
super.visitMixinDeclaration(node);
32+
33+
if (!validator.isValid(node.name.name)) {
34+
_nodes.add(node.name);
35+
}
36+
}
37+
38+
@override
39+
void visitClassDeclaration(ClassDeclaration node) {
40+
super.visitClassDeclaration(node);
41+
42+
if (!validator.isValid(node.name.name)) {
43+
_nodes.add(node.name);
44+
}
45+
}
46+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// LINT Check regular class without uppercase
2+
class example {
3+
example();
4+
}
5+
6+
// LINT Private class without uppercase
7+
class _example {
8+
_example();
9+
}
10+
11+
// LINT Private class with short name
12+
class _ex {
13+
_ex();
14+
}
15+
16+
// LINT Class with short name
17+
class ex {
18+
_ex();
19+
}
20+
21+
// LINT Private class with long name
22+
class _ExampleWithLongName {
23+
_ExampleWithLongName();
24+
}
25+
26+
// LINT Class with long name
27+
class ExampleWithLongName {
28+
ExampleWithLongName();
29+
}
30+
31+
// Check private class with name contained in exclude config
32+
class _exampleExclude {
33+
_exampleExclude();
34+
}
35+
36+
// Check regular class without error
37+
class Example {}
38+
39+
// Check private class without error
40+
class _Example {}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// LINT Check regular enum without uppercase
2+
enum example {
3+
param1,
4+
param2,
5+
}
6+
7+
// LINT Private enum without uppercase
8+
enum _example {
9+
param1,
10+
param2,
11+
}
12+
13+
// LINT Private enum with short name
14+
enum _ex {
15+
param1,
16+
param2,
17+
}
18+
19+
// LINT Enum with short name
20+
enum ex {
21+
param1,
22+
param2,
23+
}
24+
25+
// LINT Private enum with long name
26+
enum _ExampleWithLongName {
27+
param1,
28+
param2,
29+
}
30+
31+
// LINT Enum with long name
32+
enum ExampleWithLongName {
33+
param1,
34+
param2,
35+
}
36+
37+
// Check private enum with name contained in exclude config
38+
enum _exampleExclude {
39+
param1,
40+
param2,
41+
}
42+
43+
// Check regular enum without error
44+
enum Example {
45+
param1,
46+
param2,
47+
}
48+
49+
// Check private enum without error
50+
enum _Example {
51+
param1,
52+
param2,
53+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// LINT Check regular class without uppercase
2+
extension example on String {}
3+
4+
// LINT Private extension without uppercase
5+
extension _example on String {}
6+
7+
// LINT Private extension with short name
8+
extension _ex on String {}
9+
10+
// LINT Extension with short name
11+
extension ex on String {}
12+
13+
// LINT Private extension with long name
14+
extension _ExampleWithLongName on String {}
15+
16+
// LINT extension with long name
17+
extension ExampleWithLongName on String {}
18+
19+
// Check private extension with name contained in exclude config
20+
extension _exampleExclude on String {}
21+
22+
// Check regular extension without error
23+
extension Example on String {}
24+
25+
// Check private enum without error
26+
extension _Example on String {}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// LINT Check regular mixin without uppercase
2+
mixin example {}
3+
4+
// LINT Private mixin without uppercase
5+
mixin _example {}
6+
7+
// LINT Private mixin with short name
8+
mixin _ex {}
9+
10+
// LINT mixin with short name
11+
mixin ex {}
12+
13+
// LINT Private mixin with long name
14+
mixin _ExampleWithLongName {}
15+
16+
// LINT Mixin with long name
17+
mixin ExampleWithLongName {}
18+
19+
// Check private mixin with name contained in exclude config
20+
mixin _exampleExclude {}
21+
22+
// Check regular mixin without error
23+
mixin Example {}
24+
25+
// Check private mixin without error
26+
mixin _Example {}

0 commit comments

Comments
 (0)