diff --git a/src/com/google/dart/compiler/backend/js/JsSourceGenerationVisitor.java b/src/com/google/dart/compiler/backend/js/JsSourceGenerationVisitor.java index de88bdb7b74..91a3e98f337 100644 --- a/src/com/google/dart/compiler/backend/js/JsSourceGenerationVisitor.java +++ b/src/com/google/dart/compiler/backend/js/JsSourceGenerationVisitor.java @@ -4,18 +4,17 @@ package com.google.dart.compiler.backend.js; -import com.google.dart.compiler.backend.js.ast.JsBlock; -import com.google.dart.compiler.backend.js.ast.JsContext; -import com.google.dart.compiler.backend.js.ast.JsProgram; -import com.google.dart.compiler.backend.js.ast.JsProgramFragment; +import com.google.dart.compiler.backend.js.ast.*; import com.google.dart.compiler.util.TextOutput; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.js.compiler.SourceMapBuilder; /** * Generates JavaScript source from an AST. */ public class JsSourceGenerationVisitor extends JsToStringGenerationVisitor { - public JsSourceGenerationVisitor(TextOutput out) { - super(out); + public JsSourceGenerationVisitor(TextOutput out, @Nullable SourceMapBuilder sourceMapBuilder) { + super(out, sourceMapBuilder); } @Override diff --git a/src/com/google/dart/compiler/backend/js/JsToStringGenerationVisitor.java b/src/com/google/dart/compiler/backend/js/JsToStringGenerationVisitor.java index 7e78a15e46a..c254e8eb311 100644 --- a/src/com/google/dart/compiler/backend/js/JsToStringGenerationVisitor.java +++ b/src/com/google/dart/compiler/backend/js/JsToStringGenerationVisitor.java @@ -7,9 +7,14 @@ package com.google.dart.compiler.backend.js; import com.google.dart.compiler.backend.js.ast.*; import com.google.dart.compiler.backend.js.ast.JsVars.JsVar; import com.google.dart.compiler.util.TextOutput; -import gnu.trove.TIntArrayList; +import gnu.trove.THashSet; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.js.compiler.SourceMapBuilder; -import java.util.*; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; import static com.google.dart.compiler.backend.js.ast.JsNumberLiteral.JsDoubleLiteral; import static com.google.dart.compiler.backend.js.ast.JsNumberLiteral.JsIntLiteral; @@ -194,13 +199,19 @@ public class JsToStringGenerationVisitor extends JsVisitor { * because the statements designated by statementEnds and statementStarts are * those that appear directly within these global blocks. */ - private Set globalBlocks = new HashSet(); + private Set globalBlocks = new THashSet(); private final TextOutput p; - private TIntArrayList statementEnds = new TIntArrayList(); - private TIntArrayList statementStarts = new TIntArrayList(); + + @Nullable + private final SourceMapBuilder sourceMapBuilder; public JsToStringGenerationVisitor(TextOutput out) { + this(out, null); + } + + public JsToStringGenerationVisitor(TextOutput out, @Nullable SourceMapBuilder sourceMapBuilder) { p = out; + this.sourceMapBuilder = sourceMapBuilder; } @Override @@ -963,12 +974,17 @@ public class JsToStringGenerationVisitor extends JsVisitor { return false; } - protected void newline() { + protected final void newline() { + if (sourceMapBuilder != null) { + sourceMapBuilder.newLine(); + } p.newline(); } - protected void newlineOpt() { - p.newlineOpt(); + protected final void newlineOpt() { + if (!p.isCompact()) { + newline(); + } } protected void printJsBlock(JsBlock x, boolean truncate, boolean finalNewline) { @@ -998,7 +1014,6 @@ public class JsToStringGenerationVisitor extends JsVisitor { } needSemi = true; - boolean shouldRecordPositions = isGlobal && !(statement instanceof JsBlock); boolean stmtIsGlobalBlock = false; if (isGlobal) { if (statement instanceof JsBlock) { @@ -1007,9 +1022,7 @@ public class JsToStringGenerationVisitor extends JsVisitor { globalBlocks.add((JsBlock) statement); } } - if (shouldRecordPositions) { - statementStarts.add(p.getPosition()); - } + accept(statement); if (stmtIsGlobalBlock) { //noinspection SuspiciousMethodCalls @@ -1046,10 +1059,6 @@ public class JsToStringGenerationVisitor extends JsVisitor { newlineOpt(); } } - if (shouldRecordPositions) { - assert (statementStarts.size() == statementEnds.size() + 1); - statementEnds.add(p.getPosition()); - } ++count; } @@ -1352,4 +1361,26 @@ public class JsToStringGenerationVisitor extends JsVisitor { private void printStringLiteral(String value) { p.print(javaScriptString(value)); } + + @Override + protected void doTraverse(JsVisitable node, JsContext context) { + super.doTraverse(node, context); + + if (sourceMapBuilder == null) { + return; + } + + Object sourceInfo = node.getSourceInfo(); + if (sourceInfo != null) { + sourceMapBuilder.processSourceInfo(sourceInfo); + } + } + + @Override + public void endVisit(JsProgram x, JsContext context) { + super.endVisit(x, context); + if (sourceMapBuilder != null) { + sourceMapBuilder.addLink(); + } + } } diff --git a/src/com/google/dart/compiler/backend/js/ast/ChameleonJsExpression.java b/src/com/google/dart/compiler/backend/js/ast/ChameleonJsExpression.java index 00d1eaaf511..65047589936 100644 --- a/src/com/google/dart/compiler/backend/js/ast/ChameleonJsExpression.java +++ b/src/com/google/dart/compiler/backend/js/ast/ChameleonJsExpression.java @@ -52,12 +52,12 @@ public class ChameleonJsExpression implements JsExpression { } @Override - public SourceInfo getSourceInfo() { + public Object getSourceInfo() { return expression.getSourceInfo(); } @Override - public void setSourceInfo(SourceInfo info) { + public void setSourceInfo(Object info) { expression.setSourceInfo(info); } } diff --git a/src/com/google/dart/compiler/backend/js/ast/JsContext.java b/src/com/google/dart/compiler/backend/js/ast/JsContext.java index ddb170052d3..15371c8ec41 100644 --- a/src/com/google/dart/compiler/backend/js/ast/JsContext.java +++ b/src/com/google/dart/compiler/backend/js/ast/JsContext.java @@ -10,7 +10,6 @@ package com.google.dart.compiler.backend.js.ast; * node. */ public interface JsContext { - boolean canInsert(); boolean canRemove(); diff --git a/src/com/google/dart/compiler/backend/js/ast/JsExpressionStatement.java b/src/com/google/dart/compiler/backend/js/ast/JsExpressionStatement.java index 1229f30c4db..1e24b31894d 100644 --- a/src/com/google/dart/compiler/backend/js/ast/JsExpressionStatement.java +++ b/src/com/google/dart/compiler/backend/js/ast/JsExpressionStatement.java @@ -31,12 +31,12 @@ public final class JsExpressionStatement extends AbstractNode implements JsState } @Override - public SourceInfo getSourceInfo() { + public Object getSourceInfo() { return expression.getSourceInfo(); } @Override - public void setSourceInfo(SourceInfo info) { + public void setSourceInfo(Object info) { expression.setSourceInfo(info); } } diff --git a/src/com/google/dart/compiler/backend/js/ast/JsGlobalBlock.java b/src/com/google/dart/compiler/backend/js/ast/JsGlobalBlock.java index 9a14004e14c..11bbd10d86e 100644 --- a/src/com/google/dart/compiler/backend/js/ast/JsGlobalBlock.java +++ b/src/com/google/dart/compiler/backend/js/ast/JsGlobalBlock.java @@ -5,7 +5,7 @@ package com.google.dart.compiler.backend.js.ast; /** - * Represnts a JavaScript block in the global scope. + * Represents a JavaScript block in the global scope. */ public class JsGlobalBlock extends JsBlock { diff --git a/src/com/google/dart/compiler/backend/js/ast/JsNodeImpl.java b/src/com/google/dart/compiler/backend/js/ast/JsNodeImpl.java index 6128475681e..164300c68e4 100644 --- a/src/com/google/dart/compiler/backend/js/ast/JsNodeImpl.java +++ b/src/com/google/dart/compiler/backend/js/ast/JsNodeImpl.java @@ -3,18 +3,18 @@ package com.google.dart.compiler.backend.js.ast; import com.google.dart.compiler.common.SourceInfo; abstract class JsNodeImpl extends AbstractNode { - private SourceInfo sourceInfo; + private Object sourceInfo; protected JsNodeImpl() { } @Override - public SourceInfo getSourceInfo() { + public Object getSourceInfo() { return sourceInfo; } @Override - public void setSourceInfo(SourceInfo info) { + public void setSourceInfo(Object info) { sourceInfo = info; } } \ No newline at end of file diff --git a/src/com/google/dart/compiler/backend/js/ast/JsVisitable.java b/src/com/google/dart/compiler/backend/js/ast/JsVisitable.java index be539d3e775..8471d466e0e 100644 --- a/src/com/google/dart/compiler/backend/js/ast/JsVisitable.java +++ b/src/com/google/dart/compiler/backend/js/ast/JsVisitable.java @@ -4,7 +4,9 @@ package com.google.dart.compiler.backend.js.ast; -public interface JsVisitable { +import com.google.dart.compiler.common.HasSourceInfo; + +public interface JsVisitable extends HasSourceInfo { /** * Causes this object to have the visitor visit itself and its children. * diff --git a/src/com/google/dart/compiler/backend/js/ast/JsVisitor.java b/src/com/google/dart/compiler/backend/js/ast/JsVisitor.java index 11685df7925..3f0e5f3e7d5 100644 --- a/src/com/google/dart/compiler/backend/js/ast/JsVisitor.java +++ b/src/com/google/dart/compiler/backend/js/ast/JsVisitor.java @@ -11,7 +11,7 @@ import java.util.List; /** * Implemented by nodes that will visit child nodes. */ -public class JsVisitor { +abstract public class JsVisitor { protected static final JsContext LVALUE_CONTEXT = new JsContext() { @Override public boolean canInsert() { @@ -427,7 +427,7 @@ public class JsVisitor { } } - protected void doTraverse(JsVisitable node, JsContext ctx) { - node.traverse(this, ctx); + protected void doTraverse(JsVisitable node, JsContext context) { + node.traverse(this, context); } } diff --git a/src/com/google/dart/compiler/common/HasSourceInfo.java b/src/com/google/dart/compiler/common/HasSourceInfo.java index 524ed667467..251f72b71f7 100644 --- a/src/com/google/dart/compiler/common/HasSourceInfo.java +++ b/src/com/google/dart/compiler/common/HasSourceInfo.java @@ -8,11 +8,11 @@ public interface HasSourceInfo { /** * Return the source info associated with this object. */ - SourceInfo getSourceInfo(); + Object getSourceInfo(); /** * Set the source info associated with this object. * @param info */ - void setSourceInfo(SourceInfo info); + void setSourceInfo(Object info); } diff --git a/src/com/google/dart/compiler/util/TextOutput.java b/src/com/google/dart/compiler/util/TextOutput.java index 3b42663d7dc..16134ad5d60 100644 --- a/src/com/google/dart/compiler/util/TextOutput.java +++ b/src/com/google/dart/compiler/util/TextOutput.java @@ -8,31 +8,33 @@ package com.google.dart.compiler.util; * Interface used for printing text output. */ public interface TextOutput { - int getPosition(); + int getPosition(); - int getLine(); + int getLine(); - int getColumn(); + int getColumn(); - void indentIn(); + void indentIn(); - void indentOut(); + void indentOut(); - void newline(); + void newline(); - void newlineOpt(); + void print(char c); - void print(char c); - void print(int v); - void print(double v); + void print(int v); - void print(char[] s); + void print(double v); - void print(CharSequence s); + void print(char[] s); - void printOpt(char c); + void print(CharSequence s); - void printOpt(char[] s); + void printOpt(char c); - void printOpt(String s); + void printOpt(char[] s); + + void printOpt(String s); + + boolean isCompact(); } diff --git a/src/com/google/dart/compiler/util/TextOutputImpl.java b/src/com/google/dart/compiler/util/TextOutputImpl.java index 174fc429ed3..6cb76be1da3 100644 --- a/src/com/google/dart/compiler/util/TextOutputImpl.java +++ b/src/com/google/dart/compiler/util/TextOutputImpl.java @@ -24,6 +24,10 @@ public class TextOutputImpl implements TextOutput { this(false); } + public boolean isCompact() { + return compact; + } + public TextOutputImpl(boolean compact) { this.compact = compact; out = new StringBuilder(); @@ -77,13 +81,6 @@ public class TextOutputImpl implements TextOutput { justNewlined = true; } - @Override - public void newlineOpt() { - if (!compact) { - newline(); - } - } - @Override public void print(double value) { maybeIndent(); diff --git a/src/org/jetbrains/js/compiler/SourceMapBuilder.java b/src/org/jetbrains/js/compiler/SourceMapBuilder.java new file mode 100644 index 00000000000..1723a407ecc --- /dev/null +++ b/src/org/jetbrains/js/compiler/SourceMapBuilder.java @@ -0,0 +1,166 @@ +package org.jetbrains.js.compiler; + +import com.google.dart.compiler.common.SourceInfo; +import com.google.dart.compiler.util.TextOutput; +import com.intellij.util.PairConsumer; +import gnu.trove.TObjectIntHashMap; + +import java.util.ArrayList; +import java.util.List; + +public class SourceMapBuilder { + private final StringBuilder out = new StringBuilder(); + private final String generatedFilename; + private final TextOutput textOutput; + private final PairConsumer sourceInfoConsumer; + + private String lastSource; + private int lastSourceIndex; + + private final TObjectIntHashMap sources = new TObjectIntHashMap() { + @Override + public int get(String key) { + int index = index(key); + return index < 0 ? -1 : _values[index]; + } + }; + + private final List orderedSources = new ArrayList(); + + private int previousGeneratedColumn; + private int previousSourceIndex; + private int previousSourceLine; + private int previousSourceColumn; + + public SourceMapBuilder(String generatedFilename, TextOutput textOutput, PairConsumer sourceInfoConsumer) { + this.generatedFilename = generatedFilename; + this.textOutput = textOutput; + this.sourceInfoConsumer = sourceInfoConsumer; + } + + public String getOutFilename() { + return generatedFilename + ".map"; + } + + public String build() { + StringBuilder sb = new StringBuilder(); + sb.append("{\"version\":3,\"file\":\"").append(generatedFilename).append('"').append(','); + appendSources(sb); + sb.append(",\"names\":["); + sb.append("],\"mappings\":\""); + sb.append(out); + sb.append("\"}"); + return sb.toString(); + } + + private void appendSources(StringBuilder sb) { + boolean isNotFirst = false; + sb.append('"').append("sources").append("\":["); + for (String source : orderedSources) { + if (isNotFirst) { + sb.append(','); + } + else { + isNotFirst = true; + } + sb.append('"').append(source).append('"'); + } + sb.append(']'); + } + + public void newLine() { + out.append(';'); + previousGeneratedColumn = 0; + } + + public void processSourceInfo(Object sourceInfo) { + if (sourceInfo instanceof SourceInfo) { + throw new UnsupportedOperationException("SourceInfo is not yet supported"); + } + sourceInfoConsumer.consume(this, sourceInfo); + } + + private int getSourceIndex(String source) { + if (source.equals(lastSource)) { + return lastSourceIndex; + } + + int sourceIndex = sources.get(source); + if (sourceIndex == -1) { + sourceIndex = sources.put(source, orderedSources.size()); + orderedSources.add(source); + } + + lastSource = source; + lastSourceIndex = sourceIndex; + + return sourceIndex; + } + + public void addMapping(String source, int sourceLine, int sourceColumn) { + if (previousGeneratedColumn != 0) { + out.append(','); + } + + Base64VLQ.encode(out, textOutput.getColumn() - previousGeneratedColumn); + previousGeneratedColumn = textOutput.getColumn(); + + int sourceIndex = getSourceIndex(source); + Base64VLQ.encode(out, sourceIndex - previousSourceIndex); + previousSourceIndex = sourceIndex; + + Base64VLQ.encode(out, sourceLine - previousSourceLine); + previousSourceLine = sourceLine; + + Base64VLQ.encode(out, sourceColumn - previousSourceColumn); + previousSourceColumn = sourceColumn; + } + + private static int toVLQSigned(int value) { + return value < 0 ? ((-value) << 1) + 1 : value << 1; + } + + public void addLink() { + textOutput.print("\n//@ sourceMappingURL="); + textOutput.print(getOutFilename()); + } + + private static final class Base64VLQ { + // A Base64 VLQ digit can represent 5 bits, so it is base-32. + private static final int VLQ_BASE_SHIFT = 5; + private static final int VLQ_BASE = 1 << VLQ_BASE_SHIFT; + + // A mask of bits for a VLQ digit (11111), 31 decimal. + private static final int VLQ_BASE_MASK = VLQ_BASE - 1; + + // The continuation bit is the 6th bit. + private static final int VLQ_CONTINUATION_BIT = VLQ_BASE; + + /** + * A map used to convert integer values in the range 0-63 to their base64 + * values. + */ + private static final String BASE64_MAP = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "abcdefghijklmnopqrstuvwxyz" + + "0123456789+/"; + + public static void encode(StringBuilder out, int value) { + value = toVLQSigned(value); + do { + int digit = value & VLQ_BASE_MASK; + value >>>= VLQ_BASE_SHIFT; + if (value > 0) { + digit |= VLQ_CONTINUATION_BIT; + } + out.append(toBase64(digit)); + } + while (value > 0); + } + + public static char toBase64(int value) { + assert (value <= 63 && value >= 0) : "value out of range:" + value; + return BASE64_MAP.charAt(value); + } + } +}