From 9218b611411ee281c2edac085c4986e6f58aff30 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 2 May 2017 17:48:42 +0300 Subject: [PATCH] Add tests for source maps in JS BE --- .../kotlin/generators/tests/GenerateTests.kt | 7 +- .../js/test/AbstractJsLineNumberTest.kt | 118 +++++++++++++++ .../js/test/JsLineNumberTestGenerated.java | 44 ++++++ .../kotlin/js/test/utils/LineCollector.kt | 142 ++++++++++++++++++ .../test/utils/LineOutputToStringVisitor.kt | 91 +++++++++++ .../testData/lineNumbers/simple.kt | 6 + 6 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsLineNumberTest.kt create mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/test/JsLineNumberTestGenerated.java create mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineCollector.kt create mode 100644 js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineOutputToStringVisitor.kt create mode 100644 js/js.translator/testData/lineNumbers/simple.kt diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index b9262551c68..8223b61d470 100755 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -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) { testClass { model("dce/", pattern = "(.+)\\.js", targetBackend = TargetBackend.JS) } + + testClass { + model("lineNumbers/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS) + } } testGroup("js/js.tests/test", "compiler/testData") { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsLineNumberTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsLineNumberTest.kt new file mode 100644 index 00000000000..b8d37d55815 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsLineNumberTest.kt @@ -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" + } +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/JsLineNumberTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/JsLineNumberTestGenerated.java new file mode 100644 index 00000000000..1edb4afa486 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/JsLineNumberTestGenerated.java @@ -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); + } +} 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 new file mode 100644 index 00000000000..2cad8d7df0e --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineCollector.kt @@ -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() + private var currentStatement: JsStatement? = null + val lineNumbersByStatement = mutableMapOf>() + val statementsWithoutLineNumbers = mutableSetOf() + + 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 + } +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineOutputToStringVisitor.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineOutputToStringVisitor.kt new file mode 100644 index 00000000000..ac99b58f7ad --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineOutputToStringVisitor.kt @@ -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(" */ ") + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/lineNumbers/simple.kt b/js/js.translator/testData/lineNumbers/simple.kt new file mode 100644 index 00000000000..b87b95f6825 --- /dev/null +++ b/js/js.translator/testData/lineNumbers/simple.kt @@ -0,0 +1,6 @@ +fun box() { + println("foo") + println("bar") +} + +// LINES: 2 3 \ No newline at end of file