[JS IR] Support the sourceMapEmbedSources setting (partially)
This doesn't work with external libraries yet (including stdlib).
This commit is contained in:
@@ -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)
|
||||
+1
@@ -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
|
||||
|
||||
|
||||
-21
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-8
@@ -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
-3
@@ -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
|
||||
|
||||
+47
-7
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user