Use the package_resolver package.

This allows this to be compatible with package specs as well as package
directories.

See dart-lang/test#327

R=jmesserly@google.com

Review URL: https://codereview.chromium.org//2168203002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93c4fe2..7513300 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+## 1.1.0
+
+* `mapStackTrace()` now uses a `SyncPackageResolver` object from the
+  [`package_resolver` package][package_resolver] to recreate `package:` URIs.
+
+* **Deprecation**: the `packageRoot` parameter to `mapStackTrace` is deprecated
+  in favor of the `packageInfo` parameter described above. It will be removed in
+  a future release.
+
+[package_resolver]: https://pub.dartlang.org/packages/package_resolver
+
 ## 1.0.5
 
 * Add compatibility for member names that include named arguments.
diff --git a/lib/source_map_stack_trace.dart b/lib/source_map_stack_trace.dart
index 83bbf64..63c5a7a 100644
--- a/lib/source_map_stack_trace.dart
+++ b/lib/source_map_stack_trace.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:package_resolver/package_resolver.dart';
 import 'package:path/path.dart' as p;
 import 'package:source_maps/source_maps.dart';
 import 'package:stack_trace/stack_trace.dart';
@@ -12,25 +13,35 @@
 /// [minified] indicates whether or not the dart2js code was minified. If it
 /// hasn't, this tries to clean up the stack frame member names.
 ///
-/// [packageRoot] is the URI (usually a `file:` URI) for the package root that
-/// was used by dart2js. It can be a [String] or a [Uri]. If it's passed, stack
-/// frames from packages will use `package:` URLs.
+/// If [packageResolver] is passed, it's used to reconstruct `package:` URIs for
+/// stack frames that come from packages.
 ///
 /// [sdkRoot] is the URI (usually a `file:` URI) for the SDK containing dart2js.
 /// It can be a [String] or a [Uri]. If it's passed, stack frames from the SDK
 /// will have `dart:` URLs.
+///
+/// [packageRoot] is deprecated and shouldn't be used in new code. This throws
+/// an [ArgumentError] if [packageRoot] and [packageResolver] are both passed.
 StackTrace mapStackTrace(Mapping sourceMap, StackTrace stackTrace,
-    {bool minified: false, packageRoot, sdkRoot}) {
-  if (stackTrace is Chain) {
-    return new Chain(stackTrace.traces.map((trace) {
-      return new Trace.from(mapStackTrace(sourceMap, trace,
-          minified: minified, packageRoot: packageRoot, sdkRoot: sdkRoot));
-    }));
+    {bool minified: false, SyncPackageResolver packageResolver, sdkRoot,
+    @Deprecated("Use the packageResolver parameter instead.") packageRoot}) {
+  if (packageRoot != null) {
+    if (packageResolver != null) {
+      throw new ArgumentError(
+          "packageResolver and packageRoot may not both be passed.");
+    }
+
+    packageResolver = new SyncPackageResolver.root(packageRoot);
   }
 
-  if (packageRoot != null && packageRoot is! String && packageRoot is! Uri) {
-    throw new ArgumentError(
-        'packageRoot must be a String or a Uri, was "$packageRoot".');
+  if (stackTrace is Chain) {
+    return new Chain(stackTrace.traces.map((trace) {
+      return new Trace.from(mapStackTrace(
+          sourceMap, trace,
+          minified: minified,
+          packageResolver: packageResolver,
+          sdkRoot: sdkRoot));
+    }));
   }
 
   if (sdkRoot != null && sdkRoot is! String && sdkRoot is! Uri) {
@@ -38,7 +49,6 @@
         'sdkRoot must be a String or a Uri, was "$sdkRoot".');
   }
 
-  packageRoot = packageRoot == null ? null : packageRoot.toString();
   var sdkLib = sdkRoot == null ? null : "$sdkRoot/lib";
 
   var trace = new Trace.from(stackTrace);
@@ -61,9 +71,21 @@
     var sourceUrl = span.sourceUrl.toString();
     if (sdkRoot != null && p.url.isWithin(sdkLib, sourceUrl)) {
       sourceUrl = "dart:" + p.url.relative(sourceUrl, from: sdkLib);
-    } else if (packageRoot != null && p.url.isWithin(packageRoot, sourceUrl)) {
-      sourceUrl = "package:" +
-          p.url.relative(sourceUrl, from: packageRoot);
+    } else if (packageResolver != null) {
+      if (packageResolver.packageRoot != null &&
+          p.url.isWithin(packageResolver.packageRoot.toString(), sourceUrl)) {
+        sourceUrl = "package:" + p.url.relative(sourceUrl,
+            from: packageResolver.packageRoot.toString());
+      } else {
+        for (var package in packageResolver.packageConfigMap.keys) {
+          var packageUrl = packageResolver.packageConfigMap[package].toString();
+          if (!p.url.isWithin(packageUrl, sourceUrl)) continue;
+
+          sourceUrl = "package:$package/" +
+              p.url.relative(sourceUrl, from: packageUrl);
+          break;
+        }
+      }
     }
 
     return new Frame(
diff --git a/pubspec.yaml b/pubspec.yaml
index d2e7970..b59a503 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,11 +1,12 @@
 name: source_map_stack_trace
-version: 1.0.5
+version: 1.1.0
 description: >
   A package for applying source maps to stack traces.
 author: Dart Team <misc@dartlang.org>
 homepage: https://github.com/dart-lang/source_map_stack_trace
 
 dependencies:
+  package_resolver: "^1.0.0"
   stack_trace: "^1.0.0"
   source_maps: "^0.10.0"
 
diff --git a/test/source_map_stack_trace_test.dart b/test/source_map_stack_trace_test.dart
index 12ae7b1..64145a6 100644
--- a/test/source_map_stack_trace_test.dart
+++ b/test/source_map_stack_trace_test.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:package_resolver/package_resolver.dart';
 import 'package:source_maps/source_maps.dart';
 import 'package:source_span/source_span.dart';
 import 'package:stack_trace/stack_trace.dart';
@@ -102,6 +103,53 @@
     expect(frame.column, equals(4));
   });
 
+  test("uses package: URIs for frames within packageResolver.packageRoot", () {
+    var trace = new Trace.parse("foo.dart.js 10  foo");
+    var builder = new SourceMapBuilder()
+        ..addSpan(
+            new SourceMapSpan.identifier(
+                new SourceLocation(1,
+                    line: 1, column: 3, sourceUrl: "packages/foo/foo.dart"),
+                "qux"),
+            new SourceSpan(
+                new SourceLocation(8, line: 5, column: 0),
+                new SourceLocation(12, line: 9, column: 1),
+                "\n" * 4));
+
+    var mapping = parseJson(builder.build("foo.dart.js.map"));
+    var mappedTrace = _mapTrace(mapping, trace,
+        packageResolver: new SyncPackageResolver.root("packages/"));
+    var frame = mappedTrace.frames.first;
+    expect(frame.uri, equals(Uri.parse("package:foo/foo.dart")));
+    expect(frame.line, equals(2));
+    expect(frame.column, equals(4));
+  });
+
+  test("uses package: URIs for frames within a packageResolver.packageMap URL",
+      () {
+    var trace = new Trace.parse("foo.dart.js 10  foo");
+    var builder = new SourceMapBuilder()
+        ..addSpan(
+            new SourceMapSpan.identifier(
+                new SourceLocation(1,
+                    line: 1, column: 3, sourceUrl: "packages/foo/foo.dart"),
+                "qux"),
+            new SourceSpan(
+                new SourceLocation(8, line: 5, column: 0),
+                new SourceLocation(12, line: 9, column: 1),
+                "\n" * 4));
+
+    var mapping = parseJson(builder.build("foo.dart.js.map"));
+    var mappedTrace = _mapTrace(mapping, trace,
+        packageResolver: new SyncPackageResolver.config({
+          "foo": Uri.parse("packages/foo")
+        }));
+    var frame = mappedTrace.frames.first;
+    expect(frame.uri, equals(Uri.parse("package:foo/foo.dart")));
+    expect(frame.line, equals(2));
+    expect(frame.column, equals(4));
+  });
+
   test("uses dart: URIs for frames within sdkRoot", () {
     var trace = new Trace.parse("foo.dart.js 10  foo");
     var builder = new SourceMapBuilder()
@@ -198,17 +246,21 @@
 /// Like [mapStackTrace], but is guaranteed to return a [Trace] so it can be
 /// inspected.
 Trace _mapTrace(Mapping sourceMap, StackTrace stackTrace,
-    {bool minified: false, packageRoot, sdkRoot}) {
+    {bool minified: false, SyncPackageResolver packageResolver, sdkRoot,
+    packageRoot}) {
   return new Trace.from(mapStackTrace(sourceMap, stackTrace,
-      minified: minified, packageRoot: packageRoot, sdkRoot: sdkRoot));
+      minified: minified, packageResolver: packageResolver, sdkRoot: sdkRoot,
+      packageRoot: packageRoot));
 }
 
 /// Like [mapStackTrace], but is guaranteed to return a [Chain] so it can be
 /// inspected.
 Chain _mapChain(Mapping sourceMap, StackTrace stackTrace,
-    {bool minified: false, packageRoot, sdkRoot}) {
+    {bool minified: false, SyncPackageResolver packageResolver, sdkRoot,
+    packageRoot}) {
   return new Chain.forTrace(mapStackTrace(sourceMap, stackTrace,
-      minified: minified, packageRoot: packageRoot, sdkRoot: sdkRoot));
+      minified: minified, packageResolver: packageResolver, sdkRoot: sdkRoot,
+      packageRoot: packageRoot));
 }
 
 /// Runs the mapper's prettification logic on [member] and returns the result.