Permissive mode reports the exceptions that were thrown and ignored

Change-Id: I1876577acc43ba076ff4eec3b2891e329c109c24
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105407
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/edit/edit_dartfix.dart b/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
index 0525b77..d14c29a 100644
--- a/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
@@ -132,6 +132,7 @@
         listener.otherSuggestions,
         hasErrors,
         listener.sourceChange.edits,
+        details: listener.details,
       ).toResponse(request.id);
     } finally {
       server.contextManager.driverMap.values
@@ -143,6 +144,7 @@
       listener.otherSuggestions,
       hasErrors,
       listener.sourceChange.edits,
+      details: listener.details,
     ).toResponse(request.id);
   }
 
diff --git a/pkg/analysis_server/lib/src/edit/fix/dartfix_listener.dart b/pkg/analysis_server/lib/src/edit/fix/dartfix_listener.dart
index c1e9639..58657c0 100644
--- a/pkg/analysis_server/lib/src/edit/fix/dartfix_listener.dart
+++ b/pkg/analysis_server/lib/src/edit/fix/dartfix_listener.dart
@@ -17,8 +17,19 @@
   final List<DartFixSuggestion> otherSuggestions = <DartFixSuggestion>[];
   final SourceChange sourceChange = new SourceChange('dartfix');
 
+  /// The details to be returned to the client.
+  List<String> details = [];
+
   DartFixListener(this.server);
 
+  /// Add the given [detail] to the list of details to be returned to the
+  /// client.
+  void addDetail(String detail) {
+    if (details.length < 200) {
+      details.add(detail);
+    }
+  }
+
   /// Record an edit to be sent to the client.
   ///
   /// The associated suggestion should be separately added by calling
diff --git a/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart b/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart
index 62161e4..44970b6 100644
--- a/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart
+++ b/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart
@@ -11,8 +11,8 @@
 import 'package:analyzer/src/dart/analysis/experiments.dart';
 import 'package:analyzer/src/task/options.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
-import 'package:yaml/yaml.dart';
 import 'package:source_span/source_span.dart';
+import 'package:yaml/yaml.dart';
 
 /// [NonNullableFix] visits each named type in a resolved compilation unit
 /// and determines whether the associated variable or parameter can be null
@@ -143,20 +143,6 @@
     }
   }
 
-  void processYamlException(String action, String optionsFilePath, error) {
-    listener.addRecommendation('''Failed to $action options file
-  $optionsFilePath
-  $error
-
-  Manually update this file to enable non-nullable by adding:
-
-    analyzer:
-      enable-experiment:
-        - non-nullable
-''');
-    _packageIsNNBD = false;
-  }
-
   @override
   Future<void> processUnit(int phase, ResolvedUnitResult result) async {
     if (!_packageIsNNBD) {
@@ -174,6 +160,20 @@
     }
   }
 
+  void processYamlException(String action, String optionsFilePath, error) {
+    listener.addRecommendation('''Failed to $action options file
+  $optionsFilePath
+  $error
+
+  Manually update this file to enable non-nullable by adding:
+
+    analyzer:
+      enable-experiment:
+        - non-nullable
+''');
+    _packageIsNNBD = false;
+  }
+
   static void task(DartFixRegistrar registrar, DartFixListener listener) {
     registrar.registerCodeTask(new NonNullableFix(listener));
   }
@@ -185,6 +185,11 @@
   NullabilityMigrationAdapter(this.listener);
 
   @override
+  void addDetail(String detail) {
+    listener.addDetail(detail);
+  }
+
+  @override
   void addEdit(SingleNullabilityFix fix, SourceEdit edit) {
     listener.addEditWithoutSuggestion(fix.source, edit);
   }
diff --git a/pkg/analysis_server/lib/src/nullability/graph_builder.dart b/pkg/analysis_server/lib/src/nullability/graph_builder.dart
index 232ec55..feddad2 100644
--- a/pkg/analysis_server/lib/src/nullability/graph_builder.dart
+++ b/pkg/analysis_server/lib/src/nullability/graph_builder.dart
@@ -7,6 +7,7 @@
 import 'package:analysis_server/src/nullability/expression_checks.dart';
 import 'package:analysis_server/src/nullability/node_builder.dart';
 import 'package:analysis_server/src/nullability/nullability_node.dart';
+import 'package:analysis_server/src/nullability/provisional_api.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
@@ -29,7 +30,7 @@
   /// previous pass over the source code).
   final VariableRepository _variables;
 
-  final bool _permissive;
+  final NullabilityMigrationListener /*?*/ listener;
 
   final NullabilityGraph _graph;
 
@@ -74,7 +75,7 @@
   bool _inConditionalControlFlow = false;
 
   GraphBuilder(TypeProvider typeProvider, this._variables, this._graph,
-      this._source, this._permissive)
+      this._source, this.listener)
       : _notNullType =
             DecoratedType(typeProvider.objectType, NullabilityNode.never),
         _nonNullableBoolType =
@@ -404,10 +405,14 @@
 
   @override
   DecoratedType visitNode(AstNode node) {
-    if (_permissive) {
+    if (listener != null) {
       try {
         return super.visitNode(node);
-      } catch (_) {
+      } catch (exception, stackTrace) {
+        listener.addDetail('''
+$exception
+
+$stackTrace''');
         return null;
       }
     } else {
diff --git a/pkg/analysis_server/lib/src/nullability/node_builder.dart b/pkg/analysis_server/lib/src/nullability/node_builder.dart
index 7d322be..6d91d13 100644
--- a/pkg/analysis_server/lib/src/nullability/node_builder.dart
+++ b/pkg/analysis_server/lib/src/nullability/node_builder.dart
@@ -6,6 +6,7 @@
 import 'package:analysis_server/src/nullability/decorated_type.dart';
 import 'package:analysis_server/src/nullability/expression_checks.dart';
 import 'package:analysis_server/src/nullability/nullability_node.dart';
+import 'package:analysis_server/src/nullability/provisional_api.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
@@ -36,13 +37,13 @@
   /// parameters?
   DecoratedType _currentFunctionType;
 
-  final bool _permissive;
+  final NullabilityMigrationListener /*?*/ listener;
 
   final NullabilityGraph _graph;
 
   final TypeProvider _typeProvider;
 
-  NodeBuilder(this._variables, this._source, this._permissive, this._graph,
+  NodeBuilder(this._variables, this._source, this.listener, this._graph,
       this._typeProvider);
 
   /// Creates and stores a [DecoratedType] object corresponding to the given
@@ -98,10 +99,14 @@
 
   @override
   DecoratedType visitNode(AstNode node) {
-    if (_permissive) {
+    if (listener != null) {
       try {
         return super.visitNode(node);
-      } catch (_) {
+      } catch (exception, stackTrace) {
+        listener.addDetail('''
+$exception
+
+$stackTrace''');
         return null;
       }
     } else {
diff --git a/pkg/analysis_server/lib/src/nullability/provisional_api.dart b/pkg/analysis_server/lib/src/nullability/provisional_api.dart
index 02b8921..bf50955 100644
--- a/pkg/analysis_server/lib/src/nullability/provisional_api.dart
+++ b/pkg/analysis_server/lib/src/nullability/provisional_api.dart
@@ -72,7 +72,7 @@
   /// is fully implemented.
   NullabilityMigration(this.listener, {bool permissive: false})
       : _analyzerMigration =
-            analyzer.NullabilityMigration(permissive: permissive);
+            analyzer.NullabilityMigration(permissive ? listener : null);
 
   void finish() {
     for (var entry in _analyzerMigration.finish().entries) {
@@ -99,6 +99,10 @@
 /// [NullabilityMigrationListener] is used by [NullabilityMigration]
 /// to communicate source changes or "fixes" to the client.
 abstract class NullabilityMigrationListener {
+  /// Add the given [detail] to the list of details to be returned to the
+  /// client.
+  void addDetail(String detail);
+
   /// [addEdit] is called once for each source edit, in the order in which they
   /// appear in the source file.
   void addEdit(SingleNullabilityFix fix, SourceEdit edit);
diff --git a/pkg/analysis_server/lib/src/nullability/transitional_api.dart b/pkg/analysis_server/lib/src/nullability/transitional_api.dart
index 69b4d89..fc2ca3c 100644
--- a/pkg/analysis_server/lib/src/nullability/transitional_api.dart
+++ b/pkg/analysis_server/lib/src/nullability/transitional_api.dart
@@ -8,6 +8,7 @@
 import 'package:analysis_server/src/nullability/graph_builder.dart';
 import 'package:analysis_server/src/nullability/node_builder.dart';
 import 'package:analysis_server/src/nullability/nullability_node.dart';
+import 'package:analysis_server/src/nullability/provisional_api.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/src/generated/resolver.dart';
@@ -98,7 +99,7 @@
 /// TODO(paulberry): this implementation keeps a lot of CompilationUnit objects
 /// around.  Can we do better?
 class NullabilityMigration {
-  final bool _permissive;
+  final NullabilityMigrationListener /*?*/ listener;
 
   final Variables _variables;
 
@@ -110,10 +111,10 @@
   /// as far as possible even though the migration algorithm is not yet
   /// complete.  TODO(paulberry): remove this mode once the migration algorithm
   /// is fully implemented.
-  NullabilityMigration({bool permissive: false})
-      : this._(permissive, NullabilityGraph());
+  NullabilityMigration(NullabilityMigrationListener /*?*/ listener)
+      : this._(listener, NullabilityGraph());
 
-  NullabilityMigration._(this._permissive, this._graph)
+  NullabilityMigration._(this.listener, this._graph)
       : _variables = Variables(_graph);
 
   Map<Source, List<PotentialModification>> finish() {
@@ -122,13 +123,13 @@
   }
 
   void prepareInput(CompilationUnit unit, TypeProvider typeProvider) {
-    unit.accept(NodeBuilder(_variables, unit.declaredElement.source,
-        _permissive, _graph, typeProvider));
+    unit.accept(NodeBuilder(_variables, unit.declaredElement.source, listener,
+        _graph, typeProvider));
   }
 
   void processInput(CompilationUnit unit, TypeProvider typeProvider) {
     unit.accept(GraphBuilder(typeProvider, _variables, _graph,
-        unit.declaredElement.source, _permissive));
+        unit.declaredElement.source, listener));
   }
 }
 
diff --git a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
index b17cff6..46e0e5b 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -33,7 +33,7 @@
   Future<CompilationUnit> analyze(String code) async {
     var unit = await super.analyze(code);
     unit.accept(
-        GraphBuilder(typeProvider, _variables, graph, testSource, false));
+        GraphBuilder(typeProvider, _variables, graph, testSource, null));
     return unit;
   }
 
@@ -1147,8 +1147,8 @@
 
   Future<CompilationUnit> analyze(String code) async {
     await resolveTestUnit(code);
-    testUnit.accept(
-        NodeBuilder(_variables, testSource, false, graph, typeProvider));
+    testUnit
+        .accept(NodeBuilder(_variables, testSource, null, graph, typeProvider));
     return testUnit;
   }
 
diff --git a/pkg/analysis_server/test/src/nullability/provisional_api_test.dart b/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
index ea1081f..c8730d7 100644
--- a/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
+++ b/pkg/analysis_server/test/src/nullability/provisional_api_test.dart
@@ -1166,6 +1166,13 @@
 class _TestMigrationListener implements NullabilityMigrationListener {
   final _edits = <Source, List<SourceEdit>>{};
 
+  List<String> details = [];
+
+  @override
+  void addDetail(String detail) {
+    details.add(detail);
+  }
+
   @override
   void addEdit(SingleNullabilityFix fix, SourceEdit edit) {
     (_edits[fix.source] ??= []).add(edit);