Add test runner support for context messages without location information.

The CFE implementation of "why not promoted" functionality for
non-promotion of `this` doesn't associate the context message with any
location information, because there is no relevant location to cite.
For example, the output can look like this:

    tests/language/why_not_promoted/this_error_test.dart:16:10: Error: Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
    Try accessing using ?. instead.
        this.isEven;
             ^^^^^^
    Context: 'this' can't be promoted.
    See http://dart.dev/go/non-promo-this

The test runner assumes that all messages have a location, so it
wasn't picking up on this context message at all.  This CL avoids the
problem by having the test runner associate any location-less context
message with the error above it.

(Note that the analyzer doesn't have this problem; all of its context
messages have locations).

Change-Id: Ied52daa8b0090f28617e7d3784233aa44dcc897a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/195301
Reviewed-by: Bob Nystrom <rnystrom@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/test_runner/lib/src/command_output.dart b/pkg/test_runner/lib/src/command_output.dart
index 5d8e490..779325c 100644
--- a/pkg/test_runner/lib/src/command_output.dart
+++ b/pkg/test_runner/lib/src/command_output.dart
@@ -1364,7 +1364,7 @@
   /// The test runner only validates the first line of the message, and not the
   /// suggested fixes.
   static final _errorRegexp = RegExp(
-      r"^([^:]+):(\d+):(\d+): (Context|Error|Warning): (.*)$",
+      r"^(?:([^:]+):(\d+):(\d+): )?(Context|Error|Warning): (.*)$",
       multiLine: true);
 
   FastaCommandOutput(
@@ -1398,11 +1398,27 @@
       [List<StaticError> warnings]) {
     StaticError previousError;
     for (var match in regExp.allMatches(stdout)) {
-      var line = int.parse(match.group(2));
-      var column = int.parse(match.group(3));
+      var line = _parseNullableInt(match.group(2));
+      var column = _parseNullableInt(match.group(3));
       var severity = match.group(4);
       var message = match.group(5);
 
+      if (line == null) {
+        // No location information.
+        if (severity == 'Context' && previousError != null) {
+          // We can use the location information from the error message
+          line = previousError.line;
+          column = previousError.column;
+        } else {
+          // No good default location information, so disregard the error.
+          // TODO(45558): we should do something smarter here.
+          continue;
+        }
+      }
+      // Column information should have been present or it should have been
+      // filled in by the code above.
+      assert(column != null);
+
       var error = StaticError(
           severity == "Context" ? ErrorSource.context : errorSource, message,
           line: line, column: column);
@@ -1426,6 +1442,15 @@
     }
   }
 
+  /// Same as `int.parse`, but allows nulls to simply pass through.
+  static int _parseNullableInt(String s) {
+    if (s == null) {
+      // ignore: avoid_returning_null
+      return null;
+    }
+    return int.parse(s);
+  }
+
   /// Reported static errors, parsed from [stderr].
   List<StaticError> get errors {
     if (!_parsedErrors) {
diff --git a/tests/language/why_not_promoted/this_error_test.dart b/tests/language/why_not_promoted/this_error_test.dart
index 2d836ee..87d98c9 100644
--- a/tests/language/why_not_promoted/this_error_test.dart
+++ b/tests/language/why_not_promoted/this_error_test.dart
@@ -11,22 +11,22 @@
 
 extension on int? {
   extension_explicit_this() {
-    // TODO(paulberry): get this to work with the CFE.
     if (this == null) return;
     this.isEven;
 //       ^^^^^^
-// [analyzer 2] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
-// [context 2] 'this' can't be promoted.  See http://dart.dev/go/non-promo-this
-// [cfe] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
+// [analyzer 1] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
+// [context 1] 'this' can't be promoted.  See http://dart.dev/go/non-promo-this
+// [cfe 3] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
+// [context 3] 'this' can't be promoted.
   }
 
   extension_implicit_this() {
-    // TODO(paulberry): get this to work with the CFE.
     if (this == null) return;
     isEven;
 //  ^^^^^^
-// [analyzer 1] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
-// [context 1] 'this' can't be promoted.  See http://dart.dev/go/non-promo-this
-// [cfe] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
+// [analyzer 2] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
+// [context 2] 'this' can't be promoted.  See http://dart.dev/go/non-promo-this
+// [cfe 4] Property 'isEven' cannot be accessed on 'int?' because it is potentially null.
+// [context 4] 'this' can't be promoted.
   }
 }