// 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('source_map_builder'); #import('dart:json'); #import('scanner/scannerlib.dart'); #import('source_file.dart'); class SourceMapBuilder { 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+/'; List entries; Map sourceUrlMap; List sourceUrlList; Map sourceNameMap; List sourceNameList; int previousTargetLine; int previousTargetColumn; int previousSourceUrlIndex; int previousSourceLine; int previousSourceColumn; int previousSourceNameIndex; bool firstEntryInLine; SourceMapBuilder() { entries = new List(); sourceUrlMap = new Map(); sourceUrlList = new List(); sourceNameMap = new Map(); sourceNameList = new List(); previousTargetLine = 0; previousTargetColumn = 0; previousSourceUrlIndex = 0; previousSourceLine = 0; previousSourceColumn = 0; previousSourceNameIndex = 0; firstEntryInLine = true; } void addMapping(int targetOffset, SourceFileLocation sourceLocation) { entries.add(new SourceMapEntry(sourceLocation, targetOffset)); } String build(SourceFile targetFile) { StringBuffer buffer = new StringBuffer(); buffer.add('{\n'); buffer.add(' "version": 3,\n'); buffer.add(' "mappings": "'); entries.forEach((SourceMapEntry entry) => writeEntry(entry, targetFile, buffer)); buffer.add('",\n'); buffer.add(' "sources": '); JSON.printOn(sourceUrlList, buffer); buffer.add(',\n'); buffer.add(' "names": '); JSON.printOn(sourceNameList, buffer); buffer.add('\n}\n'); return buffer.toString(); } void writeEntry(SourceMapEntry entry, SourceFile targetFile, StringBuffer output) { int targetLine = targetFile.getLine(entry.targetOffset); int targetColumn = targetFile.getColumn(targetLine, entry.targetOffset); if (targetLine > previousTargetLine) { for (int i = previousTargetLine; i < targetLine; ++i) { output.add(';'); } previousTargetLine = targetLine; previousTargetColumn = 0; firstEntryInLine = true; } if (!firstEntryInLine) { output.add(','); } firstEntryInLine = false; encodeVLQ(output, targetColumn - previousTargetColumn); previousTargetColumn = targetColumn; if (entry.sourceLocation === null) return; String sourceUrl = entry.sourceLocation.getSourceUrl(); int sourceLine = entry.sourceLocation.getLine(); int sourceColumn = entry.sourceLocation.getColumn(); String sourceName = entry.sourceLocation.getSourceName(); int sourceUrlIndex = indexOf(sourceUrlList, sourceUrl, sourceUrlMap); encodeVLQ(output, sourceUrlIndex - previousSourceUrlIndex); previousSourceUrlIndex = sourceUrlIndex; encodeVLQ(output, sourceLine - previousSourceLine); previousSourceLine = sourceLine; encodeVLQ(output, sourceColumn - previousSourceColumn); previousSourceColumn = sourceColumn; if (sourceName === null) { return; } int sourceNameIndex = indexOf(sourceNameList, sourceName, sourceNameMap); encodeVLQ(output, sourceNameIndex - previousSourceNameIndex); previousSourceNameIndex = sourceNameIndex; } int indexOf(List list, String value, Map map) { return map.putIfAbsent(value, () { int index = list.length; map[value] = index; list.add(value); return index; }); } static void encodeVLQ(StringBuffer output, int value) { int signBit = 0; if (value < 0) { signBit = 1; value = -value; } value = (value << 1) | signBit; do { int digit = value & VLQ_BASE_MASK; value >>= VLQ_BASE_SHIFT; if (value > 0) { digit |= VLQ_CONTINUATION_BIT; } output.add(BASE64_DIGITS[digit]); } while (value > 0); } } class SourceMapEntry { SourceFileLocation sourceLocation; int targetOffset; SourceMapEntry(this.sourceLocation, this.targetOffset); } class SourceFileLocation { SourceFile sourceFile; Token token; int line; SourceFileLocation(this.sourceFile, this.token) { assert(isValid()); } String getSourceUrl() => sourceFile.filename; int getLine() { if (line == null) line = sourceFile.getLine(token.charOffset); return line; } int getColumn() => sourceFile.getColumn(getLine(), token.charOffset); String getSourceName() { if (token.isIdentifier()) return token.slowToString(); return null; } bool isValid() => token.charOffset < sourceFile.text.length; }