[web] Migrate sourcemap_testing to null safety

Change-Id: Id89be7e1327d7e9016fcd7d2864d124bc04d865a
Issue: https://github.com/dart-lang/sdk/issues/46617
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243701
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
diff --git a/pkg/sourcemap_testing/lib/src/stacktrace_helper.dart b/pkg/sourcemap_testing/lib/src/stacktrace_helper.dart
index 195b25c..43c9fa9 100644
--- a/pkg/sourcemap_testing/lib/src/stacktrace_helper.dart
+++ b/pkg/sourcemap_testing/lib/src/stacktrace_helper.dart
@@ -8,6 +8,7 @@
 
 import 'package:_fe_analyzer_shared/src/testing/annotated_code_helper.dart';
 import 'package:dart2js_tools/src/dart2js_mapping.dart';
+import 'package:dart2js_tools/src/util.dart';
 import 'package:expect/expect.dart';
 import 'package:source_maps/source_maps.dart';
 import 'package:source_maps/src/utils.dart';
@@ -58,10 +59,10 @@
       }
     }
 
-    List<StackTraceLine> expectedLines = <StackTraceLine>[];
+    List<StackTraceLine> expectedLines = [];
     for (int stackTraceIndex
         in (stackTraceMap.keys.toList()..sort()).reversed) {
-      expectedLines.add(stackTraceMap[stackTraceIndex]);
+      expectedLines.add(stackTraceMap[stackTraceIndex]!);
     }
     expectationMap[config] = Expectations(expectedLines, unexpectedLines);
   });
@@ -75,7 +76,7 @@
 typedef CompileFunc = Future<bool> Function(String, String);
 
 List<String> emptyPreamble(input, output) => const <String>[];
-String identityConverter(String name) => name;
+String? identityConverter(String? name) => name;
 
 /// Tests the stack trace of [test] using the expectations for [config].
 ///
@@ -105,8 +106,8 @@
     List<LineException> beforeExceptions = const <LineException>[],
     List<LineException> afterExceptions = const <LineException>[],
     bool useJsMethodNamesOnAbsence = false,
-    String Function(String name) jsNameConverter = identityConverter,
-    Directory forcedTmpDir,
+    String? Function(String? name) jsNameConverter = identityConverter,
+    Directory? forcedTmpDir,
     int stackTraceLimit = 10,
     expandDart2jsInliningData = false}) async {
   Expect.isTrue(test.expectationMap.keys.contains(config),
@@ -124,7 +125,7 @@
   Expect.isTrue(
       sourceMapFile.existsSync(), 'Source map not generated for $input');
   String sourceMapText = sourceMapFile.readAsStringSync();
-  SingleMapping sourceMap = parse(sourceMapText);
+  SingleMapping sourceMap = parseSingleMapping(jsonDecode(sourceMapText));
   String jsOutput = File(output).readAsStringSync();
 
   if (printJs) {
@@ -160,21 +161,18 @@
 
   List<StackTraceLine> dartStackTrace = <StackTraceLine>[];
   for (StackTraceLine line in jsStackTrace) {
-    TargetEntry targetEntry = _findColumn(
-        line.lineNo - 1, line.columnNo - 1, _findLine(sourceMap, line));
+    TargetEntry? targetEntry = _findColumn(
+        line.lineNo! - 1, line.columnNo! - 1, _findLine(sourceMap, line));
     if (targetEntry == null || targetEntry.sourceUrlId == null) {
       dartStackTrace.add(line);
     } else {
-      String fileName;
-      if (targetEntry.sourceUrlId != null) {
-        fileName = sourceMap.urls[targetEntry.sourceUrlId];
-      }
-      int targetLine = targetEntry.sourceLine + 1;
-      int targetColumn = targetEntry.sourceColumn + 1;
+      String fileName = sourceMap.urls[targetEntry.sourceUrlId!];
+      int targetLine = targetEntry.sourceLine! + 1;
+      int targetColumn = targetEntry.sourceColumn! + 1;
 
       if (expandDart2jsInliningData) {
         SourceFile file = SourceFile.fromString(jsOutput);
-        int offset = file.getOffset(line.lineNo - 1, line.columnNo - 1);
+        int offset = file.getOffset(line.lineNo! - 1, line.columnNo! - 1);
         Map<int, List<FrameEntry>> frames =
             _loadInlinedFrameData(sourceMap, sourceMapText);
         List<int> indices = frames.keys.toList()..sort();
@@ -182,7 +180,7 @@
         int depth = 0;
         outer:
         while (key >= 0) {
-          for (var frame in frames[indices[key]].reversed) {
+          for (var frame in frames[indices[key]]!.reversed) {
             if (frame.isEmpty) break outer;
             if (frame.isPush) {
               if (depth <= 0) {
@@ -192,9 +190,9 @@
                     targetLine,
                     targetColumn,
                     isMapped: true));
-                fileName = frame.callUri;
-                targetLine = frame.callLine + 1;
-                targetColumn = frame.callColumn + 1;
+                fileName = frame.callUri!;
+                targetLine = frame.callLine! + 1;
+                targetColumn = frame.callColumn! + 1;
               } else {
                 depth--;
               }
@@ -208,9 +206,9 @@
         targetEntry = findEnclosingFunction(jsOutput, file, offset, sourceMap);
       }
 
-      String methodName;
-      if (targetEntry.sourceNameId != null) {
-        methodName = sourceMap.names[targetEntry.sourceNameId];
+      String? methodName;
+      if (targetEntry!.sourceNameId != null) {
+        methodName = sourceMap.names[targetEntry.sourceNameId!];
       } else if (useJsMethodNamesOnAbsence) {
         methodName = jsNameConverter(line.methodName);
       }
@@ -221,7 +219,7 @@
     }
   }
 
-  Expectations expectations = test.expectationMap[config];
+  Expectations expectations = test.expectationMap[config]!;
 
   int expectedIndex = 0;
   List<StackTraceLine> unexpectedLines = <StackTraceLine>[];
@@ -251,7 +249,7 @@
       for (LineException exception in exceptions) {
         String fileName = exception.fileName;
         if (line.methodName == exception.methodName &&
-            line.fileName.endsWith(fileName)) {
+            line.fileName!.endsWith(fileName)) {
           found = true;
         }
       }
@@ -295,10 +293,10 @@
 }
 
 class StackTraceLine {
-  String methodName;
-  String fileName;
-  int lineNo;
-  int columnNo;
+  String? methodName;
+  String? fileName;
+  int? lineNo;
+  int? columnNo;
   bool isMapped;
 
   StackTraceLine(this.methodName, this.fileName, this.lineNo, this.columnNo,
@@ -318,22 +316,22 @@
     text = text.trim();
     assert(text.startsWith('at '));
     text = text.substring('at '.length);
-    String methodName;
+    String? methodName;
     if (text.endsWith(')')) {
       int nameEnd = text.indexOf(' (');
       methodName = text.substring(0, nameEnd);
       text = text.substring(nameEnd + 2, text.length - 1);
     }
-    int lineNo;
-    int columnNo;
+    int? lineNo;
+    int? columnNo;
     String fileName;
     int lastColon = text.lastIndexOf(':');
     if (lastColon != -1) {
-      int lastValue = int.tryParse(text.substring(lastColon + 1));
+      int? lastValue = int.tryParse(text.substring(lastColon + 1));
       if (lastValue != null) {
         int secondToLastColon = text.lastIndexOf(':', lastColon - 1);
         if (secondToLastColon != -1) {
-          int secondToLastValue =
+          int? secondToLastValue =
               int.tryParse(text.substring(secondToLastColon + 1, lastColon));
           if (secondToLastValue != null) {
             lineNo = secondToLastValue;
@@ -385,14 +383,14 @@
 /// number is lower or equal to [line].
 ///
 /// Copied from [SingleMapping._findLine].
-TargetLineEntry _findLine(SingleMapping sourceMap, StackTraceLine stLine) {
-  String filename = stLine.fileName
-      .substring(stLine.fileName.lastIndexOf(RegExp('[\\/]')) + 1);
+TargetLineEntry? _findLine(SingleMapping sourceMap, StackTraceLine stLine) {
+  String filename = stLine.fileName!
+      .substring(stLine.fileName!.lastIndexOf(RegExp('[\\/]')) + 1);
   if (sourceMap.targetUrl != filename) return null;
-  return _findLineInternal(sourceMap, stLine.lineNo - 1);
+  return _findLineInternal(sourceMap, stLine.lineNo! - 1);
 }
 
-TargetLineEntry _findLineInternal(SingleMapping sourceMap, int line) {
+TargetLineEntry? _findLineInternal(SingleMapping sourceMap, int line) {
   int index = binarySearch(sourceMap.lines, (e) => e.line > line);
   return (index <= 0) ? null : sourceMap.lines[index - 1];
 }
@@ -404,7 +402,7 @@
 /// the very last entry on that line.
 ///
 /// Copied from [SingleMapping._findColumn].
-TargetEntry _findColumn(int line, int column, TargetLineEntry lineEntry) {
+TargetEntry? _findColumn(int line, int column, TargetLineEntry? lineEntry) {
   if (lineEntry == null || lineEntry.entries.isEmpty) return null;
   if (lineEntry.line != line) return lineEntry.entries.last;
   var entries = lineEntry.entries;
@@ -434,9 +432,8 @@
 
 /// Search backwards in [sources] for a function declaration that includes the
 /// [start] offset.
-TargetEntry findEnclosingFunction(
+TargetEntry? findEnclosingFunction(
     String sources, SourceFile file, int start, SingleMapping mapping) {
-  if (sources == null) return null;
   var index = start;
   while (true) {
     index = nextDeclarationCandidate(sources, index);
diff --git a/pkg/sourcemap_testing/lib/src/stepping_helper.dart b/pkg/sourcemap_testing/lib/src/stepping_helper.dart
index 9833d8a..9392f77 100644
--- a/pkg/sourcemap_testing/lib/src/stepping_helper.dart
+++ b/pkg/sourcemap_testing/lib/src/stepping_helper.dart
@@ -1,6 +1,9 @@
+import 'dart:convert' show jsonDecode;
 import 'dart:io';
 
 import 'package:_fe_analyzer_shared/src/testing/annotated_code_helper.dart';
+import 'package:collection/collection.dart' show IterableNullableExtension;
+import 'package:dart2js_tools/src/util.dart';
 import 'package:expect/minitest.dart';
 import 'package:path/path.dart' as path;
 import 'package:source_maps/source_maps.dart';
@@ -14,11 +17,12 @@
 ProcessResult runD8AndStep(String outputPath, String testFileName,
     AnnotatedCode code, List<String> scriptD8Command) {
   var outputFile = path.join(outputPath, 'js.js');
-  SingleMapping sourceMap = parse(File('$outputFile.map').readAsStringSync());
+  var sourcemapText = File('$outputFile.map').readAsStringSync();
+  SingleMapping sourceMap = parseSingleMapping(jsonDecode(sourcemapText));
 
-  Set<int> mappedToLines = sourceMap.lines
+  Set<int?> mappedToLines = sourceMap.lines
       .map((entry) => entry.entries.map((entry) => entry.sourceLine).toSet())
-      .fold(<int>{}, (prev, e) => prev..addAll(e));
+      .fold(<int?>{}, (prev, e) => prev..addAll(e));
 
   for (Annotation annotation
       in code.annotations.where((a) => a.text.trim() == 'nm')) {
@@ -30,7 +34,7 @@
     }
   }
 
-  List<String> breakpoints = [];
+  List<String?> breakpoints = [];
   // Annotations are 1-based, js breakpoints are 0-based.
   for (Annotation breakAt
       in code.annotations.where((a) => a.text.trim() == 'bl')) {
@@ -44,7 +48,7 @@
   }
 
   File inspectorFile = File.fromUri(
-      sdkRoot.uri.resolve('pkg/sourcemap_testing/lib/src/js/inspector.js'));
+      sdkRoot!.uri.resolve('pkg/sourcemap_testing/lib/src/js/inspector.js'));
   if (!inspectorFile.existsSync()) throw "Couldn't find 'inspector.js'";
   var outInspectorPath = path.join(outputPath, 'inspector.js');
   inspectorFile.copySync(outInspectorPath);
@@ -65,7 +69,8 @@
     {bool debug = false}) {
   var outputFilename = 'js.js';
   var outputFile = path.join(outputPath, outputFilename);
-  SingleMapping sourceMap = parse(File('$outputFile.map').readAsStringSync());
+  var sourcemapText = File('$outputFile.map').readAsStringSync();
+  SingleMapping sourceMap = parseSingleMapping(jsonDecode(sourcemapText));
 
   List<List<_DartStackTraceDataEntry>> result =
       _extractStackTraces(d8Output, sourceMap, outputFilename);
@@ -100,8 +105,8 @@
     }
   }
 
-  List<List<String>> noBreaksStart = [];
-  List<List<String>> noBreaksEnd = [];
+  List<List<String>?> noBreaksStart = [];
+  List<List<String>?> noBreaksEnd = [];
   for (Annotation annotation in code.annotations
       .where((annotation) => annotation.text.trim().startsWith('nbb:'))) {
     String text = annotation.text.trim();
@@ -109,12 +114,9 @@
     int stopNum1 = int.parse(split[1]);
     int stopNum2 = int.parse(split[2]);
     if (noBreaksStart.length <= stopNum1) noBreaksStart.length = stopNum1 + 1;
-    noBreaksStart[stopNum1] ??= [];
+    (noBreaksStart[stopNum1] ??= []).add('test.dart:${annotation.lineNo}:');
     if (noBreaksEnd.length <= stopNum2) noBreaksEnd.length = stopNum2 + 1;
-    noBreaksEnd[stopNum2] ??= [];
-
-    noBreaksStart[stopNum1].add('test.dart:${annotation.lineNo}:');
-    noBreaksEnd[stopNum2].add('test.dart:${annotation.lineNo}:');
+    (noBreaksEnd[stopNum2] ??= []).add('test.dart:${annotation.lineNo}:');
   }
 
   _checkRecordedStops(
@@ -147,8 +149,8 @@
 void _checkRecordedStops(
     List<String> recordStops,
     List<String> expectedStops,
-    List<List<String>> noBreaksStart,
-    List<List<String>> noBreaksEnd,
+    List<List<String>?> noBreaksStart,
+    List<List<String>?> noBreaksEnd,
     bool debug) {
   // We want to find all expected lines in recorded lines in order, but allow
   // more in between in the recorded lines.
@@ -158,7 +160,7 @@
   int expectedIndex = 0;
   Set<String> aliveNoBreaks = {};
   if (noBreaksStart.isNotEmpty && noBreaksStart[0] != null) {
-    aliveNoBreaks.addAll(noBreaksStart[0]);
+    aliveNoBreaks.addAll(noBreaksStart[0]!);
   }
   int stopNumber = 0;
   for (String recorded in recordStops) {
@@ -168,11 +170,11 @@
       ++expectedIndex;
       if (noBreaksStart.length > expectedIndex &&
           noBreaksStart[expectedIndex] != null) {
-        aliveNoBreaks.addAll(noBreaksStart[expectedIndex]);
+        aliveNoBreaks.addAll(noBreaksStart[expectedIndex]!);
       }
       if (noBreaksEnd.length > expectedIndex &&
           noBreaksEnd[expectedIndex] != null) {
-        aliveNoBreaks.removeAll(noBreaksEnd[expectedIndex]);
+        aliveNoBreaks.removeAll(noBreaksEnd[expectedIndex]!);
       }
     } else {
       if (debug) {
@@ -241,7 +243,7 @@
   }
 
   List<String> sideBySide(List<String> a, List<String> b, int columns) {
-    List<String> result = List<String>.filled(a.length, null);
+    List<String> result = List<String>.filled(a.length, '');
     for (int i = 0; i < a.length; ++i) {
       String left = a[i].padRight(columns).substring(0, columns);
       String right = b[i].padRight(columns).substring(0, columns);
@@ -267,7 +269,7 @@
 }
 
 List<List<_DartStackTraceDataEntry>> _extractStackTraces(
-    lines, SingleMapping sourceMap, String outputFilename) {
+    List<String> lines, SingleMapping sourceMap, String outputFilename) {
   List<List<_DartStackTraceDataEntry>> result = [];
   bool inStackTrace = false;
   List<String> currentStackTrace = <String>[];
@@ -301,24 +303,24 @@
       continue;
     }
     Match m = ms.first;
-    int l = int.parse(m.group(1));
-    int c = int.parse(m.group(2));
-    SourceMapSpan span = _getColumnOrPredecessor(sourceMap, l, c);
+    int l = int.parse(m.group(1)!);
+    int c = int.parse(m.group(2)!);
+    SourceMapSpan? span = _getColumnOrPredecessor(sourceMap, l, c);
     if (span?.start == null) {
       result.add(_DartStackTraceDataEntry.errorWithJsPosition(
           "Source map not found for '$line'", l, c));
       continue;
     }
-    var file = span.sourceUrl?.pathSegments?.last ?? '(unknown file)';
+    var file = span!.sourceUrl?.pathSegments.last ?? '(unknown file)';
     result.add(_DartStackTraceDataEntry(
         file, span.start.line + 1, span.start.column + 1, l, c));
   }
   return result;
 }
 
-SourceMapSpan _getColumnOrPredecessor(
+SourceMapSpan? _getColumnOrPredecessor(
     SingleMapping sourceMap, int line, int column) {
-  SourceMapSpan span = sourceMap.spanFor(line, column);
+  SourceMapSpan? span = sourceMap.spanFor(line, column);
   if (span == null && line > 0) {
     span = sourceMap.spanFor(line - 1, 999999);
   }
@@ -326,10 +328,10 @@
 }
 
 class _DartStackTraceDataEntry {
-  final String file;
+  final String? file;
   final int line;
   final int column;
-  final String errorString;
+  final String? errorString;
   final int jsLine;
   final int jsColumn;
 
@@ -351,7 +353,7 @@
   bool get isError => errorString != null;
 
   @override
-  String toString() => isError ? errorString : '$file:$line:$column';
+  String toString() => isError ? errorString! : '$file:$line:$column';
 }
 
 class _PointMapping {
@@ -367,18 +369,21 @@
 ///
 /// The "magic 4" below is taken from
 /// https://github.com/ChromeDevTools/devtools-frontend/blob/fa18d70a995f06cb73365b2e5b8ae974cf60bd3a/front_end/sources/JavaScriptSourceFrame.js#L1520-L1523
-String _getJsBreakpointLine(
+String? _getJsBreakpointLine(
     String testFileName, SingleMapping sourceMap, int breakOnLine) {
   List<_PointMapping> mappingsOnLines = [];
   for (var line in sourceMap.lines) {
     for (var entry in line.entries) {
-      if (entry.sourceLine == null) continue;
-      if (entry.sourceLine >= breakOnLine &&
-          entry.sourceLine < breakOnLine + 4 &&
-          entry.sourceUrlId != null &&
-          sourceMap.urls[entry.sourceUrlId] == testFileName) {
-        mappingsOnLines.add(_PointMapping(
-            entry.sourceLine, entry.sourceColumn, line.line, entry.column));
+      final sourceLine = entry.sourceLine;
+      if (sourceLine == null) continue;
+      final sourceColumn = entry.sourceColumn!;
+      final sourceUrlId = entry.sourceUrlId;
+      if (sourceLine >= breakOnLine &&
+          sourceLine < breakOnLine + 4 &&
+          sourceUrlId != null &&
+          sourceMap.urls[sourceUrlId] == testFileName) {
+        mappingsOnLines.add(
+            _PointMapping(sourceLine, sourceColumn, line.line, entry.column));
       }
     }
   }
@@ -399,14 +404,14 @@
 }
 
 /// Input and output is expected to be 0-based.
-String _getJsBreakpointLineAndColumn(String testFileName,
+String? _getJsBreakpointLineAndColumn(String testFileName,
     SingleMapping sourceMap, int breakOnLine, int breakOnColumn) {
   for (var line in sourceMap.lines) {
     for (var entry in line.entries) {
       if (entry.sourceLine == breakOnLine &&
           entry.sourceColumn == breakOnColumn &&
           entry.sourceUrlId != null &&
-          sourceMap.urls[entry.sourceUrlId] == testFileName) {
+          sourceMap.urls[entry.sourceUrlId!] == testFileName) {
         return '${line.line}:${entry.column}';
       }
     }
@@ -415,7 +420,7 @@
 }
 
 ProcessResult _runD8(String outInspectorPath, List<String> scriptD8Command,
-    String debugAction, List<String> breakpoints) {
+    String debugAction, List<String?> breakpoints) {
   var outInspectorPathRelative = path.relative(outInspectorPath);
   ProcessResult runResult = Process.runSync(d8Executable, [
     '--enable-inspector',
@@ -423,7 +428,7 @@
     ...scriptD8Command,
     '--',
     debugAction,
-    ...breakpoints.where((s) => s != null)
+    ...breakpoints.whereNotNull()
   ]);
   if (runResult.exitCode != 0) {
     print(runResult.stderr);
@@ -433,8 +438,8 @@
   return runResult;
 }
 
-File _cachedD8File;
-Directory _cachedSdkRoot;
+File? _cachedD8File;
+Directory? _cachedSdkRoot;
 File getD8File() {
   File attemptFileFromDir(Directory dir) {
     if (Platform.isWindows) {
@@ -472,7 +477,7 @@
   return _cachedD8File ??= search();
 }
 
-Directory get sdkRoot {
+Directory? get sdkRoot {
   getD8File();
   return _cachedSdkRoot;
 }
diff --git a/pkg/sourcemap_testing/pubspec.yaml b/pkg/sourcemap_testing/pubspec.yaml
index 88a7600..fdeb960 100644
--- a/pkg/sourcemap_testing/pubspec.yaml
+++ b/pkg/sourcemap_testing/pubspec.yaml
@@ -4,10 +4,11 @@
 publish_to: none
 
 environment:
-  sdk: '>=2.11.0 <3.0.0'
+  sdk: '>=2.16.0 <3.0.0'
 
 dependencies:
   _fe_analyzer_shared: any
+  collection: any
   dart2js_tools: any
   expect: any
   path: any