OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
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. | |
4 | |
5 library source_map_builder; | |
6 | |
7 import 'util/util.dart'; | |
8 import 'scanner/scannerlib.dart' show Token; | |
9 import 'source_file.dart'; | |
10 import 'util/uri_extras.dart' show relativize; | |
11 | |
12 class SourceMapBuilder { | |
13 static const int VLQ_BASE_SHIFT = 5; | |
14 static const int VLQ_BASE_MASK = (1 << 5) - 1; | |
15 static const int VLQ_CONTINUATION_BIT = 1 << 5; | |
16 static const int VLQ_CONTINUATION_MASK = 1 << 5; | |
17 static const String BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn' | |
18 'opqrstuvwxyz0123456789+/'; | |
19 | |
20 final Uri uri; | |
21 final Uri fileUri; | |
22 | |
23 SourceFile targetFile; | |
24 List<SourceMapEntry> entries; | |
25 | |
26 Map<String, int> sourceUrlMap; | |
27 List<String> sourceUrlList; | |
28 Map<String, int> sourceNameMap; | |
29 List<String> sourceNameList; | |
30 | |
31 int previousTargetLine; | |
32 int previousTargetColumn; | |
33 int previousSourceUrlIndex; | |
34 int previousSourceLine; | |
35 int previousSourceColumn; | |
36 int previousSourceNameIndex; | |
37 bool firstEntryInLine; | |
38 | |
39 SourceMapBuilder(this.uri, this.fileUri, this.targetFile) { | |
40 entries = new List<SourceMapEntry>(); | |
41 | |
42 sourceUrlMap = new Map<String, int>(); | |
43 sourceUrlList = new List<String>(); | |
44 sourceNameMap = new Map<String, int>(); | |
45 sourceNameList = new List<String>(); | |
46 | |
47 previousTargetLine = 0; | |
48 previousTargetColumn = 0; | |
49 previousSourceUrlIndex = 0; | |
50 previousSourceLine = 0; | |
51 previousSourceColumn = 0; | |
52 previousSourceNameIndex = 0; | |
53 firstEntryInLine = true; | |
54 } | |
55 | |
56 resetPreviousSourceLocation() { | |
57 previousSourceUrlIndex = 0; | |
58 previousSourceLine = 0; | |
59 previousSourceColumn = 0; | |
60 previousSourceNameIndex = 0; | |
61 } | |
62 | |
63 updatePreviousSourceLocation(SourceFileLocation sourceLocation) { | |
64 previousSourceLine = sourceLocation.getLine(); | |
65 previousSourceColumn = sourceLocation.getColumn(); | |
66 String sourceUrl = sourceLocation.getSourceUrl(); | |
67 previousSourceUrlIndex = indexOf(sourceUrlList, sourceUrl, sourceUrlMap); | |
68 String sourceName = sourceLocation.getSourceName(); | |
69 if (sourceName != null) { | |
70 previousSourceNameIndex = | |
71 indexOf(sourceNameList, sourceName, sourceNameMap); | |
72 } | |
73 } | |
74 | |
75 bool sameAsPreviousLocation(SourceFileLocation sourceLocation) { | |
76 if (sourceLocation == null) { | |
77 return true; | |
78 } | |
79 int sourceUrlIndex = | |
80 indexOf(sourceUrlList, sourceLocation.getSourceUrl(), sourceUrlMap); | |
81 return | |
82 sourceUrlIndex == previousSourceUrlIndex && | |
83 sourceLocation.getLine() == previousSourceLine && | |
84 sourceLocation.getColumn() == previousSourceColumn; | |
85 } | |
86 | |
87 void addMapping(int targetOffset, SourceFileLocation sourceLocation) { | |
88 | |
89 bool sameLine(int position, otherPosition) { | |
90 return targetFile.getLine(position) == targetFile.getLine(otherPosition); | |
91 } | |
92 | |
93 if (!entries.isEmpty && sameLine(targetOffset, entries.last.targetOffset)) { | |
94 if (sameAsPreviousLocation(sourceLocation)) { | |
95 // The entry points to the same source location as the previous entry in | |
96 // the same line, hence it is not needed for the source map. | |
97 // | |
98 // TODO(zarah): Remove this check and make sure that [addMapping] is not | |
99 // called for this position. Instead, when consecutive lines in the | |
100 // generated code point to the same source location, record this and use | |
101 // it to generate the entries of the source map. | |
102 return; | |
103 } | |
104 } | |
105 | |
106 if (sourceLocation != null) { | |
107 updatePreviousSourceLocation(sourceLocation); | |
108 } | |
109 entries.add(new SourceMapEntry(sourceLocation, targetOffset)); | |
110 } | |
111 | |
112 void printStringListOn(List<String> strings, StringBuffer buffer) { | |
113 bool first = true; | |
114 buffer.write('['); | |
115 for (String string in strings) { | |
116 if (!first) buffer.write(','); | |
117 buffer.write('"'); | |
118 writeJsonEscapedCharsOn(string, buffer); | |
119 buffer.write('"'); | |
120 first = false; | |
121 } | |
122 buffer.write(']'); | |
123 } | |
124 | |
125 String build() { | |
126 resetPreviousSourceLocation(); | |
127 StringBuffer mappingsBuffer = new StringBuffer(); | |
128 entries.forEach((SourceMapEntry entry) => writeEntry(entry, targetFile, | |
129 mappingsBuffer)); | |
130 StringBuffer buffer = new StringBuffer(); | |
131 buffer.write('{\n'); | |
132 buffer.write(' "version": 3,\n'); | |
133 if (uri != null && fileUri != null) { | |
134 buffer.write(' "file": "${relativize(uri, fileUri, false)}",\n'); | |
135 } | |
136 buffer.write(' "sourceRoot": "",\n'); | |
137 buffer.write(' "sources": '); | |
138 if (uri != null) { | |
139 sourceUrlList = | |
140 sourceUrlList.map((url) => relativize(uri, Uri.parse(url), false)) | |
141 .toList(); | |
142 } | |
143 printStringListOn(sourceUrlList, buffer); | |
144 buffer.write(',\n'); | |
145 buffer.write(' "names": '); | |
146 printStringListOn(sourceNameList, buffer); | |
147 buffer.write(',\n'); | |
148 buffer.write(' "mappings": "'); | |
149 buffer.write(mappingsBuffer); | |
150 buffer.write('"\n}\n'); | |
151 return buffer.toString(); | |
152 } | |
153 | |
154 void writeEntry(SourceMapEntry entry, SourceFile targetFile, StringBuffer outp
ut) { | |
155 int targetLine = targetFile.getLine(entry.targetOffset); | |
156 int targetColumn = targetFile.getColumn(targetLine, entry.targetOffset); | |
157 | |
158 if (targetLine > previousTargetLine) { | |
159 for (int i = previousTargetLine; i < targetLine; ++i) { | |
160 output.write(';'); | |
161 } | |
162 previousTargetLine = targetLine; | |
163 previousTargetColumn = 0; | |
164 firstEntryInLine = true; | |
165 } | |
166 | |
167 if (!firstEntryInLine) { | |
168 output.write(','); | |
169 } | |
170 firstEntryInLine = false; | |
171 | |
172 encodeVLQ(output, targetColumn - previousTargetColumn); | |
173 previousTargetColumn = targetColumn; | |
174 | |
175 if (entry.sourceLocation == null) return; | |
176 | |
177 String sourceUrl = entry.sourceLocation.getSourceUrl(); | |
178 int sourceLine = entry.sourceLocation.getLine(); | |
179 int sourceColumn = entry.sourceLocation.getColumn(); | |
180 String sourceName = entry.sourceLocation.getSourceName(); | |
181 | |
182 int sourceUrlIndex = indexOf(sourceUrlList, sourceUrl, sourceUrlMap); | |
183 encodeVLQ(output, sourceUrlIndex - previousSourceUrlIndex); | |
184 encodeVLQ(output, sourceLine - previousSourceLine); | |
185 encodeVLQ(output, sourceColumn - previousSourceColumn); | |
186 | |
187 if (sourceName != null) { | |
188 int sourceNameIndex = indexOf(sourceNameList, sourceName, sourceNameMap); | |
189 encodeVLQ(output, sourceNameIndex - previousSourceNameIndex); | |
190 } | |
191 | |
192 // Update previous source location to ensure the next indices are relative | |
193 // to those if [entry.sourceLocation]. | |
194 updatePreviousSourceLocation(entry.sourceLocation); | |
195 } | |
196 | |
197 int indexOf(List<String> list, String value, Map<String, int> map) { | |
198 return map.putIfAbsent(value, () { | |
199 int index = list.length; | |
200 list.add(value); | |
201 return index; | |
202 }); | |
203 } | |
204 | |
205 static void encodeVLQ(StringBuffer output, int value) { | |
206 int signBit = 0; | |
207 if (value < 0) { | |
208 signBit = 1; | |
209 value = -value; | |
210 } | |
211 value = (value << 1) | signBit; | |
212 do { | |
213 int digit = value & VLQ_BASE_MASK; | |
214 value >>= VLQ_BASE_SHIFT; | |
215 if (value > 0) { | |
216 digit |= VLQ_CONTINUATION_BIT; | |
217 } | |
218 output.write(BASE64_DIGITS[digit]); | |
219 } while (value > 0); | |
220 } | |
221 } | |
222 | |
223 class SourceMapEntry { | |
224 SourceFileLocation sourceLocation; | |
225 int targetOffset; | |
226 | |
227 SourceMapEntry(this.sourceLocation, this.targetOffset); | |
228 } | |
229 | |
230 abstract class SourceFileLocation { | |
231 SourceFile sourceFile; | |
232 | |
233 SourceFileLocation(this.sourceFile) { | |
234 assert(isValid()); | |
235 } | |
236 | |
237 int line; | |
238 | |
239 int get offset; | |
240 | |
241 String getSourceUrl() => sourceFile.filename; | |
242 | |
243 int getLine() { | |
244 if (line == null) line = sourceFile.getLine(offset); | |
245 return line; | |
246 } | |
247 | |
248 int getColumn() => sourceFile.getColumn(getLine(), offset); | |
249 | |
250 String getSourceName(); | |
251 | |
252 bool isValid() => offset < sourceFile.length; | |
253 } | |
254 | |
255 class TokenSourceFileLocation extends SourceFileLocation { | |
256 final Token token; | |
257 final String name; | |
258 | |
259 TokenSourceFileLocation(SourceFile sourceFile, this.token, this.name) | |
260 : super(sourceFile); | |
261 | |
262 int get offset => token.charOffset; | |
263 | |
264 String getSourceName() { | |
265 return name; | |
266 } | |
267 } | |
OLD | NEW |