JS: generate paths in source maps relative to .map file location
See KT-19818
This commit is contained in:
@@ -251,8 +251,20 @@ public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
|
||||
}
|
||||
}
|
||||
|
||||
File outputDir = outputFile.getParentFile();
|
||||
if (outputDir == null) {
|
||||
outputDir = outputFile.getAbsoluteFile().getParentFile();
|
||||
}
|
||||
try {
|
||||
config.getConfiguration().put(JSConfigurationKeys.OUTPUT_DIR, outputDir.getCanonicalFile());
|
||||
}
|
||||
catch (IOException e) {
|
||||
messageCollector.report(ERROR, "Could not resolve output directory", null);
|
||||
return ExitCode.COMPILATION_ERROR;
|
||||
}
|
||||
|
||||
if (config.getConfiguration().getBoolean(JSConfigurationKeys.SOURCE_MAP)) {
|
||||
checkDuplicateSourceFileNames(messageCollector, sourcesFiles, config.getSourceMapRoots());
|
||||
checkDuplicateSourceFileNames(messageCollector, sourcesFiles, config);
|
||||
}
|
||||
|
||||
MainCallParameters mainCallParameters = createMainCallParameters(arguments.getMain());
|
||||
@@ -280,11 +292,6 @@ public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
|
||||
return ExitCode.COMPILATION_ERROR;
|
||||
}
|
||||
|
||||
File outputDir = outputFile.getParentFile();
|
||||
if (outputDir == null) {
|
||||
outputDir = outputFile.getAbsoluteFile().getParentFile();
|
||||
}
|
||||
|
||||
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
|
||||
|
||||
OutputUtilsKt.writeAll(outputFiles, outputDir, messageCollector,
|
||||
@@ -296,12 +303,11 @@ public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
|
||||
private static void checkDuplicateSourceFileNames(
|
||||
@NotNull MessageCollector log,
|
||||
@NotNull List<KtFile> sourceFiles,
|
||||
@NotNull List<String> sourceRoots
|
||||
@NotNull JsConfig config
|
||||
) {
|
||||
if (sourceRoots.isEmpty()) return;
|
||||
if (config.getSourceMapRoots().isEmpty()) return;
|
||||
|
||||
List<File> sourceRootFiles = sourceRoots.stream().map(File::new).collect(Collectors.toList());
|
||||
SourceFilePathResolver pathResolver = new SourceFilePathResolver(sourceRootFiles);
|
||||
SourceFilePathResolver pathResolver = SourceFilePathResolver.create(config);
|
||||
Map<String, String> pathMap = new HashMap<>();
|
||||
Set<String> duplicatePaths = new HashSet<>();
|
||||
|
||||
@@ -357,11 +363,15 @@ public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
|
||||
configuration.put(JSConfigurationKeys.SOURCE_MAP_PREFIX, arguments.getSourceMapPrefix());
|
||||
}
|
||||
|
||||
String sourceMapSourceRoots = arguments.getSourceMapBaseDirs() != null ?
|
||||
arguments.getSourceMapBaseDirs() :
|
||||
calculateSourceMapSourceRoot(messageCollector, arguments);
|
||||
List<String> sourceMapSourceRootList = StringUtil.split(sourceMapSourceRoots, File.pathSeparator);
|
||||
configuration.put(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, sourceMapSourceRootList);
|
||||
String sourceMapSourceRoots = arguments.getSourceMapBaseDirs();
|
||||
if (sourceMapSourceRoots == null && StringUtil.isNotEmpty(arguments.getSourceMapPrefix())) {
|
||||
sourceMapSourceRoots = calculateSourceMapSourceRoot(messageCollector, arguments);
|
||||
}
|
||||
|
||||
if (sourceMapSourceRoots != null) {
|
||||
List<String> sourceMapSourceRootList = StringUtil.split(sourceMapSourceRoots, File.pathSeparator);
|
||||
configuration.put(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, sourceMapSourceRootList);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (arguments.getSourceMapPrefix() != null) {
|
||||
|
||||
+2
@@ -1,5 +1,7 @@
|
||||
$TESTDATA_DIR$/sourceMap.kt
|
||||
-no-stdlib
|
||||
-source-map
|
||||
-source-map-prefix
|
||||
./
|
||||
-output
|
||||
$TEMP_DIR$/out.js
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
// EXISTS: out.js
|
||||
// CONTAINS: out.js.map, "sourceMap.kt"
|
||||
// CONTAINS: out.js.map, "./sourceMap.kt"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
$TESTDATA_DIR$/sourceMap.kt
|
||||
-no-stdlib
|
||||
-source-map
|
||||
-source-map-prefix
|
||||
./
|
||||
-source-map-embed-sources
|
||||
always
|
||||
-output
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
// EXISTS: out.js
|
||||
// CONTAINS: out.js.map, "sourceMap.kt"
|
||||
// CONTAINS: out.js.map, "./sourceMap.kt"
|
||||
// CONTAINS: out.js.map, "var log = \"\"\n\nfun foo(x: String) {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
$TESTDATA_DIR$/sourceMapRelativeRoot/lib/src/lib.kt
|
||||
-no-stdlib
|
||||
-source-map
|
||||
-meta-info
|
||||
-output
|
||||
$TESTDATA_DIR$/sourceMapRelativeRoot/lib/out/lib.js
|
||||
---
|
||||
$TESTDATA_DIR$/sourceMapRelativeRoot/main/src/main.kt
|
||||
-no-stdlib
|
||||
-libraries
|
||||
$TESTDATA_DIR$/sourceMapRelativeRoot/lib/out/
|
||||
-source-map
|
||||
-output
|
||||
$TESTDATA_DIR$/sourceMapRelativeRoot/main/out/main.js
|
||||
@@ -0,0 +1 @@
|
||||
OK
|
||||
@@ -0,0 +1,5 @@
|
||||
// EXISTS: $TESTDATA_DIR$/sourceMapRelativeRoot/lib/out/lib.js
|
||||
// EXISTS: $TESTDATA_DIR$/sourceMapRelativeRoot/main/out/main.js
|
||||
// CONTAINS: $TESTDATA_DIR$/sourceMapRelativeRoot/lib/out/lib.js.map, "../src/lib.kt"
|
||||
// CONTAINS: $TESTDATA_DIR$/sourceMapRelativeRoot/main/out/main.js.map, "../src/main.kt"
|
||||
// CONTAINS: $TESTDATA_DIR$/sourceMapRelativeRoot/main/out/main.js.map, "../../lib/src/lib.kt"
|
||||
@@ -0,0 +1 @@
|
||||
out
|
||||
@@ -0,0 +1,4 @@
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun foo() = 23
|
||||
|
||||
fun bar() = 42
|
||||
@@ -0,0 +1 @@
|
||||
fun box() = foo() + bar()
|
||||
@@ -2,5 +2,7 @@ $TESTDATA_DIR$/sourceMapRoot/foo/file1.kt
|
||||
$TESTDATA_DIR$/sourceMapRoot/bar/file2.kt
|
||||
-no-stdlib
|
||||
-source-map
|
||||
-source-map-prefix
|
||||
./
|
||||
-output
|
||||
$TEMP_DIR$/out.js
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
// EXISTS: out.js
|
||||
// CONTAINS: out.js.map, "foo/file1.kt"
|
||||
// CONTAINS: out.js.map, "bar/file2.kt"
|
||||
// CONTAINS: out.js.map, "./foo/file1.kt"
|
||||
// CONTAINS: out.js.map, "./bar/file2.kt"
|
||||
|
||||
@@ -43,19 +43,29 @@ import org.junit.Assert;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
@NotNull
|
||||
private static final String TESTDATA_DIR = "$TESTDATA_DIR$";
|
||||
|
||||
public static Pair<String, ExitCode> executeCompilerGrabOutput(@NotNull CLITool<?> compiler, @NotNull List<String> args) {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
PrintStream origErr = System.err;
|
||||
try {
|
||||
System.setErr(new PrintStream(bytes));
|
||||
ExitCode exitCode = CLITool.doMainNoExit(compiler, ArrayUtil.toStringArray(args));
|
||||
ExitCode exitCode;
|
||||
int index = 0;
|
||||
do {
|
||||
int next = args.subList(index, args.size()).indexOf("---");
|
||||
if (next == -1) {
|
||||
next = args.size();
|
||||
}
|
||||
exitCode = CLITool.doMainNoExit(compiler, ArrayUtil.toStringArray(args.subList(index, next)));
|
||||
if (exitCode != ExitCode.OK) break;
|
||||
index = next + 1;
|
||||
} while (index < args.size());
|
||||
return new Pair<>(bytes.toString("utf-8"), exitCode);
|
||||
}
|
||||
catch (Exception e) {
|
||||
@@ -70,8 +80,8 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
public static String getNormalizedCompilerOutput(@NotNull String pureOutput, @NotNull ExitCode exitCode, @NotNull String testDataDir) {
|
||||
String testDataAbsoluteDir = new File(testDataDir).getAbsolutePath();
|
||||
String normalizedOutputWithoutExitCode = StringUtil.convertLineSeparators(pureOutput)
|
||||
.replace(testDataAbsoluteDir, "$TESTDATA_DIR$")
|
||||
.replace(FileUtil.toSystemIndependentName(testDataAbsoluteDir), "$TESTDATA_DIR$")
|
||||
.replace(testDataAbsoluteDir, TESTDATA_DIR)
|
||||
.replace(FileUtil.toSystemIndependentName(testDataAbsoluteDir), TESTDATA_DIR)
|
||||
.replace(PathUtil.getKotlinPathsForDistDirectory().getHomePath().getAbsolutePath(), "$PROJECT_DIR$")
|
||||
.replace("expected version is " + JvmMetadataVersion.INSTANCE, "expected version is $ABI_VERSION$")
|
||||
.replace("expected version is " + JsMetadataVersion.INSTANCE, "expected version is $ABI_VERSION$")
|
||||
@@ -81,7 +91,7 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
return normalizedOutputWithoutExitCode + exitCode + "\n";
|
||||
}
|
||||
|
||||
private void doTest(@NotNull String fileName, @NotNull CLITool<?> compiler) throws Exception {
|
||||
private void doTest(@NotNull String fileName, @NotNull CLITool<?> compiler) {
|
||||
System.setProperty("java.awt.headless", "true");
|
||||
Pair<String, ExitCode> outputAndExitCode = executeCompilerGrabOutput(compiler, readArgs(fileName, tmpdir.getPath()));
|
||||
String actual = getNormalizedCompilerOutput(
|
||||
@@ -93,17 +103,17 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
|
||||
File additionalTestConfig = new File(fileName.replaceFirst("\\.args$", ".test"));
|
||||
if (additionalTestConfig.exists()) {
|
||||
doTestAdditionalChecks(additionalTestConfig);
|
||||
doTestAdditionalChecks(additionalTestConfig, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
private void doTestAdditionalChecks(@NotNull File testConfigFile) throws IOException {
|
||||
private void doTestAdditionalChecks(@NotNull File testConfigFile, @NotNull String argsFilePath) {
|
||||
List<String> diagnostics = new ArrayList<>(0);
|
||||
String content = FilesKt.readText(testConfigFile, Charsets.UTF_8);
|
||||
|
||||
List<String> existsList = InTextDirectivesUtils.findListWithPrefixes(content, "// EXISTS: ");
|
||||
for (String fileName : existsList) {
|
||||
File file = new File(tmpdir, fileName);
|
||||
File file = checkedPathToFile(fileName, argsFilePath);
|
||||
if (!file.exists()) {
|
||||
diagnostics.add("File does not exist, but should: " + fileName);
|
||||
}
|
||||
@@ -114,7 +124,7 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
|
||||
List<String> absentList = InTextDirectivesUtils.findListWithPrefixes(content, "// ABSENT: ");
|
||||
for (String fileName : absentList) {
|
||||
File file = new File(tmpdir, fileName);
|
||||
File file = checkedPathToFile(fileName, argsFilePath);
|
||||
if (file.exists() && file.isFile()) {
|
||||
diagnostics.add("File exists, but shouldn't: " + fileName);
|
||||
}
|
||||
@@ -125,7 +135,7 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
String[] parts = containsSpec.split(",", 2);
|
||||
String fileName = parts[0].trim();
|
||||
String contentToSearch = parts[1].trim();
|
||||
File file = new File(tmpdir, fileName);
|
||||
File file = checkedPathToFile(fileName, argsFilePath);
|
||||
if (!file.exists()) {
|
||||
diagnostics.add("File does not exist: " + fileName);
|
||||
}
|
||||
@@ -147,7 +157,17 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> readArgs(@NotNull String argsFilePath, @NotNull String tempDir) throws IOException {
|
||||
private File checkedPathToFile(@NotNull String path, @NotNull String argsFilePath) {
|
||||
if (path.startsWith(TESTDATA_DIR + "/")) {
|
||||
return new File(new File(argsFilePath).getParent(), path.substring(TESTDATA_DIR.length() + 1));
|
||||
}
|
||||
else {
|
||||
return new File(tmpdir, path);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> readArgs(@NotNull String argsFilePath, @NotNull String tempDir) {
|
||||
List<String> lines = FilesKt.readLines(new File(argsFilePath), Charsets.UTF_8);
|
||||
|
||||
return CollectionsKt.mapNotNull(lines, arg -> {
|
||||
@@ -163,7 +183,7 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
|
||||
return argsWithColonsReplaced
|
||||
.replace("$TEMP_DIR$", tempDir)
|
||||
.replace("$TESTDATA_DIR$", new File(argsFilePath).getParent())
|
||||
.replace(TESTDATA_DIR, new File(argsFilePath).getParent())
|
||||
.replace(
|
||||
"$FOREIGN_ANNOTATIONS_DIR$",
|
||||
new File(AbstractForeignAnnotationsTestKt.getFOREIGN_ANNOTATIONS_SOURCES_PATH()).getPath()
|
||||
@@ -171,15 +191,15 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
});
|
||||
}
|
||||
|
||||
protected void doJvmTest(@NotNull String fileName) throws Exception {
|
||||
protected void doJvmTest(@NotNull String fileName) {
|
||||
doTest(fileName, new K2JVMCompiler());
|
||||
}
|
||||
|
||||
protected void doJsTest(@NotNull String fileName) throws Exception {
|
||||
protected void doJsTest(@NotNull String fileName) {
|
||||
doTest(fileName, new K2JSCompiler());
|
||||
}
|
||||
|
||||
protected void doJsDceTest(@NotNull String fileName) throws Exception {
|
||||
protected void doJsDceTest(@NotNull String fileName) {
|
||||
doTest(fileName, new K2JSDce());
|
||||
}
|
||||
|
||||
|
||||
@@ -665,6 +665,12 @@ public class CliTestGenerated extends AbstractCliTest {
|
||||
doJsTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("sourceMapRelativeRoot.args")
|
||||
public void testSourceMapRelativeRoot() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMapRelativeRoot.args");
|
||||
doJsTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("sourceMapRootAuto.args")
|
||||
public void testSourceMapRootAuto() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMapRootAuto.args");
|
||||
|
||||
@@ -64,7 +64,7 @@ object JsLibraryUtils {
|
||||
|
||||
private fun File.runIfFileExists(relativePath: String, action: (JsLibrary) -> Unit) {
|
||||
if (isFile) {
|
||||
action(JsLibrary(readText(), relativePath, correspondingSourceMapFile().contentIfExists()))
|
||||
action(JsLibrary(readText(), relativePath, correspondingSourceMapFile().contentIfExists(), this))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ object JsLibraryUtils {
|
||||
|
||||
val stream = zipFile.getInputStream(entry)
|
||||
val content = FileUtil.loadTextAndClose(stream)
|
||||
librariesWithoutSourceMaps += JsLibrary(content, relativePath, null)
|
||||
librariesWithoutSourceMaps += JsLibrary(content, relativePath, null, null)
|
||||
}
|
||||
else if (entryName.endsWith(KotlinJavascriptMetadataUtils.JS_MAP_EXT)) {
|
||||
val correspondingJsPath = entryName.removeSuffix(KotlinJavascriptMetadataUtils.JS_MAP_EXT) +
|
||||
@@ -171,4 +171,4 @@ object JsLibraryUtils {
|
||||
}
|
||||
}
|
||||
|
||||
data class JsLibrary(val content: String, val path: String, val sourceMapContent: String?)
|
||||
data class JsLibrary(val content: String, val path: String, val sourceMapContent: String?, val file: File?)
|
||||
|
||||
@@ -359,7 +359,7 @@ open class KotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() {
|
||||
buildAllModules().assertSuccessful()
|
||||
|
||||
val sourceMapContent = File(getOutputDir(PROJECT_NAME), "$PROJECT_NAME.js.map").readText()
|
||||
val expectedPath = "prefix-dir/pkg/test1.kt"
|
||||
val expectedPath = "prefix-dir/src/pkg/test1.kt"
|
||||
assertTrue("Source map file should contain relative path ($expectedPath)", sourceMapContent.contains("\"$expectedPath\""))
|
||||
|
||||
val librarySourceMapFile = File(getOutputDir(PROJECT_NAME), "lib/kotlin.js.map")
|
||||
|
||||
@@ -67,6 +67,7 @@ import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
||||
import org.jetbrains.kotlin.utils.*
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.util.*
|
||||
|
||||
class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
@@ -647,7 +648,10 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
val compilerSettings = JpsKotlinCompilerSettings.getCompilerSettings(representativeModule)
|
||||
val k2JsArguments = JpsKotlinCompilerSettings.getK2JsCompilerArguments(representativeModule)
|
||||
|
||||
val sourceRoots = KotlinSourceFileCollector.getRelevantSourceRoots(representativeTarget).map { it.file }
|
||||
val sourceRoots = representativeModule.contentRootsList.urls
|
||||
.map { URI.create(it) }
|
||||
.filter { it.scheme == "file" }
|
||||
.map { File(it) }
|
||||
|
||||
val friendPaths = KotlinBuilderModuleScriptGenerator.getProductionModulesWhichInternalsAreVisible(representativeTarget).mapNotNull {
|
||||
val file = getOutputMetaFile(it, false)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -195,7 +195,7 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() {
|
||||
assertFileExists(mapFilePath)
|
||||
val map = fileInWorkingDir(mapFilePath).readText()
|
||||
|
||||
val sourceFilePath = "prefixprefix/example/Dummy.kt"
|
||||
val sourceFilePath = "prefixprefix/src/main/kotlin/example/Dummy.kt"
|
||||
assertTrue("Source map should contain reference to $sourceFilePath") { map.contains("\"$sourceFilePath\"") }
|
||||
}
|
||||
}
|
||||
@@ -211,8 +211,8 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() {
|
||||
assertFileExists(mapFilePath)
|
||||
val map = fileInWorkingDir(mapFilePath).readText()
|
||||
|
||||
assertTrue("Source map should contain reference to main.kt") { map.contains("\"main.kt\"") }
|
||||
assertTrue("Source map should contain reference to foo.kt") { map.contains("\"foo.kt\"") }
|
||||
assertTrue("Source map should contain reference to main.kt") { map.contains("\"./src/main/kotlin/main.kt\"") }
|
||||
assertTrue("Source map should contain reference to foo.kt") { map.contains("\"./src/main/kotlin/foo.kt\"") }
|
||||
assertTrue("Source map should contain source of main.kt") { map.contains("\"fun main(args: Array<String>) {\\n") }
|
||||
assertTrue("Source map should contain source of foo.kt") { map.contains("\"inline fun foo(): String {\\n") }
|
||||
}
|
||||
|
||||
+1
@@ -24,6 +24,7 @@ allprojects {
|
||||
compileKotlin2Js {
|
||||
kotlinOptions.freeCompilerArgs = [ "-Xskip-metadata-version-check" ]
|
||||
kotlinOptions.sourceMap = true
|
||||
kotlinOptions.sourceMapPrefix = "./"
|
||||
kotlinOptions.sourceMapEmbedSources = "always"
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -461,11 +461,9 @@ open class Kotlin2JsCompile() : AbstractKotlinCompile<K2JSCompilerArguments>(),
|
||||
|
||||
args.friendModules = friendDependency
|
||||
|
||||
args.sourceMapBaseDirs = source.orEmpty()
|
||||
.asSequence()
|
||||
.filterIsInstance<SourceDirectorySet>()
|
||||
.flatMap { it.srcDirs.asSequence() }
|
||||
.joinToString(File.pathSeparator) { it.absolutePath }
|
||||
if (args.sourceMapBaseDirs == null && !args.sourceMapPrefix.isNullOrEmpty()) {
|
||||
args.sourceMapBaseDirs = project.projectDir.absolutePath
|
||||
}
|
||||
|
||||
logger.kotlinDebug("compiling with args ${ArgumentUtils.convertArgumentsToStringList(args)}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user