From 1d0025c3cf81620526598c9aa8f01e70cc553dcd Mon Sep 17 00:00:00 2001 From: Sergej Jaskiewicz Date: Thu, 29 Sep 2022 15:06:42 +0200 Subject: [PATCH] [JS] Support the 'names' field in sourcemaps --- .../js/transformers/irToJs/jsAstUtils.kt | 8 ++-- .../backend/js/utils/JsGenerationContext.kt | 4 +- .../kotlin/js/backend/ast/JsLocation.kt | 10 +++- .../kotlin/js/parser/sourcemaps/SourceMap.kt | 11 +++-- .../sourcemaps/SourceMapLocationRemapper.kt | 6 +-- .../js/parser/sourcemaps/SourceMapParser.kt | 29 ++++++++--- .../kotlin/js/sourceMap/SourceMap3Builder.kt | 48 ++++++++++++++++--- .../js/sourceMap/SourceMapBuilderConsumer.kt | 5 +- .../sourceMap/SourceMapMappingConsumer.java | 2 +- 9 files changed, 92 insertions(+), 31 deletions(-) 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 772a0963554..fb42dd62331 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 @@ -522,17 +522,17 @@ object JsAstUtils { } } -internal fun T.withSource(node: IrElement, context: JsGenerationContext): T { - addSourceInfoIfNeed(node, context) +internal fun T.withSource(node: IrElement, context: JsGenerationContext, originalName: String? = null): T { + addSourceInfoIfNeed(node, context, originalName) return this } @Suppress("NOTHING_TO_INLINE") -private inline fun T.addSourceInfoIfNeed(node: IrElement, context: JsGenerationContext) { +private inline fun T.addSourceInfoIfNeed(node: IrElement, context: JsGenerationContext, originalName: String?) { val sourceMapsInfo = context.staticContext.backendContext.sourceMapsInfo ?: return - val location = context.getLocationForIrElement(node) ?: return + val location = context.getLocationForIrElement(node, originalName) ?: return val isNodeFromCurrentModule = context.currentFile.module.descriptor == context.staticContext.backendContext.module 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 9ab08f7bd8b..c8a85aea25d 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 @@ -87,9 +87,9 @@ class JsGenerationContext( fun checkIfHasAssociatedJsCode(symbol: IrFunctionSymbol): Boolean = staticContext.backendContext.getJsCodeForFunction(symbol) != null - fun getLocationForIrElement(irElement: IrElement): JsLocation? { + fun getLocationForIrElement(irElement: IrElement, originalName: String? = null): JsLocation? { return locationCache.getOrPut(irElement.startOffset) { irElement.getSourceInfo(currentFile.fileEntry) ?: return null - } + }.copy(name = originalName) } } diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt index 89c4f333eb2..f948f62610c 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt @@ -18,10 +18,11 @@ package org.jetbrains.kotlin.js.backend.ast import java.io.Reader -data class JsLocation( +data class JsLocation @JvmOverloads constructor( override val file: String, override val startLine: Int, - override val startChar: Int + override val startChar: Int, + override val name: String? = null ) : JsLocationWithSource { override val fileIdentity: Any? get() = null @@ -36,6 +37,11 @@ interface JsLocationWithSource { val startLine: Int val startChar: Int + /** + * The original name of the entity in the source code that this JS node was generated from. + */ + val name: String? + /** * An object to distinguish different files with the same paths */ diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt index 6ecedbf14da..50e2ae43cb2 100644 --- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt @@ -28,7 +28,8 @@ class SourceMap(val sourceContentResolver: (String) -> Reader?) { for ((index, group) in groups.withIndex()) { writer.print("${index + 1}:") for (segment in group.segments) { - writer.print(" ${segment.generatedColumnNumber + 1}:${segment.sourceLineNumber + 1},${segment.sourceColumnNumber + 1}") + val nameIfPresent = if (segment.name != null) "(${segment.name})" else "" + writer.print(" ${segment.generatedColumnNumber + 1}:${segment.sourceLineNumber + 1},${segment.sourceColumnNumber + 1}$nameIfPresent") } writer.println() } @@ -42,8 +43,9 @@ class SourceMap(val sourceContentResolver: (String) -> Reader?) { val generatedLine = generatedLines[index] val segmentsByColumn = group.segments.map { it.generatedColumnNumber to it }.toMap() for (i in generatedLine.indices) { - segmentsByColumn[i]?.let { (_, sourceFile, sourceLine, sourceColumn) -> - writer.print("<$sourceFile:${sourceLine + 1}:${sourceColumn + 1}>") + segmentsByColumn[i]?.let { (_, sourceFile, sourceLine, sourceColumn, name) -> + val nameIfPresent = if (name != null) "($name)" else "" + writer.print("<$sourceFile:${sourceLine + 1}:${sourceColumn + 1}$nameIfPresent>") } writer.print(generatedLine[i]) } @@ -96,7 +98,8 @@ data class SourceMapSegment( val generatedColumnNumber: Int, val sourceFileName: String?, val sourceLineNumber: Int, - val sourceColumnNumber: Int + val sourceColumnNumber: Int, + val name: String?, ) class SourceMapGroup { diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt index dcbb0e0074f..4b2374034f0 100644 --- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt @@ -70,10 +70,10 @@ class SourceMapLocationRemapper(private val sourceMap: SourceMap, private val so val segment = findCorrespondingSegment(node) val sourceFileName = segment?.sourceFileName node.source = if (sourceFileName != null) { - val location = JsLocation(sourceMapPathMapper(sourceFileName), segment.sourceLineNumber, segment.sourceColumnNumber) + val location = + JsLocation(sourceMapPathMapper(sourceFileName), segment.sourceLineNumber, segment.sourceColumnNumber, segment.name) JsLocationWithEmbeddedSource(location, null) { sourceMap.sourceContentResolver(segment.sourceFileName) } - } - else { + } else { null } } diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapParser.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapParser.kt index 4a0e23e7794..a9c00e224b5 100644 --- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapParser.kt +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapParser.kt @@ -86,6 +86,17 @@ object SourceMapParser { } } + val names = jsonObject.properties["names"].let { + if (it != null) { + val namesProperty = it as? JsonArray ?: return SourceMapError("'names' property is not of array type") + namesProperty.elements.map { + (it as? JsonString ?: return SourceMapError("'names' array must contain strings")).value + } + } else { + emptyList() + } + } + val sourcePathToContent = sources.zip(sourcesContent).associate { it } val mappings = jsonObject.properties["mappings"] ?: return SourceMapError("'mappings' property not found") @@ -95,6 +106,7 @@ object SourceMapParser { var sourceLine = 0 var sourceColumn = 0 var sourceIndex = 0 + var nameIndex = 0 val stream = MappingStream(mappings.value) val sourceMap = SourceMap { sourcePathToContent[it]?.let { StringReader(it) } } var currentGroup = SourceMapGroup().also { sourceMap.groups += it } @@ -113,17 +125,20 @@ object SourceMapParser { sourceIndex += stream.readInt() ?: return stream.createError("VLQ-encoded source index expected") sourceLine += stream.readInt() ?: return stream.createError("VLQ-encoded source line expected") sourceColumn += stream.readInt() ?: return stream.createError("VLQ-encoded source column expected") - if (stream.isEncodedInt) { - stream.readInt() ?: return stream.createError("VLQ-encoded name index expected") - } + val name = if (stream.isEncodedInt) { + nameIndex += stream.readInt() ?: return stream.createError("VLQ-encoded name index expected") + if (nameIndex !in names.indices) { + return stream.createError("Name index $nameIndex is out of bounds ${names.indices}") + } + names[nameIndex] + } else null if (sourceIndex !in sources.indices) { return stream.createError("Source index $sourceIndex is out of bounds ${sources.indices}") } - currentGroup.segments += SourceMapSegment(jsColumn, sourceRoot + sources[sourceIndex], sourceLine, sourceColumn) - } - else { - currentGroup.segments += SourceMapSegment(jsColumn, null, -1, -1) + currentGroup.segments += SourceMapSegment(jsColumn, sourceRoot + sources[sourceIndex], sourceLine, sourceColumn, name) + } else { + currentGroup.segments += SourceMapSegment(jsColumn, null, -1, -1, null) } when { diff --git a/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.kt b/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.kt index f877981c146..34a832fd595 100644 --- a/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.kt +++ b/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.kt @@ -17,15 +17,25 @@ class SourceMap3Builder( private val textOutput: TextOutput, private val pathPrefix: String ) : SourceMapBuilder { - private val out = StringBuilder(8192) - private val sources: TObjectIntHashMap = object : TObjectIntHashMap() { - override fun get(key: SourceKey): Int { + + private class ObjectIntHashMap : TObjectIntHashMap() { + override fun get(key: T): Int { val index = index(key) return if (index < 0) -1 else _values[index] } } + + private val out = StringBuilder(8192) + + private val sources = ObjectIntHashMap() private val orderedSources = mutableListOf() private val orderedSourceContentSuppliers = mutableListOf>() + + private val names = ObjectIntHashMap() + private val orderedNames = mutableListOf() + private var previousNameIndex = 0 + private var previousPreviousNameIndex = 0 + private var previousGeneratedColumn = -1 private var previousSourceIndex = 0 private var previousSourceLine = 0 @@ -45,7 +55,9 @@ class SourceMap3Builder( json.properties["file"] = JsonString(generatedFile.name) appendSources(json) appendSourcesContent(json) - json.properties["names"] = JsonArray() + json.properties["names"] = JsonArray( + orderedNames.mapTo(mutableListOf()) { JsonString(it) } + ) json.properties["mappings"] = JsonString(out.toString()) return json.toString() } @@ -97,16 +109,33 @@ class SourceMap3Builder( return sourceIndex } + private fun getNameIndex(name: String): Int { + var nameIndex = names[name] + if (nameIndex == -1) { + nameIndex = orderedNames.size + names.put(name, nameIndex) + orderedNames.add(name) + } + return nameIndex + } + override fun addMapping( source: String, fileIdentity: Any?, sourceContent: Supplier, sourceLine: Int, - sourceColumn: Int + sourceColumn: Int, + name: String?, ) { val sourceIndex = getSourceIndex(source.replace(File.separatorChar, '/'), fileIdentity, sourceContent) - if (!currentMappingIsEmpty && previousSourceIndex == sourceIndex && previousSourceLine == sourceLine && previousSourceColumn == sourceColumn) { + val nameIndex = name?.let(this::getNameIndex) ?: -1 + + if (!currentMappingIsEmpty && + previousSourceIndex == sourceIndex && + previousSourceLine == sourceLine && + previousSourceColumn == sourceColumn + ) { return } @@ -121,6 +150,11 @@ class SourceMap3Builder( Base64VLQ.encode(out, sourceColumn - previousSourceColumn) previousSourceColumn = sourceColumn + if (nameIndex >= 0) { + Base64VLQ.encode(out, nameIndex - previousNameIndex) + previousNameIndex = nameIndex + } + currentMappingIsEmpty = false } @@ -148,11 +182,13 @@ class SourceMap3Builder( previousPreviousSourceIndex = previousSourceIndex previousPreviousSourceLine = previousSourceLine previousPreviousSourceColumn = previousSourceColumn + previousPreviousNameIndex = previousNameIndex } else { out.setLength(previousMappingOffset) previousSourceIndex = previousPreviousSourceIndex previousSourceLine = previousPreviousSourceLine previousSourceColumn = previousPreviousSourceColumn + previousNameIndex = previousPreviousNameIndex } } diff --git a/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMapBuilderConsumer.kt b/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMapBuilderConsumer.kt index 0f6cfa564c3..76f0fe93d19 100644 --- a/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMapBuilderConsumer.kt +++ b/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMapBuilderConsumer.kt @@ -59,7 +59,7 @@ class SourceMapBuilderConsumer( } else { { null } } - mappingConsumer.addMapping(sourceFilePath, null, contentSupplier, startLine, startChar) + mappingConsumer.addMapping(sourceFilePath, null, contentSupplier, startLine, startChar, null) } catch (e: IOException) { throw RuntimeException("IO error occurred generating source maps", e) } @@ -85,7 +85,8 @@ class SourceMapBuilderConsumer( sourceInfo.fileIdentity, contentSupplier, sourceInfo.startLine, - sourceInfo.startChar + sourceInfo.startChar, + sourceInfo.name ) } diff --git a/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMapMappingConsumer.java b/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMapMappingConsumer.java index 067151fe9e6..bbfe26b98cd 100644 --- a/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMapMappingConsumer.java +++ b/js/js.sourcemap/src/org/jetbrains/kotlin/js/sourceMap/SourceMapMappingConsumer.java @@ -16,7 +16,7 @@ public interface SourceMapMappingConsumer { void addMapping( @NotNull String source, @Nullable Object fileIdentity, @NotNull Supplier sourceSupplier, - int sourceLine, int sourceColumn + int sourceLine, int sourceColumn, @Nullable String name ); void addEmptyMapping();