diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index a04d69b8b60..cffec22ce65 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -42,6 +42,7 @@ import org.jetbrains.kotlin.ir.backend.js.ic.buildCache import org.jetbrains.kotlin.ir.backend.js.ic.checkCaches import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory +import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult import org.jetbrains.kotlin.js.config.* import org.jetbrains.kotlin.library.KLIB_FILE_EXTENSION import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion @@ -221,17 +222,25 @@ class K2JsIrCompiler : CLICompiler() { // Run analysis if main module is sources lateinit var sourceModule: ModulesStructure if (arguments.includes == null) { - sourceModule = prepareAnalyzedSourceModule( - projectJs, - environmentForJS.getSourceFiles(), - configurationJs, - libraries, - friendLibraries, - AnalyzerWithCompilerReport(config.configuration), - icUseGlobalSignatures = icCaches.isNotEmpty(), - icUseStdlibCache = icCaches.isNotEmpty(), - icCache = if (icCaches.isNotEmpty()) checkCaches(libraries, icCaches, skipLib = arguments.includes).data else emptyMap() - ) + do { + sourceModule = prepareAnalyzedSourceModule( + projectJs, + environmentForJS.getSourceFiles(), + configurationJs, + libraries, + friendLibraries, + AnalyzerWithCompilerReport(config.configuration), + icUseGlobalSignatures = icCaches.isNotEmpty(), + icUseStdlibCache = icCaches.isNotEmpty(), + icCache = if (icCaches.isNotEmpty()) checkCaches(libraries, icCaches, skipLib = arguments.includes).data else emptyMap() + ) + val result = sourceModule.jsFrontEndResult.jsAnalysisResult + if (result is JsAnalysisResult.RetryWithAdditionalRoots) { + environmentForJS.addKotlinSourceRoots(result.additionalKotlinRoots) + } + } while (result is JsAnalysisResult.RetryWithAdditionalRoots) + if (!sourceModule.jsFrontEndResult.jsAnalysisResult.shouldGenerateCode) + return OK } if (arguments.irProduceKlibDir || arguments.irProduceKlibFile) { diff --git a/js/js.tests/test/org/jetbrains/kotlin/integration/JsIrAnalysisHandlerExtensionTest.kt b/js/js.tests/test/org/jetbrains/kotlin/integration/JsIrAnalysisHandlerExtensionTest.kt new file mode 100644 index 00000000000..f9190b738c7 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/integration/JsIrAnalysisHandlerExtensionTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.integration + +import com.intellij.mock.MockProject +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.cli.common.CLITool +import org.jetbrains.kotlin.cli.js.K2JSCompiler +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.container.ComponentProvider +import org.jetbrains.kotlin.context.ProjectContext +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.extensions.AnalysisHandlerExtension +import org.jetbrains.kotlin.test.CompilerTestUtil +import org.jetbrains.kotlin.test.TestCaseWithTmpdir +import java.io.File +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +private data class TestKtFile( + val name: String, + val content: String +) + +private val classNotFound = TestKtFile("C.kt", "class C : ClassNotFound") +private val repeatedAnalysis = TestKtFile("D.kt", "class D : Generated") + +class JsIrAnalysisHandlerExtensionTest : TestCaseWithTmpdir() { + + // Writes the service file only; CustomComponentRegistrar is already in classpath. + private fun writePlugin(): String { + val jarFile = tmpdir.resolve("plugin.jar") + ZipOutputStream(jarFile.outputStream()).use { + val entry = ZipEntry("META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar") + it.putNextEntry(entry) + it.write(CustomComponentRegistrar::class.java.name.toByteArray()) + } + return jarFile.absolutePath + } + + private val jsirStdlib: String? + get() = System.getProperty("kotlin.js.full.stdlib.path") + + private val wasmStdlib: String? + get() = System.getProperty("kotlin.wasm.stdlib.path") + + private val outjs: String + get() = tmpdir.resolve("out.js").absolutePath + + private val outklib: String + get() = tmpdir.resolve("out.klib").absolutePath + + private fun runTest(compiler: CLITool<*>, src: TestKtFile, libs: String, outFile: String, extras: List = emptyList()) { + val mainKt = tmpdir.resolve(src.name).apply { + writeText(src.content) + } + val plugin = writePlugin() + val args = listOf( + "-Xplugin=$plugin", + "-libraries", libs, + "-output", outFile, + mainKt.absolutePath) + CompilerTestUtil.executeCompilerAssertSuccessful(compiler, args + extras) + } + + fun testShouldNotGenerateCodeJs() { + if (jsirStdlib != null) + runTest(K2JSCompiler(), classNotFound, jsirStdlib!!, outjs, listOf("-Xir-produce-js")) + } + + fun testShouldNotGenerateCodeKlib() { + if (jsirStdlib != null) + runTest(K2JSCompiler(), classNotFound, jsirStdlib!!, outklib, listOf("-Xir-produce-klib-file")) + } + + fun testShouldNotGenerateCodeWasm() { + if (jsirStdlib != null && wasmStdlib != null) + runTest(K2JSCompiler(), classNotFound, "$jsirStdlib,$wasmStdlib", outjs, listOf("-Xir-produce-js", "-Xwasm")) + } + + fun testRepeatedAnalysisJs() { + if (jsirStdlib != null) + runTest(K2JSCompiler(), repeatedAnalysis, jsirStdlib!!, outjs, listOf("-Xir-produce-js")) + } + + fun testRepeatedAnalysisKlib() { + if (jsirStdlib != null) + runTest(K2JSCompiler(), repeatedAnalysis, jsirStdlib!!, outklib, listOf("-Xir-produce-klib-file")) + } + + fun testRepeatedAnalysisWasm() { + if (jsirStdlib != null && wasmStdlib != null) + runTest(K2JSCompiler(), repeatedAnalysis, "$jsirStdlib,$wasmStdlib", outjs, listOf("-Xir-produce-js", "-Xwasm")) + } +} + +class CustomComponentRegistrar : ComponentRegistrar { + override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { + AnalysisHandlerExtension.registerExtension(project, CustomAnalysisHandler()) + } +} + +// Assume that the directory of input files is writeable! +private class CustomAnalysisHandler : AnalysisHandlerExtension { + override fun doAnalysis( + project: Project, + module: ModuleDescriptor, + projectContext: ProjectContext, + files: Collection, + bindingTrace: BindingTrace, + componentProvider: ComponentProvider + ): AnalysisResult? { + val filenames = files.map { it.name } + if (repeatedAnalysis.name in filenames) { + if ("Generated.kt" in filenames) { + return null + } else { + val outDir = File(files.single { it.name == repeatedAnalysis.name }.virtualFilePath).parentFile + outDir.resolve("Generated.kt").apply { + writeText("interface Generated") + } + return AnalysisResult.RetryWithAdditionalRoots(BindingContext.EMPTY, module, emptyList(), listOf(outDir)) + } + } + return AnalysisResult.success(BindingContext.EMPTY, module, false) + } +} \ No newline at end of file