[dart2js] Migrate dart2js_tools to null safety

Change-Id: I43186c26521eca6405d0ab8b3a50b545a91b5cc2
Issue: https://github.com/dart-lang/sdk/issues/46617
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/215013
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 1c58ea1..ca074dc 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -220,7 +220,7 @@
       "name": "dart2js_tools",
       "rootUri": "../pkg/dart2js_tools",
       "packageUri": "lib/",
-      "languageVersion": "2.3"
+      "languageVersion": "2.12"
     },
     {
       "name": "dart2native",
diff --git a/pkg/dart2js_tools/bin/deobfuscate.dart b/pkg/dart2js_tools/bin/deobfuscate.dart
index d6ba837..009357f 100644
--- a/pkg/dart2js_tools/bin/deobfuscate.dart
+++ b/pkg/dart2js_tools/bin/deobfuscate.dart
@@ -10,7 +10,7 @@
 import 'package:dart2js_tools/src/util.dart';
 import 'package:dart2js_tools/src/trace_decoder.dart';
 
-/// Script that deobuscates a stack-trace given in a text file.
+/// Script that deobfuscates a stack-trace given in a text file.
 ///
 /// To run this script you need 3 or more files:
 ///
@@ -47,24 +47,24 @@
   var sb = new StringBuffer();
   try {
     String obfuscatedTrace = new File(args[0]).readAsStringSync();
-    String error = extractErrorMessage(obfuscatedTrace);
+    String? error = extractErrorMessage(obfuscatedTrace);
     var provider = new CachingFileProvider(logger: Logger());
     StackDeobfuscationResult result =
         deobfuscateStack(obfuscatedTrace, provider);
     Frame firstFrame = result.original.frames.first;
-    String translatedError =
+    String? translatedError =
         translate(error, provider.mappingFor(firstFrame.uri));
     if (translatedError == null) translatedError = '<no error message found>';
     printPadded(translatedError, error, sb);
     int longest =
-        result.deobfuscated.frames.fold(0, (m, f) => max(f.member.length, m));
+        result.deobfuscated.frames.fold(0, (m, f) => max(f.member!.length, m));
     for (var originalFrame in result.original.frames) {
       var deobfuscatedFrames = result.frameMap[originalFrame];
       if (deobfuscatedFrames == null) {
         printPadded('no mapping', '${originalFrame.location}', sb);
       } else {
         for (var frame in deobfuscatedFrames) {
-          printPadded('${frame.member.padRight(longest)} ${frame.location}',
+          printPadded('${frame.member!.padRight(longest)} ${frame.location}',
               '${originalFrame.location}', sb);
         }
       }
@@ -77,7 +77,7 @@
 final green = stdout.hasTerminal ? '\x1b[32m' : '';
 final none = stdout.hasTerminal ? '\x1b[0m' : '';
 
-printPadded(String mapping, String original, sb) {
+printPadded(String mapping, String? original, sb) {
   var len = mapping.length;
   var s = mapping.indexOf('\n');
   if (s >= 0) len -= s + 1;
diff --git a/pkg/dart2js_tools/bin/lookup_name.dart b/pkg/dart2js_tools/bin/lookup_name.dart
index f2d5c1b..e14c503 100644
--- a/pkg/dart2js_tools/bin/lookup_name.dart
+++ b/pkg/dart2js_tools/bin/lookup_name.dart
@@ -1,7 +1,8 @@
 import 'dart:io';
 import 'dart:convert';
-import 'package:source_maps/source_maps.dart';
+
 import 'package:dart2js_tools/src/dart2js_mapping.dart';
+import 'package:dart2js_tools/src/util.dart';
 
 main(List<String> args) {
   if (args.length < 2) {
@@ -16,7 +17,7 @@
     exit(1);
   }
   var json = jsonDecode(sourcemapFile.readAsStringSync());
-  Dart2jsMapping mapping = Dart2jsMapping(parseJson(json), json);
+  Dart2jsMapping mapping = Dart2jsMapping(parseSingleMapping(json), json);
   var global = mapping.globalNames[name];
   if (global != null) print('$name => $global (a global name)');
   var instance = mapping.instanceNames[name];
diff --git a/pkg/dart2js_tools/bin/show_inline_data.dart b/pkg/dart2js_tools/bin/show_inline_data.dart
index cc3d5f0..235e380 100644
--- a/pkg/dart2js_tools/bin/show_inline_data.dart
+++ b/pkg/dart2js_tools/bin/show_inline_data.dart
@@ -22,11 +22,11 @@
   var mapping = provider.mappingFor(uri);
   var starts = functionStarts(provider.sourcesFor(uri));
   var file = provider.fileFor(uri);
-  var frames = mapping.frames;
+  var frames = mapping!.frames;
   var offsets = frames.keys.toList()..sort();
   var sb = new StringBuffer();
   int depth = 0;
-  int lastFunctionStart = null;
+  int? lastFunctionStart = null;
   for (var offset in offsets) {
     int functionStart = nextFunctionStart(starts, offset, lastFunctionStart);
     if (lastFunctionStart == null || functionStart > lastFunctionStart) {
@@ -43,7 +43,7 @@
     var pad = ' ' * offsetPrefix.length;
     sb.write(offsetPrefix);
     bool first = true;
-    for (var frame in frames[offset]) {
+    for (var frame in frames[offset]!) {
       if (!first) sb.write('$pad');
       sb.write(' $frame\n');
       first = false;
@@ -75,7 +75,7 @@
   return result;
 }
 
-int nextFunctionStart(List<int> starts, int offset, int last) {
+int nextFunctionStart(List<int> starts, int offset, int? last) {
   int j = last ?? 0;
   for (; j < starts.length && starts[j] <= offset; j++);
   return j - 1;
diff --git a/pkg/dart2js_tools/lib/deobfuscate_stack_trace.dart b/pkg/dart2js_tools/lib/deobfuscate_stack_trace.dart
index 29a209e..4a43b6c 100644
--- a/pkg/dart2js_tools/lib/deobfuscate_stack_trace.dart
+++ b/pkg/dart2js_tools/lib/deobfuscate_stack_trace.dart
@@ -5,7 +5,7 @@
 import 'package:dart2js_tools/src/util.dart';
 import 'package:dart2js_tools/src/trace_decoder.dart';
 
-/// Deobuscates the given [obfuscatedTrace].
+/// Deobfuscates the given [obfuscatedTrace].
 ///
 /// This method assumes a stack trace contains URIs normalized to be file URIs.
 /// If for example you obtain a stack from a browser, you may need to preprocess
@@ -27,7 +27,7 @@
 ///  `//# sourceMappingURL=` line at the end, and load the corresponding
 /// source-map file.
 String deobfuscateStackTrace(String obfuscatedTrace) {
-  String error = extractErrorMessage(obfuscatedTrace);
+  String? error = extractErrorMessage(obfuscatedTrace);
   var provider = CachingFileProvider();
   StackDeobfuscationResult result = deobfuscateStack(obfuscatedTrace, provider);
   Frame firstFrame = result.original.frames.first;
@@ -38,14 +38,14 @@
 
   var sb = StringBuffer();
   sb.writeln(translatedError);
-  maxMemberLengthHelper(int m, Frame f) => max(f.member.length, m);
+  maxMemberLengthHelper(int m, Frame f) => max(f.member!.length, m);
   int longest = result.deobfuscated.frames.fold(0, maxMemberLengthHelper);
   longest = result.original.frames.fold(longest, maxMemberLengthHelper);
   for (var originalFrame in result.original.frames) {
     var deobfuscatedFrames = result.frameMap[originalFrame];
     if (deobfuscatedFrames == null) {
       var name = originalFrame.member;
-      sb.writeln('    at ${name.padRight(longest)} ${originalFrame.location}');
+      sb.writeln('    at ${name!.padRight(longest)} ${originalFrame.location}');
     } else {
       for (var frame in deobfuscatedFrames) {
         var name = frame.member;
@@ -53,7 +53,7 @@
         // client, we can start encoding the function name and remove this
         // workaround.
         if (name == '<unknown>') name = originalFrame.member;
-        sb.writeln('    at ${name.padRight(longest)} ${frame.location}');
+        sb.writeln('    at ${name!.padRight(longest)} ${frame.location}');
       }
     }
   }
diff --git a/pkg/dart2js_tools/lib/src/dart2js_mapping.dart b/pkg/dart2js_tools/lib/src/dart2js_mapping.dart
index ac6b9d7..6432aaa 100644
--- a/pkg/dart2js_tools/lib/src/dart2js_mapping.dart
+++ b/pkg/dart2js_tools/lib/src/dart2js_mapping.dart
@@ -23,38 +23,32 @@
   final Map<String, String> globalNames = {};
   final Map<String, String> instanceNames = {};
   final Map<int, List<FrameEntry>> frames = {};
-  List<int> _frameIndex;
-  List<int> get frameIndex {
-    if (_frameIndex == null) {
-      _frameIndex = frames.keys.toList()..sort();
-    }
-    return _frameIndex;
-  }
+  late final List<int> frameIndex = frames.keys.toList()..sort();
 
-  Dart2jsMapping(this.sourceMap, Map json, {Logger logger}) {
+  Dart2jsMapping(this.sourceMap, Map json, {Logger? logger}) {
     var extensions = json['x_org_dartlang_dart2js'];
     if (extensions == null) return;
     var minifiedNames = extensions['minified_names'];
     if (minifiedNames != null) {
-      _extractMinifedNames(
+      _extractMinifiedNames(
           minifiedNames['global'], sourceMap, globalNames, logger);
-      _extractMinifedNames(
+      _extractMinifiedNames(
           minifiedNames['instance'], sourceMap, instanceNames, logger);
     }
-    String jsonFrames = extensions['frames'];
+    String? jsonFrames = extensions['frames'];
     if (jsonFrames != null) {
       new _FrameDecoder(jsonFrames).parseFrames(frames, sourceMap);
     }
   }
 
-  Dart2jsMapping.json(Map json) : this(parseJson(json), json);
+  Dart2jsMapping.json(Map json) : this(parseSingleMapping(json), json);
 }
 
 class FrameEntry {
-  final String callUri;
-  final int callLine;
-  final int callColumn;
-  final String inlinedMethodName;
+  final String? callUri;
+  final int? callLine;
+  final int? callColumn;
+  final String? inlinedMethodName;
   final bool isEmpty;
   FrameEntry.push(
       this.callUri, this.callLine, this.callColumn, this.inlinedMethodName)
@@ -76,7 +70,7 @@
 }
 
 const _marker = "\n//# sourceMappingURL=";
-Dart2jsMapping parseMappingFor(Uri uri, {Logger logger}) {
+Dart2jsMapping? parseMappingFor(Uri uri, {Logger? logger}) {
   var file = new File.fromUri(uri);
   if (!file.existsSync()) {
     logger?.log('Error: no such file: $uri');
@@ -100,7 +94,7 @@
     return null;
   }
   var json = jsonDecode(sourcemapFile.readAsStringSync());
-  return new Dart2jsMapping(parseJson(json), json, logger: logger);
+  return new Dart2jsMapping(parseSingleMapping(json), json, logger: logger);
 }
 
 class _FrameDecoder implements Iterator<String> {
@@ -112,8 +106,9 @@
   // Iterator API is used by decodeVlq to consume VLQ entries.
   bool moveNext() => ++index < _length;
 
-  String get current =>
-      (index >= 0 && index < _length) ? _internal[index] : null;
+  String get current => (index >= 0 && index < _length)
+      ? _internal[index]
+      : throw StateError('No current value available.');
 
   bool get hasTokens => index < _length - 1 && _length > 0;
 
@@ -150,8 +145,8 @@
   }
 }
 
-_extractMinifedNames(String encodedInput, SingleMapping sourceMap,
-    Map<String, String> minifiedNames, Logger logger) {
+_extractMinifiedNames(String encodedInput, SingleMapping sourceMap,
+    Map<String, String> minifiedNames, Logger? logger) {
   if (encodedInput.isEmpty) return;
   List<String> input = encodedInput.split(',');
   if (input.length % 2 != 0) {
@@ -159,7 +154,7 @@
   }
   for (int i = 0; i < input.length; i += 2) {
     String minifiedName = input[i];
-    int id = int.tryParse(input[i + 1]);
+    int id = int.parse(input[i + 1]);
     minifiedNames[minifiedName] = sourceMap.names[id];
   }
 }
diff --git a/pkg/dart2js_tools/lib/src/name_decoder.dart b/pkg/dart2js_tools/lib/src/name_decoder.dart
index 306bead..862c7b4 100644
--- a/pkg/dart2js_tools/lib/src/name_decoder.dart
+++ b/pkg/dart2js_tools/lib/src/name_decoder.dart
@@ -9,8 +9,8 @@
 import 'dart2js_mapping.dart';
 import 'trace.dart';
 
-String translate(String error, Dart2jsMapping mapping,
-    [StackTraceLine line, TargetEntry entry]) {
+String? translate(String? error, Dart2jsMapping? mapping,
+    [StackTraceLine? line, TargetEntry? entry]) {
   for (var decoder in _errorMapDecoders) {
     var result = decoder.decode(error, mapping, line, entry);
     // More than one decoder might be applied on a single error message. This
@@ -30,10 +30,10 @@
   /// Decode [error] that was reported in [line] and has a corresponding [entry]
   /// in the source-map file. The provided [mapping] includes additional
   /// minification data that may be used to decode the error message.
-  String decode(String error, Dart2jsMapping mapping, StackTraceLine line,
-      TargetEntry entry) {
+  String? decode(String? error, Dart2jsMapping? mapping, StackTraceLine? line,
+      TargetEntry? entry) {
     if (error == null) return null;
-    Match lastMatch = null;
+    Match? lastMatch = null;
     var result = new StringBuffer();
     for (var match in _matcher.allMatches(error)) {
       var decodedMatch = _decodeInternal(match, mapping, line, entry);
@@ -49,8 +49,8 @@
     return '$result';
   }
 
-  String _decodeInternal(Match match, Dart2jsMapping mapping,
-      StackTraceLine line, TargetEntry entry);
+  String? _decodeInternal(Match match, Dart2jsMapping? mapping,
+      StackTraceLine? line, TargetEntry? entry);
 }
 
 typedef String ErrorDecoder(Match match, Dart2jsMapping mapping,
@@ -59,45 +59,45 @@
 class MinifiedNameDecoder extends ErrorMapDecoder {
   final RegExp _matcher = new RegExp("minified:([a-zA-Z0-9_\$]*)");
 
-  String _decodeInternal(Match match, Dart2jsMapping mapping,
-      StackTraceLine line, TargetEntry entry) {
+  String? _decodeInternal(Match match, Dart2jsMapping? mapping,
+      StackTraceLine? line, TargetEntry? entry) {
     var minifiedName = match.group(1);
-    return mapping.globalNames[minifiedName];
+    return mapping!.globalNames[minifiedName];
   }
 }
 
 class CannotReadPropertyDecoder extends ErrorMapDecoder {
   final RegExp _matcher = new RegExp("Cannot read property '([^']*)' of");
 
-  String _decodeInternal(Match match, Dart2jsMapping mapping,
-      StackTraceLine line, TargetEntry entry) {
+  String? _decodeInternal(Match match, Dart2jsMapping? mapping,
+      StackTraceLine? line, TargetEntry? entry) {
     var minifiedName = match.group(1);
-    var name = mapping.instanceNames[minifiedName];
+    var name = mapping!.instanceNames[minifiedName];
     if (name == null) return null;
     return "Cannot read property '$name' of";
   }
 }
 
 abstract class NoSuchMethodDecoderBase extends ErrorMapDecoder {
-  String _translateMinifiedName(Dart2jsMapping mapping, String minifiedName) {
+  String? _translateMinifiedName(Dart2jsMapping mapping, String? minifiedName) {
     var name = mapping.instanceNames[minifiedName];
     if (name != null) return "'$name'";
-    if (minifiedName.startsWith(new RegExp(r'(call)?\$[0-9]'))) {
+    if (minifiedName!.startsWith(new RegExp(r'(call)?\$[0-9]'))) {
       int first$ = minifiedName.indexOf(r'$');
       return _expandCallSignature(minifiedName.substring(first$));
     }
     return null;
   }
 
-  String _expandCallSignature(String callSignature) {
+  String? _expandCallSignature(String callSignature) {
     // Minified names are one of these forms:
     //   $0         // positional arguments only
     //   $1$2       // type parameters and positional arguments
     //   $3$name    // positional and named arguments
     //   $1$3$name  // type parameters and positional and named args
     var signature = callSignature.split(r'$');
-    var typeArgs = null;
-    var totalArgs = null;
+    int? typeArgs = null;
+    int? totalArgs = null;
     var namedArgs = <String>[];
     for (var arg in signature) {
       if (arg == "") continue;
@@ -115,6 +115,7 @@
         namedArgs.add(arg);
       }
     }
+    if (totalArgs == null) return null;
     var sb = new StringBuffer();
     sb.write("'call'");
     sb.write(" (with ");
@@ -136,11 +137,11 @@
   final RegExp _matcher = new RegExp(
       "NoSuchMethodError: method not found: '([^']*)'( on [^\\(]*)? \\(.*\\)");
 
-  String _decodeInternal(Match match, Dart2jsMapping mapping,
-      StackTraceLine line, TargetEntry entry) {
+  String? _decodeInternal(Match match, Dart2jsMapping? mapping,
+      StackTraceLine? line, TargetEntry? entry) {
     var minifiedName = match.group(1);
     var suffix = match.group(2) ?? '';
-    var name = _translateMinifiedName(mapping, minifiedName);
+    var name = _translateMinifiedName(mapping!, minifiedName);
     if (name == null) return null;
     return "NoSuchMethodError: method not found: $name$suffix";
   }
@@ -150,10 +151,10 @@
   final RegExp _matcher =
       new RegExp("NoSuchMethodError: method not found: '([^']*)'");
 
-  String _decodeInternal(Match match, Dart2jsMapping mapping,
-      StackTraceLine line, TargetEntry entry) {
+  String? _decodeInternal(Match match, Dart2jsMapping? mapping,
+      StackTraceLine? line, TargetEntry? entry) {
     var minifiedName = match.group(1);
-    var name = _translateMinifiedName(mapping, minifiedName);
+    var name = _translateMinifiedName(mapping!, minifiedName);
     if (name == null) return null;
     return "NoSuchMethodError: method not found: $name";
   }
@@ -162,10 +163,10 @@
 class UnhandledNotAFunctionError extends ErrorMapDecoder {
   final RegExp _matcher = new RegExp("Error: ([^']*) is not a function");
 
-  String _decodeInternal(Match match, Dart2jsMapping mapping,
-      StackTraceLine line, TargetEntry entry) {
+  String? _decodeInternal(Match match, Dart2jsMapping? mapping,
+      StackTraceLine? line, TargetEntry? entry) {
     var minifiedName = match.group(1);
-    var name = mapping.instanceNames[minifiedName];
+    var name = mapping!.instanceNames[minifiedName];
     if (name == null) return null;
     return "Error: $name is not a function";
   }
diff --git a/pkg/dart2js_tools/lib/src/sourcemap_helper.dart b/pkg/dart2js_tools/lib/src/sourcemap_helper.dart
index 68ffd94..cdbf85a 100644
--- a/pkg/dart2js_tools/lib/src/sourcemap_helper.dart
+++ b/pkg/dart2js_tools/lib/src/sourcemap_helper.dart
@@ -10,11 +10,10 @@
 
 /// Search backwards in [sources] for a function declaration that includes the
 /// [start] offset.
-TargetEntry findEnclosingFunction(FileProvider provider, Uri uri, int start) {
+TargetEntry? findEnclosingFunction(FileProvider provider, Uri uri, int start) {
   String sources = provider.sourcesFor(uri);
-  if (sources == null) return null;
   SourceFile file = provider.fileFor(uri);
-  SingleMapping mapping = provider.mappingFor(uri).sourceMap;
+  SingleMapping mapping = provider.mappingFor(uri)!.sourceMap;
   var index = start;
   while (true) {
     index = nextDeclarationCandidate(sources, index);
@@ -22,7 +21,7 @@
     var line = file.getLine(index);
     var lineEntry = findLine(mapping, line);
     var column = file.getColumn(index);
-    TargetEntry result = findColumn(line, column, lineEntry);
+    TargetEntry? result = findColumn(line, column, lineEntry);
     // If the name entry doesn't start exactly at the column corresponding to
     // `index`, we must be in the middle of a string or code that uses the word
     // "function", but that doesn't have a corresponding mapping. In those
@@ -61,7 +60,7 @@
 /// number is lower or equal to [line].
 ///
 /// Copied from [SingleMapping._findLine].
-TargetLineEntry findLine(SingleMapping sourceMap, int line) {
+TargetLineEntry? findLine(SingleMapping sourceMap, int line) {
   int index = binarySearch(sourceMap.lines, (e) => e.line > line);
   return (index <= 0) ? null : sourceMap.lines[index - 1];
 }
@@ -73,7 +72,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.length == 0) return null;
   if (lineEntry.line != line) return lineEntry.entries.last;
   var entries = lineEntry.entries;
diff --git a/pkg/dart2js_tools/lib/src/trace.dart b/pkg/dart2js_tools/lib/src/trace.dart
index 846af16..07bdb1e 100644
--- a/pkg/dart2js_tools/lib/src/trace.dart
+++ b/pkg/dart2js_tools/lib/src/trace.dart
@@ -14,10 +14,10 @@
 
 /// Represents a stack trace line.
 class StackTraceLine {
-  String methodName;
-  String fileName;
-  int lineNo;
-  int columnNo;
+  final String? methodName;
+  final String fileName;
+  final int? lineNo;
+  final int columnNo;
 
   StackTraceLine(this.methodName, this.fileName, this.lineNo, this.columnNo);
 
@@ -31,11 +31,11 @@
   ///     at <fileName>:<lineNo>
   ///     at <fileName>
   ///
-  factory StackTraceLine.fromText(String text, {Logger logger}) {
+  factory StackTraceLine.fromText(String text, {Logger? logger}) {
     text = text.trim();
     assert(text.startsWith('at '));
     text = text.substring('at '.length);
-    String methodName;
+    String? methodName;
     int endParen = text.indexOf(')');
     if (endParen > 0) {
       int nameEnd = text.indexOf('(');
@@ -46,16 +46,16 @@
         logger?.log('Missing left-paren in: $text');
       }
     }
-    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;
@@ -84,14 +84,14 @@
     if (methodName != null) {
       sb.write(methodName);
       sb.write(' (');
-      sb.write(fileName ?? '?');
+      sb.write(fileName);
       sb.write(':');
       sb.write(lineNo);
       sb.write(':');
       sb.write(columnNo);
       sb.write(')');
     } else {
-      sb.write(fileName ?? '?');
+      sb.write(fileName);
       sb.write(':');
       sb.write(lineNo);
       sb.write(':');
@@ -103,27 +103,27 @@
   String get inlineString {
     StringBuffer sb = new StringBuffer();
     var padding = 20;
-    if (methodName != null) {
-      sb.write(methodName);
-      padding -= (methodName.length);
+    var lineMethodName = methodName;
+    if (lineMethodName != null) {
+      sb.write(lineMethodName);
+      padding -= (lineMethodName.length);
       if (padding <= 0) {
         sb.write('\n');
         padding = 20;
       }
     }
     sb.write(' ' * padding);
-    if (fileName != null) {
-      sb.write(p.url.basename(fileName));
-      sb.write(' ');
-      sb.write(lineNo);
-      sb.write(':');
-      sb.write(columnNo);
-    }
+    sb.write(p.url.basename(fileName));
+    sb.write(' ');
+    sb.write(lineNo);
+    sb.write(':');
+    sb.write(columnNo);
+
     return sb.toString();
   }
 }
 
-List<StackTraceLine> parseStackTrace(String trace, {Logger logger}) {
+List<StackTraceLine> parseStackTrace(String trace, {Logger? logger}) {
   List<String> lines = trace.split(new RegExp(r'(\r|\n|\r\n)'));
   List<StackTraceLine> jsStackTrace = <StackTraceLine>[];
   for (String line in lines) {
@@ -138,7 +138,7 @@
 /// Returns the portion of the output that corresponds to the error message.
 ///
 /// Note: some errors can span multiple lines.
-String extractErrorMessage(String trace) {
+String? extractErrorMessage(String trace) {
   var firstStackFrame = trace.indexOf(new RegExp('\n +at'));
   if (firstStackFrame == -1) return null;
   var errorMarker = trace.indexOf('^') + 1;
diff --git a/pkg/dart2js_tools/lib/src/trace_decoder.dart b/pkg/dart2js_tools/lib/src/trace_decoder.dart
index 7ac12aa..6331bad 100644
--- a/pkg/dart2js_tools/lib/src/trace_decoder.dart
+++ b/pkg/dart2js_tools/lib/src/trace_decoder.dart
@@ -2,7 +2,7 @@
 // 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.
 
-/// Logic to expand and deobuscate stack traces.
+/// Logic to expand and deobfuscate stack traces.
 
 import 'package:stack_trace/stack_trace.dart';
 import 'package:source_span/source_span.dart';
@@ -16,12 +16,12 @@
   /// Representation of the obfuscated stack trace.
   final Trace original;
 
-  /// Representation of the deobfsucated stack trace.
+  /// Representation of the deobfuscated stack trace.
   final Trace deobfuscated;
 
   /// Details about how one original frame maps to deobfuscated frames. A single
   /// frame might map to many frames (in the case of inlining), or to a null
-  /// value (when we were unabled to deobfuscate it).
+  /// value (when we were unable to deobfuscate it).
   final Map<Frame, List<Frame>> frameMap;
 
   StackDeobfuscationResult(this.original, this.deobfuscated, this.frameMap);
@@ -35,22 +35,23 @@
   var deobfuscatedFrames = <Frame>[];
   var frameMap = <Frame, List<Frame>>{};
   for (var frame in trace.frames) {
+    var frameLine = frame.line;
     // If there's no line information, there's no way to translate this frame.
     // We could return it as-is, but these lines are usually not useful anyways.
-    if (frame.line == null) {
+    if (frameLine == null) {
       continue;
     }
 
     // If there's no column, try using the first column of the line.
     var column = frame.column ?? 1;
 
-    Dart2jsMapping mapping = provider.mappingFor(frame.uri);
+    Dart2jsMapping? mapping = provider.mappingFor(frame.uri);
     if (mapping == null) continue;
 
     // Subtract 1 because stack traces use 1-indexed lines and columns and
     // source maps uses 0-indexed.
-    SourceSpan span = mapping.sourceMap
-        .spanFor(frame.line - 1, column - 1, uri: frame.uri?.toString());
+    SourceSpan? span = mapping.sourceMap
+        .spanFor(frameLine - 1, column - 1, uri: frame.uri.toString());
 
     // If we can't find a source span, ignore the frame. It's probably something
     // internal that the user doesn't care about.
@@ -59,11 +60,11 @@
     List<Frame> mappedFrames = frameMap[frame] = [];
 
     SourceFile jsFile = provider.fileFor(frame.uri);
-    int offset = jsFile.getOffset(frame.line - 1, column - 1);
+    int offset = jsFile.getOffset(frameLine - 1, column - 1);
     String nameOf(id) =>
         _normalizeName(id >= 0 ? mapping.sourceMap.names[id] : null);
 
-    Uri fileName = span.sourceUrl;
+    Uri? fileName = span.sourceUrl;
     int targetLine = span.start.line + 1;
     int targetColumn = span.start.column + 1;
 
@@ -78,13 +79,13 @@
     int depth = 0;
     outer:
     while (key >= 0) {
-      for (var frame in frames[index[key]].reversed) {
+      for (var frame in frames[index[key]]!.reversed) {
         if (frame.isEmpty) break outer;
         if (frame.isPush) {
           if (depth <= 0) {
-            mappedFrames.add(new Frame(fileName, targetLine, targetColumn,
+            mappedFrames.add(new Frame(fileName!, targetLine, targetColumn,
                 _normalizeName(frame.inlinedMethodName) + "(inlined)"));
-            fileName = Uri.parse(frame.callUri);
+            fileName = Uri.parse(frame.callUri!);
             targetLine = (frame.callLine ?? 0) + 1;
             targetColumn = (frame.callColumn ?? 0) + 1;
           } else {
@@ -100,7 +101,8 @@
 
     var functionEntry = findEnclosingFunction(provider, frame.uri, offset);
     String methodName = nameOf(functionEntry?.sourceNameId ?? -1);
-    mappedFrames.add(new Frame(fileName, targetLine, targetColumn, methodName));
+    mappedFrames
+        .add(new Frame(fileName!, targetLine, targetColumn, methodName));
     deobfuscatedFrames.addAll(mappedFrames);
   }
   return new StackDeobfuscationResult(
@@ -109,6 +111,6 @@
 
 /// Ensure we don't use spaces in method names. At this time, they are only
 /// introduced by `<anonymous function>`.
-_normalizeName(String methodName) =>
+String _normalizeName(String? methodName) =>
     methodName?.replaceAll("<anonymous function>", "<anonymous>") ??
     '<unknown>';
diff --git a/pkg/dart2js_tools/lib/src/util.dart b/pkg/dart2js_tools/lib/src/util.dart
index 433ac5a..1a733ac 100644
--- a/pkg/dart2js_tools/lib/src/util.dart
+++ b/pkg/dart2js_tools/lib/src/util.dart
@@ -1,18 +1,19 @@
 import 'dart:io';
+import 'package:source_maps/parser.dart';
 import 'package:source_span/source_span.dart';
 import 'dart2js_mapping.dart';
 
 abstract class FileProvider {
   String sourcesFor(Uri uri);
   SourceFile fileFor(Uri uri);
-  Dart2jsMapping mappingFor(Uri uri);
+  Dart2jsMapping? mappingFor(Uri uri);
 }
 
 class CachingFileProvider implements FileProvider {
   final Map<Uri, String> _sources = {};
   final Map<Uri, SourceFile> _files = {};
-  final Map<Uri, Dart2jsMapping> _mappings = {};
-  final Logger logger;
+  final Map<Uri, Dart2jsMapping?> _mappings = {};
+  final Logger? logger;
 
   CachingFileProvider({this.logger});
 
@@ -22,7 +23,7 @@
   SourceFile fileFor(Uri uri) =>
       _files[uri] ??= new SourceFile.fromString(sourcesFor(uri));
 
-  Dart2jsMapping mappingFor(Uri uri) =>
+  Dart2jsMapping? mappingFor(Uri uri) =>
       _mappings[uri] ??= parseMappingFor(uri, logger: logger);
 }
 
@@ -44,7 +45,7 @@
 
   SourceFile fileFor(Uri uri) => super.fileFor(_localize(uri));
 
-  Dart2jsMapping mappingFor(Uri uri) => super.mappingFor(_localize(uri));
+  Dart2jsMapping? mappingFor(Uri uri) => super.mappingFor(_localize(uri));
 }
 
 class Logger {
@@ -57,3 +58,5 @@
 }
 
 var logger = Logger();
+
+SingleMapping parseSingleMapping(Map json) => parseJson(json) as SingleMapping;
diff --git a/pkg/dart2js_tools/pubspec.yaml b/pkg/dart2js_tools/pubspec.yaml
index a6a46fe..392ed56 100644
--- a/pkg/dart2js_tools/pubspec.yaml
+++ b/pkg/dart2js_tools/pubspec.yaml
@@ -10,4 +10,4 @@
   source_span: any
   stack_trace: ^1.9.3
 environment:
-  sdk: '>=2.3.0 <3.0.0'
+  sdk: '>=2.12.0 <3.0.0'