OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /// Contains the top-level function to parse source maps version 3. | 5 /// Contains the top-level function to parse source maps version 3. |
6 library source_maps.parser; | 6 library source_maps.parser; |
7 | 7 |
8 import 'dart:collection'; | 8 import 'dart:collection'; |
9 import 'dart:convert'; | 9 import 'dart:convert'; |
10 | 10 |
| 11 import 'package:path/path.dart' as path; |
11 import 'package:source_span/source_span.dart'; | 12 import 'package:source_span/source_span.dart'; |
12 | 13 |
13 import 'builder.dart' as builder; | 14 import 'builder.dart' as builder; |
14 import 'src/source_map_span.dart'; | 15 import 'src/source_map_span.dart'; |
15 import 'src/utils.dart'; | 16 import 'src/utils.dart'; |
16 import 'src/vlq.dart'; | 17 import 'src/vlq.dart'; |
17 | 18 |
18 /// Parses a source map directly from a json string. | 19 /// Parses a source map directly from a json string. |
19 /// | 20 /// |
20 /// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of | 21 /// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of |
21 /// the source map file itself. If it's passed, any URLs in the source | 22 /// the source map file itself. If it's passed, any URLs in the source |
22 /// map will be interpreted as relative to this URL when generating spans. | 23 /// map will be interpreted as relative to this URL when generating spans. |
23 // TODO(sigmund): evaluate whether other maps should have the json parsed, or | 24 // TODO(sigmund): evaluate whether other maps should have the json parsed, or |
24 // the string represenation. | 25 // the string represenation. |
25 // TODO(tjblasi): Ignore the first line of [jsonMap] if the JSON safety string | 26 // TODO(tjblasi): Ignore the first line of [jsonMap] if the JSON safety string |
26 // `)]}'` begins the string representation of the map. | 27 // `)]}'` begins the string representation of the map. |
27 Mapping parse(String jsonMap, {Map<String, Map> otherMaps, mapUrl}) => | 28 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); |
29 | 30 |
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 |
31 /// | 54 /// |
32 /// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of | 55 /// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of |
33 /// the source map file itself. If it's passed, any URLs in the source | 56 /// the source map file itself. If it's passed, any URLs in the source |
34 /// map will be interpreted as relative to this URL when generating spans. | 57 /// map will be interpreted as relative to this URL when generating spans. |
35 Mapping parseJson(Map map, {Map<String, Map> otherMaps, mapUrl}) { | 58 Mapping parseJson(Map map, {Map<String, Map> otherMaps, mapUrl}) { |
36 if (map['version'] != 3) { | 59 if (map['version'] != 3) { |
37 throw new ArgumentError( | 60 throw new ArgumentError('unexpected source map version: ${map["version"]}. ' |
38 'unexpected source map version: ${map["version"]}. ' | |
39 'Only version 3 is supported.'); | 61 'Only version 3 is supported.'); |
40 } | 62 } |
41 | 63 |
42 if (map.containsKey('sections')) { | 64 if (map.containsKey('sections')) { |
43 if (map.containsKey('mappings') || map.containsKey('sources') || | 65 if (map.containsKey('mappings') || |
| 66 map.containsKey('sources') || |
44 map.containsKey('names')) { | 67 map.containsKey('names')) { |
45 throw new FormatException('map containing "sections" ' | 68 throw new FormatException('map containing "sections" ' |
46 'cannot contain "mappings", "sources", or "names".'); | 69 'cannot contain "mappings", "sources", or "names".'); |
47 } | 70 } |
48 return new MultiSectionMapping.fromJson(map['sections'], otherMaps, | 71 return new MultiSectionMapping.fromJson(map['sections'], otherMaps, |
49 mapUrl: mapUrl); | 72 mapUrl: mapUrl); |
50 } | 73 } |
51 return new SingleMapping.fromJson(map, mapUrl: mapUrl); | 74 return new SingleMapping.fromJson(map, mapUrl: mapUrl); |
52 } | 75 } |
53 | 76 |
54 | |
55 /// A mapping parsed out of a source map. | 77 /// A mapping parsed out of a source map. |
56 abstract class Mapping { | 78 abstract class Mapping { |
57 /// Returns the span associated with [line] and [column]. | 79 /// 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}); |
59 | 86 |
60 /// Returns the span associated with [location]. | 87 /// Returns the span associated with [location]. |
61 SourceMapSpan spanForLocation(SourceLocation location, | 88 SourceMapSpan spanForLocation(SourceLocation location, |
62 {Map<String, SourceFile> files}) { | 89 {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); |
64 } | 92 } |
65 } | 93 } |
66 | 94 |
67 /// A meta-level map containing sections. | 95 /// A meta-level map containing sections. |
68 class MultiSectionMapping extends Mapping { | 96 class MultiSectionMapping extends Mapping { |
69 /// For each section, the start line offset. | 97 /// For each section, the start line offset. |
70 final List<int> _lineStart = <int>[]; | 98 final List<int> _lineStart = <int>[]; |
71 | 99 |
72 /// For each section, the start column offset. | 100 /// For each section, the start column offset. |
73 final List<int> _columnStart = <int>[]; | 101 final List<int> _columnStart = <int>[]; |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
109 } else { | 137 } else { |
110 throw new FormatException('section missing url or map'); | 138 throw new FormatException('section missing url or map'); |
111 } | 139 } |
112 } | 140 } |
113 if (_lineStart.length == 0) { | 141 if (_lineStart.length == 0) { |
114 throw new FormatException('expected at least one section'); | 142 throw new FormatException('expected at least one section'); |
115 } | 143 } |
116 } | 144 } |
117 | 145 |
118 int _indexFor(line, column) { | 146 int _indexFor(line, column) { |
119 for(int i = 0; i < _lineStart.length; i++) { | 147 for (int i = 0; i < _lineStart.length; i++) { |
120 if (line < _lineStart[i]) return i - 1; | 148 if (line < _lineStart[i]) return i - 1; |
121 if (line == _lineStart[i] && column < _columnStart[i]) return i - 1; | 149 if (line == _lineStart[i] && column < _columnStart[i]) return i - 1; |
122 } | 150 } |
123 return _lineStart.length - 1; | 151 return _lineStart.length - 1; |
124 } | 152 } |
125 | 153 |
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. |
127 int index = _indexFor(line, column); | 158 int index = _indexFor(line, column); |
128 return _maps[index].spanFor( | 159 return _maps[index].spanFor( |
129 line - _lineStart[index], column - _columnStart[index], files: files); | 160 line - _lineStart[index], column - _columnStart[index], |
| 161 files: files); |
130 } | 162 } |
131 | 163 |
132 String toString() { | 164 String toString() { |
133 var buff = new StringBuffer("$runtimeType : ["); | 165 var buff = new StringBuffer("$runtimeType : ["); |
134 for (int i = 0; i < _lineStart.length; i++) { | 166 for (int i = 0; i < _lineStart.length; i++) { |
135 buff..write('(') | 167 buff |
136 ..write(_lineStart[i]) | 168 ..write('(') |
137 ..write(',') | 169 ..write(_lineStart[i]) |
138 ..write(_columnStart[i]) | 170 ..write(',') |
139 ..write(':') | 171 ..write(_columnStart[i]) |
140 ..write(_maps[i]) | 172 ..write(':') |
141 ..write(')'); | 173 ..write(_maps[i]) |
| 174 ..write(')'); |
142 } | 175 } |
143 buff.write(']'); | 176 buff.write(']'); |
144 return buff.toString(); | 177 return buff.toString(); |
145 } | 178 } |
146 } | 179 } |
147 | 180 |
| 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 // The uri doesn't have a source map so create a SourceMapSpan matching |
| 217 // the input location as well as possible. Fake the file offset because |
| 218 // we don't really know it. This fake offset avoids locations with the |
| 219 // same offset but different lines and columns. |
| 220 var offset = line * 1000000000 + column; |
| 221 var location = new SourceLocation(offset, |
| 222 line: line, column: column, sourceUrl: Uri.parse(uri)); |
| 223 return new SourceMapSpan(location, location, ""); |
| 224 } |
| 225 } |
| 226 |
148 /// A map containing direct source mappings. | 227 /// A map containing direct source mappings. |
149 class SingleMapping extends Mapping { | 228 class SingleMapping extends Mapping { |
150 /// Source urls used in the mapping, indexed by id. | 229 /// Source urls used in the mapping, indexed by id. |
151 final List<String> urls; | 230 final List<String> urls; |
152 | 231 |
153 /// Source names used in the mapping, indexed by id. | 232 /// Source names used in the mapping, indexed by id. |
154 final List<String> names; | 233 final List<String> names; |
155 | 234 |
156 /// Entries indicating the beginning of each span. | 235 /// Entries indicating the beginning of each span. |
157 final List<TargetLineEntry> lines; | 236 final List<TargetLineEntry> lines; |
158 | 237 |
159 /// Url of the target file. | 238 /// Url of the target file. |
160 String targetUrl; | 239 String targetUrl; |
161 | 240 |
162 /// Source root prepended to all entries in [urls]. | 241 /// Source root prepended to all entries in [urls]. |
163 String sourceRoot; | 242 String sourceRoot; |
164 | 243 |
165 final Uri _mapUrl; | 244 final Uri _mapUrl; |
166 | 245 |
167 SingleMapping._(this.targetUrl, this.urls, this.names, this.lines) | 246 SingleMapping._(this.targetUrl, this.urls, this.names, this.lines) |
168 : _mapUrl = null; | 247 : _mapUrl = null; |
169 | 248 |
170 factory SingleMapping.fromEntries( | 249 factory SingleMapping.fromEntries(Iterable<builder.Entry> entries, |
171 Iterable<builder.Entry> entries, [String fileUrl]) { | 250 [String fileUrl]) { |
172 // The entries needs to be sorted by the target offsets. | 251 // The entries needs to be sorted by the target offsets. |
173 var sourceEntries = new List.from(entries)..sort(); | 252 var sourceEntries = new List.from(entries)..sort(); |
174 var lines = <TargetLineEntry>[]; | 253 var lines = <TargetLineEntry>[]; |
175 | 254 |
176 // Indices associated with file urls that will be part of the source map. We | 255 // Indices associated with file urls that will be part of the source map. We |
177 // use a linked hash-map so that `_urls.keys[_urls[u]] == u` | 256 // use a linked hash-map so that `_urls.keys[_urls[u]] == u` |
178 var urls = new LinkedHashMap<String, int>(); | 257 var urls = new LinkedHashMap<String, int>(); |
179 | 258 |
180 // Indices associated with identifiers that will be part of the source map. | 259 // Indices associated with identifiers that will be part of the source map. |
181 // We use a linked hash-map so that `_names.keys[_names[n]] == n` | 260 // We use a linked hash-map so that `_names.keys[_names[n]] == n` |
182 var names = new LinkedHashMap<String, int>(); | 261 var names = new LinkedHashMap<String, int>(); |
183 | 262 |
184 var lineNum; | 263 var lineNum; |
185 List<TargetEntry> targetEntries; | 264 List<TargetEntry> targetEntries; |
186 for (var sourceEntry in sourceEntries) { | 265 for (var sourceEntry in sourceEntries) { |
187 if (lineNum == null || sourceEntry.target.line > lineNum) { | 266 if (lineNum == null || sourceEntry.target.line > lineNum) { |
188 lineNum = sourceEntry.target.line; | 267 lineNum = sourceEntry.target.line; |
189 targetEntries = <TargetEntry>[]; | 268 targetEntries = <TargetEntry>[]; |
190 lines.add(new TargetLineEntry(lineNum, targetEntries)); | 269 lines.add(new TargetLineEntry(lineNum, targetEntries)); |
191 } | 270 } |
192 | 271 |
193 if (sourceEntry.source == null) { | 272 if (sourceEntry.source == null) { |
194 targetEntries.add(new TargetEntry(sourceEntry.target.column)); | 273 targetEntries.add(new TargetEntry(sourceEntry.target.column)); |
195 } else { | 274 } else { |
196 var sourceUrl = sourceEntry.source.sourceUrl; | 275 var sourceUrl = sourceEntry.source.sourceUrl; |
197 var urlId = urls.putIfAbsent( | 276 var urlId = urls.putIfAbsent( |
198 sourceUrl == null ? '' : sourceUrl.toString(), () => urls.length); | 277 sourceUrl == null ? '' : sourceUrl.toString(), () => urls.length); |
199 var srcNameId = sourceEntry.identifierName == null ? null : | 278 var srcNameId = sourceEntry.identifierName == null |
200 names.putIfAbsent(sourceEntry.identifierName, () => names.length); | 279 ? null |
201 targetEntries.add(new TargetEntry( | 280 : names.putIfAbsent(sourceEntry.identifierName, () => names.length); |
202 sourceEntry.target.column, | 281 targetEntries.add(new TargetEntry(sourceEntry.target.column, urlId, |
203 urlId, | 282 sourceEntry.source.line, sourceEntry.source.column, srcNameId)); |
204 sourceEntry.source.line, | |
205 sourceEntry.source.column, | |
206 srcNameId)); | |
207 } | 283 } |
208 } | 284 } |
209 return new SingleMapping._( | 285 return new SingleMapping._( |
210 fileUrl, urls.keys.toList(), names.keys.toList(), lines); | 286 fileUrl, urls.keys.toList(), names.keys.toList(), lines); |
211 } | 287 } |
212 | 288 |
213 SingleMapping.fromJson(Map map, {mapUrl}) | 289 SingleMapping.fromJson(Map map, {mapUrl}) |
214 : targetUrl = map['file'], | 290 : targetUrl = map['file'], |
215 urls = new List<String>.from(map['sources']), | 291 urls = new List<String>.from(map['sources']), |
216 names = new List<String>.from(map['names']), | 292 names = new List<String>.from(map['names']), |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
264 if (!tokenizer.nextKind.isValue) throw _segmentError(3, line); | 340 if (!tokenizer.nextKind.isValue) throw _segmentError(3, line); |
265 srcColumn += tokenizer._consumeValue(); | 341 srcColumn += tokenizer._consumeValue(); |
266 if (!tokenizer.nextKind.isValue) { | 342 if (!tokenizer.nextKind.isValue) { |
267 entries.add(new TargetEntry(column, srcUrlId, srcLine, srcColumn)); | 343 entries.add(new TargetEntry(column, srcUrlId, srcLine, srcColumn)); |
268 } else { | 344 } else { |
269 srcNameId += tokenizer._consumeValue(); | 345 srcNameId += tokenizer._consumeValue(); |
270 if (srcNameId >= names.length) { | 346 if (srcNameId >= names.length) { |
271 throw new StateError( | 347 throw new StateError( |
272 'Invalid name id: $targetUrl, $line, $srcNameId'); | 348 'Invalid name id: $targetUrl, $line, $srcNameId'); |
273 } | 349 } |
274 entries.add(new TargetEntry(column, srcUrlId, srcLine, srcColumn, | 350 entries.add( |
275 srcNameId)); | 351 new TargetEntry(column, srcUrlId, srcLine, srcColumn, srcNameId)); |
276 } | 352 } |
277 } | 353 } |
278 if (tokenizer.nextKind.isNewSegment) tokenizer._consumeNewSegment(); | 354 if (tokenizer.nextKind.isNewSegment) tokenizer._consumeNewSegment(); |
279 } | 355 } |
280 if (!entries.isEmpty) { | 356 if (!entries.isEmpty) { |
281 lines.add(new TargetLineEntry(line, entries)); | 357 lines.add(new TargetLineEntry(line, entries)); |
282 } | 358 } |
283 } | 359 } |
284 | 360 |
285 /// Encodes the Mapping mappings as a json map. | 361 /// Encodes the Mapping mappings as a json map. |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
319 | 395 |
320 if (segment.sourceNameId == null) continue; | 396 if (segment.sourceNameId == null) continue; |
321 srcNameId = _append(buff, srcNameId, segment.sourceNameId); | 397 srcNameId = _append(buff, srcNameId, segment.sourceNameId); |
322 } | 398 } |
323 } | 399 } |
324 | 400 |
325 var result = { | 401 var result = { |
326 'version': 3, | 402 'version': 3, |
327 'sourceRoot': sourceRoot == null ? '' : sourceRoot, | 403 'sourceRoot': sourceRoot == null ? '' : sourceRoot, |
328 'sources': urls, | 404 'sources': urls, |
329 'names' : names, | 405 'names': names, |
330 'mappings' : buff.toString() | 406 'mappings': buff.toString() |
331 }; | 407 }; |
332 if (targetUrl != null) { | 408 if (targetUrl != null) { |
333 result['file'] = targetUrl; | 409 result['file'] = targetUrl; |
334 } | 410 } |
335 return result; | 411 return result; |
336 } | 412 } |
337 | 413 |
338 /// Appends to [buff] a VLQ encoding of [newValue] using the difference | 414 /// Appends to [buff] a VLQ encoding of [newValue] using the difference |
339 /// between [oldValue] and [newValue] | 415 /// between [oldValue] and [newValue] |
340 static int _append(StringBuffer buff, int oldValue, int newValue) { | 416 static int _append(StringBuffer buff, int oldValue, int newValue) { |
341 buff.writeAll(encodeVlq(newValue - oldValue)); | 417 buff.writeAll(encodeVlq(newValue - oldValue)); |
342 return newValue; | 418 return newValue; |
343 } | 419 } |
344 | 420 |
345 _segmentError(int seen, int line) => new StateError( | 421 _segmentError(int seen, int line) => |
346 'Invalid entry in sourcemap, expected 1, 4, or 5' | 422 new StateError('Invalid entry in sourcemap, expected 1, 4, or 5' |
347 ' values, but got $seen.\ntargeturl: $targetUrl, line: $line'); | 423 ' values, but got $seen.\ntargeturl: $targetUrl, line: $line'); |
348 | 424 |
349 /// Returns [TargetLineEntry] which includes the location in the target [line] | 425 /// Returns [TargetLineEntry] which includes the location in the target [line] |
350 /// number. In particular, the resulting entry is the last entry whose line | 426 /// number. In particular, the resulting entry is the last entry whose line |
351 /// number is lower or equal to [line]. | 427 /// number is lower or equal to [line]. |
352 TargetLineEntry _findLine(int line) { | 428 TargetLineEntry _findLine(int line) { |
353 int index = binarySearch(lines, (e) => e.line > line); | 429 int index = binarySearch(lines, (e) => e.line > line); |
354 return (index <= 0) ? null : lines[index - 1]; | 430 return (index <= 0) ? null : lines[index - 1]; |
355 } | 431 } |
356 | 432 |
357 /// Returns [TargetEntry] which includes the location denoted by | 433 /// Returns [TargetEntry] which includes the location denoted by |
358 /// [line], [column]. If [lineEntry] corresponds to [line], then this will be | 434 /// [line], [column]. If [lineEntry] corresponds to [line], then this will be |
359 /// the last entry whose column is lower or equal than [column]. If | 435 /// the last entry whose column is lower or equal than [column]. If |
360 /// [lineEntry] corresponds to a line prior to [line], then the result will be | 436 /// [lineEntry] corresponds to a line prior to [line], then the result will be |
361 /// the very last entry on that line. | 437 /// the very last entry on that line. |
362 TargetEntry _findColumn(int line, int column, TargetLineEntry lineEntry) { | 438 TargetEntry _findColumn(int line, int column, TargetLineEntry lineEntry) { |
363 if (lineEntry == null || lineEntry.entries.length == 0) return null; | 439 if (lineEntry == null || lineEntry.entries.length == 0) return null; |
364 if (lineEntry.line != line) return lineEntry.entries.last; | 440 if (lineEntry.line != line) return lineEntry.entries.last; |
365 var entries = lineEntry.entries; | 441 var entries = lineEntry.entries; |
366 int index = binarySearch(entries, (e) => e.column > column); | 442 int index = binarySearch(entries, (e) => e.column > column); |
367 return (index <= 0) ? null : entries[index - 1]; | 443 return (index <= 0) ? null : entries[index - 1]; |
368 } | 444 } |
369 | 445 |
370 SourceMapSpan spanFor(int line, int column, {Map<String, SourceFile> files}) { | 446 SourceMapSpan spanFor(int line, int column, |
| 447 {Map<String, SourceFile> files, String uri}) { |
371 var entry = _findColumn(line, column, _findLine(line)); | 448 var entry = _findColumn(line, column, _findLine(line)); |
372 if (entry == null || entry.sourceUrlId == null) return null; | 449 if (entry == null || entry.sourceUrlId == null) return null; |
373 var url = urls[entry.sourceUrlId]; | 450 var url = urls[entry.sourceUrlId]; |
374 if (sourceRoot != null) { | 451 if (sourceRoot != null) { |
375 url = '${sourceRoot}${url}'; | 452 url = '${sourceRoot}${url}'; |
376 } | 453 } |
377 if (files != null && files[url] != null) { | 454 if (files != null && files[url] != null) { |
378 var file = files[url]; | 455 var file = files[url]; |
379 var start = file.getOffset(entry.sourceLine, entry.sourceColumn); | 456 var start = file.getOffset(entry.sourceLine, entry.sourceColumn); |
380 if (entry.sourceNameId != null) { | 457 if (entry.sourceNameId != null) { |
(...skipping 14 matching lines...) Expand all Loading... |
395 if (entry.sourceNameId != null) { | 472 if (entry.sourceNameId != null) { |
396 return new SourceMapSpan.identifier(start, names[entry.sourceNameId]); | 473 return new SourceMapSpan.identifier(start, names[entry.sourceNameId]); |
397 } else { | 474 } else { |
398 return new SourceMapSpan(start, start, ''); | 475 return new SourceMapSpan(start, start, ''); |
399 } | 476 } |
400 } | 477 } |
401 } | 478 } |
402 | 479 |
403 String toString() { | 480 String toString() { |
404 return (new StringBuffer("$runtimeType : [") | 481 return (new StringBuffer("$runtimeType : [") |
405 ..write('targetUrl: ') | 482 ..write('targetUrl: ') |
406 ..write(targetUrl) | 483 ..write(targetUrl) |
407 ..write(', sourceRoot: ') | 484 ..write(', sourceRoot: ') |
408 ..write(sourceRoot) | 485 ..write(sourceRoot) |
409 ..write(', urls: ') | 486 ..write(', urls: ') |
410 ..write(urls) | 487 ..write(urls) |
411 ..write(', names: ') | 488 ..write(', names: ') |
412 ..write(names) | 489 ..write(names) |
413 ..write(', lines: ') | 490 ..write(', lines: ') |
414 ..write(lines) | 491 ..write(lines) |
415 ..write(']')).toString(); | 492 ..write(']')) |
| 493 .toString(); |
416 } | 494 } |
417 | 495 |
418 String get debugString { | 496 String get debugString { |
419 var buff = new StringBuffer(); | 497 var buff = new StringBuffer(); |
420 for (var lineEntry in lines) { | 498 for (var lineEntry in lines) { |
421 var line = lineEntry.line; | 499 var line = lineEntry.line; |
422 for (var entry in lineEntry.entries) { | 500 for (var entry in lineEntry.entries) { |
423 buff..write(targetUrl) | 501 buff |
| 502 ..write(targetUrl) |
| 503 ..write(': ') |
| 504 ..write(line) |
| 505 ..write(':') |
| 506 ..write(entry.column); |
| 507 if (entry.sourceUrlId != null) { |
| 508 buff |
| 509 ..write(' --> ') |
| 510 ..write(sourceRoot) |
| 511 ..write(urls[entry.sourceUrlId]) |
424 ..write(': ') | 512 ..write(': ') |
425 ..write(line) | 513 ..write(entry.sourceLine) |
426 ..write(':') | 514 ..write(':') |
427 ..write(entry.column); | 515 ..write(entry.sourceColumn); |
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); | |
436 } | 516 } |
437 if (entry.sourceNameId != null) { | 517 if (entry.sourceNameId != null) { |
438 buff..write(' (') | 518 buff..write(' (')..write(names[entry.sourceNameId])..write(')'); |
439 ..write(names[entry.sourceNameId]) | |
440 ..write(')'); | |
441 } | 519 } |
442 buff.write('\n'); | 520 buff.write('\n'); |
443 } | 521 } |
444 } | 522 } |
445 return buff.toString(); | 523 return buff.toString(); |
446 } | 524 } |
447 } | 525 } |
448 | 526 |
449 /// A line entry read from a source map. | 527 /// A line entry read from a source map. |
450 class TargetLineEntry { | 528 class TargetLineEntry { |
451 final int line; | 529 final int line; |
452 List<TargetEntry> entries; | 530 List<TargetEntry> entries; |
453 TargetLineEntry(this.line, this.entries); | 531 TargetLineEntry(this.line, this.entries); |
454 | 532 |
455 String toString() => '$runtimeType: $line $entries'; | 533 String toString() => '$runtimeType: $line $entries'; |
456 } | 534 } |
457 | 535 |
458 /// A target segment entry read from a source map | 536 /// A target segment entry read from a source map |
459 class TargetEntry { | 537 class TargetEntry { |
460 final int column; | 538 final int column; |
461 final int sourceUrlId; | 539 final int sourceUrlId; |
462 final int sourceLine; | 540 final int sourceLine; |
463 final int sourceColumn; | 541 final int sourceColumn; |
464 final int sourceNameId; | 542 final int sourceNameId; |
465 | 543 |
466 TargetEntry(this.column, [this.sourceUrlId, this.sourceLine, | 544 TargetEntry(this.column, |
467 this.sourceColumn, this.sourceNameId]); | 545 [this.sourceUrlId, |
| 546 this.sourceLine, |
| 547 this.sourceColumn, |
| 548 this.sourceNameId]); |
468 | 549 |
469 String toString() => '$runtimeType: ' | 550 String toString() => '$runtimeType: ' |
470 '($column, $sourceUrlId, $sourceLine, $sourceColumn, $sourceNameId)'; | 551 '($column, $sourceUrlId, $sourceLine, $sourceColumn, $sourceNameId)'; |
471 } | 552 } |
472 | 553 |
473 /** A character iterator over a string that can peek one character ahead. */ | 554 /** A character iterator over a string that can peek one character ahead. */ |
474 class _MappingTokenizer implements Iterator<String> { | 555 class _MappingTokenizer implements Iterator<String> { |
475 final String _internal; | 556 final String _internal; |
476 final int _length; | 557 final int _length; |
477 int index = -1; | 558 int index = -1; |
478 _MappingTokenizer(String internal) | 559 _MappingTokenizer(String internal) |
479 : _internal = internal, | 560 : _internal = internal, |
480 _length = internal.length; | 561 _length = internal.length; |
481 | 562 |
482 // Iterator API is used by decodeVlq to consume VLQ entries. | 563 // Iterator API is used by decodeVlq to consume VLQ entries. |
483 bool moveNext() => ++index < _length; | 564 bool moveNext() => ++index < _length; |
484 String get current => | 565 String get current => |
485 (index >= 0 && index < _length) ? _internal[index] : null; | 566 (index >= 0 && index < _length) ? _internal[index] : null; |
486 | 567 |
487 bool get hasTokens => index < _length - 1 && _length > 0; | 568 bool get hasTokens => index < _length - 1 && _length > 0; |
488 | 569 |
489 _TokenKind get nextKind { | 570 _TokenKind get nextKind { |
490 if (!hasTokens) return _TokenKind.EOF; | 571 if (!hasTokens) return _TokenKind.EOF; |
491 var next = _internal[index + 1]; | 572 var next = _internal[index + 1]; |
492 if (next == ';') return _TokenKind.LINE; | 573 if (next == ';') return _TokenKind.LINE; |
493 if (next == ',') return _TokenKind.SEGMENT; | 574 if (next == ',') return _TokenKind.SEGMENT; |
494 return _TokenKind.VALUE; | 575 return _TokenKind.VALUE; |
495 } | 576 } |
496 | 577 |
497 int _consumeValue() => decodeVlq(this); | 578 int _consumeValue() => decodeVlq(this); |
498 void _consumeNewLine() { ++index; } | 579 void _consumeNewLine() { |
499 void _consumeNewSegment() { ++index; } | 580 ++index; |
| 581 } |
| 582 |
| 583 void _consumeNewSegment() { |
| 584 ++index; |
| 585 } |
500 | 586 |
501 // Print the state of the iterator, with colors indicating the current | 587 // Print the state of the iterator, with colors indicating the current |
502 // position. | 588 // position. |
503 String toString() { | 589 String toString() { |
504 var buff = new StringBuffer(); | 590 var buff = new StringBuffer(); |
505 for (int i = 0; i < index; i++) { | 591 for (int i = 0; i < index; i++) { |
506 buff.write(_internal[i]); | 592 buff.write(_internal[i]); |
507 } | 593 } |
508 buff.write('[31m'); | 594 buff.write('[31m'); |
509 buff.write(current == null ? '' : current); | 595 buff.write(current == null ? '' : current); |
(...skipping 12 matching lines...) Expand all Loading... |
522 static const _TokenKind EOF = const _TokenKind(isEof: true); | 608 static const _TokenKind EOF = const _TokenKind(isEof: true); |
523 static const _TokenKind VALUE = const _TokenKind(); | 609 static const _TokenKind VALUE = const _TokenKind(); |
524 final bool isNewLine; | 610 final bool isNewLine; |
525 final bool isNewSegment; | 611 final bool isNewSegment; |
526 final bool isEof; | 612 final bool isEof; |
527 bool get isValue => !isNewLine && !isNewSegment && !isEof; | 613 bool get isValue => !isNewLine && !isNewSegment && !isEof; |
528 | 614 |
529 const _TokenKind( | 615 const _TokenKind( |
530 {this.isNewLine: false, this.isNewSegment: false, this.isEof: false}); | 616 {this.isNewLine: false, this.isNewSegment: false, this.isEof: false}); |
531 } | 617 } |
OLD | NEW |