@@ -8,6 +8,7 @@ library source_maps.parser;
88import 'dart:collection' ;
99import 'dart:convert' ;
1010
11+ import 'package:path/path.dart' as path;
1112import 'package:source_span/source_span.dart' ;
1213
1314import 'builder.dart' as builder;
@@ -25,22 +26,44 @@ import 'src/vlq.dart';
2526// TODO(tjblasi): Ignore the first line of [jsonMap] if the JSON safety string
2627// `)]}'` begins the string representation of the map.
2728Mapping parse (String jsonMap, {Map <String , Map > otherMaps, mapUrl}) =>
28- parseJson (JSON .decode (jsonMap), otherMaps: otherMaps, mapUrl: mapUrl);
29+ parseJson (JSON .decode (jsonMap), otherMaps: otherMaps, mapUrl: mapUrl);
2930
30- /// Parses a source map directly from a json map object.
31+ /// Parses a source map or source map bundle directly from a json string.
32+ ///
33+ /// [mapUrl] , which may be either a [String] or a [Uri] , indicates the URL of
34+ /// the source map file itself. If it's passed, any URLs in the source
35+ /// map will be interpreted as relative to this URL when generating spans.
36+ Mapping parseExtended (String jsonMap, {Map <String , Map > otherMaps, mapUrl}) =>
37+ parseJsonExtended (JSON .decode (jsonMap),
38+ otherMaps: otherMaps, mapUrl: mapUrl);
39+
40+ /// Parses a source map or source map bundle.
41+ ///
42+ /// [mapUrl] , which may be either a [String] or a [Uri] , indicates the URL of
43+ /// the source map file itself. If it's passed, any URLs in the source
44+ /// map will be interpreted as relative to this URL when generating spans.
45+ Mapping parseJsonExtended (/*List|Map*/ json,
46+ {Map <String , Map > otherMaps, mapUrl}) {
47+ if (json is List ) {
48+ return new MappingBundle .fromJson (json, mapUrl: mapUrl);
49+ }
50+ return parseJson (json as Map );
51+ }
52+
53+ /// Parses a source map
3154///
3255/// [mapUrl] , which may be either a [String] or a [Uri] , indicates the URL of
3356/// the source map file itself. If it's passed, any URLs in the source
3457/// map will be interpreted as relative to this URL when generating spans.
3558Mapping parseJson (Map map, {Map <String , Map > otherMaps, mapUrl}) {
3659 if (map['version' ] != 3 ) {
37- throw new ArgumentError (
38- 'unexpected source map version: ${map ["version" ]}. '
60+ throw new ArgumentError ('unexpected source map version: ${map ["version" ]}. '
3961 'Only version 3 is supported.' );
4062 }
4163
4264 if (map.containsKey ('sections' )) {
43- if (map.containsKey ('mappings' ) || map.containsKey ('sources' ) ||
65+ if (map.containsKey ('mappings' ) ||
66+ map.containsKey ('sources' ) ||
4467 map.containsKey ('names' )) {
4568 throw new FormatException ('map containing "sections" '
4669 'cannot contain "mappings", "sources", or "names".' );
@@ -51,16 +74,21 @@ Mapping parseJson(Map map, {Map<String, Map> otherMaps, mapUrl}) {
5174 return new SingleMapping .fromJson (map, mapUrl: mapUrl);
5275}
5376
54-
5577/// A mapping parsed out of a source map.
5678abstract class Mapping {
5779 /// Returns the span associated with [line] and [column] .
58- SourceMapSpan spanFor (int line, int column, {Map <String , SourceFile > files});
80+ ///
81+ /// [uri] is the optional location of the output file to find the span for
82+ /// to disambiguate cases where a mapping may have different mappings for
83+ /// different output files.
84+ SourceMapSpan spanFor (int line, int column,
85+ {Map <String , SourceFile > files, String uri});
5986
6087 /// Returns the span associated with [location] .
6188 SourceMapSpan spanForLocation (SourceLocation location,
6289 {Map <String , SourceFile > files}) {
63- return spanFor (location.line, location.column, files: files);
90+ return spanFor (location.line, location.column,
91+ uri: location.sourceUrl? .toString (), files: files);
6492 }
6593}
6694
@@ -116,35 +144,79 @@ class MultiSectionMapping extends Mapping {
116144 }
117145
118146 int _indexFor (line, column) {
119- for (int i = 0 ; i < _lineStart.length; i++ ) {
147+ for (int i = 0 ; i < _lineStart.length; i++ ) {
120148 if (line < _lineStart[i]) return i - 1 ;
121149 if (line == _lineStart[i] && column < _columnStart[i]) return i - 1 ;
122150 }
123151 return _lineStart.length - 1 ;
124152 }
125153
126- SourceMapSpan spanFor (int line, int column, {Map <String , SourceFile > files}) {
154+ SourceMapSpan spanFor (int line, int column,
155+ {Map <String , SourceFile > files, String uri}) {
156+ // TODO(jacobr): perhaps verify that targetUrl matches the actual uri
157+ // or at least ends in the same file name.
127158 int index = _indexFor (line, column);
128159 return _maps[index].spanFor (
129- line - _lineStart[index], column - _columnStart[index], files: files);
160+ line - _lineStart[index], column - _columnStart[index],
161+ files: files);
130162 }
131163
132164 String toString () {
133165 var buff = new StringBuffer ("$runtimeType : [" );
134166 for (int i = 0 ; i < _lineStart.length; i++ ) {
135- buff..write ('(' )
136- ..write (_lineStart[i])
137- ..write (',' )
138- ..write (_columnStart[i])
139- ..write (':' )
140- ..write (_maps[i])
141- ..write (')' );
167+ buff
168+ ..write ('(' )
169+ ..write (_lineStart[i])
170+ ..write (',' )
171+ ..write (_columnStart[i])
172+ ..write (':' )
173+ ..write (_maps[i])
174+ ..write (')' );
142175 }
143176 buff.write (']' );
144177 return buff.toString ();
145178 }
146179}
147180
181+ class MappingBundle extends Mapping {
182+ Map <String , SingleMapping > _mappings = {};
183+
184+ MappingBundle .fromJson (List json, {String mapUrl}) {
185+ for (var map in json) {
186+ var mapping = parseJson (map, mapUrl: mapUrl) as SingleMapping ;
187+ var targetUrl = mapping.targetUrl;
188+ _mappings[targetUrl] = mapping;
189+ }
190+ }
191+
192+ /// Encodes the Mapping mappings as a json map.
193+ List toJson () => _mappings.values.map ((v) => v.toJson ()).toList ();
194+
195+ String toString () {
196+ var buff = new StringBuffer ();
197+ for (var map in _mappings.values) {
198+ buff.write (map.toString ());
199+ }
200+ return buff.toString ();
201+ }
202+
203+ SourceMapSpan spanFor (int line, int column,
204+ {Map <String , SourceFile > files, String uri}) {
205+ if (uri == null ) {
206+ throw new ArgumentError .notNull ('uri' );
207+ }
208+ if (_mappings.containsKey (uri)) {
209+ return _mappings[uri].spanFor (line, column, files: files, uri: uri);
210+ }
211+ // Fall back to looking up the source map on just the basename.
212+ var name = path.basename (uri.toString ());
213+ if (_mappings.containsKey (name)) {
214+ return _mappings[name].spanFor (line, column, files: files, uri: name);
215+ }
216+ return null ;
217+ }
218+ }
219+
148220/// A map containing direct source mappings.
149221class SingleMapping extends Mapping {
150222 /// Source urls used in the mapping, indexed by id.
@@ -167,8 +239,8 @@ class SingleMapping extends Mapping {
167239 SingleMapping ._(this .targetUrl, this .urls, this .names, this .lines)
168240 : _mapUrl = null ;
169241
170- factory SingleMapping .fromEntries (
171- Iterable <builder. Entry > entries, [String fileUrl]) {
242+ factory SingleMapping .fromEntries (Iterable <builder. Entry > entries,
243+ [String fileUrl]) {
172244 // The entries needs to be sorted by the target offsets.
173245 var sourceEntries = new List .from (entries)..sort ();
174246 var lines = < TargetLineEntry > [];
@@ -196,14 +268,11 @@ class SingleMapping extends Mapping {
196268 var sourceUrl = sourceEntry.source.sourceUrl;
197269 var urlId = urls.putIfAbsent (
198270 sourceUrl == null ? '' : sourceUrl.toString (), () => urls.length);
199- var srcNameId = sourceEntry.identifierName == null ? null :
200- names.putIfAbsent (sourceEntry.identifierName, () => names.length);
201- targetEntries.add (new TargetEntry (
202- sourceEntry.target.column,
203- urlId,
204- sourceEntry.source.line,
205- sourceEntry.source.column,
206- srcNameId));
271+ var srcNameId = sourceEntry.identifierName == null
272+ ? null
273+ : names.putIfAbsent (sourceEntry.identifierName, () => names.length);
274+ targetEntries.add (new TargetEntry (sourceEntry.target.column, urlId,
275+ sourceEntry.source.line, sourceEntry.source.column, srcNameId));
207276 }
208277 }
209278 return new SingleMapping ._(
@@ -271,8 +340,8 @@ class SingleMapping extends Mapping {
271340 throw new StateError (
272341 'Invalid name id: $targetUrl , $line , $srcNameId ' );
273342 }
274- entries.add (new TargetEntry (column, srcUrlId, srcLine, srcColumn,
275- srcNameId));
343+ entries.add (
344+ new TargetEntry (column, srcUrlId, srcLine, srcColumn, srcNameId));
276345 }
277346 }
278347 if (tokenizer.nextKind.isNewSegment) tokenizer._consumeNewSegment ();
@@ -326,8 +395,8 @@ class SingleMapping extends Mapping {
326395 'version' : 3 ,
327396 'sourceRoot' : sourceRoot == null ? '' : sourceRoot,
328397 'sources' : urls,
329- 'names' : names,
330- 'mappings' : buff.toString ()
398+ 'names' : names,
399+ 'mappings' : buff.toString ()
331400 };
332401 if (targetUrl != null ) {
333402 result['file' ] = targetUrl;
@@ -342,9 +411,9 @@ class SingleMapping extends Mapping {
342411 return newValue;
343412 }
344413
345- _segmentError (int seen, int line) => new StateError (
346- 'Invalid entry in sourcemap, expected 1, 4, or 5'
347- ' values, but got $seen .\n targeturl: $targetUrl , line: $line ' );
414+ _segmentError (int seen, int line) =>
415+ new StateError ( 'Invalid entry in sourcemap, expected 1, 4, or 5'
416+ ' values, but got $seen .\n targeturl: $targetUrl , line: $line ' );
348417
349418 /// Returns [TargetLineEntry] which includes the location in the target [line]
350419 /// number. In particular, the resulting entry is the last entry whose line
@@ -367,7 +436,8 @@ class SingleMapping extends Mapping {
367436 return (index <= 0 ) ? null : entries[index - 1 ];
368437 }
369438
370- SourceMapSpan spanFor (int line, int column, {Map <String , SourceFile > files}) {
439+ SourceMapSpan spanFor (int line, int column,
440+ {Map <String , SourceFile > files, String uri}) {
371441 var entry = _findColumn (line, column, _findLine (line));
372442 if (entry == null || entry.sourceUrlId == null ) return null ;
373443 var url = urls[entry.sourceUrlId];
@@ -402,42 +472,43 @@ class SingleMapping extends Mapping {
402472
403473 String toString () {
404474 return (new StringBuffer ("$runtimeType : [" )
405- ..write ('targetUrl: ' )
406- ..write (targetUrl)
407- ..write (', sourceRoot: ' )
408- ..write (sourceRoot)
409- ..write (', urls: ' )
410- ..write (urls)
411- ..write (', names: ' )
412- ..write (names)
413- ..write (', lines: ' )
414- ..write (lines)
415- ..write (']' )).toString ();
475+ ..write ('targetUrl: ' )
476+ ..write (targetUrl)
477+ ..write (', sourceRoot: ' )
478+ ..write (sourceRoot)
479+ ..write (', urls: ' )
480+ ..write (urls)
481+ ..write (', names: ' )
482+ ..write (names)
483+ ..write (', lines: ' )
484+ ..write (lines)
485+ ..write (']' ))
486+ .toString ();
416487 }
417488
418489 String get debugString {
419490 var buff = new StringBuffer ();
420491 for (var lineEntry in lines) {
421492 var line = lineEntry.line;
422493 for (var entry in lineEntry.entries) {
423- buff..write (targetUrl)
494+ buff
495+ ..write (targetUrl)
496+ ..write (': ' )
497+ ..write (line)
498+ ..write (':' )
499+ ..write (entry.column);
500+ if (entry.sourceUrlId != null ) {
501+ buff
502+ ..write (' --> ' )
503+ ..write (sourceRoot)
504+ ..write (urls[entry.sourceUrlId])
424505 ..write (': ' )
425- ..write (line )
506+ ..write (entry.sourceLine )
426507 ..write (':' )
427- ..write (entry.column);
428- if (entry.sourceUrlId != null ) {
429- buff..write (' --> ' )
430- ..write (sourceRoot)
431- ..write (urls[entry.sourceUrlId])
432- ..write (': ' )
433- ..write (entry.sourceLine)
434- ..write (':' )
435- ..write (entry.sourceColumn);
508+ ..write (entry.sourceColumn);
436509 }
437510 if (entry.sourceNameId != null ) {
438- buff..write (' (' )
439- ..write (names[entry.sourceNameId])
440- ..write (')' );
511+ buff..write (' (' )..write (names[entry.sourceNameId])..write (')' );
441512 }
442513 buff.write ('\n ' );
443514 }
@@ -463,8 +534,11 @@ class TargetEntry {
463534 final int sourceColumn;
464535 final int sourceNameId;
465536
466- TargetEntry (this .column, [this .sourceUrlId, this .sourceLine,
467- this .sourceColumn, this .sourceNameId]);
537+ TargetEntry (this .column,
538+ [this .sourceUrlId,
539+ this .sourceLine,
540+ this .sourceColumn,
541+ this .sourceNameId]);
468542
469543 String toString () => '$runtimeType : '
470544 '($column , $sourceUrlId , $sourceLine , $sourceColumn , $sourceNameId )' ;
@@ -482,7 +556,7 @@ class _MappingTokenizer implements Iterator<String> {
482556 // Iterator API is used by decodeVlq to consume VLQ entries.
483557 bool moveNext () => ++ index < _length;
484558 String get current =>
485- (index >= 0 && index < _length) ? _internal[index] : null ;
559+ (index >= 0 && index < _length) ? _internal[index] : null ;
486560
487561 bool get hasTokens => index < _length - 1 && _length > 0 ;
488562
@@ -495,8 +569,13 @@ class _MappingTokenizer implements Iterator<String> {
495569 }
496570
497571 int _consumeValue () => decodeVlq (this );
498- void _consumeNewLine () { ++ index; }
499- void _consumeNewSegment () { ++ index; }
572+ void _consumeNewLine () {
573+ ++ index;
574+ }
575+
576+ void _consumeNewSegment () {
577+ ++ index;
578+ }
500579
501580 // Print the state of the iterator, with colors indicating the current
502581 // position.
0 commit comments