JS: generate paths in source maps relative to .map file location
See KT-19818
This commit is contained in:
@@ -119,7 +119,8 @@ class DeadCodeElimination(private val logConsumer: (DCELogLevel, String) -> Unit
|
||||
val sourceMapFile = File(file.outputPath + ".map")
|
||||
val textOutput = TextOutputImpl()
|
||||
val sourceMapBuilder = SourceMap3Builder(File(file.outputPath), textOutput, "")
|
||||
val consumer = SourceMapBuilderConsumer(sourceMapBuilder, SourceFilePathResolver(mutableListOf()), true, true)
|
||||
val sourcePathResolver = SourceFilePathResolver(mutableListOf(), File(file.outputPath).parentFile)
|
||||
val consumer = SourceMapBuilderConsumer(sourceMapBuilder, sourcePathResolver, true, true)
|
||||
block.accept(JsToStringGenerationVisitor(textOutput, consumer))
|
||||
val sourceMapContent = sourceMapBuilder.build()
|
||||
sourceMapBuilder.addLink()
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider;
|
||||
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer;
|
||||
import org.jetbrains.kotlin.serialization.js.ModuleKind;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public class JSConfigurationKeys {
|
||||
@@ -30,6 +31,9 @@ public class JSConfigurationKeys {
|
||||
public static final CompilerConfigurationKey<Boolean> SOURCE_MAP =
|
||||
CompilerConfigurationKey.create("generate source map");
|
||||
|
||||
public static final CompilerConfigurationKey<File> OUTPUT_DIR =
|
||||
CompilerConfigurationKey.create("output directory");
|
||||
|
||||
public static final CompilerConfigurationKey<String> SOURCE_MAP_PREFIX =
|
||||
CompilerConfigurationKey.create("prefix to add to paths in source map");
|
||||
|
||||
|
||||
@@ -125,7 +125,11 @@ public class JsConfig {
|
||||
|
||||
@NotNull
|
||||
public List<String> getSourceMapRoots() {
|
||||
return configuration.get(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, Collections.singletonList("."));
|
||||
return configuration.get(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, Collections.emptyList());
|
||||
}
|
||||
|
||||
public boolean shouldGenerateRelativePathsInSourceMap() {
|
||||
return getSourceMapPrefix().isEmpty() && getSourceMapRoots().isEmpty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.builtins.isFunctionTypeOrSubtype
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.*
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
import org.jetbrains.kotlin.js.config.JsConfig
|
||||
import org.jetbrains.kotlin.js.inline.util.*
|
||||
import org.jetbrains.kotlin.js.parser.OffsetToSourceMapping
|
||||
@@ -77,7 +78,8 @@ class FunctionReader(
|
||||
val kotlinVariable: String,
|
||||
val specialFunctions: Map<String, SpecialFunction>,
|
||||
offsetToSourceMappingProvider: () -> OffsetToSourceMapping,
|
||||
val sourceMap: SourceMap?
|
||||
val sourceMap: SourceMap?,
|
||||
val outputDir: File?
|
||||
) {
|
||||
val offsetToSourceMapping by lazy(offsetToSourceMappingProvider)
|
||||
|
||||
@@ -89,7 +91,7 @@ class FunctionReader(
|
||||
private val moduleNameToInfo by lazy {
|
||||
val result = HashMultimap.create<String, ModuleInfo>()
|
||||
|
||||
JsLibraryUtils.traverseJsLibraries(config.libraries.map(::File)) { (content, path, sourceMapContent) ->
|
||||
JsLibraryUtils.traverseJsLibraries(config.libraries.map(::File)) { (content, path, sourceMapContent, file) ->
|
||||
var current = 0
|
||||
|
||||
while (true) {
|
||||
@@ -131,7 +133,8 @@ class FunctionReader(
|
||||
kotlinVariable = kotlinVariable,
|
||||
specialFunctions = specialFunctions,
|
||||
offsetToSourceMappingProvider = { OffsetToSourceMapping(content) },
|
||||
sourceMap = sourceMap
|
||||
sourceMap = sourceMap,
|
||||
outputDir = file?.parentFile
|
||||
)
|
||||
|
||||
result.put(moduleName, moduleInfo)
|
||||
@@ -142,6 +145,8 @@ class FunctionReader(
|
||||
}
|
||||
|
||||
private val moduleNameMap: Map<String, JsExpression>
|
||||
private val shouldRemapPathToRelativeForm = config.shouldGenerateRelativePathsInSourceMap()
|
||||
private val relativePathCalculator = config.configuration[JSConfigurationKeys.OUTPUT_DIR]?.let { RelativePathCalculator(it) }
|
||||
|
||||
init {
|
||||
moduleNameMap = buildModuleNameMap(fragments)
|
||||
@@ -242,7 +247,9 @@ class FunctionReader(
|
||||
|
||||
val sourceMap = info.sourceMap
|
||||
if (sourceMap != null) {
|
||||
val remapper = SourceMapLocationRemapper(sourceMap)
|
||||
val remapper = SourceMapLocationRemapper(sourceMap) {
|
||||
remapPath(removeRedundantPathPrefix(it), info)
|
||||
}
|
||||
remapper.remap(function)
|
||||
wrapperStatements?.forEach { remapper.remap(it) }
|
||||
}
|
||||
@@ -301,6 +308,25 @@ class FunctionReader(
|
||||
param.hasDefaultValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeRedundantPathPrefix(path: String): String {
|
||||
var index = 0
|
||||
while (index + 2 <= path.length && path.substring(index, index + 2) == "./") {
|
||||
index += 2
|
||||
while (index < path.length && path[index] == '/') {
|
||||
++index
|
||||
}
|
||||
}
|
||||
|
||||
return path.substring(index)
|
||||
}
|
||||
|
||||
private fun remapPath(path: String, info: ModuleInfo): String {
|
||||
if (!shouldRemapPathToRelativeForm) return path
|
||||
val outputDir = info.outputDir ?: return path
|
||||
val calculator = relativePathCalculator ?: return path
|
||||
return calculator.calculateRelativePathTo(File(outputDir, path)) ?: path
|
||||
}
|
||||
}
|
||||
|
||||
private val Char.isWhitespaceOrComma: Boolean
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.inline.util
|
||||
|
||||
import java.io.File
|
||||
|
||||
class RelativePathCalculator(baseDir: File) {
|
||||
private val baseDirPath = generateSequence(baseDir.canonicalFile) { it.parentFile }.toList().asReversed()
|
||||
|
||||
fun calculateRelativePathTo(file: File): String? {
|
||||
val path = generateSequence(file.canonicalFile) { it.parentFile }.toList().asReversed()
|
||||
if (baseDirPath[0] != path[0]) return null
|
||||
|
||||
val commonLength = baseDirPath.zip(path).takeWhile { (first, second) -> first == second }.size
|
||||
|
||||
val sb = StringBuilder()
|
||||
for (i in commonLength until baseDirPath.size) {
|
||||
sb.append("../")
|
||||
}
|
||||
for (i in commonLength until path.size) {
|
||||
sb.append(path[i].name).append('/')
|
||||
}
|
||||
sb.setLength(sb.lastIndex)
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -18,7 +18,7 @@ package org.jetbrains.kotlin.js.parser.sourcemaps
|
||||
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
|
||||
class SourceMapLocationRemapper(private val sourceMap: SourceMap) {
|
||||
class SourceMapLocationRemapper(private val sourceMap: SourceMap, private val sourceMapPathMapper: (String) -> String = { it }) {
|
||||
fun remap(node: JsNode) {
|
||||
val listCollector = JsNodeFlatListCollector()
|
||||
node.accept(listCollector)
|
||||
@@ -65,7 +65,7 @@ class SourceMapLocationRemapper(private val sourceMap: SourceMap) {
|
||||
val segment = findCorrespondingSegment(node)
|
||||
val sourceFileName = segment?.sourceFileName
|
||||
node.source = if (sourceFileName != null) {
|
||||
val location = JsLocation(segment.sourceFileName, segment.sourceLineNumber, segment.sourceColumnNumber)
|
||||
val location = JsLocation(sourceMapPathMapper(sourceFileName), segment.sourceLineNumber, segment.sourceColumnNumber)
|
||||
JsLocationWithEmbeddedSource(location, null) { sourceMap.sourceContentResolver(segment.sourceFileName) }
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -439,7 +439,7 @@ abstract class BasicBoxTest(
|
||||
generatedProgram.accept(AmbiguousAstSourcePropagation())
|
||||
|
||||
val output = TextOutputImpl()
|
||||
val pathResolver = SourceFilePathResolver(mutableListOf(File(".")))
|
||||
val pathResolver = SourceFilePathResolver(mutableListOf(File(".")), null)
|
||||
val sourceMapBuilder = SourceMap3Builder(outputFile, output, "")
|
||||
generatedProgram.accept(JsToStringGenerationVisitor(output, SourceMapBuilderConsumer(sourceMapBuilder, pathResolver, false, false)))
|
||||
val code = output.toString()
|
||||
|
||||
@@ -128,8 +128,7 @@ public final class K2JSTranslator {
|
||||
ModuleDescriptor moduleDescriptor = analysisResult.getModuleDescriptor();
|
||||
Diagnostics diagnostics = bindingTrace.getBindingContext().getDiagnostics();
|
||||
|
||||
List<File> sourceRoots = config.getSourceMapRoots().stream().map(File::new).collect(Collectors.toList());
|
||||
SourceFilePathResolver pathResolver = new SourceFilePathResolver(sourceRoots);
|
||||
SourceFilePathResolver pathResolver = SourceFilePathResolver.create(config);
|
||||
|
||||
AstGenerationResult translationResult = Translation.generateAst(
|
||||
bindingTrace, units, mainCallParameters, moduleDescriptor, config, pathResolver);
|
||||
@@ -155,7 +154,6 @@ public final class K2JSTranslator {
|
||||
|
||||
ExpandIsCallsKt.expandIsCalls(newFragments);
|
||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
|
||||
|
||||
JsAstSerializer serializer = new JsAstSerializer(file -> {
|
||||
try {
|
||||
return pathResolver.getPathRelativeToSourceRoots(file);
|
||||
|
||||
@@ -65,9 +65,8 @@ abstract class TranslationResult protected constructor(val diagnostics: Diagnost
|
||||
val sourceMapBuilder = SourceMap3Builder(outputFile, output, config.sourceMapPrefix)
|
||||
val sourceMapBuilderConsumer =
|
||||
if (config.configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) {
|
||||
val sourceRoots = config.sourceMapRoots.map { File(it) }
|
||||
val sourceMapContentEmbedding = config.sourceMapContentEmbedding
|
||||
val pathResolver = SourceFilePathResolver(sourceRoots)
|
||||
val pathResolver = SourceFilePathResolver.create(config)
|
||||
SourceMapBuilderConsumer(
|
||||
sourceMapBuilder,
|
||||
pathResolver,
|
||||
|
||||
@@ -18,23 +18,33 @@ package org.jetbrains.kotlin.js.sourceMap;
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys;
|
||||
import org.jetbrains.kotlin.js.config.JsConfig;
|
||||
import org.jetbrains.kotlin.js.inline.util.RelativePathCalculator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SourceFilePathResolver {
|
||||
@NotNull
|
||||
private final Set<File> sourceRoots;
|
||||
|
||||
@Nullable
|
||||
private final RelativePathCalculator outputDirPathResolver;
|
||||
|
||||
@NotNull
|
||||
private final Map<File, String> cache = new HashMap<>();
|
||||
|
||||
public SourceFilePathResolver(@NotNull List<File> sourceRoots) {
|
||||
public SourceFilePathResolver(@NotNull List<File> sourceRoots, @Nullable File outputDir) {
|
||||
this.sourceRoots = new HashSet<>();
|
||||
for (File sourceRoot : sourceRoots) {
|
||||
this.sourceRoots.add(sourceRoot.getAbsoluteFile());
|
||||
}
|
||||
|
||||
outputDirPathResolver = outputDir != null ? new RelativePathCalculator(outputDir) : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -47,7 +57,11 @@ public class SourceFilePathResolver {
|
||||
return path;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String calculatePathRelativeToSourceRoots(@NotNull File file) throws IOException {
|
||||
String pathRelativeToOutput = calculatePathRelativeToOutput(file);
|
||||
if (pathRelativeToOutput != null) return pathRelativeToOutput;
|
||||
|
||||
List<String> parts = new ArrayList<>();
|
||||
File currentFile = file.getCanonicalFile();
|
||||
|
||||
@@ -64,4 +78,19 @@ public class SourceFilePathResolver {
|
||||
}
|
||||
return file.getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String calculatePathRelativeToOutput(@NotNull File file) {
|
||||
return outputDirPathResolver != null ? outputDirPathResolver.calculateRelativePathTo(file) : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static SourceFilePathResolver create(@NotNull JsConfig config) {
|
||||
List<File> sourceRoots = config.getSourceMapRoots().stream().map(File::new).collect(Collectors.toList());
|
||||
File outputDir = null;
|
||||
if (config.shouldGenerateRelativePathsInSourceMap()) {
|
||||
outputDir = config.getConfiguration().get(JSConfigurationKeys.OUTPUT_DIR);
|
||||
}
|
||||
return new SourceFilePathResolver(sourceRoots, outputDir);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user