Initial release.

R=kevmoo@google.com

Review URL: https://codereview.chromium.org//1115783002
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..0d8803f
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+* Initial release.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..86772e5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+`source_map_stack_trace` is a package for converting stack traces generated by
+dart2js-compiled JavaScript code into readable native Dart stack traces using
+source maps. For example:
+
+```dart
+import 'package:source_map_stack_trace/source_map_stack_trace.dart';
+
+void main() {
+  var jsTrace = // Get a StackTrace generated by dart2js.
+  var mapping = // Get a source map mapping the JS to the Dart source.
+
+  // Convert jsTrace to refer to the Dart source instead.
+  var dartTrace = mapStackTrace(jsTrace, sourceMap);
+  print(dartTrace);
+}
+```
+
+This can convert the following JavaScript trace:
+
+```
+expect_async_test.dart.browser_test.dart.js 2636:15   dart.wrapException
+expect_async_test.dart.browser_test.dart.js 14661:15  main__closure16.call$0
+expect_async_test.dart.browser_test.dart.js 18237:26  Declarer_test__closure.call$1
+expect_async_test.dart.browser_test.dart.js 17905:23  StackZoneSpecification_registerUnaryCallback__closure.call$0
+expect_async_test.dart.browser_test.dart.js 17876:16  StackZoneSpecification._stack_zone_specification$_run$2
+expect_async_test.dart.browser_test.dart.js 17899:26  StackZoneSpecification_registerUnaryCallback_closure.call$1
+expect_async_test.dart.browser_test.dart.js 6115:16   _rootRunUnary
+expect_async_test.dart.browser_test.dart.js 8576:39   _CustomZone.runUnary$2
+expect_async_test.dart.browser_test.dart.js 7135:57   _Future__propagateToListeners_handleValueCallback.call$0
+expect_async_test.dart.browser_test.dart.js 7031:147  dart._Future.static._Future__propagateToListeners
+```
+
+to:
+
+```
+dart:_internal/compiler/js_lib/js_helper.dart 1210:1          wrapException
+test/frontend/expect_async_test.dart 24:5                     main.<fn>.<fn>
+package:test/src/backend/declarer.dart 45:48                  Declarer.test.<fn>.<fn>
+package:stack_trace/src/stack_zone_specification.dart 134:30  StackZoneSpecification.registerUnaryCallback.<fn>.<fn>
+package:stack_trace/src/stack_zone_specification.dart 210:7   StackZoneSpecification._run
+package:stack_trace/src/stack_zone_specification.dart 135:5   StackZoneSpecification.registerUnaryCallback.<fn>
+dart:async/zone.dart 904:14                                   _rootRunUnary
+dart:async/zone.dart 806:3                                    _CustomZone.runUnary
+dart:async/future_impl.dart 486:13                            _Future._propagateToListeners.handleValueCallback
+dart:async/future_impl.dart 567:32                            _Future._propagateToListeners
+```
diff --git a/codereview.settings b/codereview.settings
new file mode 100644
index 0000000..78c567f
--- /dev/null
+++ b/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: https://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/source_map_stack_trace/commit/
+CC_LIST: reviews@dartlang.org
\ No newline at end of file
diff --git a/lib/source_map_stack_trace.dart b/lib/source_map_stack_trace.dart
new file mode 100644
index 0000000..9c65dd9
--- /dev/null
+++ b/lib/source_map_stack_trace.dart
@@ -0,0 +1,107 @@
+// Copyright (c) 2015, 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 source_map_stack_trace;
+
+import 'package:path/path.dart' as p;
+import 'package:source_maps/source_maps.dart';
+import 'package:stack_trace/stack_trace.dart';
+
+/// Convert [stackTrace], a stack trace generated by dart2js-compiled
+/// JavaScript, to a native-looking stack trace using [sourceMap].
+///
+/// [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.
+///
+/// [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.
+StackTrace mapStackTrace(Mapping sourceMap, StackTrace stackTrace,
+    {bool minified: false, packageRoot, sdkRoot}) {
+  if (stackTrace is Chain) {
+    return new Chain(stackTrace.traces.map((trace) {
+      return mapStackTrace(trace, sourceMap,
+          minified: minified, packageRoot: packageRoot, sdkRoot: sdkRoot);
+    }));
+  }
+
+  if (packageRoot != null && packageRoot is! String && packageRoot is! Uri) {
+    throw new ArgumentError(
+        'packageRoot must be a String or a Uri, was "$packageRoot".');
+  }
+
+  if (sdkRoot != null && sdkRoot is! String && sdkRoot is! Uri) {
+    throw new ArgumentError(
+        '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);
+  return new Trace(trace.frames.map((frame) {
+    // If there's no line information, there's no way to translate this frame.
+    // We could return it as-is, but these lines are usually not useful anyways.
+    if (frame.line == null) return null;
+
+    // If there's no column, try using the first column of the line.
+    var column = frame.column == null ? 0 : frame.column;
+    var span = sourceMap.spanFor(frame.line, column);
+
+    // If we can't find a source span, ignore the frame. It's probably something
+    // internal that the user doesn't care about.
+    if (span == null) return null;
+
+    var sourceUrl = span.sourceUrl.toString();
+    if (packageRoot != null && p.url.isWithin(packageRoot, sourceUrl)) {
+      sourceUrl = "package:" +
+          p.url.relative(sourceUrl, from: packageRoot);
+    } else if (sdkRoot != null && p.url.isWithin(sdkLib, sourceUrl)) {
+      sourceUrl = "dart:" + p.url.relative(sourceUrl, from: sdkLib);
+    }
+
+    return new Frame(
+        Uri.parse(sourceUrl),
+        span.start.line + 1,
+        span.start.column + 1,
+        // If the dart2js output is minified, there's no use trying to prettify
+        // its member names. Use the span's identifier if available, otherwise
+        // use the minified member name.
+        minified
+            ? (span.isIdentifier ? span.text : frame.member)
+            : _prettifyMember(frame.member));
+  }).where((frame) => frame != null));
+}
+
+/// Reformats a JS member name to make it look more Dart-like.
+String _prettifyMember(String member) {
+  return member
+      // Get rid of the noise that Firefox sometimes adds.
+      .replaceAll(new RegExp(r"/?<$"), "")
+      // Get rid of arity indicators.
+      .replaceAll(new RegExp(r"\$\d+$"), "")
+      // Convert closures to <fn>.
+      .replaceAllMapped(new RegExp(r"(_+)closure\d*\.call$"),
+          // The number of underscores before "closure" indicates how nested it
+          // is.
+          (match) => ".<fn>" * match[1].length)
+      // Get rid of explicitly-generated calls.
+      .replaceAll(new RegExp(r"\.call$"), "")
+      // Get rid of the top-level method prefix.
+      .replaceAll(new RegExp(r"^dart\."), "")
+      // Get rid of library namespaces.
+      .replaceAll(new RegExp(r"[a-zA-Z_0-9]+\$"), "")
+      // Get rid of the static method prefix. The class name also exists in the
+      // invocation, so we're not getting rid of any information.
+      .replaceAll(new RegExp(r"^[a-zA-Z_0-9]+.static."), "")
+      // Convert underscores after identifiers to dots. This runs the risk of
+      // incorrectly converting members that contain underscores, but those are
+      // contrary to the style guide anyway.
+      .replaceAllMapped(new RegExp(r"([a-zA-Z0-9]+)_"),
+          (match) => match[1] + ".");
+}
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..f847d45
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,16 @@
+name: source_map_stack_trace
+version: 1.0.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:
+  stack_trace: "^1.0.0"
+  source_maps: "^0.10.0"
+
+dev_dependencies:
+  test: "^0.12.0-dev.1"
+
+environment:
+  sdk: '>=1.8.0 <2.0.0'
diff --git a/test/source_map_stack_trace_test.dart b/test/source_map_stack_trace_test.dart
new file mode 100644
index 0000000..432b616
--- /dev/null
+++ b/test/source_map_stack_trace_test.dart
@@ -0,0 +1,177 @@
+// Copyright (c) 2015, 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:stack_trace/stack_trace.dart';
+import 'package:source_map_stack_trace/source_map_stack_trace.dart';
+import 'package:test/test.dart';
+
+/// A simple [Mapping] for tests that don't need anything special.
+final _simpleMapping = parseJson(
+    (new SourceMapBuilder()
+        ..addSpan(
+            new SourceMapSpan.identifier(
+                new SourceLocation(1,
+                    line: 1, column: 3, sourceUrl: "foo.dart"),
+                "qux"),
+            new SourceSpan(
+                new SourceLocation(8, line: 5, column: 0),
+                new SourceLocation(18, line: 15, column: 0),
+                "\n" * 10)))
+        .build("foo.dart.js.map"));
+
+void main() {
+  test("maps a JS line and column to a Dart line and span", () {
+    var trace = new Trace.parse("foo.dart.js 10:11  foo");
+    var frame = mapStackTrace(_simpleMapping, trace).frames.first;
+    expect(frame.uri, equals(Uri.parse("foo.dart")));
+
+    // These are +1 because stack_trace is 1-based whereas source_span is
+    // 0-basd.
+    expect(frame.line, equals(2));
+    expect(frame.column, equals(4));
+  });
+
+  test("ignores JS frames without line info", () {
+    var trace = new Trace.parse("""
+foo.dart.js 10:11  foo
+foo.dart.js        bar
+foo.dart.js 10:11  baz
+""");
+    var frames = mapStackTrace(_simpleMapping, trace).frames;
+
+    expect(frames.length, equals(2));
+    expect(frames.first.member, equals("foo"));
+    expect(frames.last.member, equals("baz"));
+  });
+
+  test("ignores JS frames without corresponding spans", () {
+    var trace = new Trace.parse("""
+foo.dart.js 10:11  foo
+foo.dart.js 1:1   bar
+foo.dart.js 10:11  baz
+""");
+
+    var frames = mapStackTrace(_simpleMapping, trace).frames;
+
+    expect(frames.length, equals(2));
+    expect(frames.first.member, equals("foo"));
+    expect(frames.last.member, equals("baz"));
+  });
+
+  test("falls back to column 0 for unlisted column", () {
+    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: "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 frame = mapStackTrace(mapping, trace).frames.first;
+    expect(frame.uri, equals(Uri.parse("foo.dart")));
+    expect(frame.line, equals(2));
+    expect(frame.column, equals(4));
+  });
+
+  test("uses package: URIs for frames within 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 frame = mapStackTrace(mapping, trace, packageRoot: "packages/")
+        .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()
+        ..addSpan(
+            new SourceMapSpan.identifier(
+                new SourceLocation(1,
+                    line: 1, column: 3, sourceUrl: "sdk/lib/async/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 frame = mapStackTrace(mapping, trace, sdkRoot: "sdk/").frames.first;
+    expect(frame.uri, equals(Uri.parse("dart:async/foo.dart")));
+    expect(frame.line, equals(2));
+    expect(frame.column, equals(4));
+  });
+
+  group("cleans up", () {
+    test("Firefox junk", () {
+      expect(_prettify("foo/<"), equals("foo"));
+      expect(_prettify("foo<"), equals("foo"));
+    });
+
+    test("arity indicators", () {
+      expect(_prettify(r"foo$1"), equals("foo"));
+      expect(_prettify(r"foo$1234"), equals("foo"));
+    });
+
+    test("closures", () {
+      expect(_prettify("foo_closure.call"), equals("foo.<fn>"));
+    });
+
+    test("nested closures", () {
+      expect(_prettify("foo__closure.call"), equals("foo.<fn>.<fn>"));
+      expect(_prettify("foo____closure.call"),
+          equals("foo.<fn>.<fn>.<fn>.<fn>"));
+    });
+
+    test(".call", () {
+      expect(_prettify("foo.call"), equals("foo"));
+    });
+
+    test("top-level functions", () {
+      expect(_prettify("dart.foo"), equals("foo"));
+    });
+
+    test("library namespaces", () {
+      expect(_prettify(r"my_library$foo"), equals("foo"));
+    });
+
+    test("static methods", () {
+      expect(_prettify(r"Foo.static.foo"), equals("foo"));
+    });
+
+    test("instance methods", () {
+      expect(_prettify(r"Foo_bar__baz"), equals("Foo.bar._baz"));
+    });
+
+    test("lots of stuff", () {
+      expect(_prettify(r"lib$Foo.static.lib$Foo_closure.call$0/<"),
+          equals("Foo.<fn>"));
+    });
+  });
+}
+
+/// Runs the mapper's prettification logic on [member] and returns the result.
+String _prettify(String member) {
+  var trace = new Trace([new Frame(Uri.parse("foo.dart.js"), 10, 11, member)]);
+  return mapStackTrace(_simpleMapping, trace).frames.first.member;
+}
diff --git a/todo.txt b/todo.txt
deleted file mode 100644
index 2bd1c23..0000000
--- a/todo.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-- rename project in pubspec.yaml
-- update description in pubspec.yaml
-- update the homepage: field in pubspec.yaml
-
-- rename project in readme.md
-- update description in readme.md
-- update the [tracker] url in readme.md
-
-- (optionally) add a codereview.settings file
-- if building via travis-ci.org, add a .travis.yml file
-
-- delete todo.txt :)