| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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 library dart2js.source_map_builder; | 5 library dart2js.source_map_builder; |
| 6 | 6 |
| 7 import '../util/util.dart'; | 7 import '../util/util.dart'; |
| 8 import '../util/uri_extras.dart' show relativize; | 8 import '../util/uri_extras.dart' show relativize; |
| 9 import 'line_column_provider.dart'; | 9 import 'line_column_provider.dart'; |
| 10 import 'source_information.dart' show SourceLocation; | 10 import 'source_information.dart' show SourceLocation; |
| 11 | 11 |
| 12 class SourceMapBuilder { | 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 | 13 |
| 20 /// The URI of the source map file. | 14 /// The URI of the source map file. |
| 21 final Uri sourceMapUri; | 15 final Uri sourceMapUri; |
| 22 /// The URI of the target language file. | 16 /// The URI of the target language file. |
| 23 final Uri targetFileUri; | 17 final Uri targetFileUri; |
| 24 | 18 |
| 25 LineColumnProvider lineColumnProvider; | 19 final LineColumnProvider lineColumnProvider; |
| 26 List<SourceMapEntry> entries; | 20 final List<SourceMapEntry> entries = new List<SourceMapEntry>(); |
| 27 | |
| 28 Map<Uri, int> sourceUriMap; | |
| 29 List<Uri> sourceUriList; | |
| 30 Map<String, int> sourceNameMap; | |
| 31 List<String> sourceNameList; | |
| 32 | |
| 33 int previousTargetLine; | |
| 34 int previousTargetColumn; | |
| 35 int previousSourceUriIndex; | |
| 36 int previousSourceLine; | |
| 37 int previousSourceColumn; | |
| 38 int previousSourceNameIndex; | |
| 39 bool firstEntryInLine; | |
| 40 | 21 |
| 41 SourceMapBuilder(this.sourceMapUri, | 22 SourceMapBuilder(this.sourceMapUri, |
| 42 this.targetFileUri, | 23 this.targetFileUri, |
| 43 this.lineColumnProvider) { | 24 this.lineColumnProvider); |
| 44 entries = new List<SourceMapEntry>(); | |
| 45 | |
| 46 sourceUriMap = new Map<Uri, int>(); | |
| 47 sourceUriList = new List<Uri>(); | |
| 48 sourceNameMap = new Map<String, int>(); | |
| 49 sourceNameList = new List<String>(); | |
| 50 | |
| 51 previousTargetLine = 0; | |
| 52 previousTargetColumn = 0; | |
| 53 previousSourceUriIndex = 0; | |
| 54 previousSourceLine = 0; | |
| 55 previousSourceColumn = 0; | |
| 56 previousSourceNameIndex = 0; | |
| 57 firstEntryInLine = true; | |
| 58 } | |
| 59 | |
| 60 resetPreviousSourceLocation() { | |
| 61 previousSourceUriIndex = 0; | |
| 62 previousSourceLine = 0; | |
| 63 previousSourceColumn = 0; | |
| 64 previousSourceNameIndex = 0; | |
| 65 } | |
| 66 | |
| 67 updatePreviousSourceLocation(SourceLocation sourceLocation) { | |
| 68 previousSourceLine = sourceLocation.line; | |
| 69 previousSourceColumn = sourceLocation.column; | |
| 70 Uri sourceUri = sourceLocation.sourceUri; | |
| 71 previousSourceUriIndex = indexOf(sourceUriList, sourceUri, sourceUriMap); | |
| 72 String sourceName = sourceLocation.sourceName; | |
| 73 if (sourceName != null) { | |
| 74 previousSourceNameIndex = | |
| 75 indexOf(sourceNameList, sourceName, sourceNameMap); | |
| 76 } | |
| 77 } | |
| 78 | |
| 79 bool sameAsPreviousLocation(SourceLocation sourceLocation) { | |
| 80 if (sourceLocation == null) { | |
| 81 return true; | |
| 82 } | |
| 83 int sourceUriIndex = | |
| 84 indexOf(sourceUriList, sourceLocation.sourceUri, sourceUriMap); | |
| 85 return | |
| 86 sourceUriIndex == previousSourceUriIndex && | |
| 87 sourceLocation.line == previousSourceLine && | |
| 88 sourceLocation.column == previousSourceColumn; | |
| 89 } | |
| 90 | 25 |
| 91 void addMapping(int targetOffset, SourceLocation sourceLocation) { | 26 void addMapping(int targetOffset, SourceLocation sourceLocation) { |
| 92 | |
| 93 bool sameLine(int position, otherPosition) { | |
| 94 return lineColumnProvider.getLine(position) == | |
| 95 lineColumnProvider.getLine(otherPosition); | |
| 96 } | |
| 97 | |
| 98 if (!entries.isEmpty && sameLine(targetOffset, entries.last.targetOffset)) { | |
| 99 if (sameAsPreviousLocation(sourceLocation)) { | |
| 100 // The entry points to the same source location as the previous entry in | |
| 101 // the same line, hence it is not needed for the source map. | |
| 102 // | |
| 103 // TODO(zarah): Remove this check and make sure that [addMapping] is not | |
| 104 // called for this position. Instead, when consecutive lines in the | |
| 105 // generated code point to the same source location, record this and use | |
| 106 // it to generate the entries of the source map. | |
| 107 return; | |
| 108 } | |
| 109 } | |
| 110 | |
| 111 if (sourceLocation != null) { | |
| 112 updatePreviousSourceLocation(sourceLocation); | |
| 113 } | |
| 114 entries.add(new SourceMapEntry(sourceLocation, targetOffset)); | 27 entries.add(new SourceMapEntry(sourceLocation, targetOffset)); |
| 115 } | 28 } |
| 116 | 29 |
| 117 void printStringListOn(List<String> strings, StringBuffer buffer) { | 30 void printStringListOn(Iterable<String> strings, StringBuffer buffer) { |
| 118 bool first = true; | 31 bool first = true; |
| 119 buffer.write('['); | 32 buffer.write('['); |
| 120 for (String string in strings) { | 33 for (String string in strings) { |
| 121 if (!first) buffer.write(','); | 34 if (!first) buffer.write(','); |
| 122 buffer.write('"'); | 35 buffer.write('"'); |
| 123 writeJsonEscapedCharsOn(string, buffer); | 36 writeJsonEscapedCharsOn(string, buffer); |
| 124 buffer.write('"'); | 37 buffer.write('"'); |
| 125 first = false; | 38 first = false; |
| 126 } | 39 } |
| 127 buffer.write(']'); | 40 buffer.write(']'); |
| 128 } | 41 } |
| 129 | 42 |
| 130 String build() { | 43 String build() { |
| 131 resetPreviousSourceLocation(); | 44 |
| 45 LineColumnMap<SourceMapEntry> lineColumnMap = |
| 46 new LineColumnMap<SourceMapEntry>(); |
| 47 Map<Uri, LineColumnMap<SourceMapEntry>> sourceLocationMap = |
| 48 <Uri, LineColumnMap<SourceMapEntry>>{}; |
| 49 entries.forEach((SourceMapEntry sourceMapEntry) { |
| 50 int line = lineColumnProvider.getLine(sourceMapEntry.targetOffset); |
| 51 int column = |
| 52 lineColumnProvider.getColumn(line, sourceMapEntry.targetOffset); |
| 53 lineColumnMap.add(line, column, sourceMapEntry); |
| 54 |
| 55 SourceLocation location = sourceMapEntry.sourceLocation; |
| 56 if (location != null) { |
| 57 LineColumnMap<SourceMapEntry> sourceLineColumnMap = |
| 58 sourceLocationMap.putIfAbsent(location.sourceUri, |
| 59 () => new LineColumnMap<SourceMapEntry>()); |
| 60 sourceLineColumnMap.add(location.line, location.column, sourceMapEntry); |
| 61 } |
| 62 }); |
| 63 |
| 64 return _build(lineColumnMap); |
| 65 } |
| 66 |
| 67 String _build(LineColumnMap<SourceMapEntry> lineColumnMap) { |
| 68 IndexMap<Uri> uriMap = new IndexMap<Uri>(); |
| 69 IndexMap<String> nameMap = new IndexMap<String>(); |
| 70 |
| 71 lineColumnMap.forEachElement((SourceMapEntry entry) { |
| 72 SourceLocation sourceLocation = entry.sourceLocation; |
| 73 if (sourceLocation != null) { |
| 74 uriMap.register(sourceLocation.sourceUri); |
| 75 if (sourceLocation.sourceName != null) { |
| 76 nameMap.register(sourceLocation.sourceName); |
| 77 } |
| 78 } |
| 79 }); |
| 80 |
| 132 StringBuffer mappingsBuffer = new StringBuffer(); | 81 StringBuffer mappingsBuffer = new StringBuffer(); |
| 133 entries.forEach((SourceMapEntry entry) { | 82 writeEntries(lineColumnMap, uriMap, nameMap, mappingsBuffer); |
| 134 writeEntry(entry, mappingsBuffer); | 83 |
| 135 }); | |
| 136 StringBuffer buffer = new StringBuffer(); | 84 StringBuffer buffer = new StringBuffer(); |
| 137 buffer.write('{\n'); | 85 buffer.write('{\n'); |
| 138 buffer.write(' "version": 3,\n'); | 86 buffer.write(' "version": 3,\n'); |
| 139 if (sourceMapUri != null && targetFileUri != null) { | 87 if (sourceMapUri != null && targetFileUri != null) { |
| 140 buffer.write( | 88 buffer.write( |
| 141 ' "file": "${relativize(sourceMapUri, targetFileUri, false)}",\n'); | 89 ' "file": "${relativize(sourceMapUri, targetFileUri, false)}",\n'); |
| 142 } | 90 } |
| 143 buffer.write(' "sourceRoot": "",\n'); | 91 buffer.write(' "sourceRoot": "",\n'); |
| 144 buffer.write(' "sources": '); | 92 buffer.write(' "sources": '); |
| 145 List<String> relativeSourceUriList = <String>[]; | 93 Iterable<String> relativeSourceUriList = const <String>[]; |
| 146 if (sourceMapUri != null) { | 94 if (sourceMapUri != null) { |
| 147 relativeSourceUriList = sourceUriList | 95 relativeSourceUriList = uriMap.elements |
| 148 .map((u) => relativize(sourceMapUri, u, false)) | 96 .map((u) => relativize(sourceMapUri, u, false)); |
| 149 .toList(); | |
| 150 } | 97 } |
| 151 printStringListOn(relativeSourceUriList, buffer); | 98 printStringListOn(relativeSourceUriList, buffer); |
| 152 buffer.write(',\n'); | 99 buffer.write(',\n'); |
| 153 buffer.write(' "names": '); | 100 buffer.write(' "names": '); |
| 154 printStringListOn(sourceNameList, buffer); | 101 printStringListOn(nameMap.elements, buffer); |
| 155 buffer.write(',\n'); | 102 buffer.write(',\n'); |
| 156 buffer.write(' "mappings": "'); | 103 buffer.write(' "mappings": "'); |
| 157 buffer.write(mappingsBuffer); | 104 buffer.write(mappingsBuffer); |
| 158 buffer.write('"\n}\n'); | 105 buffer.write('"\n}\n'); |
| 159 return buffer.toString(); | 106 return buffer.toString(); |
| 160 } | 107 } |
| 161 | 108 |
| 162 void writeEntry(SourceMapEntry entry, StringBuffer output) { | 109 void writeEntries(LineColumnMap<SourceMapEntry> entries, |
| 163 int targetLine = lineColumnProvider.getLine(entry.targetOffset); | 110 IndexMap<Uri> uriMap, |
| 164 int targetColumn = | 111 IndexMap<String> nameMap, |
| 165 lineColumnProvider.getColumn(targetLine, entry.targetOffset); | 112 StringBuffer output) { |
| 113 SourceLocation previousSourceLocation; |
| 114 int previousTargetLine = 0; |
| 115 DeltaEncoder targetColumnEncoder = new DeltaEncoder(); |
| 116 bool firstEntryInLine = true; |
| 117 DeltaEncoder sourceUriIndexEncoder = new DeltaEncoder(); |
| 118 DeltaEncoder sourceLineEncoder = new DeltaEncoder(); |
| 119 DeltaEncoder sourceColumnEncoder = new DeltaEncoder(); |
| 120 DeltaEncoder sourceNameIndexEncoder = new DeltaEncoder(); |
| 166 | 121 |
| 167 if (targetLine > previousTargetLine) { | 122 entries.forEach((int targetLine, |
| 168 for (int i = previousTargetLine; i < targetLine; ++i) { | 123 int targetColumn, |
| 169 output.write(';'); | 124 SourceMapEntry entry) { |
| 125 SourceLocation sourceLocation = entry.sourceLocation; |
| 126 if (sourceLocation == previousSourceLocation) { |
| 127 return; |
| 170 } | 128 } |
| 171 previousTargetLine = targetLine; | |
| 172 previousTargetColumn = 0; | |
| 173 firstEntryInLine = true; | |
| 174 } | |
| 175 | 129 |
| 176 if (!firstEntryInLine) { | 130 if (targetLine > previousTargetLine) { |
| 177 output.write(','); | 131 for (int i = previousTargetLine; i < targetLine; ++i) { |
| 178 } | 132 output.write(';'); |
| 179 firstEntryInLine = false; | 133 } |
| 134 previousTargetLine = targetLine; |
| 135 previousSourceLocation = null; |
| 136 targetColumnEncoder.reset(); |
| 137 firstEntryInLine = true; |
| 138 } |
| 180 | 139 |
| 181 encodeVLQ(output, targetColumn - previousTargetColumn); | 140 if (!firstEntryInLine) { |
| 182 previousTargetColumn = targetColumn; | 141 output.write(','); |
| 142 } |
| 143 firstEntryInLine = false; |
| 183 | 144 |
| 184 if (entry.sourceLocation == null) return; | 145 targetColumnEncoder.encode(output, targetColumn); |
| 185 | 146 |
| 186 Uri sourceUri = entry.sourceLocation.sourceUri; | 147 if (sourceLocation == null) { |
| 187 int sourceLine = entry.sourceLocation.line; | 148 return; |
| 188 int sourceColumn = entry.sourceLocation.column; | 149 } |
| 189 String sourceName = entry.sourceLocation.sourceName; | |
| 190 | 150 |
| 191 int sourceUriIndex = indexOf(sourceUriList, sourceUri, sourceUriMap); | 151 Uri sourceUri = sourceLocation.sourceUri; |
| 192 encodeVLQ(output, sourceUriIndex - previousSourceUriIndex); | 152 sourceUriIndexEncoder.encode(output, uriMap[sourceUri]); |
| 193 encodeVLQ(output, sourceLine - previousSourceLine); | 153 sourceLineEncoder.encode(output, sourceLocation.line); |
| 194 encodeVLQ(output, sourceColumn - previousSourceColumn); | 154 sourceColumnEncoder.encode(output, sourceLocation.column); |
| 195 | 155 |
| 196 if (sourceName != null) { | 156 String sourceName = sourceLocation.sourceName; |
| 197 int sourceNameIndex = indexOf(sourceNameList, sourceName, sourceNameMap); | 157 if (sourceName != null) { |
| 198 encodeVLQ(output, sourceNameIndex - previousSourceNameIndex); | 158 sourceNameIndexEncoder.encode(output, nameMap[sourceName]); |
| 199 } | 159 } |
| 200 | 160 |
| 201 // Update previous source location to ensure the next indices are relative | 161 previousSourceLocation = sourceLocation; |
| 202 // to those if [entry.sourceLocation]. | 162 }); |
| 203 updatePreviousSourceLocation(entry.sourceLocation); | 163 } |
| 164 } |
| 165 |
| 166 /// Encoder for value deltas in VLQ format. |
| 167 class DeltaEncoder { |
| 168 /// The last emitted value of the encoder. |
| 169 int _value = 0; |
| 170 |
| 171 /// Reset the encoder to its initial state. |
| 172 void reset() { |
| 173 _value = 0; |
| 204 } | 174 } |
| 205 | 175 |
| 206 int indexOf(List list, value, Map<dynamic, int> map) { | 176 /// Writes the VLQ of delta between [value] and the last emitted value into |
| 207 return map.putIfAbsent(value, () { | 177 /// [output] and updates the last emitted value of the encoder. |
| 208 int index = list.length; | 178 void encode(StringBuffer output, int value) { |
| 209 list.add(value); | 179 _value = encodeVLQ(output, value, _value); |
| 210 return index; | |
| 211 }); | |
| 212 } | 180 } |
| 213 | 181 |
| 214 static void encodeVLQ(StringBuffer output, int value) { | 182 static const int VLQ_BASE_SHIFT = 5; |
| 183 static const int VLQ_BASE_MASK = (1 << 5) - 1; |
| 184 static const int VLQ_CONTINUATION_BIT = 1 << 5; |
| 185 static const int VLQ_CONTINUATION_MASK = 1 << 5; |
| 186 static const String BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn' |
| 187 'opqrstuvwxyz0123456789+/'; |
| 188 |
| 189 /// Writes the VLQ of delta between [value] and [offset] into [output] and |
| 190 /// return [value]. |
| 191 static int encodeVLQ(StringBuffer output, int value, int offset) { |
| 192 int delta = value - offset; |
| 215 int signBit = 0; | 193 int signBit = 0; |
| 216 if (value < 0) { | 194 if (delta < 0) { |
| 217 signBit = 1; | 195 signBit = 1; |
| 218 value = -value; | 196 delta = -delta; |
| 219 } | 197 } |
| 220 value = (value << 1) | signBit; | 198 delta = (delta << 1) | signBit; |
| 221 do { | 199 do { |
| 222 int digit = value & VLQ_BASE_MASK; | 200 int digit = delta & VLQ_BASE_MASK; |
| 223 value >>= VLQ_BASE_SHIFT; | 201 delta >>= VLQ_BASE_SHIFT; |
| 224 if (value > 0) { | 202 if (delta > 0) { |
| 225 digit |= VLQ_CONTINUATION_BIT; | 203 digit |= VLQ_CONTINUATION_BIT; |
| 226 } | 204 } |
| 227 output.write(BASE64_DIGITS[digit]); | 205 output.write(BASE64_DIGITS[digit]); |
| 228 } while (value > 0); | 206 } while (delta > 0); |
| 207 return value; |
| 229 } | 208 } |
| 230 } | 209 } |
| 231 | 210 |
| 232 class SourceMapEntry { | 211 class SourceMapEntry { |
| 233 SourceLocation sourceLocation; | 212 SourceLocation sourceLocation; |
| 234 int targetOffset; | 213 int targetOffset; |
| 235 | 214 |
| 236 SourceMapEntry(this.sourceLocation, this.targetOffset); | 215 SourceMapEntry(this.sourceLocation, this.targetOffset); |
| 237 } | 216 } |
| 217 |
| 218 /// Map from line/column pairs to lists of [T] elements. |
| 219 class LineColumnMap<T> { |
| 220 Map<int, Map<int, List<T>>> _map = <int, Map<int, List<T>>>{}; |
| 221 |
| 222 /// Returns the list of elements associated with ([line],[column]). |
| 223 List<T> _getList(int line, int column) { |
| 224 Map<int, List<T>> lineMap = _map.putIfAbsent(line, () => <int, List<T>>{}); |
| 225 return lineMap.putIfAbsent(column, () => <T>[]); |
| 226 } |
| 227 |
| 228 /// Adds [element] to the end of the list of elements associated with |
| 229 /// ([line],[column]). |
| 230 void add(int line, int column, T element) { |
| 231 _getList(line, column).add(element); |
| 232 } |
| 233 |
| 234 /// Adds [element] to the beginning of the list of elements associated with |
| 235 /// ([line],[column]). |
| 236 void addFirst(int line, int column, T element) { |
| 237 _getList(line, column).insert(0, element); |
| 238 } |
| 239 |
| 240 /// Calls [f] with the line number for each line with associated elements. |
| 241 /// |
| 242 /// [f] is called in increasing line order. |
| 243 void forEachLine(f(int line)) { |
| 244 List<int> lines = _map.keys.toList()..sort(); |
| 245 lines.forEach(f); |
| 246 } |
| 247 |
| 248 /// Returns the elements for the first the column in [line] that has |
| 249 /// associated elements. |
| 250 List<T> getFirstElementsInLine(int line) { |
| 251 Map<int, List<T>> lineMap = _map[line]; |
| 252 if (lineMap == null) return null; |
| 253 List<int> columns = lineMap.keys.toList()..sort(); |
| 254 return lineMap[columns.first]; |
| 255 } |
| 256 |
| 257 /// Calls [f] for each column with associated elements in [line]. |
| 258 /// |
| 259 /// [f] is called in increasing column order. |
| 260 void forEachColumn(int line, f(int column, List<T> elements)) { |
| 261 Map<int, List<T>> lineMap = _map[line]; |
| 262 if (lineMap != null) { |
| 263 List<int> columns = lineMap.keys.toList()..sort(); |
| 264 columns.forEach((int column) { |
| 265 f(column, lineMap[column]); |
| 266 }); |
| 267 } |
| 268 } |
| 269 |
| 270 /// Calls [f] for each line/column/element triplet in the map. |
| 271 /// |
| 272 /// [f] is called in increasing line, column, element order. |
| 273 void forEach(f(int line, int column, T element)) { |
| 274 List<int> lines = _map.keys.toList()..sort(); |
| 275 for (int line in lines) { |
| 276 Map<int, List<T>> lineMap = _map[line]; |
| 277 List<int> columns = lineMap.keys.toList()..sort(); |
| 278 for (int column in columns) { |
| 279 lineMap[column].forEach((e) => f(line, column, e)); |
| 280 } |
| 281 } |
| 282 } |
| 283 |
| 284 /// Calls [f] for each element associated in the map. |
| 285 /// |
| 286 /// [f] is called in increasing line, column, element order. |
| 287 void forEachElement(f(T element)) { |
| 288 forEach((line, column, element) => f(element)); |
| 289 } |
| 290 } |
| 291 |
| 292 /// Map from [T] elements to assigned indices. |
| 293 class IndexMap<T> { |
| 294 Map<T, int> map = <T, int>{}; |
| 295 |
| 296 /// Register [element] and returns its index. |
| 297 int register(T element) { |
| 298 return map.putIfAbsent(element, () => map.length); |
| 299 } |
| 300 |
| 301 /// Returns the index of [element]. |
| 302 int operator [](T element) => map[element]; |
| 303 |
| 304 /// Returns the indexed elements. |
| 305 Iterable<T> get elements => map.keys; |
| 306 } |
| OLD | NEW |