| 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:source_span/source_span.dart'; | 11 import 'package:source_span/source_span.dart'; |
| 12 | 12 |
| 13 import 'builder.dart' as builder; | 13 import 'builder.dart' as builder; |
| 14 import 'src/source_map_span.dart'; | 14 import 'src/source_map_span.dart'; |
| 15 import 'src/utils.dart'; | 15 import 'src/utils.dart'; |
| 16 import 'src/vlq.dart'; | 16 import 'src/vlq.dart'; |
| 17 | 17 |
| 18 /// Parses a source map directly from a json string. | 18 /// Parses a source map directly from a json string. |
| 19 /// |
| 20 /// [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 /// map will be interpreted as relative to this URL when generating spans. |
| 19 // TODO(sigmund): evaluate whether other maps should have the json parsed, or | 23 // TODO(sigmund): evaluate whether other maps should have the json parsed, or |
| 20 // the string represenation. | 24 // the string represenation. |
| 21 // TODO(tjblasi): Ignore the first line of [jsonMap] if the JSON safety string | 25 // TODO(tjblasi): Ignore the first line of [jsonMap] if the JSON safety string |
| 22 // `)]}'` begins the string representation of the map. | 26 // `)]}'` begins the string representation of the map. |
| 23 Mapping parse(String jsonMap, {Map<String, Map> otherMaps}) => | 27 Mapping parse(String jsonMap, {Map<String, Map> otherMaps, mapUrl}) => |
| 24 parseJson(JSON.decode(jsonMap), otherMaps: otherMaps); | 28 parseJson(JSON.decode(jsonMap), otherMaps: otherMaps, mapUrl: mapUrl); |
| 25 | 29 |
| 26 /// Parses a source map directly from a json map object. | 30 /// Parses a source map directly from a json map object. |
| 27 Mapping parseJson(Map map, {Map<String, Map> otherMaps}) { | 31 /// |
| 32 /// [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 |
| 34 /// map will be interpreted as relative to this URL when generating spans. |
| 35 Mapping parseJson(Map map, {Map<String, Map> otherMaps, mapUrl}) { |
| 28 if (map['version'] != 3) { | 36 if (map['version'] != 3) { |
| 29 throw new ArgumentError( | 37 throw new ArgumentError( |
| 30 'unexpected source map version: ${map["version"]}. ' | 38 'unexpected source map version: ${map["version"]}. ' |
| 31 'Only version 3 is supported.'); | 39 'Only version 3 is supported.'); |
| 32 } | 40 } |
| 33 | 41 |
| 34 if (map.containsKey('sections')) { | 42 if (map.containsKey('sections')) { |
| 35 if (map.containsKey('mappings') || map.containsKey('sources') || | 43 if (map.containsKey('mappings') || map.containsKey('sources') || |
| 36 map.containsKey('names')) { | 44 map.containsKey('names')) { |
| 37 throw new FormatException('map containing "sections" ' | 45 throw new FormatException('map containing "sections" ' |
| 38 'cannot contain "mappings", "sources", or "names".'); | 46 'cannot contain "mappings", "sources", or "names".'); |
| 39 } | 47 } |
| 40 return new MultiSectionMapping.fromJson(map['sections'], otherMaps); | 48 return new MultiSectionMapping.fromJson(map['sections'], otherMaps, |
| 49 mapUrl: mapUrl); |
| 41 } | 50 } |
| 42 return new SingleMapping.fromJson(map); | 51 return new SingleMapping.fromJson(map, mapUrl: mapUrl); |
| 43 } | 52 } |
| 44 | 53 |
| 45 | 54 |
| 46 /// A mapping parsed out of a source map. | 55 /// A mapping parsed out of a source map. |
| 47 abstract class Mapping { | 56 abstract class Mapping { |
| 48 /// Returns the span associated with [line] and [column]. | 57 /// Returns the span associated with [line] and [column]. |
| 49 SourceMapSpan spanFor(int line, int column, {Map<String, SourceFile> files}); | 58 SourceMapSpan spanFor(int line, int column, {Map<String, SourceFile> files}); |
| 50 | 59 |
| 51 /// Returns the span associated with [location]. | 60 /// Returns the span associated with [location]. |
| 52 SourceMapSpan spanForLocation(SourceLocation location, | 61 SourceMapSpan spanForLocation(SourceLocation location, |
| 53 {Map<String, SourceFile> files}) { | 62 {Map<String, SourceFile> files}) { |
| 54 return spanFor(location.line, location.column, files: files); | 63 return spanFor(location.line, location.column, files: files); |
| 55 } | 64 } |
| 56 } | 65 } |
| 57 | 66 |
| 58 /// A meta-level map containing sections. | 67 /// A meta-level map containing sections. |
| 59 class MultiSectionMapping extends Mapping { | 68 class MultiSectionMapping extends Mapping { |
| 60 /// For each section, the start line offset. | 69 /// For each section, the start line offset. |
| 61 final List<int> _lineStart = <int>[]; | 70 final List<int> _lineStart = <int>[]; |
| 62 | 71 |
| 63 /// For each section, the start column offset. | 72 /// For each section, the start column offset. |
| 64 final List<int> _columnStart = <int>[]; | 73 final List<int> _columnStart = <int>[]; |
| 65 | 74 |
| 66 /// For each section, the actual source map information, which is not adjusted | 75 /// For each section, the actual source map information, which is not adjusted |
| 67 /// for offsets. | 76 /// for offsets. |
| 68 final List<Mapping> _maps = <Mapping>[]; | 77 final List<Mapping> _maps = <Mapping>[]; |
| 69 | 78 |
| 70 /// Creates a section mapping from json. | 79 /// Creates a section mapping from json. |
| 71 MultiSectionMapping.fromJson(List sections, Map<String, Map> otherMaps) { | 80 MultiSectionMapping.fromJson(List sections, Map<String, Map> otherMaps, |
| 81 {mapUrl}) { |
| 72 for (var section in sections) { | 82 for (var section in sections) { |
| 73 var offset = section['offset']; | 83 var offset = section['offset']; |
| 74 if (offset == null) throw new FormatException('section missing offset'); | 84 if (offset == null) throw new FormatException('section missing offset'); |
| 75 | 85 |
| 76 var line = section['offset']['line']; | 86 var line = section['offset']['line']; |
| 77 if (line == null) throw new FormatException('offset missing line'); | 87 if (line == null) throw new FormatException('offset missing line'); |
| 78 | 88 |
| 79 var column = section['offset']['column']; | 89 var column = section['offset']['column']; |
| 80 if (column == null) throw new FormatException('offset missing column'); | 90 if (column == null) throw new FormatException('offset missing column'); |
| 81 | 91 |
| 82 _lineStart.add(line); | 92 _lineStart.add(line); |
| 83 _columnStart.add(column); | 93 _columnStart.add(column); |
| 84 | 94 |
| 85 var url = section['url']; | 95 var url = section['url']; |
| 86 var map = section['map']; | 96 var map = section['map']; |
| 87 | 97 |
| 88 if (url != null && map != null) { | 98 if (url != null && map != null) { |
| 89 throw new FormatException("section can't use both url and map entries"); | 99 throw new FormatException("section can't use both url and map entries"); |
| 90 } else if (url != null) { | 100 } else if (url != null) { |
| 91 if (otherMaps == null || otherMaps[url] == null) { | 101 if (otherMaps == null || otherMaps[url] == null) { |
| 92 throw new FormatException( | 102 throw new FormatException( |
| 93 'section contains refers to $url, but no map was ' | 103 'section contains refers to $url, but no map was ' |
| 94 'given for it. Make sure a map is passed in "otherMaps"'); | 104 'given for it. Make sure a map is passed in "otherMaps"'); |
| 95 } | 105 } |
| 96 _maps.add(parseJson(otherMaps[url], otherMaps: otherMaps)); | 106 _maps.add(parseJson(otherMaps[url], otherMaps: otherMaps, mapUrl: url)); |
| 97 } else if (map != null) { | 107 } else if (map != null) { |
| 98 _maps.add(parseJson(map, otherMaps: otherMaps)); | 108 _maps.add(parseJson(map, otherMaps: otherMaps, mapUrl: mapUrl)); |
| 99 } else { | 109 } else { |
| 100 throw new FormatException('section missing url or map'); | 110 throw new FormatException('section missing url or map'); |
| 101 } | 111 } |
| 102 } | 112 } |
| 103 if (_lineStart.length == 0) { | 113 if (_lineStart.length == 0) { |
| 104 throw new FormatException('expected at least one section'); | 114 throw new FormatException('expected at least one section'); |
| 105 } | 115 } |
| 106 } | 116 } |
| 107 | 117 |
| 108 int _indexFor(line, column) { | 118 int _indexFor(line, column) { |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 142 | 152 |
| 143 /// Source names used in the mapping, indexed by id. | 153 /// Source names used in the mapping, indexed by id. |
| 144 final List<String> names; | 154 final List<String> names; |
| 145 | 155 |
| 146 /// Entries indicating the beginning of each span. | 156 /// Entries indicating the beginning of each span. |
| 147 final List<TargetLineEntry> lines; | 157 final List<TargetLineEntry> lines; |
| 148 | 158 |
| 149 /// Url of the target file. | 159 /// Url of the target file. |
| 150 String targetUrl; | 160 String targetUrl; |
| 151 | 161 |
| 152 /// Source root appended to the start of all entries in [urls]. | 162 /// Source root prepended to all entries in [urls]. |
| 153 String sourceRoot; | 163 String sourceRoot; |
| 154 | 164 |
| 155 SingleMapping._(this.targetUrl, this.urls, this.names, this.lines); | 165 final Uri _mapUrl; |
| 166 |
| 167 SingleMapping._(this.targetUrl, this.urls, this.names, this.lines) |
| 168 : _mapUrl = null; |
| 156 | 169 |
| 157 factory SingleMapping.fromEntries( | 170 factory SingleMapping.fromEntries( |
| 158 Iterable<builder.Entry> entries, [String fileUrl]) { | 171 Iterable<builder.Entry> entries, [String fileUrl]) { |
| 159 // The entries needs to be sorted by the target offsets. | 172 // The entries needs to be sorted by the target offsets. |
| 160 var sourceEntries = new List.from(entries)..sort(); | 173 var sourceEntries = new List.from(entries)..sort(); |
| 161 var lines = <TargetLineEntry>[]; | 174 var lines = <TargetLineEntry>[]; |
| 162 | 175 |
| 163 // Indices associated with file urls that will be part of the source map. We | 176 // Indices associated with file urls that will be part of the source map. We |
| 164 // use a linked hash-map so that `_urls.keys[_urls[u]] == u` | 177 // use a linked hash-map so that `_urls.keys[_urls[u]] == u` |
| 165 var urls = new LinkedHashMap<String, int>(); | 178 var urls = new LinkedHashMap<String, int>(); |
| (...skipping 24 matching lines...) Expand all Loading... |
| 190 urlId, | 203 urlId, |
| 191 sourceEntry.source.line, | 204 sourceEntry.source.line, |
| 192 sourceEntry.source.column, | 205 sourceEntry.source.column, |
| 193 srcNameId)); | 206 srcNameId)); |
| 194 } | 207 } |
| 195 } | 208 } |
| 196 return new SingleMapping._( | 209 return new SingleMapping._( |
| 197 fileUrl, urls.keys.toList(), names.keys.toList(), lines); | 210 fileUrl, urls.keys.toList(), names.keys.toList(), lines); |
| 198 } | 211 } |
| 199 | 212 |
| 200 SingleMapping.fromJson(Map map) | 213 SingleMapping.fromJson(Map map, {mapUrl}) |
| 201 : targetUrl = map['file'], | 214 : targetUrl = map['file'], |
| 202 urls = map['sources'], | 215 urls = map['sources'], |
| 203 names = map['names'], | 216 names = map['names'], |
| 204 sourceRoot = map['sourceRoot'], | 217 sourceRoot = map['sourceRoot'], |
| 205 lines = <TargetLineEntry>[] { | 218 lines = <TargetLineEntry>[], |
| 219 _mapUrl = mapUrl is String ? Uri.parse(mapUrl) : mapUrl { |
| 206 int line = 0; | 220 int line = 0; |
| 207 int column = 0; | 221 int column = 0; |
| 208 int srcUrlId = 0; | 222 int srcUrlId = 0; |
| 209 int srcLine = 0; | 223 int srcLine = 0; |
| 210 int srcColumn = 0; | 224 int srcColumn = 0; |
| 211 int srcNameId = 0; | 225 int srcNameId = 0; |
| 212 var tokenizer = new _MappingTokenizer(map['mappings']); | 226 var tokenizer = new _MappingTokenizer(map['mappings']); |
| 213 var entries = <TargetEntry>[]; | 227 var entries = <TargetEntry>[]; |
| 214 | 228 |
| 215 while (tokenizer.hasTokens) { | 229 while (tokenizer.hasTokens) { |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 366 if (entry.sourceNameId != null) { | 380 if (entry.sourceNameId != null) { |
| 367 var text = names[entry.sourceNameId]; | 381 var text = names[entry.sourceNameId]; |
| 368 return new SourceMapFileSpan( | 382 return new SourceMapFileSpan( |
| 369 files[url].span(start, start + text.length), | 383 files[url].span(start, start + text.length), |
| 370 isIdentifier: true); | 384 isIdentifier: true); |
| 371 } else { | 385 } else { |
| 372 return new SourceMapFileSpan(files[url].location(start).pointSpan()); | 386 return new SourceMapFileSpan(files[url].location(start).pointSpan()); |
| 373 } | 387 } |
| 374 } else { | 388 } else { |
| 375 var start = new SourceLocation(0, | 389 var start = new SourceLocation(0, |
| 376 sourceUrl: url, line: entry.sourceLine, column: entry.sourceColumn); | 390 sourceUrl: _mapUrl == null ? url : _mapUrl.resolve(url), |
| 391 line: entry.sourceLine, |
| 392 column: entry.sourceColumn); |
| 393 |
| 377 // Offset and other context is not available. | 394 // Offset and other context is not available. |
| 378 if (entry.sourceNameId != null) { | 395 if (entry.sourceNameId != null) { |
| 379 return new SourceMapSpan.identifier(start, names[entry.sourceNameId]); | 396 return new SourceMapSpan.identifier(start, names[entry.sourceNameId]); |
| 380 } else { | 397 } else { |
| 381 return new SourceMapSpan(start, start, ''); | 398 return new SourceMapSpan(start, start, ''); |
| 382 } | 399 } |
| 383 } | 400 } |
| 384 } | 401 } |
| 385 | 402 |
| 386 String toString() { | 403 String toString() { |
| (...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 505 static const _TokenKind EOF = const _TokenKind(isEof: true); | 522 static const _TokenKind EOF = const _TokenKind(isEof: true); |
| 506 static const _TokenKind VALUE = const _TokenKind(); | 523 static const _TokenKind VALUE = const _TokenKind(); |
| 507 final bool isNewLine; | 524 final bool isNewLine; |
| 508 final bool isNewSegment; | 525 final bool isNewSegment; |
| 509 final bool isEof; | 526 final bool isEof; |
| 510 bool get isValue => !isNewLine && !isNewSegment && !isEof; | 527 bool get isValue => !isNewLine && !isNewSegment && !isEof; |
| 511 | 528 |
| 512 const _TokenKind( | 529 const _TokenKind( |
| 513 {this.isNewLine: false, this.isNewSegment: false, this.isEof: false}); | 530 {this.isNewLine: false, this.isNewSegment: false, this.isEof: false}); |
| 514 } | 531 } |
| OLD | NEW |