Fix bug in source-maps parsing: parser failed when most information could be
inferred. This fixes bug http://dartbug.com/11782

R=dgrove@google.com

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

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/source_maps@24981 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/lib/builder.dart b/lib/builder.dart
index ad80fa0..be00c9e 100644
--- a/lib/builder.dart
+++ b/lib/builder.dart
@@ -74,12 +74,20 @@
       first = false;
       column = _append(buff, column, entry.target.column);
 
-      if (entry.source == null) continue;
+      // Encoding can be just the column offset if there is no source
+      // information, or if two consecutive mappings share exactly the same
+      // source information.
+      var source = entry.source;
+      if (source == null) continue;
+      var newUrlId = _indexOf(_urls, source.sourceUrl);
+      if (newUrlId == srcUrlId && source.line == srcLine
+          && source.column == srcColumn && entry.identifierName == null) {
+        continue;
+      }
 
-      srcUrlId = _append(buff, srcUrlId,
-          _indexOf(_urls, entry.source.sourceUrl));
-      srcLine = _append(buff, srcLine, entry.source.line);
-      srcColumn = _append(buff, srcColumn, entry.source.column);
+      srcUrlId = _append(buff, srcUrlId, newUrlId);
+      srcLine = _append(buff, srcLine, source.line);
+      srcColumn = _append(buff, srcColumn, source.column);
 
       if (entry.identifierName == null) continue;
       srcNameId = _append(buff, srcNameId,
diff --git a/lib/parser.dart b/lib/parser.dart
index 3849913..e293263 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -25,9 +25,8 @@
         'Only version 3 is supported.');
   }
 
-  // TODO(sigmund): relax this? dart2js doesn't generate the file entry.
   if (!map.containsKey('file')) {
-    throw new ArgumentError('missing "file" in source map');
+    print('warning: missing "file" entry in source map');
   }
 
   if (map.containsKey('sections')) {
@@ -186,7 +185,7 @@
       if (tokenizer.nextKind.isNewSegment) throw _segmentError(0, line);
       column += tokenizer._consumeValue();
       if (!tokenizer.nextKind.isValue) {
-        entries.add(new TargetEntry(column));
+        entries.add(new TargetEntry(column, srcUrlId, srcLine, srcColumn));
       } else {
         srcUrlId += tokenizer._consumeValue();
         if (srcUrlId >= urls.length) {
@@ -242,7 +241,6 @@
   }
 
   Span spanFor(int line, int column, {Map<String, SourceFile> files}) {
-    var lineEntry = _findLine(line);
     var entry = _findColumn(line, column, _findLine(line));
     if (entry == null) return null;
     var url = urls[entry.sourceUrlId];
@@ -323,8 +321,9 @@
   final int sourceLine;
   final int sourceColumn;
   final int sourceNameId;
-  TargetEntry(this.column, [this.sourceUrlId, this.sourceLine,
-      this.sourceColumn, this.sourceNameId]);
+
+  TargetEntry(this.column, this.sourceUrlId, this.sourceLine,
+      this.sourceColumn, [this.sourceNameId]);
 
   String toString() => '$runtimeType: '
       '($column, $sourceUrlId, $sourceLine, $sourceColumn, $sourceNameId)';
diff --git a/lib/source_maps.dart b/lib/source_maps.dart
index 1594827..8fc48c9 100644
--- a/lib/source_maps.dart
+++ b/lib/source_maps.dart
@@ -2,22 +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.
 
-/// Source maps library.
-///
-/// ## Installing ##
-///
-/// Use [pub][] to install this package. Add the following to your
-/// `pubspec.yaml` file.
-///
-///     dependencies:
-///       source_maps: any
-///
-/// Then run `pub install`.
-///
-/// For more information, see the
-/// [source_maps package on pub.dartlang.org][pkg].
-///
-/// ## Using ##
+/// Library to create and parse source maps.
 ///
 /// Create a source map using [SourceMapBuilder]. For example:
 ///     var json = (new SourceMapBuilder()
@@ -33,6 +18,20 @@
 ///     var mapping = parse(json);
 ///     mapping.spanFor(outputSpan1.line, outputSpan1.column)
 ///
+/// ## Getting the code ##
+///
+/// This library is distributed as a [pub][] package. To install this package,
+/// add the following to your `pubspec.yaml` file:
+///
+///     dependencies:
+///       source_maps: any
+///
+/// After you run `pub install`, you should be able to access this library by
+/// importing `package:source_maps/source_maps.dart`.
+///
+/// For more information, see the
+/// [source_maps package on pub.dartlang.org][pkg].
+///
 /// [pub]: http://pub.dartlang.org
 /// [pkg]: http://pub.dartlang.org/packages/source_maps
 library source_maps;
diff --git a/pubspec.yaml b/pubspec.yaml
index a559b58..4ae1802 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
 name: source_maps
 author: "Dart Team <misc@dartlang.org>"
-homepage: https://github.com/dart-lang/source-maps
+homepage: http://www.dartlang.org
 description: Library to programmatically manipulate source map files.
 dev_dependencies:
   unittest: any
diff --git a/test/common.dart b/test/common.dart
index 661979b..0c1f28a 100644
--- a/test/common.dart
+++ b/test/common.dart
@@ -27,6 +27,11 @@
 Span inputVar1 = ispan(30, 38, true);
 Span inputFunction = ispan(74, 82, true);
 Span inputVar2 = ispan(87, 95, true);
+
+Span inputVar1NoSymbol = ispan(30, 38);
+Span inputFunctionNoSymbol = ispan(74, 82);
+Span inputVar2NoSymbol = ispan(87, 95);
+
 Span inputExpr = ispan(108, 127);
 
 /// Content of the target file
@@ -43,6 +48,9 @@
 Span outputVar1 = ospan(4, 5, true);
 Span outputFunction = ospan(11, 12, true);
 Span outputVar2 = ospan(13, 14, true);
+Span outputVar1NoSymbol = ospan(4, 5);
+Span outputFunctionNoSymbol = ospan(11, 12);
+Span outputVar2NoSymbol = ospan(13, 14);
 Span outputExpr = ospan(19, 24);
 
 /// Expected output mapping when recording the following four mappings:
diff --git a/test/end2end_test.dart b/test/end2end_test.dart
index 5ea958a..99fe40d 100644
--- a/test/end2end_test.dart
+++ b/test/end2end_test.dart
@@ -13,11 +13,17 @@
     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');
   });
 
@@ -35,6 +41,55 @@
     check(outputExpr, mapping, inputExpr, false);
   });
 
+  test('build + parse - no symbols', () {
+    var map = (new SourceMapBuilder()
+        ..addSpan(inputVar1NoSymbol, outputVar1NoSymbol)
+        ..addSpan(inputFunctionNoSymbol, outputFunctionNoSymbol)
+        ..addSpan(inputVar2NoSymbol, outputVar2NoSymbol)
+        ..addSpan(inputExpr, outputExpr))
+        .build(output.url);
+    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 = (new 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);
+    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 = (new 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);
+    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 = (new SourceMapBuilder()
         ..addSpan(inputVar1, outputVar1)