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 |