Load module annotations in IDE

#KT-22759 Fixed
This commit is contained in:
Alexander Udalov
2018-01-25 17:53:57 +01:00
parent 890374a42a
commit 21e2a3c0b4
13 changed files with 193 additions and 10 deletions
@@ -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<String> = 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<String>) {
runJsCompiler(listOf("-meta-info", "-output", outputFile.absolutePath, sourcesPath, *extraOptions.toTypedArray()))
}
fun compileKotlinModule(buildFilePath: String) {
@@ -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"
*/
@@ -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<PackageParts> =
FileBasedIndex.getInstance().getValues(KotlinModuleMappingIndex.KEY, packageFqName, scope)
override fun getAnnotationsOnBinaryModule(moduleName: String): List<ClassId> {
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<ClassId> =
FileBasedIndex.getInstance().getValues(KotlinJvmModuleAnnotationsIndex.KEY, moduleName, scope).flatten()
}
@@ -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<ClassId> {
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)
}
}
@@ -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<String, List<ClassId>>() {
val KEY: ID<String, List<ClassId>> = ID.create(KotlinJvmModuleAnnotationsIndex::class.java.canonicalName)
private val KEY_DESCRIPTOR = KotlinModuleMappingIndex.STRING_KEY_DESCRIPTOR
private val VALUE_EXTERNALIZER = object : DataExternalizer<List<ClassId>> {
override fun read(input: DataInput): List<ClassId>? =
IOUtil.readStringList(input).map(ClassId::fromString)
override fun save(out: DataOutput, value: List<ClassId>) =
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<String, List<ClassId>, 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()
}
}
}
@@ -30,7 +30,7 @@ object KotlinModuleMappingIndex : FileBasedIndexExtension<String, PackageParts>(
val KEY: ID<String, PackageParts> = ID.create(KotlinModuleMappingIndex::class.java.canonicalName)
private val KEY_DESCRIPTOR = object : KeyDescriptor<String> {
internal val STRING_KEY_DESCRIPTOR = object : KeyDescriptor<String> {
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<String, PackageParts>(
override fun dependsOnFileContent() = true
override fun getKeyDescriptor() = KEY_DESCRIPTOR
override fun getKeyDescriptor() = STRING_KEY_DESCRIPTOR
override fun getValueExternalizer() = VALUE_EXTERNALIZER
@@ -71,7 +71,7 @@ public class SdkAndMockLibraryProjectDescriptor extends KotlinLightProjectDescri
List<String> 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);
+4
View File
@@ -247,6 +247,9 @@
<projectService serviceInterface="org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver"
serviceImplementation="org.jetbrains.kotlin.idea.modules.IdeJavaModuleResolver"/>
<projectService serviceInterface="org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver"
serviceImplementation="org.jetbrains.kotlin.idea.compiler.IdeModuleAnnotationsResolver"/>
<projectService serviceInterface="org.jetbrains.kotlin.asJava.LightClassGenerationSupport"
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.IDELightClassGenerationSupport"/>
@@ -687,6 +690,7 @@
<fileBasedIndex implementation="org.jetbrains.kotlin.idea.vfilefinder.KotlinMetadataFilePackageIndex"/>
<fileBasedIndex implementation="org.jetbrains.kotlin.idea.vfilefinder.KotlinModuleMappingIndex"/>
<fileBasedIndex implementation="org.jetbrains.kotlin.idea.vfilefinder.KotlinPackageSourcesMemberNamesIndex"/>
<fileBasedIndex implementation="org.jetbrains.kotlin.idea.vfilefinder.KotlinJvmModuleAnnotationsIndex"/>
<idIndexer filetype="Kotlin" implementationClass="org.jetbrains.kotlin.idea.search.KotlinIdIndexer"/>
<todoIndexer filetype="Kotlin" implementationClass="org.jetbrains.kotlin.idea.search.KotlinTodoIndexer"/>
@@ -0,0 +1,8 @@
package lib
@Experimental
annotation class ExperimentalAPI
class Foo
fun bar() {}
@@ -0,0 +1,14 @@
package usage
import lib.*
fun fail(foo: <error>Foo</error>): <error>Foo</error> {
<error>bar</error>()
return foo
}
@ExperimentalAPI
fun ok(foo: Foo): Foo {
bar()
return foo
}
@@ -0,0 +1,8 @@
package lib
@Experimental
annotation class ExperimentalAPI
class Foo
fun bar() {}
@@ -0,0 +1,14 @@
package usage
import lib.*
fun fail(foo: <error>Foo</error>): <error>Foo</error> {
<error>bar</error>()
return foo
}
@ExperimentalAPI
fun ok(foo: Foo): Foo {
bar()
return foo
}
@@ -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 {