diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinFileMetadata.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinFileMetadata.kt new file mode 100644 index 00000000000..160860048b0 --- /dev/null +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinFileMetadata.kt @@ -0,0 +1,28 @@ +/* + * 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.serialization.js + +import org.jetbrains.kotlin.psi.KtFile + +sealed class KotlinFileMetadata + +data class KotlinPsiFileMetadata(val ktFile: KtFile) : KotlinFileMetadata() + +data class KotlinDeserializedFileMetadata( + val packageFragment: KotlinJavascriptPackageFragment, + val fileId: Int +) : KotlinFileMetadata() \ No newline at end of file diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinFileRegistry.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinFileRegistry.kt index d80cc8a4994..48a5a7ba4c5 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinFileRegistry.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinFileRegistry.kt @@ -16,13 +16,11 @@ package org.jetbrains.kotlin.serialization.js -import org.jetbrains.kotlin.psi.KtFile - class KotlinFileRegistry { - private val fileIdsImpl = mutableMapOf() + private val fileIdsImpl = mutableMapOf() - fun lookup(file: KtFile) = fileIdsImpl.getOrPut(file) { fileIdsImpl.size } + fun lookup(file: KotlinFileMetadata) = fileIdsImpl.getOrPut(file) { fileIdsImpl.size } - val fileIds: Map + val fileIds: Map get() = fileIdsImpl } diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptPackageFragment.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptPackageFragment.kt index 3257b25b2b7..e61e07c3deb 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptPackageFragment.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptPackageFragment.kt @@ -29,10 +29,7 @@ import org.jetbrains.kotlin.serialization.deserialization.AnnotationDeserializer import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration import org.jetbrains.kotlin.serialization.deserialization.DeserializedPackageFragmentImpl import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErrorData -import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource -import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor -import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.storage.getValue @@ -44,7 +41,7 @@ class KotlinJavascriptPackageFragment( header: JsProtoBuf.Header, configuration: DeserializationConfiguration ) : DeserializedPackageFragmentImpl(fqName, storageManager, module, proto, JsContainerSource(fqName, header, configuration)) { - private val fileMap: Map by storageManager.createLazyValue { + val fileMap: Map by storageManager.createLazyValue { this.proto.getExtension(JsProtoBuf.packageFragmentFiles).fileList.withIndex().associate { (index, file) -> (if (file.hasId()) file.id else index) to FileHolder(file.annotationList) } @@ -58,17 +55,12 @@ class KotlinJavascriptPackageFragment( if (DescriptorUtils.getParentOfType(descriptor, PackageFragmentDescriptor::class.java) != this) { throw IllegalArgumentException("Provided descriptor $descriptor does not belong to this package $this") } - val fileId = when (descriptor) { - is DeserializedClassDescriptor -> descriptor.classProto.getExtension(JsProtoBuf.classContainingFileId) - is DeserializedSimpleFunctionDescriptor -> descriptor.proto.getExtension(JsProtoBuf.functionContainingFileId) - is DeserializedPropertyDescriptor -> descriptor.proto.getExtension(JsProtoBuf.propertyContainingFileId) - else -> null - } + val fileId = descriptor.extractFileId() return fileId?.let { fileMap[it] }?.annotations.orEmpty() } - private inner class FileHolder(val annotationsProto: List) { + inner class FileHolder(private val annotationsProto: List) { val annotations: List by storageManager.createLazyValue { annotationsProto.map { annotationDeserializer.deserializeAnnotation(it, nameResolver) } } diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt index 8fa0d7013f2..18fc957c476 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt @@ -29,6 +29,9 @@ import org.jetbrains.kotlin.serialization.AnnotationSerializer import org.jetbrains.kotlin.serialization.DescriptorSerializer import org.jetbrains.kotlin.serialization.ProtoBuf import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.utils.JsMetadataVersion import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils @@ -130,7 +133,9 @@ object KotlinJavascriptSerializationUtil { val builder = ProtoBuf.PackageFragment.newBuilder() // TODO: ModuleDescriptor should be able to return the package only with the contents of that module, without dependencies - val skip: (DeclarationDescriptor) -> Boolean = { DescriptorUtils.getContainingModule(it) != module || (it is MemberDescriptor && it.isExpect) } + val skip: (DeclarationDescriptor) -> Boolean = { + DescriptorUtils.getContainingModule(it) != module || (it is MemberDescriptor && it.isExpect) + } val fileRegistry = KotlinFileRegistry() val serializerExtension = KotlinJavascriptSerializerExtension(fileRegistry) @@ -183,8 +188,11 @@ object KotlinJavascriptSerializationUtil { if (id != filesProto.fileCount) { fileProto.id = id } - for (annotationPsi in file.annotationEntries) { - val annotation = bindingContext[BindingContext.ANNOTATION, annotationPsi]!! + val annotations = when (file) { + is KotlinPsiFileMetadata -> file.ktFile.annotationEntries.map { bindingContext[BindingContext.ANNOTATION, it]!! } + is KotlinDeserializedFileMetadata -> file.packageFragment.fileMap[file.fileId]!!.annotations + } + for (annotation in annotations) { fileProto.addAnnotation(serializer.serializeAnnotation(annotation)) } filesProto.addFile(fileProto) @@ -282,4 +290,11 @@ object KotlinJavascriptSerializationUtil { } } -data class KotlinJavaScriptLibraryParts(val header: JsProtoBuf.Header, val body: List) \ No newline at end of file +data class KotlinJavaScriptLibraryParts(val header: JsProtoBuf.Header, val body: List) + +internal fun DeclarationDescriptor.extractFileId(): Int? = when (this) { + is DeserializedClassDescriptor -> classProto.getExtension(JsProtoBuf.classContainingFileId) + is DeserializedSimpleFunctionDescriptor -> proto.getExtension(JsProtoBuf.functionContainingFileId) + is DeserializedPropertyDescriptor -> proto.getExtension(JsProtoBuf.propertyContainingFileId) + else -> null +} \ No newline at end of file diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializerExtension.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializerExtension.kt index 24aef0aa4b8..452282931d9 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializerExtension.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializerExtension.kt @@ -64,11 +64,17 @@ class KotlinJavascriptSerializerExtension( private fun getFileId(descriptor: DeclarationDescriptor): Int? { if (!DescriptorUtils.isTopLevelDeclaration(descriptor) || descriptor !is DeclarationDescriptorWithSource) return null - val file = descriptor.source.containingFile - if (file !is PsiSourceFile) return null + val fileId = descriptor.extractFileId() + if (fileId != null) { + (descriptor.containingDeclaration as? KotlinJavascriptPackageFragment)?.let { packageFragment -> + return fileRegistry.lookup(KotlinDeserializedFileMetadata(packageFragment, fileId)) + } + } + + val file = descriptor.source.containingFile as? PsiSourceFile ?: return null val psiFile = file.psiFile - return (psiFile as? KtFile)?.let { fileRegistry.lookup(it) } + return (psiFile as? KtFile)?.let { fileRegistry.lookup(KotlinPsiFileMetadata(it)) } } } 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 6417f6da46d..7e626bcb86e 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 @@ -298,7 +298,8 @@ abstract class BasicBoxTest( val outputFile = File(outputFileName) val incrementalData = IncrementalData() - translateFiles(psiFiles.map(TranslationUnit::SourceFile), outputFile, config, outputPrefixFile, outputPostfixFile, mainCallParameters, incrementalData) + translateFiles(psiFiles.map(TranslationUnit::SourceFile), outputFile, config, outputPrefixFile, outputPostfixFile, + mainCallParameters, incrementalData) if (module.hasFilesToRecompile) { checkIncrementalCompilation(sourceDirs, module, kotlinFiles, dependencies, friends, multiModule, outputFile, @@ -334,7 +335,7 @@ abstract class BasicBoxTest( .sortedBy { it.canonicalPath } .map { sourceToTranslationUnit[it]!! } - val recompiledConfig = createConfig(sourceDirs,module, dependencies, friends, multiModule, incrementalData) + val recompiledConfig = createConfig(sourceDirs, module, dependencies, friends, multiModule, incrementalData) val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js") translateFiles(translationUnits, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile, @@ -348,6 +349,13 @@ abstract class BasicBoxTest( val recompiledSourceMap = removeRecompiledSuffix( FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.name + ".map"))) TestCase.assertEquals("Source map file changed after recompilation", originalSourceMap, recompiledSourceMap) + + if (multiModule) { + val originalMetadata = FileUtil.loadFile(File(outputFile.parentFile, outputFile.nameWithoutExtension + ".meta.js")) + val recompiledMetadata = removeRecompiledSuffix( + FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.nameWithoutExtension + ".meta.js"))) + TestCase.assertEquals("Metadata file changed after recompilation", originalMetadata, recompiledMetadata) + } } private fun removeRecompiledSuffix(text: String): String = text.replace("-recompiled.js", ".js") @@ -475,7 +483,8 @@ abstract class BasicBoxTest( private fun createPsiFiles(fileNames: List): List = fileNames.map(this::createPsiFile) private fun createConfig( - sourceDirs: List,module: TestModule, dependencies: List, friends: List, multiModule: Boolean, incrementalData: IncrementalData? + sourceDirs: List,module: TestModule, dependencies: List, friends: List, + multiModule: Boolean, incrementalData: IncrementalData? ): JsConfig { val configuration = environment.configuration.copy() 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 e5fb9d3cb6a..0c7b103cf0d 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 @@ -3686,6 +3686,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("jsModule.kt") + public void testJsModule() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/jsModule.kt"); + doTest(fileName); + } + @TestMetadata("multipleExport.kt") public void testMultipleExport() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/multipleExport.kt"); diff --git a/js/js.translator/testData/box/incremental/jsModule.js b/js/js.translator/testData/box/incremental/jsModule.js new file mode 100644 index 00000000000..e23b8236e20 --- /dev/null +++ b/js/js.translator/testData/box/incremental/jsModule.js @@ -0,0 +1,14 @@ +define("foo", [], function() { + return { + "fooF": function() { + return "foo"; + } + }; +}); +define("bar", [], function() { + return { + "barF": function() { + return "bar"; + } + }; +}); \ No newline at end of file diff --git a/js/js.translator/testData/box/incremental/jsModule.kt b/js/js.translator/testData/box/incremental/jsModule.kt new file mode 100644 index 00000000000..7904eb29357 --- /dev/null +++ b/js/js.translator/testData/box/incremental/jsModule.kt @@ -0,0 +1,32 @@ +// EXPECTED_REACHABLE_NODES: 1018 + +// MODULE: lib +// FILE: a.kt +// MODULE_KIND: AMD +@file:JsModule("foo") + +external fun fooF(): String + +// FILE: b.kt +@file:JsModule("bar") + +external fun barF(): String + +// FILE: c.kt +// RECOMPILE +fun dummyF() = "dummy" + +// MODULE: main(lib) +// FILE: main.kt +// MODULE_KIND: AMD + +fun box(): String { + val foo = fooF() + if (foo != "foo") return "fail1: $foo" + + val bar = barF() + if (bar != "bar") return "fail2: $bar" + + return "OK" +} +