| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library dart2js.source_map_builder; |
| |
| import '../util/uri_extras.dart' show relativize; |
| import '../util/util.dart'; |
| import 'line_column_provider.dart'; |
| import 'source_information.dart' show SourceLocation; |
| |
| class SourceMapBuilder { |
| /// The URI of the source map file. |
| final Uri sourceMapUri; |
| |
| /// The URI of the target language file. |
| final Uri targetFileUri; |
| |
| final LineColumnProvider lineColumnProvider; |
| final List<SourceMapEntry> entries = new List<SourceMapEntry>(); |
| |
| SourceMapBuilder( |
| this.sourceMapUri, this.targetFileUri, this.lineColumnProvider); |
| |
| void addMapping(int targetOffset, SourceLocation sourceLocation) { |
| entries.add(new SourceMapEntry(sourceLocation, targetOffset)); |
| } |
| |
| void printStringListOn(Iterable<String> strings, StringBuffer buffer) { |
| bool first = true; |
| buffer.write('['); |
| for (String string in strings) { |
| if (!first) buffer.write(','); |
| buffer.write('"'); |
| writeJsonEscapedCharsOn(string, buffer); |
| buffer.write('"'); |
| first = false; |
| } |
| buffer.write(']'); |
| } |
| |
| String build() { |
| LineColumnMap<SourceMapEntry> lineColumnMap = |
| new LineColumnMap<SourceMapEntry>(); |
| Map<Uri, LineColumnMap<SourceMapEntry>> sourceLocationMap = |
| <Uri, LineColumnMap<SourceMapEntry>>{}; |
| entries.forEach((SourceMapEntry sourceMapEntry) { |
| int line = lineColumnProvider.getLine(sourceMapEntry.targetOffset); |
| int column = |
| lineColumnProvider.getColumn(line, sourceMapEntry.targetOffset); |
| lineColumnMap.add(line, column, sourceMapEntry); |
| |
| SourceLocation location = sourceMapEntry.sourceLocation; |
| if (location != null) { |
| LineColumnMap<SourceMapEntry> sourceLineColumnMap = |
| sourceLocationMap.putIfAbsent( |
| location.sourceUri, () => new LineColumnMap<SourceMapEntry>()); |
| sourceLineColumnMap.add(location.line, location.column, sourceMapEntry); |
| } |
| }); |
| |
| return _build(lineColumnMap); |
| } |
| |
| String _build(LineColumnMap<SourceMapEntry> lineColumnMap) { |
| IndexMap<Uri> uriMap = new IndexMap<Uri>(); |
| IndexMap<String> nameMap = new IndexMap<String>(); |
| |
| lineColumnMap.forEachElement((SourceMapEntry entry) { |
| SourceLocation sourceLocation = entry.sourceLocation; |
| if (sourceLocation != null) { |
| uriMap.register(sourceLocation.sourceUri); |
| if (sourceLocation.sourceName != null) { |
| nameMap.register(sourceLocation.sourceName); |
| } |
| } |
| }); |
| |
| StringBuffer mappingsBuffer = new StringBuffer(); |
| writeEntries(lineColumnMap, uriMap, nameMap, mappingsBuffer); |
| |
| StringBuffer buffer = new StringBuffer(); |
| buffer.write('{\n'); |
| buffer.write(' "version": 3,\n'); |
| if (sourceMapUri != null && targetFileUri != null) { |
| buffer.write( |
| ' "file": "${relativize(sourceMapUri, targetFileUri, false)}",\n'); |
| } |
| buffer.write(' "sourceRoot": "",\n'); |
| buffer.write(' "sources": '); |
| Iterable<String> relativeSourceUriList = const <String>[]; |
| if (sourceMapUri != null) { |
| relativeSourceUriList = |
| uriMap.elements.map((u) => relativize(sourceMapUri, u, false)); |
| } |
| printStringListOn(relativeSourceUriList, buffer); |
| buffer.write(',\n'); |
| buffer.write(' "names": '); |
| printStringListOn(nameMap.elements, buffer); |
| buffer.write(',\n'); |
| buffer.write(' "mappings": "'); |
| buffer.write(mappingsBuffer); |
| buffer.write('"\n}\n'); |
| return buffer.toString(); |
| } |
| |
| void writeEntries(LineColumnMap<SourceMapEntry> entries, IndexMap<Uri> uriMap, |
| IndexMap<String> nameMap, StringBuffer output) { |
| SourceLocation previousSourceLocation; |
| int previousTargetLine = 0; |
| DeltaEncoder targetColumnEncoder = new DeltaEncoder(); |
| bool firstEntryInLine = true; |
| DeltaEncoder sourceUriIndexEncoder = new DeltaEncoder(); |
| DeltaEncoder sourceLineEncoder = new DeltaEncoder(); |
| DeltaEncoder sourceColumnEncoder = new DeltaEncoder(); |
| DeltaEncoder sourceNameIndexEncoder = new DeltaEncoder(); |
| |
| entries.forEach((int targetLine, int targetColumn, SourceMapEntry entry) { |
| SourceLocation sourceLocation = entry.sourceLocation; |
| if (sourceLocation == previousSourceLocation) { |
| return; |
| } |
| |
| if (targetLine > previousTargetLine) { |
| for (int i = previousTargetLine; i < targetLine; ++i) { |
| output.write(';'); |
| } |
| previousTargetLine = targetLine; |
| previousSourceLocation = null; |
| targetColumnEncoder.reset(); |
| firstEntryInLine = true; |
| } |
| |
| if (!firstEntryInLine) { |
| output.write(','); |
| } |
| firstEntryInLine = false; |
| |
| targetColumnEncoder.encode(output, targetColumn); |
| |
| if (sourceLocation == null) { |
| return; |
| } |
| |
| Uri sourceUri = sourceLocation.sourceUri; |
| sourceUriIndexEncoder.encode(output, uriMap[sourceUri]); |
| sourceLineEncoder.encode(output, sourceLocation.line); |
| sourceColumnEncoder.encode(output, sourceLocation.column); |
| |
| String sourceName = sourceLocation.sourceName; |
| if (sourceName != null) { |
| sourceNameIndexEncoder.encode(output, nameMap[sourceName]); |
| } |
| |
| previousSourceLocation = sourceLocation; |
| }); |
| } |
| } |
| |
| /// Encoder for value deltas in VLQ format. |
| class DeltaEncoder { |
| /// The last emitted value of the encoder. |
| int _value = 0; |
| |
| /// Reset the encoder to its initial state. |
| void reset() { |
| _value = 0; |
| } |
| |
| /// Writes the VLQ of delta between [value] and the last emitted value into |
| /// [output] and updates the last emitted value of the encoder. |
| void encode(StringBuffer output, int value) { |
| _value = encodeVLQ(output, value, _value); |
| } |
| |
| static const int VLQ_BASE_SHIFT = 5; |
| static const int VLQ_BASE_MASK = (1 << 5) - 1; |
| static const int VLQ_CONTINUATION_BIT = 1 << 5; |
| static const int VLQ_CONTINUATION_MASK = 1 << 5; |
| static const String BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn' |
| 'opqrstuvwxyz0123456789+/'; |
| |
| /// Writes the VLQ of delta between [value] and [offset] into [output] and |
| /// return [value]. |
| static int encodeVLQ(StringBuffer output, int value, int offset) { |
| int delta = value - offset; |
| int signBit = 0; |
| if (delta < 0) { |
| signBit = 1; |
| delta = -delta; |
| } |
| delta = (delta << 1) | signBit; |
| do { |
| int digit = delta & VLQ_BASE_MASK; |
| delta >>= VLQ_BASE_SHIFT; |
| if (delta > 0) { |
| digit |= VLQ_CONTINUATION_BIT; |
| } |
| output.write(BASE64_DIGITS[digit]); |
| } while (delta > 0); |
| return value; |
| } |
| } |
| |
| class SourceMapEntry { |
| SourceLocation sourceLocation; |
| int targetOffset; |
| |
| SourceMapEntry(this.sourceLocation, this.targetOffset); |
| } |
| |
| /// Map from line/column pairs to lists of [T] elements. |
| class LineColumnMap<T> { |
| Map<int, Map<int, List<T>>> _map = <int, Map<int, List<T>>>{}; |
| |
| /// Returns the list of elements associated with ([line],[column]). |
| List<T> _getList(int line, int column) { |
| Map<int, List<T>> lineMap = _map.putIfAbsent(line, () => <int, List<T>>{}); |
| return lineMap.putIfAbsent(column, () => <T>[]); |
| } |
| |
| /// Adds [element] to the end of the list of elements associated with |
| /// ([line],[column]). |
| void add(int line, int column, T element) { |
| _getList(line, column).add(element); |
| } |
| |
| /// Adds [element] to the beginning of the list of elements associated with |
| /// ([line],[column]). |
| void addFirst(int line, int column, T element) { |
| _getList(line, column).insert(0, element); |
| } |
| |
| /// Calls [f] with the line number for each line with associated elements. |
| /// |
| /// [f] is called in increasing line order. |
| void forEachLine(f(int line)) { |
| List<int> lines = _map.keys.toList()..sort(); |
| lines.forEach(f); |
| } |
| |
| /// Returns the elements for the first the column in [line] that has |
| /// associated elements. |
| List<T> getFirstElementsInLine(int line) { |
| Map<int, List<T>> lineMap = _map[line]; |
| if (lineMap == null) return null; |
| List<int> columns = lineMap.keys.toList()..sort(); |
| return lineMap[columns.first]; |
| } |
| |
| /// Calls [f] for each column with associated elements in [line]. |
| /// |
| /// [f] is called in increasing column order. |
| void forEachColumn(int line, f(int column, List<T> elements)) { |
| Map<int, List<T>> lineMap = _map[line]; |
| if (lineMap != null) { |
| List<int> columns = lineMap.keys.toList()..sort(); |
| columns.forEach((int column) { |
| f(column, lineMap[column]); |
| }); |
| } |
| } |
| |
| /// Calls [f] for each line/column/element triplet in the map. |
| /// |
| /// [f] is called in increasing line, column, element order. |
| void forEach(f(int line, int column, T element)) { |
| List<int> lines = _map.keys.toList()..sort(); |
| for (int line in lines) { |
| Map<int, List<T>> lineMap = _map[line]; |
| List<int> columns = lineMap.keys.toList()..sort(); |
| for (int column in columns) { |
| lineMap[column].forEach((e) => f(line, column, e)); |
| } |
| } |
| } |
| |
| /// Calls [f] for each element associated in the map. |
| /// |
| /// [f] is called in increasing line, column, element order. |
| void forEachElement(f(T element)) { |
| forEach((line, column, element) => f(element)); |
| } |
| } |
| |
| /// Map from [T] elements to assigned indices. |
| class IndexMap<T> { |
| Map<T, int> map = <T, int>{}; |
| |
| /// Register [element] and returns its index. |
| int register(T element) { |
| return map.putIfAbsent(element, () => map.length); |
| } |
| |
| /// Returns the index of [element]. |
| int operator [](T element) => map[element]; |
| |
| /// Returns the indexed elements. |
| Iterable<T> get elements => map.keys; |
| } |