[JS] Support the 'names' field in sourcemaps

This commit is contained in:
Sergej Jaskiewicz
2022-09-29 15:06:42 +02:00
committed by Space Team
parent 227864c6ec
commit 1d0025c3cf
9 changed files with 92 additions and 31 deletions
@@ -522,17 +522,17 @@ object JsAstUtils {
} }
} }
internal fun <T : JsNode> T.withSource(node: IrElement, context: JsGenerationContext): T { internal fun <T : JsNode> T.withSource(node: IrElement, context: JsGenerationContext, originalName: String? = null): T {
addSourceInfoIfNeed(node, context) addSourceInfoIfNeed(node, context, originalName)
return this return this
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline fun <T : JsNode> T.addSourceInfoIfNeed(node: IrElement, context: JsGenerationContext) { private inline fun <T : JsNode> T.addSourceInfoIfNeed(node: IrElement, context: JsGenerationContext, originalName: String?) {
val sourceMapsInfo = context.staticContext.backendContext.sourceMapsInfo ?: return 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 val isNodeFromCurrentModule = context.currentFile.module.descriptor == context.staticContext.backendContext.module
@@ -87,9 +87,9 @@ class JsGenerationContext(
fun checkIfHasAssociatedJsCode(symbol: IrFunctionSymbol): Boolean = staticContext.backendContext.getJsCodeForFunction(symbol) != null 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) { return locationCache.getOrPut(irElement.startOffset) {
irElement.getSourceInfo(currentFile.fileEntry) ?: return null irElement.getSourceInfo(currentFile.fileEntry) ?: return null
} }.copy(name = originalName)
} }
} }
@@ -18,10 +18,11 @@ package org.jetbrains.kotlin.js.backend.ast
import java.io.Reader import java.io.Reader
data class JsLocation( data class JsLocation @JvmOverloads constructor(
override val file: String, override val file: String,
override val startLine: Int, override val startLine: Int,
override val startChar: Int override val startChar: Int,
override val name: String? = null
) : JsLocationWithSource { ) : JsLocationWithSource {
override val fileIdentity: Any? override val fileIdentity: Any?
get() = null get() = null
@@ -36,6 +37,11 @@ interface JsLocationWithSource {
val startLine: Int val startLine: Int
val startChar: 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 * An object to distinguish different files with the same paths
*/ */
@@ -28,7 +28,8 @@ class SourceMap(val sourceContentResolver: (String) -> Reader?) {
for ((index, group) in groups.withIndex()) { for ((index, group) in groups.withIndex()) {
writer.print("${index + 1}:") writer.print("${index + 1}:")
for (segment in group.segments) { 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() writer.println()
} }
@@ -42,8 +43,9 @@ class SourceMap(val sourceContentResolver: (String) -> Reader?) {
val generatedLine = generatedLines[index] val generatedLine = generatedLines[index]
val segmentsByColumn = group.segments.map { it.generatedColumnNumber to it }.toMap() val segmentsByColumn = group.segments.map { it.generatedColumnNumber to it }.toMap()
for (i in generatedLine.indices) { for (i in generatedLine.indices) {
segmentsByColumn[i]?.let { (_, sourceFile, sourceLine, sourceColumn) -> segmentsByColumn[i]?.let { (_, sourceFile, sourceLine, sourceColumn, name) ->
writer.print("<$sourceFile:${sourceLine + 1}:${sourceColumn + 1}>") val nameIfPresent = if (name != null) "($name)" else ""
writer.print("<$sourceFile:${sourceLine + 1}:${sourceColumn + 1}$nameIfPresent>")
} }
writer.print(generatedLine[i]) writer.print(generatedLine[i])
} }
@@ -96,7 +98,8 @@ data class SourceMapSegment(
val generatedColumnNumber: Int, val generatedColumnNumber: Int,
val sourceFileName: String?, val sourceFileName: String?,
val sourceLineNumber: Int, val sourceLineNumber: Int,
val sourceColumnNumber: Int val sourceColumnNumber: Int,
val name: String?,
) )
class SourceMapGroup { class SourceMapGroup {
@@ -70,10 +70,10 @@ class SourceMapLocationRemapper(private val sourceMap: SourceMap, private val so
val segment = findCorrespondingSegment(node) val segment = findCorrespondingSegment(node)
val sourceFileName = segment?.sourceFileName val sourceFileName = segment?.sourceFileName
node.source = if (sourceFileName != null) { 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) } JsLocationWithEmbeddedSource(location, null) { sourceMap.sourceContentResolver(segment.sourceFileName) }
} } else {
else {
null null
} }
} }
@@ -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 sourcePathToContent = sources.zip(sourcesContent).associate { it }
val mappings = jsonObject.properties["mappings"] ?: return SourceMapError("'mappings' property not found") val mappings = jsonObject.properties["mappings"] ?: return SourceMapError("'mappings' property not found")
@@ -95,6 +106,7 @@ object SourceMapParser {
var sourceLine = 0 var sourceLine = 0
var sourceColumn = 0 var sourceColumn = 0
var sourceIndex = 0 var sourceIndex = 0
var nameIndex = 0
val stream = MappingStream(mappings.value) val stream = MappingStream(mappings.value)
val sourceMap = SourceMap { sourcePathToContent[it]?.let { StringReader(it) } } val sourceMap = SourceMap { sourcePathToContent[it]?.let { StringReader(it) } }
var currentGroup = SourceMapGroup().also { sourceMap.groups += 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") sourceIndex += stream.readInt() ?: return stream.createError("VLQ-encoded source index expected")
sourceLine += stream.readInt() ?: return stream.createError("VLQ-encoded source line expected") sourceLine += stream.readInt() ?: return stream.createError("VLQ-encoded source line expected")
sourceColumn += stream.readInt() ?: return stream.createError("VLQ-encoded source column expected") sourceColumn += stream.readInt() ?: return stream.createError("VLQ-encoded source column expected")
if (stream.isEncodedInt) { val name = if (stream.isEncodedInt) {
stream.readInt() ?: return stream.createError("VLQ-encoded name index expected") 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) { if (sourceIndex !in sources.indices) {
return stream.createError("Source index $sourceIndex is out of bounds ${sources.indices}") return stream.createError("Source index $sourceIndex is out of bounds ${sources.indices}")
} }
currentGroup.segments += SourceMapSegment(jsColumn, sourceRoot + sources[sourceIndex], sourceLine, sourceColumn) currentGroup.segments += SourceMapSegment(jsColumn, sourceRoot + sources[sourceIndex], sourceLine, sourceColumn, name)
} } else {
else { currentGroup.segments += SourceMapSegment(jsColumn, null, -1, -1, null)
currentGroup.segments += SourceMapSegment(jsColumn, null, -1, -1)
} }
when { when {
@@ -17,15 +17,25 @@ class SourceMap3Builder(
private val textOutput: TextOutput, private val textOutput: TextOutput,
private val pathPrefix: String private val pathPrefix: String
) : SourceMapBuilder { ) : SourceMapBuilder {
private val out = StringBuilder(8192)
private val sources: TObjectIntHashMap<SourceKey> = object : TObjectIntHashMap<SourceKey>() { private class ObjectIntHashMap<T> : TObjectIntHashMap<T>() {
override fun get(key: SourceKey): Int { override fun get(key: T): Int {
val index = index(key) val index = index(key)
return if (index < 0) -1 else _values[index] return if (index < 0) -1 else _values[index]
} }
} }
private val out = StringBuilder(8192)
private val sources = ObjectIntHashMap<SourceKey>()
private val orderedSources = mutableListOf<String>() private val orderedSources = mutableListOf<String>()
private val orderedSourceContentSuppliers = mutableListOf<Supplier<Reader?>>() private val orderedSourceContentSuppliers = mutableListOf<Supplier<Reader?>>()
private val names = ObjectIntHashMap<String>()
private val orderedNames = mutableListOf<String>()
private var previousNameIndex = 0
private var previousPreviousNameIndex = 0
private var previousGeneratedColumn = -1 private var previousGeneratedColumn = -1
private var previousSourceIndex = 0 private var previousSourceIndex = 0
private var previousSourceLine = 0 private var previousSourceLine = 0
@@ -45,7 +55,9 @@ class SourceMap3Builder(
json.properties["file"] = JsonString(generatedFile.name) json.properties["file"] = JsonString(generatedFile.name)
appendSources(json) appendSources(json)
appendSourcesContent(json) appendSourcesContent(json)
json.properties["names"] = JsonArray() json.properties["names"] = JsonArray(
orderedNames.mapTo(mutableListOf()) { JsonString(it) }
)
json.properties["mappings"] = JsonString(out.toString()) json.properties["mappings"] = JsonString(out.toString())
return json.toString() return json.toString()
} }
@@ -97,16 +109,33 @@ class SourceMap3Builder(
return sourceIndex 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( override fun addMapping(
source: String, source: String,
fileIdentity: Any?, fileIdentity: Any?,
sourceContent: Supplier<Reader?>, sourceContent: Supplier<Reader?>,
sourceLine: Int, sourceLine: Int,
sourceColumn: Int sourceColumn: Int,
name: String?,
) { ) {
val sourceIndex = getSourceIndex(source.replace(File.separatorChar, '/'), fileIdentity, sourceContent) 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 return
} }
@@ -121,6 +150,11 @@ class SourceMap3Builder(
Base64VLQ.encode(out, sourceColumn - previousSourceColumn) Base64VLQ.encode(out, sourceColumn - previousSourceColumn)
previousSourceColumn = sourceColumn previousSourceColumn = sourceColumn
if (nameIndex >= 0) {
Base64VLQ.encode(out, nameIndex - previousNameIndex)
previousNameIndex = nameIndex
}
currentMappingIsEmpty = false currentMappingIsEmpty = false
} }
@@ -148,11 +182,13 @@ class SourceMap3Builder(
previousPreviousSourceIndex = previousSourceIndex previousPreviousSourceIndex = previousSourceIndex
previousPreviousSourceLine = previousSourceLine previousPreviousSourceLine = previousSourceLine
previousPreviousSourceColumn = previousSourceColumn previousPreviousSourceColumn = previousSourceColumn
previousPreviousNameIndex = previousNameIndex
} else { } else {
out.setLength(previousMappingOffset) out.setLength(previousMappingOffset)
previousSourceIndex = previousPreviousSourceIndex previousSourceIndex = previousPreviousSourceIndex
previousSourceLine = previousPreviousSourceLine previousSourceLine = previousPreviousSourceLine
previousSourceColumn = previousPreviousSourceColumn previousSourceColumn = previousPreviousSourceColumn
previousNameIndex = previousPreviousNameIndex
} }
} }
@@ -59,7 +59,7 @@ class SourceMapBuilderConsumer(
} else { } else {
{ null } { null }
} }
mappingConsumer.addMapping(sourceFilePath, null, contentSupplier, startLine, startChar) mappingConsumer.addMapping(sourceFilePath, null, contentSupplier, startLine, startChar, null)
} catch (e: IOException) { } catch (e: IOException) {
throw RuntimeException("IO error occurred generating source maps", e) throw RuntimeException("IO error occurred generating source maps", e)
} }
@@ -85,7 +85,8 @@ class SourceMapBuilderConsumer(
sourceInfo.fileIdentity, sourceInfo.fileIdentity,
contentSupplier, contentSupplier,
sourceInfo.startLine, sourceInfo.startLine,
sourceInfo.startChar sourceInfo.startChar,
sourceInfo.name
) )
} }
@@ -16,7 +16,7 @@ public interface SourceMapMappingConsumer {
void addMapping( void addMapping(
@NotNull String source, @Nullable Object fileIdentity, @NotNull Supplier<Reader> sourceSupplier, @NotNull String source, @Nullable Object fileIdentity, @NotNull Supplier<Reader> sourceSupplier,
int sourceLine, int sourceColumn int sourceLine, int sourceColumn, @Nullable String name
); );
void addEmptyMapping(); void addEmptyMapping();