Add tests for source maps in JS BE

This commit is contained in:
Alexey Andreev
2017-05-02 17:48:42 +03:00
parent f8914013ef
commit 9218b61141
6 changed files with 407 additions and 1 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* 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.
@@ -145,6 +145,7 @@ import org.jetbrains.kotlin.jps.build.*
import org.jetbrains.kotlin.jps.build.android.AbstractAndroidJpsTestCase
import org.jetbrains.kotlin.jps.incremental.AbstractProtoComparisonTest
import org.jetbrains.kotlin.js.test.AbstractDceTest
import org.jetbrains.kotlin.js.test.AbstractJsLineNumberTest
import org.jetbrains.kotlin.js.test.semantics.*
import org.jetbrains.kotlin.jvm.compiler.*
import org.jetbrains.kotlin.jvm.runtime.AbstractJvm8RuntimeDescriptorLoaderTest
@@ -1356,6 +1357,10 @@ fun main(args: Array<String>) {
testClass<AbstractDceTest> {
model("dce/", pattern = "(.+)\\.js", targetBackend = TargetBackend.JS)
}
testClass<AbstractJsLineNumberTest> {
model("lineNumbers/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS)
}
}
testGroup("js/js.tests/test", "compiler/testData") {
@@ -0,0 +1,118 @@
/*
* 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
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiManager
import junit.framework.TestCase
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
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.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.LineCollector
import org.jetbrains.kotlin.js.test.utils.LineOutputToStringVisitor
import org.jetbrains.kotlin.js.util.TextOutputImpl
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import java.nio.charset.Charset
abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() {
fun doTest(filePath: String) {
val translator = K2JSTranslator(createConfig())
val unit = TranslationUnit.SourceFile(createPsiFile(filePath))
val translationResult = translator.translateUnits(listOf(unit), MainCallParameters.noCall())
if (translationResult !is TranslationResult.Success) {
val outputStream = ByteArrayOutputStream()
val collector = PrintingMessageCollector(PrintStream(outputStream), MessageRenderer.PLAIN_FULL_PATHS, true)
AnalyzerWithCompilerReport.reportDiagnostics(translationResult.diagnostics, collector)
val messages = outputStream.toByteArray().toString(Charset.forName("UTF-8"))
throw AssertionError("The following errors occurred compiling test:\n" + messages)
}
val lineCollector = LineCollector()
lineCollector.accept(translationResult.program)
val programOutput = TextOutputImpl()
translationResult.program.globalBlock.accept(LineOutputToStringVisitor(programOutput, lineCollector))
val generatedCode = programOutput.toString()
val relativePath = File(filePath).relativeTo(File(BASE_PATH)).path.removeSuffix(".kt")
with(File(File(OUT_PATH), relativePath + ".js")) {
parentFile.mkdirs()
writeText(generatedCode)
}
val sourceCode = FileUtil.loadFile(File(filePath))
val linesMatcher = LINES_PATTERN.find(sourceCode) ?:
error("'// LINES: ' comment was not found in source file. Generated code is:\n$generatedCode")
val expectedLines = linesMatcher.groups[1]!!.value
val actualLines = lineCollector.lines
.dropLastWhile { it == null }
.joinToString(" ") { if (it == null) "*" else (it + 1).toString() }
TestCase.assertEquals(generatedCode, expectedLines, actualLines)
}
override fun createEnvironment(): KotlinCoreEnvironment {
return KotlinCoreEnvironment.createForTests(testRootDisposable, CompilerConfiguration(), EnvironmentConfigFiles.JS_CONFIG_FILES)
}
private fun createConfig(): JsConfig {
val configuration = environment.configuration.copy()
configuration.put(JSConfigurationKeys.LIBRARIES, JsConfig.JS_STDLIB + JsConfig.JS_KOTLIN_TEST)
configuration.put(CommonConfigurationKeys.MODULE_NAME, "test")
configuration.put(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN)
configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.v5)
configuration.put(JSConfigurationKeys.SOURCE_MAP, true)
return JsConfig(project, configuration)
}
private fun createPsiFile(fileName: String): KtFile {
val psiManager = PsiManager.getInstance(project)
val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL)
return psiManager.findFile(fileSystem.findFileByPath(fileName)!!) as KtFile
}
companion object {
private val DIR_NAME = "lineNumbers"
private val LINES_PATTERN = Regex("^ *// *LINES: *(.*)$", RegexOption.MULTILINE)
private val BASE_PATH = "${BasicBoxTest.TEST_DATA_DIR_PATH}/$DIR_NAME"
private val OUT_PATH = "${BasicBoxTest.TEST_DATA_DIR_PATH}/out/$DIR_NAME"
}
}
@@ -0,0 +1,44 @@
/*
* 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;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("js/js.translator/testData/lineNumbers")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class JsLineNumberTestGenerated extends AbstractJsLineNumberTest {
public void testAllFilesPresentInLineNumbers() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/lineNumbers"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS, true);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/lineNumbers/simple.kt");
doTest(fileName);
}
}
@@ -0,0 +1,142 @@
/*
* 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 com.intellij.psi.PsiElement
import org.jetbrains.kotlin.js.backend.ast.*
class LineCollector : RecursiveJsVisitor() {
val lines = mutableListOf<Int?>()
private var currentStatement: JsStatement? = null
val lineNumbersByStatement = mutableMapOf<JsStatement, MutableList<Int>>()
val statementsWithoutLineNumbers = mutableSetOf<JsStatement>()
override fun visitElement(node: JsNode) {
handleNodeLocation(node)
super.visitElement(node)
}
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)
currentStatement?.let {
val linesByStatement = lineNumbersByStatement.getOrPut(it, ::mutableListOf)
if (linesByStatement.lastOrNull() != line) {
linesByStatement += line
lines += line
}
}
}
}
override fun visitExpressionStatement(x: JsExpressionStatement) {
val expression = x.expression
if (expression is JsFunction) {
expression.accept(this)
}
else {
withStatement(x) {
super.visitExpressionStatement(x)
}
}
}
override fun visitIf(x: JsIf) {
withStatement(x) {
x.ifExpression.accept(this)
}
x.thenStatement.accept(this)
x.elseStatement?.accept(this)
}
override fun visitWhile(x: JsWhile) {
withStatement(x) {
x.condition.accept(this)
}
x.body.accept(this)
}
override fun visitDoWhile(x: JsDoWhile) {
withStatement(x) {
x.condition.accept(this)
}
x.body.accept(this)
}
override fun visitFor(x: JsFor) {
withStatement(x) {
x.initExpression?.accept(this)
x.initVars?.accept(this)
x.condition?.accept(this)
x.incrementExpression?.accept(this)
}
x.body?.accept(this)
}
override fun visitBreak(x: JsBreak) {
withStatement(x) {
super.visitBreak(x)
}
}
override fun visitContinue(x: JsContinue) {
withStatement(x) {
super.visitContinue(x)
}
}
override fun visitReturn(x: JsReturn) {
withStatement(x) {
super.visitReturn(x)
}
}
override fun visitVars(x: JsVars) {
withStatement(x) {
super.visitVars(x)
}
}
override fun visit(x: JsSwitch) {
withStatement(x) {
x.expression.accept(this)
}
x.cases.forEach { accept(it) }
}
override fun visitThrow(x: JsThrow) {
withStatement(x) {
super.visitThrow(x)
}
}
private fun withStatement(statement: JsStatement, action: () -> Unit) {
val oldStatement = currentStatement
currentStatement = statement
action()
if (statement !in lineNumbersByStatement && lines.lastOrNull() != null) {
lines.add(null)
statementsWithoutLineNumbers += statement
}
currentStatement = oldStatement
}
}
@@ -0,0 +1,91 @@
/*
* 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.JsToStringGenerationVisitor
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.util.TextOutput
class LineOutputToStringVisitor(output: TextOutput, val lineCollector: LineCollector) :
JsToStringGenerationVisitor(output) {
override fun visitIf(x: JsIf) {
printLineNumbers(x)
super.visitIf(x)
}
override fun visitExpressionStatement(x: JsExpressionStatement) {
printLineNumbers(x)
super.visitExpressionStatement(x)
}
override fun visitWhile(x: JsWhile) {
printLineNumbers(x)
super.visitWhile(x)
}
override fun visitDoWhile(x: JsDoWhile) {
printLineNumbers(x)
super.visitDoWhile(x)
}
override fun visitBreak(x: JsBreak) {
printLineNumbers(x)
super.visitBreak(x)
}
override fun visitContinue(x: JsContinue) {
printLineNumbers(x)
super.visitContinue(x)
}
override fun visitFor(x: JsFor) {
printLineNumbers(x)
super.visitFor(x)
}
override fun visitVars(x: JsVars) {
printLineNumbers(x)
super.visitVars(x)
}
override fun visitThrow(x: JsThrow) {
printLineNumbers(x)
super.visitThrow(x)
}
override fun visitReturn(x: JsReturn) {
printLineNumbers(x)
super.visitReturn(x)
}
override fun visit(x: JsSwitch) {
printLineNumbers(x)
super.visit(x)
}
private fun printLineNumbers(statement: JsStatement) {
if (statement in lineCollector.statementsWithoutLineNumbers) {
p.print("/* no-line */ ")
}
else if (statement in lineCollector.lineNumbersByStatement) {
p.print("/* ")
p.print(lineCollector.lineNumbersByStatement[statement]!!.joinToString(" ") { (it + 1).toString() })
p.print(" */ ")
}
}
}
+6
View File
@@ -0,0 +1,6 @@
fun box() {
println("foo")
println("bar")
}
// LINES: 2 3