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

Commit 448b1f6

Browse files
author
Konoshenko Vlad
authored
feat: add static code diagnostics avoid-global-state. (#624)
1 parent 720937a commit 448b1f6

File tree

8 files changed

+171
-0
lines changed

8 files changed

+171
-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 diagnostics `avoid-global-state`.
56
* chore: migrate from deprecated api
67
* fix: fixed issue with type check in prefer-match-file-name
78
* doc: add flutter favorite badge

analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ dart_code_metrics:
4747
- avoid-unused-parameters
4848
- avoid-unnecessary-type-assertions
4949
- avoid-unnecessary-type-casts
50+
- avoid-global-state
5051
- binary-expression-operand-order
5152
- double-literal-format
5253
- newline-before-return

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'models/rule.dart';
22
import 'rules_list/always_remove_listener/always_remove_listener_rule.dart';
33
import 'rules_list/avoid-unnecessary-type-casts/avoid_unnecessary_type_casts_rule.dart';
4+
import 'rules_list/avoid_global_state/avoid_global_state_rule.dart';
45
import 'rules_list/avoid_ignoring_return_values/avoid_ignoring_return_values_rule.dart';
56
import 'rules_list/avoid_late_keyword/avoid_late_keyword_rule.dart';
67
import 'rules_list/avoid_missing_enum_constant_in_map/avoid_missing_enum_constant_in_map_rule.dart';
@@ -43,6 +44,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
4344
AlwaysRemoveListenerRule.ruleId: (config) => AlwaysRemoveListenerRule(config),
4445
AvoidUnnecessaryTypeCastsRule.ruleId: (config) =>
4546
AvoidUnnecessaryTypeCastsRule(config),
47+
AvoidGlobalStateRule.ruleId: (config) => AvoidGlobalStateRule(config),
4648
AvoidIgnoringReturnValuesRule.ruleId: (config) =>
4749
AvoidIgnoringReturnValuesRule(config),
4850
AvoidLateKeywordRule.ruleId: (config) => AvoidLateKeywordRule(config),
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 AvoidGlobalStateRule extends CommonRule {
18+
static const String ruleId = 'avoid-global-state';
19+
20+
static const _warning =
21+
'Avoid using global variable without const or final keywords.';
22+
23+
AvoidGlobalStateRule([Map<String, Object> config = const {}])
24+
: super(
25+
id: ruleId,
26+
severity: readSeverity(config, Severity.warning),
27+
excludes: readExcludes(config),
28+
);
29+
30+
@override
31+
Iterable<Issue> check(InternalResolvedUnitResult source) {
32+
final visitor = _Visitor();
33+
34+
source.unit.visitChildren(visitor);
35+
36+
return visitor.declarations
37+
.map((declaration) => createIssue(
38+
rule: this,
39+
location: nodeLocation(
40+
node: declaration,
41+
source: source,
42+
),
43+
message: _warning,
44+
))
45+
.toList(growable: false);
46+
}
47+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
part of 'avoid_global_state_rule.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
final _declarations = <AstNode>[];
5+
6+
Iterable<AstNode> get declarations => _declarations;
7+
8+
@override
9+
void visitVariableDeclaration(VariableDeclaration node) {
10+
super.visitVariableDeclaration(node);
11+
12+
if (!node.isFinal &&
13+
!node.isConst &&
14+
node.declaredElement?.enclosingElement is CompilationUnitElement) {
15+
_declarations.add(node);
16+
}
17+
}
18+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
@TestOn('vm')
2+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart';
3+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/avoid_global_state/avoid_global_state_rule.dart';
4+
import 'package:test/test.dart';
5+
6+
import '../../../../../helpers/rule_test_helper.dart';
7+
8+
const _examplePath = 'avoid_global_state/examples/example.dart';
9+
10+
void main() {
11+
group('AvoidGlobalStateRule', () {
12+
test('initialization', () async {
13+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
14+
final issues = AvoidGlobalStateRule().check(unit);
15+
16+
RuleTestHelper.verifyInitialization(
17+
issues: issues,
18+
ruleId: 'avoid-global-state',
19+
severity: Severity.warning,
20+
);
21+
});
22+
23+
test('reports about found issues', () async {
24+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
25+
final issues = AvoidGlobalStateRule().check(unit);
26+
27+
RuleTestHelper.verifyIssues(
28+
issues: issues,
29+
startOffsets: [4, 29],
30+
startLines: [1, 2],
31+
startColumns: [5, 5],
32+
endOffsets: [15, 87],
33+
locationTexts: [
34+
'answer = 42',
35+
'evenNumbers = [1, 2, 3].where((element) => element.isEven)',
36+
],
37+
messages: [
38+
'Avoid using global variable without const or final keywords.',
39+
'Avoid using global variable without const or final keywords.',
40+
],
41+
);
42+
});
43+
});
44+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
var answer = 42; // LINT
2+
var evenNumbers = [1, 2, 3].where((element) => element.isEven); // LINT
3+
const a = 1;
4+
final b = 1;
5+
6+
void function() {}
7+
8+
class Foo {
9+
static int? a;
10+
final int? b;
11+
dynamic c;
12+
const d = 1;
13+
void function() {}
14+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Avoid global state
2+
3+
## Rule id {#rule-id}
4+
5+
avoid-global-state
6+
7+
## Severity {#severity}
8+
9+
Warning
10+
11+
## Description {#description}
12+
13+
The rule should violate on not final and non-const top-level variables.
14+
15+
Having many mutable global variables inside application is a pretty bad practice:
16+
- application state becomes distributed between multiple files
17+
- application state is not protected: it can be modified in almost any place
18+
- it might be hard to debug such applications
19+
20+
So the common practice is to use state management solutions instead of mutable global variables.
21+
22+
### Example {#example}
23+
24+
Bad:
25+
26+
```dart
27+
var answer = 42; // LINT
28+
var evenNumbers = [1, 2, 3].where((element) => element.isEven); // LINT
29+
30+
class Foo {
31+
static int? bar; // LINT
32+
}
33+
```
34+
35+
Good:
36+
37+
```dart
38+
const answer = 42;
39+
final evenNumbers = [1, 2, 3].where((element) => element.isEven);
40+
41+
class Foo {
42+
static int bar = 42;
43+
}
44+
```

0 commit comments

Comments
 (0)