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