Add an analysis option allowing linter exceptions to be propagated.

This will be used in internal builds to ensure that an exception
occuring during linting fails the build; this should help us be more
confident in the robustness of the linter code.

Change-Id: I4eaa47044b32b45433a67cc919ad047663dc771c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/208141
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/yaml/analysis_options_generator.dart b/pkg/analysis_server/lib/src/services/completion/yaml/analysis_options_generator.dart
index 7f60edd..bd175b9 100644
--- a/pkg/analysis_server/lib/src/services/completion/yaml/analysis_options_generator.dart
+++ b/pkg/analysis_server/lib/src/services/completion/yaml/analysis_options_generator.dart
@@ -30,6 +30,7 @@
         AnalyzerOptions.chromeOsManifestChecks: EmptyProducer(),
       }),
       AnalyzerOptions.plugins: EmptyProducer(),
+      AnalyzerOptions.propagateLinterExceptions: EmptyProducer(),
       AnalyzerOptions.strong_mode: MapProducer({
         AnalyzerOptions.declarationCasts: EmptyProducer(),
         AnalyzerOptions.implicitCasts: EmptyProducer(),
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
index aec3305..af4dab4 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
@@ -12,6 +12,7 @@
 import 'package:analyzer/src/dart/analysis/file_state.dart';
 import 'package:analyzer/src/dart/analysis/testing_data.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
+import 'package:analyzer/src/dart/ast/utilities.dart';
 import 'package:analyzer/src/dart/constant/compute.dart';
 import 'package:analyzer/src/dart/constant/constant_verifier.dart';
 import 'package:analyzer/src/dart/constant/evaluation.dart';
@@ -351,7 +352,10 @@
     }
 
     // Run lints that handle specific node types.
-    unit.accept(LinterVisitor(nodeRegistry));
+    unit.accept(LinterVisitor(
+        nodeRegistry,
+        LinterExceptionHandler(_analysisOptions.propagateLinterExceptions)
+            .logException));
   }
 
   void _computeVerifyErrors(FileState file, CompilationUnit unit) {
diff --git a/pkg/analyzer/lib/src/dart/ast/utilities.dart b/pkg/analyzer/lib/src/dart/ast/utilities.dart
index a068ad7..4387e37 100644
--- a/pkg/analyzer/lib/src/dart/ast/utilities.dart
+++ b/pkg/analyzer/lib/src/dart/ast/utilities.dart
@@ -1306,6 +1306,12 @@
 ///
 /// Clients may not extend, implement or mix-in this class.
 class LinterExceptionHandler {
+  /// Indicates whether linter exceptions should be propagated to the caller (by
+  /// re-throwing them)
+  final bool propagateLinterExceptions;
+
+  LinterExceptionHandler(this.propagateLinterExceptions);
+
   /// A method that can be passed to the `LinterVisitor` constructor to handle
   /// exceptions that occur during linting.
   void logException(
@@ -1326,6 +1332,9 @@
     // TODO(39284): should this exception be silent?
     AnalysisEngine.instance.instrumentationService.logException(
         SilentException(buffer.toString(), exception, stackTrace));
+    if (propagateLinterExceptions) {
+      throw exception;
+    }
   }
 }
 
diff --git a/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart b/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
index 0b1635c..327af6e 100644
--- a/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
@@ -348,7 +348,10 @@
     }
 
     // Run lints that handle specific node types.
-    unit.accept(LinterVisitor(nodeRegistry));
+    unit.accept(LinterVisitor(
+        nodeRegistry,
+        LinterExceptionHandler(_analysisOptions.propagateLinterExceptions)
+            .logException));
   }
 
   void _computeVerifyErrors(FileState file, CompilationUnit unit) {
diff --git a/pkg/analyzer/lib/src/generated/engine.dart b/pkg/analyzer/lib/src/generated/engine.dart
index 7f04b64..c678842 100644
--- a/pkg/analyzer/lib/src/generated/engine.dart
+++ b/pkg/analyzer/lib/src/generated/engine.dart
@@ -281,6 +281,10 @@
   /// This option is experimental and subject to change.
   bool implicitDynamic = true;
 
+  /// Indicates whether linter exceptions should be propagated to the caller (by
+  /// re-throwing them)
+  bool propagateLinterExceptions = false;
+
   /// A flag indicating whether inference failures are allowed, off by default.
   ///
   /// This option is experimental and subject to change.
@@ -319,6 +323,7 @@
     if (options is AnalysisOptionsImpl) {
       implicitCasts = options.implicitCasts;
       implicitDynamic = options.implicitDynamic;
+      propagateLinterExceptions = options.propagateLinterExceptions;
       strictInference = options.strictInference;
       strictRawTypes = options.strictRawTypes;
     }
@@ -382,6 +387,7 @@
       // Append boolean flags.
       buffer.addBool(implicitCasts);
       buffer.addBool(implicitDynamic);
+      buffer.addBool(propagateLinterExceptions);
       buffer.addBool(strictInference);
       buffer.addBool(strictRawTypes);
       buffer.addBool(useFastaParser);
diff --git a/pkg/analyzer/lib/src/lint/linter_visitor.dart b/pkg/analyzer/lib/src/lint/linter_visitor.dart
index 25afec4..0794a4c 100644
--- a/pkg/analyzer/lib/src/lint/linter_visitor.dart
+++ b/pkg/analyzer/lib/src/lint/linter_visitor.dart
@@ -19,7 +19,7 @@
 
   LinterVisitor(this.registry, [LintRuleExceptionHandler? exceptionHandler])
       : exceptionHandler =
-            exceptionHandler ?? LinterExceptionHandler().logException;
+            exceptionHandler ?? LinterExceptionHandler(true).logException;
 
   @override
   void visitAdjacentStrings(AdjacentStrings node) {
diff --git a/pkg/analyzer/lib/src/task/options.dart b/pkg/analyzer/lib/src/task/options.dart
index 28b4452..2efaaa1 100644
--- a/pkg/analyzer/lib/src/task/options.dart
+++ b/pkg/analyzer/lib/src/task/options.dart
@@ -151,6 +151,8 @@
   /// Ways to say `include`.
   static const List<String> includeSynonyms = ['include', 'true'];
 
+  static const String propagateLinterExceptions = 'propagate-linter-exceptions';
+
   /// Ways to say `true` or `false`.
   static const List<String> trueOrFalse = ['true', 'false'];
 
@@ -163,6 +165,7 @@
     language,
     optionalChecks,
     plugins,
+    propagateLinterExceptions,
     strong_mode,
   ];
 
@@ -805,6 +808,9 @@
       if (feature == AnalyzerOptions.implicitDynamic) {
         options.implicitDynamic = boolValue;
       }
+      if (feature == AnalyzerOptions.propagateLinterExceptions) {
+        options.propagateLinterExceptions = boolValue;
+      }
     }
   }
 
diff --git a/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart b/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
index f0a3fd2..44a7634 100644
--- a/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
@@ -236,6 +236,8 @@
     );
     expect(actual.implicitCasts, expected.implicitCasts);
     expect(actual.implicitDynamic, expected.implicitDynamic);
+    expect(
+        actual.propagateLinterExceptions, expected.propagateLinterExceptions);
     expect(actual.strictInference, expected.strictInference);
     expect(actual.strictRawTypes, expected.strictRawTypes);
   }