diff --git a/js/js.translator/lib/dart-ast.jar b/js/js.translator/lib/dart-ast.jar index ba5c8028bdc..de3a20e193f 100644 Binary files a/js/js.translator/lib/dart-ast.jar and b/js/js.translator/lib/dart-ast.jar differ diff --git a/js/js.translator/lib/src/dart-ast-src.jar b/js/js.translator/lib/src/dart-ast-src.jar index b03781e1fbc..7830e0022c6 100644 Binary files a/js/js.translator/lib/src/dart-ast-src.jar and b/js/js.translator/lib/src/dart-ast-src.jar differ diff --git a/js/js.translator/src/org/jetbrains/js/compiler/JsSourceGenerationVisitor.java b/js/js.translator/src/org/jetbrains/js/compiler/JsSourceGenerationVisitor.java new file mode 100644 index 00000000000..023414ed69d --- /dev/null +++ b/js/js.translator/src/org/jetbrains/js/compiler/JsSourceGenerationVisitor.java @@ -0,0 +1,93 @@ +/* + * Copyright 2010-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.js.compiler; + +import com.google.dart.compiler.backend.js.JsToStringGenerationVisitor; +import com.google.dart.compiler.backend.js.ast.*; +import com.google.dart.compiler.util.TextOutput; +import org.jetbrains.annotations.Nullable; + +public class JsSourceGenerationVisitor extends JsToStringGenerationVisitor implements TextOutput.OutListener { + @Nullable + private final SourceMapBuilder sourceMapBuilder; + + private Object pendingSourceInfo; + + public JsSourceGenerationVisitor(TextOutput out, @Nullable SourceMapBuilder sourceMapBuilder) { + super(out); + this.sourceMapBuilder = sourceMapBuilder; + out.setOutListener(this); + } + + @Override + public boolean visit(JsProgram program, JsContext ctx) { + return true; + } + + @Override + public boolean visit(JsProgramFragment x, JsContext ctx) { + // Descend naturally. + return true; + } + + @Override + public boolean visit(JsBlock x, JsContext ctx) { + printJsBlock(x, false, true); + return false; + } + + @Override + public void newLined() { + if (sourceMapBuilder != null) { + sourceMapBuilder.newLine(); + } + } + + @Override + public void indentedAfterNewLine() { + if (pendingSourceInfo != null) { + assert sourceMapBuilder != null; + sourceMapBuilder.processSourceInfo(pendingSourceInfo); + pendingSourceInfo = null; + } + } + + @Override + protected void doTraverse(JsVisitable node, JsContext context) { + if (sourceMapBuilder != null) { + Object sourceInfo = node.getSourceInfo(); + if (sourceInfo != null) { + assert pendingSourceInfo == null; + if (p.isJustNewlined()) { + pendingSourceInfo = sourceInfo; + } + else { + sourceMapBuilder.processSourceInfo(sourceInfo); + } + } + } + super.doTraverse(node, context); + } + + @Override + public void endVisit(JsProgram x, JsContext context) { + super.endVisit(x, context); + if (sourceMapBuilder != null) { + sourceMapBuilder.addLink(); + } + } +} diff --git a/js/js.translator/src/org/jetbrains/js/compiler/sourcemap/SourceMap3Builder.java b/js/js.translator/src/org/jetbrains/js/compiler/sourcemap/SourceMap3Builder.java new file mode 100644 index 00000000000..b8551da2862 --- /dev/null +++ b/js/js.translator/src/org/jetbrains/js/compiler/sourcemap/SourceMap3Builder.java @@ -0,0 +1,168 @@ +package org.jetbrains.js.compiler.sourcemap; + +import com.google.dart.compiler.common.SourceInfo; +import com.google.dart.compiler.util.TextOutput; +import com.intellij.util.PairConsumer; +import gnu.trove.TObjectIntHashMap; +import org.jetbrains.js.compiler.SourceMapBuilder; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class SourceMap3Builder implements SourceMapBuilder { + private final StringBuilder out = new StringBuilder(8192); + private final File generatedFile; + 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 = -1; + private int previousSourceIndex; + private int previousSourceLine; + private int previousSourceColumn; + + public SourceMap3Builder(File generatedFile, TextOutput textOutput, PairConsumer sourceInfoConsumer) { + this.generatedFile = generatedFile; + this.textOutput = textOutput; + this.sourceInfoConsumer = sourceInfoConsumer; + } + + @Override + public File getOutFile() { + return new File(generatedFile.getParentFile(), generatedFile.getName() + ".map"); + } + + @Override + public String build() { + StringBuilder sb = new StringBuilder(out.length() + (128 * orderedSources.size())); + sb.append("{\"version\":3,\"file\":\"").append(generatedFile.getName()).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("file://").append(source).append('"'); + } + sb.append(']'); + } + + @Override + public void newLine() { + out.append(';'); + previousGeneratedColumn = -1; + } + + @Override + 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 = orderedSources.size(); + sources.put(source, sourceIndex); + orderedSources.add(source); + } + + lastSource = source; + lastSourceIndex = sourceIndex; + + return sourceIndex; + } + + @Override + public void addMapping(String source, int sourceLine, int sourceColumn) { + if (previousGeneratedColumn == -1) { + previousGeneratedColumn = 0; + } + else { + 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; + } + + @Override + public void addLink() { + textOutput.print("\n//@ sourceMappingURL=file://"); + textOutput.print(getOutFile().getAbsolutePath()); + } + + 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; + + private static final char[] BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + + private Base64VLQ() { + } + + private static int toVLQSigned(int value) { + return value < 0 ? ((-value) << 1) + 1 : value << 1; + } + + 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(BASE64_MAP[digit]); + } + while (value > 0); + } + } +} diff --git a/js/js.translator/src/org/jetbrains/k2js/facade/K2JSTranslator.java b/js/js.translator/src/org/jetbrains/k2js/facade/K2JSTranslator.java index f6e0c1a7296..83511204a89 100644 --- a/js/js.translator/src/org/jetbrains/k2js/facade/K2JSTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/facade/K2JSTranslator.java @@ -16,7 +16,6 @@ package org.jetbrains.k2js.facade; -import com.google.dart.compiler.backend.js.JsSourceGenerationVisitor; import com.google.dart.compiler.backend.js.ast.JsProgram; import com.google.dart.compiler.util.TextOutputImpl; import com.intellij.openapi.project.Project; @@ -25,7 +24,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.js.compiler.JsSourceGenerationVisitor; import org.jetbrains.js.compiler.SourceMapBuilder; +import org.jetbrains.js.compiler.sourcemap.SourceMap3Builder; import org.jetbrains.k2js.analyze.AnalyzerFacadeForJS; import org.jetbrains.k2js.config.Config; import org.jetbrains.k2js.facade.exceptions.TranslationException; @@ -54,7 +55,7 @@ public final class K2JSTranslator { K2JSTranslator translator = new K2JSTranslator(config); File outFile = new File(outputPath); TextOutputImpl output = new TextOutputImpl(); - SourceMapBuilder sourceMapBuilder = config.isSourcemap() ? new SourceMapBuilder(outFile, output, new SourceMapBuilderConsumer()) : null; + SourceMapBuilder sourceMapBuilder = config.isSourcemap() ? new SourceMap3Builder(outFile, output, new SourceMapBuilderConsumer()) : null; String programCode = translator.generateProgramCode(files, mainCall, output, sourceMapBuilder); FileUtil.writeToFile(outFile, programCode); if (sourceMapBuilder != null) { diff --git a/js/js.translator/src/org/jetbrains/k2js/facade/SourceMapBuilderConsumer.java b/js/js.translator/src/org/jetbrains/k2js/facade/SourceMapBuilderConsumer.java index 00b5d4f1c80..90db9c0a6ab 100644 --- a/js/js.translator/src/org/jetbrains/k2js/facade/SourceMapBuilderConsumer.java +++ b/js/js.translator/src/org/jetbrains/k2js/facade/SourceMapBuilderConsumer.java @@ -36,6 +36,6 @@ class SourceMapBuilderConsumer implements PairConsumer assert document != null; int line = document.getLineNumber(offset); int column = offset - document.getLineStartOffset(line); - builder.addMapping(file.getViewProvider().getVirtualFile().getUrl(), line, column); + builder.addMapping(file.getViewProvider().getVirtualFile().getPath(), line, column); } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java b/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java index 1aa9f437b49..14b092ae0d2 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/expression/ExpressionVisitor.java @@ -112,7 +112,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { jsBlock.getStatements().add(convertToStatement(jsNode)); } } - return jsBlock; + return source(jsBlock, jetBlock); } @Override @@ -120,10 +120,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { public JsNode visitReturnExpression(@NotNull JetReturnExpression jetReturnExpression, @NotNull TranslationContext context) { JetExpression returnedExpression = jetReturnExpression.getReturnedExpression(); - if (returnedExpression != null) { - return new JsReturn(translateAsExpression(returnedExpression, context)); - } - return new JsReturn(); + return source(new JsReturn(returnedExpression != null ? translateAsExpression(returnedExpression, context) : null), jetReturnExpression); } @Override @@ -158,14 +155,14 @@ public final class ExpressionVisitor extends TranslatorVisitor { context.aliasingContext().registerAlias(descriptor, alias); } - return newVar(name, initializer); + return source(newVar(name, initializer), expression); } @Override @NotNull public JsNode visitCallExpression(@NotNull JetCallExpression expression, @NotNull TranslationContext context) { - return CallExpressionTranslator.translate(expression, null, CallType.NORMAL, context); + return source(CallExpressionTranslator.translate(expression, null, CallType.NORMAL, context), expression); } @Override @@ -181,10 +178,11 @@ public final class ExpressionVisitor extends TranslatorVisitor { boolean isKotlinStatement = BindingUtils.isStatement(context.bindingContext(), expression); boolean canBeJsExpression = thenNode instanceof JsExpression && elseNode instanceof JsExpression; if (!isKotlinStatement && canBeJsExpression) { - return new JsConditional(testExpression, convertToExpression(thenNode), convertToExpression(elseNode)); + return source(new JsConditional(testExpression, convertToExpression(thenNode), convertToExpression(elseNode)), expression); } else { JsIf ifStatement = new JsIf(testExpression, convertToStatement(thenNode), elseNode == null ? null : convertToStatement(elseNode)); + source(ifStatement, expression); if (isKotlinStatement) { return ifStatement; } @@ -198,12 +196,11 @@ public final class ExpressionVisitor extends TranslatorVisitor { @Override @NotNull - public JsNode visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression, + public JsExpression visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression, @NotNull TranslationContext context) { return ReferenceTranslator.translateSimpleName(expression, context); } - @NotNull private JsStatement translateNullableExpressionAsNotNullStatement(@Nullable JetExpression nullableExpression, @NotNull TranslationContext context) { @@ -245,7 +242,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { private JsNode createWhile(@NotNull JsWhile result, @NotNull JetWhileExpressionBase expression, @NotNull TranslationContext context) { result.setCondition(translateConditionExpression(expression.getCondition(), context)); result.setBody(translateNullableExpressionAsNotNullStatement(expression.getBody(), context)); - return result; + return source(result, expression); } @Override @@ -256,7 +253,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { if (stringLiteral != null) { return stringLiteral; } - return resolveAsTemplate(expression, context); + return source(resolveAsTemplate(expression, context), expression); } @NotNull @@ -316,10 +313,9 @@ public final class ExpressionVisitor extends TranslatorVisitor { @Nullable public JsNode visitWhenExpression(@NotNull JetWhenExpression expression, @NotNull TranslationContext context) { - return Translation.translateWhenExpression(expression, context); + return WhenTranslator.translate(expression, context); } - @Override @NotNull public JsNode visitBinaryWithTypeRHSExpression(@NotNull JetBinaryExpressionWithTypeRHS expression, @@ -367,7 +363,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { JsExpression alias = context.literalFunctionTranslator().translate(expression, descriptor, context); JsName name = context.scope().declareFreshName(descriptor.getName().asString()); context.aliasingContext().registerAlias(descriptor, name.makeRef()); - return new JsVars(new JsVars.JsVar(name, alias)); + return source(new JsVars(new JsVars.JsVar(name, alias)), expression); } @Override @@ -406,7 +402,7 @@ public final class ExpressionVisitor extends TranslatorVisitor { @NotNull TranslationContext context) { JetExpression thrownExpression = expression.getThrownExpression(); assert thrownExpression != null : "Thrown expression must not be null"; - return new JsThrow(translateAsExpression(thrownExpression, context)); + return source(new JsThrow(translateAsExpression(thrownExpression, context)), expression); } @Override @@ -424,6 +420,6 @@ public final class ExpressionVisitor extends TranslatorVisitor { DeclarationDescriptor descriptor = getDescriptorForElement(context.bindingContext(), objectDeclarationName); JsName propertyName = context.getNameForDescriptor(descriptor); JsExpression value = ClassTranslator.generateClassCreation(expression, context); - return newVar(propertyName, value); + return source(newVar(propertyName, value), expression); } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/general/Translation.java b/js/js.translator/src/org/jetbrains/k2js/translate/general/Translation.java index 24c1fd12459..f91b894306d 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/general/Translation.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/general/Translation.java @@ -20,7 +20,10 @@ import com.google.dart.compiler.backend.js.ast.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; -import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.psi.JetDeclarationWithBody; +import org.jetbrains.jet.lang.psi.JetExpression; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.psi.JetNamedFunction; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.k2js.config.Config; import org.jetbrains.k2js.facade.MainCallParameters; @@ -35,7 +38,6 @@ import org.jetbrains.k2js.translate.declaration.NamespaceDeclarationTranslator; import org.jetbrains.k2js.translate.expression.ExpressionVisitor; import org.jetbrains.k2js.translate.expression.FunctionTranslator; import org.jetbrains.k2js.translate.expression.PatternTranslator; -import org.jetbrains.k2js.translate.expression.WhenTranslator; import org.jetbrains.k2js.translate.reference.CallBuilder; import org.jetbrains.k2js.translate.test.JSTestGenerator; import org.jetbrains.k2js.translate.test.JSTester; @@ -88,9 +90,7 @@ public final class Translation { //NOTE: use with care @NotNull public static JsNode doTranslateExpression(JetExpression expression, TranslationContext context) { - JsNode jsNode = expression.accept(new ExpressionVisitor(), context); - jsNode.setSourceInfo(expression); - return jsNode; + return expression.accept(new ExpressionVisitor(), context); } @NotNull @@ -105,12 +105,6 @@ public final class Translation { return convertToStatement(translateExpression(expression, context)); } - @Nullable - public static JsNode translateWhenExpression(@NotNull JetWhenExpression expression, - @NotNull TranslationContext context) { - return WhenTranslator.translate(expression, context); - } - @NotNull public static JsProgram generateAst(@NotNull BindingContext bindingContext, @NotNull Collection files, @NotNull MainCallParameters mainCallParameters, diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/operation/BinaryOperationTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/operation/BinaryOperationTranslator.java index c240882a5db..a8a5130d51b 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/operation/BinaryOperationTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/operation/BinaryOperationTranslator.java @@ -37,6 +37,7 @@ import static org.jetbrains.k2js.translate.operation.CompareToTranslator.isCompa import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptorForOperationExpression; import static org.jetbrains.k2js.translate.utils.BindingUtils.getResolvedCall; import static org.jetbrains.k2js.translate.utils.JsAstUtils.not; +import static org.jetbrains.k2js.translate.utils.JsAstUtils.source; import static org.jetbrains.k2js.translate.utils.PsiUtils.*; import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateLeftExpression; import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateRightExpression; @@ -47,13 +48,15 @@ public final class BinaryOperationTranslator extends AbstractTranslator { @NotNull public static JsExpression translate(@NotNull JetBinaryExpression expression, @NotNull TranslationContext context) { - return (new BinaryOperationTranslator(expression, context).translate()); + JsExpression jsExpression = new BinaryOperationTranslator(expression, context).translate(); + return source(jsExpression, expression); } @NotNull /*package*/ static JsExpression translateAsOverloadedCall(@NotNull JetBinaryExpression expression, @NotNull TranslationContext context) { - return (new BinaryOperationTranslator(expression, context)).translateAsOverloadedBinaryOperation(); + JsExpression jsExpression = (new BinaryOperationTranslator(expression, context)).translateAsOverloadedBinaryOperation(); + return source(jsExpression, expression); } @NotNull diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java index e5b1d2808bd..7adb73e25e4 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallTranslator.java @@ -79,7 +79,7 @@ public final class CallTranslator extends AbstractTranslator { } @NotNull - /*package*/ JsExpression translate() { + /*package*/ JsExpression translate() { JsExpression result = intrinsicInvocation(); if (result != null) { return result; diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java index bfb7a2e40c3..349144b9fba 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/utils/JsAstUtils.java @@ -24,6 +24,7 @@ import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; import org.jetbrains.jet.lang.descriptors.Modality; import org.jetbrains.jet.lang.descriptors.PropertyDescriptor; +import org.jetbrains.jet.lang.psi.JetElement; import org.jetbrains.k2js.translate.context.TranslationContext; import java.util.Arrays; @@ -296,4 +297,11 @@ public final class JsAstUtils { public static JsObjectLiteral wrapValue(@NotNull JsExpression label, @NotNull JsExpression value) { return new JsObjectLiteral(Collections.singletonList(new JsPropertyInitializer(label, value))); } + + @NotNull + public static T source(@NotNull T jsNode, @NotNull JetElement ktElement) { + jsNode.setSourceInfo(ktElement); + return jsNode; + } + }