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

Commit 5408c36

Browse files
authored
feat: add support for presets (#1025)
* feat: add support for presets * refactor: move reusable code to function * docs: add docs about presets
1 parent c573273 commit 5408c36

File tree

10 files changed

+206
-43
lines changed

10 files changed

+206
-43
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# Changelog
22

3-
## 4.21.0
3+
## Unreleased
44

55
* fix: add rule 'missing-test-assertion' in rules factory.
66
* feat: add static code diagnostic [`missing-test-assertion`](https://dartcodemetrics.dev/docs/rules/common/missing-test-assertion).
7+
* feat: add support for presets
78

89
## 4.20.0
910

lib/presets/all.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dart_code_metrics:
2+
extends:
3+
- package:dart_code_metrics/presets/dart_all.yaml
4+
- package:dart_code_metrics/presets/flutter_all.yaml

lib/presets/dart_all.yaml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
dart_code_metrics:
2+
rules:
3+
- avoid-banned-imports
4+
- avoid-collection-methods-with-unrelated-types
5+
- avoid-duplicate-exports
6+
- avoid-dynamic
7+
- avoid-global-state
8+
- avoid-ignoring-return-values
9+
- avoid-late-keyword
10+
- avoid-missing-enum-constant-in-map
11+
- avoid-nested-conditional-expressions
12+
- avoid-non-ascii-symbols
13+
- avoid-non-null-assertion
14+
- avoid-passing-async-when-sync-expected
15+
- avoid-redundant-async
16+
- avoid-throw-in-catch-block
17+
- avoid-top-level-members-in-tests
18+
- avoid-unnecessary-type-assertions
19+
- avoid-unnecessary-type-casts
20+
- avoid-unrelated-type-assertions
21+
- avoid-unused-parameters
22+
- ban-name
23+
- binary-expression-operand-order
24+
- double-literal-format
25+
- format-comment
26+
- member-ordering-extended
27+
- newline-before-return
28+
- no-boolean-literal-compare
29+
- no-empty-block
30+
- no-equal-arguments
31+
- no-equal-then-else
32+
- no-magic-number
33+
- no-object-declaration
34+
- prefer-async-await
35+
- prefer-commenting-analyzer-ignores
36+
- prefer-conditional-expressions
37+
- prefer-correct-identifier-length
38+
- prefer-correct-test-file-name
39+
- prefer-correct-type-name
40+
- prefer-enums-by-name
41+
- prefer-first
42+
- prefer-immediate-return
43+
- prefer-iterable-of
44+
- prefer-last
45+
- prefer-match-file-name
46+
- prefer-moving-to-variable
47+
- prefer-trailing-comma
48+
- prefer-trailing-comma
49+
- tag-name

lib/presets/flutter_all.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
dart_code_metrics:
2+
rules:
3+
- always-remove-listener
4+
- avoid-border-all
5+
- avoid-returning-widgets
6+
- avoid-shrink-wrap-in-lists
7+
- avoid-unnecessary-setstate
8+
- avoid-use-expanded-as-spacer
9+
- avoid-wrapping-in-padding
10+
- check-for-equals-in-render-object-setters
11+
- consistent-update-render-object
12+
- prefer-const-border-radius
13+
- prefer-correct-edge-insets-constructor
14+
- prefer-extracting-callbacks
15+
- prefer-single-widget-per-file

lib/src/analyzers/lint_analyzer/lint_config.dart

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,17 @@ class LintConfig {
2222
});
2323

2424
/// Creates the config from analysis [options].
25-
factory LintConfig.fromAnalysisOptions(AnalysisOptions options) {
26-
const rootKey = 'dart_code_metrics';
27-
28-
return LintConfig(
29-
excludePatterns: options.readIterableOfString(['analyzer', 'exclude']),
30-
excludeForMetricsPatterns:
31-
options.readIterableOfString([rootKey, 'metrics-exclude']),
32-
metrics: options.readMap([rootKey, 'metrics']),
33-
rules: options.readMapOfMap([rootKey, 'rules']),
34-
excludeForRulesPatterns:
35-
options.readIterableOfString([rootKey, 'rules-exclude']),
36-
antiPatterns: options.readMapOfMap([rootKey, 'anti-patterns']),
37-
);
38-
}
25+
factory LintConfig.fromAnalysisOptions(AnalysisOptions options) => LintConfig(
26+
excludePatterns: options.readIterableOfString(['analyzer', 'exclude']),
27+
excludeForMetricsPatterns: options
28+
.readIterableOfString(['metrics-exclude'], packageRelated: true),
29+
metrics: options.readMap(['metrics'], packageRelated: true),
30+
rules: options.readMapOfMap(['rules'], packageRelated: true),
31+
excludeForRulesPatterns: options
32+
.readIterableOfString(['rules-exclude'], packageRelated: true),
33+
antiPatterns:
34+
options.readMapOfMap(['anti-patterns'], packageRelated: true),
35+
);
3936

4037
/// Creates the config from cli [arguments].
4138
factory LintConfig.fromArgs(ParsedArguments arguments) => LintConfig(

lib/src/config_builder/models/analysis_options.dart

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import '../analysis_options_utils.dart';
1010

1111
const _analysisOptionsFileName = 'analysis_options.yaml';
1212

13+
const _rootKey = 'dart_code_metrics';
14+
1315
/// Class representing dart analysis options.
1416
class AnalysisOptions {
1517
final Map<String, Object> options;
@@ -24,10 +26,16 @@ class AnalysisOptions {
2426
return finalPath == null ? null : p.dirname(finalPath);
2527
}
2628

27-
Iterable<String> readIterableOfString(Iterable<String> pathSegments) {
29+
Iterable<String> readIterableOfString(
30+
Iterable<String> pathSegments, {
31+
bool packageRelated = false,
32+
}) {
33+
final usedSegments =
34+
packageRelated ? [_rootKey, ...pathSegments] : pathSegments;
35+
2836
Object? data = options;
2937

30-
for (final key in pathSegments) {
38+
for (final key in usedSegments) {
3139
if (data is Map<String, Object> && data.containsKey(key)) {
3240
data = data[key];
3341
} else {
@@ -38,10 +46,16 @@ class AnalysisOptions {
3846
return isIterableOfStrings(data) ? (data as Iterable).cast<String>() : [];
3947
}
4048

41-
Map<String, Object> readMap(Iterable<String> pathSegments) {
49+
Map<String, Object> readMap(
50+
Iterable<String> pathSegments, {
51+
bool packageRelated = false,
52+
}) {
53+
final usedSegments =
54+
packageRelated ? [_rootKey, ...pathSegments] : pathSegments;
55+
4256
Object? data = options;
4357

44-
for (final key in pathSegments) {
58+
for (final key in usedSegments) {
4559
if (data is Map<String, Object?> && data.containsKey(key)) {
4660
data = data[key];
4761
} else {
@@ -52,10 +66,16 @@ class AnalysisOptions {
5266
return data is Map<String, Object> ? data : {};
5367
}
5468

55-
Map<String, Map<String, Object>> readMapOfMap(Iterable<String> pathSegments) {
69+
Map<String, Map<String, Object>> readMapOfMap(
70+
Iterable<String> pathSegments, {
71+
bool packageRelated = false,
72+
}) {
73+
final usedSegments =
74+
packageRelated ? [_rootKey, ...pathSegments] : pathSegments;
75+
5676
Object? data = options;
5777

58-
for (final key in pathSegments) {
78+
for (final key in usedSegments) {
5979
if (data is Map<String, Object?> && data.containsKey(key)) {
6080
data = data[key];
6181
} else {
@@ -154,24 +174,49 @@ Map<String, Object> _loadConfigFromYamlFile(
154174
var optionsNode =
155175
node is YamlMap ? yamlMapToDartMap(node) : <String, Object>{};
156176

157-
final includeNode = optionsNode['include'];
158-
if (includeNode is String) {
159-
final packageImport = includeNode.startsWith('package:');
160-
161-
final resolvedUri = packageImport
162-
? converter.uriToPath(Uri.parse(includeNode))
163-
: p.absolute(p.dirname(options.path), includeNode);
177+
final path = optionsNode['include'];
178+
if (path is String) {
179+
optionsNode =
180+
_resolveImportAsOptions(options, converter, path, optionsNode);
181+
}
164182

165-
if (resolvedUri != null) {
166-
final resolvedYamlMap =
167-
_loadConfigFromYamlFile(File(resolvedUri), converter);
168-
optionsNode =
169-
mergeMaps(defaults: resolvedYamlMap, overrides: optionsNode);
170-
}
183+
final rootConfig = optionsNode[_rootKey];
184+
final extendedNode =
185+
rootConfig is Map<String, Object> ? rootConfig['extends'] : null;
186+
final extendConfig = extendedNode is String
187+
? [extendedNode]
188+
: extendedNode is Iterable<Object>
189+
? extendedNode.cast<String>()
190+
: <String>[];
191+
for (final path in extendConfig) {
192+
optionsNode =
193+
_resolveImportAsOptions(options, converter, path, optionsNode);
171194
}
172195

173196
return optionsNode;
174197
} on YamlException catch (e) {
175198
throw FormatException(e.message, e.span);
176199
}
177200
}
201+
202+
Map<String, Object> _resolveImportAsOptions(
203+
File options,
204+
UriConverter converter,
205+
String path,
206+
Map<String, Object> optionsNode,
207+
) {
208+
final packageImport = path.startsWith('package:');
209+
210+
final resolvedUri = packageImport
211+
? converter.uriToPath(Uri.parse(path))
212+
: p.absolute(p.dirname(options.path), path);
213+
214+
if (resolvedUri != null) {
215+
final resolvedYamlMap =
216+
_loadConfigFromYamlFile(File(resolvedUri), converter);
217+
218+
return mergeMaps(defaults: resolvedYamlMap, overrides: optionsNode);
219+
}
220+
221+
return optionsNode;
222+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
include: package:lints/recommended.yaml
2+
3+
analyzer:
4+
exclude:
5+
- example/**
6+
strong-mode:
7+
implicit-casts: false
8+
implicit-dynamic: false
9+
10+
dart_code_metrics:
11+
extends:
12+
- package:test_lints/presets.yaml
13+
metrics-exclude:
14+
- test/**
15+
rules:
16+
- rule-id4
17+
- rule-id5
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dart_code_metrics:
2+
rules:
3+
- rule-id10

test/src/config_builder/models/analysis_options_test.dart

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const _options = {
1212
'strong-mode': {'implicit-casts': false, 'implicit-dynamic': false},
1313
},
1414
'dart_code_metrics': {
15+
'extends': ['package:test_lint/presets.yaml'],
1516
'anti-patterns': {
1617
'anti-pattern-id1': true,
1718
'anti-pattern-id2': false,
@@ -125,6 +126,22 @@ void main() {
125126
equals(['dart_code_metrics']),
126127
);
127128
});
129+
130+
test('extends config', () {
131+
const yamlFilePath =
132+
'./test/resources/analysis_options_with_extends.yaml';
133+
134+
final options = analysisOptionsFromFile(
135+
File(yamlFilePath),
136+
collection.contexts.first,
137+
);
138+
139+
expect(options.options['dart_code_metrics'], contains('rules'));
140+
expect(
141+
(options.options['dart_code_metrics'] as Map<String, Object>)['rules'],
142+
allOf(contains('rule-id10')),
143+
);
144+
});
128145
});
129146

130147
group('AnalysisOptions', () {
@@ -134,15 +151,15 @@ void main() {
134151
expect(options.readIterableOfString([]), isEmpty);
135152
expect(options.readIterableOfString(['key']), isEmpty);
136153
expect(
137-
options.readIterableOfString(['dart_code_metrics', 'anti-patterns']),
154+
options.readIterableOfString(['anti-patterns'], packageRelated: true),
138155
isEmpty,
139156
);
140157
expect(
141158
options.readIterableOfString(['analyzer', 'exclude']),
142159
equals(['test/resources/**']),
143160
);
144161
expect(
145-
options.readIterableOfString(['dart_code_metrics', 'metrics-exclude']),
162+
options.readIterableOfString(['metrics-exclude'], packageRelated: true),
146163
equals(['test/**', 'examples/**']),
147164
);
148165
});
@@ -152,12 +169,13 @@ void main() {
152169

153170
expect(options.readMap([]), equals(_options));
154171
expect(options.readMap(['include']), isEmpty);
172+
expect(options.readMap(['extends'], packageRelated: true), isEmpty);
155173
expect(
156-
options.readMap(['dart_code_metrics', 'metrics-exclude']),
174+
options.readMap(['metrics-exclude'], packageRelated: true),
157175
isEmpty,
158176
);
159177
expect(
160-
options.readMap(['dart_code_metrics', 'rules']),
178+
options.readMap(['rules'], packageRelated: true),
161179
allOf(
162180
containsPair('rule-id1', false),
163181
containsPair('rule-id2', true),
@@ -193,7 +211,7 @@ void main() {
193211
expect(options.readMapOfMap(['key']), isEmpty);
194212

195213
expect(
196-
options.readMapOfMap(['dart_code_metrics', 'rules1']),
214+
options.readMapOfMap(['rules1'], packageRelated: true),
197215
allOf(
198216
containsPair('rule-id1', <String, Object>{}),
199217
containsPair('rule-id2', <String, Object>{}),
@@ -202,26 +220,26 @@ void main() {
202220
);
203221

204222
expect(
205-
options.readMapOfMap(['dart_code_metrics', 'rules2']),
223+
options.readMapOfMap(['rules2'], packageRelated: true),
206224
allOf(
207225
containsPair('rule-id2', <String, Object>{}),
208226
containsPair('rule-id3', <String, Object>{}),
209227
),
210228
);
211229

212230
expect(
213-
options.readMapOfMap(['dart_code_metrics', 'rules3']),
231+
options.readMapOfMap(['rules3'], packageRelated: true),
214232
allOf(
215233
containsPair('rule-id1', <String, Object>{}),
216234
containsPair('rule-id2', {'severity': 'info'}),
217235
containsPair('rule-id3', <String, Object>{}),
218236
),
219237
);
220238

221-
expect(options.readMapOfMap(['dart_code_metrics', 'rules4']), isEmpty);
239+
expect(options.readMapOfMap(['rules4'], packageRelated: true), isEmpty);
222240

223241
expect(
224-
options.readMapOfMap(['dart_code_metrics', 'rules5']),
242+
options.readMapOfMap(['rules5'], packageRelated: true),
225243
allOf(
226244
containsPair('rule-id2', <String, Object>{}),
227245
containsPair('rule-id3', <String, Object>{}),

website/docs/getting-started/configuration.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ To configure the package add a `dart_code_metrics` entry to `analysis_options.ya
88

99
```yaml title="analysis_options.yaml"
1010
dart_code_metrics:
11+
extends:
12+
- ... # configures the list of preset configurations
1113
metrics:
1214
- ... # configures the list of reported metrics
1315
metrics-exclude:
@@ -42,6 +44,18 @@ dart_code_metrics:
4244
- long-parameter-list
4345
```
4446
47+
## Extending an existing configuration preset
48+
49+
To extend an existing preset add a reference to the `.yaml` file with the preset in the `extends` entry. For example:
50+
51+
```yaml title="analysis_options.yaml"
52+
dart_code_metrics:
53+
extends:
54+
- package:dart_code_metrics/presets/all.yaml
55+
```
56+
57+
All predefined preset [can be found here](https://github.com/dart-code-checker/dart-code-metrics/tree/master/lib/presets).
58+
4559
## Configuring a metrics entry {#configuring-a-metrics-entry}
4660

4761
To enable a metric add its id to the `metrics` entry in the `analysis_options.yaml`. All metrics can take a threshold value. If no value was provided, the default value will be used.

0 commit comments

Comments
 (0)