[DAS] Trigger incompatible lint for included rules
Fixes: https://github.com/dart-lang/sdk/issues/60125
Change-Id: Icc0b8efda0e7e915d7315a1c2e70c1a7a5093057
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419982
Auto-Submit: Felipe Morschel <git@fmorschel.dev>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart
index 76df52c..35e4dcd 100644
--- a/pkg/analysis_server/lib/src/context_manager.dart
+++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -403,6 +403,7 @@
driver.sourceFactory,
driver.currentSession.analysisContext.contextRoot.root.path,
sdkVersionConstraint,
+ resourceProvider,
);
var converter = AnalyzerConverter();
convertedErrors = converter.convertAnalysisErrors(
diff --git a/pkg/analysis_server/lib/src/handler/legacy/edit_get_fixes.dart b/pkg/analysis_server/lib/src/handler/legacy/edit_get_fixes.dart
index f52ab5f..f05635e 100644
--- a/pkg/analysis_server/lib/src/handler/legacy/edit_get_fixes.dart
+++ b/pkg/analysis_server/lib/src/handler/legacy/edit_get_fixes.dart
@@ -133,6 +133,7 @@
sourceFactory,
analysisContext.contextRoot.root.path,
sdkVersionConstraint,
+ resourceProvider,
);
var options = _getOptions(sourceFactory, content);
if (options == null) {
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/code_actions/analysis_options.dart b/pkg/analysis_server/lib/src/lsp/handlers/code_actions/analysis_options.dart
index 012d4ec..17e2dc7 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/code_actions/analysis_options.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/code_actions/analysis_options.dart
@@ -86,6 +86,7 @@
sourceFactory,
contextRoot.root.path,
sdkVersionConstraint,
+ resourceProvider,
);
var codeActions = <CodeActionWithPriority>[];
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 8b7d89e..ddfde1c 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -85,11 +85,21 @@
status: noFix
notes: |-
The fix needs to be made in the included file.
+AnalysisOptionsWarningCode.INCOMPATIBLE_INCLUDED_LINT:
+ status: noFix
+ notes: |-
+ Without knowing which rule the user would prefer to use, we cannot remove
+ the reported one.
AnalysisOptionsWarningCode.INCOMPATIBLE_LINT:
status: noFix
notes: |-
Without knowing which rule the user would prefer to use, we cannot remove
the reported one.
+AnalysisOptionsWarningCode.INCOMPATIBLE_LINT_FILE:
+ status: noFix
+ notes: |-
+ Without knowing which rule the user would prefer to use, we cannot remove
+ the reported one.
AnalysisOptionsWarningCode.INVALID_OPTION:
status: needsFix
notes: |-
diff --git a/pkg/analysis_server/test/services/linter/linter_test.dart b/pkg/analysis_server/test/services/linter/linter_test.dart
index 7ec8391..15902ac 100644
--- a/pkg/analysis_server/test/services/linter/linter_test.dart
+++ b/pkg/analysis_server/test/services/linter/linter_test.dart
@@ -9,6 +9,7 @@
import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
import 'package:analyzer/src/analysis_options/error/option_codes.dart';
import 'package:analyzer/src/lint/options_rule_validator.dart';
+import 'package:analyzer_testing/resource_provider_mixin.dart';
import 'package:linter/src/rules.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -20,14 +21,17 @@
}
@reflectiveTest
-class LinterRuleOptionsValidatorTest {
+class LinterRuleOptionsValidatorTest with ResourceProviderMixin {
late RecordingErrorListener recorder;
late ErrorReporter reporter;
List<Diagnostic> get errors => recorder.errors;
- LinterRuleOptionsValidator get validator => LinterRuleOptionsValidator();
+ LinterRuleOptionsValidator get validator => LinterRuleOptionsValidator(
+ optionsProvider: AnalysisOptionsProvider(),
+ resourceProvider: resourceProvider,
+ );
void setUp() {
registerLintRules();
diff --git a/pkg/analysis_server/test/src/services/correction/fix/analysis_options/test_support.dart b/pkg/analysis_server/test/src/services/correction/fix/analysis_options/test_support.dart
index 2fb4213..787e404 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/analysis_options/test_support.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/analysis_options/test_support.dart
@@ -52,6 +52,7 @@
sourceFactory,
'/',
dart2_12,
+ resourceProvider,
);
if (diagnosticFilter != null) {
if (errors.length == 1) {
diff --git a/pkg/analyzer/lib/src/analysis_options/error/option_codes.g.dart b/pkg/analyzer/lib/src/analysis_options/error/option_codes.g.dart
index 3e93485..542fbcb 100644
--- a/pkg/analyzer/lib/src/analysis_options/error/option_codes.g.dart
+++ b/pkg/analyzer/lib/src/analysis_options/error/option_codes.g.dart
@@ -154,6 +154,22 @@
/// Parameters:
/// 0: the rule name
/// 1: the incompatible rule
+ static const AnalysisOptionsWarningCode
+ INCOMPATIBLE_INCLUDED_LINT = AnalysisOptionsWarningCode(
+ 'INCOMPATIBLE_LINT',
+ "This file enables '{0}' that is incompatible with '{1}' enabled in "
+ "another include.",
+ correctionMessage:
+ "Try removing one of the incompatible files or locally disable one of "
+ "the conflicting rules.",
+ uniqueName: 'INCOMPATIBLE_INCLUDED_LINT',
+ );
+
+ /// An error code indicating an incompatible rule.
+ ///
+ /// Parameters:
+ /// 0: the rule name
+ /// 1: the incompatible rule
static const AnalysisOptionsWarningCode INCOMPATIBLE_LINT =
AnalysisOptionsWarningCode(
'INCOMPATIBLE_LINT',
@@ -161,6 +177,22 @@
correctionMessage: "Try removing one of the incompatible rules.",
);
+ /// An error code indicating an incompatible rule.
+ ///
+ /// Parameters:
+ /// 0: the rule name
+ /// 1: the incompatible rule
+ /// 2: the path of the file containing the conflicting rule
+ static const AnalysisOptionsWarningCode
+ INCOMPATIBLE_LINT_FILE = AnalysisOptionsWarningCode(
+ 'INCOMPATIBLE_LINT',
+ "The rule '{0}' is incompatible with the rule '{1}' enabled at '{2}'.",
+ correctionMessage:
+ "Try removing the incompatible rule or locally disable the conflicting "
+ "rule.",
+ uniqueName: 'INCOMPATIBLE_LINT_FILE',
+ );
+
/// An error code indicating that a plugin is being configured with an invalid
/// value for an option and a detail message is provided.
///
diff --git a/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart b/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart
index 637e173..9038882 100644
--- a/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart
+++ b/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart
@@ -11,6 +11,7 @@
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart';
import 'package:analyzer/src/error/codes.dart';
+import 'package:yaml/yaml.dart';
/// A factory used to create diagnostics.
class DiagnosticFactory {
@@ -283,6 +284,29 @@
);
}
+ Diagnostic incompatibleIncludedLint({
+ required Source source,
+ required String referenceRule,
+ required String incompatibleRule,
+ required YamlNode reference,
+ required YamlNode incompatible,
+ }) => Diagnostic.tmp(
+ source: source,
+ offset: incompatible.span.start.offset,
+ length: incompatible.span.end.offset,
+ errorCode: AnalysisOptionsWarningCode.INCOMPATIBLE_INCLUDED_LINT,
+ arguments: [referenceRule, incompatibleRule],
+ contextMessages: [
+ DiagnosticMessageImpl(
+ filePath: source.fullName,
+ length: reference.span.length,
+ message: 'The included file with the reference rule.',
+ offset: reference.span.start.offset,
+ url: source.uri.toString(),
+ ),
+ ],
+ );
+
Diagnostic invalidNullAwareAfterShortCircuit(
Source source,
int offset,
diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart
index f8a2d53..931c815 100644
--- a/pkg/analyzer/lib/src/error/error_code_values.g.dart
+++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart
@@ -34,7 +34,9 @@
AnalysisOptionsWarningCode.DUPLICATE_RULE,
AnalysisOptionsWarningCode.INCLUDED_FILE_WARNING,
AnalysisOptionsWarningCode.INCLUDE_FILE_NOT_FOUND,
+ AnalysisOptionsWarningCode.INCOMPATIBLE_INCLUDED_LINT,
AnalysisOptionsWarningCode.INCOMPATIBLE_LINT,
+ AnalysisOptionsWarningCode.INCOMPATIBLE_LINT_FILE,
AnalysisOptionsWarningCode.INVALID_OPTION,
AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT,
AnalysisOptionsWarningCode.MULTIPLE_PLUGINS,
diff --git a/pkg/analyzer/lib/src/lint/options_rule_validator.dart b/pkg/analyzer/lib/src/lint/options_rule_validator.dart
index 24933f7..13844a1 100644
--- a/pkg/analyzer/lib/src/lint/options_rule_validator.dart
+++ b/pkg/analyzer/lib/src/lint/options_rule_validator.dart
@@ -4,25 +4,38 @@
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/source/file_source.dart';
+import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
import 'package:analyzer/src/analysis_options/error/option_codes.dart';
+import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/registry.dart';
import 'package:analyzer/src/lint/state.dart';
import 'package:analyzer/src/plugin/options.dart';
import 'package:analyzer/src/util/yaml.dart';
+import 'package:analyzer/src/utilities/uri_cache.dart';
import 'package:collection/collection.dart';
+import 'package:path/path.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart';
/// Validates `linter` rule configurations.
class LinterRuleOptionsValidator extends OptionsValidator {
+ static const includeKey = 'include';
static const linter = 'linter';
static const rulesKey = 'rules';
+ static final diagnosticFactory = DiagnosticFactory();
+
final VersionConstraint? sdkVersionConstraint;
final bool sourceIsOptionsForContextRoot;
+ final AnalysisOptionsProvider optionsProvider;
+ final ResourceProvider resourceProvider;
LinterRuleOptionsValidator({
+ required this.resourceProvider,
+ required this.optionsProvider,
this.sdkVersionConstraint,
this.sourceIsOptionsForContextRoot = true,
});
@@ -49,29 +62,196 @@
@override
List<Diagnostic> validate(ErrorReporter reporter, YamlMap options) {
+ var includeRules = <YamlScalar, Set<String>>{};
var node = options.valueAt(linter);
+
+ var includeNode = options.valueAt(includeKey);
+ if (includeNode != null) {
+ includeRules = _processIncludes(includeNode, reporter);
+ }
+
if (node is YamlMap) {
var rules = node.valueAt(rulesKey);
- _validateRules(rules, reporter);
+ _validateRules(rules, reporter, includeRules);
}
return const [];
}
- void _validateRules(YamlNode? rules, ErrorReporter reporter) {
- var seenRules = <String>{};
+ Uri? _actualIncludePath(String includePath, Uri? sourceUri) {
+ var (first, last) = (
+ includePath.codeUnits.firstOrNull,
+ includePath.codeUnits.lastOrNull,
+ );
+ if ((first == 0x0022 || first == 0x0027) && first == last) {
+ // The URI begins and ends with either a double quote or single quote
+ // i.e. the value of the "include" field is quoted.
+ includePath = includePath.substring(1, includePath.length - 1);
+ }
- String? findIncompatibleRule(AbstractAnalysisRule rule) {
- for (var incompatibleRule in rule.incompatibleRules) {
- if (seenRules.contains(incompatibleRule)) {
- return incompatibleRule;
- }
- }
+ if (includePath.isEmpty) return null;
+
+ var uri = Uri.parse(includePath);
+ if (uri == sourceUri) {
+ // The URI is the same as the source URI, so we don't need to resolve it.
return null;
}
- void validateRule(YamlNode node, bool enabled) {
+ if (uri.isAbsolute) {
+ // The URI is absolute, so we don't need to resolve it.
+ return uri;
+ }
+
+ if (sourceUri == null) {
+ // The URI is relative, but we don't have a base URI to resolve it
+ // against.
+ return null;
+ }
+
+ return uriCache.resolveRelative(sourceUri, uri);
+ }
+
+ Set<String> _collectRules(YamlNode? rules) {
+ var includeRules = <String>{};
+ if (rules is YamlList) {
+ for (var ruleNode in rules.nodes) {
+ var value = ruleNode.value;
+ if (value != null) {
+ var rule = getRegisteredLint(value as Object);
+ if (rule != null) {
+ includeRules.add(rule.name);
+ }
+ }
+ }
+ } else if (rules is YamlMap) {
+ for (var entry in rules.nodeMap.entries) {
+ var value = entry.key.value as Object?;
+ if (value == null) {
+ continue;
+ }
+ var enabled = entry.value.value;
+ if (enabled is! bool) {
+ continue;
+ }
+ if (enabled) {
+ var rule = getRegisteredLint(value);
+ if (rule != null) {
+ includeRules.add(rule.name);
+ }
+ }
+ }
+ }
+ return includeRules;
+ }
+
+ /// Returns the first rule that is incompatible with the given [rule].
+ ///
+ /// If the rule is found in the [activeRules] set, it returns the rule name
+ /// and null for the file path.
+ ///
+ /// If the rule is found in the [includeRules] map, it returns the file path
+ /// and the rule name.
+ (YamlScalar?, String)? _findIncompatibleRule(
+ AbstractAnalysisRule rule, {
+ required Set<String> activeRules,
+ required Map<YamlScalar, Set<String>> includeRules,
+ }) {
+ for (var incompatibleRule in rule.incompatibleRules) {
+ if (activeRules.contains(incompatibleRule)) {
+ return (null, incompatibleRule);
+ }
+ for (var MapEntry(key: filePath, value: rules) in includeRules.entries) {
+ if (rules.contains(incompatibleRule)) {
+ return (filePath, incompatibleRule);
+ }
+ }
+ }
+ return null;
+ }
+
+ Map<YamlScalar, Set<String>> _processIncludes(
+ YamlNode includeNode,
+ ErrorReporter reporter,
+ ) {
+ var seenRules = <YamlScalar, Set<String>>{};
+ var includes = <(YamlScalar, String)>[];
+ if (includeNode is YamlScalar) {
+ includes.add((includeNode, includeNode.value.toString()));
+ } else if (includeNode is YamlList) {
+ for (var node in includeNode.nodes) {
+ if (node is YamlScalar) {
+ includes.add((node, node.value.toString()));
+ }
+ }
+ }
+
+ var uri = includeNode.span.sourceUrl;
+ for (var (includeNode, includePath) in includes) {
+ File file;
+ try {
+ var pathStr = _actualIncludePath(includePath, uri);
+ if (pathStr == null) continue;
+ file = resourceProvider.getFile(fromUri(pathStr));
+ } catch (_) {
+ // if files are invalid, we ignore them
+ continue;
+ }
+ var includedOptions = optionsProvider.getOptionsFromFile(file);
+ var linterNode = includedOptions.valueAt(linter);
+ if (linterNode is YamlMap) {
+ var rulesNode = linterNode.valueAt(rulesKey);
+ var rules = _collectRules(rulesNode);
+ AbstractAnalysisRule? lintRule;
+ YamlScalar? filePath;
+ String? incompatible;
+ for (var rule in rules) {
+ lintRule = getRegisteredLint(rule);
+ if (lintRule == null) {
+ continue;
+ }
+ var record = _findIncompatibleRule(
+ lintRule,
+ activeRules: {},
+ includeRules: seenRules,
+ );
+ if (record != null) {
+ filePath = record.$1!;
+ incompatible = record.$2;
+ break;
+ }
+ }
+ if (filePath != null && incompatible != null && lintRule != null) {
+ // Report the first incompatible rule found.
+ reporter.reportError(
+ diagnosticFactory.incompatibleIncludedLint(
+ source: FileSource(
+ resourceProvider.getFile(
+ includeNode.span.sourceUrl!.toFilePath(),
+ ),
+ ),
+ referenceRule: lintRule.name,
+ incompatibleRule: incompatible,
+ reference: includeNode,
+ incompatible: filePath,
+ ),
+ );
+ }
+ seenRules[includeNode] = rules;
+ }
+ }
+ return seenRules;
+ }
+
+ void _validateRules(
+ YamlNode? rules,
+ ErrorReporter reporter,
+ Map<YamlScalar, Set<String>> includeRules,
+ ) {
+ var activeRules = <String>{};
+
+ void validateRule(YamlNode node, Object? enabled) {
var value = node.value;
if (value == null) return;
+ if (enabled == null) return;
var rule = getRegisteredLint(value as Object);
if (rule == null) {
@@ -83,15 +263,49 @@
return;
}
- if (enabled) {
- var incompatibleRule = findIncompatibleRule(rule);
- if (incompatibleRule != null) {
- reporter.atSourceSpan(
- node.span,
- AnalysisOptionsWarningCode.INCOMPATIBLE_LINT,
- arguments: [value, incompatibleRule],
- );
- } else if (!seenRules.add(rule.name)) {
+ Object? enabledValue;
+ if (enabled is YamlNode) {
+ enabledValue = enabled.value;
+ } else if (enabled is bool) {
+ enabledValue = enabled;
+ }
+
+ if (enabledValue == null) {
+ return;
+ }
+
+ if (enabledValue is! bool) {
+ var warningNode = enabled is YamlNode ? enabled : node;
+ reporter.atSourceSpan(
+ warningNode.span,
+ AnalysisOptionsWarningCode.UNSUPPORTED_VALUE,
+ arguments: [value, enabledValue, "'true' or 'false'"],
+ );
+ return;
+ }
+
+ if (enabledValue) {
+ var incompatible = _findIncompatibleRule(
+ rule,
+ activeRules: activeRules,
+ includeRules: includeRules,
+ );
+ if (incompatible != null) {
+ var (filePath, incompatibleRule) = incompatible;
+ if (filePath == null) {
+ reporter.atSourceSpan(
+ node.span,
+ AnalysisOptionsWarningCode.INCOMPATIBLE_LINT,
+ arguments: [value, incompatibleRule],
+ );
+ } else {
+ reporter.atSourceSpan(
+ node.span,
+ AnalysisOptionsWarningCode.INCOMPATIBLE_LINT_FILE,
+ arguments: [value, incompatibleRule, filePath.toString()],
+ );
+ }
+ } else if (!activeRules.add(rule.name)) {
reporter.atSourceSpan(
node.span,
AnalysisOptionsWarningCode.DUPLICATE_RULE,
@@ -143,8 +357,8 @@
validateRule(ruleNode, true);
}
} else if (rules is YamlMap) {
- for (var ruleEntry in rules.nodeMap.entries) {
- validateRule(ruleEntry.key, ruleEntry.value.value as bool);
+ for (var MapEntry(:key, :value) in rules.nodeMap.entries) {
+ validateRule(key, value);
}
}
}
diff --git a/pkg/analyzer/lib/src/task/options.dart b/pkg/analyzer/lib/src/task/options.dart
index b348b69..dd856c1 100644
--- a/pkg/analyzer/lib/src/task/options.dart
+++ b/pkg/analyzer/lib/src/task/options.dart
@@ -6,6 +6,7 @@
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/source/error_processor.dart';
import 'package:analyzer/source/source.dart';
import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
@@ -29,6 +30,7 @@
SourceFactory sourceFactory,
String contextRoot,
VersionConstraint? sdkVersionConstraint,
+ ResourceProvider resourceProvider,
) {
List<Diagnostic> errors = [];
Source initialSource = source;
@@ -81,6 +83,8 @@
source,
sdkVersionConstraint: sdkVersionConstraint,
sourceIsOptionsForContextRoot: sourceIsOptionsForContextRoot,
+ optionsProvider: optionsProvider,
+ resourceProvider: resourceProvider,
).validate(options);
addDirectErrorOrIncludedError(
validationErrors,
@@ -394,12 +398,16 @@
this._source, {
VersionConstraint? sdkVersionConstraint,
required bool sourceIsOptionsForContextRoot,
+ required AnalysisOptionsProvider optionsProvider,
+ required ResourceProvider resourceProvider,
}) : _validators = [
AnalyzerOptionsValidator(),
_CodeStyleOptionsValidator(),
_FormatterOptionsValidator(),
_LinterTopLevelOptionsValidator(),
LinterRuleOptionsValidator(
+ resourceProvider: resourceProvider,
+ optionsProvider: optionsProvider,
sdkVersionConstraint: sdkVersionConstraint,
sourceIsOptionsForContextRoot: sourceIsOptionsForContextRoot,
),
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 1fc065f..64d15b1 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -140,6 +140,29 @@
Parameters:
0: the rule name
1: the incompatible rule
+ INCOMPATIBLE_LINT_FILE:
+ sharedName: INCOMPATIBLE_LINT
+ problemMessage: "The rule '{0}' is incompatible with the rule '{1}' enabled at '{2}'."
+ correctionMessage: "Try removing the incompatible rule or locally disable the conflicting rule."
+ hasPublishedDocs: false
+ comment: |-
+ An error code indicating an incompatible rule.
+
+ Parameters:
+ 0: the rule name
+ 1: the incompatible rule
+ 2: the path of the file containing the conflicting rule
+ INCOMPATIBLE_INCLUDED_LINT:
+ sharedName: INCOMPATIBLE_LINT
+ problemMessage: "This file enables '{0}' that is incompatible with '{1}' enabled in another include."
+ correctionMessage: "Try removing one of the incompatible files or locally disable one of the conflicting rules."
+ hasPublishedDocs: false
+ comment: |-
+ An error code indicating an incompatible rule.
+
+ Parameters:
+ 0: the rule name
+ 1: the incompatible rule
INVALID_OPTION:
problemMessage: "Invalid option specified for '{0}': {1}"
hasPublishedDocs: false
diff --git a/pkg/analyzer/test/src/diagnostics/analysis_options/analysis_options_test_support.dart b/pkg/analyzer/test/src/diagnostics/analysis_options/analysis_options_test_support.dart
index f4b9674..0138e92 100644
--- a/pkg/analyzer/test/src/diagnostics/analysis_options/analysis_options_test_support.dart
+++ b/pkg/analyzer/test/src/diagnostics/analysis_options/analysis_options_test_support.dart
@@ -32,12 +32,16 @@
sourceFactory,
'/',
sdkVersionConstraint,
+ resourceProvider,
);
var errorListener = GatheringErrorListener();
errorListener.addAll(diagnostics);
errorListener.assertErrors(expectedErrors);
}
+ Future<void> assertNoErrorsInCode(String code) async =>
+ await assertErrorsInCode(code, const []);
+
ExpectedError error(
DiagnosticCode code,
int offset,
diff --git a/pkg/analyzer/test/src/options/options_rule_validator_test.dart b/pkg/analyzer/test/src/options/options_rule_validator_test.dart
index dc911db..91995f8 100644
--- a/pkg/analyzer/test/src/options/options_rule_validator_test.dart
+++ b/pkg/analyzer/test/src/options/options_rule_validator_test.dart
@@ -4,6 +4,7 @@
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
import 'package:analyzer/src/analysis_options/error/option_codes.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/options_rule_validator.dart';
@@ -45,35 +46,125 @@
@reflectiveTest
class OptionsRuleValidatorIncludedFileTest extends AbstractAnalysisOptionsTest
with OptionsRuleValidatorTestMixin {
- test_deprecated_rule_inInclude_ok() {
+ void test_compatible_multiple_include() {
+ newFile('/included1.yaml', '''
+linter:
+ rules:
+ rule_pos: true
+''');
+ newFile('/included2.yaml', '''
+linter:
+ rules:
+ rule_pos: true
+''');
+ assertNoErrors('''
+include:
+ - included1.yaml
+ - included2.yaml
+''');
+ }
+
+ void test_deprecated_rule_inInclude_ok() {
newFile('/included.yaml', '''
linter:
rules:
- deprecated_lint
''');
- assertErrorsInCode('''
+ assertNoErrorsInCode('''
include: included.yaml
-''', []);
+''');
}
- test_removed_rule_inInclude_ok() {
+ void test_incompatible_multiple_include() {
+ newFile('/included1.yaml', '''
+linter:
+ rules:
+ rule_neg: true
+''');
+ newFile('/included2.yaml', '''
+linter:
+ rules:
+ rule_pos: true
+''');
+ assertErrors(
+ '''
+include:
+ - included1.yaml
+ - included2.yaml
+''',
+ [AnalysisOptionsWarningCode.INCOMPATIBLE_INCLUDED_LINT],
+ );
+ }
+
+ void test_incompatible_noTrigger_invalidMap() {
+ newFile('/included.yaml', '''
+linter:
+ rules:
+ rule_neg: true
+''');
+ assertNoErrors('''
+include: included.yaml
+
+linter:
+ rules:
+ rule_neg: true
+ rule_pos:
+''');
+ }
+
+ void test_incompatible_rule_map_include() {
+ newFile('/included.yaml', '''
+linter:
+ rules:
+ rule_neg: true
+''');
+ assertErrors(
+ '''
+include: included.yaml
+
+linter:
+ rules:
+ rule_pos: true
+''',
+ [AnalysisOptionsWarningCode.INCOMPATIBLE_LINT_FILE],
+ );
+ }
+
+ void test_incompatible_unsuportedValue_invalidMap() {
+ newFile('/included.yaml', '''
+linter:
+ rules:
+ rule_neg: true
+''');
+ assertErrors(
+ '''
+include: included.yaml
+
+linter:
+ rules:
+ rule_pos: invalid_value
+''',
+ [AnalysisOptionsWarningCode.UNSUPPORTED_VALUE],
+ );
+ }
+
+ void test_removed_rule_inInclude_ok() {
newFile('/included.yaml', '''
linter:
rules:
- removed_in_2_12_lint
''');
-
- assertErrorsInCode('''
+ assertNoErrorsInCode('''
include: included.yaml
-''', []);
+''');
}
}
@reflectiveTest
class OptionsRuleValidatorTest extends AbstractAnalysisOptionsTest
with OptionsRuleValidatorTestMixin {
- test_deprecated_rule() {
+ void test_deprecated_rule() {
assertErrors(
'''
linter:
@@ -84,7 +175,7 @@
);
}
- test_deprecated_rule_map() {
+ void test_deprecated_rule_map() {
assertErrors(
'''
linter:
@@ -95,7 +186,7 @@
);
}
- test_deprecated_rule_withReplacement() {
+ void test_deprecated_rule_withReplacement() {
assertErrors(
'''
linter:
@@ -106,7 +197,7 @@
);
}
- test_deprecated_rule_withSince_inCurrentSdk() {
+ void test_deprecated_rule_withSince_inCurrentSdk() {
assertErrors(
'''
linter:
@@ -118,31 +209,23 @@
);
}
- test_deprecated_rule_withSince_notInCurrentSdk() {
- assertErrors(
- '''
+ void test_deprecated_rule_withSince_notInCurrentSdk() {
+ assertNoErrors('''
linter:
rules:
- deprecated_since_3_lint
-''',
- [],
- sdk: Version(2, 17, 0),
- );
+''', sdk: Version(2, 17, 0));
}
- test_deprecated_rule_withSince_unknownSdk() {
- assertErrors(
- '''
+ void test_deprecated_rule_withSince_unknownSdk() {
+ assertNoErrors('''
linter:
rules:
- deprecated_since_3_lint
-''',
- // No error
- [],
- );
+''');
}
- test_duplicated_rule() {
+ void test_duplicated_rule() {
assertErrors(
'''
linter:
@@ -154,7 +237,7 @@
);
}
- test_incompatible_rule() {
+ void test_incompatible_rule() {
assertErrors(
'''
linter:
@@ -166,7 +249,7 @@
);
}
- test_incompatible_rule_map() {
+ void test_incompatible_rule_map() {
assertErrors(
'''
linter:
@@ -178,16 +261,16 @@
);
}
- test_incompatible_rule_map_disabled() {
- assertErrors('''
+ void test_incompatible_rule_map_disabled() {
+ assertNoErrors('''
linter:
rules:
rule_pos: true
rule_neg: false
-''', []);
+''');
}
- test_removed_rule() {
+ void test_removed_rule() {
assertErrors(
'''
linter:
@@ -199,19 +282,15 @@
);
}
- test_removed_rule_notYet_ok() {
- assertErrors(
- '''
+ void test_removed_rule_notYet_ok() {
+ assertNoErrors('''
linter:
rules:
- removed_in_2_12_lint
-''',
- [],
- sdk: Version(2, 11, 0),
- );
+''', sdk: Version(2, 11, 0));
}
- test_replaced_rule() {
+ void test_replaced_rule() {
assertErrors(
'''
linter:
@@ -223,23 +302,23 @@
);
}
- test_stable_rule() {
- assertErrors('''
+ void test_stable_rule() {
+ assertNoErrors('''
linter:
rules:
- stable_lint
-''', []);
+''');
}
- test_stable_rule_map() {
- assertErrors('''
+ void test_stable_rule_map() {
+ assertNoErrors('''
linter:
rules:
stable_lint: true
-''', []);
+''');
}
- test_undefined_rule() {
+ void test_undefined_rule() {
assertErrors(
'''
linter:
@@ -250,7 +329,7 @@
);
}
- test_undefined_rule_map() {
+ void test_undefined_rule_map() {
assertErrors(
'''
linter:
@@ -270,16 +349,26 @@
List<DiagnosticCode> expectedCodes, {
VersionConstraint? sdk,
}) {
- GatheringErrorListener listener = GatheringErrorListener();
- ErrorReporter reporter = ErrorReporter(
- listener,
- StringSource(content, 'analysis_options.yaml'),
+ var listener = GatheringErrorListener();
+ var source = StringSource(content, 'analysis_options.yaml');
+ var reporter = ErrorReporter(listener, source);
+ var validator = LinterRuleOptionsValidator(
+ optionsProvider: AnalysisOptionsProvider(sourceFactory),
+ resourceProvider: resourceProvider,
+ sdkVersionConstraint: sdk,
);
- var validator = LinterRuleOptionsValidator(sdkVersionConstraint: sdk);
- validator.validate(reporter, loadYamlNode(content) as YamlMap);
+ validator.validate(
+ reporter,
+ loadYamlNode(content, sourceUrl: source.uri) as YamlMap,
+ );
listener.assertErrorsWithCodes(expectedCodes);
}
+ /// Assert that when the validator is used on the given [content] no errors
+ /// are produced.
+ void assertNoErrors(String content, {VersionConstraint? sdk}) =>
+ assertErrors(content, const [], sdk: sdk);
+
@override
void setUp() {
registerLintRules([
diff --git a/pkg/analyzer/test/src/task/options_test.dart b/pkg/analyzer/test/src/task/options_test.dart
index 0527b9a..0c22542 100644
--- a/pkg/analyzer/test/src/task/options_test.dart
+++ b/pkg/analyzer/test/src/task/options_test.dart
@@ -73,10 +73,13 @@
}
@reflectiveTest
-class OptionsFileValidatorTest with LintRegistrationMixin {
- final OptionsFileValidator validator = OptionsFileValidator(
+class OptionsFileValidatorTest
+ with LintRegistrationMixin, ResourceProviderMixin {
+ late final OptionsFileValidator validator = OptionsFileValidator(
TestSource(),
sourceIsOptionsForContextRoot: true,
+ optionsProvider: optionsProvider,
+ resourceProvider: resourceProvider,
);
final AnalysisOptionsProvider optionsProvider = AnalysisOptionsProvider();
@@ -620,6 +623,7 @@
sourceFactory,
'/',
null /*sdkVersionConstraint*/,
+ resourceProvider,
);
assertErrorsInList(diagnostics, expectedErrors);
@@ -806,6 +810,7 @@
sourceFactory,
'/',
null /*sdkVersionConstraint*/,
+ resourceProvider,
);
expect(
diagnostics.map((Diagnostic e) => e.errorCode),
diff --git a/pkg/analyzer_cli/lib/src/driver.dart b/pkg/analyzer_cli/lib/src/driver.dart
index 56bf8f4..8ccb8e8 100644
--- a/pkg/analyzer_cli/lib/src/driver.dart
+++ b/pkg/analyzer_cli/lib/src/driver.dart
@@ -282,6 +282,7 @@
analysisDriver.sourceFactory,
contextRoot.root.path,
sdkVersionConstraint,
+ resourceProvider,
);
var analysisOptions = fileResult.analysisOptions;
await formatter.formatErrors([