diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java index ced4459e2c5..dcaf3a71364 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java @@ -608,13 +608,9 @@ public class JsToStringGenerationVisitor extends JsVisitor { } p.maybeIndent(); - beforeNodePrinted(nameRef); p.print(nameRef.getIdent()); } - protected void beforeNodePrinted(JsNode node) { - } - @Override public void visitNew(@NotNull JsNew x) { p.print(CHARS_NEW); diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsObjectLiteral.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsObjectLiteral.java index 987ea27b9ba..b7e02325cf6 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsObjectLiteral.java +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsObjectLiteral.java @@ -13,20 +13,24 @@ import java.util.List; public final class JsObjectLiteral extends JsLiteral { private final List properties; - private final boolean multiline; + private boolean multiline; public JsObjectLiteral() { - this(new SmartList()); + this(new SmartList<>()); } public JsObjectLiteral(boolean multiline) { - this(new SmartList(), multiline); + this(new SmartList<>(), multiline); } public boolean isMultiline() { return multiline; } + public void setMultiline(boolean multiline) { + this.multiline = multiline; + } + public JsObjectLiteral(List properties) { this(properties, false); } diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVars.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVars.java index f65317c9b98..1ae8434ba38 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVars.java +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVars.java @@ -18,14 +18,14 @@ import java.util.*; public class JsVars extends SourceInfoAwareJsNode implements JsStatement, Iterable { private final List vars; - private final boolean multiline; + private boolean multiline; public JsVars() { - this(new SmartList(), false); + this(new SmartList<>(), false); } public JsVars(boolean multiline) { - this(new SmartList(), multiline); + this(new SmartList<>(), multiline); } public JsVars(List vars, boolean multiline) { @@ -34,17 +34,21 @@ public class JsVars extends SourceInfoAwareJsNode implements JsStatement, Iterab } public JsVars(JsVar var) { - this(new SmartList(var), false); + this(new SmartList<>(var), false); } public JsVars(JsVar... vars) { - this(new SmartList(vars), false); + this(new SmartList<>(vars), false); } public boolean isMultiline() { return multiline; } + public void setMultiline(boolean multiline) { + this.multiline = multiline; + } + /** * A var declared using the JavaScript var statement. */ @@ -138,6 +142,7 @@ public class JsVars extends SourceInfoAwareJsNode implements JsStatement, Iterab } @Override + @NotNull public Iterator iterator() { return vars.iterator(); } diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java index e7b8b190cb4..7ab3be9429d 100644 --- a/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java @@ -708,88 +708,98 @@ public class Parser extends Observable { } private Node expr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); Node pn = assignExpr(ts, inForInit); while (ts.matchToken(TokenStream.COMMA)) { - pn = nf.createBinary(TokenStream.COMMA, pn, assignExpr(ts, inForInit), pn.getPosition()); + pn = nf.createBinary(TokenStream.COMMA, pn, assignExpr(ts, inForInit), position); } return pn; } private Node assignExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); Node pn = condExpr(ts, inForInit); if (ts.matchToken(TokenStream.ASSIGN)) { // omitted: "invalid assignment left-hand side" check. - pn = nf.createBinary(TokenStream.ASSIGN, ts.getOp(), pn, assignExpr(ts, inForInit), pn.getPosition()); + pn = nf.createBinary(TokenStream.ASSIGN, ts.getOp(), pn, assignExpr(ts, inForInit), position); } return pn; } private Node condExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); Node pn = orExpr(ts, inForInit); if (ts.matchToken(TokenStream.HOOK)) { Node ifTrue = assignExpr(ts, false); mustMatchToken(ts, TokenStream.COLON, "msg.no.colon.cond"); Node ifFalse = assignExpr(ts, inForInit); - return nf.createTernary(pn, ifTrue, ifFalse, pn.getPosition()); + return nf.createTernary(pn, ifTrue, ifFalse, position); } return pn; } private Node orExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); Node pn = andExpr(ts, inForInit); while (ts.matchToken(TokenStream.OR)) { - pn = nf.createBinary(TokenStream.OR, pn, andExpr(ts, inForInit), pn.getPosition()); + pn = nf.createBinary(TokenStream.OR, pn, andExpr(ts, inForInit), position); } return pn; } private Node andExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); Node pn = bitOrExpr(ts, inForInit); while (ts.matchToken(TokenStream.AND)) { - pn = nf.createBinary(TokenStream.AND, pn, bitOrExpr(ts, inForInit), pn.getPosition()); + pn = nf.createBinary(TokenStream.AND, pn, bitOrExpr(ts, inForInit), position); } return pn; } private Node bitOrExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); Node pn = bitXorExpr(ts, inForInit); while (ts.matchToken(TokenStream.BITOR)) { - pn = nf.createBinary(TokenStream.BITOR, pn, bitXorExpr(ts, inForInit), pn.getPosition()); + pn = nf.createBinary(TokenStream.BITOR, pn, bitXorExpr(ts, inForInit), position); } return pn; } private Node bitXorExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); Node pn = bitAndExpr(ts, inForInit); while (ts.matchToken(TokenStream.BITXOR)) { - pn = nf.createBinary(TokenStream.BITXOR, pn, bitAndExpr(ts, inForInit), pn.getPosition()); + pn = nf.createBinary(TokenStream.BITXOR, pn, bitAndExpr(ts, inForInit), position); } return pn; } private Node bitAndExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); Node pn = eqExpr(ts, inForInit); while (ts.matchToken(TokenStream.BITAND)) { - pn = nf.createBinary(TokenStream.BITAND, pn, eqExpr(ts, inForInit), pn.getPosition()); + pn = nf.createBinary(TokenStream.BITAND, pn, eqExpr(ts, inForInit), position); } return pn; } private Node eqExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); Node pn = relExpr(ts, inForInit); while (ts.matchToken(TokenStream.EQOP)) { - pn = nf.createBinary(TokenStream.EQOP, ts.getOp(), pn, relExpr(ts, inForInit), pn.getPosition()); + pn = nf.createBinary(TokenStream.EQOP, ts.getOp(), pn, relExpr(ts, inForInit), position); } return pn; } private Node relExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); Node pn = shiftExpr(ts); while (ts.matchToken(TokenStream.RELOP)) { int op = ts.getOp(); @@ -798,26 +808,28 @@ public class Parser extends Observable { break; } - pn = nf.createBinary(TokenStream.RELOP, op, pn, shiftExpr(ts), pn.getPosition()); + pn = nf.createBinary(TokenStream.RELOP, op, pn, shiftExpr(ts), position); } return pn; } private Node shiftExpr(TokenStream ts) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); Node pn = addExpr(ts); while (ts.matchToken(TokenStream.SHOP)) { - pn = nf.createBinary(TokenStream.SHOP, ts.getOp(), pn, addExpr(ts), pn.getPosition()); + pn = nf.createBinary(TokenStream.SHOP, ts.getOp(), pn, addExpr(ts), position); } return pn; } private Node addExpr(TokenStream ts) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); int tt; Node pn = mulExpr(ts); while ((tt = ts.getToken()) == TokenStream.ADD || tt == TokenStream.SUB) { // flushNewLines - pn = nf.createBinary(tt, pn, mulExpr(ts), pn.getPosition()); + pn = nf.createBinary(tt, pn, mulExpr(ts), position); } ts.ungetToken(tt); @@ -825,13 +837,14 @@ public class Parser extends Observable { } private Node mulExpr(TokenStream ts) throws IOException, JavaScriptException { + CodePosition position = getNextTokenPosition(ts); int tt; Node pn = unaryExpr(ts); while ((tt = ts.peekToken()) == TokenStream.MUL || tt == TokenStream.DIV || tt == TokenStream.MOD) { tt = ts.getToken(); - pn = nf.createBinary(tt, pn, unaryExpr(ts), pn.getPosition()); + pn = nf.createBinary(tt, pn, unaryExpr(ts), position); } return pn; @@ -1133,6 +1146,13 @@ public class Parser extends Observable { return null; // should never reach here } + private static CodePosition getNextTokenPosition(TokenStream ts) throws IOException, JavaScriptException { + int tt = ts.getToken(); + CodePosition result = ts.tokenPosition; + ts.ungetToken(tt); + return result; + } + private int lastExprEndLine; // Hack to handle function expr termination. private final IRFactory nf; private boolean ok; // Did the parse encounter an error? diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt index 82095fe946e..576d13ed673 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.js.test +import com.google.gwt.dev.js.ThrowExceptionOnErrorReporter import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.vfs.StandardFileSystems @@ -31,19 +32,19 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.config.* import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.js.JavaScript -import org.jetbrains.kotlin.js.backend.ast.JsProgram +import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.config.EcmaVersion import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.dce.DeadCodeElimination import org.jetbrains.kotlin.js.dce.InputFile -import org.jetbrains.kotlin.js.facade.K2JSTranslator -import org.jetbrains.kotlin.js.facade.MainCallParameters -import org.jetbrains.kotlin.js.facade.TranslationResult -import org.jetbrains.kotlin.js.facade.TranslationUnit -import org.jetbrains.kotlin.js.test.utils.DirectiveTestUtils -import org.jetbrains.kotlin.js.test.utils.JsTestUtils -import org.jetbrains.kotlin.js.test.utils.verifyAst +import org.jetbrains.kotlin.js.facade.* +import org.jetbrains.kotlin.js.parser.parse +import org.jetbrains.kotlin.js.parser.sourcemaps.* +import org.jetbrains.kotlin.js.sourceMap.JsSourceGenerationVisitor +import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder +import org.jetbrains.kotlin.js.test.utils.* +import org.jetbrains.kotlin.js.util.TextOutputImpl import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtPsiFactory @@ -55,10 +56,7 @@ import org.jetbrains.kotlin.test.KotlinTestUtils.TestFileFactory import org.jetbrains.kotlin.test.KotlinTestWithEnvironment import org.jetbrains.kotlin.test.TargetBackend import org.jetbrains.kotlin.utils.DFS -import java.io.ByteArrayOutputStream -import java.io.Closeable -import java.io.File -import java.io.PrintStream +import java.io.* import java.nio.charset.Charset import java.util.regex.Pattern @@ -386,6 +384,7 @@ abstract class BasicBoxTest( } processJsProgram(translationResult.program, units.filterIsInstance().map { it.file }) + checkSourceMap(outputFile, translationResult.program) } protected fun processJsProgram(program: JsProgram, psiFiles: List) { @@ -395,6 +394,56 @@ abstract class BasicBoxTest( program.verifyAst() } + private fun checkSourceMap(outputFile: File, program: JsProgram) { + val generatedProgram = JsProgram() + generatedProgram.globalBlock.statements += program.globalBlock.statements.map { it.deepCopy() } + generatedProgram.accept(object : RecursiveJsVisitor() { + override fun visitObjectLiteral(x: JsObjectLiteral) { + super.visitObjectLiteral(x) + x.isMultiline = false + } + override fun visitVars(x: JsVars) { + x.isMultiline = false + super.visitVars(x) + } + }) + removeLocationFromBlocks(generatedProgram) + generatedProgram.accept(AmbiguousAstSourcePropagation()) + + val output = TextOutputImpl() + val sourceMapBuilder = SourceMap3Builder(outputFile, output, SourceMapBuilderConsumer()) + generatedProgram.accept(JsSourceGenerationVisitor(output, sourceMapBuilder)) + val code = output.toString() + val generatedSourceMap = sourceMapBuilder.build() + + val codeWithLines = generatedProgram.toStringWithLineNumbers() + + val parsedProgram = JsProgram() + parsedProgram.globalBlock.statements += parse(code, ThrowExceptionOnErrorReporter, parsedProgram.scope, outputFile.path) + removeLocationFromBlocks(parsedProgram) + val sourceMapParseResult = SourceMapParser.parse(StringReader(generatedSourceMap)) + val sourceMap = when (sourceMapParseResult) { + is SourceMapSuccess -> sourceMapParseResult.value + is SourceMapError -> error("Could not parse source map: ${sourceMapParseResult.message}") + } + + val remapper = SourceMapLocationRemapper(mapOf(outputFile.path to sourceMap)) + remapper.remap(parsedProgram) + + val codeWithRemappedLines = parsedProgram.toStringWithLineNumbers() + + TestCase.assertEquals(codeWithLines, codeWithRemappedLines) + } + + private fun removeLocationFromBlocks(program: JsProgram) { + program.globalBlock.accept(object : RecursiveJsVisitor() { + override fun visitBlock(x: JsBlock) { + super.visitBlock(x) + x.source = null + } + }) + } + private fun createPsiFile(fileName: String): KtFile { val psiManager = PsiManager.getInstance(project) val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) @@ -422,12 +471,10 @@ abstract class BasicBoxTest( configuration.put(JSConfigurationKeys.MODULE_KIND, module.moduleKind) configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.v5) - configuration.put(JSConfigurationKeys.SOURCE_MAP, generateSourceMap) - val hasFilesToRecompile = module.hasFilesToRecompile configuration.put(JSConfigurationKeys.META_INFO, multiModule) configuration.put(JSConfigurationKeys.SERIALIZE_FRAGMENTS, hasFilesToRecompile) - configuration.put(JSConfigurationKeys.SOURCE_MAP, hasFilesToRecompile) + configuration.put(JSConfigurationKeys.SOURCE_MAP, hasFilesToRecompile || generateSourceMap) if (additionalMetadata != null) { val metadata = PackagesWithHeaderMetadata( diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineCollector.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineCollector.kt index 8e4d1be89de..ef3e072a93b 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineCollector.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineCollector.kt @@ -31,11 +31,21 @@ class LineCollector : RecursiveJsVisitor() { } private fun handleNodeLocation(node: JsNode) { - (node.source as? PsiElement)?.let { source -> - val file = source.containingFile - val offset = source.node.startOffset - val document = file.viewProvider.document!! - val line = document.getLineNumber(offset) + val source = node.source + val line = when (source) { + is PsiElement -> { + val file = source.containingFile + val offset = source.node.startOffset + val document = file.viewProvider.document!! + document.getLineNumber(offset) + } + is JsLocation -> { + source.startLine + } + else -> null + } + + if (line != null) { currentStatement?.let { val linesByStatement = lineNumbersByStatement.getOrPut(it, ::mutableListOf) if (linesByStatement.lastOrNull() != line) { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/lineNumberUtils.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/lineNumberUtils.kt new file mode 100644 index 00000000000..2ccec5c1a5e --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/lineNumberUtils.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2017 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.kotlin.js.test.utils + +import org.jetbrains.kotlin.js.backend.ast.JsProgram +import org.jetbrains.kotlin.js.util.TextOutputImpl + +fun JsProgram.toStringWithLineNumbers(): String { + val output = TextOutputImpl() + val lineCollector = LineCollector().also { it.accept(this) } + LineOutputToStringVisitor(output, lineCollector).accept(this.globalBlock) + return output.toString() +} \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/facade/SourceMapBuilderConsumer.java b/js/js.translator/src/org/jetbrains/kotlin/js/facade/SourceMapBuilderConsumer.java index d0f6f90721a..76d11e64509 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/facade/SourceMapBuilderConsumer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/facade/SourceMapBuilderConsumer.java @@ -23,7 +23,7 @@ import com.intellij.util.PairConsumer; import org.jetbrains.kotlin.js.backend.ast.JsLocation; import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilder; -class SourceMapBuilderConsumer implements PairConsumer { +public class SourceMapBuilderConsumer implements PairConsumer { @Override public void consume(SourceMapBuilder builder, Object sourceInfo) { if (sourceInfo instanceof PsiElement) { diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/JsSourceGenerationVisitor.java b/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/JsSourceGenerationVisitor.java index de68165c67d..f626b8b725a 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/JsSourceGenerationVisitor.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/JsSourceGenerationVisitor.java @@ -57,9 +57,7 @@ public class JsSourceGenerationVisitor extends JsToStringGenerationVisitor imple @Override public void accept(JsNode node) { - if (!(node instanceof JsNameRef) && !(node instanceof JsThisRef)) { - mapSource(node); - } + mapSource(node); super.accept(node); } @@ -77,11 +75,6 @@ public class JsSourceGenerationVisitor extends JsToStringGenerationVisitor imple } } - @Override - protected void beforeNodePrinted(JsNode node) { - mapSource(node); - } - @Override public void visitProgram(@NotNull JsProgram program) { program.acceptChildren(this);