Skip to content
This repository was archived by the owner on Mar 24, 2026. It is now read-only.

Commit 5351622

Browse files
authored
Merge pull request #326 from Artur-Wisniewski:feat/errors-severities-config
feat: add support for configuration error severities in `analysis_options.yaml`
2 parents 92376da + f03e181 commit 5351622

7 files changed

Lines changed: 322 additions & 8 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,19 @@ custom_lint:
227227
some_parameter: "some value"
228228
```
229229

230+
#### Overriding lint error severities
231+
232+
You can also override the severity of lint rules in the `analysis_options.yaml` file.
233+
This allows you to change INFO level lints to WARNING or ERROR, or vice versa:
234+
235+
```yaml
236+
custom_lint:
237+
errors:
238+
my_lint_rule: error
239+
```
240+
241+
The available severity levels are: `error`, `warning`, `info`, and `ignore`.
242+
230243
### Obtaining the list of lints in the CI
231244

232245
Unfortunately, running `dart analyze` does not pick up our newly defined lints.

packages/custom_lint/example/analysis_options.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ linter:
88
rules:
99
public_member_api_docs: false
1010
avoid_print: false
11-
unreachable_from_main: false
11+
unreachable_from_main: false
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:analyzer/error/error.dart';
5+
import 'package:test/test.dart';
6+
7+
import 'cli_process_test.dart';
8+
import 'create_project.dart';
9+
import 'peer_project_meta.dart';
10+
11+
void main() {
12+
group('Errors severities override', () {
13+
Future<ProcessResult> runProcess(String workingDirectoryPath) async =>
14+
Process.run(
15+
'dart',
16+
[customLintBinPath],
17+
workingDirectory: workingDirectoryPath,
18+
stdoutEncoding: utf8,
19+
stderrEncoding: utf8,
20+
);
21+
22+
Directory createLintUsageWith({
23+
required Uri pluginUri,
24+
required String analysisOptions,
25+
}) =>
26+
createLintUsage(
27+
name: 'test_app',
28+
source: {'lib/main.dart': 'void fn() {}'},
29+
plugins: {'test_lint': pluginUri},
30+
analysisOptions: analysisOptions,
31+
);
32+
33+
Directory createTestPlugin({
34+
ErrorSeverity errorSeverity = ErrorSeverity.INFO,
35+
}) =>
36+
createPlugin(
37+
name: 'test_lint',
38+
main: createPluginSource([
39+
TestLintRule(
40+
code: 'test_lint',
41+
message: 'Test lint message',
42+
errorSeverity: errorSeverity,
43+
),
44+
]),
45+
);
46+
test('correctly applies error severity from analysis_options.yaml',
47+
() async {
48+
final plugin = createTestPlugin();
49+
50+
final app = createLintUsageWith(
51+
pluginUri: plugin.uri,
52+
analysisOptions: '''
53+
custom_lint:
54+
errors:
55+
test_lint: error
56+
''',
57+
);
58+
59+
final process = await runProcess(app.path);
60+
61+
expect(trimDependencyOverridesWarning(process.stderr), isEmpty);
62+
expect(process.stdout, '''
63+
Analyzing...
64+
65+
lib/main.dart:1:6 • Test lint message • test_lint • ERROR
66+
67+
1 issue found.
68+
''');
69+
expect(process.exitCode, 1);
70+
});
71+
72+
test('correctly applies warning severity from analysis_options.yaml',
73+
() async {
74+
final plugin = createTestPlugin();
75+
76+
final app = createLintUsageWith(
77+
pluginUri: plugin.uri,
78+
analysisOptions: '''
79+
custom_lint:
80+
errors:
81+
test_lint: warning
82+
''',
83+
);
84+
85+
final process = await runProcess(app.path);
86+
87+
expect(trimDependencyOverridesWarning(process.stderr), isEmpty);
88+
expect(process.stdout, '''
89+
Analyzing...
90+
91+
lib/main.dart:1:6 • Test lint message • test_lint • WARNING
92+
93+
1 issue found.
94+
''');
95+
expect(process.exitCode, 1);
96+
});
97+
98+
test('correctly applies info severity from analysis_options.yaml',
99+
() async {
100+
final plugin = createTestPlugin();
101+
102+
final app = createLintUsageWith(
103+
pluginUri: plugin.uri,
104+
analysisOptions: '''
105+
custom_lint:
106+
errors:
107+
test_lint: info
108+
''',
109+
);
110+
111+
final process = await runProcess(app.path);
112+
113+
expect(trimDependencyOverridesWarning(process.stderr), isEmpty);
114+
expect(process.stdout, '''
115+
Analyzing...
116+
117+
lib/main.dart:1:6 • Test lint message • test_lint • INFO
118+
119+
1 issue found.
120+
''');
121+
expect(process.exitCode, 1);
122+
});
123+
124+
test('correctly applies ignore severity from analysis_options.yaml',
125+
() async {
126+
final plugin = createTestPlugin();
127+
128+
final app = createLintUsageWith(
129+
pluginUri: plugin.uri,
130+
analysisOptions: '''
131+
custom_lint:
132+
errors:
133+
test_lint: ignore
134+
''',
135+
);
136+
137+
final process = await runProcess(app.path);
138+
139+
expect(trimDependencyOverridesWarning(process.stderr), isEmpty);
140+
expect(process.stdout, '''
141+
Analyzing...
142+
143+
No issues found!
144+
''');
145+
expect(process.exitCode, 0);
146+
});
147+
});
148+
}

packages/custom_lint/test/create_project.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ Directory createLintUsage({
219219
Directory? parent,
220220
Map<String, Uri> plugins = const {},
221221
Map<String, String> source = const {},
222+
String? analysisOptions,
222223
Map<String, Uri> extraPackageConfig = const {},
223224
bool installAsDevDependency = true,
224225
required String name,
@@ -239,6 +240,7 @@ analyzer:
239240
plugins:
240241
- custom_lint
241242
243+
${analysisOptions ?? ''}
242244
''',
243245
pubspec: '''
244246
name: $name

packages/custom_lint_builder/lib/src/client.dart

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -991,11 +991,27 @@ class _ClientAnalyzerPlugin extends analyzer_plugin.ServerPlugin {
991991
CustomLintEvent.analyzerPluginNotification(
992992
analyzer_plugin.AnalysisErrorsParams(
993993
path,
994-
CustomAnalyzerConverter().convertAnalysisErrors(
995-
allAnalysisErrors,
996-
lineInfo: resolver.lineInfo,
997-
options: analysisContext.getAnalysisOptionsForFile(file),
998-
),
994+
CustomAnalyzerConverter()
995+
.convertAnalysisErrors(
996+
allAnalysisErrors,
997+
lineInfo: resolver.lineInfo,
998+
options: analysisContext.getAnalysisOptionsForFile(file),
999+
)
1000+
// Filter out lints with severity: ignore
1001+
.whereNot(
1002+
(error) =>
1003+
configs.configs.errors[error.code] == ErrorSeverity.NONE,
1004+
)
1005+
// Override severities from analysis_options.yaml
1006+
.map((error) {
1007+
var severity = error.severity;
1008+
if (configs.configs.errors[error.code]
1009+
case final ErrorSeverity errorSeverity) {
1010+
severity = CustomAnalyzerConverter()
1011+
.convertErrorSeverity(errorSeverity);
1012+
}
1013+
return error.copyWith(severity: severity);
1014+
}).toList(),
9991015
).toNotification(),
10001016
),
10011017
);
@@ -1241,3 +1257,29 @@ extension on ChangeReporterBuilder {
12411257
);
12421258
}
12431259
}
1260+
1261+
extension on analyzer_plugin.AnalysisError {
1262+
analyzer_plugin.AnalysisError copyWith({
1263+
analyzer_plugin.AnalysisErrorSeverity? severity,
1264+
String? correction,
1265+
analyzer_plugin.Location? location,
1266+
String? message,
1267+
analyzer_plugin.AnalysisErrorType? type,
1268+
String? code,
1269+
String? url,
1270+
List<analyzer_plugin.DiagnosticMessage>? contextMessages,
1271+
bool? hasFix,
1272+
}) {
1273+
return analyzer_plugin.AnalysisError(
1274+
severity ?? this.severity,
1275+
type ?? this.type,
1276+
location ?? this.location,
1277+
message ?? this.message,
1278+
code ?? this.code,
1279+
correction: correction ?? this.correction,
1280+
url: url ?? this.url,
1281+
contextMessages: contextMessages ?? this.contextMessages,
1282+
hasFix: hasFix ?? this.hasFix,
1283+
);
1284+
}
1285+
}

packages/custom_lint_core/lib/src/configs.dart

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:analyzer/error/error.dart';
12
import 'package:analyzer/file_system/file_system.dart';
23
import 'package:collection/collection.dart';
34
import 'package:meta/meta.dart';
@@ -17,6 +18,7 @@ class CustomLintConfigs {
1718
required this.verbose,
1819
required this.debug,
1920
required this.rules,
21+
required this.errors,
2022
});
2123

2224
/// Decode a [CustomLintConfigs] from a file.
@@ -108,11 +110,30 @@ class CustomLintConfigs {
108110
}
109111
}
110112

113+
final errors = <String, ErrorSeverity>{...includedOptions.errors};
114+
115+
if (customLint['errors'] case final YamlMap errorsYaml) {
116+
for (final entry in errorsYaml.entries) {
117+
if (entry.key case final String key) {
118+
errors[key] = switch (entry.value) {
119+
'info' => ErrorSeverity.INFO,
120+
'warning' => ErrorSeverity.WARNING,
121+
'error' => ErrorSeverity.ERROR,
122+
'ignore' => ErrorSeverity.NONE,
123+
_ => throw UnsupportedError(
124+
'Unsupported severity ${entry.value} for key: ${entry.key}',
125+
),
126+
};
127+
}
128+
}
129+
}
130+
111131
return CustomLintConfigs(
112132
enableAllLintRules: enableAllLintRules,
113133
verbose: verbose,
114134
debug: debug,
115135
rules: UnmodifiableMapView(rules),
136+
errors: UnmodifiableMapView(errors),
116137
);
117138
}
118139

@@ -123,6 +144,7 @@ class CustomLintConfigs {
123144
verbose: false,
124145
debug: false,
125146
rules: {},
147+
errors: {},
126148
);
127149

128150
/// A field representing whether to enable/disable lint rules that are not
@@ -147,20 +169,26 @@ class CustomLintConfigs {
147169
/// Whether enable hot-reload and log the VM-service URI.
148170
final bool debug;
149171

172+
/// A map of lint rules to their severity. This is used to override the severity
173+
/// of a lint rule for a specific lint.
174+
final Map<String, ErrorSeverity> errors;
175+
150176
@override
151177
bool operator ==(Object other) =>
152178
other is CustomLintConfigs &&
153179
other.enableAllLintRules == enableAllLintRules &&
154180
other.verbose == verbose &&
155181
other.debug == debug &&
156-
const MapEquality<String, LintOptions>().equals(other.rules, rules);
182+
const MapEquality<String, LintOptions>().equals(other.rules, rules) &&
183+
const MapEquality<String, ErrorSeverity>().equals(other.errors, errors);
157184

158185
@override
159186
int get hashCode => Object.hash(
160187
enableAllLintRules,
161188
verbose,
162189
debug,
163190
const MapEquality<String, LintOptions>().hash(rules),
191+
const MapEquality<String, ErrorSeverity>().hash(errors),
164192
);
165193
}
166194

0 commit comments

Comments
 (0)