diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/SourceMapsInfo.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/SourceMapsInfo.kt new file mode 100644 index 00000000000..ccb623cf797 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/SourceMapsInfo.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2022 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.ir.backend.js + +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding +import java.io.File + +data class SourceMapsInfo( + val sourceMapPrefix: String, + val sourceRoots: List, + val outputDir: File?, + val sourceMapContentEmbedding: SourceMapSourceEmbedding, +) { + companion object { + fun from(configuration: CompilerConfiguration): SourceMapsInfo? = + if (configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) { + SourceMapsInfo( + configuration.get(JSConfigurationKeys.SOURCE_MAP_PREFIX, ""), + configuration.get(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, emptyList()), + configuration.get(JSConfigurationKeys.OUTPUT_DIR), + configuration.get(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, SourceMapSourceEmbedding.INLINING), + ) + } else { + null + } + } +} + +val JsCommonBackendContext.sourceMapsInfo: SourceMapsInfo? + get() = SourceMapsInfo.from(configuration) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt index 70166bea16f..393070eff9f 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsExecutableProducer.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.ic import org.jetbrains.kotlin.ir.backend.js.CompilationOutputs +import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.* import org.jetbrains.kotlin.serialization.js.ModuleKind diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt index 49d539efa1a..c914de57869 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs import org.jetbrains.kotlin.config.CommonConfigurationKeys -import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.ir.backend.js.* import org.jetbrains.kotlin.ir.backend.js.dce.eliminateDeadDeclarations import org.jetbrains.kotlin.ir.backend.js.export.* @@ -471,23 +470,3 @@ fun generateSingleWrappedModuleBody( ) } -data class SourceMapsInfo( - val sourceMapPrefix: String, - val sourceRoots: List, - val outputDir: File?, - val sourceMapContentEmbedding: SourceMapSourceEmbedding, -) { - companion object { - fun from(configuration: CompilerConfiguration): SourceMapsInfo? = - if (configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) { - SourceMapsInfo( - configuration.get(JSConfigurationKeys.SOURCE_MAP_PREFIX, ""), - configuration.get(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, emptyList()), - configuration.get(JSConfigurationKeys.OUTPUT_DIR), - configuration.get(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, SourceMapSourceEmbedding.INLINING), - ) - } else { - null - } - } -} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index c0fa58c570b..422001fcdad 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -9,8 +9,8 @@ import org.jetbrains.kotlin.backend.common.compilationException import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrFileEntry import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET -import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins +import org.jetbrains.kotlin.ir.backend.js.sourceMapsInfo import org.jetbrains.kotlin.ir.backend.js.utils.* import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrDeclaration @@ -23,8 +23,13 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.common.isValidES5Identifier +import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.utils.addIfNotNull +import java.io.FileInputStream +import java.io.IOException +import java.io.InputStreamReader +import java.nio.charset.StandardCharsets fun jsVar(name: JsName, initializer: IrExpression?, context: JsGenerationContext): JsVars { val jsInitializer = initializer?.accept(IrElementToJsExpressionTransformer(), context) @@ -492,17 +497,33 @@ internal fun T.withSource(node: IrElement, context: JsGenerationCon @Suppress("NOTHING_TO_INLINE") private inline fun T.addSourceInfoIfNeed(node: IrElement, context: JsGenerationContext) { - if (!context.staticContext.genSourcemaps) return - var cachedLocation = context.getLocationFromCache(node) - if (cachedLocation == null) { - cachedLocation = node.getSourceInfo(context.currentFile.fileEntry) ?: return - context.saveLocationToCache(node, cachedLocation) - } + val sourceMapsInfo = context.staticContext.backendContext.sourceMapsInfo ?: return + + val location = context.getLocationForIrElement(node) ?: return + + val isNodeFromCurrentModule = context.currentFile.module.descriptor == context.staticContext.backendContext.module // TODO maybe it's better to fix in JsExpressionStatement val locationTarget = if (this is JsExpressionStatement) this.expression else this - locationTarget.source = cachedLocation + + locationTarget.source = when (sourceMapsInfo.sourceMapContentEmbedding) { + SourceMapSourceEmbedding.NEVER -> location + SourceMapSourceEmbedding.INLINING -> if (isNodeFromCurrentModule) location else location.withEmbeddedSource(context) + SourceMapSourceEmbedding.ALWAYS -> location.withEmbeddedSource(context) + } +} + +private fun JsLocation.withEmbeddedSource( + context: JsGenerationContext +) = JsLocationWithEmbeddedSource(this, identityObject = context.currentFile) { + try { + InputStreamReader(FileInputStream(file), StandardCharsets.UTF_8) + } catch (e: IOException) { + // TODO: If the source file is not available at path (e. g. it's an stdlib file), use heuristics to find it. + // If all heuristics fail, use dumpKotlinLike() on freshly deserialized IrFile. + null + } } fun IrElement.getSourceInfo(container: IrDeclaration): JsLocation? { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt index 9e5e54986da..9ab08f7bd8b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.ir.backend.js.utils import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.getSourceInfo import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.IrLoop import org.jetbrains.kotlin.ir.expressions.IrReturnableBlock @@ -86,7 +87,9 @@ class JsGenerationContext( fun checkIfHasAssociatedJsCode(symbol: IrFunctionSymbol): Boolean = staticContext.backendContext.getJsCodeForFunction(symbol) != null - fun getLocationFromCache(node: IrElement) = locationCache[node.startOffset] - - fun saveLocationToCache(node: IrElement, location: JsLocation) = locationCache.put(node.startOffset, location) + fun getLocationForIrElement(irElement: IrElement): JsLocation? { + return locationCache.getOrPut(irElement.startOffset) { + irElement.getSourceInfo(currentFile.fileEntry) ?: return null + } + } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt index cc6bb413c2b..2f9c2e1be27 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsStaticContext.kt @@ -24,6 +24,4 @@ class JsStaticContext( val coroutineImplDeclaration = backendContext.ir.symbols.coroutineImpl.owner val initializerBlock = JsCompositeBlock() - - val genSourcemaps = backendContext.configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP) } diff --git a/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt b/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt index 85d3f37515c..1e2b2569613 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt @@ -20,7 +20,7 @@ import org.jetbrains.kotlin.ir.backend.js.* import org.jetbrains.kotlin.ir.backend.js.ic.* import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrFragmentAndBinaryAst -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.SourceMapsInfo +import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrModuleFragment diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt index 7b26bed6151..a868ae4ec19 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.ir.backend.js.ic.JsExecutableProducer import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformerTmp -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.SourceMapsInfo +import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC import org.jetbrains.kotlin.ir.util.IrMessageLogger diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt index 00eb884ddff..1e26b643b9b 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt @@ -776,17 +776,21 @@ abstract class AbstractKotlin2JsGradlePluginIT(protected val irBackend: Boolean) } } - @DisplayName("sources can be embedded into source map") - @DisabledIf( - "org.jetbrains.kotlin.gradle.AbstractKotlin2JsGradlePluginIT#getIrBackend", - disabledReason = "Source maps are not supported in IR backend" - ) + @DisplayName("sources of all modules are embedded into source map") @GradleTest - fun testKotlinJsSourceMapEmbedSources(gradleVersion: GradleVersion) { + fun testKotlinJsSourceMapEmbedSourcesAlways(gradleVersion: GradleVersion) { project("kotlin2JsProjectWithSourceMap", gradleVersion) { buildGradleKts.appendText( """ - |allprojects { + |project("lib") { + | tasks.withType() { + | kotlinOptions { + | sourceMap = true + | sourceMapEmbedSources = "always" + | } + | } + |} + |project("app") { | tasks.withType { | kotlinOptions.sourceMapEmbedSources = "always" | } @@ -805,6 +809,42 @@ abstract class AbstractKotlin2JsGradlePluginIT(protected val irBackend: Boolean) } } + @DisplayName("only sources of external modules are embedded into source map") + @GradleTest + fun testKotlinJsSourceMapEmbedSourcesInlining(gradleVersion: GradleVersion) { + project("kotlin2JsProjectWithSourceMap", gradleVersion) { + buildGradleKts.appendText( + """ + |project("lib") { + | tasks.withType() { + | kotlinOptions { + | sourceMap = true + | sourceMapEmbedSources = "always" + | } + | } + |} + |project("app") { + | tasks.withType { + | kotlinOptions.sourceMapEmbedSources = "inlining" + | } + |} + | + """.trimMargin() + ) + build(if (irBackend) "compileDevelopmentExecutableKotlinJs" else "compileKotlinJs") { + val mapFilePath = projectPath.resolve("build/js/packages/$projectName-app/kotlin/$projectName-app.js.map") + assertFileDoesNotContain( + mapFilePath, + "\"fun main(args: Array) {" + ) + assertFileContains( + mapFilePath, + "\"inline fun foo(): String {", + ) + } + } + } + @DisplayName("incremental compilation for js works") @GradleTest fun testIncrementalCompilation(gradleVersion: GradleVersion) {