Merge package:source_maps into the tools monorepo
diff --git a/pkgs/source_maps/.github/dependabot.yaml b/pkgs/source_maps/.github/dependabot.yaml
new file mode 100644
index 0000000..bf6b38a
--- /dev/null
+++ b/pkgs/source_maps/.github/dependabot.yaml
@@ -0,0 +1,14 @@
+# Dependabot configuration file.
+version: 2
+
+updates:
+  - package-ecosystem: github-actions
+    directory: /
+    schedule:
+      interval: monthly
+    labels:
+      - autosubmit
+    groups:
+      github-actions:
+        patterns:
+          - "*"
diff --git a/pkgs/source_maps/.github/workflows/no-response.yml b/pkgs/source_maps/.github/workflows/no-response.yml
new file mode 100644
index 0000000..ab1ac49
--- /dev/null
+++ b/pkgs/source_maps/.github/workflows/no-response.yml
@@ -0,0 +1,37 @@
+# A workflow to close issues where the author hasn't responded to a request for
+# more information; see https://github.com/actions/stale.
+
+name: No Response
+
+# Run as a daily cron.
+on:
+  schedule:
+    # Every day at 8am
+    - cron: '0 8 * * *'
+
+# All permissions not specified are set to 'none'.
+permissions:
+  issues: write
+  pull-requests: write
+
+jobs:
+  no-response:
+    runs-on: ubuntu-latest
+    if: ${{ github.repository_owner == 'dart-lang' }}
+    steps:
+      - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e
+        with:
+          # Don't automatically mark inactive issues+PRs as stale.
+          days-before-stale: -1
+          # Close needs-info issues and PRs after 14 days of inactivity.
+          days-before-close: 14
+          stale-issue-label: "needs-info"
+          close-issue-message: >
+            Without additional information we're not able to resolve this issue.
+            Feel free to add more info or respond to any questions above and we
+            can reopen the case. Thanks for your contribution!
+          stale-pr-label: "needs-info"
+          close-pr-message: >
+            Without additional information we're not able to resolve this PR.
+            Feel free to add more info or respond to any questions above.
+            Thanks for your contribution!
diff --git a/pkgs/source_maps/.github/workflows/publish.yaml b/pkgs/source_maps/.github/workflows/publish.yaml
new file mode 100644
index 0000000..27157a0
--- /dev/null
+++ b/pkgs/source_maps/.github/workflows/publish.yaml
@@ -0,0 +1,17 @@
+# A CI configuration to auto-publish pub packages.
+
+name: Publish
+
+on:
+  pull_request:
+    branches: [ master ]
+  push:
+    tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ]
+
+jobs:
+  publish:
+    if: ${{ github.repository_owner == 'dart-lang' }}
+    uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main
+    permissions:
+      id-token: write # Required for authentication using OIDC
+      pull-requests: write # Required for writing the pull request note
diff --git a/pkgs/source_maps/.github/workflows/test-package.yml b/pkgs/source_maps/.github/workflows/test-package.yml
new file mode 100644
index 0000000..e6f7c86
--- /dev/null
+++ b/pkgs/source_maps/.github/workflows/test-package.yml
@@ -0,0 +1,61 @@
+name: Dart CI
+
+on:
+  # Run on PRs and pushes to the default branch.
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+  schedule:
+    - cron: "0 0 * * 0"
+
+env:
+  PUB_ENVIRONMENT: bot.github
+
+jobs:
+  # Check code formatting and static analysis on a single OS (linux)
+  # against Dart dev.
+  analyze:
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        sdk: [dev]
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+        with:
+          sdk: ${{ matrix.sdk }}
+      - id: install
+        name: Install dependencies
+        run: dart pub get
+      - name: Check formatting
+        run: dart format --output=none --set-exit-if-changed .
+        if: always() && steps.install.outcome == 'success'
+      - name: Analyze code
+        run: dart analyze --fatal-infos
+        if: always() && steps.install.outcome == 'success'
+
+  # Run tests on a matrix consisting of two dimensions:
+  # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+  # 2. release channel: dev
+  test:
+    needs: analyze
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        # Add macos-latest and/or windows-latest if relevant for this package.
+        os: [ubuntu-latest]
+        sdk: [3.3.0, dev]
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+        with:
+          sdk: ${{ matrix.sdk }}
+      - id: install
+        name: Install dependencies
+        run: dart pub get
+      - name: Run VM tests
+        run: dart test --platform vm
+        if: always() && steps.install.outcome == 'success'
diff --git a/pkgs/source_maps/.gitignore b/pkgs/source_maps/.gitignore
new file mode 100644
index 0000000..f73b2f9
--- /dev/null
+++ b/pkgs/source_maps/.gitignore
@@ -0,0 +1,4 @@
+.dart_tool/
+.packages
+.pub/
+pubspec.lock
diff --git a/pkgs/source_maps/CHANGELOG.md b/pkgs/source_maps/CHANGELOG.md
new file mode 100644
index 0000000..9c8e7b3
--- /dev/null
+++ b/pkgs/source_maps/CHANGELOG.md
@@ -0,0 +1,130 @@
+## 0.10.13-wip
+
+- Require Dart 3.3
+
+## 0.10.12
+
+* Add additional types at API boundaries.
+
+## 0.10.11
+
+* Populate the pubspec `repository` field.
+* Update the source map documentation link in the readme.
+
+## 0.10.10
+
+* Stable release for null safety.
+
+## 0.10.9
+
+* Fix a number of document comment issues.
+* Allow parsing source map files with a missing `names` field.
+
+## 0.10.8
+
+* Preserve source-map extensions in `SingleMapping`. Extensions are keys in the
+  json map that start with `"x_"`.
+
+## 0.10.7
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 0.10.6
+
+* Require version 2.0.0 of the Dart SDK.
+
+## 0.10.5
+
+* Add a `SingleMapping.files` field which provides access to `SourceFile`s
+  representing the `"sourcesContent"` fields in the source map.
+
+* Add an `includeSourceContents` flag to `SingleMapping.toJson()` which
+  indicates whether to include source file contents in the source map.
+
+## 0.10.4
+* Implement `highlight` in `SourceMapFileSpan`.
+* Require version `^1.3.0` of `source_span`.
+
+## 0.10.3
+ * Add `addMapping` and `containsMapping` members to `MappingBundle`.
+
+## 0.10.2
+ * Support for extended source map format.
+ * Polish `MappingBundle.spanFor` handling of URIs that have a suffix that
+   exactly match a source map in the MappingBundle.
+
+## 0.10.1+5
+ * Fix strong mode warning in test.
+
+## 0.10.1+4
+
+* Extend `MappingBundle.spanFor` to accept requests for output files that
+  don't have source maps.
+
+## 0.10.1+3
+
+* Add `MappingBundle` class that handles extended source map format that
+  supports source maps for multiple output files in a single mapper.
+  Extend `Mapping.spanFor` API to accept a uri parameter that is optional
+  for normal source maps but required for MappingBundle source maps.
+
+## 0.10.1+2
+
+* Fix more strong mode warnings.
+
+## 0.10.1+1
+
+* Fix all strong mode warnings.
+
+## 0.10.1
+
+* Add a `mapUrl` named argument to `parse` and `parseJson`. This argument is
+  used to resolve source URLs for source spans.
+
+## 0.10.0+2
+
+* Fix analyzer error (FileSpan has a new field since `source_span` 1.1.1)
+
+## 0.10.0+1
+
+* Remove an unnecessary warning printed when the "file" field is missing from a
+  Json formatted source map. This field is optional and its absence is not
+  unusual.
+
+## 0.10.0
+
+* Remove the `Span`, `Location` and `SourceFile` classes. Use the
+  corresponding `source_span` classes instead.
+
+## 0.9.4
+
+* Update `SpanFormatException` with `source` and `offset`.
+
+* All methods that take `Span`s, `Location`s, and `SourceFile`s as inputs now
+  also accept the corresponding `source_span` classes as well. Using the old
+  classes is now deprecated and will be unsupported in version 0.10.0.
+
+## 0.9.3
+
+* Support writing SingleMapping objects to source map version 3 format.
+* Support the `sourceRoot` field in the SingleMapping class.
+* Support updating the `targetUrl` field in the SingleMapping class.
+
+## 0.9.2+2
+
+* Fix a bug in `FixedSpan.getLocationMessage`.
+
+## 0.9.2+1
+
+* Minor readability improvements to `FixedSpan.getLocationMessage` and
+  `SpanException.toString`.
+
+## 0.9.2
+
+* Add `SpanException` and `SpanFormatException` classes.
+
+## 0.9.1
+
+* Support unmapped areas in source maps.
+
+* Increase the readability of location messages.
diff --git a/pkgs/source_maps/LICENSE b/pkgs/source_maps/LICENSE
new file mode 100644
index 0000000..162572a
--- /dev/null
+++ b/pkgs/source_maps/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google LLC nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/source_maps/README.md b/pkgs/source_maps/README.md
new file mode 100644
index 0000000..30da7f8
--- /dev/null
+++ b/pkgs/source_maps/README.md
@@ -0,0 +1,25 @@
+[![Dart CI](https://github.com/dart-lang/source_maps/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/source_maps/actions/workflows/test-package.yml)
+[![pub package](https://img.shields.io/pub/v/source_maps.svg)](https://pub.dev/packages/source_maps)
+[![package publisher](https://img.shields.io/pub/publisher/source_maps.svg)](https://pub.dev/packages/source_maps/publisher)
+
+This project implements a Dart pub package to work with source maps.
+
+## Docs and usage
+
+The implementation is based on the [source map version 3 spec][spec] which was
+originated from the [Closure Compiler][closure] and has been implemented in
+Chrome and Firefox.
+
+In this package we provide:
+
+  * Data types defining file locations and spans: these are not part of the
+    original source map specification. These data types are great for tracking
+    source locations on source maps, but they can also be used by tools to
+    reporting useful error messages that include on source locations.
+  * A builder that creates a source map programmatically and produces the encoded
+    source map format.
+  * A parser that reads the source map format and provides APIs to read the
+    mapping information.
+
+[closure]: https://github.com/google/closure-compiler/wiki/Source-Maps
+[spec]: https://docs.google.com/a/google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
diff --git a/pkgs/source_maps/analysis_options.yaml b/pkgs/source_maps/analysis_options.yaml
new file mode 100644
index 0000000..d978f81
--- /dev/null
+++ b/pkgs/source_maps/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
diff --git a/pkgs/source_maps/lib/builder.dart b/pkgs/source_maps/lib/builder.dart
new file mode 100644
index 0000000..54ba743
--- /dev/null
+++ b/pkgs/source_maps/lib/builder.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Contains a builder object useful for creating source maps programatically.
+library source_maps.builder;
+
+// TODO(sigmund): add a builder for multi-section mappings.
+
+import 'dart:convert';
+
+import 'package:source_span/source_span.dart';
+
+import 'parser.dart';
+import 'src/source_map_span.dart';
+
+/// Builds a source map given a set of mappings.
+class SourceMapBuilder {
+  final List<Entry> _entries = <Entry>[];
+
+  /// Adds an entry mapping the [targetOffset] to [source].
+  void addFromOffset(SourceLocation source, SourceFile targetFile,
+      int targetOffset, String identifier) {
+    ArgumentError.checkNotNull(targetFile, 'targetFile');
+    _entries.add(Entry(source, targetFile.location(targetOffset), identifier));
+  }
+
+  /// Adds an entry mapping [target] to [source].
+  ///
+  /// If [isIdentifier] is true or if [target] is a [SourceMapSpan] with
+  /// `isIdentifier` set to true, this entry is considered to represent an
+  /// identifier whose value will be stored in the source map. [isIdentifier]
+  /// takes precedence over [target]'s `isIdentifier` value.
+  void addSpan(SourceSpan source, SourceSpan target, {bool? isIdentifier}) {
+    isIdentifier ??= source is SourceMapSpan ? source.isIdentifier : false;
+
+    var name = isIdentifier ? source.text : null;
+    _entries.add(Entry(source.start, target.start, name));
+  }
+
+  /// Adds an entry mapping [target] to [source].
+  void addLocation(
+      SourceLocation source, SourceLocation target, String? identifier) {
+    _entries.add(Entry(source, target, identifier));
+  }
+
+  /// Encodes all mappings added to this builder as a json map.
+  Map<String, dynamic> build(String fileUrl) {
+    return SingleMapping.fromEntries(_entries, fileUrl).toJson();
+  }
+
+  /// Encodes all mappings added to this builder as a json string.
+  String toJson(String fileUrl) => jsonEncode(build(fileUrl));
+}
+
+/// An entry in the source map builder.
+class Entry implements Comparable<Entry> {
+  /// Span denoting the original location in the input source file
+  final SourceLocation source;
+
+  /// Span indicating the corresponding location in the target file.
+  final SourceLocation target;
+
+  /// An identifier name, when this location is the start of an identifier.
+  final String? identifierName;
+
+  /// Creates a new [Entry] mapping [target] to [source].
+  Entry(this.source, this.target, this.identifierName);
+
+  /// Implements [Comparable] to ensure that entries are ordered by their
+  /// location in the target file. We sort primarily by the target offset
+  /// because source map files are encoded by printing each mapping in order as
+  /// they appear in the target file.
+  @override
+  int compareTo(Entry other) {
+    var res = target.compareTo(other.target);
+    if (res != 0) return res;
+    res = source.sourceUrl
+        .toString()
+        .compareTo(other.source.sourceUrl.toString());
+    if (res != 0) return res;
+    return source.compareTo(other.source);
+  }
+}
diff --git a/pkgs/source_maps/lib/parser.dart b/pkgs/source_maps/lib/parser.dart
new file mode 100644
index 0000000..b699ac7
--- /dev/null
+++ b/pkgs/source_maps/lib/parser.dart
@@ -0,0 +1,718 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Contains the top-level function to parse source maps version 3.
+library source_maps.parser;
+
+import 'dart:convert';
+
+import 'package:source_span/source_span.dart';
+
+import 'builder.dart' as builder;
+import 'src/source_map_span.dart';
+import 'src/utils.dart';
+import 'src/vlq.dart';
+
+/// Parses a source map directly from a json string.
+///
+/// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of
+/// the source map file itself. If it's passed, any URLs in the source
+/// map will be interpreted as relative to this URL when generating spans.
+// TODO(sigmund): evaluate whether other maps should have the json parsed, or
+// the string represenation.
+// TODO(tjblasi): Ignore the first line of [jsonMap] if the JSON safety string
+// `)]}'` begins the string representation of the map.
+Mapping parse(String jsonMap,
+        {Map<String, Map>? otherMaps, /*String|Uri*/ Object? mapUrl}) =>
+    parseJson(jsonDecode(jsonMap) as Map, otherMaps: otherMaps, mapUrl: mapUrl);
+
+/// Parses a source map or source map bundle directly from a json string.
+///
+/// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of
+/// the source map file itself. If it's passed, any URLs in the source
+/// map will be interpreted as relative to this URL when generating spans.
+Mapping parseExtended(String jsonMap,
+        {Map<String, Map>? otherMaps, /*String|Uri*/ Object? mapUrl}) =>
+    parseJsonExtended(jsonDecode(jsonMap),
+        otherMaps: otherMaps, mapUrl: mapUrl);
+
+/// Parses a source map or source map bundle.
+///
+/// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of
+/// the source map file itself. If it's passed, any URLs in the source
+/// map will be interpreted as relative to this URL when generating spans.
+Mapping parseJsonExtended(/*List|Map*/ Object? json,
+    {Map<String, Map>? otherMaps, /*String|Uri*/ Object? mapUrl}) {
+  if (json is List) {
+    return MappingBundle.fromJson(json, mapUrl: mapUrl);
+  }
+  return parseJson(json as Map);
+}
+
+/// Parses a source map.
+///
+/// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of
+/// the source map file itself. If it's passed, any URLs in the source
+/// map will be interpreted as relative to this URL when generating spans.
+Mapping parseJson(Map map,
+    {Map<String, Map>? otherMaps, /*String|Uri*/ Object? mapUrl}) {
+  if (map['version'] != 3) {
+    throw ArgumentError('unexpected source map version: ${map["version"]}. '
+        'Only version 3 is supported.');
+  }
+
+  if (map.containsKey('sections')) {
+    if (map.containsKey('mappings') ||
+        map.containsKey('sources') ||
+        map.containsKey('names')) {
+      throw const FormatException('map containing "sections" '
+          'cannot contain "mappings", "sources", or "names".');
+    }
+    return MultiSectionMapping.fromJson(map['sections'] as List, otherMaps,
+        mapUrl: mapUrl);
+  }
+  return SingleMapping.fromJson(map.cast<String, dynamic>(), mapUrl: mapUrl);
+}
+
+/// A mapping parsed out of a source map.
+abstract class Mapping {
+  /// Returns the span associated with [line] and [column].
+  ///
+  /// [uri] is the optional location of the output file to find the span for
+  /// to disambiguate cases where a mapping may have different mappings for
+  /// different output files.
+  SourceMapSpan? spanFor(int line, int column,
+      {Map<String, SourceFile>? files, String? uri});
+
+  /// Returns the span associated with [location].
+  SourceMapSpan? spanForLocation(SourceLocation location,
+      {Map<String, SourceFile>? files}) {
+    return spanFor(location.line, location.column,
+        uri: location.sourceUrl?.toString(), files: files);
+  }
+}
+
+/// A meta-level map containing sections.
+class MultiSectionMapping extends Mapping {
+  /// For each section, the start line offset.
+  final List<int> _lineStart = <int>[];
+
+  /// For each section, the start column offset.
+  final List<int> _columnStart = <int>[];
+
+  /// For each section, the actual source map information, which is not adjusted
+  /// for offsets.
+  final List<Mapping> _maps = <Mapping>[];
+
+  /// Creates a section mapping from json.
+  MultiSectionMapping.fromJson(List sections, Map<String, Map>? otherMaps,
+      {/*String|Uri*/ Object? mapUrl}) {
+    for (var section in sections.cast<Map>()) {
+      var offset = section['offset'] as Map?;
+      if (offset == null) throw const FormatException('section missing offset');
+
+      var line = offset['line'] as int?;
+      if (line == null) throw const FormatException('offset missing line');
+
+      var column = offset['column'] as int?;
+      if (column == null) throw const FormatException('offset missing column');
+
+      _lineStart.add(line);
+      _columnStart.add(column);
+
+      var url = section['url'] as String?;
+      var map = section['map'] as Map?;
+
+      if (url != null && map != null) {
+        throw const FormatException(
+            "section can't use both url and map entries");
+      } else if (url != null) {
+        var other = otherMaps?[url];
+        if (otherMaps == null || other == null) {
+          throw FormatException(
+              'section contains refers to $url, but no map was '
+              'given for it. Make sure a map is passed in "otherMaps"');
+        }
+        _maps.add(parseJson(other, otherMaps: otherMaps, mapUrl: url));
+      } else if (map != null) {
+        _maps.add(parseJson(map, otherMaps: otherMaps, mapUrl: mapUrl));
+      } else {
+        throw const FormatException('section missing url or map');
+      }
+    }
+    if (_lineStart.isEmpty) {
+      throw const FormatException('expected at least one section');
+    }
+  }
+
+  int _indexFor(int line, int column) {
+    for (var i = 0; i < _lineStart.length; i++) {
+      if (line < _lineStart[i]) return i - 1;
+      if (line == _lineStart[i] && column < _columnStart[i]) return i - 1;
+    }
+    return _lineStart.length - 1;
+  }
+
+  @override
+  SourceMapSpan? spanFor(int line, int column,
+      {Map<String, SourceFile>? files, String? uri}) {
+    // TODO(jacobr): perhaps verify that targetUrl matches the actual uri
+    // or at least ends in the same file name.
+    var index = _indexFor(line, column);
+    return _maps[index].spanFor(
+        line - _lineStart[index], column - _columnStart[index],
+        files: files);
+  }
+
+  @override
+  String toString() {
+    var buff = StringBuffer('$runtimeType : [');
+    for (var i = 0; i < _lineStart.length; i++) {
+      buff
+        ..write('(')
+        ..write(_lineStart[i])
+        ..write(',')
+        ..write(_columnStart[i])
+        ..write(':')
+        ..write(_maps[i])
+        ..write(')');
+    }
+    buff.write(']');
+    return buff.toString();
+  }
+}
+
+class MappingBundle extends Mapping {
+  final Map<String, SingleMapping> _mappings = {};
+
+  MappingBundle();
+
+  MappingBundle.fromJson(List json, {/*String|Uri*/ Object? mapUrl}) {
+    for (var map in json) {
+      addMapping(parseJson(map as Map, mapUrl: mapUrl) as SingleMapping);
+    }
+  }
+
+  void addMapping(SingleMapping mapping) {
+    // TODO(jacobr): verify that targetUrl is valid uri instead of a windows
+    // path.
+    // TODO: Remove type arg https://github.com/dart-lang/sdk/issues/42227
+    var targetUrl = ArgumentError.checkNotNull<String>(
+        mapping.targetUrl, 'mapping.targetUrl');
+    _mappings[targetUrl] = mapping;
+  }
+
+  /// Encodes the Mapping mappings as a json map.
+  List toJson() => _mappings.values.map((v) => v.toJson()).toList();
+
+  @override
+  String toString() {
+    var buff = StringBuffer();
+    for (var map in _mappings.values) {
+      buff.write(map.toString());
+    }
+    return buff.toString();
+  }
+
+  bool containsMapping(String url) => _mappings.containsKey(url);
+
+  @override
+  SourceMapSpan? spanFor(int line, int column,
+      {Map<String, SourceFile>? files, String? uri}) {
+    // TODO: Remove type arg https://github.com/dart-lang/sdk/issues/42227
+    uri = ArgumentError.checkNotNull<String>(uri, 'uri');
+
+    // Find the longest suffix of the uri that matches the sourcemap
+    // where the suffix starts after a path segment boundary.
+    // We consider ":" and "/" as path segment boundaries so that
+    // "package:" uris can be handled with minimal special casing. Having a
+    // few false positive path segment boundaries is not a significant issue
+    // as we prefer the longest matching prefix.
+    // Using package:path `path.split` to find path segment boundaries would
+    // not generate all of the path segment boundaries we want for "package:"
+    // urls as "package:package_name" would be one path segment when we want
+    // "package" and "package_name" to be sepearate path segments.
+
+    var onBoundary = true;
+    var separatorCodeUnits = ['/'.codeUnitAt(0), ':'.codeUnitAt(0)];
+    for (var i = 0; i < uri.length; ++i) {
+      if (onBoundary) {
+        var candidate = uri.substring(i);
+        var candidateMapping = _mappings[candidate];
+        if (candidateMapping != null) {
+          return candidateMapping.spanFor(line, column,
+              files: files, uri: candidate);
+        }
+      }
+      onBoundary = separatorCodeUnits.contains(uri.codeUnitAt(i));
+    }
+
+    // Note: when there is no source map for an uri, this behaves like an
+    // identity function, returning the requested location as the result.
+
+    // Create a mock offset for the output location. We compute it in terms
+    // of the input line and column to minimize the chances that two different
+    // line and column locations are mapped to the same offset.
+    var offset = line * 1000000 + column;
+    var location = SourceLocation(offset,
+        line: line, column: column, sourceUrl: Uri.parse(uri));
+    return SourceMapSpan(location, location, '');
+  }
+}
+
+/// A map containing direct source mappings.
+class SingleMapping extends Mapping {
+  /// Source urls used in the mapping, indexed by id.
+  final List<String> urls;
+
+  /// Source names used in the mapping, indexed by id.
+  final List<String> names;
+
+  /// The [SourceFile]s to which the entries in [lines] refer.
+  ///
+  /// This is in the same order as [urls]. If this was constructed using
+  /// [SingleMapping.fromEntries], this contains files from any [FileLocation]s
+  /// used to build the mapping. If it was parsed from JSON, it contains files
+  /// for any sources whose contents were provided via the `"sourcesContent"`
+  /// field.
+  ///
+  /// Files whose contents aren't available are `null`.
+  final List<SourceFile?> files;
+
+  /// Entries indicating the beginning of each span.
+  final List<TargetLineEntry> lines;
+
+  /// Url of the target file.
+  String? targetUrl;
+
+  /// Source root prepended to all entries in [urls].
+  String? sourceRoot;
+
+  final Uri? _mapUrl;
+
+  final Map<String, dynamic> extensions;
+
+  SingleMapping._(this.targetUrl, this.files, this.urls, this.names, this.lines)
+      : _mapUrl = null,
+        extensions = {};
+
+  factory SingleMapping.fromEntries(Iterable<builder.Entry> entries,
+      [String? fileUrl]) {
+    // The entries needs to be sorted by the target offsets.
+    var sourceEntries = entries.toList()..sort();
+    var lines = <TargetLineEntry>[];
+
+    // Indices associated with file urls that will be part of the source map. We
+    // rely on map order so that `urls.keys[urls[u]] == u`
+    var urls = <String, int>{};
+
+    // Indices associated with identifiers that will be part of the source map.
+    // We rely on map order so that `names.keys[names[n]] == n`
+    var names = <String, int>{};
+
+    /// The file for each URL, indexed by [urls]' values.
+    var files = <int, SourceFile>{};
+
+    int? lineNum;
+    late List<TargetEntry> targetEntries;
+    for (var sourceEntry in sourceEntries) {
+      if (lineNum == null || sourceEntry.target.line > lineNum) {
+        lineNum = sourceEntry.target.line;
+        targetEntries = <TargetEntry>[];
+        lines.add(TargetLineEntry(lineNum, targetEntries));
+      }
+
+      var sourceUrl = sourceEntry.source.sourceUrl;
+      var urlId = urls.putIfAbsent(
+          sourceUrl == null ? '' : sourceUrl.toString(), () => urls.length);
+
+      if (sourceEntry.source is FileLocation) {
+        files.putIfAbsent(
+            urlId, () => (sourceEntry.source as FileLocation).file);
+      }
+
+      var sourceEntryIdentifierName = sourceEntry.identifierName;
+      var srcNameId = sourceEntryIdentifierName == null
+          ? null
+          : names.putIfAbsent(sourceEntryIdentifierName, () => names.length);
+      targetEntries.add(TargetEntry(sourceEntry.target.column, urlId,
+          sourceEntry.source.line, sourceEntry.source.column, srcNameId));
+    }
+    return SingleMapping._(fileUrl, urls.values.map((i) => files[i]).toList(),
+        urls.keys.toList(), names.keys.toList(), lines);
+  }
+
+  SingleMapping.fromJson(Map<String, dynamic> map, {Object? mapUrl})
+      : targetUrl = map['file'] as String?,
+        urls = List<String>.from(map['sources'] as List),
+        names = List<String>.from((map['names'] as List?) ?? []),
+        files = List.filled((map['sources'] as List).length, null),
+        sourceRoot = map['sourceRoot'] as String?,
+        lines = <TargetLineEntry>[],
+        _mapUrl = mapUrl is String ? Uri.parse(mapUrl) : (mapUrl as Uri?),
+        extensions = {} {
+    var sourcesContent = map['sourcesContent'] == null
+        ? const <String?>[]
+        : List<String?>.from(map['sourcesContent'] as List);
+    for (var i = 0; i < urls.length && i < sourcesContent.length; i++) {
+      var source = sourcesContent[i];
+      if (source == null) continue;
+      files[i] = SourceFile.fromString(source, url: urls[i]);
+    }
+
+    var line = 0;
+    var column = 0;
+    var srcUrlId = 0;
+    var srcLine = 0;
+    var srcColumn = 0;
+    var srcNameId = 0;
+    var tokenizer = _MappingTokenizer(map['mappings'] as String);
+    var entries = <TargetEntry>[];
+
+    while (tokenizer.hasTokens) {
+      if (tokenizer.nextKind.isNewLine) {
+        if (entries.isNotEmpty) {
+          lines.add(TargetLineEntry(line, entries));
+          entries = <TargetEntry>[];
+        }
+        line++;
+        column = 0;
+        tokenizer._consumeNewLine();
+        continue;
+      }
+
+      // Decode the next entry, using the previous encountered values to
+      // decode the relative values.
+      //
+      // We expect 1, 4, or 5 values. If present, values are expected in the
+      // following order:
+      //   0: the starting column in the current line of the generated file
+      //   1: the id of the original source file
+      //   2: the starting line in the original source
+      //   3: the starting column in the original source
+      //   4: the id of the original symbol name
+      // The values are relative to the previous encountered values.
+      if (tokenizer.nextKind.isNewSegment) throw _segmentError(0, line);
+      column += tokenizer._consumeValue();
+      if (!tokenizer.nextKind.isValue) {
+        entries.add(TargetEntry(column));
+      } else {
+        srcUrlId += tokenizer._consumeValue();
+        if (srcUrlId >= urls.length) {
+          throw StateError(
+              'Invalid source url id. $targetUrl, $line, $srcUrlId');
+        }
+        if (!tokenizer.nextKind.isValue) throw _segmentError(2, line);
+        srcLine += tokenizer._consumeValue();
+        if (!tokenizer.nextKind.isValue) throw _segmentError(3, line);
+        srcColumn += tokenizer._consumeValue();
+        if (!tokenizer.nextKind.isValue) {
+          entries.add(TargetEntry(column, srcUrlId, srcLine, srcColumn));
+        } else {
+          srcNameId += tokenizer._consumeValue();
+          if (srcNameId >= names.length) {
+            throw StateError('Invalid name id: $targetUrl, $line, $srcNameId');
+          }
+          entries.add(
+              TargetEntry(column, srcUrlId, srcLine, srcColumn, srcNameId));
+        }
+      }
+      if (tokenizer.nextKind.isNewSegment) tokenizer._consumeNewSegment();
+    }
+    if (entries.isNotEmpty) {
+      lines.add(TargetLineEntry(line, entries));
+    }
+
+    map.forEach((name, value) {
+      if (name.startsWith('x_')) extensions[name] = value;
+    });
+  }
+
+  /// Encodes the Mapping mappings as a json map.
+  ///
+  /// If [includeSourceContents] is `true`, this includes the source file
+  /// contents from [files] in the map if possible.
+  Map<String, dynamic> toJson({bool includeSourceContents = false}) {
+    var buff = StringBuffer();
+    var line = 0;
+    var column = 0;
+    var srcLine = 0;
+    var srcColumn = 0;
+    var srcUrlId = 0;
+    var srcNameId = 0;
+    var first = true;
+
+    for (var entry in lines) {
+      var nextLine = entry.line;
+      if (nextLine > line) {
+        for (var i = line; i < nextLine; ++i) {
+          buff.write(';');
+        }
+        line = nextLine;
+        column = 0;
+        first = true;
+      }
+
+      for (var segment in entry.entries) {
+        if (!first) buff.write(',');
+        first = false;
+        column = _append(buff, column, segment.column);
+
+        // Encoding can be just the column offset if there is no source
+        // information.
+        var newUrlId = segment.sourceUrlId;
+        if (newUrlId == null) continue;
+        srcUrlId = _append(buff, srcUrlId, newUrlId);
+        srcLine = _append(buff, srcLine, segment.sourceLine!);
+        srcColumn = _append(buff, srcColumn, segment.sourceColumn!);
+
+        if (segment.sourceNameId == null) continue;
+        srcNameId = _append(buff, srcNameId, segment.sourceNameId!);
+      }
+    }
+
+    var result = <String, dynamic>{
+      'version': 3,
+      'sourceRoot': sourceRoot ?? '',
+      'sources': urls,
+      'names': names,
+      'mappings': buff.toString(),
+    };
+    if (targetUrl != null) result['file'] = targetUrl!;
+
+    if (includeSourceContents) {
+      result['sourcesContent'] = files.map((file) => file?.getText(0)).toList();
+    }
+    extensions.forEach((name, value) => result[name] = value);
+
+    return result;
+  }
+
+  /// Appends to [buff] a VLQ encoding of [newValue] using the difference
+  /// between [oldValue] and [newValue]
+  static int _append(StringBuffer buff, int oldValue, int newValue) {
+    buff.writeAll(encodeVlq(newValue - oldValue));
+    return newValue;
+  }
+
+  StateError _segmentError(int seen, int line) =>
+      StateError('Invalid entry in sourcemap, expected 1, 4, or 5'
+          ' values, but got $seen.\ntargeturl: $targetUrl, line: $line');
+
+  /// Returns [TargetLineEntry] which includes the location in the target [line]
+  /// number. In particular, the resulting entry is the last entry whose line
+  /// number is lower or equal to [line].
+  TargetLineEntry? _findLine(int line) {
+    var index = binarySearch(lines, (e) => e.line > line);
+    return (index <= 0) ? null : lines[index - 1];
+  }
+
+  /// Returns [TargetEntry] which includes the location denoted by
+  /// [line], [column]. If [lineEntry] corresponds to [line], then this will be
+  /// the last entry whose column is lower or equal than [column]. If
+  /// [lineEntry] corresponds to a line prior to [line], then the result will be
+  /// the very last entry on that line.
+  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;
+    var index = binarySearch(entries, (e) => e.column > column);
+    return (index <= 0) ? null : entries[index - 1];
+  }
+
+  @override
+  SourceMapSpan? spanFor(int line, int column,
+      {Map<String, SourceFile>? files, String? uri}) {
+    var entry = _findColumn(line, column, _findLine(line));
+    if (entry == null) return null;
+
+    var sourceUrlId = entry.sourceUrlId;
+    if (sourceUrlId == null) return null;
+
+    var url = urls[sourceUrlId];
+    if (sourceRoot != null) {
+      url = '$sourceRoot$url';
+    }
+
+    var sourceNameId = entry.sourceNameId;
+    var file = files?[url];
+    if (file != null) {
+      var start = file.getOffset(entry.sourceLine!, entry.sourceColumn);
+      if (sourceNameId != null) {
+        var text = names[sourceNameId];
+        return SourceMapFileSpan(file.span(start, start + text.length),
+            isIdentifier: true);
+      } else {
+        return SourceMapFileSpan(file.location(start).pointSpan());
+      }
+    } else {
+      var start = SourceLocation(0,
+          sourceUrl: _mapUrl?.resolve(url) ?? url,
+          line: entry.sourceLine,
+          column: entry.sourceColumn);
+
+      // Offset and other context is not available.
+      if (sourceNameId != null) {
+        return SourceMapSpan.identifier(start, names[sourceNameId]);
+      } else {
+        return SourceMapSpan(start, start, '');
+      }
+    }
+  }
+
+  @override
+  String toString() {
+    return (StringBuffer('$runtimeType : [')
+          ..write('targetUrl: ')
+          ..write(targetUrl)
+          ..write(', sourceRoot: ')
+          ..write(sourceRoot)
+          ..write(', urls: ')
+          ..write(urls)
+          ..write(', names: ')
+          ..write(names)
+          ..write(', lines: ')
+          ..write(lines)
+          ..write(']'))
+        .toString();
+  }
+
+  String get debugString {
+    var buff = StringBuffer();
+    for (var lineEntry in lines) {
+      var line = lineEntry.line;
+      for (var entry in lineEntry.entries) {
+        buff
+          ..write(targetUrl)
+          ..write(': ')
+          ..write(line)
+          ..write(':')
+          ..write(entry.column);
+        var sourceUrlId = entry.sourceUrlId;
+        if (sourceUrlId != null) {
+          buff
+            ..write('   -->   ')
+            ..write(sourceRoot)
+            ..write(urls[sourceUrlId])
+            ..write(': ')
+            ..write(entry.sourceLine)
+            ..write(':')
+            ..write(entry.sourceColumn);
+        }
+        var sourceNameId = entry.sourceNameId;
+        if (sourceNameId != null) {
+          buff
+            ..write(' (')
+            ..write(names[sourceNameId])
+            ..write(')');
+        }
+        buff.write('\n');
+      }
+    }
+    return buff.toString();
+  }
+}
+
+/// A line entry read from a source map.
+class TargetLineEntry {
+  final int line;
+  List<TargetEntry> entries;
+  TargetLineEntry(this.line, this.entries);
+
+  @override
+  String toString() => '$runtimeType: $line $entries';
+}
+
+/// A target segment entry read from a source map
+class TargetEntry {
+  final int column;
+  final int? sourceUrlId;
+  final int? sourceLine;
+  final int? sourceColumn;
+  final int? sourceNameId;
+
+  TargetEntry(this.column,
+      [this.sourceUrlId,
+      this.sourceLine,
+      this.sourceColumn,
+      this.sourceNameId]);
+
+  @override
+  String toString() => '$runtimeType: '
+      '($column, $sourceUrlId, $sourceLine, $sourceColumn, $sourceNameId)';
+}
+
+/// A character iterator over a string that can peek one character ahead.
+class _MappingTokenizer implements Iterator<String> {
+  final String _internal;
+  final int _length;
+  int index = -1;
+  _MappingTokenizer(String internal)
+      : _internal = internal,
+        _length = internal.length;
+
+  // Iterator API is used by decodeVlq to consume VLQ entries.
+  @override
+  bool moveNext() => ++index < _length;
+
+  @override
+  String get current => (index >= 0 && index < _length)
+      ? _internal[index]
+      : throw RangeError.index(index, _internal);
+
+  bool get hasTokens => index < _length - 1 && _length > 0;
+
+  _TokenKind get nextKind {
+    if (!hasTokens) return _TokenKind.eof;
+    var next = _internal[index + 1];
+    if (next == ';') return _TokenKind.line;
+    if (next == ',') return _TokenKind.segment;
+    return _TokenKind.value;
+  }
+
+  int _consumeValue() => decodeVlq(this);
+  void _consumeNewLine() {
+    ++index;
+  }
+
+  void _consumeNewSegment() {
+    ++index;
+  }
+
+  // Print the state of the iterator, with colors indicating the current
+  // position.
+  @override
+  String toString() {
+    var buff = StringBuffer();
+    for (var i = 0; i < index; i++) {
+      buff.write(_internal[i]);
+    }
+    buff.write('');
+    try {
+      buff.write(current);
+      // TODO: Determine whether this try / catch can be removed.
+      // ignore: avoid_catching_errors
+    } on RangeError catch (_) {}
+    buff.write('');
+    for (var i = index + 1; i < _internal.length; i++) {
+      buff.write(_internal[i]);
+    }
+    buff.write(' ($index)');
+    return buff.toString();
+  }
+}
+
+class _TokenKind {
+  static const _TokenKind line = _TokenKind(isNewLine: true);
+  static const _TokenKind segment = _TokenKind(isNewSegment: true);
+  static const _TokenKind eof = _TokenKind(isEof: true);
+  static const _TokenKind value = _TokenKind();
+  final bool isNewLine;
+  final bool isNewSegment;
+  final bool isEof;
+  bool get isValue => !isNewLine && !isNewSegment && !isEof;
+
+  const _TokenKind(
+      {this.isNewLine = false, this.isNewSegment = false, this.isEof = false});
+}
diff --git a/pkgs/source_maps/lib/printer.dart b/pkgs/source_maps/lib/printer.dart
new file mode 100644
index 0000000..17733cd
--- /dev/null
+++ b/pkgs/source_maps/lib/printer.dart
@@ -0,0 +1,262 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Contains a code printer that generates code by recording the source maps.
+library source_maps.printer;
+
+import 'package:source_span/source_span.dart';
+
+import 'builder.dart';
+import 'src/source_map_span.dart';
+import 'src/utils.dart';
+
+/// A simple printer that keeps track of offset locations and records source
+/// maps locations.
+class Printer {
+  final String filename;
+  final StringBuffer _buff = StringBuffer();
+  final SourceMapBuilder _maps = SourceMapBuilder();
+  String get text => _buff.toString();
+  String get map => _maps.toJson(filename);
+
+  /// Current source location mapping.
+  SourceLocation? _loc;
+
+  /// Current line in the buffer;
+  int _line = 0;
+
+  /// Current column in the buffer.
+  int _column = 0;
+
+  Printer(this.filename);
+
+  /// Add [str] contents to the output, tracking new lines to track correct
+  /// positions for span locations. When [projectMarks] is true, this method
+  /// adds a source map location on each new line, projecting that every new
+  /// line in the target file (printed here) corresponds to a new line in the
+  /// source file.
+  void add(String str, {bool projectMarks = false}) {
+    var chars = str.runes.toList();
+    var length = chars.length;
+    for (var i = 0; i < length; i++) {
+      var c = chars[i];
+      if (c == lineFeed ||
+          (c == carriageReturn &&
+              (i + 1 == length || chars[i + 1] != lineFeed))) {
+        // Return not followed by line-feed is treated as a new line.
+        _line++;
+        _column = 0;
+        {
+          // **Warning**: Any calls to `mark` will change the value of `_loc`,
+          // so this local variable is no longer up to date after that point.
+          //
+          // This is why it has been put inside its own block to limit the
+          // scope in which it is available.
+          var loc = _loc;
+          if (projectMarks && loc != null) {
+            if (loc is FileLocation) {
+              var file = loc.file;
+              mark(file.location(file.getOffset(loc.line + 1)));
+            } else {
+              mark(SourceLocation(0,
+                  sourceUrl: loc.sourceUrl, line: loc.line + 1, column: 0));
+            }
+          }
+        }
+      } else {
+        _column++;
+      }
+    }
+    _buff.write(str);
+  }
+
+  /// Append a [total] number of spaces in the target file. Typically used for
+  /// formatting indentation.
+  void addSpaces(int total) {
+    for (var i = 0; i < total; i++) {
+      _buff.write(' ');
+    }
+    _column += total;
+  }
+
+  /// Marks that the current point in the target file corresponds to the [mark]
+  /// in the source file, which can be either a [SourceLocation] or a
+  /// [SourceSpan]. When the mark is a [SourceMapSpan] with `isIdentifier` set,
+  /// this also records the name of the identifier in the source map
+  /// information.
+  void mark(Object mark) {
+    late final SourceLocation loc;
+    String? identifier;
+    if (mark is SourceLocation) {
+      loc = mark;
+    } else if (mark is SourceSpan) {
+      loc = mark.start;
+      if (mark is SourceMapSpan && mark.isIdentifier) identifier = mark.text;
+    }
+    _maps.addLocation(loc,
+        SourceLocation(_buff.length, line: _line, column: _column), identifier);
+    _loc = loc;
+  }
+}
+
+/// A more advanced printer that keeps track of offset locations to record
+/// source maps, but additionally allows nesting of different kind of items,
+/// including [NestedPrinter]s, and it let's you automatically indent text.
+///
+/// This class is especially useful when doing code generation, where different
+/// pieces of the code are generated independently on separate printers, and are
+/// finally put together in the end.
+class NestedPrinter implements NestedItem {
+  /// Items recoded by this printer, which can be [String] literals,
+  /// [NestedItem]s, and source map information like [SourceLocation] and
+  /// [SourceSpan].
+  final List<Object> _items = [];
+
+  /// Internal buffer to merge consecutive strings added to this printer.
+  StringBuffer? _buff;
+
+  /// Current indentation, which can be updated from outside this class.
+  int indent;
+
+  /// [Printer] used during the last call to [build], if any.
+  Printer? printer;
+
+  /// Returns the text produced after calling [build].
+  String? get text => printer?.text;
+
+  /// Returns the source-map information produced after calling [build].
+  String? get map => printer?.map;
+
+  /// Item used to indicate that the following item is copied from the original
+  /// source code, and hence we should preserve source-maps on every new line.
+  static final _original = Object();
+
+  NestedPrinter([this.indent = 0]);
+
+  /// Adds [object] to this printer. [object] can be a [String],
+  /// [NestedPrinter], or anything implementing [NestedItem]. If [object] is a
+  /// [String], the value is appended directly, without doing any formatting
+  /// changes. If you wish to add a line of code with automatic indentation, use
+  /// [addLine] instead.  [NestedPrinter]s and [NestedItem]s are not processed
+  /// until [build] gets called later on. We ensure that [build] emits every
+  /// object in the order that they were added to this printer.
+  ///
+  /// The [location] and [span] parameters indicate the corresponding source map
+  /// location of [object] in the original input. Only one, [location] or
+  /// [span], should be provided at a time.
+  ///
+  /// Indicate [isOriginal] when [object] is copied directly from the user code.
+  /// Setting [isOriginal] will make this printer propagate source map locations
+  /// on every line-break.
+  void add(Object object,
+      {SourceLocation? location, SourceSpan? span, bool isOriginal = false}) {
+    if (object is! String || location != null || span != null || isOriginal) {
+      _flush();
+      assert(location == null || span == null);
+      if (location != null) _items.add(location);
+      if (span != null) _items.add(span);
+      if (isOriginal) _items.add(_original);
+    }
+
+    if (object is String) {
+      _appendString(object);
+    } else {
+      _items.add(object);
+    }
+  }
+
+  /// Append `2 * indent` spaces to this printer.
+  void insertIndent() => _indent(indent);
+
+  /// Add a [line], autoindenting to the current value of [indent]. Note,
+  /// indentation is not inferred from the contents added to this printer. If a
+  /// line starts or ends an indentation block, you need to also update [indent]
+  /// accordingly. Also, indentation is not adapted for nested printers. If
+  /// you add a [NestedPrinter] to this printer, its indentation is set
+  /// separately and will not include any the indentation set here.
+  ///
+  /// The [location] and [span] parameters indicate the corresponding source map
+  /// location of [line] in the original input. Only one, [location] or
+  /// [span], should be provided at a time.
+  void addLine(String? line, {SourceLocation? location, SourceSpan? span}) {
+    if (location != null || span != null) {
+      _flush();
+      assert(location == null || span == null);
+      if (location != null) _items.add(location);
+      if (span != null) _items.add(span);
+    }
+    if (line == null) return;
+    if (line != '') {
+      // We don't indent empty lines.
+      _indent(indent);
+      _appendString(line);
+    }
+    _appendString('\n');
+  }
+
+  /// Appends a string merging it with any previous strings, if possible.
+  void _appendString(String s) {
+    var buf = _buff ??= StringBuffer();
+    buf.write(s);
+  }
+
+  /// Adds all of the current [_buff] contents as a string item.
+  void _flush() {
+    if (_buff != null) {
+      _items.add(_buff.toString());
+      _buff = null;
+    }
+  }
+
+  void _indent(int indent) {
+    for (var i = 0; i < indent; i++) {
+      _appendString('  ');
+    }
+  }
+
+  /// Returns a string representation of all the contents appended to this
+  /// printer, including source map location tokens.
+  @override
+  String toString() {
+    _flush();
+    return (StringBuffer()..writeAll(_items)).toString();
+  }
+
+  /// Builds the output of this printer and source map information. After
+  /// calling this function, you can use [text] and [map] to retrieve the
+  /// geenrated code and source map information, respectively.
+  void build(String filename) {
+    writeTo(printer = Printer(filename));
+  }
+
+  /// Implements the [NestedItem] interface.
+  @override
+  void writeTo(Printer printer) {
+    _flush();
+    var propagate = false;
+    for (var item in _items) {
+      if (item is NestedItem) {
+        item.writeTo(printer);
+      } else if (item is String) {
+        printer.add(item, projectMarks: propagate);
+        propagate = false;
+      } else if (item is SourceLocation || item is SourceSpan) {
+        printer.mark(item);
+      } else if (item == _original) {
+        // we insert booleans when we are about to quote text that was copied
+        // from the original source. In such case, we will propagate marks on
+        // every new-line.
+        propagate = true;
+      } else {
+        throw UnsupportedError('Unknown item type: $item');
+      }
+    }
+  }
+}
+
+/// An item added to a [NestedPrinter].
+abstract class NestedItem {
+  /// Write the contents of this item into [printer].
+  void writeTo(Printer printer);
+}
diff --git a/pkgs/source_maps/lib/refactor.dart b/pkgs/source_maps/lib/refactor.dart
new file mode 100644
index 0000000..98e0c93
--- /dev/null
+++ b/pkgs/source_maps/lib/refactor.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Tools to help implement refactoring like transformations to Dart code.
+///
+/// [TextEditTransaction] supports making a series of changes to a text buffer.
+/// [guessIndent] helps to guess the appropriate indentiation for the new code.
+library source_maps.refactor;
+
+import 'package:source_span/source_span.dart';
+
+import 'printer.dart';
+import 'src/utils.dart';
+
+/// Editable text transaction.
+///
+/// Applies a series of edits using original location
+/// information, and composes them into the edited string.
+class TextEditTransaction {
+  final SourceFile? file;
+  final String original;
+  final _edits = <_TextEdit>[];
+
+  /// Creates a new transaction.
+  TextEditTransaction(this.original, this.file);
+
+  bool get hasEdits => _edits.isNotEmpty;
+
+  /// Edit the original text, replacing text on the range [begin] and [end]
+  /// with the [replacement]. [replacement] can be either a string or a
+  /// [NestedPrinter].
+  void edit(int begin, int end, Object replacement) {
+    _edits.add(_TextEdit(begin, end, replacement));
+  }
+
+  /// Create a source map [SourceLocation] for [offset], if [file] is not
+  /// `null`.
+  SourceLocation? _loc(int offset) => file?.location(offset);
+
+  /// Applies all pending [edit]s and returns a [NestedPrinter] containing the
+  /// rewritten string and source map information. [file]`.location` is given to
+  /// the underlying printer to indicate the name of the generated file that
+  /// will contains the source map information.
+  ///
+  /// Throws [UnsupportedError] if the edits were overlapping. If no edits were
+  /// made, the printer simply contains the original string.
+  NestedPrinter commit() {
+    var printer = NestedPrinter();
+    if (_edits.isEmpty) {
+      return printer..add(original, location: _loc(0), isOriginal: true);
+    }
+
+    // Sort edits by start location.
+    _edits.sort();
+
+    var consumed = 0;
+    for (var edit in _edits) {
+      if (consumed > edit.begin) {
+        var sb = StringBuffer();
+        sb
+          ..write(file?.location(edit.begin).toolString)
+          ..write(': overlapping edits. Insert at offset ')
+          ..write(edit.begin)
+          ..write(' but have consumed ')
+          ..write(consumed)
+          ..write(' input characters. List of edits:');
+        for (var e in _edits) {
+          sb
+            ..write('\n    ')
+            ..write(e);
+        }
+        throw UnsupportedError(sb.toString());
+      }
+
+      // Add characters from the original string between this edit and the last
+      // one, if any.
+      var betweenEdits = original.substring(consumed, edit.begin);
+      printer
+        ..add(betweenEdits, location: _loc(consumed), isOriginal: true)
+        ..add(edit.replace, location: _loc(edit.begin));
+      consumed = edit.end;
+    }
+
+    // Add any text from the end of the original string that was not replaced.
+    printer.add(original.substring(consumed),
+        location: _loc(consumed), isOriginal: true);
+    return printer;
+  }
+}
+
+class _TextEdit implements Comparable<_TextEdit> {
+  final int begin;
+  final int end;
+
+  /// The replacement used by the edit, can be a string or a [NestedPrinter].
+  final Object replace;
+
+  _TextEdit(this.begin, this.end, this.replace);
+
+  int get length => end - begin;
+
+  @override
+  String toString() => '(Edit @ $begin,$end: "$replace")';
+
+  @override
+  int compareTo(_TextEdit other) {
+    var diff = begin - other.begin;
+    if (diff != 0) return diff;
+    return end - other.end;
+  }
+}
+
+/// Returns all whitespace characters at the start of [charOffset]'s line.
+String guessIndent(String code, int charOffset) {
+  // Find the beginning of the line
+  var lineStart = 0;
+  for (var i = charOffset - 1; i >= 0; i--) {
+    var c = code.codeUnitAt(i);
+    if (c == lineFeed || c == carriageReturn) {
+      lineStart = i + 1;
+      break;
+    }
+  }
+
+  // Grab all the whitespace
+  var whitespaceEnd = code.length;
+  for (var i = lineStart; i < code.length; i++) {
+    var c = code.codeUnitAt(i);
+    if (c != _space && c != _tab) {
+      whitespaceEnd = i;
+      break;
+    }
+  }
+
+  return code.substring(lineStart, whitespaceEnd);
+}
+
+const int _tab = 9;
+const int _space = 32;
diff --git a/pkgs/source_maps/lib/source_maps.dart b/pkgs/source_maps/lib/source_maps.dart
new file mode 100644
index 0000000..58f805a
--- /dev/null
+++ b/pkgs/source_maps/lib/source_maps.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Library to create and parse source maps.
+///
+/// Create a source map using [SourceMapBuilder]. For example:
+///
+/// ```dart
+/// var json = (new SourceMapBuilder()
+///     ..add(inputSpan1, outputSpan1)
+///     ..add(inputSpan2, outputSpan2)
+///     ..add(inputSpan3, outputSpan3)
+///     .toJson(outputFile);
+/// ```
+///
+/// Use the source_span package's [SourceSpan] and [SourceFile] classes to
+/// specify span locations.
+///
+/// Parse a source map using [parse], and call `spanFor` on the returned mapping
+/// object. For example:
+///
+/// ```dart
+/// var mapping = parse(json);
+/// mapping.spanFor(outputSpan1.line, outputSpan1.column)
+/// ```
+library source_maps;
+
+import 'package:source_span/source_span.dart';
+
+import 'builder.dart';
+import 'parser.dart';
+
+export 'builder.dart';
+export 'parser.dart';
+export 'printer.dart';
+export 'refactor.dart';
+export 'src/source_map_span.dart';
diff --git a/pkgs/source_maps/lib/src/source_map_span.dart b/pkgs/source_maps/lib/src/source_map_span.dart
new file mode 100644
index 0000000..aad8a32
--- /dev/null
+++ b/pkgs/source_maps/lib/src/source_map_span.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// 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:source_span/source_span.dart';
+
+/// A [SourceSpan] for spans coming from or being written to source maps.
+///
+/// These spans have an extra piece of metadata: whether or not they represent
+/// an identifier (see [isIdentifier]).
+class SourceMapSpan extends SourceSpanBase {
+  /// Whether this span represents an identifier.
+  ///
+  /// If this is `true`, [text] is the value of the identifier.
+  final bool isIdentifier;
+
+  SourceMapSpan(super.start, super.end, super.text,
+      {this.isIdentifier = false});
+
+  /// Creates a [SourceMapSpan] for an identifier with value [text] starting at
+  /// [start].
+  ///
+  /// The [end] location is determined by adding [text] to [start].
+  SourceMapSpan.identifier(SourceLocation start, String text)
+      : this(
+            start,
+            SourceLocation(start.offset + text.length,
+                sourceUrl: start.sourceUrl,
+                line: start.line,
+                column: start.column + text.length),
+            text,
+            isIdentifier: true);
+}
+
+/// A wrapper aruond a [FileSpan] that implements [SourceMapSpan].
+class SourceMapFileSpan implements SourceMapSpan, FileSpan {
+  final FileSpan _inner;
+  @override
+  final bool isIdentifier;
+
+  @override
+  SourceFile get file => _inner.file;
+  @override
+  FileLocation get start => _inner.start;
+  @override
+  FileLocation get end => _inner.end;
+  @override
+  String get text => _inner.text;
+  @override
+  String get context => _inner.context;
+  @override
+  Uri? get sourceUrl => _inner.sourceUrl;
+  @override
+  int get length => _inner.length;
+
+  SourceMapFileSpan(this._inner, {this.isIdentifier = false});
+
+  @override
+  int compareTo(SourceSpan other) => _inner.compareTo(other);
+  @override
+  String highlight({Object? color}) => _inner.highlight(color: color);
+  @override
+  SourceSpan union(SourceSpan other) => _inner.union(other);
+  @override
+  FileSpan expand(FileSpan other) => _inner.expand(other);
+  @override
+  String message(String message, {Object? color}) =>
+      _inner.message(message, color: color);
+  @override
+  String toString() =>
+      _inner.toString().replaceAll('FileSpan', 'SourceMapFileSpan');
+}
diff --git a/pkgs/source_maps/lib/src/utils.dart b/pkgs/source_maps/lib/src/utils.dart
new file mode 100644
index 0000000..f70531e
--- /dev/null
+++ b/pkgs/source_maps/lib/src/utils.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Utilities that shouldn't be in this package.
+library source_maps.utils;
+
+/// Find the first entry in a sorted [list] that matches a monotonic predicate.
+/// Given a result `n`, that all items before `n` will not match, `n` matches,
+/// and all items after `n` match too. The result is -1 when there are no
+/// items, 0 when all items match, and list.length when none does.
+// TODO(sigmund): remove this function after dartbug.com/5624 is fixed.
+int binarySearch<T>(List<T> list, bool Function(T) matches) {
+  if (list.isEmpty) return -1;
+  if (matches(list.first)) return 0;
+  if (!matches(list.last)) return list.length;
+
+  var min = 0;
+  var max = list.length - 1;
+  while (min < max) {
+    var half = min + ((max - min) ~/ 2);
+    if (matches(list[half])) {
+      max = half;
+    } else {
+      min = half + 1;
+    }
+  }
+  return max;
+}
+
+const int lineFeed = 10;
+const int carriageReturn = 13;
diff --git a/pkgs/source_maps/lib/src/vlq.dart b/pkgs/source_maps/lib/src/vlq.dart
new file mode 100644
index 0000000..61b4768
--- /dev/null
+++ b/pkgs/source_maps/lib/src/vlq.dart
@@ -0,0 +1,101 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Utilities to encode and decode VLQ values used in source maps.
+///
+/// Sourcemaps are encoded with variable length numbers as base64 encoded
+/// strings with the least significant digit coming first. Each base64 digit
+/// encodes a 5-bit value (0-31) and a continuation bit. Signed values can be
+/// represented by using the least significant bit of the value as the sign bit.
+///
+/// For more details see the source map [version 3 documentation](https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?usp=sharing).
+library source_maps.src.vlq;
+
+import 'dart:math';
+
+const int vlqBaseShift = 5;
+
+const int vlqBaseMask = (1 << 5) - 1;
+
+const int vlqContinuationBit = 1 << 5;
+
+const int vlqContinuationMask = 1 << 5;
+
+const String base64Digits =
+    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+
+final Map<String, int> _digits = () {
+  var map = <String, int>{};
+  for (var i = 0; i < 64; i++) {
+    map[base64Digits[i]] = i;
+  }
+  return map;
+}();
+
+final int maxInt32 = (pow(2, 31) as int) - 1;
+final int minInt32 = -(pow(2, 31) as int);
+
+/// Creates the VLQ encoding of [value] as a sequence of characters
+Iterable<String> encodeVlq(int value) {
+  if (value < minInt32 || value > maxInt32) {
+    throw ArgumentError('expected 32 bit int, got: $value');
+  }
+  var res = <String>[];
+  var signBit = 0;
+  if (value < 0) {
+    signBit = 1;
+    value = -value;
+  }
+  value = (value << 1) | signBit;
+  do {
+    var digit = value & vlqBaseMask;
+    value >>= vlqBaseShift;
+    if (value > 0) {
+      digit |= vlqContinuationBit;
+    }
+    res.add(base64Digits[digit]);
+  } while (value > 0);
+  return res;
+}
+
+/// Decodes a value written as a sequence of VLQ characters. The first input
+/// character will be `chars.current` after calling `chars.moveNext` once. The
+/// iterator is advanced until a stop character is found (a character without
+/// the [vlqContinuationBit]).
+int decodeVlq(Iterator<String> chars) {
+  var result = 0;
+  var stop = false;
+  var shift = 0;
+  while (!stop) {
+    if (!chars.moveNext()) throw StateError('incomplete VLQ value');
+    var char = chars.current;
+    var digit = _digits[char];
+    if (digit == null) {
+      throw FormatException('invalid character in VLQ encoding: $char');
+    }
+    stop = (digit & vlqContinuationBit) == 0;
+    digit &= vlqBaseMask;
+    result += digit << shift;
+    shift += vlqBaseShift;
+  }
+
+  // Result uses the least significant bit as a sign bit. We convert it into a
+  // two-complement value. For example,
+  //   2 (10 binary) becomes 1
+  //   3 (11 binary) becomes -1
+  //   4 (100 binary) becomes 2
+  //   5 (101 binary) becomes -2
+  //   6 (110 binary) becomes 3
+  //   7 (111 binary) becomes -3
+  var negate = (result & 1) == 1;
+  result = result >> 1;
+  result = negate ? -result : result;
+
+  // TODO(sigmund): can we detect this earlier?
+  if (result < minInt32 || result > maxInt32) {
+    throw FormatException(
+        'expected an encoded 32 bit int, but we got: $result');
+  }
+  return result;
+}
diff --git a/pkgs/source_maps/pubspec.yaml b/pkgs/source_maps/pubspec.yaml
new file mode 100644
index 0000000..fcc9925
--- /dev/null
+++ b/pkgs/source_maps/pubspec.yaml
@@ -0,0 +1,15 @@
+name: source_maps
+version: 0.10.13-wip
+description: A library to programmatically manipulate source map files.
+repository: https://github.com/dart-lang/source_maps
+
+environment:
+  sdk: ^3.3.0
+
+dependencies:
+  source_span: ^1.8.0
+
+dev_dependencies:
+  dart_flutter_team_lints: ^2.0.0
+  term_glyph: ^1.2.0
+  test: ^1.16.0
diff --git a/pkgs/source_maps/test/builder_test.dart b/pkgs/source_maps/test/builder_test.dart
new file mode 100644
index 0000000..4f773e7
--- /dev/null
+++ b/pkgs/source_maps/test/builder_test.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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 'dart:convert';
+
+import 'package:source_maps/source_maps.dart';
+import 'package:test/test.dart';
+
+import 'common.dart';
+
+void main() {
+  test('builder - with span', () {
+    var map = (SourceMapBuilder()
+          ..addSpan(inputVar1, outputVar1)
+          ..addSpan(inputFunction, outputFunction)
+          ..addSpan(inputVar2, outputVar2)
+          ..addSpan(inputExpr, outputExpr))
+        .build(output.url.toString());
+    expect(map, equals(expectedMap));
+  });
+
+  test('builder - with location', () {
+    var str = (SourceMapBuilder()
+          ..addLocation(inputVar1.start, outputVar1.start, 'longVar1')
+          ..addLocation(inputFunction.start, outputFunction.start, 'longName')
+          ..addLocation(inputVar2.start, outputVar2.start, 'longVar2')
+          ..addLocation(inputExpr.start, outputExpr.start, null))
+        .toJson(output.url.toString());
+    expect(str, jsonEncode(expectedMap));
+  });
+}
diff --git a/pkgs/source_maps/test/common.dart b/pkgs/source_maps/test/common.dart
new file mode 100644
index 0000000..f6139de
--- /dev/null
+++ b/pkgs/source_maps/test/common.dart
@@ -0,0 +1,107 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Common input/output used by builder, parser and end2end tests
+library test.common;
+
+import 'package:source_maps/source_maps.dart';
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+
+/// Content of the source file
+const String inputContent = '''
+/** this is a comment. */
+int longVar1 = 3;
+
+// this is a comment too
+int longName(int longVar2) {
+  return longVar1 + longVar2;
+}
+''';
+final input = SourceFile.fromString(inputContent, url: 'input.dart');
+
+/// A span in the input file
+SourceMapSpan ispan(int start, int end, [bool isIdentifier = false]) =>
+    SourceMapFileSpan(input.span(start, end), isIdentifier: isIdentifier);
+
+SourceMapSpan inputVar1 = ispan(30, 38, true);
+SourceMapSpan inputFunction = ispan(74, 82, true);
+SourceMapSpan inputVar2 = ispan(87, 95, true);
+
+SourceMapSpan inputVar1NoSymbol = ispan(30, 38);
+SourceMapSpan inputFunctionNoSymbol = ispan(74, 82);
+SourceMapSpan inputVar2NoSymbol = ispan(87, 95);
+
+SourceMapSpan inputExpr = ispan(108, 127);
+
+/// Content of the target file
+const String outputContent = '''
+var x = 3;
+f(y) => x + y;
+''';
+final output = SourceFile.fromString(outputContent, url: 'output.dart');
+
+/// A span in the output file
+SourceMapSpan ospan(int start, int end, [bool isIdentifier = false]) =>
+    SourceMapFileSpan(output.span(start, end), isIdentifier: isIdentifier);
+
+SourceMapSpan outputVar1 = ospan(4, 5, true);
+SourceMapSpan outputFunction = ospan(11, 12, true);
+SourceMapSpan outputVar2 = ospan(13, 14, true);
+SourceMapSpan outputVar1NoSymbol = ospan(4, 5);
+SourceMapSpan outputFunctionNoSymbol = ospan(11, 12);
+SourceMapSpan outputVar2NoSymbol = ospan(13, 14);
+SourceMapSpan outputExpr = ospan(19, 24);
+
+/// Expected output mapping when recording the following four mappings:
+///      inputVar1       <=   outputVar1
+///      inputFunction   <=   outputFunction
+///      inputVar2       <=   outputVar2
+///      inputExpr       <=   outputExpr
+///
+/// This mapping is stored in the tests so we can independently test the builder
+/// and parser algorithms without relying entirely on end2end tests.
+const Map<String, dynamic> expectedMap = {
+  'version': 3,
+  'sourceRoot': '',
+  'sources': ['input.dart'],
+  'names': ['longVar1', 'longName', 'longVar2'],
+  'mappings': 'IACIA;AAGAC,EAAaC,MACR',
+  'file': 'output.dart'
+};
+
+void check(SourceSpan outputSpan, Mapping mapping, SourceMapSpan inputSpan,
+    bool realOffsets) {
+  var line = outputSpan.start.line;
+  var column = outputSpan.start.column;
+  var files = realOffsets ? {'input.dart': input} : null;
+  var span = mapping.spanFor(line, column, files: files)!;
+  var span2 = mapping.spanForLocation(outputSpan.start, files: files)!;
+
+  // Both mapping APIs are equivalent.
+  expect(span.start.offset, span2.start.offset);
+  expect(span.start.line, span2.start.line);
+  expect(span.start.column, span2.start.column);
+  expect(span.end.offset, span2.end.offset);
+  expect(span.end.line, span2.end.line);
+  expect(span.end.column, span2.end.column);
+
+  // Mapping matches our input location (modulo using real offsets)
+  expect(span.start.line, inputSpan.start.line);
+  expect(span.start.column, inputSpan.start.column);
+  expect(span.sourceUrl, inputSpan.sourceUrl);
+  expect(span.start.offset, realOffsets ? inputSpan.start.offset : 0);
+
+  // Mapping includes the identifier, if any
+  if (inputSpan.isIdentifier) {
+    expect(span.end.line, inputSpan.end.line);
+    expect(span.end.column, inputSpan.end.column);
+    expect(span.end.offset, span.start.offset + inputSpan.text.length);
+    if (realOffsets) expect(span.end.offset, inputSpan.end.offset);
+  } else {
+    expect(span.end.offset, span.start.offset);
+    expect(span.end.line, span.start.line);
+    expect(span.end.column, span.start.column);
+  }
+}
diff --git a/pkgs/source_maps/test/end2end_test.dart b/pkgs/source_maps/test/end2end_test.dart
new file mode 100644
index 0000000..84dd5ba
--- /dev/null
+++ b/pkgs/source_maps/test/end2end_test.dart
@@ -0,0 +1,160 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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:source_maps/source_maps.dart';
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+
+import 'common.dart';
+
+void main() {
+  test('end-to-end setup', () {
+    expect(inputVar1.text, 'longVar1');
+    expect(inputFunction.text, 'longName');
+    expect(inputVar2.text, 'longVar2');
+    expect(inputVar1NoSymbol.text, 'longVar1');
+    expect(inputFunctionNoSymbol.text, 'longName');
+    expect(inputVar2NoSymbol.text, 'longVar2');
+    expect(inputExpr.text, 'longVar1 + longVar2');
+
+    expect(outputVar1.text, 'x');
+    expect(outputFunction.text, 'f');
+    expect(outputVar2.text, 'y');
+    expect(outputVar1NoSymbol.text, 'x');
+    expect(outputFunctionNoSymbol.text, 'f');
+    expect(outputVar2NoSymbol.text, 'y');
+    expect(outputExpr.text, 'x + y');
+  });
+
+  test('build + parse', () {
+    var map = (SourceMapBuilder()
+          ..addSpan(inputVar1, outputVar1)
+          ..addSpan(inputFunction, outputFunction)
+          ..addSpan(inputVar2, outputVar2)
+          ..addSpan(inputExpr, outputExpr))
+        .build(output.url.toString());
+    var mapping = parseJson(map);
+    check(outputVar1, mapping, inputVar1, false);
+    check(outputVar2, mapping, inputVar2, false);
+    check(outputFunction, mapping, inputFunction, false);
+    check(outputExpr, mapping, inputExpr, false);
+  });
+
+  test('build + parse - no symbols', () {
+    var map = (SourceMapBuilder()
+          ..addSpan(inputVar1NoSymbol, outputVar1NoSymbol)
+          ..addSpan(inputFunctionNoSymbol, outputFunctionNoSymbol)
+          ..addSpan(inputVar2NoSymbol, outputVar2NoSymbol)
+          ..addSpan(inputExpr, outputExpr))
+        .build(output.url.toString());
+    var mapping = parseJson(map);
+    check(outputVar1NoSymbol, mapping, inputVar1NoSymbol, false);
+    check(outputVar2NoSymbol, mapping, inputVar2NoSymbol, false);
+    check(outputFunctionNoSymbol, mapping, inputFunctionNoSymbol, false);
+    check(outputExpr, mapping, inputExpr, false);
+  });
+
+  test('build + parse, repeated entries', () {
+    var map = (SourceMapBuilder()
+          ..addSpan(inputVar1, outputVar1)
+          ..addSpan(inputVar1, outputVar1)
+          ..addSpan(inputFunction, outputFunction)
+          ..addSpan(inputFunction, outputFunction)
+          ..addSpan(inputVar2, outputVar2)
+          ..addSpan(inputVar2, outputVar2)
+          ..addSpan(inputExpr, outputExpr)
+          ..addSpan(inputExpr, outputExpr))
+        .build(output.url.toString());
+    var mapping = parseJson(map);
+    check(outputVar1, mapping, inputVar1, false);
+    check(outputVar2, mapping, inputVar2, false);
+    check(outputFunction, mapping, inputFunction, false);
+    check(outputExpr, mapping, inputExpr, false);
+  });
+
+  test('build + parse - no symbols, repeated entries', () {
+    var map = (SourceMapBuilder()
+          ..addSpan(inputVar1NoSymbol, outputVar1NoSymbol)
+          ..addSpan(inputVar1NoSymbol, outputVar1NoSymbol)
+          ..addSpan(inputFunctionNoSymbol, outputFunctionNoSymbol)
+          ..addSpan(inputFunctionNoSymbol, outputFunctionNoSymbol)
+          ..addSpan(inputVar2NoSymbol, outputVar2NoSymbol)
+          ..addSpan(inputVar2NoSymbol, outputVar2NoSymbol)
+          ..addSpan(inputExpr, outputExpr))
+        .build(output.url.toString());
+    var mapping = parseJson(map);
+    check(outputVar1NoSymbol, mapping, inputVar1NoSymbol, false);
+    check(outputVar2NoSymbol, mapping, inputVar2NoSymbol, false);
+    check(outputFunctionNoSymbol, mapping, inputFunctionNoSymbol, false);
+    check(outputExpr, mapping, inputExpr, false);
+  });
+
+  test('build + parse with file', () {
+    var json = (SourceMapBuilder()
+          ..addSpan(inputVar1, outputVar1)
+          ..addSpan(inputFunction, outputFunction)
+          ..addSpan(inputVar2, outputVar2)
+          ..addSpan(inputExpr, outputExpr))
+        .toJson(output.url.toString());
+    var mapping = parse(json);
+    check(outputVar1, mapping, inputVar1, true);
+    check(outputVar2, mapping, inputVar2, true);
+    check(outputFunction, mapping, inputFunction, true);
+    check(outputExpr, mapping, inputExpr, true);
+  });
+
+  test('printer projecting marks + parse', () {
+    var out = inputContent.replaceAll('long', '_s');
+    var file = SourceFile.fromString(out, url: 'output2.dart');
+    var printer = Printer('output2.dart');
+    printer.mark(ispan(0, 0));
+
+    var segments = inputContent.split('long');
+    expect(segments.length, 6);
+    printer.add(segments[0], projectMarks: true);
+    printer.mark(inputVar1);
+    printer.add('_s');
+    printer.add(segments[1], projectMarks: true);
+    printer.mark(inputFunction);
+    printer.add('_s');
+    printer.add(segments[2], projectMarks: true);
+    printer.mark(inputVar2);
+    printer.add('_s');
+    printer.add(segments[3], projectMarks: true);
+    printer.mark(inputExpr);
+    printer.add('_s');
+    printer.add(segments[4], projectMarks: true);
+    printer.add('_s');
+    printer.add(segments[5], projectMarks: true);
+
+    expect(printer.text, out);
+
+    var mapping = parse(printer.map);
+    void checkHelper(SourceMapSpan inputSpan, int adjustment) {
+      var start = inputSpan.start.offset - adjustment;
+      var end = (inputSpan.end.offset - adjustment) - 2;
+      var span = SourceMapFileSpan(file.span(start, end),
+          isIdentifier: inputSpan.isIdentifier);
+      check(span, mapping, inputSpan, true);
+    }
+
+    checkHelper(inputVar1, 0);
+    checkHelper(inputFunction, 2);
+    checkHelper(inputVar2, 4);
+    checkHelper(inputExpr, 6);
+
+    // We projected correctly lines that have no mappings
+    check(file.span(66, 66), mapping, ispan(45, 45), true);
+    check(file.span(63, 64), mapping, ispan(45, 45), true);
+    check(file.span(68, 68), mapping, ispan(70, 70), true);
+    check(file.span(71, 71), mapping, ispan(70, 70), true);
+
+    // Start of the last line
+    var oOffset = out.length - 2;
+    var iOffset = inputContent.length - 2;
+    check(file.span(oOffset, oOffset), mapping, ispan(iOffset, iOffset), true);
+    check(file.span(oOffset + 1, oOffset + 1), mapping, ispan(iOffset, iOffset),
+        true);
+  });
+}
diff --git a/pkgs/source_maps/test/parser_test.dart b/pkgs/source_maps/test/parser_test.dart
new file mode 100644
index 0000000..6cfe928
--- /dev/null
+++ b/pkgs/source_maps/test/parser_test.dart
@@ -0,0 +1,431 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// ignore_for_file: inference_failure_on_collection_literal
+// ignore_for_file: inference_failure_on_instance_creation
+
+import 'dart:convert';
+
+import 'package:source_maps/source_maps.dart';
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+
+import 'common.dart';
+
+const Map<String, dynamic> _mapWithNoSourceLocation = {
+  'version': 3,
+  'sourceRoot': '',
+  'sources': ['input.dart'],
+  'names': [],
+  'mappings': 'A',
+  'file': 'output.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocation = {
+  'version': 3,
+  'sourceRoot': '',
+  'sources': ['input.dart'],
+  'names': [],
+  'mappings': 'AAAA',
+  'file': 'output.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocationAndMissingNames = {
+  'version': 3,
+  'sourceRoot': '',
+  'sources': ['input.dart'],
+  'mappings': 'AAAA',
+  'file': 'output.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocationAndName = {
+  'version': 3,
+  'sourceRoot': '',
+  'sources': ['input.dart'],
+  'names': ['var'],
+  'mappings': 'AAAAA',
+  'file': 'output.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocationAndName1 = {
+  'version': 3,
+  'sourceRoot': 'pkg/',
+  'sources': ['input1.dart'],
+  'names': ['var1'],
+  'mappings': 'AAAAA',
+  'file': 'output.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocationAndName2 = {
+  'version': 3,
+  'sourceRoot': 'pkg/',
+  'sources': ['input2.dart'],
+  'names': ['var2'],
+  'mappings': 'AAAAA',
+  'file': 'output2.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocationAndName3 = {
+  'version': 3,
+  'sourceRoot': 'pkg/',
+  'sources': ['input3.dart'],
+  'names': ['var3'],
+  'mappings': 'AAAAA',
+  'file': '3/output.dart'
+};
+
+const _sourceMapBundle = [
+  _mapWithSourceLocationAndName1,
+  _mapWithSourceLocationAndName2,
+  _mapWithSourceLocationAndName3,
+];
+
+void main() {
+  test('parse', () {
+    var mapping = parseJson(expectedMap);
+    check(outputVar1, mapping, inputVar1, false);
+    check(outputVar2, mapping, inputVar2, false);
+    check(outputFunction, mapping, inputFunction, false);
+    check(outputExpr, mapping, inputExpr, false);
+  });
+
+  test('parse + json', () {
+    var mapping = parse(jsonEncode(expectedMap));
+    check(outputVar1, mapping, inputVar1, false);
+    check(outputVar2, mapping, inputVar2, false);
+    check(outputFunction, mapping, inputFunction, false);
+    check(outputExpr, mapping, inputExpr, false);
+  });
+
+  test('parse with file', () {
+    var mapping = parseJson(expectedMap);
+    check(outputVar1, mapping, inputVar1, true);
+    check(outputVar2, mapping, inputVar2, true);
+    check(outputFunction, mapping, inputFunction, true);
+    check(outputExpr, mapping, inputExpr, true);
+  });
+
+  test('parse with no source location', () {
+    var map = parse(jsonEncode(_mapWithNoSourceLocation)) as SingleMapping;
+    expect(map.lines.length, 1);
+    expect(map.lines.first.entries.length, 1);
+    var entry = map.lines.first.entries.first;
+
+    expect(entry.column, 0);
+    expect(entry.sourceUrlId, null);
+    expect(entry.sourceColumn, null);
+    expect(entry.sourceLine, null);
+    expect(entry.sourceNameId, null);
+  });
+
+  test('parse with source location and no name', () {
+    var map = parse(jsonEncode(_mapWithSourceLocation)) as SingleMapping;
+    expect(map.lines.length, 1);
+    expect(map.lines.first.entries.length, 1);
+    var entry = map.lines.first.entries.first;
+
+    expect(entry.column, 0);
+    expect(entry.sourceUrlId, 0);
+    expect(entry.sourceColumn, 0);
+    expect(entry.sourceLine, 0);
+    expect(entry.sourceNameId, null);
+  });
+
+  test('parse with source location and missing names entry', () {
+    var map = parse(jsonEncode(_mapWithSourceLocationAndMissingNames))
+        as SingleMapping;
+    expect(map.lines.length, 1);
+    expect(map.lines.first.entries.length, 1);
+    var entry = map.lines.first.entries.first;
+
+    expect(entry.column, 0);
+    expect(entry.sourceUrlId, 0);
+    expect(entry.sourceColumn, 0);
+    expect(entry.sourceLine, 0);
+    expect(entry.sourceNameId, null);
+  });
+
+  test('parse with source location and name', () {
+    var map = parse(jsonEncode(_mapWithSourceLocationAndName)) as SingleMapping;
+    expect(map.lines.length, 1);
+    expect(map.lines.first.entries.length, 1);
+    var entry = map.lines.first.entries.first;
+
+    expect(entry.sourceUrlId, 0);
+    expect(entry.sourceUrlId, 0);
+    expect(entry.sourceColumn, 0);
+    expect(entry.sourceLine, 0);
+    expect(entry.sourceNameId, 0);
+  });
+
+  test('parse with source root', () {
+    var inputMap = Map.from(_mapWithSourceLocation);
+    inputMap['sourceRoot'] = '/pkg/';
+    var mapping = parseJson(inputMap) as SingleMapping;
+    expect(mapping.spanFor(0, 0)?.sourceUrl, Uri.parse('/pkg/input.dart'));
+    expect(
+        mapping
+            .spanForLocation(
+                SourceLocation(0, sourceUrl: Uri.parse('ignored.dart')))
+            ?.sourceUrl,
+        Uri.parse('/pkg/input.dart'));
+
+    var newSourceRoot = '/new/';
+
+    mapping.sourceRoot = newSourceRoot;
+    inputMap['sourceRoot'] = newSourceRoot;
+
+    expect(mapping.toJson(), equals(inputMap));
+  });
+
+  test('parse with map URL', () {
+    var inputMap = Map.from(_mapWithSourceLocation);
+    inputMap['sourceRoot'] = 'pkg/';
+    var mapping = parseJson(inputMap, mapUrl: 'file:///path/to/map');
+    expect(mapping.spanFor(0, 0)?.sourceUrl,
+        Uri.parse('file:///path/to/pkg/input.dart'));
+  });
+
+  group('parse with bundle', () {
+    var mapping =
+        parseJsonExtended(_sourceMapBundle, mapUrl: 'file:///path/to/map');
+
+    test('simple', () {
+      expect(
+          mapping
+              .spanForLocation(SourceLocation(0,
+                  sourceUrl: Uri.file('/path/to/output.dart')))
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input1.dart'));
+      expect(
+          mapping
+              .spanForLocation(SourceLocation(0,
+                  sourceUrl: Uri.file('/path/to/output2.dart')))
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input2.dart'));
+      expect(
+          mapping
+              .spanForLocation(SourceLocation(0,
+                  sourceUrl: Uri.file('/path/to/3/output.dart')))
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input3.dart'));
+
+      expect(
+          mapping.spanFor(0, 0, uri: 'file:///path/to/output.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input1.dart'));
+      expect(
+          mapping.spanFor(0, 0, uri: 'file:///path/to/output2.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input2.dart'));
+      expect(
+          mapping
+              .spanFor(0, 0, uri: 'file:///path/to/3/output.dart')
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input3.dart'));
+    });
+
+    test('package uris', () {
+      expect(
+          mapping
+              .spanForLocation(SourceLocation(0,
+                  sourceUrl: Uri.parse('package:1/output.dart')))
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input1.dart'));
+      expect(
+          mapping
+              .spanForLocation(SourceLocation(0,
+                  sourceUrl: Uri.parse('package:2/output2.dart')))
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input2.dart'));
+      expect(
+          mapping
+              .spanForLocation(SourceLocation(0,
+                  sourceUrl: Uri.parse('package:3/output.dart')))
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input3.dart'));
+
+      expect(mapping.spanFor(0, 0, uri: 'package:1/output.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input1.dart'));
+      expect(mapping.spanFor(0, 0, uri: 'package:2/output2.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input2.dart'));
+      expect(mapping.spanFor(0, 0, uri: 'package:3/output.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input3.dart'));
+    });
+
+    test('unmapped path', () {
+      var span = mapping.spanFor(0, 0, uri: 'unmapped_output.dart')!;
+      expect(span.sourceUrl, Uri.parse('unmapped_output.dart'));
+      expect(span.start.line, equals(0));
+      expect(span.start.column, equals(0));
+
+      span = mapping.spanFor(10, 5, uri: 'unmapped_output.dart')!;
+      expect(span.sourceUrl, Uri.parse('unmapped_output.dart'));
+      expect(span.start.line, equals(10));
+      expect(span.start.column, equals(5));
+    });
+
+    test('missing path', () {
+      expect(() => mapping.spanFor(0, 0), throwsA(anything));
+    });
+
+    test('incomplete paths', () {
+      expect(mapping.spanFor(0, 0, uri: 'output.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input1.dart'));
+      expect(mapping.spanFor(0, 0, uri: 'output2.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input2.dart'));
+      expect(mapping.spanFor(0, 0, uri: '3/output.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input3.dart'));
+    });
+
+    test('parseExtended', () {
+      var mapping = parseExtended(jsonEncode(_sourceMapBundle),
+          mapUrl: 'file:///path/to/map');
+
+      expect(mapping.spanFor(0, 0, uri: 'output.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input1.dart'));
+      expect(mapping.spanFor(0, 0, uri: 'output2.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input2.dart'));
+      expect(mapping.spanFor(0, 0, uri: '3/output.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input3.dart'));
+    });
+
+    test('build bundle incrementally', () {
+      var mapping = MappingBundle();
+
+      mapping.addMapping(parseJson(_mapWithSourceLocationAndName1,
+          mapUrl: 'file:///path/to/map') as SingleMapping);
+      expect(mapping.spanFor(0, 0, uri: 'output.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input1.dart'));
+
+      expect(mapping.containsMapping('output2.dart'), isFalse);
+      mapping.addMapping(parseJson(_mapWithSourceLocationAndName2,
+          mapUrl: 'file:///path/to/map') as SingleMapping);
+      expect(mapping.containsMapping('output2.dart'), isTrue);
+      expect(mapping.spanFor(0, 0, uri: 'output2.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input2.dart'));
+
+      expect(mapping.containsMapping('3/output.dart'), isFalse);
+      mapping.addMapping(parseJson(_mapWithSourceLocationAndName3,
+          mapUrl: 'file:///path/to/map') as SingleMapping);
+      expect(mapping.containsMapping('3/output.dart'), isTrue);
+      expect(mapping.spanFor(0, 0, uri: '3/output.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input3.dart'));
+    });
+
+    // Test that the source map can handle cases where the uri passed in is
+    // not from the expected host but it is still unambiguous which source
+    // map should be used.
+    test('different paths', () {
+      expect(
+          mapping
+              .spanForLocation(SourceLocation(0,
+                  sourceUrl: Uri.parse('http://localhost/output.dart')))
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input1.dart'));
+      expect(
+          mapping
+              .spanForLocation(SourceLocation(0,
+                  sourceUrl: Uri.parse('http://localhost/output2.dart')))
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input2.dart'));
+      expect(
+          mapping
+              .spanForLocation(SourceLocation(0,
+                  sourceUrl: Uri.parse('http://localhost/3/output.dart')))
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input3.dart'));
+
+      expect(
+          mapping.spanFor(0, 0, uri: 'http://localhost/output.dart')?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input1.dart'));
+      expect(
+          mapping
+              .spanFor(0, 0, uri: 'http://localhost/output2.dart')
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input2.dart'));
+      expect(
+          mapping
+              .spanFor(0, 0, uri: 'http://localhost/3/output.dart')
+              ?.sourceUrl,
+          Uri.parse('file:///path/to/pkg/input3.dart'));
+    });
+  });
+
+  test('parse and re-emit', () {
+    for (var expected in [
+      expectedMap,
+      _mapWithNoSourceLocation,
+      _mapWithSourceLocation,
+      _mapWithSourceLocationAndName
+    ]) {
+      var mapping = parseJson(expected) as SingleMapping;
+      expect(mapping.toJson(), equals(expected));
+
+      mapping = parseJsonExtended(expected) as SingleMapping;
+      expect(mapping.toJson(), equals(expected));
+    }
+
+    var mapping = parseJsonExtended(_sourceMapBundle) as MappingBundle;
+    expect(mapping.toJson(), equals(_sourceMapBundle));
+  });
+
+  test('parse extensions', () {
+    var map = Map.from(expectedMap);
+    map['x_foo'] = 'a';
+    map['x_bar'] = [3];
+    var mapping = parseJson(map) as SingleMapping;
+    expect(mapping.toJson(), equals(map));
+    expect(mapping.extensions['x_foo'], equals('a'));
+    expect((mapping.extensions['x_bar'] as List).first, equals(3));
+  });
+
+  group('source files', () {
+    group('from fromEntries()', () {
+      test('are null for non-FileLocations', () {
+        var mapping = SingleMapping.fromEntries([
+          Entry(SourceLocation(10, line: 1, column: 8), outputVar1.start, null)
+        ]);
+        expect(mapping.files, equals([null]));
+      });
+
+      test("use a file location's file", () {
+        var mapping = SingleMapping.fromEntries(
+            [Entry(inputVar1.start, outputVar1.start, null)]);
+        expect(mapping.files, equals([input]));
+      });
+    });
+
+    group('from parse()', () {
+      group('are null', () {
+        test('with no sourcesContent field', () {
+          var mapping = parseJson(expectedMap) as SingleMapping;
+          expect(mapping.files, equals([null]));
+        });
+
+        test('with null sourcesContent values', () {
+          var map = Map.from(expectedMap);
+          map['sourcesContent'] = [null];
+          var mapping = parseJson(map) as SingleMapping;
+          expect(mapping.files, equals([null]));
+        });
+
+        test('with a too-short sourcesContent', () {
+          var map = Map.from(expectedMap);
+          map['sourcesContent'] = [];
+          var mapping = parseJson(map) as SingleMapping;
+          expect(mapping.files, equals([null]));
+        });
+      });
+
+      test('are parsed from sourcesContent', () {
+        var map = Map.from(expectedMap);
+        map['sourcesContent'] = ['hello, world!'];
+        var mapping = parseJson(map) as SingleMapping;
+
+        var file = mapping.files[0]!;
+        expect(file.url, equals(Uri.parse('input.dart')));
+        expect(file.getText(0), equals('hello, world!'));
+      });
+    });
+  });
+}
diff --git a/pkgs/source_maps/test/printer_test.dart b/pkgs/source_maps/test/printer_test.dart
new file mode 100644
index 0000000..89265e3
--- /dev/null
+++ b/pkgs/source_maps/test/printer_test.dart
@@ -0,0 +1,126 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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 'dart:convert';
+
+import 'package:source_maps/source_maps.dart';
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+
+import 'common.dart';
+
+void main() {
+  test('printer', () {
+    var printer = Printer('output.dart');
+    printer
+      ..add('var ')
+      ..mark(inputVar1)
+      ..add('x = 3;\n')
+      ..mark(inputFunction)
+      ..add('f(')
+      ..mark(inputVar2)
+      ..add('y) => ')
+      ..mark(inputExpr)
+      ..add('x + y;\n');
+    expect(printer.text, outputContent);
+    expect(printer.map, jsonEncode(expectedMap));
+  });
+
+  test('printer projecting marks', () {
+    var out = inputContent.replaceAll('long', '_s');
+    var printer = Printer('output2.dart');
+
+    var segments = inputContent.split('long');
+    expect(segments.length, 6);
+    printer
+      ..mark(ispan(0, 0))
+      ..add(segments[0], projectMarks: true)
+      ..mark(inputVar1)
+      ..add('_s')
+      ..add(segments[1], projectMarks: true)
+      ..mark(inputFunction)
+      ..add('_s')
+      ..add(segments[2], projectMarks: true)
+      ..mark(inputVar2)
+      ..add('_s')
+      ..add(segments[3], projectMarks: true)
+      ..mark(inputExpr)
+      ..add('_s')
+      ..add(segments[4], projectMarks: true)
+      ..add('_s')
+      ..add(segments[5], projectMarks: true);
+
+    expect(printer.text, out);
+    // 8 new lines in the source map:
+    expect(printer.map.split(';').length, 8);
+
+    SourceMapSpan asFixed(SourceMapSpan s) =>
+        SourceMapSpan(s.start, s.end, s.text, isIdentifier: s.isIdentifier);
+
+    // The result is the same if we use fixed positions
+    var printer2 = Printer('output2.dart');
+    printer2
+      ..mark(SourceLocation(0, sourceUrl: 'input.dart').pointSpan())
+      ..add(segments[0], projectMarks: true)
+      ..mark(asFixed(inputVar1))
+      ..add('_s')
+      ..add(segments[1], projectMarks: true)
+      ..mark(asFixed(inputFunction))
+      ..add('_s')
+      ..add(segments[2], projectMarks: true)
+      ..mark(asFixed(inputVar2))
+      ..add('_s')
+      ..add(segments[3], projectMarks: true)
+      ..mark(asFixed(inputExpr))
+      ..add('_s')
+      ..add(segments[4], projectMarks: true)
+      ..add('_s')
+      ..add(segments[5], projectMarks: true);
+
+    expect(printer2.text, out);
+    expect(printer2.map, printer.map);
+  });
+
+  group('nested printer', () {
+    test('simple use', () {
+      var printer = NestedPrinter();
+      printer
+        ..add('var ')
+        ..add('x = 3;\n', span: inputVar1)
+        ..add('f(', span: inputFunction)
+        ..add('y) => ', span: inputVar2)
+        ..add('x + y;\n', span: inputExpr)
+        ..build('output.dart');
+      expect(printer.text, outputContent);
+      expect(printer.map, jsonEncode(expectedMap));
+    });
+
+    test('nested use', () {
+      var printer = NestedPrinter();
+      printer
+        ..add('var ')
+        ..add(NestedPrinter()..add('x = 3;\n', span: inputVar1))
+        ..add('f(', span: inputFunction)
+        ..add(NestedPrinter()..add('y) => ', span: inputVar2))
+        ..add('x + y;\n', span: inputExpr)
+        ..build('output.dart');
+      expect(printer.text, outputContent);
+      expect(printer.map, jsonEncode(expectedMap));
+    });
+
+    test('add indentation', () {
+      var out = inputContent.replaceAll('long', '_s');
+      var lines = inputContent.trim().split('\n');
+      expect(lines.length, 7);
+      var printer = NestedPrinter();
+      for (var i = 0; i < lines.length; i++) {
+        if (i == 5) printer.indent++;
+        printer.addLine(lines[i].replaceAll('long', '_s').trim());
+        if (i == 5) printer.indent--;
+      }
+      printer.build('output.dart');
+      expect(printer.text, out);
+    });
+  });
+}
diff --git a/pkgs/source_maps/test/refactor_test.dart b/pkgs/source_maps/test/refactor_test.dart
new file mode 100644
index 0000000..5bc3818
--- /dev/null
+++ b/pkgs/source_maps/test/refactor_test.dart
@@ -0,0 +1,199 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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:source_maps/parser.dart' show Mapping, parse;
+import 'package:source_maps/refactor.dart';
+import 'package:source_span/source_span.dart';
+import 'package:term_glyph/term_glyph.dart' as term_glyph;
+import 'package:test/test.dart';
+
+void main() {
+  setUpAll(() {
+    term_glyph.ascii = true;
+  });
+
+  group('conflict detection', () {
+    var original = '0123456789abcdefghij';
+    var file = SourceFile.fromString(original);
+
+    test('no conflict, in order', () {
+      var txn = TextEditTransaction(original, file);
+      txn.edit(2, 4, '.');
+      txn.edit(5, 5, '|');
+      txn.edit(6, 6, '-');
+      txn.edit(6, 7, '_');
+      expect((txn.commit()..build('')).text, '01.4|5-_789abcdefghij');
+    });
+
+    test('no conflict, out of order', () {
+      var txn = TextEditTransaction(original, file);
+      txn.edit(2, 4, '.');
+      txn.edit(5, 5, '|');
+
+      // Regresion test for issue #404: there is no conflict/overlap for edits
+      // that don't remove any of the original code.
+      txn.edit(6, 7, '_');
+      txn.edit(6, 6, '-');
+      expect((txn.commit()..build('')).text, '01.4|5-_789abcdefghij');
+    });
+
+    test('conflict', () {
+      var txn = TextEditTransaction(original, file);
+      txn.edit(2, 4, '.');
+      txn.edit(3, 3, '-');
+      expect(
+          () => txn.commit(),
+          throwsA(
+              predicate((e) => e.toString().contains('overlapping edits'))));
+    });
+  });
+
+  test('generated source maps', () {
+    var original =
+        '0123456789\n0*23456789\n01*3456789\nabcdefghij\nabcd*fghij\n';
+    var file = SourceFile.fromString(original);
+    var txn = TextEditTransaction(original, file);
+    txn.edit(27, 29, '__\n    ');
+    txn.edit(34, 35, '___');
+    var printer = (txn.commit()..build(''));
+    var output = printer.text;
+    var map = parse(printer.map!);
+    expect(output,
+        '0123456789\n0*23456789\n01*34__\n    789\na___cdefghij\nabcd*fghij\n');
+
+    // Line 1 and 2 are unmodified: mapping any column returns the beginning
+    // of the corresponding line:
+    expect(
+        _span(1, 1, map, file),
+        'line 1, column 1: \n'
+        '  ,\n'
+        '1 | 0123456789\n'
+        '  | ^\n'
+        "  '");
+    expect(
+        _span(1, 5, map, file),
+        'line 1, column 1: \n'
+        '  ,\n'
+        '1 | 0123456789\n'
+        '  | ^\n'
+        "  '");
+    expect(
+        _span(2, 1, map, file),
+        'line 2, column 1: \n'
+        '  ,\n'
+        '2 | 0*23456789\n'
+        '  | ^\n'
+        "  '");
+    expect(
+        _span(2, 8, map, file),
+        'line 2, column 1: \n'
+        '  ,\n'
+        '2 | 0*23456789\n'
+        '  | ^\n'
+        "  '");
+
+    // Line 3 is modified part way: mappings before the edits have the right
+    // mapping, after the edits the mapping is null.
+    expect(
+        _span(3, 1, map, file),
+        'line 3, column 1: \n'
+        '  ,\n'
+        '3 | 01*3456789\n'
+        '  | ^\n'
+        "  '");
+    expect(
+        _span(3, 5, map, file),
+        'line 3, column 1: \n'
+        '  ,\n'
+        '3 | 01*3456789\n'
+        '  | ^\n'
+        "  '");
+
+    // Start of edits map to beginning of the edit secion:
+    expect(
+        _span(3, 6, map, file),
+        'line 3, column 6: \n'
+        '  ,\n'
+        '3 | 01*3456789\n'
+        '  |      ^\n'
+        "  '");
+    expect(
+        _span(3, 7, map, file),
+        'line 3, column 6: \n'
+        '  ,\n'
+        '3 | 01*3456789\n'
+        '  |      ^\n'
+        "  '");
+
+    // Lines added have no mapping (they should inherit the last mapping),
+    // but the end of the edit region continues were we left off:
+    expect(_span(4, 1, map, file), isNull);
+    expect(
+        _span(4, 5, map, file),
+        'line 3, column 8: \n'
+        '  ,\n'
+        '3 | 01*3456789\n'
+        '  |        ^\n'
+        "  '");
+
+    // Subsequent lines are still mapped correctly:
+    // a (in a___cd...)
+    expect(
+        _span(5, 1, map, file),
+        'line 4, column 1: \n'
+        '  ,\n'
+        '4 | abcdefghij\n'
+        '  | ^\n'
+        "  '");
+    // _ (in a___cd...)
+    expect(
+        _span(5, 2, map, file),
+        'line 4, column 2: \n'
+        '  ,\n'
+        '4 | abcdefghij\n'
+        '  |  ^\n'
+        "  '");
+    // _ (in a___cd...)
+    expect(
+        _span(5, 3, map, file),
+        'line 4, column 2: \n'
+        '  ,\n'
+        '4 | abcdefghij\n'
+        '  |  ^\n'
+        "  '");
+    // _ (in a___cd...)
+    expect(
+        _span(5, 4, map, file),
+        'line 4, column 2: \n'
+        '  ,\n'
+        '4 | abcdefghij\n'
+        '  |  ^\n'
+        "  '");
+    // c (in a___cd...)
+    expect(
+        _span(5, 5, map, file),
+        'line 4, column 3: \n'
+        '  ,\n'
+        '4 | abcdefghij\n'
+        '  |   ^\n'
+        "  '");
+    expect(
+        _span(6, 1, map, file),
+        'line 5, column 1: \n'
+        '  ,\n'
+        '5 | abcd*fghij\n'
+        '  | ^\n'
+        "  '");
+    expect(
+        _span(6, 8, map, file),
+        'line 5, column 1: \n'
+        '  ,\n'
+        '5 | abcd*fghij\n'
+        '  | ^\n'
+        "  '");
+  });
+}
+
+String? _span(int line, int column, Mapping map, SourceFile file) =>
+    map.spanFor(line - 1, column - 1, files: {'': file})?.message('').trim();
diff --git a/pkgs/source_maps/test/utils_test.dart b/pkgs/source_maps/test/utils_test.dart
new file mode 100644
index 0000000..4abdce2
--- /dev/null
+++ b/pkgs/source_maps/test/utils_test.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Tests for the binary search utility algorithm.
+library test.utils_test;
+
+import 'package:source_maps/src/utils.dart';
+import 'package:test/test.dart';
+
+void main() {
+  group('binary search', () {
+    test('empty', () {
+      expect(binarySearch([], (x) => true), -1);
+    });
+
+    test('single element', () {
+      expect(binarySearch([1], (x) => true), 0);
+      expect(binarySearch([1], (x) => false), 1);
+    });
+
+    test('no matches', () {
+      var list = [1, 2, 3, 4, 5, 6, 7];
+      expect(binarySearch(list, (x) => false), list.length);
+    });
+
+    test('all match', () {
+      var list = [1, 2, 3, 4, 5, 6, 7];
+      expect(binarySearch(list, (x) => true), 0);
+    });
+
+    test('compare with linear search', () {
+      for (var size = 0; size < 100; size++) {
+        var list = <int>[];
+        for (var i = 0; i < size; i++) {
+          list.add(i);
+        }
+        for (var pos = 0; pos <= size; pos++) {
+          expect(binarySearch(list, (x) => x >= pos),
+              _linearSearch(list, (x) => x >= pos));
+        }
+      }
+    });
+  });
+}
+
+int _linearSearch<T>(List<T> list, bool Function(T) predicate) {
+  if (list.isEmpty) return -1;
+  for (var i = 0; i < list.length; i++) {
+    if (predicate(list[i])) return i;
+  }
+  return list.length;
+}
diff --git a/pkgs/source_maps/test/vlq_test.dart b/pkgs/source_maps/test/vlq_test.dart
new file mode 100644
index 0000000..4568cff
--- /dev/null
+++ b/pkgs/source_maps/test/vlq_test.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// 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 'dart:math';
+
+import 'package:source_maps/src/vlq.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('encode and decode - simple values', () {
+    expect(encodeVlq(1).join(''), 'C');
+    expect(encodeVlq(2).join(''), 'E');
+    expect(encodeVlq(3).join(''), 'G');
+    expect(encodeVlq(100).join(''), 'oG');
+    expect(decodeVlq('C'.split('').iterator), 1);
+    expect(decodeVlq('E'.split('').iterator), 2);
+    expect(decodeVlq('G'.split('').iterator), 3);
+    expect(decodeVlq('oG'.split('').iterator), 100);
+  });
+
+  test('encode and decode', () {
+    for (var i = -10000; i < 10000; i++) {
+      _checkEncodeDecode(i);
+    }
+  });
+
+  test('only 32-bit ints allowed', () {
+    var maxInt = (pow(2, 31) as int) - 1;
+    var minInt = -(pow(2, 31) as int);
+    _checkEncodeDecode(maxInt - 1);
+    _checkEncodeDecode(minInt + 1);
+    _checkEncodeDecode(maxInt);
+    _checkEncodeDecode(minInt);
+
+    expect(encodeVlq(minInt).join(''), 'hgggggE');
+    expect(decodeVlq('hgggggE'.split('').iterator), minInt);
+
+    expect(() => encodeVlq(maxInt + 1), throwsA(anything));
+    expect(() => encodeVlq(maxInt + 2), throwsA(anything));
+    expect(() => encodeVlq(minInt - 1), throwsA(anything));
+    expect(() => encodeVlq(minInt - 2), throwsA(anything));
+
+    // if we allowed more than 32 bits, these would be the expected encodings
+    // for the large numbers above.
+    expect(() => decodeVlq('ggggggE'.split('').iterator), throwsA(anything));
+    expect(() => decodeVlq('igggggE'.split('').iterator), throwsA(anything));
+    expect(() => decodeVlq('jgggggE'.split('').iterator), throwsA(anything));
+    expect(() => decodeVlq('lgggggE'.split('').iterator), throwsA(anything));
+  },
+      // This test uses integers so large they overflow in JS.
+      testOn: 'dart-vm');
+}
+
+void _checkEncodeDecode(int value) {
+  var encoded = encodeVlq(value);
+  expect(decodeVlq(encoded.iterator), value);
+  expect(decodeVlq(encoded.join('').split('').iterator), value);
+}