From a0e1bde5940c19d84c816b89e443277c66a823ef Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 14 Jun 2017 14:03:37 +0300 Subject: [PATCH] Allow to embed source files into JS source maps --- .../cli/common/arguments/DefaultValues.kt | 9 ++ .../arguments/K2JSCompilerArguments.java | 8 ++ .../arguments/K2JsArgumentConstants.java | 4 + .../jetbrains/kotlin/cli/js/K2JSCompiler.java | 23 +++++ compiler/testData/cli/js/jsHelp.out | 2 + .../cli/js/sourceMapEmbedSources.args | 7 ++ .../testData/cli/js/sourceMapEmbedSources.out | 1 + .../cli/js/sourceMapEmbedSources.test | 3 + .../kotlin/cli/CliTestGenerated.java | 6 ++ .../jetbrains/kotlin/utils/JsLibraryUtils.kt | 4 +- .../kotlin/js/backend/ast/JsLocation.kt | 31 ++++++- .../kotlin/js/config/JSConfigurationKeys.java | 3 + .../jetbrains/kotlin/js/config/JsConfig.java | 5 + .../js/config/SourceMapSourceEmbedding.java | 23 +++++ .../kotlin/js/parser/sourcemaps/SourceMap.kt | 4 +- .../sourcemaps/SourceMapLocationRemapper.kt | 8 +- .../js/parser/sourcemaps/SourceMapParser.kt | 17 +++- .../serialization/js/ast/JsAstDeserializer.kt | 15 ++- .../serialization/js/ast/JsAstSerializer.kt | 4 +- .../jetbrains/kotlin/js/test/BasicBoxTest.kt | 93 ++++++++++++------- .../js/test/semantics/BoxJsTestGenerated.java | 6 ++ .../utils/AmbiguousAstSourcePropagation.kt | 2 +- .../kotlin/js/test/utils/LineCollector.kt | 2 +- .../js/facade/SourceMapBuilderConsumer.java | 42 +++++++-- .../kotlin/js/facade/TranslationResult.kt | 8 +- .../js/sourceMap/SourceMap3Builder.java | 93 +++++++++++++++---- .../kotlin/js/sourceMap/SourceMapBuilder.java | 4 +- .../js/translate/general/Translation.java | 5 +- .../incremental/sourceMapSourceEmbedding.kt | 12 +++ .../kotlin/gradle/Kotlin2JsGradlePluginIT.kt | 18 ++++ .../app/src/main/kotlin/main.kt | 3 + .../build.gradle | 36 +++++++ .../lib/src/main/kotlin/foo.kt | 3 + .../settings.gradle | 1 + .../kotlin/gradle/dsl/KotlinJsOptions.kt | 7 ++ .../kotlin/gradle/dsl/KotlinJsOptionsBase.kt | 7 ++ .../it/test-js-sourceMapEmbedSources/pom.xml | 42 +++++++++ .../main/kotlin/org/jetbrains/HelloWorld.kt | 3 + .../test-js-sourceMapEmbedSources/verify.bsh | 4 + .../kotlin/maven/K2JSCompilerMojo.java | 4 + 40 files changed, 491 insertions(+), 81 deletions(-) create mode 100644 compiler/testData/cli/js/sourceMapEmbedSources.args create mode 100644 compiler/testData/cli/js/sourceMapEmbedSources.out create mode 100644 compiler/testData/cli/js/sourceMapEmbedSources.test create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/config/SourceMapSourceEmbedding.java create mode 100644 js/js.translator/testData/box/incremental/sourceMapSourceEmbedding.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/app/src/main/kotlin/main.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/build.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/lib/src/main/kotlin/foo.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/settings.gradle create mode 100644 libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/pom.xml create mode 100644 libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/src/main/kotlin/org/jetbrains/HelloWorld.kt create mode 100644 libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/verify.bsh diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/DefaultValues.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/DefaultValues.kt index ce3952ea2eb..976f5157f37 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/DefaultValues.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/DefaultValues.kt @@ -48,6 +48,15 @@ open class DefaultValues(val defaultValue: String, val possibleValues: List { private static final Map moduleKindMap = new HashMap<>(); + private static final Map sourceMapContentEmbeddingMap = new LinkedHashMap<>(); static { moduleKindMap.put(K2JsArgumentConstants.MODULE_PLAIN, ModuleKind.PLAIN); moduleKindMap.put(K2JsArgumentConstants.MODULE_COMMONJS, ModuleKind.COMMON_JS); moduleKindMap.put(K2JsArgumentConstants.MODULE_AMD, ModuleKind.AMD); moduleKindMap.put(K2JsArgumentConstants.MODULE_UMD, ModuleKind.UMD); + + sourceMapContentEmbeddingMap.put(K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_ALWAYS, SourceMapSourceEmbedding.ALWAYS); + sourceMapContentEmbeddingMap.put(K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_NEVER, SourceMapSourceEmbedding.NEVER); + sourceMapContentEmbeddingMap.put(K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_INLINING, SourceMapSourceEmbedding.INLINING); } public static void main(String... args) { @@ -357,8 +363,25 @@ public class K2JSCompiler extends CLICompiler { messageCollector.report( ERROR, "Unknown module kind: " + moduleKindName + ". Valid values are: plain, amd, commonjs, umd", null ); + moduleKind = ModuleKind.PLAIN; } configuration.put(JSConfigurationKeys.MODULE_KIND, moduleKind); + + String sourceMapEmbedContentString = arguments.sourceMapEmbedSources; + SourceMapSourceEmbedding sourceMapContentEmbedding = sourceMapEmbedContentString != null ? + sourceMapContentEmbeddingMap.get(sourceMapEmbedContentString) : + SourceMapSourceEmbedding.INLINING; + if (sourceMapContentEmbedding == null) { + String message = "Unknown source map source embedding mode: " + sourceMapEmbedContentString + ". Valid values are: " + + StringUtil.join(sourceMapContentEmbeddingMap.keySet(), ", "); + messageCollector.report(ERROR, message, null); + sourceMapContentEmbedding = SourceMapSourceEmbedding.INLINING; + } + configuration.put(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, sourceMapContentEmbedding); + + if (!arguments.sourceMap && sourceMapEmbedContentString != null) { + messageCollector.report(WARNING, "source-map-embed-sources argument has no effect without source map", null); + } } @NotNull diff --git a/compiler/testData/cli/js/jsHelp.out b/compiler/testData/cli/js/jsHelp.out index a96737b4520..b411282abe1 100644 --- a/compiler/testData/cli/js/jsHelp.out +++ b/compiler/testData/cli/js/jsHelp.out @@ -7,6 +7,8 @@ where possible options include: -source-map-prefix Prefix for paths in a source map -source-map-source-roots Base directories which are used to calculate relative paths to source files in source map + -source-map-embed-sources { always, never, inlining } + Embed source files into 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 } diff --git a/compiler/testData/cli/js/sourceMapEmbedSources.args b/compiler/testData/cli/js/sourceMapEmbedSources.args new file mode 100644 index 00000000000..595760bdea0 --- /dev/null +++ b/compiler/testData/cli/js/sourceMapEmbedSources.args @@ -0,0 +1,7 @@ +$TESTDATA_DIR$/sourceMap.kt +-no-stdlib +-source-map +-source-map-embed-sources +always +-output +$TEMP_DIR$/out.js diff --git a/compiler/testData/cli/js/sourceMapEmbedSources.out b/compiler/testData/cli/js/sourceMapEmbedSources.out new file mode 100644 index 00000000000..a0aba9318ad --- /dev/null +++ b/compiler/testData/cli/js/sourceMapEmbedSources.out @@ -0,0 +1 @@ +OK \ No newline at end of file diff --git a/compiler/testData/cli/js/sourceMapEmbedSources.test b/compiler/testData/cli/js/sourceMapEmbedSources.test new file mode 100644 index 00000000000..abad696f62b --- /dev/null +++ b/compiler/testData/cli/js/sourceMapEmbedSources.test @@ -0,0 +1,3 @@ +// EXISTS: out.js +// CONTAINS: out.js.map, "sourceMap.kt" +// CONTAINS: out.js.map, "var log = \"\"\n\nfun foo(x: String) { diff --git a/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java index 9988fb6f2d3..41a4639ae0a 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java @@ -539,6 +539,12 @@ public class CliTestGenerated extends AbstractCliTest { doJsTest(fileName); } + @TestMetadata("sourceMapEmbedSources.args") + public void testSourceMapEmbedSources() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMapEmbedSources.args"); + doJsTest(fileName); + } + @TestMetadata("sourceMapPrefix.args") public void testSourceMapPrefix() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMapPrefix.args"); diff --git a/compiler/util/src/org/jetbrains/kotlin/utils/JsLibraryUtils.kt b/compiler/util/src/org/jetbrains/kotlin/utils/JsLibraryUtils.kt index 7caad458fee..e5c32325528 100644 --- a/compiler/util/src/org/jetbrains/kotlin/utils/JsLibraryUtils.kt +++ b/compiler/util/src/org/jetbrains/kotlin/utils/JsLibraryUtils.kt @@ -124,7 +124,9 @@ object JsLibraryUtils { librariesWithoutSourceMaps += JsLibrary(content, relativePath, null) } else if (entryName.endsWith(KotlinJavascriptMetadataUtils.JS_MAP_EXT)) { - possibleMapFiles[entryName.removeSuffix(KotlinJavascriptMetadataUtils.JS_MAP_EXT)] = entry + val correspondingJsPath = entryName.removeSuffix(KotlinJavascriptMetadataUtils.JS_MAP_EXT) + + KotlinJavascriptMetadataUtils.JS_EXT + possibleMapFiles[correspondingJsPath] = entry } } } diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt index b3d19330b39..4519d9af843 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt @@ -16,8 +16,31 @@ package org.jetbrains.kotlin.js.backend.ast +import java.io.Reader + data class JsLocation( - val file: String, - val startLine: Int, - val startChar: Int -) \ No newline at end of file + override val file: String, + override val startLine: Int, + override val startChar: Int +) : JsLocationWithSource { + override val identityObject: Any? = null + override val sourceProvider: () -> Reader? = { null } + + override fun asSimpleLocation(): JsLocation = this +} + +interface JsLocationWithSource { + val file: String + val startLine: Int + val startChar: Int + val identityObject: Any? + val sourceProvider: () -> Reader? + + fun asSimpleLocation(): JsLocation +} + +class JsLocationWithEmbeddedSource( + private val location: JsLocation, + override val identityObject: Any?, + override val sourceProvider: () -> Reader? +) : JsLocationWithSource by location \ No newline at end of file diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java index 662a97d2ec0..7170d30a783 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java @@ -35,6 +35,9 @@ public class JSConfigurationKeys { public static final CompilerConfigurationKey> SOURCE_MAP_SOURCE_ROOTS = CompilerConfigurationKey.create("base directories used to calculate relative paths for source map"); + public static final CompilerConfigurationKey SOURCE_MAP_EMBED_SOURCES = + CompilerConfigurationKey.create("embed source files into source map"); + public static final CompilerConfigurationKey META_INFO = CompilerConfigurationKey.create("generate .meta.js and .kjsm files"); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java index 42319aaf41f..58ad3a195c3 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java @@ -113,6 +113,11 @@ public class JsConfig { return configuration.get(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, Collections.singletonList(".")); } + @NotNull + public SourceMapSourceEmbedding getSourceMapContentEmbedding() { + return configuration.get(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, SourceMapSourceEmbedding.INLINING); + } + @NotNull public List getFriends() { if (getConfiguration().getBoolean(JSConfigurationKeys.FRIEND_PATHS_DISABLED)) return Collections.emptyList(); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/config/SourceMapSourceEmbedding.java b/js/js.frontend/src/org/jetbrains/kotlin/js/config/SourceMapSourceEmbedding.java new file mode 100644 index 00000000000..60fb00d5536 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/config/SourceMapSourceEmbedding.java @@ -0,0 +1,23 @@ +/* + * 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.config; + +public enum SourceMapSourceEmbedding { + NEVER, + ALWAYS, + INLINING +} diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt index e19e90834a5..dbd24ef2a91 100644 --- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt @@ -16,7 +16,9 @@ package org.jetbrains.kotlin.js.parser.sourcemaps -class SourceMap { +import java.io.Reader + +class SourceMap(val sourceContentResolver: (String) -> Reader?) { val groups = mutableListOf() } diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt index 5adf792e1ce..467d2e279b7 100644 --- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt @@ -16,10 +16,7 @@ package org.jetbrains.kotlin.js.parser.sourcemaps -import org.jetbrains.kotlin.js.backend.ast.JsLocation -import org.jetbrains.kotlin.js.backend.ast.JsNode -import org.jetbrains.kotlin.js.backend.ast.RecursiveJsVisitor -import org.jetbrains.kotlin.js.backend.ast.SourceInfoAwareJsNode +import org.jetbrains.kotlin.js.backend.ast.* class SourceMapLocationRemapper(val sourceMaps: Map) { fun remap(node: JsNode) { @@ -62,7 +59,8 @@ class SourceMapLocationRemapper(val sourceMaps: Map) { } val segment = group.segments[lastSegmentIndex] - node.source = JsLocation(segment.sourceFileName, segment.sourceLineNumber, segment.sourceColumnNumber) + val location = JsLocation(segment.sourceFileName, segment.sourceLineNumber, segment.sourceColumnNumber) + node.source = JsLocationWithEmbeddedSource(location, sourceMap) { sourceMap.sourceContentResolver(segment.sourceFileName) } return true } diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapParser.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapParser.kt index 65323b064f1..422fb26bb11 100644 --- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapParser.kt +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapParser.kt @@ -38,6 +38,7 @@ import org.json.JSONObject import org.json.JSONTokener import java.io.IOException import java.io.Reader +import java.io.StringReader object SourceMapParser { @Throws(IOException::class) @@ -72,6 +73,20 @@ object SourceMapParser { emptyList() } + val sourcesContent: List = if (jsonObject.has("sourcesContent")) { + val sourcesContentProperty = jsonObject.get("sourcesContent") as? JSONArray ?: + return SourceMapError("'sourcesContent' property is not of array type") + if (sourcesContentProperty.any { it != JSONObject.NULL && it !is String? }) { + return SourceMapError("'sources' array must contain strings") + } + sourcesContentProperty.map { if (it == JSONObject.NULL) null else it as String? } + } + else { + emptyList() + } + + val sourcePathToContent = sources.zip(sourcesContent).associate { it } + if (!jsonObject.has("mappings")) return SourceMapError("'mappings' property not found") val mappings = jsonObject.get("mappings") as? String ?: return SourceMapError("'mappings' property is not of string type") @@ -80,7 +95,7 @@ object SourceMapParser { var sourceColumn = 0 var sourceIndex = 0 val stream = MappingStream(mappings) - val sourceMap = SourceMap() + val sourceMap = SourceMap { sourcePathToContent[it]?.let { StringReader(it) } } var currentGroup = SourceMapGroup().also { sourceMap.groups += it } while (!stream.isEof) { diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt index d30c5ac576f..58e744445fb 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt @@ -22,11 +22,14 @@ import org.jetbrains.kotlin.protobuf.CodedInputStream import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.* import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.Expression.ExpressionCase import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.Statement.StatementCase +import java.io.File +import java.io.FileInputStream import java.io.InputStream +import java.io.InputStreamReader import java.util.* import org.jetbrains.kotlin.resolve.inline.InlineStrategy as KotlinInlineStrategy -class JsAstDeserializer(program: JsProgram) { +class JsAstDeserializer(program: JsProgram, private val sourceRoots: Iterable) { private val scope = JsRootScope(program) private val stringTable = mutableListOf() private val nameTable = mutableListOf() @@ -515,7 +518,15 @@ class JsAstDeserializer(program: JsProgram) { } val node = action() if (deserializedLocation != null) { - node.source = deserializedLocation + val contentFile = sourceRoots + .map { File(it, file) } + .firstOrNull { it.exists() } + node.source = if (contentFile != null) { + JsLocationWithEmbeddedSource(deserializedLocation, null) { InputStreamReader(FileInputStream(contentFile), "UTF-8") } + } + else { + deserializedLocation + } } if (shouldUpdateFile) { fileStack.pop() diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt index 3c341366032..941db57e2eb 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt @@ -386,7 +386,7 @@ class JsAstSerializer(private val pathResolver: (File) -> String) { val name = nameRef.name val qualifier = nameRef.qualifier if (name != null) { - if (qualifier != null || (nameRef.inlineStrategy?.isInline ?: false)) { + if (qualifier != null || nameRef.inlineStrategy?.isInline == true) { val nameRefBuilder = NameReference.newBuilder() nameRefBuilder.nameId = serialize(name) if (qualifier != null) { @@ -589,7 +589,7 @@ class JsAstSerializer(private val pathResolver: (File) -> String) { private fun extractLocation(node: JsNode): JsLocation? { val source = node.source return when (source) { - is JsLocation -> source + is JsLocationWithSource -> source.asSimpleLocation() is PsiElement -> extractLocation(source) else -> null } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt index 8d7fe778e6a..d117416ea77 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt @@ -36,6 +36,7 @@ import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.config.EcmaVersion import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JsConfig +import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding import org.jetbrains.kotlin.js.dce.DeadCodeElimination import org.jetbrains.kotlin.js.dce.InputFile import org.jetbrains.kotlin.js.facade.* @@ -284,45 +285,63 @@ abstract class BasicBoxTest( val additionalFiles = globalCommonFiles + localCommonFiles + additionalCommonFiles val psiFiles = createPsiFiles(testFiles + additionalFiles) - val config = createConfig(module, dependencies, friends, multiModule, additionalMetadata = null) + val sourceDirs = (testFiles + additionalFiles).map { File(it).parent }.distinct() + val config = createConfig(sourceDirs, module, dependencies, friends, multiModule, additionalMetadata = null) val outputFile = File(outputFileName) translateFiles(psiFiles.map(TranslationUnit::SourceFile), outputFile, config, outputPrefixFile, outputPostfixFile, mainCallParameters) if (module.hasFilesToRecompile) { - val incrementalDir = File(outputFile.parentFile, "incremental/" + outputFile.nameWithoutExtension) - val serializedMetadata = mutableListOf() - val translationUnits = kotlinFiles.withIndex().map { (index, file) -> - if (file.recompile) { - TranslationUnit.SourceFile(createPsiFile(file.fileName)) - } - else { - serializedMetadata += File(incrementalDir, "$index.$METADATA_EXTENSION") - val astFile = File(incrementalDir, "$index.$AST_EXTENSION") - TranslationUnit.BinaryAst(FileUtil.loadFileBytes(astFile)) - } + checkIncrementalCompilation(sourceDirs, module, kotlinFiles, additionalFiles, dependencies, friends, multiModule, outputFile, + outputPrefixFile, outputPostfixFile, mainCallParameters) + } + } + + private fun checkIncrementalCompilation( + sourceDirs: List, + module: TestModule, + kotlinFiles: List, + additionalFiles: List, + dependencies: List, + friends: List, + multiModule: Boolean, + outputFile: File, + outputPrefixFile: File?, + outputPostfixFile: File?, + mainCallParameters: MainCallParameters + ) { + val incrementalDir = File(outputFile.parentFile, "incremental/" + outputFile.nameWithoutExtension) + val serializedMetadata = mutableListOf() + val translationUnits = kotlinFiles.withIndex().map { (index, file) -> + if (file.recompile) { + TranslationUnit.SourceFile(createPsiFile(file.fileName)) } - val allTranslationUnits = translationUnits + additionalFiles.withIndex().map { (index, _) -> - val astFile = File(incrementalDir, "${index + translationUnits.size}.$AST_EXTENSION") + else { + serializedMetadata += File(incrementalDir, "$index.$METADATA_EXTENSION") + val astFile = File(incrementalDir, "$index.$AST_EXTENSION") TranslationUnit.BinaryAst(FileUtil.loadFileBytes(astFile)) } - - val headerFile = File(incrementalDir, HEADER_FILE) - val recompiledConfig = createConfig(module, dependencies, friends, multiModule, Pair(headerFile,serializedMetadata)) - val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js") - - translateFiles(allTranslationUnits, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile, - mainCallParameters) - - val originalOutput = FileUtil.loadFile(outputFile) - val recompiledOutput = removeRecompiledSuffix(FileUtil.loadFile(recompiledOutputFile)) - TestCase.assertEquals("Output file changed after recompilation", originalOutput, recompiledOutput) - - val originalSourceMap = FileUtil.loadFile(File(outputFile.parentFile, outputFile.name + ".map")) - val recompiledSourceMap = removeRecompiledSuffix( - FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.name + ".map"))) - TestCase.assertEquals("Source map file changed after recompilation", originalSourceMap, recompiledSourceMap) } + val allTranslationUnits = translationUnits + additionalFiles.withIndex().map { (index, _) -> + val astFile = File(incrementalDir, "${index + translationUnits.size}.$AST_EXTENSION") + TranslationUnit.BinaryAst(FileUtil.loadFileBytes(astFile)) + } + + val headerFile = File(incrementalDir, HEADER_FILE) + val recompiledConfig = createConfig(sourceDirs, module, dependencies, friends, multiModule, Pair(headerFile,serializedMetadata)) + val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js") + + translateFiles(allTranslationUnits, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile, + mainCallParameters) + + val originalOutput = FileUtil.loadFile(outputFile) + val recompiledOutput = removeRecompiledSuffix(FileUtil.loadFile(recompiledOutputFile)) + TestCase.assertEquals("Output file changed after recompilation", originalOutput, recompiledOutput) + + val originalSourceMap = FileUtil.loadFile(File(outputFile.parentFile, outputFile.name + ".map")) + val recompiledSourceMap = removeRecompiledSuffix( + FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.name + ".map"))) + TestCase.assertEquals("Source map file changed after recompilation", originalSourceMap, recompiledSourceMap) } private fun removeRecompiledSuffix(text: String): String = text.replace("-recompiled.js", ".js") @@ -411,7 +430,7 @@ abstract class BasicBoxTest( val output = TextOutputImpl() val pathResolver = SourceFilePathResolver(mutableListOf(File("."))) - val sourceMapBuilder = SourceMap3Builder(outputFile, output, "", SourceMapBuilderConsumer(pathResolver)) + val sourceMapBuilder = SourceMap3Builder(outputFile, output, "", SourceMapBuilderConsumer(pathResolver, false, false)) generatedProgram.accept(JsSourceGenerationVisitor(output, sourceMapBuilder)) val code = output.toString() val generatedSourceMap = sourceMapBuilder.build() @@ -454,7 +473,9 @@ abstract class BasicBoxTest( private fun createPsiFiles(fileNames: List): List = fileNames.map(this::createPsiFile) private fun createConfig( - module: TestModule, dependencies: List, friends: List, multiModule: Boolean, additionalMetadata: Pair>? + sourceDirs: List, + module: TestModule, dependencies: List, friends: List, multiModule: Boolean, + additionalMetadata: Pair>? ): JsConfig { val configuration = environment.configuration.copy() @@ -475,6 +496,8 @@ abstract class BasicBoxTest( configuration.put(JSConfigurationKeys.META_INFO, multiModule) configuration.put(JSConfigurationKeys.SERIALIZE_FRAGMENTS, hasFilesToRecompile) configuration.put(JSConfigurationKeys.SOURCE_MAP, hasFilesToRecompile || generateSourceMap) + configuration.put(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, sourceDirs) + configuration.put(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, module.sourceMapSourceEmbedding) if (additionalMetadata != null) { val metadata = PackagesWithHeaderMetadata( @@ -570,6 +593,10 @@ abstract class BasicBoxTest( currentModule.languageVersion = LanguageVersion.fromVersionString(version) } + SOURCE_MAP_SOURCE_EMBEDDING.find(text)?.let { match -> + currentModule.sourceMapSourceEmbedding = SourceMapSourceEmbedding.valueOf(match.groupValues[1]) + } + return TestFile(temporaryFile.absolutePath, currentModule, recompile = RECOMPILE_PATTERN.matcher(text).find()) } @@ -599,6 +626,7 @@ abstract class BasicBoxTest( var inliningDisabled = false val files = mutableListOf() var languageVersion: LanguageVersion? = null + var sourceMapSourceEmbedding = SourceMapSourceEmbedding.NEVER val hasFilesToRecompile get() = files.any { it.recompile } } @@ -623,6 +651,7 @@ abstract class BasicBoxTest( private val EXPECTED_REACHABLE_NODES_DIRECTIVE = "EXPECTED_REACHABLE_NODES" private val EXPECTED_REACHABLE_NODES = Pattern.compile("^// *$EXPECTED_REACHABLE_NODES_DIRECTIVE: *([0-9]+) *$", Pattern.MULTILINE) private val RECOMPILE_PATTERN = Pattern.compile("^// *RECOMPILE *$", Pattern.MULTILINE) + private val SOURCE_MAP_SOURCE_EMBEDDING = Regex("^// *SOURCE_MAP_EMBED_SOURCES: ([A-Z]+)*\$", RegexOption.MULTILINE) private val AST_EXTENSION = "jsast" private val METADATA_EXTENSION = "jsmeta" private val HEADER_FILE = "header.$METADATA_EXTENSION" diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 9c717530ed5..3831fb96d85 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -3539,6 +3539,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("sourceMapSourceEmbedding.kt") + public void testSourceMapSourceEmbedding() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/sourceMapSourceEmbedding.kt"); + doTest(fileName); + } + @TestMetadata("syntheticStatement.kt") public void testSyntheticStatement() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/syntheticStatement.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/AmbiguousAstSourcePropagation.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/AmbiguousAstSourcePropagation.kt index 9316e252089..2769ed9c360 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/AmbiguousAstSourcePropagation.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/AmbiguousAstSourcePropagation.kt @@ -68,7 +68,7 @@ class AmbiguousAstSourcePropagation : RecursiveJsVisitor() { private fun propagate(node: JsNode) { if (!sourceDefined) { val source = node.source - if (source is JsLocation || source is PsiElement) { + if (source is JsLocationWithSource || source is PsiElement) { sourceDefined = true } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineCollector.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineCollector.kt index ef3e072a93b..c70ca558636 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineCollector.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/LineCollector.kt @@ -39,7 +39,7 @@ class LineCollector : RecursiveJsVisitor() { val document = file.viewProvider.document!! document.getLineNumber(offset) } - is JsLocation -> { + is JsLocationWithSource -> { source.startLine } else -> null diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/facade/SourceMapBuilderConsumer.java b/js/js.translator/src/org/jetbrains/kotlin/js/facade/SourceMapBuilderConsumer.java index 1b362345b85..2154bf93c84 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/facade/SourceMapBuilderConsumer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/facade/SourceMapBuilderConsumer.java @@ -21,19 +21,29 @@ 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.backend.ast.JsLocationWithSource; import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver; import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilder; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.nio.charset.Charset; +import java.util.function.Supplier; public class SourceMapBuilderConsumer implements PairConsumer { @NotNull private final SourceFilePathResolver pathResolver; - public SourceMapBuilderConsumer(@NotNull SourceFilePathResolver pathResolver) { + private final boolean provideCurrentModuleContent; + + private final boolean provideExternalModuleContent; + + public SourceMapBuilderConsumer( + @NotNull SourceFilePathResolver pathResolver, + boolean provideCurrentModuleContent, boolean provideExternalModuleContent + ) { this.pathResolver = pathResolver; + this.provideCurrentModuleContent = provideCurrentModuleContent; + this.provideExternalModuleContent = provideExternalModuleContent; } @Override @@ -49,15 +59,31 @@ public class SourceMapBuilderConsumer implements PairConsumer contentSupplier; + if (provideCurrentModuleContent) { + contentSupplier = () -> { + try { + return new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")); + } + catch (IOException e) { + return null; + } + }; + } + else { + contentSupplier = () -> null; + } + builder.addMapping(pathResolver.getPathRelativeToSourceRoots(file), null, contentSupplier, line, column); } catch (IOException e) { throw new RuntimeException("IO error occurred generating source maps", e); } } - else if (sourceInfo instanceof JsLocation) { - JsLocation location = (JsLocation) sourceInfo; - builder.addMapping(location.getFile(), location.getStartLine(), location.getStartChar()); + else if (sourceInfo instanceof JsLocationWithSource) { + JsLocationWithSource location = (JsLocationWithSource) sourceInfo; + Supplier contentSupplier = provideExternalModuleContent ? location.getSourceProvider()::invoke : () -> null; + builder.addMapping(location.getFile(), location.getIdentityObject(), contentSupplier, + location.getStartLine(), location.getStartChar()); } } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/facade/TranslationResult.kt b/js/js.translator/src/org/jetbrains/kotlin/js/facade/TranslationResult.kt index f2bc8b42958..5a995536292 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/facade/TranslationResult.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/facade/TranslationResult.kt @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor 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.config.SourceMapSourceEmbedding import org.jetbrains.kotlin.js.sourceMap.JsSourceGenerationVisitor import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder @@ -61,8 +62,13 @@ abstract class TranslationResult protected constructor(val diagnostics: Diagnost val sourceMapBuilder = if (config.configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) { val sourceRoots = config.sourceMapRoots.map { File(it) } + val sourceMapContentEmbedding = config.sourceMapContentEmbedding val pathResolver = SourceFilePathResolver(sourceRoots) - SourceMap3Builder(outputFile, output, config.sourceMapPrefix, SourceMapBuilderConsumer(pathResolver)) + val consumer = SourceMapBuilderConsumer( + pathResolver, + sourceMapContentEmbedding == SourceMapSourceEmbedding.ALWAYS, + sourceMapContentEmbedding != SourceMapSourceEmbedding.NEVER) + SourceMap3Builder(outputFile, output, config.sourceMapPrefix, consumer) } else { null diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.java b/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.java index ec40e58e51a..4d542078151 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceMap3Builder.java @@ -16,15 +16,19 @@ package org.jetbrains.kotlin.js.sourceMap; -import org.jetbrains.kotlin.js.common.SourceInfo; -import org.jetbrains.kotlin.js.util.TextOutput; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.PairConsumer; import gnu.trove.TObjectIntHashMap; +import kotlin.io.TextStreamsKt; +import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor; +import org.jetbrains.kotlin.js.common.SourceInfo; +import org.jetbrains.kotlin.js.util.TextOutput; import java.io.File; +import java.io.Reader; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; public class SourceMap3Builder implements SourceMapBuilder { private final StringBuilder out = new StringBuilder(8192); @@ -33,18 +37,16 @@ public class SourceMap3Builder implements SourceMapBuilder { private final String pathPrefix; private final PairConsumer sourceInfoConsumer; - private String lastSource; - private int lastSourceIndex; - - private final TObjectIntHashMap sources = new TObjectIntHashMap() { + private final TObjectIntHashMap sources = new TObjectIntHashMap() { @Override - public int get(String key) { + public int get(SourceKey key) { int index = index(key); return index < 0 ? -1 : _values[index]; } }; private final List orderedSources = new ArrayList<>(); + private final List> orderedSourceContentSuppliers = new ArrayList<>(); private int previousGeneratedColumn = -1; private int previousSourceIndex; @@ -68,7 +70,11 @@ public class SourceMap3Builder implements SourceMapBuilder { public String build() { StringBuilder sb = new StringBuilder(out.length() + (128 * orderedSources.size())); sb.append("{\"version\":3,\"file\":\"").append(generatedFile.getName()).append('"').append(','); + appendSources(sb); + sb.append(","); + appendSourcesContent(sb); + sb.append(",\"names\":["); sb.append("],\"mappings\":\""); sb.append(out); @@ -86,7 +92,29 @@ public class SourceMap3Builder implements SourceMapBuilder { else { isNotFirst = true; } - sb.append('"').append(pathPrefix).append(source).append('"'); + sb.append(JsToStringGenerationVisitor.javaScriptString(pathPrefix + source, true)); + } + sb.append(']'); + } + + private void appendSourcesContent(StringBuilder sb) { + boolean isNotFirst = false; + sb.append('"').append("sourcesContent").append("\":["); + for (Supplier contentSupplier : orderedSourceContentSuppliers) { + if (isNotFirst) { + sb.append(','); + } + else { + isNotFirst = true; + } + + Reader reader = contentSupplier.get(); + if (reader != null) { + sb.append(JsToStringGenerationVisitor.javaScriptString(TextStreamsKt.readText(reader), true)); + } + else { + sb.append("null"); + } } sb.append(']'); } @@ -110,26 +138,21 @@ public class SourceMap3Builder implements SourceMapBuilder { sourceInfoConsumer.consume(this, sourceInfo); } - private int getSourceIndex(String source) { - if (source.equals(lastSource)) { - return lastSourceIndex; - } - - int sourceIndex = sources.get(source); + private int getSourceIndex(String source, Object identityObject, Supplier contentSupplier) { + SourceKey key = new SourceKey(source, identityObject); + int sourceIndex = sources.get(key); if (sourceIndex == -1) { sourceIndex = orderedSources.size(); - sources.put(source, sourceIndex); + sources.put(key, sourceIndex); orderedSources.add(source); + orderedSourceContentSuppliers.add(contentSupplier); } - lastSource = source; - lastSourceIndex = sourceIndex; - return sourceIndex; } @Override - public void addMapping(String source, int sourceLine, int sourceColumn) { + public void addMapping(String source, Object identityObject, Supplier sourceContent, int sourceLine, int sourceColumn) { source = source.replace(File.separatorChar, '/'); boolean newGroupStarted = previousGeneratedColumn == -1; if (newGroupStarted) { @@ -148,7 +171,7 @@ public class SourceMap3Builder implements SourceMapBuilder { // assert columnDiff != 0; Base64VLQ.encode(out, columnDiff); previousGeneratedColumn = textOutput.getColumn(); - int sourceIndex = getSourceIndex(source); + int sourceIndex = getSourceIndex(source, identityObject, sourceContent); Base64VLQ.encode(out, sourceIndex - previousSourceIndex); previousSourceIndex = sourceIndex; @@ -200,4 +223,34 @@ public class SourceMap3Builder implements SourceMapBuilder { while (value > 0); } } + + static final class SourceKey { + private final String sourcePath; + private final Object identityKey; + + SourceKey(String sourcePath, Object identityKey) { + this.sourcePath = sourcePath; + this.identityKey = identityKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SourceKey)) return false; + + SourceKey key = (SourceKey) o; + + if (!sourcePath.equals(key.sourcePath)) return false; + if (identityKey != null ? !identityKey.equals(key.identityKey) : key.identityKey != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = sourcePath.hashCode(); + result = 31 * result + (identityKey != null ? identityKey.hashCode() : 0); + return result; + } + } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceMapBuilder.java b/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceMapBuilder.java index 9c5e93cb574..3b1b4fd2ecb 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceMapBuilder.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceMapBuilder.java @@ -17,13 +17,15 @@ package org.jetbrains.kotlin.js.sourceMap; import java.io.File; +import java.io.Reader; +import java.util.function.Supplier; public interface SourceMapBuilder { void newLine(); void skipLinesAtBeginning(int count); - void addMapping(String source, int sourceLine, int sourceColumn); + void addMapping(String source, Object identityObject, Supplier sourceContent, int sourceLine, int sourceColumn); void processSourceInfo(Object info); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java index ecd033a2f80..242106dacc6 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java @@ -61,7 +61,9 @@ import org.jetbrains.kotlin.types.TypeUtils; import org.jetbrains.kotlin.utils.ExceptionUtilsKt; import java.io.ByteArrayInputStream; +import java.io.File; import java.util.*; +import java.util.stream.Collectors; import static org.jetbrains.kotlin.js.translate.general.ModuleWrapperTranslation.wrapIfNecessary; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.convertToStatement; @@ -274,7 +276,8 @@ public final class Translation { Map> fileMemberScopes = new HashMap<>(); - JsAstDeserializer deserializer = new JsAstDeserializer(program); + List sourceRoots = config.getSourceMapRoots().stream().map(File::new).collect(Collectors.toList()); + JsAstDeserializer deserializer = new JsAstDeserializer(program, sourceRoots); for (TranslationUnit unit : units) { if (unit instanceof TranslationUnit.SourceFile) { KtFile file = ((TranslationUnit.SourceFile) unit).getFile(); diff --git a/js/js.translator/testData/box/incremental/sourceMapSourceEmbedding.kt b/js/js.translator/testData/box/incremental/sourceMapSourceEmbedding.kt new file mode 100644 index 00000000000..3b0737e3926 --- /dev/null +++ b/js/js.translator/testData/box/incremental/sourceMapSourceEmbedding.kt @@ -0,0 +1,12 @@ +// EXPECTED_REACHABLE_NODES: 489 +// SOURCE_MAP_EMBED_SOURCES: ALWAYS +// FILE: a.kt +fun foo() = "O" + +// FILE: b.kt +// RECOMPILE +fun bar() = "K" + +// FILE: main.kt +// RECOMPILE +fun box() = foo() + bar() \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt index c609f120bf5..fe14b717494 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt @@ -196,4 +196,22 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() { assertTrue("Source map should contain reference to $sourceFilePath") { map.contains("\"$sourceFilePath\"") } } } + + @Test + fun testKotlinJsSourceMapInline() { + val project = Project("kotlin2JsProjectWithSourceMapInline", "2.10") + + project.build("build") { + assertSuccessful() + + val mapFilePath = "app/build/classes/main/app_main.js.map" + 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 source of main.kt") { map.contains("\"fun main(args: Array) {\\n") } + assertTrue("Source map should contain source of foo.kt") { map.contains("\"inline fun foo(): String {\\n") } + } + } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/app/src/main/kotlin/main.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/app/src/main/kotlin/main.kt new file mode 100644 index 00000000000..1d0f791fc59 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/app/src/main/kotlin/main.kt @@ -0,0 +1,3 @@ +fun main(args: Array) { + println(foo()) +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/build.gradle new file mode 100644 index 00000000000..66eb386a68f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/build.gradle @@ -0,0 +1,36 @@ +buildscript { + repositories { + mavenLocal() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + apply plugin: 'kotlin2js' + + repositories { + mavenLocal() + mavenCentral() + maven { url "https://dl.bintray.com/kotlin/kotlin-dev/" } + } + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" + } + + compileKotlin2Js { + kotlinOptions.freeCompilerArgs = [ "-Xskip-metadata-version-check" ] + kotlinOptions.sourceMap = true + kotlinOptions.sourceMapEmbedSources = "always" + } +} + +project("app") { + dependencies { + compile project(":lib") + } +} + + diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/lib/src/main/kotlin/foo.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/lib/src/main/kotlin/foo.kt new file mode 100644 index 00000000000..3192672a263 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/lib/src/main/kotlin/foo.kt @@ -0,0 +1,3 @@ +inline fun foo(): String { + return "OK" +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/settings.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/settings.gradle new file mode 100644 index 00000000000..c0b70ce1c99 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/settings.gradle @@ -0,0 +1 @@ +include ':app', ':lib' \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJsOptions.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJsOptions.kt index 94e56ce516c..a677f4f3d5f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJsOptions.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJsOptions.kt @@ -48,6 +48,13 @@ interface KotlinJsOptions : org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions */ var sourceMap: kotlin.Boolean + /** + * Embed source files into source map + * Possible values: "never", "always", "inlining" + * Default value: "inlining" + */ + var sourceMapEmbedSources: kotlin.String + /** * Prefix for paths in a source map * Default value: null diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJsOptionsBase.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJsOptionsBase.kt index a867aeaa078..65b990da07d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJsOptionsBase.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJsOptionsBase.kt @@ -59,6 +59,11 @@ internal abstract class KotlinJsOptionsBase : org.jetbrains.kotlin.gradle.dsl.Ko get() = sourceMapField ?: false set(value) { sourceMapField = value } + private var sourceMapEmbedSourcesField: kotlin.String? = null + override var sourceMapEmbedSources: kotlin.String + get() = sourceMapEmbedSourcesField ?: "inlining" + set(value) { sourceMapEmbedSourcesField = value } + private var sourceMapPrefixField: kotlin.String?? = null override var sourceMapPrefix: kotlin.String? get() = sourceMapPrefixField ?: null @@ -86,6 +91,7 @@ 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 } + sourceMapEmbedSourcesField?.let { args.sourceMapEmbedSources = it } sourceMapPrefixField?.let { args.sourceMapPrefix = it } targetField?.let { args.target = it } typedArraysField?.let { args.typedArrays = it } @@ -104,6 +110,7 @@ internal fun org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments.fil noStdlib = true outputFile = null sourceMap = false + sourceMapEmbedSources = "inlining" sourceMapPrefix = null target = "v5" typedArrays = false diff --git a/libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/pom.xml b/libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/pom.xml new file mode 100644 index 00000000000..fa49124ccd4 --- /dev/null +++ b/libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + org.jetbrains.kotlin + test-js-sourceMapEmbedSources + 1.0-SNAPSHOT + + + + org.jetbrains.kotlin + kotlin-stdlib-js + ${kotlin.version} + + + + + ${project.basedir}/src/main/kotlin + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + compile + + js + + + + + true + always + + + + + + diff --git a/libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/src/main/kotlin/org/jetbrains/HelloWorld.kt b/libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/src/main/kotlin/org/jetbrains/HelloWorld.kt new file mode 100644 index 00000000000..82b141634f1 --- /dev/null +++ b/libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/src/main/kotlin/org/jetbrains/HelloWorld.kt @@ -0,0 +1,3 @@ +package org.jetbrains + +fun bar() = "OK" diff --git a/libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/verify.bsh b/libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/verify.bsh new file mode 100644 index 00000000000..3e9584a11e8 --- /dev/null +++ b/libraries/tools/kotlin-maven-plugin-test/src/it/test-js-sourceMapEmbedSources/verify.bsh @@ -0,0 +1,4 @@ +source(new File(basedir, "../../../verify-common.bsh").getAbsolutePath()); + +assertFileContains("target/js/test-js-sourceMapEmbedSources.js.map", "\"org/jetbrains/HelloWorld.kt\""); +assertFileContains("target/js/test-js-sourceMapEmbedSources.js.map", "\"package org.jetbrains\\n\\nfun bar() = \\\"OK\\\""); diff --git a/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/K2JSCompilerMojo.java b/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/K2JSCompilerMojo.java index 7be5a95e6c8..1bd7aee1e42 100644 --- a/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/K2JSCompilerMojo.java +++ b/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/K2JSCompilerMojo.java @@ -70,6 +70,9 @@ public class K2JSCompilerMojo extends KotlinCompileMojoBasecall and noCall. */ @@ -110,6 +113,7 @@ public class K2JSCompilerMojo extends KotlinCompileMojoBase> collector = getOutputDirectoriesCollector();