| 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:convert'; | 9 import 'dart:convert'; |
| 9 | 10 |
| 11 import 'builder.dart' as builder; |
| 10 import 'span.dart'; | 12 import 'span.dart'; |
| 11 import 'src/utils.dart'; | 13 import 'src/utils.dart'; |
| 12 import 'src/vlq.dart'; | 14 import 'src/vlq.dart'; |
| 13 | 15 |
| 14 /// Parses a source map directly from a json string. | 16 /// Parses a source map directly from a json string. |
| 15 // TODO(sigmund): evaluate whether other maps should have the json parsed, or | 17 // TODO(sigmund): evaluate whether other maps should have the json parsed, or |
| 16 // the string represenation. | 18 // the string represenation. |
| 17 Mapping parse(String jsonMap, {Map<String, Map> otherMaps}) => | 19 Mapping parse(String jsonMap, {Map<String, Map> otherMaps}) => |
| 18 parseJson(JSON.decode(jsonMap), otherMaps: otherMaps); | 20 parseJson(JSON.decode(jsonMap), otherMaps: otherMaps); |
| 19 | 21 |
| (...skipping 14 matching lines...) Expand all Loading... |
| 34 map.containsKey('names')) { | 36 map.containsKey('names')) { |
| 35 throw new FormatException('map containing "sections" ' | 37 throw new FormatException('map containing "sections" ' |
| 36 'cannot contain "mappings", "sources", or "names".'); | 38 'cannot contain "mappings", "sources", or "names".'); |
| 37 } | 39 } |
| 38 return new MultiSectionMapping.fromJson(map['sections'], otherMaps); | 40 return new MultiSectionMapping.fromJson(map['sections'], otherMaps); |
| 39 } | 41 } |
| 40 return new SingleMapping.fromJson(map); | 42 return new SingleMapping.fromJson(map); |
| 41 } | 43 } |
| 42 | 44 |
| 43 | 45 |
| 44 /// A mapping parsed our of a source map. | 46 /// A mapping parsed out of a source map. |
| 45 abstract class Mapping { | 47 abstract class Mapping { |
| 46 Span spanFor(int line, int column, {Map<String, SourceFile> files}); | 48 Span spanFor(int line, int column, {Map<String, SourceFile> files}); |
| 47 | 49 |
| 48 Span spanForLocation(Location loc, {Map<String, SourceFile> files}) { | 50 Span spanForLocation(Location loc, {Map<String, SourceFile> files}) { |
| 49 return spanFor(loc.line, loc.column, files: files); | 51 return spanFor(loc.line, loc.column, files: files); |
| 50 } | 52 } |
| 51 } | 53 } |
| 52 | 54 |
| 53 /// A meta-level map containing sections. | 55 /// A meta-level map containing sections. |
| 54 class MultiSectionMapping extends Mapping { | 56 class MultiSectionMapping extends Mapping { |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 124 ..write(':') | 126 ..write(':') |
| 125 ..write(_maps[i]) | 127 ..write(_maps[i]) |
| 126 ..write(')'); | 128 ..write(')'); |
| 127 } | 129 } |
| 128 buff.write(']'); | 130 buff.write(']'); |
| 129 return buff.toString(); | 131 return buff.toString(); |
| 130 } | 132 } |
| 131 } | 133 } |
| 132 | 134 |
| 133 /// A map containing direct source mappings. | 135 /// A map containing direct source mappings. |
| 134 // TODO(sigmund): integrate mapping and sourcemap builder? | |
| 135 class SingleMapping extends Mapping { | 136 class SingleMapping extends Mapping { |
| 136 /// Url of the target file. | 137 /// Url of the target file. |
| 137 final String targetUrl; | 138 final String targetUrl; |
| 138 | 139 |
| 139 /// Source urls used in the mapping, indexed by id. | 140 /// Source urls used in the mapping, indexed by id. |
| 140 final List<String> urls; | 141 final List<String> urls; |
| 141 | 142 |
| 142 /// Source names used in the mapping, indexed by id. | 143 /// Source names used in the mapping, indexed by id. |
| 143 final List<String> names; | 144 final List<String> names; |
| 144 | 145 |
| 145 /// Entries indicating the beginning of each span. | 146 /// Entries indicating the beginning of each span. |
| 146 final List<TargetLineEntry> lines = <TargetLineEntry>[]; | 147 final List<TargetLineEntry> lines; |
| 148 |
| 149 SingleMapping._internal(this.targetUrl, this.urls, this.names, this.lines); |
| 150 |
| 151 factory SingleMapping.fromEntries( |
| 152 Iterable<builder.Entry> entries, [String fileUrl]) { |
| 153 // The entries needs to be sorted by the target offsets. |
| 154 var sourceEntries = new List.from(entries)..sort(); |
| 155 var lines = <TargetLineEntry>[]; |
| 156 |
| 157 // Indices associated with file urls that will be part of the source map. We |
| 158 // use a linked hash-map so that `_urls.keys[_urls[u]] == u` |
| 159 var urls = new LinkedHashMap<String, int>(); |
| 160 |
| 161 // Indices associated with identifiers that will be part of the source map. |
| 162 // We use a linked hash-map so that `_names.keys[_names[n]] == n` |
| 163 var names = new LinkedHashMap<String, int>(); |
| 164 |
| 165 var lineNum; |
| 166 var targetEntries; |
| 167 for (var sourceEntry in sourceEntries) { |
| 168 if (lineNum == null || sourceEntry.target.line > lineNum) { |
| 169 lineNum = sourceEntry.target.line; |
| 170 targetEntries = <TargetEntry>[]; |
| 171 lines.add(new TargetLineEntry(lineNum, targetEntries)); |
| 172 } |
| 173 |
| 174 if (sourceEntry.source == null) { |
| 175 targetEntries.add(new TargetEntry(sourceEntry.target.column)); |
| 176 } else { |
| 177 var urlId = urls.putIfAbsent( |
| 178 sourceEntry.source.sourceUrl, () => urls.length); |
| 179 var srcNameId = sourceEntry.identifierName == null ? null : |
| 180 names.putIfAbsent(sourceEntry.identifierName, () => names.length); |
| 181 targetEntries.add(new TargetEntry( |
| 182 sourceEntry.target.column, |
| 183 urlId, |
| 184 sourceEntry.source.line, |
| 185 sourceEntry.source.column, |
| 186 srcNameId)); |
| 187 } |
| 188 } |
| 189 return new SingleMapping._internal( |
| 190 fileUrl, urls.keys.toList(), names.keys.toList(), lines); |
| 191 } |
| 147 | 192 |
| 148 SingleMapping.fromJson(Map map) | 193 SingleMapping.fromJson(Map map) |
| 149 : targetUrl = map['file'], | 194 : targetUrl = map['file'], |
| 150 // TODO(sigmund): add support for 'sourceRoot' | 195 // TODO(sigmund): add support for 'sourceRoot' |
| 151 urls = map['sources'], | 196 urls = map['sources'], |
| 152 names = map['names'] { | 197 names = map['names'], |
| 198 lines = <TargetLineEntry>[] { |
| 153 int line = 0; | 199 int line = 0; |
| 154 int column = 0; | 200 int column = 0; |
| 155 int srcUrlId = 0; | 201 int srcUrlId = 0; |
| 156 int srcLine = 0; | 202 int srcLine = 0; |
| 157 int srcColumn = 0; | 203 int srcColumn = 0; |
| 158 int srcNameId = 0; | 204 int srcNameId = 0; |
| 159 var tokenizer = new _MappingTokenizer(map['mappings']); | 205 var tokenizer = new _MappingTokenizer(map['mappings']); |
| 160 var entries = <TargetEntry>[]; | 206 var entries = <TargetEntry>[]; |
| 161 | 207 |
| 162 while (tokenizer.hasTokens) { | 208 while (tokenizer.hasTokens) { |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 208 srcNameId)); | 254 srcNameId)); |
| 209 } | 255 } |
| 210 } | 256 } |
| 211 if (tokenizer.nextKind.isNewSegment) tokenizer._consumeNewSegment(); | 257 if (tokenizer.nextKind.isNewSegment) tokenizer._consumeNewSegment(); |
| 212 } | 258 } |
| 213 if (!entries.isEmpty) { | 259 if (!entries.isEmpty) { |
| 214 lines.add(new TargetLineEntry(line, entries)); | 260 lines.add(new TargetLineEntry(line, entries)); |
| 215 } | 261 } |
| 216 } | 262 } |
| 217 | 263 |
| 264 /// Encodes the Mapping mappings as a json map. |
| 265 Map toJson() { |
| 266 var buff = new StringBuffer(); |
| 267 var line = 0; |
| 268 var column = 0; |
| 269 var srcLine = 0; |
| 270 var srcColumn = 0; |
| 271 var srcUrlId = 0; |
| 272 var srcNameId = 0; |
| 273 var first = true; |
| 274 |
| 275 for (var entry in lines) { |
| 276 int nextLine = entry.line; |
| 277 if (nextLine > line) { |
| 278 for (int i = line; i < nextLine; ++i) { |
| 279 buff.write(';'); |
| 280 } |
| 281 line = nextLine; |
| 282 column = 0; |
| 283 first = true; |
| 284 } |
| 285 |
| 286 for (var segment in entry.entries) { |
| 287 if (!first) buff.write(','); |
| 288 first = false; |
| 289 column = _append(buff, column, segment.column); |
| 290 |
| 291 // Encoding can be just the column offset if there is no source |
| 292 // information. |
| 293 var newUrlId = segment.sourceUrlId; |
| 294 if (newUrlId == null) continue; |
| 295 srcUrlId = _append(buff, srcUrlId, newUrlId); |
| 296 srcLine = _append(buff, srcLine, segment.sourceLine); |
| 297 srcColumn = _append(buff, srcColumn, segment.sourceColumn); |
| 298 |
| 299 if (segment.sourceNameId == null) continue; |
| 300 srcNameId = _append(buff, srcNameId, segment.sourceNameId); |
| 301 } |
| 302 } |
| 303 |
| 304 var result = { |
| 305 'version': 3, |
| 306 'sourceRoot': '', |
| 307 'sources': urls, |
| 308 'names' : names, |
| 309 'mappings' : buff.toString() |
| 310 }; |
| 311 if (targetUrl != null) { |
| 312 result['file'] = targetUrl; |
| 313 } |
| 314 return result; |
| 315 } |
| 316 |
| 317 /// Appends to [buff] a VLQ encoding of [newValue] using the difference |
| 318 /// between [oldValue] and [newValue] |
| 319 static int _append(StringBuffer buff, int oldValue, int newValue) { |
| 320 buff.writeAll(encodeVlq(newValue - oldValue)); |
| 321 return newValue; |
| 322 } |
| 323 |
| 218 _segmentError(int seen, int line) => new StateError( | 324 _segmentError(int seen, int line) => new StateError( |
| 219 'Invalid entry in sourcemap, expected 1, 4, or 5' | 325 'Invalid entry in sourcemap, expected 1, 4, or 5' |
| 220 ' values, but got $seen.\ntargeturl: $targetUrl, line: $line'); | 326 ' values, but got $seen.\ntargeturl: $targetUrl, line: $line'); |
| 221 | 327 |
| 222 /// Returns [TargetLineEntry] which includes the location in the target [line] | 328 /// Returns [TargetLineEntry] which includes the location in the target [line] |
| 223 /// number. In particular, the resulting entry is the last entry whose line | 329 /// number. In particular, the resulting entry is the last entry whose line |
| 224 /// number is lower or equal to [line]. | 330 /// number is lower or equal to [line]. |
| 225 TargetLineEntry _findLine(int line) { | 331 TargetLineEntry _findLine(int line) { |
| 226 int index = binarySearch(lines, (e) => e.line > line); | 332 int index = binarySearch(lines, (e) => e.line > line); |
| 227 return (index <= 0) ? null : lines[index - 1]; | 333 return (index <= 0) ? null : lines[index - 1]; |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 301 buff.write('\n'); | 407 buff.write('\n'); |
| 302 } | 408 } |
| 303 } | 409 } |
| 304 return buff.toString(); | 410 return buff.toString(); |
| 305 } | 411 } |
| 306 } | 412 } |
| 307 | 413 |
| 308 /// A line entry read from a source map. | 414 /// A line entry read from a source map. |
| 309 class TargetLineEntry { | 415 class TargetLineEntry { |
| 310 final int line; | 416 final int line; |
| 311 List<TargetEntry> entries = <TargetEntry>[]; | 417 List<TargetEntry> entries; |
| 312 TargetLineEntry(this.line, this.entries); | 418 TargetLineEntry(this.line, this.entries); |
| 313 | 419 |
| 314 String toString() => '$runtimeType: $line $entries'; | 420 String toString() => '$runtimeType: $line $entries'; |
| 315 } | 421 } |
| 316 | 422 |
| 317 /// A target segment entry read from a source map | 423 /// A target segment entry read from a source map |
| 318 class TargetEntry { | 424 class TargetEntry { |
| 319 final int column; | 425 final int column; |
| 320 final int sourceUrlId; | 426 final int sourceUrlId; |
| 321 final int sourceLine; | 427 final int sourceLine; |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 381 static const _TokenKind EOF = const _TokenKind(isEof: true); | 487 static const _TokenKind EOF = const _TokenKind(isEof: true); |
| 382 static const _TokenKind VALUE = const _TokenKind(); | 488 static const _TokenKind VALUE = const _TokenKind(); |
| 383 final bool isNewLine; | 489 final bool isNewLine; |
| 384 final bool isNewSegment; | 490 final bool isNewSegment; |
| 385 final bool isEof; | 491 final bool isEof; |
| 386 bool get isValue => !isNewLine && !isNewSegment && !isEof; | 492 bool get isValue => !isNewLine && !isNewSegment && !isEof; |
| 387 | 493 |
| 388 const _TokenKind( | 494 const _TokenKind( |
| 389 {this.isNewLine: false, this.isNewSegment: false, this.isEof: false}); | 495 {this.isNewLine: false, this.isNewSegment: false, this.isEof: false}); |
| 390 } | 496 } |
| OLD | NEW |