Generate relative paths in JS source maps
Also, add CLI options to manipulate prefixes of path See KT-4078
This commit is contained in:
+12
@@ -41,6 +41,18 @@ public class K2JSCompilerArguments extends CommonCompilerArguments {
|
||||
@Argument(value = "-source-map", description = "Generate source map")
|
||||
public boolean sourceMap;
|
||||
|
||||
@GradleOption(DefaultValues.StringNullDefault.class)
|
||||
@Argument(value = "-source-map-prefix", description = "Prefix for paths in a source map")
|
||||
public String sourceMapPrefix;
|
||||
|
||||
@GradleOption(DefaultValues.StringNullDefault.class)
|
||||
@Argument(
|
||||
value = "-source-map-source-roots",
|
||||
valueDescription = "<path>",
|
||||
description = "Base directories which are used to calculate relative paths to source files in source map"
|
||||
)
|
||||
public String sourceMapSourceRoots;
|
||||
|
||||
@GradleOption(DefaultValues.BooleanTrueDefault.class)
|
||||
@Argument(value = "-meta-info", description = "Generate .meta.js and .kjsm files with metadata. Use to create a library")
|
||||
public boolean metaInfo;
|
||||
|
||||
@@ -19,7 +19,9 @@ package org.jetbrains.kotlin.cli.js;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.ExceptionUtil;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
@@ -34,6 +36,7 @@ import org.jetbrains.kotlin.cli.common.ExitCode;
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments;
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants;
|
||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport;
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity;
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
|
||||
import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt;
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
|
||||
@@ -58,8 +61,8 @@ import org.jetbrains.kotlin.utils.PathUtil;
|
||||
import org.jetbrains.kotlin.utils.StringsKt;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR;
|
||||
import static org.jetbrains.kotlin.cli.common.ExitCode.OK;
|
||||
@@ -261,6 +264,23 @@ public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
|
||||
|
||||
if (arguments.sourceMap) {
|
||||
configuration.put(JSConfigurationKeys.SOURCE_MAP, true);
|
||||
if (arguments.sourceMapPrefix != null) {
|
||||
configuration.put(JSConfigurationKeys.SOURCE_MAP_PREFIX, arguments.sourceMapPrefix);
|
||||
}
|
||||
|
||||
String sourceMapSourceRoots = arguments.sourceMapSourceRoots != null ?
|
||||
arguments.sourceMapSourceRoots :
|
||||
calculateSourceMapSourceRoot(messageCollector, arguments);
|
||||
List<String> sourceMapSourceRootList = StringUtil.split(sourceMapSourceRoots, File.pathSeparator);
|
||||
configuration.put(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, sourceMapSourceRootList);
|
||||
}
|
||||
else {
|
||||
if (arguments.sourceMapPrefix != null) {
|
||||
messageCollector.report(WARNING, "source-map-prefix argument has no effect without source map", null);
|
||||
}
|
||||
if (arguments.sourceMapSourceRoots != null) {
|
||||
messageCollector.report(WARNING, "source-map-source-root argument has no effect without source map", null);
|
||||
}
|
||||
}
|
||||
if (arguments.metaInfo) {
|
||||
configuration.put(JSConfigurationKeys.META_INFO, true);
|
||||
@@ -299,6 +319,58 @@ public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
|
||||
configuration.put(JSConfigurationKeys.MODULE_KIND, moduleKind);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String calculateSourceMapSourceRoot(
|
||||
@NotNull MessageCollector messageCollector,
|
||||
@NotNull K2JSCompilerArguments arguments
|
||||
) {
|
||||
File commonPath = null;
|
||||
List<File> pathToRoot = new ArrayList<>();
|
||||
Map<File, Integer> pathToRootIndexes = new HashMap<>();
|
||||
|
||||
try {
|
||||
for (String path : arguments.freeArgs) {
|
||||
File file = new File(path).getCanonicalFile();
|
||||
if (commonPath == null) {
|
||||
commonPath = file;
|
||||
|
||||
while (file != null) {
|
||||
pathToRoot.add(file);
|
||||
file = file.getParentFile();
|
||||
}
|
||||
Collections.reverse(pathToRoot);
|
||||
|
||||
for (int i = 0; i < pathToRoot.size(); ++i) {
|
||||
pathToRootIndexes.put(pathToRoot.get(i), i);
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (file != null) {
|
||||
Integer existingIndex = pathToRootIndexes.get(file);
|
||||
if (existingIndex != null) {
|
||||
existingIndex = Math.min(existingIndex, pathToRoot.size() - 1);
|
||||
pathToRoot.subList(existingIndex + 1, pathToRoot.size()).clear();
|
||||
commonPath = pathToRoot.get(pathToRoot.size() - 1);
|
||||
break;
|
||||
}
|
||||
file = file.getParentFile();
|
||||
}
|
||||
if (file == null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
String text = ExceptionUtil.getThrowableText(e);
|
||||
messageCollector.report(CompilerMessageSeverity.ERROR, "IO error occurred calculating source root:\n" + text, null);
|
||||
return ".";
|
||||
}
|
||||
|
||||
return commonPath != null ? commonPath.getPath() : ".";
|
||||
}
|
||||
|
||||
|
||||
private static MainCallParameters createMainCallParameters(String main) {
|
||||
if (K2JsArgumentConstants.NO_CALL.equals(main)) {
|
||||
return MainCallParameters.noCall();
|
||||
|
||||
Vendored
+3
@@ -4,6 +4,9 @@ where possible options include:
|
||||
-no-stdlib Don't use bundled Kotlin stdlib
|
||||
-libraries <path> Paths to Kotlin libraries with .meta.js and .kjsm files, separated by system path separator
|
||||
-source-map Generate source map
|
||||
-source-map-prefix Prefix for paths in a source map
|
||||
-source-map-source-roots <path>
|
||||
Base directories which are used to calculate relative paths to source files in source map
|
||||
-meta-info Generate .meta.js and .kjsm files with metadata. Use to create a library
|
||||
-target { v5 } Generate JS files for specific ECMA version
|
||||
-module-kind { plain, amd, commonjs, umd }
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
$TESTDATA_DIR$/sourceMap.kt
|
||||
-no-stdlib
|
||||
-source-map
|
||||
-output
|
||||
$TEMP_DIR$/out.js
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
var log = ""
|
||||
|
||||
fun foo(x: String) {
|
||||
log += "$x;"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
OK
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// EXISTS: out.js
|
||||
// CONTAINS: out.js.map, "sourceMap.kt"
|
||||
@@ -0,0 +1,7 @@
|
||||
$TESTDATA_DIR$/sourceMap.kt
|
||||
-no-stdlib
|
||||
-source-map
|
||||
-source-map-prefix
|
||||
http://localhost:8080/src/
|
||||
-output
|
||||
$TEMP_DIR$/out.js
|
||||
@@ -0,0 +1 @@
|
||||
OK
|
||||
@@ -0,0 +1,2 @@
|
||||
// EXISTS: out.js
|
||||
// CONTAINS: out.js.map, "http://localhost:8080/src/sourceMap.kt"
|
||||
@@ -0,0 +1,3 @@
|
||||
fun h(): Long {
|
||||
return 42L
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun g(): Int {
|
||||
return 23
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun j(): Float {
|
||||
return 3.14159f
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun f(): String {
|
||||
return "f()"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
$TESTDATA_DIR$/sourceMapRoot/foo/file1.kt
|
||||
$TESTDATA_DIR$/sourceMapRoot/bar/file2.kt
|
||||
-no-stdlib
|
||||
-source-map
|
||||
-output
|
||||
$TEMP_DIR$/out.js
|
||||
@@ -0,0 +1 @@
|
||||
OK
|
||||
@@ -0,0 +1,3 @@
|
||||
// EXISTS: out.js
|
||||
// CONTAINS: out.js.map, "foo/file1.kt"
|
||||
// CONTAINS: out.js.map, "bar/file2.kt"
|
||||
@@ -0,0 +1,8 @@
|
||||
$TESTDATA_DIR$/sourceMapRoot/foo/file1.kt
|
||||
$TESTDATA_DIR$/sourceMapRoot/bar/file2.kt
|
||||
-no-stdlib
|
||||
-source-map
|
||||
-source-map-source-roots
|
||||
$TESTDATA_DIR$/
|
||||
-output
|
||||
$TEMP_DIR$/out.js
|
||||
@@ -0,0 +1 @@
|
||||
OK
|
||||
@@ -0,0 +1,3 @@
|
||||
// EXISTS: out.js
|
||||
// CONTAINS: out.js.map, "sourceMapRoot/foo/file1.kt"
|
||||
// CONTAINS: out.js.map, "sourceMapRoot/bar/file2.kt"
|
||||
@@ -0,0 +1,10 @@
|
||||
$TESTDATA_DIR$/sourceMapRoot/foo/file1.kt
|
||||
$TESTDATA_DIR$/sourceMapRoot/bar/file2.kt
|
||||
$TESTDATA_DIR$/sourceMapRoot/bar/baz/file3.kt
|
||||
$TESTDATA_DIR$/sourceMapRoot/foo/baz/file4.kt
|
||||
-no-stdlib
|
||||
-source-map
|
||||
-source-map-source-roots
|
||||
$TESTDATA_DIR$/sourceMapRoot/foo
|
||||
-output
|
||||
$TEMP_DIR$/out.js
|
||||
@@ -0,0 +1 @@
|
||||
OK
|
||||
@@ -0,0 +1,5 @@
|
||||
// EXISTS: out.js
|
||||
// CONTAINS: out.js.map, "file1.kt"
|
||||
// CONTAINS: out.js.map, "baz/file4.kt"
|
||||
// CONTAINS: out.js.map, "file2.kt"
|
||||
// CONTAINS: out.js.map, "file3.kt"
|
||||
@@ -0,0 +1,8 @@
|
||||
$TESTDATA_DIR$/sourceMapRoot/foo/file1.kt
|
||||
$TESTDATA_DIR$/sourceMapRoot/bar/file2.kt
|
||||
-no-stdlib
|
||||
-source-map
|
||||
-source-map-source-roots
|
||||
$TESTDATA_DIR$/foo:$TESTDATA_DIR$/bar
|
||||
-output
|
||||
$TEMP_DIR$/out.js
|
||||
@@ -0,0 +1 @@
|
||||
OK
|
||||
@@ -0,0 +1,3 @@
|
||||
// EXISTS: out.js
|
||||
// CONTAINS: out.js.map, "file1.kt"
|
||||
// CONTAINS: out.js.map, "file2.kt"
|
||||
@@ -24,7 +24,6 @@ import kotlin.collections.CollectionsKt;
|
||||
import kotlin.io.FilesKt;
|
||||
import kotlin.text.Charsets;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.cli.common.CLICompiler;
|
||||
import org.jetbrains.kotlin.cli.common.CLITool;
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode;
|
||||
import org.jetbrains.kotlin.cli.js.K2JSCompiler;
|
||||
@@ -120,6 +119,26 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
}
|
||||
}
|
||||
|
||||
List<String> containsTextList = InTextDirectivesUtils.findLinesWithPrefixesRemoved(content, "// CONTAINS: ");
|
||||
for (String containsSpec : containsTextList) {
|
||||
String[] parts = containsSpec.split(",", 2);
|
||||
String fileName = parts[0].trim();
|
||||
String contentToSearch = parts[1].trim();
|
||||
File file = new File(tmpdir, fileName);
|
||||
if (!file.exists()) {
|
||||
diagnostics.add("File does not exist: " + fileName);
|
||||
}
|
||||
else if (file.isDirectory()) {
|
||||
diagnostics.add("File is a directory: " + fileName);
|
||||
}
|
||||
else {
|
||||
String text = FilesKt.readText(file, Charsets.UTF_8);
|
||||
if (!text.contains(contentToSearch)) {
|
||||
diagnostics.add("File " + fileName + " does not contain string: " + contentToSearch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!diagnostics.isEmpty()) {
|
||||
diagnostics.add(0, diagnostics.size() + " problem(s) found:");
|
||||
Assert.fail(StringsKt.join(diagnostics, "\n"));
|
||||
@@ -127,7 +146,7 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static List<String> readArgs(@NotNull String argsFilePath, @NotNull String tempDir) throws IOException {
|
||||
private static List<String> readArgs(@NotNull String argsFilePath, @NotNull String tempDir) throws IOException {
|
||||
List<String> lines = FilesKt.readLines(new File(argsFilePath), Charsets.UTF_8);
|
||||
|
||||
return CollectionsKt.mapNotNull(lines, arg -> {
|
||||
|
||||
@@ -515,6 +515,42 @@ public class CliTestGenerated extends AbstractCliTest {
|
||||
doJsTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("sourceMap.args")
|
||||
public void testSourceMap() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMap.args");
|
||||
doJsTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("sourceMapPrefix.args")
|
||||
public void testSourceMapPrefix() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMapPrefix.args");
|
||||
doJsTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("sourceMapRootAuto.args")
|
||||
public void testSourceMapRootAuto() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMapRootAuto.args");
|
||||
doJsTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("sourceMapRootManual.args")
|
||||
public void testSourceMapRootManual() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMapRootManual.args");
|
||||
doJsTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("sourceMapRootMissing.args")
|
||||
public void testSourceMapRootMissing() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMapRootMissing.args");
|
||||
doJsTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("sourceMapRootMultiple.args")
|
||||
public void testSourceMapRootMultiple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMapRootMultiple.args");
|
||||
doJsTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("suppressAllWarningsJS.args")
|
||||
public void testSuppressAllWarningsJS() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/suppressAllWarningsJS.args");
|
||||
|
||||
@@ -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(']');
|
||||
}
|
||||
|
||||
+12
@@ -48,6 +48,18 @@ interface KotlinJsOptions : org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
|
||||
*/
|
||||
var sourceMap: kotlin.Boolean
|
||||
|
||||
/**
|
||||
* Prefix for paths in a source map
|
||||
* Default value: null
|
||||
*/
|
||||
var sourceMapPrefix: kotlin.String?
|
||||
|
||||
/**
|
||||
* Base directories which are used to calculate relative paths to source files in source map
|
||||
* Default value: null
|
||||
*/
|
||||
var sourceMapSourceRoots: kotlin.String?
|
||||
|
||||
/**
|
||||
* Generate JS files for specific ECMA version
|
||||
* Possible values: "v5"
|
||||
|
||||
+14
@@ -59,6 +59,16 @@ internal abstract class KotlinJsOptionsBase : org.jetbrains.kotlin.gradle.dsl.Ko
|
||||
get() = sourceMapField ?: false
|
||||
set(value) { sourceMapField = value }
|
||||
|
||||
private var sourceMapPrefixField: kotlin.String?? = null
|
||||
override var sourceMapPrefix: kotlin.String?
|
||||
get() = sourceMapPrefixField ?: null
|
||||
set(value) { sourceMapPrefixField = value }
|
||||
|
||||
private var sourceMapSourceRootsField: kotlin.String?? = null
|
||||
override var sourceMapSourceRoots: kotlin.String?
|
||||
get() = sourceMapSourceRootsField ?: null
|
||||
set(value) { sourceMapSourceRootsField = value }
|
||||
|
||||
private var targetField: kotlin.String? = null
|
||||
override var target: kotlin.String
|
||||
get() = targetField ?: "v5"
|
||||
@@ -81,6 +91,8 @@ internal abstract class KotlinJsOptionsBase : org.jetbrains.kotlin.gradle.dsl.Ko
|
||||
noStdlibField?.let { args.noStdlib = it }
|
||||
outputFileField?.let { args.outputFile = it }
|
||||
sourceMapField?.let { args.sourceMap = it }
|
||||
sourceMapPrefixField?.let { args.sourceMapPrefix = it }
|
||||
sourceMapSourceRootsField?.let { args.sourceMapSourceRoots = it }
|
||||
targetField?.let { args.target = it }
|
||||
typedArraysField?.let { args.typedArrays = it }
|
||||
}
|
||||
@@ -98,6 +110,8 @@ internal fun org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments.fil
|
||||
noStdlib = true
|
||||
outputFile = null
|
||||
sourceMap = false
|
||||
sourceMapPrefix = null
|
||||
sourceMapSourceRoots = null
|
||||
target = "v5"
|
||||
typedArrays = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user