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

Commit 5645400

Browse files
feat: add configuration to prefer-moving-to-variable (#934)
* feat: add configuration to prefer-moving-to-variable * test: update tests * chore: add missed trailing comma Co-authored-by: Dmitry Krutskikh <dmitry.krutskikh@gmail.com>
1 parent 93fb44e commit 5645400

File tree

7 files changed

+101
-19
lines changed

7 files changed

+101
-19
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
## Unreleased
44

5+
* feat: add configuration to [`prefer-moving-to-variable`](https://dartcodemetrics.dev/docs/rules/common/prefer-moving-to-variable).
56
* feat: add static code diagnostic [`avoid-duplicate-exports`](https://dartcodemetrics.dev/docs/rules/common/avoid-duplicate-exports).
67
* feat: add static code diagnostic [`avoid-shrink-wrap-in-lists`](https://dartcodemetrics.dev/docs/rules/flutter/avoid-shrink-wrap-in-lists).
78
* feat: add static code diagnostic [`avoid-top-level-members-in-tests`](https://dartcodemetrics.dev/docs/rules/common/avoid-top-level-members-in-tests).
89
* feat: add static code diagnostic [`prefer-correct-edge-insets-constructor-rule`](https://dartcodemetrics.dev/docs/rules/flutter/prefer-correct-edge-insets-constructor).
910
* feat: add static code diagnostic [`prefer-enums-by-name`](https://dartcodemetrics.dev/docs/rules/common/prefer-enums-by-name).
10-
* fix: add zero exit to command runner.
1111
* feat: add suppressions for `check-unused-files`, `check-unused-code`, `check-unnecessary-nullable` commands.
12+
* fix: add zero exit to command runner
1213

1314
## 4.17.0-dev.1
1415

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
part of 'prefer_moving_to_variable_rule.dart';
2+
3+
class _ConfigParser {
4+
static const _allowedDuplicatedChains = 'allowed-duplicated-chains';
5+
6+
static int? parseAllowedDuplicatedChains(Map<String, Object> config) {
7+
final raw = config[_allowedDuplicatedChains];
8+
9+
return raw is int? ? raw : null;
10+
}
11+
}

lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_moving_to_variable/prefer_moving_to_variable_rule.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import '../../../models/severity.dart';
1111
import '../../models/common_rule.dart';
1212
import '../../rule_utils.dart';
1313

14+
part 'config_parser.dart';
1415
part 'visitor.dart';
1516

1617
class PreferMovingToVariableRule extends CommonRule {
@@ -19,16 +20,20 @@ class PreferMovingToVariableRule extends CommonRule {
1920
static const _warningMessage =
2021
'Prefer moving repeated invocations to variable and use it instead.';
2122

23+
final int? _duplicatesThreshold;
24+
2225
PreferMovingToVariableRule([Map<String, Object> config = const {}])
23-
: super(
26+
: _duplicatesThreshold =
27+
_ConfigParser.parseAllowedDuplicatedChains(config),
28+
super(
2429
id: ruleId,
2530
severity: readSeverity(config, Severity.warning),
2631
excludes: readExcludes(config),
2732
);
2833

2934
@override
3035
Iterable<Issue> check(InternalResolvedUnitResult source) {
31-
final visitor = _Visitor();
36+
final visitor = _Visitor(_duplicatesThreshold);
3237

3338
source.unit.visitChildren(visitor);
3439

lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_moving_to_variable/visitor.dart

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ part of 'prefer_moving_to_variable_rule.dart';
33
class _Visitor extends RecursiveAstVisitor<void> {
44
final _nodes = <AstNode>[];
55

6+
final int? _duplicatesThreshold;
7+
68
Iterable<AstNode> get nodes => _nodes;
79

10+
_Visitor(this._duplicatesThreshold);
11+
812
@override
913
void visitBlockFunctionBody(BlockFunctionBody node) {
10-
final visitor = _BlockVisitor();
14+
final visitor = _BlockVisitor(_duplicatesThreshold);
1115
node.visitChildren(visitor);
1216

1317
_nodes.addAll(visitor.duplicates);
@@ -17,11 +21,22 @@ class _Visitor extends RecursiveAstVisitor<void> {
1721
class _BlockVisitor extends RecursiveAstVisitor<void> {
1822
final Map<String, AstNode> _visitedInvocations = {};
1923
final Set<AstNode> _visitedNodes = {};
20-
final Set<AstNode> _duplicates = {};
24+
final Map<String, Set<AstNode>> _duplicates = {};
25+
26+
final int? _duplicatesThreshold;
27+
28+
Set<AstNode> get duplicates =>
29+
_duplicates.entries.fold(<AstNode>{}, (previousValue, element) {
30+
final duplicatesThreshold = _duplicatesThreshold;
31+
if (duplicatesThreshold == null ||
32+
element.value.length >= duplicatesThreshold) {
33+
previousValue.addAll(element.value);
34+
}
2135

22-
Set<AstNode> get duplicates => _duplicates;
36+
return previousValue;
37+
});
2338

24-
_BlockVisitor();
39+
_BlockVisitor(this._duplicatesThreshold);
2540

2641
@override
2742
void visitPropertyAccess(PropertyAccess node) {
@@ -54,7 +69,11 @@ class _BlockVisitor extends RecursiveAstVisitor<void> {
5469
final isDuplicate =
5570
visitedInvocation != null && _isDuplicate(visitedInvocation, node);
5671
if (isDuplicate) {
57-
_duplicates.addAll([visitedInvocation, node]);
72+
_duplicates.update(
73+
access,
74+
(value) => value..addAll([visitedInvocation, node]),
75+
ifAbsent: () => {visitedInvocation, node},
76+
);
5877
}
5978

6079
if (_visitedNodes.contains(node)) {

test/src/analyzers/lint_analyzer/rules/rules_list/prefer_moving_to_variable/prefer_moving_to_variable_rule_test.dart

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ void main() {
4242
11,
4343
13,
4444
14,
45+
31,
46+
32,
4547
19,
4648
20,
4749
22,
4850
23,
4951
28,
5052
29,
51-
31,
52-
32,
5353
47,
5454
48,
5555
],
@@ -82,14 +82,14 @@ void main() {
8282
'getValue()',
8383
'getValue()',
8484
'getValue()',
85+
'getValue()',
86+
'getValue()',
8587
'Theme.after().value',
8688
'Theme.after().value',
8789
'Theme.from().value',
8890
'Theme.from().value',
8991
'Theme.from().notVoidMethod()',
9092
'Theme.from().notVoidMethod()',
91-
'getValue()',
92-
'getValue()',
9393
"string.indexOf('').sign",
9494
"string.indexOf('').sign",
9595
],
@@ -212,5 +212,35 @@ void main() {
212212

213213
RuleTestHelper.verifyNoIssues(issues);
214214
});
215+
216+
test('reports issues with custom config', () async {
217+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
218+
final config = {
219+
'allowed-duplicated-chains': 3,
220+
};
221+
final issues = PreferMovingToVariableRule(config).check(unit);
222+
223+
RuleTestHelper.verifyIssues(
224+
issues: issues,
225+
startLines: [10, 11, 13, 14, 31, 32],
226+
startColumns: [3, 3, 3, 3, 3, 3],
227+
locationTexts: [
228+
'getValue()',
229+
'getValue()',
230+
'getValue()',
231+
'getValue()',
232+
'getValue()',
233+
'getValue()',
234+
],
235+
messages: [
236+
'Prefer moving repeated invocations to variable and use it instead.',
237+
'Prefer moving repeated invocations to variable and use it instead.',
238+
'Prefer moving repeated invocations to variable and use it instead.',
239+
'Prefer moving repeated invocations to variable and use it instead.',
240+
'Prefer moving repeated invocations to variable and use it instead.',
241+
'Prefer moving repeated invocations to variable and use it instead.',
242+
],
243+
);
244+
});
215245
});
216246
}

website/docs/rules/common/prefer-moving-to-variable.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Prefer moving to variable
22

3+
![Configurable](https://img.shields.io/badge/-configurable-informational)
4+
35
## Rule id {#rule-id}
46

57
prefer-moving-to-variable
@@ -21,6 +23,19 @@ final age = getUser().age;
2123

2224
the rule will suggest to move `getUser()` call to a single variable.
2325

26+
Use `allowed-duplicated-chains` configuration, if you want to set a threshold after which the rule should trigger on duplicated lines.
27+
28+
### Config example {#config-example}
29+
30+
```yaml
31+
dart_code_metrics:
32+
...
33+
rules:
34+
...
35+
- prefer-moving-to-variable:
36+
allowed-duplicated-chains: 3
37+
```
38+
2439
### Example {#example}
2540
2641
Bad:

website/docs/rules/flutter/prefer-extracting-callbacks.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ Style
1414

1515
Warns about inline callbacks in a widget tree and suggests to extract them to widget methods in order to make a `build` method more readable. In addition extracting can help test those methods separately as well.
1616

17-
**NOTE** the rule will not trigger on:
18-
- arrow functions like `onPressed: () => _handler(...)` in order to cover cases when a callback needs a variable from the outside;
19-
- empty blocks.
20-
- Flutter specific: arguments with functions returning `Widget` type (or its subclass) and with first parameter of type `BuildContext`. "Builder" functions is a common pattern in Flutter, for example, [IndexedWidgetBuilder typedef](https://api.flutter.dev/flutter/widgets/IndexedWidgetBuilder.html) is used in [ListView.builder](https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html).
17+
**NOTE** the rule will not trigger on:
18+
19+
- arrow functions like `onPressed: () => _handler(...)` in order to cover cases when a callback needs a variable from the outside;
20+
- empty blocks.
21+
- Flutter specific: arguments with functions returning `Widget` type (or its subclass) and with first parameter of type `BuildContext`. "Builder" functions is a common pattern in Flutter, for example, [IndexedWidgetBuilder typedef](https://api.flutter.dev/flutter/widgets/IndexedWidgetBuilder.html) is used in [ListView.builder](https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html).
2122

2223
Use `ignored-named-arguments` configuration, if you want to ignore specific named parameters.
2324

@@ -45,7 +46,7 @@ class MyWidget extends StatelessWidget {
4546
return TextButton(
4647
style: ...,
4748
onPressed: () { // LINT
48-
// Some
49+
// Some
4950
// Huge
5051
// Callback
5152
},
@@ -67,7 +68,7 @@ class MyWidget extends StatelessWidget {
6768
child: ...
6869
);
6970
}
70-
71+
7172
void handlePressed(BuildContext context) {
7273
...
7374
}
@@ -82,7 +83,7 @@ class MyWidget extends StatelessWidget {
8283
child: ...
8384
);
8485
}
85-
86+
8687
void handlePressed() {
8788
...
8889
}

0 commit comments

Comments
 (0)