Chromium Code Reviews| Index: lib/parser.dart |
| diff --git a/lib/parser.dart b/lib/parser.dart |
| index b659654f04693f1b339472675699b95fe604381f..c84dd792c7c81f3274662f52224f0884183c45ff 100644 |
| --- a/lib/parser.dart |
| +++ b/lib/parser.dart |
| @@ -8,6 +8,7 @@ library source_maps.parser; |
| import 'dart:collection'; |
| import 'dart:convert'; |
| +import 'package:path/path.dart' as path; |
| import 'package:source_span/source_span.dart'; |
| import 'builder.dart' as builder; |
| @@ -25,22 +26,26 @@ import 'src/vlq.dart'; |
| // 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, mapUrl}) => |
| - parseJson(JSON.decode(jsonMap), otherMaps: otherMaps, mapUrl: mapUrl); |
| + parseJson(JSON.decode(jsonMap), otherMaps: otherMaps, mapUrl: mapUrl); |
| -/// Parses a source map directly from a json map object. |
| +/// Parses a source map or source map bundle directly from a json object. |
| /// |
| /// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of |
| /// the source map file itself. If it's passed, any URLs in the source |
| /// map will be interpreted as relative to this URL when generating spans. |
| -Mapping parseJson(Map map, {Map<String, Map> otherMaps, mapUrl}) { |
| +Mapping parseJson(/*Map|List*/ json, {Map<String, Map> otherMaps, mapUrl}) { |
|
Siggi Cherem (dart-lang)
2016/12/07 18:06:22
Since this is not part of the standard source-map
Jacob
2016/12/07 20:40:31
added parseJsonExtended method
that parses the ext
|
| + if (json is List) { |
| + return new MappingBundle.fromJson(json, mapUrl: mapUrl); |
| + } |
| + Map map = json; |
| if (map['version'] != 3) { |
| - throw new ArgumentError( |
| - 'unexpected source map version: ${map["version"]}. ' |
| + throw new ArgumentError('unexpected source map version: ${map["version"]}. ' |
| 'Only version 3 is supported.'); |
| } |
| if (map.containsKey('sections')) { |
| - if (map.containsKey('mappings') || map.containsKey('sources') || |
| + if (map.containsKey('mappings') || |
| + map.containsKey('sources') || |
| map.containsKey('names')) { |
| throw new FormatException('map containing "sections" ' |
| 'cannot contain "mappings", "sources", or "names".'); |
| @@ -51,16 +56,19 @@ Mapping parseJson(Map map, {Map<String, Map> otherMaps, mapUrl}) { |
| return new SingleMapping.fromJson(map, mapUrl: mapUrl); |
| } |
| - |
| /// A mapping parsed out of a source map. |
| abstract class Mapping { |
| /// Returns the span associated with [line] and [column]. |
| - SourceMapSpan spanFor(int line, int column, {Map<String, SourceFile> files}); |
| + /// [uri] is the optional location of the output file to find the span for |
|
Siggi Cherem (dart-lang)
2016/12/07 18:06:22
Assuming we agree with splitting the APIs, I'd rem
Jacob
2016/12/07 20:40:31
discussed offline. kept but checked.
|
| + /// to disambiguate cases where a mapping may have different mappings for |
| + /// different output files. |
| + SourceMapSpan spanFor(int line, int column, |
| + {Map<String, SourceFile> files, String uri}); |
| /// Returns the span associated with [location]. |
| SourceMapSpan spanForLocation(SourceLocation location, |
| - {Map<String, SourceFile> files}) { |
| - return spanFor(location.line, location.column, files: files); |
| + {Map<String, SourceFile> files, String uri}) { |
|
Jacob
2016/12/07 20:40:31
removed uri from this method as
SourceLocation spe
|
| + return spanFor(location.line, location.column, files: files, uri: uri); |
| } |
| } |
| @@ -116,35 +124,76 @@ class MultiSectionMapping extends Mapping { |
| } |
| int _indexFor(line, column) { |
| - for(int i = 0; i < _lineStart.length; i++) { |
| + for (int i = 0; i < _lineStart.length; i++) { |
| if (line < _lineStart[i]) return i - 1; |
| if (line == _lineStart[i] && column < _columnStart[i]) return i - 1; |
| } |
| return _lineStart.length - 1; |
| } |
| - SourceMapSpan spanFor(int line, int column, {Map<String, SourceFile> files}) { |
| + SourceMapSpan spanFor(int line, int column, |
| + {Map<String, SourceFile> files, String uri}) { |
| + // TODO(jacobr): perhaps verify that targetUrl matches the actual uri |
| + // or at least ends in the same file name. |
| int index = _indexFor(line, column); |
| return _maps[index].spanFor( |
| - line - _lineStart[index], column - _columnStart[index], files: files); |
| + line - _lineStart[index], column - _columnStart[index], |
| + files: files); |
| } |
| String toString() { |
| var buff = new StringBuffer("$runtimeType : ["); |
| for (int i = 0; i < _lineStart.length; i++) { |
| - buff..write('(') |
| - ..write(_lineStart[i]) |
| - ..write(',') |
| - ..write(_columnStart[i]) |
| - ..write(':') |
| - ..write(_maps[i]) |
| - ..write(')'); |
| + buff |
| + ..write('(') |
| + ..write(_lineStart[i]) |
| + ..write(',') |
| + ..write(_columnStart[i]) |
| + ..write(':') |
| + ..write(_maps[i]) |
| + ..write(')'); |
| } |
| buff.write(']'); |
| return buff.toString(); |
| } |
| } |
| +class MappingBundle extends Mapping { |
| + Map<String, SingleMapping> _mappings = {}; |
| + |
| + MappingBundle.fromJson(List json, {String mapUrl}) { |
| + for (var map in json) { |
| + var mapping = new SingleMapping.fromJson(map, mapUrl: mapUrl); |
|
Siggi Cherem (dart-lang)
2016/12/07 18:06:22
maybe use `parseJson` so we get the validation we
Jacob
2016/12/07 20:40:31
Done. Note we cast to SingleMapping as only Single
|
| + var targetUrl = mapping.targetUrl; |
| + _mappings[targetUrl] = mapping; |
| + } |
| + } |
| + |
| + /// Encodes the Mapping mappings as a json map. |
| + List toJson() => _mappings.values.map((v) => v.toJson()).toList(); |
| + |
| + String toString() { |
| + var buff = new StringBuffer(); |
| + for (var map in _mappings.values) { |
| + buff.write(map.toString()); |
| + } |
| + return buff.toString(); |
| + } |
| + |
| + SourceMapSpan spanFor(int line, int column, |
| + {Map<String, SourceFile> files, String uri}) { |
|
Siggi Cherem (dart-lang)
2016/12/07 18:06:22
2 suggestions:
- make uri mandatory
- rename it
Jacob
2016/12/07 20:40:31
discussed offline. added check that it is not null
|
| + if (_mappings.containsKey(uri)) { |
|
Siggi Cherem (dart-lang)
2016/12/07 18:06:22
check that `uri` is not null?
Jacob
2016/12/07 20:40:31
Done.
|
| + return _mappings[uri].spanFor(line, column, files: files, uri: uri); |
| + } |
| + // Fall back to looking up the source map on just the basename. |
| + var name = path.basename(uri.toString()); |
| + if (_mappings.containsKey(name)) { |
| + return _mappings[name].spanFor(line, column, files: files, uri: name); |
| + } |
| + return null; |
| + } |
| +} |
| + |
| /// A map containing direct source mappings. |
| class SingleMapping extends Mapping { |
| /// Source urls used in the mapping, indexed by id. |
| @@ -167,8 +216,8 @@ class SingleMapping extends Mapping { |
| SingleMapping._(this.targetUrl, this.urls, this.names, this.lines) |
| : _mapUrl = null; |
| - factory SingleMapping.fromEntries( |
| - Iterable<builder.Entry> entries, [String fileUrl]) { |
| + factory SingleMapping.fromEntries(Iterable<builder.Entry> entries, |
| + [String fileUrl]) { |
| // The entries needs to be sorted by the target offsets. |
| var sourceEntries = new List.from(entries)..sort(); |
| var lines = <TargetLineEntry>[]; |
| @@ -196,14 +245,11 @@ class SingleMapping extends Mapping { |
| var sourceUrl = sourceEntry.source.sourceUrl; |
| var urlId = urls.putIfAbsent( |
| sourceUrl == null ? '' : sourceUrl.toString(), () => urls.length); |
| - var srcNameId = sourceEntry.identifierName == null ? null : |
| - names.putIfAbsent(sourceEntry.identifierName, () => names.length); |
| - targetEntries.add(new TargetEntry( |
| - sourceEntry.target.column, |
| - urlId, |
| - sourceEntry.source.line, |
| - sourceEntry.source.column, |
| - srcNameId)); |
| + var srcNameId = sourceEntry.identifierName == null |
| + ? null |
| + : names.putIfAbsent(sourceEntry.identifierName, () => names.length); |
| + targetEntries.add(new TargetEntry(sourceEntry.target.column, urlId, |
| + sourceEntry.source.line, sourceEntry.source.column, srcNameId)); |
| } |
| } |
| return new SingleMapping._( |
| @@ -271,8 +317,8 @@ class SingleMapping extends Mapping { |
| throw new StateError( |
| 'Invalid name id: $targetUrl, $line, $srcNameId'); |
| } |
| - entries.add(new TargetEntry(column, srcUrlId, srcLine, srcColumn, |
| - srcNameId)); |
| + entries.add( |
| + new TargetEntry(column, srcUrlId, srcLine, srcColumn, srcNameId)); |
| } |
| } |
| if (tokenizer.nextKind.isNewSegment) tokenizer._consumeNewSegment(); |
| @@ -326,8 +372,8 @@ class SingleMapping extends Mapping { |
| 'version': 3, |
| 'sourceRoot': sourceRoot == null ? '' : sourceRoot, |
| 'sources': urls, |
| - 'names' : names, |
| - 'mappings' : buff.toString() |
| + 'names': names, |
| + 'mappings': buff.toString() |
| }; |
| if (targetUrl != null) { |
| result['file'] = targetUrl; |
| @@ -342,9 +388,9 @@ class SingleMapping extends Mapping { |
| return newValue; |
| } |
| - _segmentError(int seen, int line) => new StateError( |
| - 'Invalid entry in sourcemap, expected 1, 4, or 5' |
| - ' values, but got $seen.\ntargeturl: $targetUrl, line: $line'); |
| + _segmentError(int seen, int line) => |
| + new StateError('Invalid entry in sourcemap, expected 1, 4, or 5' |
| + ' values, but got $seen.\ntargeturl: $targetUrl, line: $line'); |
| /// Returns [TargetLineEntry] which includes the location in the target [line] |
| /// number. In particular, the resulting entry is the last entry whose line |
| @@ -367,7 +413,8 @@ class SingleMapping extends Mapping { |
| return (index <= 0) ? null : entries[index - 1]; |
| } |
| - SourceMapSpan spanFor(int line, int column, {Map<String, SourceFile> files}) { |
| + SourceMapSpan spanFor(int line, int column, |
| + {Map<String, SourceFile> files, String uri}) { |
| var entry = _findColumn(line, column, _findLine(line)); |
| if (entry == null || entry.sourceUrlId == null) return null; |
| var url = urls[entry.sourceUrlId]; |
| @@ -402,17 +449,18 @@ class SingleMapping extends Mapping { |
| String toString() { |
| return (new StringBuffer("$runtimeType : [") |
| - ..write('targetUrl: ') |
| - ..write(targetUrl) |
| - ..write(', sourceRoot: ') |
| - ..write(sourceRoot) |
| - ..write(', urls: ') |
| - ..write(urls) |
| - ..write(', names: ') |
| - ..write(names) |
| - ..write(', lines: ') |
| - ..write(lines) |
| - ..write(']')).toString(); |
| + ..write('targetUrl: ') |
| + ..write(targetUrl) |
| + ..write(', sourceRoot: ') |
| + ..write(sourceRoot) |
| + ..write(', urls: ') |
| + ..write(urls) |
| + ..write(', names: ') |
| + ..write(names) |
| + ..write(', lines: ') |
| + ..write(lines) |
| + ..write(']')) |
| + .toString(); |
| } |
| String get debugString { |
| @@ -420,24 +468,24 @@ class SingleMapping extends Mapping { |
| for (var lineEntry in lines) { |
| var line = lineEntry.line; |
| for (var entry in lineEntry.entries) { |
| - buff..write(targetUrl) |
| + buff |
| + ..write(targetUrl) |
| + ..write(': ') |
| + ..write(line) |
| + ..write(':') |
| + ..write(entry.column); |
| + if (entry.sourceUrlId != null) { |
| + buff |
| + ..write(' --> ') |
| + ..write(sourceRoot) |
| + ..write(urls[entry.sourceUrlId]) |
| ..write(': ') |
| - ..write(line) |
| + ..write(entry.sourceLine) |
| ..write(':') |
| - ..write(entry.column); |
| - if (entry.sourceUrlId != null) { |
| - buff..write(' --> ') |
| - ..write(sourceRoot) |
| - ..write(urls[entry.sourceUrlId]) |
| - ..write(': ') |
| - ..write(entry.sourceLine) |
| - ..write(':') |
| - ..write(entry.sourceColumn); |
| + ..write(entry.sourceColumn); |
| } |
| if (entry.sourceNameId != null) { |
| - buff..write(' (') |
| - ..write(names[entry.sourceNameId]) |
| - ..write(')'); |
| + buff..write(' (')..write(names[entry.sourceNameId])..write(')'); |
| } |
| buff.write('\n'); |
| } |
| @@ -463,8 +511,11 @@ class TargetEntry { |
| 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)'; |
| @@ -482,7 +533,7 @@ class _MappingTokenizer implements Iterator<String> { |
| // Iterator API is used by decodeVlq to consume VLQ entries. |
| bool moveNext() => ++index < _length; |
| String get current => |
| - (index >= 0 && index < _length) ? _internal[index] : null; |
| + (index >= 0 && index < _length) ? _internal[index] : null; |
| bool get hasTokens => index < _length - 1 && _length > 0; |
| @@ -495,8 +546,13 @@ class _MappingTokenizer implements Iterator<String> { |
| } |
| int _consumeValue() => decodeVlq(this); |
| - void _consumeNewLine() { ++index; } |
| - void _consumeNewSegment() { ++index; } |
| + void _consumeNewLine() { |
| + ++index; |
| + } |
| + |
| + void _consumeNewSegment() { |
| + ++index; |
| + } |
| // Print the state of the iterator, with colors indicating the current |
| // position. |