JS: generate paths in source maps relative to .map file location

See KT-19818
This commit is contained in:
Alexey Andreev
2017-08-29 14:28:15 +03:00
parent 93a3026c6d
commit 9008791a54
31 changed files with 236 additions and 63 deletions
@@ -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()
}
}
@@ -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);
}
}