| 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 |