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

Commit 4900412

Browse files
author
Roman Petrov
authored
feat: add static code diagnostic prefer-static-class (#1086)
* feat: add static code diagnostic prefer-static-class * fix: address review comments * fix: review comment * fix: review comment
1 parent ef042f2 commit 4900412

File tree

19 files changed

+454
-11
lines changed

19 files changed

+454
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* feat: add static code diagnostic [`arguments-ordering`](https://dartcodemetrics.dev/docs/rules/common/arguments-ordering).
66
* feat: add method call chains support for [`ban-name`](https://dartcodemetrics.dev/docs/rules/common/ban-name).
77
* fix: update `dart_all.yaml` preset to contain missing rules.
8+
* feat: add static code diagnostic [`prefer-static-class`](https://dartcodemetrics.dev/docs/rules/common/prefer-static-class).
9+
* feat: ignore `hcwidget` annotations in ['avoid-returning-widgets'](https://dartcodemetrics.dev/docs/rules/common/avoid-returning-widgets) rule by default.
810

911
## 5.0.1
1012

lib/presets/dart_all.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ dart_code_metrics:
4646
- prefer-last
4747
- prefer-match-file-name
4848
- prefer-moving-to-variable
49-
- prefer-trailing-comma
49+
- prefer-static-class
5050
- prefer-trailing-comma
5151
- tag-name

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import 'rules_list/prefer_match_file_name/prefer_match_file_name_rule.dart';
6363
import 'rules_list/prefer_moving_to_variable/prefer_moving_to_variable_rule.dart';
6464
import 'rules_list/prefer_on_push_cd_strategy/prefer_on_push_cd_strategy_rule.dart';
6565
import 'rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_rule.dart';
66+
import 'rules_list/prefer_static_class/prefer_static_class_rule.dart';
6667
import 'rules_list/prefer_trailing_comma/prefer_trailing_comma_rule.dart';
6768
import 'rules_list/provide_correct_intl_args/provide_correct_intl_args_rule.dart';
6869
import 'rules_list/tag_name/tag_name_rule.dart';
@@ -141,6 +142,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
141142
PreferMovingToVariableRule.ruleId: PreferMovingToVariableRule.new,
142143
PreferOnPushCdStrategyRule.ruleId: PreferOnPushCdStrategyRule.new,
143144
PreferSingleWidgetPerFileRule.ruleId: PreferSingleWidgetPerFileRule.new,
145+
PreferStaticClassRule.ruleId: PreferStaticClassRule.new,
144146
PreferTrailingCommaRule.ruleId: PreferTrailingCommaRule.new,
145147
ProvideCorrectIntlArgsRule.ruleId: ProvideCorrectIntlArgsRule.new,
146148
TagNameRule.ruleId: TagNameRule.new,

lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_returning_widgets/avoid_returning_widgets_rule.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import '../../../models/issue.dart';
1313
import '../../../models/severity.dart';
1414
import '../../models/flutter_rule.dart';
1515
import '../../rule_utils.dart';
16+
import '../common_config.dart';
1617

1718
part 'config_parser.dart';
1819
part 'visit_declaration.dart';

lib/src/analyzers/lint_analyzer/rules/rules_list/avoid_returning_widgets/config_parser.dart

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,12 @@ class _ConfigParser {
44
static const _ignoredNamesConfig = 'ignored-names';
55
static const _ignoredAnnotationsConfig = 'ignored-annotations';
66

7-
static const _defaultIgnoredAnnotations = [
8-
'FunctionalWidget',
9-
'swidget',
10-
'hwidget',
11-
];
12-
137
static Iterable<String> getIgnoredNames(Map<String, Object> config) =>
148
_getIterable(config, _ignoredNamesConfig) ?? [];
159

1610
static Iterable<String> getIgnoredAnnotations(Map<String, Object> config) =>
1711
_getIterable(config, _ignoredAnnotationsConfig) ??
18-
_defaultIgnoredAnnotations;
12+
functionalWidgetAnnotations;
1913

2014
static Iterable<String>? _getIterable(
2115
Map<String, Object> config,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const functionalWidgetAnnotations = [
2+
'FunctionalWidget',
3+
'swidget',
4+
'hwidget',
5+
'hcwidget',
6+
];
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
part of 'prefer_static_class_rule.dart';
2+
3+
class _ConfigParser {
4+
static const _ignorePrivate = 'ignore-private';
5+
static const _ignoreNames = 'ignore-names';
6+
static const _ignoreAnnotations = 'ignore-annotations';
7+
8+
static const _defaultIgnorePrivate = false;
9+
static const _defaultIgnoreNames = <String>[];
10+
static const _defaultIgnoreAnnotations = [
11+
...functionalWidgetAnnotations,
12+
'riverpod',
13+
];
14+
15+
static bool getIgnorePrivate(Map<String, Object> config) =>
16+
config[_ignorePrivate] as bool? ?? _defaultIgnorePrivate;
17+
18+
static Iterable<String> getIgnoreNames(Map<String, Object> config) =>
19+
_getIterable(config, _ignoreNames) ?? _defaultIgnoreNames;
20+
21+
static Iterable<String> getIgnoreAnnotations(Map<String, Object> config) =>
22+
_getIterable(config, _ignoreAnnotations) ?? _defaultIgnoreAnnotations;
23+
24+
static Iterable<String>? _getIterable(
25+
Map<String, Object> config,
26+
String name,
27+
) =>
28+
config[name] is Iterable
29+
? List<String>.from(config[name] as Iterable)
30+
: null;
31+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// ignore_for_file: public_member_api_docs
2+
3+
import 'package:analyzer/dart/ast/ast.dart';
4+
import 'package:analyzer/dart/ast/token.dart';
5+
import 'package:analyzer/dart/ast/visitor.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+
import '../common_config.dart';
15+
16+
part 'config_parser.dart';
17+
part 'visitor.dart';
18+
19+
class PreferStaticClassRule extends CommonRule {
20+
static const String ruleId = 'prefer-static-class';
21+
22+
static const _warningMessage =
23+
'Prefer declaring static class members instead of global functions, variables and constants.';
24+
25+
final bool _ignorePrivate;
26+
final Iterable<String> _ignoreNames;
27+
final Iterable<String> _ignoreAnnotations;
28+
29+
PreferStaticClassRule([Map<String, Object> config = const {}])
30+
: _ignorePrivate = _ConfigParser.getIgnorePrivate(config),
31+
_ignoreAnnotations = _ConfigParser.getIgnoreAnnotations(config),
32+
_ignoreNames = _ConfigParser.getIgnoreNames(config),
33+
super(
34+
id: ruleId,
35+
severity: readSeverity(config, Severity.style),
36+
excludes: readExcludes(config),
37+
includes: readIncludes(config),
38+
);
39+
40+
@override
41+
Map<String, Object?> toJson() {
42+
final json = super.toJson();
43+
json[_ConfigParser._ignorePrivate] = _ignorePrivate;
44+
json[_ConfigParser._ignoreNames] = _ignoreNames.toList();
45+
json[_ConfigParser._ignoreAnnotations] = _ignoreAnnotations.toList();
46+
47+
return json;
48+
}
49+
50+
@override
51+
Iterable<Issue> check(InternalResolvedUnitResult source) {
52+
final visitor = _Visitor(
53+
ignorePrivate: _ignorePrivate,
54+
ignoreNames: _ignoreNames,
55+
ignoreAnnotations: _ignoreAnnotations,
56+
);
57+
58+
source.unit.visitChildren(visitor);
59+
60+
return visitor.declarations
61+
.map((declaration) => createIssue(
62+
rule: this,
63+
location: nodeLocation(
64+
node: declaration,
65+
source: source,
66+
),
67+
message: _warningMessage,
68+
))
69+
.toList(growable: false);
70+
}
71+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
part of 'prefer_static_class_rule.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
_Visitor({
5+
required bool ignorePrivate,
6+
required Iterable<String> ignoreNames,
7+
required Iterable<String> ignoreAnnotations,
8+
}) : _ignorePrivate = ignorePrivate,
9+
_ignoreNames = ignoreNames.map(RegExp.new),
10+
_ignoreAnnotations = ignoreAnnotations;
11+
12+
final bool _ignorePrivate;
13+
final Iterable<RegExp> _ignoreNames;
14+
final Iterable<String> _ignoreAnnotations;
15+
16+
final _declarations = <AstNode>[];
17+
18+
Iterable<AstNode> get declarations => _declarations;
19+
20+
@override
21+
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
22+
super.visitTopLevelVariableDeclaration(node);
23+
24+
if (!_hasIgnoredAnnotation(node) &&
25+
!node.variables.variables
26+
.every((variable) => _shouldIgnoreName(variable.name.lexeme))) {
27+
_declarations.add(node);
28+
}
29+
}
30+
31+
@override
32+
void visitFunctionDeclaration(FunctionDeclaration node) {
33+
super.visitFunctionDeclaration(node);
34+
final nodeName = node.name.lexeme;
35+
if (node.parent is CompilationUnit &&
36+
!_hasIgnoredAnnotation(node) &&
37+
!_shouldIgnoreName(nodeName) &&
38+
!isEntrypoint(nodeName, node.metadata)) {
39+
_declarations.add(node);
40+
}
41+
}
42+
43+
bool _shouldIgnoreName(String name) =>
44+
(_ignorePrivate && Identifier.isPrivateName(name)) ||
45+
_ignoreNames.any((element) => element.hasMatch(name));
46+
47+
bool _hasIgnoredAnnotation(Declaration node) => node.metadata.any(
48+
(node) =>
49+
_ignoreAnnotations.contains(node.name.name) &&
50+
node.atSign.type == TokenType.AT,
51+
);
52+
}

test/src/analyzers/lint_analyzer/rules/rules_list/avoid_returning_widgets/avoid_returning_widgets_rule_test.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ void main() {
2727

2828
RuleTestHelper.verifyIssues(
2929
issues: issues,
30-
startLines: [36, 38, 40, 42, 44, 47, 16, 53, 67],
30+
startLines: [36, 38, 40, 42, 44, 47, 16, 53, 70],
3131
startColumns: [5, 5, 5, 5, 5, 14, 3, 1, 1],
3232
locationTexts: [
3333
'_localBuildMyWidget()',
@@ -73,8 +73,8 @@ void main() {
7373

7474
RuleTestHelper.verifyIssues(
7575
issues: issues,
76-
startLines: [36, 38, 40, 16, 53, 57, 60, 63],
77-
startColumns: [5, 5, 5, 3, 1, 1, 1, 1],
76+
startLines: [36, 38, 40, 16, 53, 57, 60, 63, 66],
77+
startColumns: [5, 5, 5, 3, 1, 1, 1, 1, 1],
7878
locationTexts: [
7979
'_localBuildMyWidget()',
8080
'_getWidgetsIterable()',
@@ -89,6 +89,8 @@ void main() {
8989
'Widget _getStatelessFunctionalWidget() => Container();',
9090
'@hwidget\n'
9191
'Widget _getHookFunctionalWidget() => Container();',
92+
'@hcwidget\n'
93+
'Widget _getHookConsumerFunctionalWidget() => Container();',
9294
],
9395
messages: [
9496
'Avoid returning widgets from a function.',
@@ -99,6 +101,7 @@ void main() {
99101
'Avoid returning widgets from a global function.',
100102
'Avoid returning widgets from a global function.',
101103
'Avoid returning widgets from a global function.',
104+
'Avoid returning widgets from a global function.',
102105
],
103106
);
104107
});

test/src/analyzers/lint_analyzer/rules/rules_list/avoid_returning_widgets/examples/example.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ Widget _getStatelessFunctionalWidget() => Container();
6363
@hwidget
6464
Widget _getHookFunctionalWidget() => Container();
6565

66+
@hcwidget
67+
Widget _getHookConsumerFunctionalWidget() => Container();
68+
6669
// LINT
6770
@ignoredAnnotation
6871
Widget _getWidgetWithIgnoredAnnotation() => Container();
@@ -73,6 +76,7 @@ class FunctionalWidget {
7376

7477
const swidget = FunctionalWidget();
7578
const hwidget = FunctionalWidget();
79+
const hcwidget = FunctionalWidget();
7680

7781
class IgnoredAnnotation {
7882
const IgnoredAnnotation();
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
void main() {}
2+
3+
Future<void> main() {}
4+
5+
class Example {
6+
void classMethod() {}
7+
static void staticClassMethod() {}
8+
var classVariable = 42;
9+
static var staticClassVariable = 42;
10+
static final staticClassFinalVariable = 42;
11+
static const classConstant = 42;
12+
13+
void _privateClassMethod() {}
14+
static void _privateStaticClassMethod() {}
15+
var _privateClassVariable = 42;
16+
static var _privateStaticClassVariable = 42;
17+
static final _privateStaticClassFinalVariable = 42;
18+
static const _privateClassConstant = 42;
19+
}
20+
21+
@FunctionalWidget
22+
Widget functionalWidget() {}
23+
24+
@swidget
25+
Widget statelessWidget() {}
26+
27+
@hwidget
28+
Widget hookWidget() {}
29+
30+
@hcwidget
31+
Widget hookConsumerWidget() {}
32+
33+
@riverpod
34+
int riverpodFunction(riverpodFunction ref) => 0;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@ignoredAnnotation
2+
void globalFunction() {}
3+
4+
@ignoredAnnotation
5+
var globalVariable = 42;
6+
7+
@ignoredAnnotation
8+
final globalFinalVariable = 42;
9+
10+
@ignoredAnnotation
11+
const globalConstant = 42;
12+
13+
@ignoredAnnotation
14+
void _privateGlobalFunction() {}
15+
16+
@ignoredAnnotation
17+
var _privateGlobalVariable = 42;
18+
19+
@ignoredAnnotation
20+
final _privateGlobalFinalVariable = 42;
21+
22+
@ignoredAnnotation
23+
const _privateGlobalConstant = 42;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
final someRiverpodProvider = 42;
2+
3+
final useSomeRiverpodHook = 42;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
void _privateGlobalFunction() {}
2+
3+
var _privateGlobalVariable = 42;
4+
5+
final _privateGlobalFinalVariable = 42;
6+
7+
const _privateGlobalConstant = 42;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
void globalFunction() {} // LINT
2+
var globalVariable = 42; // LINT
3+
final globalFinalVariable = 42; // LINT
4+
const globalConstant = 42; // LINT
5+
6+
void _privateGlobalFunction() {} // LINT
7+
var _privateGlobalVariable = 42; // LINT
8+
final _privateGlobalFinalVariable = 42; // LINT
9+
const _privateGlobalConstant = 42; // LINT

0 commit comments

Comments
 (0)