Add test infrastructure for JS source map parser and generator
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -13,20 +13,24 @@ import java.util.List;
|
||||
public final class JsObjectLiteral extends JsLiteral {
|
||||
private final List<JsPropertyInitializer> properties;
|
||||
|
||||
private final boolean multiline;
|
||||
private boolean multiline;
|
||||
|
||||
public JsObjectLiteral() {
|
||||
this(new SmartList<JsPropertyInitializer>());
|
||||
this(new SmartList<>());
|
||||
}
|
||||
|
||||
public JsObjectLiteral(boolean multiline) {
|
||||
this(new SmartList<JsPropertyInitializer>(), multiline);
|
||||
this(new SmartList<>(), multiline);
|
||||
}
|
||||
|
||||
public boolean isMultiline() {
|
||||
return multiline;
|
||||
}
|
||||
|
||||
public void setMultiline(boolean multiline) {
|
||||
this.multiline = multiline;
|
||||
}
|
||||
|
||||
public JsObjectLiteral(List<JsPropertyInitializer> properties) {
|
||||
this(properties, false);
|
||||
}
|
||||
|
||||
@@ -18,14 +18,14 @@ import java.util.*;
|
||||
public class JsVars extends SourceInfoAwareJsNode implements JsStatement, Iterable<JsVars.JsVar> {
|
||||
private final List<JsVar> vars;
|
||||
|
||||
private final boolean multiline;
|
||||
private boolean multiline;
|
||||
|
||||
public JsVars() {
|
||||
this(new SmartList<JsVar>(), false);
|
||||
this(new SmartList<>(), false);
|
||||
}
|
||||
|
||||
public JsVars(boolean multiline) {
|
||||
this(new SmartList<JsVar>(), multiline);
|
||||
this(new SmartList<>(), multiline);
|
||||
}
|
||||
|
||||
public JsVars(List<JsVar> vars, boolean multiline) {
|
||||
@@ -34,17 +34,21 @@ public class JsVars extends SourceInfoAwareJsNode implements JsStatement, Iterab
|
||||
}
|
||||
|
||||
public JsVars(JsVar var) {
|
||||
this(new SmartList<JsVar>(var), false);
|
||||
this(new SmartList<>(var), false);
|
||||
}
|
||||
|
||||
public JsVars(JsVar... vars) {
|
||||
this(new SmartList<JsVar>(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 <code>var</code> statement.
|
||||
*/
|
||||
@@ -138,6 +142,7 @@ public class JsVars extends SourceInfoAwareJsNode implements JsStatement, Iterab
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Iterator<JsVar> iterator() {
|
||||
return vars.iterator();
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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<TranslationUnit.SourceFile>().map { it.file })
|
||||
checkSourceMap(outputFile, translationResult.program)
|
||||
}
|
||||
|
||||
protected fun processJsProgram(program: JsProgram, psiFiles: List<KtFile>) {
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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<SourceMapBuilder, Object> {
|
||||
public class SourceMapBuilderConsumer implements PairConsumer<SourceMapBuilder, Object> {
|
||||
@Override
|
||||
public void consume(SourceMapBuilder builder, Object sourceInfo) {
|
||||
if (sourceInfo instanceof PsiElement) {
|
||||
|
||||
+1
-8
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user