Skip to content
This repository was archived by the owner on Jan 14, 2025. It is now read-only.

Commit 453723b

Browse files
committed
Support a new source map bundle format useful for the Dart Dev Compiler. A source map bundle is a JSON array where each entry is a source map.
BUG= R=sigmund@google.com Review URL: https://codereview.chromium.org//2560623003 .
1 parent ac76be3 commit 453723b

File tree

4 files changed

+290
-89
lines changed

4 files changed

+290
-89
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.10.1+3
2+
3+
* Add `MappingBundle` class that handles extended source map format that
4+
supports source maps for multiple output files in a single mapper.
5+
Extend `Mapping.spanFor` API to accept a uri parameter that is optional
6+
for normal source maps but required for MappingBundle source maps.
7+
18
## 0.10.1+2
29

310
* Fix more strong mode warnings.

lib/parser.dart

Lines changed: 145 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ library source_maps.parser;
88
import 'dart:collection';
99
import 'dart:convert';
1010

11+
import 'package:path/path.dart' as path;
1112
import 'package:source_span/source_span.dart';
1213

1314
import '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.
2728
Mapping 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.
3558
Mapping 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.
5678
abstract 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.
149221
class 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.\ntargeturl: $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.\ntargeturl: $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.

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: source_maps
2-
version: 0.10.1+2
2+
version: 0.10.1+3
33
author: Dart Team <misc@dartlang.org>
44
description: Library to programmatically manipulate source map files.
55
homepage: http://github.com/dart-lang/source_maps

0 commit comments

Comments
 (0)