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 a builder object useful for creating source maps programatically. | 5 /// Contains a builder object useful for creating source maps programatically. |
6 library source_maps.builder; | 6 library source_maps.builder; |
7 | 7 |
8 // TODO(sigmund): add a builder for multi-section mappings. | 8 // TODO(sigmund): add a builder for multi-section mappings. |
9 | 9 |
10 import 'dart:collection'; | |
11 import 'dart:convert'; | 10 import 'dart:convert'; |
12 | 11 |
| 12 import 'parser.dart'; |
13 import 'span.dart'; | 13 import 'span.dart'; |
14 import 'src/vlq.dart'; | |
15 | 14 |
16 /// Builds a source map given a set of mappings. | 15 /// Builds a source map given a set of mappings. |
17 class SourceMapBuilder { | 16 class SourceMapBuilder { |
18 | 17 |
19 final List<Entry> _entries = <Entry>[]; | 18 final List<Entry> _entries = <Entry>[]; |
20 | 19 |
21 /// Indices associated with file urls that will be part of the source map. We | |
22 /// use a linked hash-map so that `_urls.keys[_urls[u]] == u` | |
23 final Map<String, int> _urls = new LinkedHashMap<String, int>(); | |
24 | |
25 /// Indices associated with identifiers that will be part of the source map. | |
26 /// We use a linked hash-map so that `_names.keys[_names[n]] == n` | |
27 final Map<String, int> _names = new LinkedHashMap<String, int>(); | |
28 | |
29 /// Adds an entry mapping the [targetOffset] to [source]. | 20 /// Adds an entry mapping the [targetOffset] to [source]. |
30 void addFromOffset(Location source, | 21 void addFromOffset(Location source, |
31 SourceFile targetFile, int targetOffset, String identifier) { | 22 SourceFile targetFile, int targetOffset, String identifier) { |
32 if (targetFile == null) { | 23 if (targetFile == null) { |
33 throw new ArgumentError('targetFile cannot be null'); | 24 throw new ArgumentError('targetFile cannot be null'); |
34 } | 25 } |
35 _entries.add(new Entry(source, | 26 _entries.add(new Entry(source, |
36 new FileLocation(targetFile, targetOffset), identifier)); | 27 new FileLocation(targetFile, targetOffset), identifier)); |
37 } | 28 } |
38 | 29 |
39 /// Adds an entry mapping [target] to [source]. | 30 /// Adds an entry mapping [target] to [source]. |
40 void addSpan(Span source, Span target) { | 31 void addSpan(Span source, Span target) { |
41 var name = source.isIdentifier ? source.text : null; | 32 var name = source.isIdentifier ? source.text : null; |
42 _entries.add(new Entry(source.start, target.start, name)); | 33 _entries.add(new Entry(source.start, target.start, name)); |
43 } | 34 } |
44 | 35 |
45 void addLocation(Location source, Location target, String identifier) { | 36 void addLocation(Location source, Location target, String identifier) { |
46 _entries.add(new Entry(source, target, identifier)); | 37 _entries.add(new Entry(source, target, identifier)); |
47 } | 38 } |
48 | 39 |
49 /// Encodes all mappings added to this builder as a json map. | 40 /// Encodes all mappings added to this builder as a json map. |
50 Map build(String fileUrl) { | 41 Map build(String fileUrl) { |
51 var buff = new StringBuffer(); | 42 return new SingleMapping.fromEntries(this._entries, fileUrl).toJson(); |
52 var line = 0; | |
53 var column = 0; | |
54 var srcLine = 0; | |
55 var srcColumn = 0; | |
56 var srcUrlId = 0; | |
57 var srcNameId = 0; | |
58 var first = true; | |
59 | |
60 // The encoding needs to be sorted by the target offsets. | |
61 _entries.sort(); | |
62 for (var entry in _entries) { | |
63 int nextLine = entry.target.line; | |
64 if (nextLine > line) { | |
65 for (int i = line; i < nextLine; ++i) { | |
66 buff.write(';'); | |
67 } | |
68 line = nextLine; | |
69 column = 0; | |
70 first = true; | |
71 } | |
72 | |
73 if (!first) buff.write(','); | |
74 first = false; | |
75 column = _append(buff, column, entry.target.column); | |
76 | |
77 // Encoding can be just the column offset if there is no source | |
78 // information. | |
79 var source = entry.source; | |
80 if (source == null) continue; | |
81 var newUrlId = _indexOf(_urls, source.sourceUrl); | |
82 | |
83 srcUrlId = _append(buff, srcUrlId, newUrlId); | |
84 srcLine = _append(buff, srcLine, source.line); | |
85 srcColumn = _append(buff, srcColumn, source.column); | |
86 | |
87 if (entry.identifierName == null) continue; | |
88 srcNameId = _append(buff, srcNameId, | |
89 _indexOf(_names, entry.identifierName)); | |
90 } | |
91 | |
92 var result = { | |
93 'version': 3, | |
94 'sourceRoot': '', | |
95 'sources': _urls.keys.toList(), | |
96 'names' : _names.keys.toList(), | |
97 'mappings' : buff.toString() | |
98 }; | |
99 if (fileUrl != null) { | |
100 result['file'] = fileUrl; | |
101 } | |
102 return result; | |
103 } | 43 } |
104 | 44 |
105 /// Encodes all mappings added to this builder as a json string. | 45 /// Encodes all mappings added to this builder as a json string. |
106 String toJson(String fileUrl) => JSON.encode(build(fileUrl)); | 46 String toJson(String fileUrl) => JSON.encode(build(fileUrl)); |
107 | |
108 /// Get the index of [value] in [map], or create one if it doesn't exist. | |
109 int _indexOf(Map<String, int> map, String value) { | |
110 return map.putIfAbsent(value, () { | |
111 int index = map.length; | |
112 map[value] = index; | |
113 return index; | |
114 }); | |
115 } | |
116 | |
117 /// Appends to [buff] a VLQ encoding of [newValue] using the difference | |
118 /// between [oldValue] and [newValue] | |
119 static int _append(StringBuffer buff, int oldValue, int newValue) { | |
120 buff.writeAll(encodeVlq(newValue - oldValue)); | |
121 return newValue; | |
122 } | |
123 } | 47 } |
124 | 48 |
125 /// An entry in the source map builder. | 49 /// An entry in the source map builder. |
126 class Entry implements Comparable { | 50 class Entry implements Comparable { |
127 /// Span denoting the original location in the input source file | 51 /// Span denoting the original location in the input source file |
128 final Location source; | 52 final Location source; |
129 | 53 |
130 /// Span indicating the corresponding location in the target file. | 54 /// Span indicating the corresponding location in the target file. |
131 final Location target; | 55 final Location target; |
132 | 56 |
133 /// An identifier name, when this location is the start of an identifier. | 57 /// An identifier name, when this location is the start of an identifier. |
134 final String identifierName; | 58 final String identifierName; |
135 | 59 |
136 Entry(this.source, this.target, this.identifierName); | 60 Entry(this.source, this.target, this.identifierName); |
137 | 61 |
138 /// Implements [Comparable] to ensure that entries are ordered by their | 62 /// Implements [Comparable] to ensure that entries are ordered by their |
139 /// location in the target file. We sort primarily by the target offset | 63 /// location in the target file. We sort primarily by the target offset |
140 /// because source map files are encoded by printing each mapping in order as | 64 /// because source map files are encoded by printing each mapping in order as |
141 /// they appear in the target file. | 65 /// they appear in the target file. |
142 int compareTo(Entry other) { | 66 int compareTo(Entry other) { |
143 int res = target.compareTo(other.target); | 67 int res = target.compareTo(other.target); |
144 if (res != 0) return res; | 68 if (res != 0) return res; |
145 res = source.sourceUrl.compareTo(other.source.sourceUrl); | 69 res = source.sourceUrl.compareTo(other.source.sourceUrl); |
146 if (res != 0) return res; | 70 if (res != 0) return res; |
147 return source.compareTo(other.source); | 71 return source.compareTo(other.source); |
148 } | 72 } |
149 } | 73 } |
OLD | NEW |