Summarize the context messages in diagnostics

Change-Id: Id03e778cf9bebe9be61d9ded601408121b891097
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107102
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 7c1fa00..d11d395 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -837,12 +837,14 @@
    * Initialize a newly created analysis error with given values.
    */
   AnalysisError.forValues(this.source, int offset, int length, this.errorCode,
-      String message, this._correction) {
+      String message, this._correction,
+      {List<DiagnosticMessage> contextMessages}) {
     _problemMessage = new DiagnosticMessageImpl(
         filePath: source?.fullName,
         length: length,
         message: message,
         offset: offset);
+    _contextMessages = contextMessages;
   }
 
   List<DiagnosticMessage> get contextMessages => _contextMessages ?? const [];
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index ead50fe5..3ce5fe3 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -12,6 +12,7 @@
 import 'package:analyzer/dart/analysis/session.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/element.dart' show LibraryElement;
+import 'package:analyzer/diagnostic/diagnostic.dart' as diagnostic;
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/exception/exception.dart';
@@ -29,6 +30,7 @@
 import 'package:analyzer/src/dart/analysis/session.dart';
 import 'package:analyzer/src/dart/analysis/status.dart';
 import 'package:analyzer/src/dart/analysis/top_level_declaration.dart';
+import 'package:analyzer/src/diagnostic/diagnostic.dart';
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/engine.dart'
     show
@@ -94,7 +96,7 @@
   /**
    * The version of data format, should be incremented on every format change.
    */
-  static const int DATA_VERSION = 83;
+  static const int DATA_VERSION = 84;
 
   /**
    * The number of exception contexts allowed to write. Once this field is
@@ -1717,13 +1719,25 @@
         AnalysisEngine.instance.instrumentationService
             .logError('No error code for "$error" in "$file"');
       } else {
+        List<DiagnosticMessageImpl> contextMessages;
+        if (error.contextMessages.isNotEmpty) {
+          contextMessages = <DiagnosticMessageImpl>[];
+          for (var message in error.contextMessages) {
+            contextMessages.add(DiagnosticMessageImpl(
+                filePath: message.filePath,
+                length: message.length,
+                message: message.message,
+                offset: message.offset));
+          }
+        }
         errors.add(new AnalysisError.forValues(
             file.source,
             error.offset,
             error.length,
             errorCode,
             error.message,
-            error.correction.isEmpty ? null : error.correction));
+            error.correction.isEmpty ? null : error.correction,
+            contextMessages: contextMessages));
       }
     }
     return errors;
@@ -1814,14 +1828,26 @@
         ? indexUnit(resolvedUnit)
         : new AnalysisDriverUnitIndexBuilder();
     return new AnalysisDriverResolvedUnitBuilder(
-            errors: errors
-                .map((error) => new AnalysisDriverUnitErrorBuilder(
-                    offset: error.offset,
-                    length: error.length,
-                    uniqueName: error.errorCode.uniqueName,
-                    message: error.message,
-                    correction: error.correction))
-                .toList(),
+            errors: errors.map((error) {
+              List<DiagnosticMessageBuilder> contextMessages;
+              if (error.contextMessages != null) {
+                contextMessages = <DiagnosticMessageBuilder>[];
+                for (var message in error.contextMessages) {
+                  contextMessages.add(DiagnosticMessageBuilder(
+                      filePath: message.filePath,
+                      length: message.length,
+                      message: message.message,
+                      offset: message.offset));
+                }
+              }
+              return new AnalysisDriverUnitErrorBuilder(
+                  offset: error.offset,
+                  length: error.length,
+                  uniqueName: error.errorCode.uniqueName,
+                  message: error.message,
+                  correction: error.correction,
+                  contextMessages: contextMessages);
+            }).toList(),
             index: index)
         .toBuffer();
   }
diff --git a/pkg/analyzer/lib/src/summary/format.dart b/pkg/analyzer/lib/src/summary/format.dart
index 5c6d947..6ac4f35 100644
--- a/pkg/analyzer/lib/src/summary/format.dart
+++ b/pkg/analyzer/lib/src/summary/format.dart
@@ -887,6 +887,7 @@
 class AnalysisDriverUnitErrorBuilder extends Object
     with _AnalysisDriverUnitErrorMixin
     implements idl.AnalysisDriverUnitError {
+  List<DiagnosticMessageBuilder> _contextMessages;
   String _correction;
   int _length;
   String _message;
@@ -894,6 +895,15 @@
   String _uniqueName;
 
   @override
+  List<DiagnosticMessageBuilder> get contextMessages =>
+      _contextMessages ??= <DiagnosticMessageBuilder>[];
+
+  /// The context messages associated with the error.
+  set contextMessages(List<DiagnosticMessageBuilder> value) {
+    this._contextMessages = value;
+  }
+
+  @override
   String get correction => _correction ??= '';
 
   /// The optional correction hint for the error.
@@ -936,19 +946,23 @@
   }
 
   AnalysisDriverUnitErrorBuilder(
-      {String correction,
+      {List<DiagnosticMessageBuilder> contextMessages,
+      String correction,
       int length,
       String message,
       int offset,
       String uniqueName})
-      : _correction = correction,
+      : _contextMessages = contextMessages,
+        _correction = correction,
         _length = length,
         _message = message,
         _offset = offset,
         _uniqueName = uniqueName;
 
   /// Flush [informative] data recursively.
-  void flushInformative() {}
+  void flushInformative() {
+    _contextMessages?.forEach((b) => b.flushInformative());
+  }
 
   /// Accumulate non-[informative] data into [signature].
   void collectApiSignature(api_sig.ApiSignature signature) {
@@ -957,12 +971,25 @@
     signature.addString(this._uniqueName ?? '');
     signature.addString(this._message ?? '');
     signature.addString(this._correction ?? '');
+    if (this._contextMessages == null) {
+      signature.addInt(0);
+    } else {
+      signature.addInt(this._contextMessages.length);
+      for (var x in this._contextMessages) {
+        x?.collectApiSignature(signature);
+      }
+    }
   }
 
   fb.Offset finish(fb.Builder fbBuilder) {
+    fb.Offset offset_contextMessages;
     fb.Offset offset_correction;
     fb.Offset offset_message;
     fb.Offset offset_uniqueName;
+    if (!(_contextMessages == null || _contextMessages.isEmpty)) {
+      offset_contextMessages = fbBuilder
+          .writeList(_contextMessages.map((b) => b.finish(fbBuilder)).toList());
+    }
     if (_correction != null) {
       offset_correction = fbBuilder.writeString(_correction);
     }
@@ -973,6 +1000,9 @@
       offset_uniqueName = fbBuilder.writeString(_uniqueName);
     }
     fbBuilder.startTable();
+    if (offset_contextMessages != null) {
+      fbBuilder.addOffset(5, offset_contextMessages);
+    }
     if (offset_correction != null) {
       fbBuilder.addOffset(4, offset_correction);
     }
@@ -1009,6 +1039,7 @@
 
   _AnalysisDriverUnitErrorImpl(this._bc, this._bcOffset);
 
+  List<idl.DiagnosticMessage> _contextMessages;
   String _correction;
   int _length;
   String _message;
@@ -1016,6 +1047,14 @@
   String _uniqueName;
 
   @override
+  List<idl.DiagnosticMessage> get contextMessages {
+    _contextMessages ??= const fb.ListReader<idl.DiagnosticMessage>(
+            const _DiagnosticMessageReader())
+        .vTableGet(_bc, _bcOffset, 5, const <idl.DiagnosticMessage>[]);
+    return _contextMessages;
+  }
+
+  @override
   String get correction {
     _correction ??= const fb.StringReader().vTableGet(_bc, _bcOffset, 4, '');
     return _correction;
@@ -1051,6 +1090,9 @@
   @override
   Map<String, Object> toJson() {
     Map<String, Object> _result = <String, Object>{};
+    if (contextMessages.isNotEmpty)
+      _result["contextMessages"] =
+          contextMessages.map((_value) => _value.toJson()).toList();
     if (correction != '') _result["correction"] = correction;
     if (length != 0) _result["length"] = length;
     if (message != '') _result["message"] = message;
@@ -1061,6 +1103,7 @@
 
   @override
   Map<String, Object> toMap() => {
+        "contextMessages": contextMessages,
         "correction": correction,
         "length": length,
         "message": message,
@@ -3526,6 +3569,162 @@
   String toString() => convert.json.encode(toJson());
 }
 
+class DiagnosticMessageBuilder extends Object
+    with _DiagnosticMessageMixin
+    implements idl.DiagnosticMessage {
+  String _filePath;
+  int _length;
+  String _message;
+  int _offset;
+
+  @override
+  String get filePath => _filePath ??= '';
+
+  /// The absolute and normalized path of the file associated with this message.
+  set filePath(String value) {
+    this._filePath = value;
+  }
+
+  @override
+  int get length => _length ??= 0;
+
+  /// The length of the source range associated with this message.
+  set length(int value) {
+    assert(value == null || value >= 0);
+    this._length = value;
+  }
+
+  @override
+  String get message => _message ??= '';
+
+  /// The text of the message.
+  set message(String value) {
+    this._message = value;
+  }
+
+  @override
+  int get offset => _offset ??= 0;
+
+  /// The zero-based offset from the start of the file to the beginning of the
+  /// source range associated with this message.
+  set offset(int value) {
+    assert(value == null || value >= 0);
+    this._offset = value;
+  }
+
+  DiagnosticMessageBuilder(
+      {String filePath, int length, String message, int offset})
+      : _filePath = filePath,
+        _length = length,
+        _message = message,
+        _offset = offset;
+
+  /// Flush [informative] data recursively.
+  void flushInformative() {}
+
+  /// Accumulate non-[informative] data into [signature].
+  void collectApiSignature(api_sig.ApiSignature signature) {
+    signature.addString(this._filePath ?? '');
+    signature.addInt(this._length ?? 0);
+    signature.addString(this._message ?? '');
+    signature.addInt(this._offset ?? 0);
+  }
+
+  fb.Offset finish(fb.Builder fbBuilder) {
+    fb.Offset offset_filePath;
+    fb.Offset offset_message;
+    if (_filePath != null) {
+      offset_filePath = fbBuilder.writeString(_filePath);
+    }
+    if (_message != null) {
+      offset_message = fbBuilder.writeString(_message);
+    }
+    fbBuilder.startTable();
+    if (offset_filePath != null) {
+      fbBuilder.addOffset(0, offset_filePath);
+    }
+    if (_length != null && _length != 0) {
+      fbBuilder.addUint32(1, _length);
+    }
+    if (offset_message != null) {
+      fbBuilder.addOffset(2, offset_message);
+    }
+    if (_offset != null && _offset != 0) {
+      fbBuilder.addUint32(3, _offset);
+    }
+    return fbBuilder.endTable();
+  }
+}
+
+class _DiagnosticMessageReader extends fb.TableReader<_DiagnosticMessageImpl> {
+  const _DiagnosticMessageReader();
+
+  @override
+  _DiagnosticMessageImpl createObject(fb.BufferContext bc, int offset) =>
+      new _DiagnosticMessageImpl(bc, offset);
+}
+
+class _DiagnosticMessageImpl extends Object
+    with _DiagnosticMessageMixin
+    implements idl.DiagnosticMessage {
+  final fb.BufferContext _bc;
+  final int _bcOffset;
+
+  _DiagnosticMessageImpl(this._bc, this._bcOffset);
+
+  String _filePath;
+  int _length;
+  String _message;
+  int _offset;
+
+  @override
+  String get filePath {
+    _filePath ??= const fb.StringReader().vTableGet(_bc, _bcOffset, 0, '');
+    return _filePath;
+  }
+
+  @override
+  int get length {
+    _length ??= const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 1, 0);
+    return _length;
+  }
+
+  @override
+  String get message {
+    _message ??= const fb.StringReader().vTableGet(_bc, _bcOffset, 2, '');
+    return _message;
+  }
+
+  @override
+  int get offset {
+    _offset ??= const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 3, 0);
+    return _offset;
+  }
+}
+
+abstract class _DiagnosticMessageMixin implements idl.DiagnosticMessage {
+  @override
+  Map<String, Object> toJson() {
+    Map<String, Object> _result = <String, Object>{};
+    if (filePath != '') _result["filePath"] = filePath;
+    if (length != 0) _result["length"] = length;
+    if (message != '') _result["message"] = message;
+    if (offset != 0) _result["offset"] = offset;
+    return _result;
+  }
+
+  @override
+  Map<String, Object> toMap() => {
+        "filePath": filePath,
+        "length": length,
+        "message": message,
+        "offset": offset,
+      };
+
+  @override
+  String toString() => convert.json.encode(toJson());
+}
+
 class DirectiveInfoBuilder extends Object
     with _DirectiveInfoMixin
     implements idl.DirectiveInfo {
diff --git a/pkg/analyzer/lib/src/summary/format.fbs b/pkg/analyzer/lib/src/summary/format.fbs
index caaf7d3..e6aca08 100644
--- a/pkg/analyzer/lib/src/summary/format.fbs
+++ b/pkg/analyzer/lib/src/summary/format.fbs
@@ -1455,6 +1455,9 @@
 
 /// Information about an error in a resolved unit.
 table AnalysisDriverUnitError {
+  /// The context messages associated with the error.
+  contextMessages:[DiagnosticMessage] (id: 5);
+
   /// The optional correction hint for the error.
   correction:string (id: 4);
 
@@ -1689,6 +1692,21 @@
   offset:uint (id: 0);
 }
 
+table DiagnosticMessage {
+  /// The absolute and normalized path of the file associated with this message.
+  filePath:string (id: 0);
+
+  /// The length of the source range associated with this message.
+  length:uint (id: 1);
+
+  /// The text of the message.
+  message:string (id: 2);
+
+  /// The zero-based offset from the start of the file to the beginning of the
+  /// source range associated with this message.
+  offset:uint (id: 3);
+}
+
 /// Information about the Dartdoc directives in an [AvailableFile].
 table DirectiveInfo {
   /// The names of the defined templates.
diff --git a/pkg/analyzer/lib/src/summary/idl.dart b/pkg/analyzer/lib/src/summary/idl.dart
index 1034e2b..394c448 100644
--- a/pkg/analyzer/lib/src/summary/idl.dart
+++ b/pkg/analyzer/lib/src/summary/idl.dart
@@ -118,6 +118,10 @@
 
 /// Information about an error in a resolved unit.
 abstract class AnalysisDriverUnitError extends base.SummaryClass {
+  /// The context messages associated with the error.
+  @Id(5)
+  List<DiagnosticMessage> get contextMessages;
+
   /// The optional correction hint for the error.
   @Id(4)
   String get correction;
@@ -444,6 +448,25 @@
   int get offset;
 }
 
+abstract class DiagnosticMessage extends base.SummaryClass {
+  /// The absolute and normalized path of the file associated with this message.
+  @Id(0)
+  String get filePath;
+
+  /// The length of the source range associated with this message.
+  @Id(1)
+  int get length;
+
+  /// The text of the message.
+  @Id(2)
+  String get message;
+
+  /// The zero-based offset from the start of the file to the beginning of the
+  /// source range associated with this message.
+  @Id(3)
+  int get offset;
+}
+
 /// Information about the Dartdoc directives in an [AvailableFile].
 abstract class DirectiveInfo extends base.SummaryClass {
   /// The names of the defined templates.