[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([