Generate relative paths in JS source maps

Also, add CLI options to manipulate prefixes of path

See KT-4078
This commit is contained in:
Alexey Andreev
2017-06-09 14:52:04 +03:00
parent 44d3b8fb1a
commit 13ab63ae09
39 changed files with 399 additions and 18 deletions
@@ -29,6 +29,12 @@ public class JSConfigurationKeys {
public static final CompilerConfigurationKey<Boolean> SOURCE_MAP =
CompilerConfigurationKey.create("generate source map");
public static final CompilerConfigurationKey<String> SOURCE_MAP_PREFIX =
CompilerConfigurationKey.create("prefix to add to paths in source map");
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<Boolean> META_INFO =
CompilerConfigurationKey.create("generate .meta.js and .kjsm files");
@@ -103,6 +103,16 @@ public class JsConfig {
return getConfiguration().getList(JSConfigurationKeys.LIBRARIES);
}
@NotNull
public String getSourceMapPrefix() {
return configuration.get(JSConfigurationKeys.SOURCE_MAP_PREFIX, "");
}
@NotNull
public List<String> getSourceMapRoots() {
return configuration.get(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, Collections.singletonList("."));
}
@NotNull
public List<String> getFriends() {
if (getConfiguration().getBoolean(JSConfigurationKeys.FRIEND_PATHS_DISABLED)) return Collections.emptyList();
@@ -22,11 +22,12 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.*
import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.*
import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.BinaryOperation.Type.*
import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.UnaryOperation.Type.*
import java.io.File
import java.io.OutputStream
import java.util.*
import org.jetbrains.kotlin.resolve.inline.InlineStrategy as KotlinInlineStrategy
class JsAstSerializer {
class JsAstSerializer(private val pathResolver: (File) -> String) {
private val nameTableBuilder = NameTable.newBuilder()
private val stringTableBuilder = StringTable.newBuilder()
private val nameMap = mutableMapOf<JsName, Int>()
@@ -567,9 +568,9 @@ class JsAstSerializer {
if (location != null) {
val lastFile = fileStack.peek()
val newFile = location.file
fileChanged = lastFile != newFile && newFile != null
fileChanged = lastFile != newFile
if (fileChanged) {
fileConsumer(serialize(newFile!!))
fileConsumer(serialize(newFile))
fileStack.push(location.file)
}
val locationBuilder = Location.newBuilder()
@@ -598,7 +599,7 @@ class JsAstSerializer {
val file = element.containingFile
val document = file.viewProvider.document!!
val path = file.viewProvider.virtualFile.path
val path = pathResolver(File(file.viewProvider.virtualFile.path))
val startOffset = element.node.startOffset
val startLine = document.getLineNumber(startOffset)
@@ -42,6 +42,7 @@ import org.jetbrains.kotlin.js.facade.*
import org.jetbrains.kotlin.js.parser.parse
import org.jetbrains.kotlin.js.parser.sourcemaps.*
import org.jetbrains.kotlin.js.sourceMap.JsSourceGenerationVisitor
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
import org.jetbrains.kotlin.js.test.utils.*
import org.jetbrains.kotlin.js.util.TextOutputImpl
@@ -409,7 +410,8 @@ abstract class BasicBoxTest(
generatedProgram.accept(AmbiguousAstSourcePropagation())
val output = TextOutputImpl()
val sourceMapBuilder = SourceMap3Builder(outputFile, output, SourceMapBuilderConsumer())
val pathResolver = SourceFilePathResolver(mutableListOf(File(".")))
val sourceMapBuilder = SourceMap3Builder(outputFile, output, "", SourceMapBuilderConsumer(pathResolver))
generatedProgram.accept(JsSourceGenerationVisitor(output, sourceMapBuilder))
val code = output.toString()
val generatedSourceMap = sourceMapBuilder.build()
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.js.inline.JsInliner;
import org.jetbrains.kotlin.js.inline.clean.LabeledBlockToDoWhileTransformation;
import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedImportsKt;
import org.jetbrains.kotlin.js.inline.clean.ResolveTemporaryNamesKt;
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult;
import org.jetbrains.kotlin.js.translate.general.FileTranslationResult;
import org.jetbrains.kotlin.js.translate.general.Translation;
@@ -45,10 +46,13 @@ import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil;
import org.jetbrains.kotlin.serialization.js.ast.JsAstSerializer;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.jetbrains.kotlin.diagnostics.DiagnosticUtils.hasError;
@@ -144,8 +148,18 @@ public final class K2JSTranslator {
ExpandIsCallsKt.expandIsCalls(newFragments);
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
List<File> sourceRoots = config.getSourceMapRoots().stream().map(File::new).collect(Collectors.toList());
SourceFilePathResolver pathResolver = new SourceFilePathResolver(sourceRoots);
Map<KtFile, FileTranslationResult> fileMap = new HashMap<>();
JsAstSerializer serializer = new JsAstSerializer();
JsAstSerializer serializer = new JsAstSerializer(file -> {
try {
return pathResolver.getPathRelativeToSourceRoots(file);
}
catch (IOException e) {
throw new RuntimeException("IO error occurred resolving path to source file", e);
}
});
byte[] metadataHeader = null;
boolean serializeFragments = config.getConfiguration().get(JSConfigurationKeys.SERIALIZE_FRAGMENTS, false);
for (KtFile file : files) {
@@ -20,21 +20,40 @@ import com.intellij.openapi.editor.Document;
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.sourceMap.SourceFilePathResolver;
import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilder;
import java.io.File;
import java.io.IOException;
public class SourceMapBuilderConsumer implements PairConsumer<SourceMapBuilder, Object> {
@NotNull
private final SourceFilePathResolver pathResolver;
public SourceMapBuilderConsumer(@NotNull SourceFilePathResolver pathResolver) {
this.pathResolver = pathResolver;
}
@Override
public void consume(SourceMapBuilder builder, Object sourceInfo) {
if (sourceInfo instanceof PsiElement) {
PsiElement element = (PsiElement) sourceInfo;
PsiFile file = element.getContainingFile();
PsiFile psiFile = element.getContainingFile();
int offset = element.getNode().getStartOffset();
Document document = file.getViewProvider().getDocument();
Document document = psiFile.getViewProvider().getDocument();
assert document != null;
int line = document.getLineNumber(offset);
int column = offset - document.getLineStartOffset(line);
builder.addMapping(file.getViewProvider().getVirtualFile().getPath(), line, column);
File file = new File(psiFile.getViewProvider().getVirtualFile().getPath());
try {
builder.addMapping(pathResolver.getPathRelativeToSourceRoots(file), line, column);
}
catch (IOException e) {
throw new RuntimeException("IO error occurred generating source maps", e);
}
}
else if (sourceInfo instanceof JsLocation) {
JsLocation location = (JsLocation) sourceInfo;
@@ -24,6 +24,7 @@ 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.sourceMap.JsSourceGenerationVisitor
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilder
import org.jetbrains.kotlin.js.translate.general.FileTranslationResult
@@ -58,9 +59,14 @@ abstract class TranslationResult protected constructor(val diagnostics: Diagnost
fun getOutputFiles(outputFile: File, outputPrefixFile: File?, outputPostfixFile: File?): OutputFileCollection {
val output = TextOutputImpl()
val sourceMapBuilder =
if (config.configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP))
SourceMap3Builder(outputFile, output, SourceMapBuilderConsumer())
else null
if (config.configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) {
val sourceRoots = config.sourceMapRoots.map { File(it) }
val pathResolver = SourceFilePathResolver(sourceRoots)
SourceMap3Builder(outputFile, output, config.sourceMapPrefix, SourceMapBuilderConsumer(pathResolver))
}
else {
null
}
val code = getCode(output, sourceMapBuilder)
val prefix = outputPrefixFile?.readText() ?: ""
@@ -0,0 +1,67 @@
/*
* 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.sourceMap;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class SourceFilePathResolver {
@NotNull
private final Set<File> sourceRoots;
@NotNull
private final Map<File, String> cache = new HashMap<>();
public SourceFilePathResolver(@NotNull List<File> sourceRoots) {
this.sourceRoots = new HashSet<>();
for (File sourceRoot : sourceRoots) {
this.sourceRoots.add(sourceRoot.getAbsoluteFile());
}
}
@NotNull
public String getPathRelativeToSourceRoots(@NotNull File file) throws IOException {
String path = cache.get(file);
if (path == null) {
path = calculatePathRelativeToSourceRoots(file);
cache.put(file, path);
}
return path;
}
private String calculatePathRelativeToSourceRoots(@NotNull File file) throws IOException {
List<String> parts = new ArrayList<>();
File currentFile = file.getCanonicalFile();
while (currentFile != null) {
if (sourceRoots.contains(currentFile)) {
if (parts.isEmpty()) {
break;
}
Collections.reverse(parts);
return StringUtil.join(parts, File.separator);
}
parts.add(currentFile.getName());
currentFile = currentFile.getParentFile();
}
return file.getName();
}
}
@@ -30,6 +30,7 @@ public class SourceMap3Builder implements SourceMapBuilder {
private final StringBuilder out = new StringBuilder(8192);
private final File generatedFile;
private final TextOutput textOutput;
private final String pathPrefix;
private final PairConsumer<SourceMapBuilder, Object> sourceInfoConsumer;
private String lastSource;
@@ -50,9 +51,11 @@ public class SourceMap3Builder implements SourceMapBuilder {
private int previousSourceLine;
private int previousSourceColumn;
public SourceMap3Builder(File generatedFile, TextOutput textOutput, PairConsumer<SourceMapBuilder, Object> sourceInfoConsumer) {
public SourceMap3Builder(File generatedFile, TextOutput textOutput, String pathPrefix,
PairConsumer<SourceMapBuilder, Object> sourceInfoConsumer) {
this.generatedFile = generatedFile;
this.textOutput = textOutput;
this.pathPrefix = pathPrefix;
this.sourceInfoConsumer = sourceInfoConsumer;
}
@@ -83,7 +86,7 @@ public class SourceMap3Builder implements SourceMapBuilder {
else {
isNotFirst = true;
}
sb.append('"').append("file://").append(source).append('"');
sb.append('"').append(pathPrefix).append(source).append('"');
}
sb.append(']');
}