Display context messages in command-line analyzer output

Change-Id: I2b2f668ef738cf7801dbfdc77d070a8d8a473fe5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107303
Reviewed-by: Devon Carew <devoncarew@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer_cli/lib/src/analyzer_impl.dart b/pkg/analyzer_cli/lib/src/analyzer_impl.dart
index 617bdf9..2d88a91 100644
--- a/pkg/analyzer_cli/lib/src/analyzer_impl.dart
+++ b/pkg/analyzer_cli/lib/src/analyzer_impl.dart
@@ -46,7 +46,7 @@
   final Set<String> files = new Set<String>();
 
   /// All [AnalysisErrorInfo]s in the analyzed library.
-  final List<AnalysisErrorInfo> errorInfos = new List<AnalysisErrorInfo>();
+  final List<ErrorsResult> errorsResults = [];
 
   /// If the file specified on the command line is part of a package, the name
   /// of that package.  Otherwise `null`.  This allows us to analyze the file
@@ -108,8 +108,8 @@
   /// Returns the maximal [ErrorSeverity] of the recorded errors.
   ErrorSeverity computeMaxErrorSeverity() {
     ErrorSeverity status = ErrorSeverity.NONE;
-    for (AnalysisErrorInfo errorInfo in errorInfos) {
-      for (AnalysisError error in errorInfo.errors) {
+    for (ErrorsResult result in errorsResults) {
+      for (AnalysisError error in result.errors) {
         if (_defaultSeverityProcessor(error) == null) {
           continue;
         }
@@ -119,7 +119,7 @@
     return status;
   }
 
-  /// Fills [errorInfos] using [files].
+  /// Fills [errorsResults] using [files].
   Future<void> prepareErrors() async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
@@ -127,8 +127,7 @@
     try {
       for (String path in files) {
         ErrorsResult errorsResult = await analysisDriver.getErrors(path);
-        errorInfos.add(new AnalysisErrorInfoImpl(
-            errorsResult.errors, errorsResult.lineInfo));
+        errorsResults.add(errorsResult);
       }
     } finally {
       previous.makeCurrent();
@@ -145,7 +144,7 @@
   /// Setup local fields such as the analysis context for analysis.
   void setupForAnalysis() {
     files.clear();
-    errorInfos.clear();
+    errorsResults.clear();
     Uri libraryUri = libraryFile.uri;
     if (libraryUri.scheme == 'package' && libraryUri.pathSegments.length > 0) {
       _selfPackageName = libraryUri.pathSegments[0];
@@ -170,7 +169,7 @@
 
     // Print errors and performance numbers.
     if (printMode == 1) {
-      formatter.formatErrors(errorInfos);
+      formatter.formatErrors(errorsResults);
     } else if (printMode == 2) {
       _printColdPerf();
     }
diff --git a/pkg/analyzer_cli/lib/src/build_mode.dart b/pkg/analyzer_cli/lib/src/build_mode.dart
index c50528d..dd86768 100644
--- a/pkg/analyzer_cli/lib/src/build_mode.dart
+++ b/pkg/analyzer_cli/lib/src/build_mode.dart
@@ -633,9 +633,7 @@
               severityProcessor: severityProcessor);
       for (Source source in explicitSources) {
         var result = await analysisDriver.getErrors(source.fullName);
-        var errorInfo =
-            new AnalysisErrorInfoImpl(result.errors, result.lineInfo);
-        formatter.formatErrors([errorInfo]);
+        formatter.formatErrors([result]);
       }
       formatter.flush();
       if (!options.machineFormat) {
diff --git a/pkg/analyzer_cli/lib/src/driver.dart b/pkg/analyzer_cli/lib/src/driver.dart
index bd54543..9252730 100644
--- a/pkg/analyzer_cli/lib/src/driver.dart
+++ b/pkg/analyzer_cli/lib/src/driver.dart
@@ -14,6 +14,7 @@
 import 'package:analyzer/src/dart/analysis/driver.dart';
 import 'package:analyzer/src/dart/analysis/file_state.dart';
 import 'package:analyzer/src/dart/analysis/performance_logger.dart';
+import 'package:analyzer/src/dart/analysis/results.dart';
 import 'package:analyzer/src/dart/sdk/sdk.dart';
 import 'package:analyzer/src/file_system/file_system.dart';
 import 'package:analyzer/src/generated/engine.dart';
@@ -23,8 +24,8 @@
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/generated/utilities_general.dart'
     show PerformanceTag;
-import 'package:analyzer/src/plugin/resolver_provider.dart';
 import 'package:analyzer/src/manifest/manifest_validator.dart';
+import 'package:analyzer/src/plugin/resolver_provider.dart';
 import 'package:analyzer/src/pubspec/pubspec_validator.dart';
 import 'package:analyzer/src/source/package_map_resolver.dart';
 import 'package:analyzer/src/source/path_filter.dart';
@@ -350,7 +351,10 @@
           LineInfo lineInfo = new LineInfo.fromContent(content);
           List<AnalysisError> errors = analyzeAnalysisOptions(
               file.createSource(), content, analysisDriver.sourceFactory);
-          formatter.formatErrors([new AnalysisErrorInfoImpl(errors, lineInfo)]);
+          formatter.formatErrors([
+            ErrorsResultImpl(analysisDriver.currentSession, path, null,
+                lineInfo, false, errors)
+          ]);
           for (AnalysisError error in errors) {
             ErrorSeverity severity = determineProcessedSeverity(
                 error, options, analysisDriver.analysisOptions);
@@ -368,8 +372,10 @@
                   new PubspecValidator(resourceProvider, file.createSource());
               LineInfo lineInfo = new LineInfo.fromContent(content);
               List<AnalysisError> errors = validator.validate(node.nodes);
-              formatter
-                  .formatErrors([new AnalysisErrorInfoImpl(errors, lineInfo)]);
+              formatter.formatErrors([
+                ErrorsResultImpl(analysisDriver.currentSession, path, null,
+                    lineInfo, false, errors)
+              ]);
               for (AnalysisError error in errors) {
                 ErrorSeverity severity = determineProcessedSeverity(
                     error, options, analysisDriver.analysisOptions);
@@ -388,8 +394,10 @@
             LineInfo lineInfo = new LineInfo.fromContent(content);
             List<AnalysisError> errors = validator.validate(
                 content, analysisDriver.analysisOptions.chromeOsManifestChecks);
-            formatter
-                .formatErrors([new AnalysisErrorInfoImpl(errors, lineInfo)]);
+            formatter.formatErrors([
+              ErrorsResultImpl(analysisDriver.currentSession, path, null,
+                  lineInfo, false, errors)
+            ]);
             for (AnalysisError error in errors) {
               ErrorSeverity severity = determineProcessedSeverity(
                   error, options, analysisDriver.analysisOptions);
diff --git a/pkg/analyzer_cli/lib/src/error_formatter.dart b/pkg/analyzer_cli/lib/src/error_formatter.dart
index e6e61f4..51e1333 100644
--- a/pkg/analyzer_cli/lib/src/error_formatter.dart
+++ b/pkg/analyzer_cli/lib/src/error_formatter.dart
@@ -2,9 +2,10 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/source/line_info.dart';
-import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer_cli/src/ansi.dart';
 import 'package:analyzer_cli/src/options.dart';
@@ -109,6 +110,7 @@
   final int line;
   final int column;
   final String message;
+  final List<ContextMessage> contextMessages;
   final String errorCode;
   final String correction;
   final String url;
@@ -120,6 +122,7 @@
     this.line,
     this.column,
     this.message,
+    this.contextMessages,
     this.errorCode,
     this.correction,
     this.url,
@@ -161,6 +164,14 @@
   }
 }
 
+class ContextMessage {
+  final String filePath;
+  final String message;
+  final int line;
+  final int column;
+  ContextMessage(this.filePath, this.message, this.line, this.column);
+}
+
 /// Helper for formatting [AnalysisError]s.
 ///
 /// The two format options are a user consumable format and a machine consumable
@@ -181,19 +192,18 @@
   void flush();
 
   void formatError(
-      Map<AnalysisError, LineInfo> errorToLine, AnalysisError error);
+      Map<AnalysisError, ErrorsResult> errorToLine, AnalysisError error);
 
-  void formatErrors(List<AnalysisErrorInfo> errorInfos) {
-    stats.unfilteredCount += errorInfos.length;
+  void formatErrors(List<ErrorsResult> results) {
+    stats.unfilteredCount += results.length;
 
     List<AnalysisError> errors = new List<AnalysisError>();
-    Map<AnalysisError, LineInfo> errorToLine =
-        new Map<AnalysisError, LineInfo>();
-    for (AnalysisErrorInfo errorInfo in errorInfos) {
-      for (AnalysisError error in errorInfo.errors) {
+    Map<AnalysisError, ErrorsResult> errorToLine = {};
+    for (ErrorsResult result in results) {
+      for (AnalysisError error in result.errors) {
         if (_computeSeverity(error) != null) {
           errors.add(error);
-          errorToLine[error] = errorInfo.lineInfo;
+          errorToLine[error] = result;
         }
       }
     }
@@ -253,6 +263,11 @@
       // If verbose, also print any associated correction and URL.
       if (options.verbose) {
         String padding = ' '.padLeft(error.severity.length + 2);
+        for (var message in error.contextMessages) {
+          out.write('$padding${message.message} ');
+          out.write('at ${message.filePath}');
+          out.writeln(':${message.line}:${message.column}');
+        }
         if (error.correction != null) {
           out.writeln('$padding${error.correction}');
         }
@@ -267,9 +282,10 @@
   }
 
   void formatError(
-      Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) {
+      Map<AnalysisError, ErrorsResult> errorToLine, AnalysisError error) {
     Source source = error.source;
-    var location = errorToLine[error].getLocation(error.offset);
+    var result = errorToLine[error];
+    var location = result.lineInfo.getLocation(error.offset);
 
     ErrorSeverity severity = _severityProcessor(error);
 
@@ -300,6 +316,17 @@
     } else {
       sourcePath = _relative(source.fullName);
     }
+    List<ContextMessage> contextMessages = [];
+    for (var message in error.contextMessages) {
+      var session = result.session.analysisContext;
+      if (session is DriverBasedAnalysisContext) {
+        LineInfo lineInfo =
+            session.driver.getFileSync(message.filePath)?.lineInfo;
+        var location = lineInfo.getLocation(message.offset);
+        contextMessages.add(ContextMessage(message.filePath, message.message,
+            location.lineNumber, location.columnNumber));
+      }
+    }
 
     batchedErrors.add(new CLIError(
       severity: errorType,
@@ -308,6 +335,7 @@
       line: location.lineNumber,
       column: location.columnNumber,
       message: message,
+      contextMessages: contextMessages,
       errorCode: error.errorCode.name.toLowerCase(),
       correction: error.correction,
       url: error.errorCode.url,
@@ -330,13 +358,13 @@
   void flush() {}
 
   void formatError(
-      Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) {
+      Map<AnalysisError, ErrorsResult> errorToLine, AnalysisError error) {
     // Ensure we don't over-report (#36062).
     if (!_seenErrors.add(error)) {
       return;
     }
     Source source = error.source;
-    var location = errorToLine[error].getLocation(error.offset);
+    var location = errorToLine[error].lineInfo.getLocation(error.offset);
     int length = error.length;
 
     ErrorSeverity severity = _severityProcessor(error);
diff --git a/pkg/analyzer_cli/test/mocks.dart b/pkg/analyzer_cli/test/mocks.dart
index b5e5627..e65733c 100644
--- a/pkg/analyzer_cli/test/mocks.dart
+++ b/pkg/analyzer_cli/test/mocks.dart
@@ -31,7 +31,7 @@
   MockAnalysisError(this.source, this.errorCode, this.offset, this.message);
 
   @override
-  List<DiagnosticMessage> get contextMessages => null;
+  List<DiagnosticMessage> get contextMessages => const [];
 
   @override
   String get correction => null;
diff --git a/pkg/analyzer_cli/test/reporter_test.dart b/pkg/analyzer_cli/test/reporter_test.dart
index 26d37ac..e7f359b 100644
--- a/pkg/analyzer_cli/test/reporter_test.dart
+++ b/pkg/analyzer_cli/test/reporter_test.dart
@@ -4,7 +4,7 @@
 
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/source/line_info.dart';
-import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/dart/analysis/results.dart';
 import 'package:analyzer_cli/src/ansi.dart' as ansi;
 import 'package:analyzer_cli/src/error_formatter.dart';
 import 'package:test/test.dart' hide ErrorFormatter;
@@ -39,8 +39,8 @@
     });
 
     test('error', () {
-      AnalysisErrorInfo error =
-          mockError(ErrorType.SYNTACTIC_ERROR, ErrorSeverity.ERROR);
+      ErrorsResultImpl error =
+          mockResult(ErrorType.SYNTACTIC_ERROR, ErrorSeverity.ERROR);
       reporter.formatErrors([error]);
       reporter.flush();
 
@@ -49,7 +49,7 @@
     });
 
     test('hint', () {
-      AnalysisErrorInfo error = mockError(ErrorType.HINT, ErrorSeverity.INFO);
+      ErrorsResultImpl error = mockResult(ErrorType.HINT, ErrorSeverity.INFO);
       reporter.formatErrors([error]);
       reporter.flush();
 
@@ -58,7 +58,7 @@
     });
 
     test('stats', () {
-      AnalysisErrorInfo error = mockError(ErrorType.HINT, ErrorSeverity.INFO);
+      ErrorsResultImpl error = mockResult(ErrorType.HINT, ErrorSeverity.INFO);
       reporter.formatErrors([error]);
       reporter.flush();
       stats.print(out);
@@ -70,7 +70,7 @@
   });
 }
 
-MockAnalysisErrorInfo mockError(ErrorType type, ErrorSeverity severity) {
+ErrorsResultImpl mockResult(ErrorType type, ErrorSeverity severity) {
   // ErrorInfo
   var location = new CharacterLocation(3, 3);
   var lineInfo = new MockLineInfo(defaultLocation: location);
@@ -80,5 +80,6 @@
   var source = new MockSource('/foo/bar/baz.dart');
   var error = new MockAnalysisError(source, code, 20, 'MSG');
 
-  return new MockAnalysisErrorInfo(lineInfo, [error]);
+  return ErrorsResultImpl(
+      null, source.fullName, null, lineInfo, false, [error]);
 }