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 Base64VLQ.encode(out, textOutput.getColumn() - previousGeneratedColumn); 117 previousGeneratedColumn = textOutput.getColumn(); 118 int sourceIndex = getSourceIndex(source); 119 Base64VLQ.encode(out, sourceIndex - previousSourceIndex); 120 previousSourceIndex = sourceIndex; 121 122 Base64VLQ.encode(out, sourceLine - previousSourceLine); 123 previousSourceLine = sourceLine; 124 125 Base64VLQ.encode(out, sourceColumn - previousSourceColumn); 126 previousSourceColumn = sourceColumn; 127 } 128 129 @Override 130 public void addLink() { 131 textOutput.print("\n//@ sourceMappingURL=file://"); 132 textOutput.print(getOutFile().getAbsolutePath()); 133 } 134 135 private static final class Base64VLQ { 136 // A Base64 VLQ digit can represent 5 bits, so it is base-32. 137 private static final int VLQ_BASE_SHIFT = 5; 138 private static final int VLQ_BASE = 1 << VLQ_BASE_SHIFT; 139 140 // A mask of bits for a VLQ digit (11111), 31 decimal. 141 private static final int VLQ_BASE_MASK = VLQ_BASE - 1; 142 143 // The continuation bit is the 6th bit. 144 private static final int VLQ_CONTINUATION_BIT = VLQ_BASE; 145 146 private static final char[] BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); 147 148 private Base64VLQ() { 149 } 150 151 private static int toVLQSigned(int value) { 152 return value < 0 ? ((-value) << 1) + 1 : value << 1; 153 } 154 155 public static void encode(StringBuilder out, int value) { 156 value = toVLQSigned(value); 157 do { 158 int digit = value & VLQ_BASE_MASK; 159 value >>>= VLQ_BASE_SHIFT; 160 if (value > 0) { 161 digit |= VLQ_CONTINUATION_BIT; 162 } 163 out.append(BASE64_MAP[digit]); 164 } 165 while (value > 0); 166 } 167 } 168 }