001 package org.jetbrains.js.compiler.sourcemap; 002 003 import com.google.dart.compiler.common.SourceInfo; 004 import com.google.dart.compiler.util.TextOutput; 005 import com.intellij.openapi.util.text.StringUtil; 006 import com.intellij.util.PairConsumer; 007 import gnu.trove.TObjectIntHashMap; 008 009 import java.io.File; 010 import java.util.ArrayList; 011 import java.util.List; 012 013 public class SourceMap3Builder implements SourceMapBuilder { 014 private final StringBuilder out = new StringBuilder(8192); 015 private final File generatedFile; 016 private final TextOutput textOutput; 017 private final PairConsumer<SourceMapBuilder, Object> sourceInfoConsumer; 018 019 private String lastSource; 020 private int lastSourceIndex; 021 022 private final TObjectIntHashMap<String> sources = new TObjectIntHashMap<String>() { 023 @Override 024 public int get(String key) { 025 int index = index(key); 026 return index < 0 ? -1 : _values[index]; 027 } 028 }; 029 030 private final List<String> orderedSources = new ArrayList<String>(); 031 032 private int previousGeneratedColumn = -1; 033 private int previousSourceIndex; 034 private int previousSourceLine; 035 private int previousSourceColumn; 036 037 public SourceMap3Builder(File generatedFile, TextOutput textOutput, PairConsumer<SourceMapBuilder, Object> sourceInfoConsumer) { 038 this.generatedFile = generatedFile; 039 this.textOutput = textOutput; 040 this.sourceInfoConsumer = sourceInfoConsumer; 041 } 042 043 @Override 044 public File getOutFile() { 045 return new File(generatedFile.getParentFile(), generatedFile.getName() + ".map"); 046 } 047 048 @Override 049 public String build() { 050 StringBuilder sb = new StringBuilder(out.length() + (128 * orderedSources.size())); 051 sb.append("{\"version\":3,\"file\":\"").append(generatedFile.getName()).append('"').append(','); 052 appendSources(sb); 053 sb.append(",\"names\":["); 054 sb.append("],\"mappings\":\""); 055 sb.append(out); 056 sb.append("\"}"); 057 return sb.toString(); 058 } 059 060 private void appendSources(StringBuilder sb) { 061 boolean isNotFirst = false; 062 sb.append('"').append("sources").append("\":["); 063 for (String source : orderedSources) { 064 if (isNotFirst) { 065 sb.append(','); 066 } 067 else { 068 isNotFirst = true; 069 } 070 sb.append('"').append("file://").append(source).append('"'); 071 } 072 sb.append(']'); 073 } 074 075 @Override 076 public void newLine() { 077 out.append(';'); 078 previousGeneratedColumn = -1; 079 } 080 081 @Override 082 public void skipLinesAtBeginning(int count) { 083 out.insert(0, StringUtil.repeatSymbol(';', count)); 084 } 085 086 @Override 087 public void processSourceInfo(Object sourceInfo) { 088 if (sourceInfo instanceof SourceInfo) { 089 throw new UnsupportedOperationException("SourceInfo is not yet supported"); 090 } 091 sourceInfoConsumer.consume(this, sourceInfo); 092 } 093 094 private int getSourceIndex(String source) { 095 if (source.equals(lastSource)) { 096 return lastSourceIndex; 097 } 098 099 int sourceIndex = sources.get(source); 100 if (sourceIndex == -1) { 101 sourceIndex = orderedSources.size(); 102 sources.put(source, sourceIndex); 103 orderedSources.add(source); 104 } 105 106 lastSource = source; 107 lastSourceIndex = sourceIndex; 108 109 return sourceIndex; 110 } 111 112 @Override 113 public void addMapping(String source, int sourceLine, int sourceColumn) { 114 if (previousGeneratedColumn == -1) { 115 previousGeneratedColumn = 0; 116 } 117 else { 118 out.append(','); 119 } 120 121 int columnDiff = textOutput.getColumn() - previousGeneratedColumn; 122 // TODO fix sections overlapping 123 // assert columnDiff != 0; 124 Base64VLQ.encode(out, columnDiff); 125 previousGeneratedColumn = textOutput.getColumn(); 126 int sourceIndex = getSourceIndex(source); 127 Base64VLQ.encode(out, sourceIndex - previousSourceIndex); 128 previousSourceIndex = sourceIndex; 129 130 Base64VLQ.encode(out, sourceLine - previousSourceLine); 131 previousSourceLine = sourceLine; 132 133 Base64VLQ.encode(out, sourceColumn - previousSourceColumn); 134 previousSourceColumn = sourceColumn; 135 } 136 137 @Override 138 public void addLink() { 139 textOutput.print("\n//@ sourceMappingURL="); 140 textOutput.print(generatedFile.getName()); 141 textOutput.print(".map\n"); 142 } 143 144 private static final class Base64VLQ { 145 // A Base64 VLQ digit can represent 5 bits, so it is base-32. 146 private static final int VLQ_BASE_SHIFT = 5; 147 private static final int VLQ_BASE = 1 << VLQ_BASE_SHIFT; 148 149 // A mask of bits for a VLQ digit (11111), 31 decimal. 150 private static final int VLQ_BASE_MASK = VLQ_BASE - 1; 151 152 // The continuation bit is the 6th bit. 153 private static final int VLQ_CONTINUATION_BIT = VLQ_BASE; 154 155 @SuppressWarnings("SpellCheckingInspection") 156 private static final char[] BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); 157 158 private Base64VLQ() { 159 } 160 161 private static int toVLQSigned(int value) { 162 return value < 0 ? ((-value) << 1) + 1 : value << 1; 163 } 164 165 public static void encode(StringBuilder out, int value) { 166 value = toVLQSigned(value); 167 do { 168 int digit = value & VLQ_BASE_MASK; 169 value >>>= VLQ_BASE_SHIFT; 170 if (value > 0) { 171 digit |= VLQ_CONTINUATION_BIT; 172 } 173 out.append(BASE64_MAP[digit]); 174 } 175 while (value > 0); 176 } 177 } 178 }