[JS IR] Support the sourceMapEmbedSources setting (partially)

This doesn't work with external libraries yet (including stdlib).
This commit is contained in:
Sergej Jaskiewicz
2022-07-20 17:11:21 +02:00
committed by Space
parent b406722e20
commit b9cac8a4ca
9 changed files with 120 additions and 43 deletions
@@ -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<String>,
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)
@@ -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
@@ -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<String>,
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<String>()),
configuration.get(JSConfigurationKeys.OUTPUT_DIR),
configuration.get(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, SourceMapSourceEmbedding.INLINING),
)
} else {
null
}
}
}
@@ -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 : JsNode> T.withSource(node: IrElement, context: JsGenerationCon
@Suppress("NOTHING_TO_INLINE")
private inline fun <T : JsNode> 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? {
@@ -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
}
}
}
@@ -24,6 +24,4 @@ class JsStaticContext(
val coroutineImplDeclaration = backendContext.ir.symbols.coroutineImpl.owner
val initializerBlock = JsCompositeBlock()
val genSourcemaps = backendContext.configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)
}
@@ -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
@@ -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
@@ -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<KotlinJsCompile>() {
| kotlinOptions {
| sourceMap = true
| sourceMapEmbedSources = "always"
| }
| }
|}
|project("app") {
| tasks.withType<KotlinJsCompile> {
| 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<KotlinJsCompile>() {
| kotlinOptions {
| sourceMap = true
| sourceMapEmbedSources = "always"
| }
| }
|}
|project("app") {
| tasks.withType<KotlinJsCompile> {
| 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<String>) {"
)
assertFileContains(
mapFilePath,
"\"inline fun foo(): String {",
)
}
}
}
@DisplayName("incremental compilation for js works")
@GradleTest
fun testIncrementalCompilation(gradleVersion: GradleVersion) {