Allow to embed source files into JS source maps

This commit is contained in:
Alexey Andreev
2017-06-14 14:03:37 +03:00
parent 73c37ecd25
commit a0e1bde594
40 changed files with 491 additions and 81 deletions
@@ -16,8 +16,31 @@
package org.jetbrains.kotlin.js.backend.ast
import java.io.Reader
data class JsLocation(
val file: String,
val startLine: Int,
val startChar: Int
)
override val file: String,
override val startLine: Int,
override val startChar: Int
) : JsLocationWithSource {
override val identityObject: Any? = null
override val sourceProvider: () -> Reader? = { null }
override fun asSimpleLocation(): JsLocation = this
}
interface JsLocationWithSource {
val file: String
val startLine: Int
val startChar: Int
val identityObject: Any?
val sourceProvider: () -> Reader?
fun asSimpleLocation(): JsLocation
}
class JsLocationWithEmbeddedSource(
private val location: JsLocation,
override val identityObject: Any?,
override val sourceProvider: () -> Reader?
) : JsLocationWithSource by location
@@ -35,6 +35,9 @@ public class JSConfigurationKeys {
public static final CompilerConfigurationKey<List<String>> SOURCE_MAP_SOURCE_ROOTS =
CompilerConfigurationKey.create("base directories used to calculate relative paths for source map");
public static final CompilerConfigurationKey<SourceMapSourceEmbedding> SOURCE_MAP_EMBED_SOURCES =
CompilerConfigurationKey.create("embed source files into source map");
public static final CompilerConfigurationKey<Boolean> META_INFO =
CompilerConfigurationKey.create("generate .meta.js and .kjsm files");
@@ -113,6 +113,11 @@ public class JsConfig {
return configuration.get(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, Collections.singletonList("."));
}
@NotNull
public SourceMapSourceEmbedding getSourceMapContentEmbedding() {
return configuration.get(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, SourceMapSourceEmbedding.INLINING);
}
@NotNull
public List<String> getFriends() {
if (getConfiguration().getBoolean(JSConfigurationKeys.FRIEND_PATHS_DISABLED)) return Collections.emptyList();
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.config;
public enum SourceMapSourceEmbedding {
NEVER,
ALWAYS,
INLINING
}
@@ -16,7 +16,9 @@
package org.jetbrains.kotlin.js.parser.sourcemaps
class SourceMap {
import java.io.Reader
class SourceMap(val sourceContentResolver: (String) -> Reader?) {
val groups = mutableListOf<SourceMapGroup>()
}
@@ -16,10 +16,7 @@
package org.jetbrains.kotlin.js.parser.sourcemaps
import org.jetbrains.kotlin.js.backend.ast.JsLocation
import org.jetbrains.kotlin.js.backend.ast.JsNode
import org.jetbrains.kotlin.js.backend.ast.RecursiveJsVisitor
import org.jetbrains.kotlin.js.backend.ast.SourceInfoAwareJsNode
import org.jetbrains.kotlin.js.backend.ast.*
class SourceMapLocationRemapper(val sourceMaps: Map<String, SourceMap>) {
fun remap(node: JsNode) {
@@ -62,7 +59,8 @@ class SourceMapLocationRemapper(val sourceMaps: Map<String, SourceMap>) {
}
val segment = group.segments[lastSegmentIndex]
node.source = JsLocation(segment.sourceFileName, segment.sourceLineNumber, segment.sourceColumnNumber)
val location = JsLocation(segment.sourceFileName, segment.sourceLineNumber, segment.sourceColumnNumber)
node.source = JsLocationWithEmbeddedSource(location, sourceMap) { sourceMap.sourceContentResolver(segment.sourceFileName) }
return true
}
@@ -38,6 +38,7 @@ import org.json.JSONObject
import org.json.JSONTokener
import java.io.IOException
import java.io.Reader
import java.io.StringReader
object SourceMapParser {
@Throws(IOException::class)
@@ -72,6 +73,20 @@ object SourceMapParser {
emptyList()
}
val sourcesContent: List<String?> = if (jsonObject.has("sourcesContent")) {
val sourcesContentProperty = jsonObject.get("sourcesContent") as? JSONArray ?:
return SourceMapError("'sourcesContent' property is not of array type")
if (sourcesContentProperty.any { it != JSONObject.NULL && it !is String? }) {
return SourceMapError("'sources' array must contain strings")
}
sourcesContentProperty.map { if (it == JSONObject.NULL) null else it as String? }
}
else {
emptyList()
}
val sourcePathToContent = sources.zip(sourcesContent).associate { it }
if (!jsonObject.has("mappings")) return SourceMapError("'mappings' property not found")
val mappings = jsonObject.get("mappings") as? String ?: return SourceMapError("'mappings' property is not of string type")
@@ -80,7 +95,7 @@ object SourceMapParser {
var sourceColumn = 0
var sourceIndex = 0
val stream = MappingStream(mappings)
val sourceMap = SourceMap()
val sourceMap = SourceMap { sourcePathToContent[it]?.let { StringReader(it) } }
var currentGroup = SourceMapGroup().also { sourceMap.groups += it }
while (!stream.isEof) {
@@ -22,11 +22,14 @@ import org.jetbrains.kotlin.protobuf.CodedInputStream
import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.*
import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.Expression.ExpressionCase
import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.Statement.StatementCase
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.io.InputStreamReader
import java.util.*
import org.jetbrains.kotlin.resolve.inline.InlineStrategy as KotlinInlineStrategy
class JsAstDeserializer(program: JsProgram) {
class JsAstDeserializer(program: JsProgram, private val sourceRoots: Iterable<File>) {
private val scope = JsRootScope(program)
private val stringTable = mutableListOf<String>()
private val nameTable = mutableListOf<Name>()
@@ -515,7 +518,15 @@ class JsAstDeserializer(program: JsProgram) {
}
val node = action()
if (deserializedLocation != null) {
node.source = deserializedLocation
val contentFile = sourceRoots
.map { File(it, file) }
.firstOrNull { it.exists() }
node.source = if (contentFile != null) {
JsLocationWithEmbeddedSource(deserializedLocation, null) { InputStreamReader(FileInputStream(contentFile), "UTF-8") }
}
else {
deserializedLocation
}
}
if (shouldUpdateFile) {
fileStack.pop()
@@ -386,7 +386,7 @@ class JsAstSerializer(private val pathResolver: (File) -> String) {
val name = nameRef.name
val qualifier = nameRef.qualifier
if (name != null) {
if (qualifier != null || (nameRef.inlineStrategy?.isInline ?: false)) {
if (qualifier != null || nameRef.inlineStrategy?.isInline == true) {
val nameRefBuilder = NameReference.newBuilder()
nameRefBuilder.nameId = serialize(name)
if (qualifier != null) {
@@ -589,7 +589,7 @@ class JsAstSerializer(private val pathResolver: (File) -> String) {
private fun extractLocation(node: JsNode): JsLocation? {
val source = node.source
return when (source) {
is JsLocation -> source
is JsLocationWithSource -> source.asSimpleLocation()
is PsiElement -> extractLocation(source)
else -> null
}
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.config.EcmaVersion
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding
import org.jetbrains.kotlin.js.dce.DeadCodeElimination
import org.jetbrains.kotlin.js.dce.InputFile
import org.jetbrains.kotlin.js.facade.*
@@ -284,45 +285,63 @@ abstract class BasicBoxTest(
val additionalFiles = globalCommonFiles + localCommonFiles + additionalCommonFiles
val psiFiles = createPsiFiles(testFiles + additionalFiles)
val config = createConfig(module, dependencies, friends, multiModule, additionalMetadata = null)
val sourceDirs = (testFiles + additionalFiles).map { File(it).parent }.distinct()
val config = createConfig(sourceDirs, module, dependencies, friends, multiModule, additionalMetadata = null)
val outputFile = File(outputFileName)
translateFiles(psiFiles.map(TranslationUnit::SourceFile), outputFile, config, outputPrefixFile, outputPostfixFile, mainCallParameters)
if (module.hasFilesToRecompile) {
val incrementalDir = File(outputFile.parentFile, "incremental/" + outputFile.nameWithoutExtension)
val serializedMetadata = mutableListOf<File>()
val translationUnits = kotlinFiles.withIndex().map { (index, file) ->
if (file.recompile) {
TranslationUnit.SourceFile(createPsiFile(file.fileName))
}
else {
serializedMetadata += File(incrementalDir, "$index.$METADATA_EXTENSION")
val astFile = File(incrementalDir, "$index.$AST_EXTENSION")
TranslationUnit.BinaryAst(FileUtil.loadFileBytes(astFile))
}
checkIncrementalCompilation(sourceDirs, module, kotlinFiles, additionalFiles, dependencies, friends, multiModule, outputFile,
outputPrefixFile, outputPostfixFile, mainCallParameters)
}
}
private fun checkIncrementalCompilation(
sourceDirs: List<String>,
module: TestModule,
kotlinFiles: List<TestFile>,
additionalFiles: List<String>,
dependencies: List<String>,
friends: List<String>,
multiModule: Boolean,
outputFile: File,
outputPrefixFile: File?,
outputPostfixFile: File?,
mainCallParameters: MainCallParameters
) {
val incrementalDir = File(outputFile.parentFile, "incremental/" + outputFile.nameWithoutExtension)
val serializedMetadata = mutableListOf<File>()
val translationUnits = kotlinFiles.withIndex().map { (index, file) ->
if (file.recompile) {
TranslationUnit.SourceFile(createPsiFile(file.fileName))
}
val allTranslationUnits = translationUnits + additionalFiles.withIndex().map { (index, _) ->
val astFile = File(incrementalDir, "${index + translationUnits.size}.$AST_EXTENSION")
else {
serializedMetadata += File(incrementalDir, "$index.$METADATA_EXTENSION")
val astFile = File(incrementalDir, "$index.$AST_EXTENSION")
TranslationUnit.BinaryAst(FileUtil.loadFileBytes(astFile))
}
val headerFile = File(incrementalDir, HEADER_FILE)
val recompiledConfig = createConfig(module, dependencies, friends, multiModule, Pair(headerFile,serializedMetadata))
val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js")
translateFiles(allTranslationUnits, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile,
mainCallParameters)
val originalOutput = FileUtil.loadFile(outputFile)
val recompiledOutput = removeRecompiledSuffix(FileUtil.loadFile(recompiledOutputFile))
TestCase.assertEquals("Output file changed after recompilation", originalOutput, recompiledOutput)
val originalSourceMap = FileUtil.loadFile(File(outputFile.parentFile, outputFile.name + ".map"))
val recompiledSourceMap = removeRecompiledSuffix(
FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.name + ".map")))
TestCase.assertEquals("Source map file changed after recompilation", originalSourceMap, recompiledSourceMap)
}
val allTranslationUnits = translationUnits + additionalFiles.withIndex().map { (index, _) ->
val astFile = File(incrementalDir, "${index + translationUnits.size}.$AST_EXTENSION")
TranslationUnit.BinaryAst(FileUtil.loadFileBytes(astFile))
}
val headerFile = File(incrementalDir, HEADER_FILE)
val recompiledConfig = createConfig(sourceDirs, module, dependencies, friends, multiModule, Pair(headerFile,serializedMetadata))
val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js")
translateFiles(allTranslationUnits, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile,
mainCallParameters)
val originalOutput = FileUtil.loadFile(outputFile)
val recompiledOutput = removeRecompiledSuffix(FileUtil.loadFile(recompiledOutputFile))
TestCase.assertEquals("Output file changed after recompilation", originalOutput, recompiledOutput)
val originalSourceMap = FileUtil.loadFile(File(outputFile.parentFile, outputFile.name + ".map"))
val recompiledSourceMap = removeRecompiledSuffix(
FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.name + ".map")))
TestCase.assertEquals("Source map file changed after recompilation", originalSourceMap, recompiledSourceMap)
}
private fun removeRecompiledSuffix(text: String): String = text.replace("-recompiled.js", ".js")
@@ -411,7 +430,7 @@ abstract class BasicBoxTest(
val output = TextOutputImpl()
val pathResolver = SourceFilePathResolver(mutableListOf(File(".")))
val sourceMapBuilder = SourceMap3Builder(outputFile, output, "", SourceMapBuilderConsumer(pathResolver))
val sourceMapBuilder = SourceMap3Builder(outputFile, output, "", SourceMapBuilderConsumer(pathResolver, false, false))
generatedProgram.accept(JsSourceGenerationVisitor(output, sourceMapBuilder))
val code = output.toString()
val generatedSourceMap = sourceMapBuilder.build()
@@ -454,7 +473,9 @@ abstract class BasicBoxTest(
private fun createPsiFiles(fileNames: List<String>): List<KtFile> = fileNames.map(this::createPsiFile)
private fun createConfig(
module: TestModule, dependencies: List<String>, friends: List<String>, multiModule: Boolean, additionalMetadata: Pair<File, List<File>>?
sourceDirs: List<String>,
module: TestModule, dependencies: List<String>, friends: List<String>, multiModule: Boolean,
additionalMetadata: Pair<File, List<File>>?
): JsConfig {
val configuration = environment.configuration.copy()
@@ -475,6 +496,8 @@ abstract class BasicBoxTest(
configuration.put(JSConfigurationKeys.META_INFO, multiModule)
configuration.put(JSConfigurationKeys.SERIALIZE_FRAGMENTS, hasFilesToRecompile)
configuration.put(JSConfigurationKeys.SOURCE_MAP, hasFilesToRecompile || generateSourceMap)
configuration.put(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, sourceDirs)
configuration.put(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, module.sourceMapSourceEmbedding)
if (additionalMetadata != null) {
val metadata = PackagesWithHeaderMetadata(
@@ -570,6 +593,10 @@ abstract class BasicBoxTest(
currentModule.languageVersion = LanguageVersion.fromVersionString(version)
}
SOURCE_MAP_SOURCE_EMBEDDING.find(text)?.let { match ->
currentModule.sourceMapSourceEmbedding = SourceMapSourceEmbedding.valueOf(match.groupValues[1])
}
return TestFile(temporaryFile.absolutePath, currentModule, recompile = RECOMPILE_PATTERN.matcher(text).find())
}
@@ -599,6 +626,7 @@ abstract class BasicBoxTest(
var inliningDisabled = false
val files = mutableListOf<TestFile>()
var languageVersion: LanguageVersion? = null
var sourceMapSourceEmbedding = SourceMapSourceEmbedding.NEVER
val hasFilesToRecompile get() = files.any { it.recompile }
}
@@ -623,6 +651,7 @@ abstract class BasicBoxTest(
private val EXPECTED_REACHABLE_NODES_DIRECTIVE = "EXPECTED_REACHABLE_NODES"
private val EXPECTED_REACHABLE_NODES = Pattern.compile("^// *$EXPECTED_REACHABLE_NODES_DIRECTIVE: *([0-9]+) *$", Pattern.MULTILINE)
private val RECOMPILE_PATTERN = Pattern.compile("^// *RECOMPILE *$", Pattern.MULTILINE)
private val SOURCE_MAP_SOURCE_EMBEDDING = Regex("^// *SOURCE_MAP_EMBED_SOURCES: ([A-Z]+)*\$", RegexOption.MULTILINE)
private val AST_EXTENSION = "jsast"
private val METADATA_EXTENSION = "jsmeta"
private val HEADER_FILE = "header.$METADATA_EXTENSION"
@@ -3539,6 +3539,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
doTest(fileName);
}
@TestMetadata("sourceMapSourceEmbedding.kt")
public void testSourceMapSourceEmbedding() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/sourceMapSourceEmbedding.kt");
doTest(fileName);
}
@TestMetadata("syntheticStatement.kt")
public void testSyntheticStatement() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/syntheticStatement.kt");
@@ -68,7 +68,7 @@ class AmbiguousAstSourcePropagation : RecursiveJsVisitor() {
private fun propagate(node: JsNode) {
if (!sourceDefined) {
val source = node.source
if (source is JsLocation || source is PsiElement) {
if (source is JsLocationWithSource || source is PsiElement) {
sourceDefined = true
}
}
@@ -39,7 +39,7 @@ class LineCollector : RecursiveJsVisitor() {
val document = file.viewProvider.document!!
document.getLineNumber(offset)
}
is JsLocation -> {
is JsLocationWithSource -> {
source.startLine
}
else -> null
@@ -21,19 +21,29 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.PairConsumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.js.backend.ast.JsLocation;
import org.jetbrains.kotlin.js.backend.ast.JsLocationWithSource;
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilder;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.nio.charset.Charset;
import java.util.function.Supplier;
public class SourceMapBuilderConsumer implements PairConsumer<SourceMapBuilder, Object> {
@NotNull
private final SourceFilePathResolver pathResolver;
public SourceMapBuilderConsumer(@NotNull SourceFilePathResolver pathResolver) {
private final boolean provideCurrentModuleContent;
private final boolean provideExternalModuleContent;
public SourceMapBuilderConsumer(
@NotNull SourceFilePathResolver pathResolver,
boolean provideCurrentModuleContent, boolean provideExternalModuleContent
) {
this.pathResolver = pathResolver;
this.provideCurrentModuleContent = provideCurrentModuleContent;
this.provideExternalModuleContent = provideExternalModuleContent;
}
@Override
@@ -49,15 +59,31 @@ public class SourceMapBuilderConsumer implements PairConsumer<SourceMapBuilder,
File file = new File(psiFile.getViewProvider().getVirtualFile().getPath());
try {
builder.addMapping(pathResolver.getPathRelativeToSourceRoots(file), line, column);
Supplier<Reader> contentSupplier;
if (provideCurrentModuleContent) {
contentSupplier = () -> {
try {
return new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"));
}
catch (IOException e) {
return null;
}
};
}
else {
contentSupplier = () -> null;
}
builder.addMapping(pathResolver.getPathRelativeToSourceRoots(file), null, contentSupplier, line, column);
}
catch (IOException e) {
throw new RuntimeException("IO error occurred generating source maps", e);
}
}
else if (sourceInfo instanceof JsLocation) {
JsLocation location = (JsLocation) sourceInfo;
builder.addMapping(location.getFile(), location.getStartLine(), location.getStartChar());
else if (sourceInfo instanceof JsLocationWithSource) {
JsLocationWithSource location = (JsLocationWithSource) sourceInfo;
Supplier<Reader> contentSupplier = provideExternalModuleContent ? location.getSourceProvider()::invoke : () -> null;
builder.addMapping(location.getFile(), location.getIdentityObject(), contentSupplier,
location.getStartLine(), location.getStartChar());
}
}
}
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.js.backend.ast.JsProgram
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding
import org.jetbrains.kotlin.js.sourceMap.JsSourceGenerationVisitor
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
@@ -61,8 +62,13 @@ abstract class TranslationResult protected constructor(val diagnostics: Diagnost
val sourceMapBuilder =
if (config.configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) {
val sourceRoots = config.sourceMapRoots.map { File(it) }
val sourceMapContentEmbedding = config.sourceMapContentEmbedding
val pathResolver = SourceFilePathResolver(sourceRoots)
SourceMap3Builder(outputFile, output, config.sourceMapPrefix, SourceMapBuilderConsumer(pathResolver))
val consumer = SourceMapBuilderConsumer(
pathResolver,
sourceMapContentEmbedding == SourceMapSourceEmbedding.ALWAYS,
sourceMapContentEmbedding != SourceMapSourceEmbedding.NEVER)
SourceMap3Builder(outputFile, output, config.sourceMapPrefix, consumer)
}
else {
null
@@ -16,15 +16,19 @@
package org.jetbrains.kotlin.js.sourceMap;
import org.jetbrains.kotlin.js.common.SourceInfo;
import org.jetbrains.kotlin.js.util.TextOutput;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.PairConsumer;
import gnu.trove.TObjectIntHashMap;
import kotlin.io.TextStreamsKt;
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor;
import org.jetbrains.kotlin.js.common.SourceInfo;
import org.jetbrains.kotlin.js.util.TextOutput;
import java.io.File;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class SourceMap3Builder implements SourceMapBuilder {
private final StringBuilder out = new StringBuilder(8192);
@@ -33,18 +37,16 @@ public class SourceMap3Builder implements SourceMapBuilder {
private final String pathPrefix;
private final PairConsumer<SourceMapBuilder, Object> sourceInfoConsumer;
private String lastSource;
private int lastSourceIndex;
private final TObjectIntHashMap<String> sources = new TObjectIntHashMap<String>() {
private final TObjectIntHashMap<SourceKey> sources = new TObjectIntHashMap<SourceKey>() {
@Override
public int get(String key) {
public int get(SourceKey key) {
int index = index(key);
return index < 0 ? -1 : _values[index];
}
};
private final List<String> orderedSources = new ArrayList<>();
private final List<Supplier<Reader>> orderedSourceContentSuppliers = new ArrayList<>();
private int previousGeneratedColumn = -1;
private int previousSourceIndex;
@@ -68,7 +70,11 @@ public class SourceMap3Builder implements SourceMapBuilder {
public String build() {
StringBuilder sb = new StringBuilder(out.length() + (128 * orderedSources.size()));
sb.append("{\"version\":3,\"file\":\"").append(generatedFile.getName()).append('"').append(',');
appendSources(sb);
sb.append(",");
appendSourcesContent(sb);
sb.append(",\"names\":[");
sb.append("],\"mappings\":\"");
sb.append(out);
@@ -86,7 +92,29 @@ public class SourceMap3Builder implements SourceMapBuilder {
else {
isNotFirst = true;
}
sb.append('"').append(pathPrefix).append(source).append('"');
sb.append(JsToStringGenerationVisitor.javaScriptString(pathPrefix + source, true));
}
sb.append(']');
}
private void appendSourcesContent(StringBuilder sb) {
boolean isNotFirst = false;
sb.append('"').append("sourcesContent").append("\":[");
for (Supplier<Reader> contentSupplier : orderedSourceContentSuppliers) {
if (isNotFirst) {
sb.append(',');
}
else {
isNotFirst = true;
}
Reader reader = contentSupplier.get();
if (reader != null) {
sb.append(JsToStringGenerationVisitor.javaScriptString(TextStreamsKt.readText(reader), true));
}
else {
sb.append("null");
}
}
sb.append(']');
}
@@ -110,26 +138,21 @@ public class SourceMap3Builder implements SourceMapBuilder {
sourceInfoConsumer.consume(this, sourceInfo);
}
private int getSourceIndex(String source) {
if (source.equals(lastSource)) {
return lastSourceIndex;
}
int sourceIndex = sources.get(source);
private int getSourceIndex(String source, Object identityObject, Supplier<Reader> contentSupplier) {
SourceKey key = new SourceKey(source, identityObject);
int sourceIndex = sources.get(key);
if (sourceIndex == -1) {
sourceIndex = orderedSources.size();
sources.put(source, sourceIndex);
sources.put(key, sourceIndex);
orderedSources.add(source);
orderedSourceContentSuppliers.add(contentSupplier);
}
lastSource = source;
lastSourceIndex = sourceIndex;
return sourceIndex;
}
@Override
public void addMapping(String source, int sourceLine, int sourceColumn) {
public void addMapping(String source, Object identityObject, Supplier<Reader> sourceContent, int sourceLine, int sourceColumn) {
source = source.replace(File.separatorChar, '/');
boolean newGroupStarted = previousGeneratedColumn == -1;
if (newGroupStarted) {
@@ -148,7 +171,7 @@ public class SourceMap3Builder implements SourceMapBuilder {
// assert columnDiff != 0;
Base64VLQ.encode(out, columnDiff);
previousGeneratedColumn = textOutput.getColumn();
int sourceIndex = getSourceIndex(source);
int sourceIndex = getSourceIndex(source, identityObject, sourceContent);
Base64VLQ.encode(out, sourceIndex - previousSourceIndex);
previousSourceIndex = sourceIndex;
@@ -200,4 +223,34 @@ public class SourceMap3Builder implements SourceMapBuilder {
while (value > 0);
}
}
static final class SourceKey {
private final String sourcePath;
private final Object identityKey;
SourceKey(String sourcePath, Object identityKey) {
this.sourcePath = sourcePath;
this.identityKey = identityKey;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SourceKey)) return false;
SourceKey key = (SourceKey) o;
if (!sourcePath.equals(key.sourcePath)) return false;
if (identityKey != null ? !identityKey.equals(key.identityKey) : key.identityKey != null) return false;
return true;
}
@Override
public int hashCode() {
int result = sourcePath.hashCode();
result = 31 * result + (identityKey != null ? identityKey.hashCode() : 0);
return result;
}
}
}
@@ -17,13 +17,15 @@
package org.jetbrains.kotlin.js.sourceMap;
import java.io.File;
import java.io.Reader;
import java.util.function.Supplier;
public interface SourceMapBuilder {
void newLine();
void skipLinesAtBeginning(int count);
void addMapping(String source, int sourceLine, int sourceColumn);
void addMapping(String source, Object identityObject, Supplier<Reader> sourceContent, int sourceLine, int sourceColumn);
void processSourceInfo(Object info);
@@ -61,7 +61,9 @@ import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import static org.jetbrains.kotlin.js.translate.general.ModuleWrapperTranslation.wrapIfNecessary;
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.convertToStatement;
@@ -274,7 +276,8 @@ public final class Translation {
Map<KtFile, List<DeclarationDescriptor>> fileMemberScopes = new HashMap<>();
JsAstDeserializer deserializer = new JsAstDeserializer(program);
List<File> sourceRoots = config.getSourceMapRoots().stream().map(File::new).collect(Collectors.toList());
JsAstDeserializer deserializer = new JsAstDeserializer(program, sourceRoots);
for (TranslationUnit unit : units) {
if (unit instanceof TranslationUnit.SourceFile) {
KtFile file = ((TranslationUnit.SourceFile) unit).getFile();
@@ -0,0 +1,12 @@
// EXPECTED_REACHABLE_NODES: 489
// SOURCE_MAP_EMBED_SOURCES: ALWAYS
// FILE: a.kt
fun foo() = "O"
// FILE: b.kt
// RECOMPILE
fun bar() = "K"
// FILE: main.kt
// RECOMPILE
fun box() = foo() + bar()