Add support for testing the context messages for diagnostics

Change-Id: I33d4abc0dcc969f3beecfdc7cbc814e8a486ad1c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107142
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 3ce5fe3..f95a185 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -12,7 +12,6 @@
 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';
@@ -1737,7 +1736,7 @@
             errorCode,
             error.message,
             error.correction.isEmpty ? null : error.correction,
-            contextMessages: contextMessages));
+            contextMessages: contextMessages ?? const []));
       }
     }
     return errors;
diff --git a/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart b/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart
index 7b359dd..c742828 100644
--- a/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart
+++ b/pkg/analyzer/lib/src/diagnostic/diagnostic_factory.dart
@@ -44,6 +44,6 @@
         identifier.length,
         CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION,
         [name],
-        contextMessages);
+        contextMessages ?? const []);
   }
 }
diff --git a/pkg/analyzer/test/generated/compile_time_error_code.dart b/pkg/analyzer/test/generated/compile_time_error_code.dart
index 181575f..3057230 100644
--- a/pkg/analyzer/test/generated/compile_time_error_code.dart
+++ b/pkg/analyzer/test/generated/compile_time_error_code.dart
@@ -4723,7 +4723,8 @@
 }
 print(x) {}
 ''', [
-      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 28, 1),
+      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 28, 1,
+          expectedMessages: [message('/test/lib/test.dart', 34, 1)]),
     ]);
   }
 
@@ -4736,7 +4737,8 @@
 }
 print(x) {}
 ''', [
-      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 28, 1),
+      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 28, 1,
+          expectedMessages: [message('/test/lib/test.dart', 38, 1)]),
     ]);
   }
 
@@ -4751,7 +4753,8 @@
 }
 print(x) {}
 ''', [
-      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 34, 1),
+      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 34, 1,
+          expectedMessages: [message('/test/lib/test.dart', 48, 1)]),
     ]);
   }
 
@@ -4761,7 +4764,8 @@
   var v = () => v;
 }
 ''', [
-      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 25, 1),
+      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 25, 1,
+          expectedMessages: [message('/test/lib/test.dart', 15, 1)]),
     ]);
   }
 
@@ -4771,7 +4775,8 @@
   var v = v;
 }
 ''', [
-      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 19, 1),
+      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 19, 1,
+          expectedMessages: [message('/test/lib/test.dart', 15, 1)]),
     ]);
   }
 
@@ -4783,7 +4788,8 @@
   print(s + String);
 }
 ''', [
-      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 23, 6),
+      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 23, 6,
+          expectedMessages: [message('/test/lib/test.dart', 44, 6)]),
     ]);
   }
 
@@ -4795,7 +4801,8 @@
   print(s + String);
 }
 ''', [
-      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 23, 6),
+      error(CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, 23, 6,
+          expectedMessages: [message('/test/lib/test.dart', 44, 6)]),
     ]);
   }
 
diff --git a/pkg/analyzer/test/generated/test_support.dart b/pkg/analyzer/test/generated/test_support.dart
index 25a512d..b795962 100644
--- a/pkg/analyzer/test/generated/test_support.dart
+++ b/pkg/analyzer/test/generated/test_support.dart
@@ -5,6 +5,7 @@
 import 'package:analyzer/dart/ast/ast.dart' show AstNode, SimpleIdentifier;
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/diagnostic/diagnostic.dart';
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/exception/exception.dart';
@@ -118,15 +119,61 @@
   /// The offset of the beginning of the error's region.
   final int length;
 
+  /// The list of context messages that are expected to be associated with the
+  /// error.
+  final List<ExpectedMessage> expectedMessages;
+
   /// Initialize a newly created error description.
-  ExpectedError(this.code, this.offset, this.length);
+  ExpectedError(this.code, this.offset, this.length,
+      {this.expectedMessages = const <ExpectedMessage>[]});
 
   /// Return `true` if the [error] matches this description of what it's
   /// expected to be.
   bool matches(AnalysisError error) {
-    return error.offset == offset &&
-        error.length == length &&
-        error.errorCode == code;
+    if (error.offset != offset ||
+        error.length != length ||
+        error.errorCode != code) {
+      return false;
+    }
+    List<DiagnosticMessage> contextMessages = error.contextMessages.toList();
+    contextMessages.sort((first, second) {
+      int result = first.filePath.compareTo(second.filePath);
+      if (result != 0) {
+        return result;
+      }
+      return second.offset - first.offset;
+    });
+    if (contextMessages.length != expectedMessages.length) {
+      return false;
+    }
+    for (int i = 0; i < expectedMessages.length; i++) {
+      if (!expectedMessages[i].matches(contextMessages[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
+
+/// A description of a message that is expected to be reported with an error.
+class ExpectedMessage {
+  /// The path of the file with which the message is associated.
+  final String filePath;
+
+  /// The offset of the beginning of the error's region.
+  final int offset;
+
+  /// The offset of the beginning of the error's region.
+  final int length;
+
+  ExpectedMessage(this.filePath, this.offset, this.length);
+
+  /// Return `true` if the [message] matches this description of what it's
+  /// expected to be.
+  bool matches(DiagnosticMessage message) {
+    return message.filePath == filePath &&
+        message.offset == offset &&
+        message.length == length;
   }
 }
 
@@ -227,12 +274,30 @@
       buffer.writeln();
       buffer.writeln('To accept the current state, expect:');
       for (AnalysisError actual in errors) {
+        List<DiagnosticMessage> contextMessages = actual.contextMessages;
         buffer.write('  error(');
         buffer.write(actual.errorCode);
         buffer.write(', ');
         buffer.write(actual.offset);
         buffer.write(', ');
         buffer.write(actual.length);
+        if (contextMessages.isNotEmpty) {
+          buffer.write(', expectedMessages: [');
+          for (int i = 0; i < contextMessages.length; i++) {
+            DiagnosticMessage message = contextMessages[i];
+            if (i > 0) {
+              buffer.write(', ');
+            }
+            buffer.write('message(resourceProvider.convertPath(\'');
+            buffer.write(message.filePath);
+            buffer.write('\'), ');
+            buffer.write(message.offset);
+            buffer.write(', ');
+            buffer.write(message.length);
+            buffer.write(')');
+          }
+          buffer.write(']');
+        }
         buffer.writeln('),');
       }
       fail(buffer.toString());
diff --git a/pkg/analyzer/test/src/dart/resolution/resolution.dart b/pkg/analyzer/test/src/dart/resolution/resolution.dart
index 56deafe..4040944 100644
--- a/pkg/analyzer/test/src/dart/resolution/resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/resolution.dart
@@ -419,8 +419,10 @@
     expect(node.staticType, isNull);
   }
 
-  ExpectedError error(ErrorCode code, int offset, int length) =>
-      new ExpectedError(code, offset, length);
+  ExpectedError error(ErrorCode code, int offset, int length,
+          {List<ExpectedMessage> expectedMessages =
+              const <ExpectedMessage>[]}) =>
+      ExpectedError(code, offset, length, expectedMessages: expectedMessages);
 
   Element getNodeElement(AstNode node) {
     if (node is Annotation) {
@@ -450,6 +452,9 @@
     }
   }
 
+  ExpectedMessage message(String filePath, int offset, int length) =>
+      ExpectedMessage(convertPath(filePath), offset, length);
+
   Future<ResolvedUnitResult> resolveFile(String path);
 
   Future<void> resolveTestFile() async {