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.util.PairConsumer; 006 import gnu.trove.TObjectIntHashMap; 007 import org.jetbrains.js.compiler.SourceMapBuilder; 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 processSourceInfo(Object sourceInfo) { 083 if (sourceInfo instanceof SourceInfo) { 084 throw new UnsupportedOperationException("SourceInfo is not yet supported"); 085 } 086 sourceInfoConsumer.consume(this, sourceInfo); 087 } 088 089 private int getSourceIndex(String source) { 090 if (source.equals(lastSource)) { 091 return lastSourceIndex; 092 } 093 094 int sourceIndex = sources.get(source); 095 if (sourceIndex == -1) { 096 sourceIndex = orderedSources.size(); 097 sources.put(source, sourceIndex); 098 orderedSources.add(source); 099 } 100 101 lastSource = source; 102 lastSourceIndex = sourceIndex; 103 104 return sourceIndex; 105 } 106 107 @Override 108 public void addMapping(String source, int sourceLine, int sourceColumn) { 109 if (previousGeneratedColumn == -1) { 110 previousGeneratedColumn = 0; 111 } 112 else { 113 out.append(','); 114 } 115 116 int columnDiff = textOutput.getColumn() - previousGeneratedColumn; 117 // TODO fix sections overlapping 118 // assert columnDiff != 0; 119 Base64VLQ.encode(out, columnDiff); 120 previousGeneratedColumn = textOutput.getColumn(); 121 int sourceIndex = getSourceIndex(source); 122 Base64VLQ.encode(out, sourceIndex - previousSourceIndex); 123 previousSourceIndex = sourceIndex; 124 125 Base64VLQ.encode(out, sourceLine - previousSourceLine); 126 previousSourceLine = sourceLine; 127 128 Base64VLQ.encode(out, sourceColumn - previousSourceColumn); 129 previousSourceColumn = sourceColumn; 130 } 131 132 @Override 133 public void addLink() { 134 textOutput.print("\n//@ sourceMappingURL="); 135 textOutput.print(generatedFile.getName()); 136 textOutput.print(".map"); 137 } 138 139 private static final class Base64VLQ { 140 // A Base64 VLQ digit can represent 5 bits, so it is base-32. 141 private static final int VLQ_BASE_SHIFT = 5; 142 private static final int VLQ_BASE = 1 << VLQ_BASE_SHIFT; 143 144 // A mask of bits for a VLQ digit (11111), 31 decimal. 145 private static final int VLQ_BASE_MASK = VLQ_BASE - 1; 146 147 // The continuation bit is the 6th bit. 148 private static final int VLQ_CONTINUATION_BIT = VLQ_BASE; 149 150 @SuppressWarnings("SpellCheckingInspection") 151 private static final char[] BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); 152 153 private Base64VLQ() { 154 } 155 156 private static int toVLQSigned(int value) { 157 return value < 0 ? ((-value) << 1) + 1 : value << 1; 158 } 159 160 public static void encode(StringBuilder out, int value) { 161 value = toVLQSigned(value); 162 do { 163 int digit = value & VLQ_BASE_MASK; 164 value >>>= VLQ_BASE_SHIFT; 165 if (value > 0) { 166 digit |= VLQ_CONTINUATION_BIT; 167 } 168 out.append(BASE64_MAP[digit]); 169 } 170 while (value > 0); 171 } 172 } 173 }