Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for configuration error severities in analysis_options.yaml #326

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,24 @@ custom_lint:
some_parameter: "some value"
```

#### Overriding lint error severities

You can also override the severity of lint rules in the `analysis_options.yaml` file.
This allows you to change INFO level lints to WARNING or ERROR, or vice versa:

```yaml
custom_lint:
rules:
- my_lint_rule # enable the rule with default severity
errors:
my_lint_rule: error # Override severity to ERROR
another_rule: warning # Override severity to WARNING
third_rule: info # Override severity to INFO
fourth_rule: none # Suppress the lint entirely
```

The available severity levels are: `error`, `warning`, `info`, and `none`.

### Obtaining the list of lints in the CI

Unfortunately, running `dart analyze` does not pick up our newly defined lints.
Expand Down
4 changes: 4 additions & 0 deletions packages/custom_lint/example/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ linter:
public_member_api_docs: false
avoid_print: false
unreachable_from_main: false

custom_lint:
errors:
riverpod_final_provider: error
148 changes: 148 additions & 0 deletions packages/custom_lint/test/analysis_options_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import 'dart:convert';
import 'dart:io';

import 'package:analyzer/error/error.dart';
import 'package:test/test.dart';

import 'cli_process_test.dart';
import 'create_project.dart';
import 'peer_project_meta.dart';

void main() {
group('Errors severities override', () {
Future<ProcessResult> runProcess(String workingDirectoryPath) async =>
Process.run(
'dart',
[customLintBinPath],
workingDirectory: workingDirectoryPath,
stdoutEncoding: utf8,
stderrEncoding: utf8,
);

Directory createLintUsageWith({
required Uri pluginUri,
required String analysisOptions,
}) =>
createLintUsage(
name: 'test_app',
source: {'lib/main.dart': 'void fn() {}'},
plugins: {'test_lint': pluginUri},
analysisOptions: analysisOptions,
);

Directory createTestPlugin({
ErrorSeverity errorSeverity = ErrorSeverity.INFO,
}) =>
createPlugin(
name: 'test_lint',
main: createPluginSource([
TestLintRule(
code: 'test_lint',
message: 'Test lint message',
errorSeverity: errorSeverity,
),
]),
);
test('correctly applies error severity from analysis_options.yaml',
() async {
final plugin = createTestPlugin(errorSeverity: ErrorSeverity.ERROR);

final app = createLintUsageWith(
pluginUri: plugin.uri,
analysisOptions: '''
custom_lint:
errors:
test_lint: error
''',
);

final process = await runProcess(app.path);

expect(trimDependencyOverridesWarning(process.stderr), isEmpty);
expect(process.stdout, '''
Analyzing...

lib/main.dart:1:6 • Test lint message • test_lint • ERROR

1 issue found.
''');
expect(process.exitCode, 1);
});

test('correctly applies warning severity from analysis_options.yaml',
() async {
final plugin = createTestPlugin();

final app = createLintUsageWith(
pluginUri: plugin.uri,
analysisOptions: '''
custom_lint:
errors:
test_lint: warning
''',
);

final process = await runProcess(app.path);

expect(trimDependencyOverridesWarning(process.stderr), isEmpty);
expect(process.stdout, '''
Analyzing...

lib/main.dart:1:6 • Test lint message • test_lint • WARNING

1 issue found.
''');
expect(process.exitCode, 1);
});

test('correctly applies info severity from analysis_options.yaml',
() async {
final plugin = createTestPlugin();

final app = createLintUsageWith(
pluginUri: plugin.uri,
analysisOptions: '''
custom_lint:
errors:
test_lint: info
''',
);

final process = await runProcess(app.path);

expect(trimDependencyOverridesWarning(process.stderr), isEmpty);
expect(process.stdout, '''
Analyzing...

lib/main.dart:1:6 • Test lint message • test_lint • INFO

1 issue found.
''');
expect(process.exitCode, 1);
});

test('correctly applies none severity from analysis_options.yaml',
() async {
final plugin = createTestPlugin();

final app = createLintUsageWith(
pluginUri: plugin.uri,
analysisOptions: '''
custom_lint:
errors:
test_lint: none
''',
);

final process = await runProcess(app.path);

expect(trimDependencyOverridesWarning(process.stderr), isEmpty);
expect(process.stdout, '''
Analyzing...

No issues found!
''');
expect(process.exitCode, 0);
});
});
}
2 changes: 2 additions & 0 deletions packages/custom_lint/test/create_project.dart
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ Directory createLintUsage({
Directory? parent,
Map<String, Uri> plugins = const {},
Map<String, String> source = const {},
String? analysisOptions,
Map<String, Uri> extraPackageConfig = const {},
bool installAsDevDependency = true,
required String name,
Expand All @@ -239,6 +240,7 @@ analyzer:
plugins:
- custom_lint

${analysisOptions ?? ''}
''',
pubspec: '''
name: $name
Expand Down
1 change: 1 addition & 0 deletions packages/custom_lint_builder/lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,7 @@ class _ClientAnalyzerPlugin extends analyzer_plugin.ServerPlugin {
allAnalysisErrors,
lineInfo: resolver.lineInfo,
options: analysisContext.getAnalysisOptionsForFile(file),
configSeverities: configs.configs.errors,
),
).toNotification(),
),
Expand Down
37 changes: 21 additions & 16 deletions packages/custom_lint_builder/lib/src/custom_analyzer_converter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,23 +72,28 @@ class CustomAnalyzerConverter {
/// start column. If an analysis [options] is provided then the severities of
/// the errors will be altered based on those options.
List<plugin.AnalysisError> convertAnalysisErrors(
List<analyzer.AnalysisError> errors,
{analyzer.LineInfo? lineInfo,
analyzer.AnalysisOptions? options}) {
var serverErrors = <plugin.AnalysisError>[];
for (var error in errors) {
var processor = analyzer.ErrorProcessor.getProcessor(options, error);
if (processor != null) {
var severity = processor.severity;
// Errors with null severity are filtered out.
if (severity != null) {
// Specified severities override.
serverErrors.add(convertAnalysisError(error,
lineInfo: lineInfo, severity: severity));
}
} else {
serverErrors.add(convertAnalysisError(error, lineInfo: lineInfo));
List<analyzer.AnalysisError> errors, {
analyzer.LineInfo? lineInfo,
analyzer.AnalysisOptions? options,
Map<String, analyzer.ErrorSeverity>? configSeverities,
}) {
final serverErrors = <plugin.AnalysisError>[];
for (final error in errors) {
final processor = analyzer.ErrorProcessor.getProcessor(options, error);
final configSeverity = configSeverities?[error.errorCode.name];
// Config severities override processor severities
final severity = configSeverity ?? processor?.severity;

// Errors with none severity are filtered out.
if (severity == analyzer.ErrorSeverity.NONE) {
continue;
}

serverErrors.add(convertAnalysisError(
error,
lineInfo: lineInfo,
severity: severity,
));
}
return serverErrors;
}
Expand Down
33 changes: 32 additions & 1 deletion packages/custom_lint_core/lib/src/configs.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:analyzer/error/error.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
Expand All @@ -17,6 +18,7 @@ class CustomLintConfigs {
required this.verbose,
required this.debug,
required this.rules,
required this.errors,
});

/// Decode a [CustomLintConfigs] from a file.
Expand Down Expand Up @@ -108,11 +110,33 @@ class CustomLintConfigs {
}
}

final errors = <String, ErrorSeverity>{...includedOptions.errors};

final errorsYaml = customLint['errors'] as Object?;
if (errorsYaml is Map) {
for (final entry in errorsYaml.entries) {
final value = entry.value;
if (entry.key case final String key?) {
final severity = ErrorSeverity.values.firstWhereOrNull(
(e) => e.displayName == value,
);
if (severity == null) {
throw ArgumentError(
'Provided error severity: $value specified for key: $key is not valid. '
'Valid error severities are: ${ErrorSeverity.values.map((e) => e.displayName).join(', ')}',
);
}
errors[key] = severity;
}
}
}

return CustomLintConfigs(
enableAllLintRules: enableAllLintRules,
verbose: verbose,
debug: debug,
rules: UnmodifiableMapView(rules),
errors: UnmodifiableMapView(errors),
);
}

Expand All @@ -123,6 +147,7 @@ class CustomLintConfigs {
verbose: false,
debug: false,
rules: {},
errors: {},
);

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

/// A map of lint rules to their severity. This is used to override the severity
/// of a lint rule for a specific lint.
final Map<String, ErrorSeverity> errors;

@override
bool operator ==(Object other) =>
other is CustomLintConfigs &&
other.enableAllLintRules == enableAllLintRules &&
other.verbose == verbose &&
other.debug == debug &&
const MapEquality<String, LintOptions>().equals(other.rules, rules);
const MapEquality<String, LintOptions>().equals(other.rules, rules) &&
const MapEquality<String, ErrorSeverity>().equals(other.errors, errors);

@override
int get hashCode => Object.hash(
enableAllLintRules,
verbose,
debug,
const MapEquality<String, LintOptions>().hash(rules),
const MapEquality<String, ErrorSeverity>().hash(errors),
);
}

Expand Down
Loading