diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/MockLibraryUtil.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/test/MockLibraryUtil.kt index faf1cb36fda..eb7f18f5678 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/test/MockLibraryUtil.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/MockLibraryUtil.kt @@ -126,12 +126,12 @@ object MockLibraryUtil { } @JvmStatic - fun compileJsLibraryToJar(sourcesPath: String, jarName: String, addSources: Boolean): File { + fun compileJsLibraryToJar(sourcesPath: String, jarName: String, addSources: Boolean, extraOptions: List = emptyList()): File { val contentDir = KotlinTestUtils.tmpDir("testLibrary-" + jarName) val outDir = File(contentDir, "out") val outputFile = File(outDir, jarName + ".js") - compileKotlin2JS(sourcesPath, outputFile) + compileKotlin2JS(sourcesPath, outputFile, extraOptions) return createJarFile(contentDir, outDir, jarName, sourcesPath.takeIf { addSources }) } @@ -189,8 +189,8 @@ object MockLibraryUtil { runJvmCompiler(args) } - private fun compileKotlin2JS(sourcesPath: String, outputFile: File) { - runJsCompiler(listOf("-meta-info", "-output", outputFile.absolutePath, sourcesPath)) + private fun compileKotlin2JS(sourcesPath: String, outputFile: File, extraOptions: List) { + runJsCompiler(listOf("-meta-info", "-output", outputFile.absolutePath, sourcesPath, *extraOptions.toTypedArray())) } fun compileKotlinModule(buildFilePath: String) { diff --git a/core/descriptors/src/org/jetbrains/kotlin/name/ClassId.java b/core/descriptors/src/org/jetbrains/kotlin/name/ClassId.java index fa5fbf4d8a4..9bf3f1920c2 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/name/ClassId.java +++ b/core/descriptors/src/org/jetbrains/kotlin/name/ClassId.java @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.name; +import kotlin.text.StringsKt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -94,6 +95,16 @@ public final class ClassId { return packageFqName.startsWith(segment); } + /** + * @param string a string where packages are delimited by '/' and classes by '.', e.g. "kotlin/Map.Entry" + */ + @NotNull + public static ClassId fromString(@NotNull String string) { + String packageName = StringsKt.substringBeforeLast(string, '/', "").replace('/', '.'); + String className = StringsKt.substringAfterLast(string, '/', string); + return new ClassId(new FqName(packageName), new FqName(className), false); + } + /** * @return a string where packages are delimited by '/' and classes by '.', e.g. "kotlin/Map.Entry" */ diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEPackagePartProvider.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEPackagePartProvider.kt index d47ac154f0f..4aeb717ee21 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEPackagePartProvider.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEPackagePartProvider.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.caches.resolve import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.indexing.FileBasedIndex import org.jetbrains.kotlin.descriptors.PackagePartProvider +import org.jetbrains.kotlin.idea.vfilefinder.KotlinJvmModuleAnnotationsIndex import org.jetbrains.kotlin.idea.vfilefinder.KotlinModuleMappingIndex import org.jetbrains.kotlin.load.kotlin.PackageParts import org.jetbrains.kotlin.name.ClassId @@ -33,7 +34,7 @@ class IDEPackagePartProvider(val scope: GlobalSearchScope) : PackagePartProvider private fun getPackageParts(packageFqName: String): MutableList = FileBasedIndex.getInstance().getValues(KotlinModuleMappingIndex.KEY, packageFqName, scope) - override fun getAnnotationsOnBinaryModule(moduleName: String): List { - throw UnsupportedOperationException() - } + // Note that in case of several modules with the same name, we return all annotations on all of them, which is probably incorrect + override fun getAnnotationsOnBinaryModule(moduleName: String): List = + FileBasedIndex.getInstance().getValues(KotlinJvmModuleAnnotationsIndex.KEY, moduleName, scope).flatten() } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/compiler/IdeModuleAnnotationsResolver.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/compiler/IdeModuleAnnotationsResolver.kt new file mode 100644 index 00000000000..86bc158801a --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/compiler/IdeModuleAnnotationsResolver.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.compiler + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.IDEPackagePartProvider +import org.jetbrains.kotlin.js.resolve.getAnnotationsOnContainingJsModule +import org.jetbrains.kotlin.load.kotlin.getJvmModuleNameForDeserializedDescriptor +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver + +class IdeModuleAnnotationsResolver(private val project: Project) : ModuleAnnotationsResolver { + override fun getAnnotationsOnContainingModule(descriptor: DeclarationDescriptor): List { + getAnnotationsOnContainingJsModule(descriptor)?.let { return it } + + val moduleName = getJvmModuleNameForDeserializedDescriptor(descriptor) ?: return emptyList() + // TODO: allScope is incorrect here, need to look only in the root where this element comes from + return IDEPackagePartProvider(GlobalSearchScope.allScope(project)).getAnnotationsOnBinaryModule(moduleName) + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/KotlinJvmModuleAnnotationsIndex.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/KotlinJvmModuleAnnotationsIndex.kt new file mode 100644 index 00000000000..270f345e4d9 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/KotlinJvmModuleAnnotationsIndex.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.vfilefinder + +import com.intellij.util.indexing.* +import com.intellij.util.io.DataExternalizer +import com.intellij.util.io.IOUtil +import org.jetbrains.kotlin.load.kotlin.ModuleMapping +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration +import java.io.DataInput +import java.io.DataOutput + +object KotlinJvmModuleAnnotationsIndex : FileBasedIndexExtension>() { + + val KEY: ID> = ID.create(KotlinJvmModuleAnnotationsIndex::class.java.canonicalName) + + private val KEY_DESCRIPTOR = KotlinModuleMappingIndex.STRING_KEY_DESCRIPTOR + + private val VALUE_EXTERNALIZER = object : DataExternalizer> { + override fun read(input: DataInput): List? = + IOUtil.readStringList(input).map(ClassId::fromString) + + override fun save(out: DataOutput, value: List) = + IOUtil.writeStringList(out, value.map(ClassId::asString)) + } + + override fun getName() = KEY + + override fun dependsOnFileContent() = true + + override fun getKeyDescriptor() = KEY_DESCRIPTOR + + override fun getValueExternalizer() = VALUE_EXTERNALIZER + + override fun getInputFilter(): FileBasedIndex.InputFilter = + FileBasedIndex.InputFilter { file -> file.extension == ModuleMapping.MAPPING_FILE_EXT } + + override fun getVersion(): Int = 1 + + override fun getIndexer(): DataIndexer, FileContent> = DataIndexer { inputData -> + val file = inputData.file + try { + val moduleMapping = ModuleMapping.create(inputData.content, file.toString(), DeserializationConfiguration.Default) + return@DataIndexer mapOf(file.nameWithoutExtension to moduleMapping.moduleData.annotations) + } catch (e: Exception) { + // Exceptions are already reported in KotlinModuleMappingIndex + emptyMap() + } + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/KotlinModuleMappingIndex.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/KotlinModuleMappingIndex.kt index dcafc318444..a922ea82de3 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/KotlinModuleMappingIndex.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/KotlinModuleMappingIndex.kt @@ -30,7 +30,7 @@ object KotlinModuleMappingIndex : FileBasedIndexExtension( val KEY: ID = ID.create(KotlinModuleMappingIndex::class.java.canonicalName) - private val KEY_DESCRIPTOR = object : KeyDescriptor { + internal val STRING_KEY_DESCRIPTOR = object : KeyDescriptor { override fun save(output: DataOutput, value: String) = IOUtil.writeUTF(output, value) override fun read(input: DataInput) = IOUtil.readUTF(input) @@ -63,7 +63,7 @@ object KotlinModuleMappingIndex : FileBasedIndexExtension( override fun dependsOnFileContent() = true - override fun getKeyDescriptor() = KEY_DESCRIPTOR + override fun getKeyDescriptor() = STRING_KEY_DESCRIPTOR override fun getValueExternalizer() = VALUE_EXTERNALIZER diff --git a/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/SdkAndMockLibraryProjectDescriptor.java b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/SdkAndMockLibraryProjectDescriptor.java index 26c691ff03a..5c7c1b6d393 100644 --- a/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/SdkAndMockLibraryProjectDescriptor.java +++ b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/SdkAndMockLibraryProjectDescriptor.java @@ -71,7 +71,7 @@ public class SdkAndMockLibraryProjectDescriptor extends KotlinLightProjectDescri List extraOptions = allowKotlinPackage ? Collections.singletonList("-Xallow-kotlin-package") : emptyList(); File libraryJar = isJsLibrary - ? MockLibraryUtil.compileJsLibraryToJar(sourcesPath, LIBRARY_NAME, withSources) + ? MockLibraryUtil.compileJsLibraryToJar(sourcesPath, LIBRARY_NAME, withSources, Collections.emptyList()) : MockLibraryUtil.compileJvmLibraryToJar(sourcesPath, LIBRARY_NAME, withSources, true, extraOptions, classpath); String jarUrl = getJarUrl(libraryJar); diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 14f2ea8a811..816d5bee71e 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -247,6 +247,9 @@ + + @@ -687,6 +690,7 @@ + diff --git a/idea/testData/multiModuleHighlighting/jsExperimentalLibrary/lib/lib.kt b/idea/testData/multiModuleHighlighting/jsExperimentalLibrary/lib/lib.kt new file mode 100644 index 00000000000..920a5a68bf7 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/jsExperimentalLibrary/lib/lib.kt @@ -0,0 +1,8 @@ +package lib + +@Experimental +annotation class ExperimentalAPI + +class Foo + +fun bar() {} diff --git a/idea/testData/multiModuleHighlighting/jsExperimentalLibrary/usage/usage.kt b/idea/testData/multiModuleHighlighting/jsExperimentalLibrary/usage/usage.kt new file mode 100644 index 00000000000..49c4e43e1a5 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/jsExperimentalLibrary/usage/usage.kt @@ -0,0 +1,14 @@ +package usage + +import lib.* + +fun fail(foo: Foo): Foo { + bar() + return foo +} + +@ExperimentalAPI +fun ok(foo: Foo): Foo { + bar() + return foo +} diff --git a/idea/testData/multiModuleHighlighting/jvmExperimentalLibrary/lib/lib.kt b/idea/testData/multiModuleHighlighting/jvmExperimentalLibrary/lib/lib.kt new file mode 100644 index 00000000000..920a5a68bf7 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/jvmExperimentalLibrary/lib/lib.kt @@ -0,0 +1,8 @@ +package lib + +@Experimental +annotation class ExperimentalAPI + +class Foo + +fun bar() {} diff --git a/idea/testData/multiModuleHighlighting/jvmExperimentalLibrary/usage/usage.kt b/idea/testData/multiModuleHighlighting/jvmExperimentalLibrary/usage/usage.kt new file mode 100644 index 00000000000..49c4e43e1a5 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/jvmExperimentalLibrary/usage/usage.kt @@ -0,0 +1,14 @@ +package usage + +import lib.* + +fun fail(foo: Foo): Foo { + bar() + return foo +} + +@ExperimentalAPI +fun ok(foo: Foo): Foo { + bar() + return foo +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt index 83f84f17ddf..96750f24b4d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt @@ -31,9 +31,11 @@ import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ResolverForModuleComputationTracker import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.config.LanguageVersion +import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder import org.jetbrains.kotlin.idea.completion.test.withServiceRegistered import org.jetbrains.kotlin.idea.facet.KotlinFacetConfiguration import org.jetbrains.kotlin.idea.facet.KotlinFacetType +import org.jetbrains.kotlin.idea.framework.JSLibraryKind import org.jetbrains.kotlin.idea.project.KotlinCodeBlockModificationListener import org.jetbrains.kotlin.idea.project.KotlinModuleModificationTracker import org.jetbrains.kotlin.idea.test.PluginTestCaseBase @@ -42,6 +44,7 @@ import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.projectStructure.sdk import org.jetbrains.kotlin.samWithReceiver.SamWithReceiverCommandLineProcessor.Companion.ANNOTATION_OPTION import org.jetbrains.kotlin.samWithReceiver.SamWithReceiverCommandLineProcessor.Companion.PLUGIN_ID +import org.jetbrains.kotlin.test.MockLibraryUtil import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() { @@ -209,6 +212,47 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() { checkHighlightingInAllFiles() } + fun testJvmExperimentalLibrary() { + val lib = MockLibraryUtil.compileJvmLibraryToJar( + testDataPath + "${getTestName(true)}/lib", "lib", + extraOptions = listOf( + "-Xskip-runtime-version-check", + "-language-version", + "1.3", + "-Xexperimental=lib.ExperimentalAPI" + ) + ) + withSkipMetadataVersionCheck { + module("usage").addLibrary(lib) + checkHighlightingInAllFiles() + } + } + + fun testJsExperimentalLibrary() { + val lib = MockLibraryUtil.compileJsLibraryToJar( + testDataPath + "${getTestName(true)}/lib", "lib", false, + extraOptions = listOf( + "-Xskip-runtime-version-check", + "-language-version", + "1.3", + "-Xexperimental=lib.ExperimentalAPI" + ) + ) + withSkipMetadataVersionCheck { + module("usage").addLibrary(lib, kind = JSLibraryKind) + checkHighlightingInAllFiles() + } + } + + private fun withSkipMetadataVersionCheck(block: () -> Unit) { + val holder = KotlinCommonCompilerArgumentsHolder.getInstance(project) + try { + holder.update { skipMetadataVersionCheck = true } + block() + } finally { + holder.update { skipMetadataVersionCheck = false } + } + } private fun Module.setupKotlinFacet(configure: KotlinFacetConfiguration.() -> Unit) = apply { runWriteAction {