Adding support for `sourceRoot` to the SingleMapping class.

BUG=
R=sigmund@google.com

Review URL: https://codereview.chromium.org//383823002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/source_maps@38241 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/lib/parser.dart b/lib/parser.dart
index 23835a6..f53f7ed 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -16,6 +16,8 @@
 /// Parses a source map directly from a json string.
 // 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}) =>
   parseJson(JSON.decode(jsonMap), otherMaps: otherMaps);
 
@@ -146,6 +148,9 @@
   /// Entries indicating the beginning of each span.
   final List<TargetLineEntry> lines;
 
+  /// Source root appended to the start of all entries in [urls].
+  String sourceRoot = null;
+
   SingleMapping._internal(this.targetUrl, this.urls, this.names, this.lines);
 
   factory SingleMapping.fromEntries(
@@ -192,9 +197,9 @@
 
   SingleMapping.fromJson(Map map)
       : targetUrl = map['file'],
-        // TODO(sigmund): add support for 'sourceRoot'
         urls = map['sources'],
         names = map['names'],
+        sourceRoot = map['sourceRoot'],
         lines = <TargetLineEntry>[] {
     int line = 0;
     int column = 0;
@@ -303,7 +308,7 @@
 
     var result = {
       'version': 3,
-      'sourceRoot': '',
+      'sourceRoot': sourceRoot == null ? '' : sourceRoot,
       'sources': urls,
       'names' : names,
       'mappings' : buff.toString()
@@ -350,6 +355,9 @@
     var entry = _findColumn(line, column, _findLine(line));
     if (entry == null || entry.sourceUrlId == null) return null;
     var url = urls[entry.sourceUrlId];
+    if (sourceRoot != null) {
+      url = '${sourceRoot}${url}';
+    }
     if (files != null && files[url] != null) {
       var file = files[url];
       var start = file.getOffset(entry.sourceLine, entry.sourceColumn);
@@ -374,6 +382,8 @@
     return (new StringBuffer("$runtimeType : [")
         ..write('targetUrl: ')
         ..write(targetUrl)
+        ..write(', sourceRoot: ')
+        ..write(sourceRoot)
         ..write(', urls: ')
         ..write(urls)
         ..write(', names: ')
@@ -392,13 +402,16 @@
             ..write(': ')
             ..write(line)
             ..write(':')
-            ..write(entry.column)
-            ..write('   -->   ')
-            ..write(urls[entry.sourceUrlId])
-            ..write(': ')
-            ..write(entry.sourceLine)
-            ..write(':')
-            ..write(entry.sourceColumn);
+            ..write(entry.column);
+        if (entry.sourceUrlId != null) {
+          buff..write('   -->   ')
+              ..write(sourceRoot)
+              ..write(urls[entry.sourceUrlId])
+              ..write(': ')
+              ..write(entry.sourceLine)
+              ..write(':')
+              ..write(entry.sourceColumn);
+        }
         if (entry.sourceNameId != null) {
           buff..write(' (')
               ..write(names[entry.sourceNameId])
diff --git a/test/parser_test.dart b/test/parser_test.dart
index 8f1be3d..62cd08f 100644
--- a/test/parser_test.dart
+++ b/test/parser_test.dart
@@ -100,6 +100,20 @@
     expect(entry.sourceNameId, 0);
   });
 
+  test('parse with source root', () {
+    var inputMap = new Map.from(MAP_WITH_SOURCE_LOCATION);
+    inputMap['sourceRoot'] = '/pkg/';
+    var mapping = parseJson(inputMap);
+    expect(mapping.spanFor(0, 0).sourceUrl, "/pkg/input.dart");
+
+    var newSourceRoot = '/new/';
+
+    mapping.sourceRoot = newSourceRoot;
+    inputMap["sourceRoot"] = newSourceRoot;
+
+    expect(mapping.toJson(), equals(inputMap));
+  });
+
   test('parse and re-emit', () {
     for (var expected in [
         EXPECTED_MAP,