Show warnings for Java methods with external annotations when they called from Kotlin code
This commit is contained in:
+8
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.load.java.structure.impl;
|
||||
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager;
|
||||
import com.intellij.psi.PsiAnnotation;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
@@ -86,4 +87,11 @@ public class JavaAnnotationImpl extends JavaElementImpl<PsiAnnotation> implement
|
||||
PsiElement resolved = referenceElement.resolve();
|
||||
return resolved instanceof PsiClass ? (PsiClass) resolved : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdeExternalAnnotation() {
|
||||
PsiAnnotation psi = getPsi();
|
||||
ExternalAnnotationsManager externalAnnotationManager = ExternalAnnotationsManager.getInstance(psi.getProject());
|
||||
return externalAnnotationManager.isExternalAnnotation(psi);
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -20,6 +20,7 @@ package org.jetbrains.kotlin.load.java.structure.impl
|
||||
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.load.java.NULLABILITY_ANNOTATIONS
|
||||
import org.jetbrains.kotlin.load.java.structure.*
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
@@ -67,6 +68,14 @@ internal fun classifierTypes(classTypes: Array<PsiClassType>): Collection<JavaCl
|
||||
internal fun annotations(annotations: Array<out PsiAnnotation>): Collection<JavaAnnotation> =
|
||||
annotations.convert(::JavaAnnotationImpl)
|
||||
|
||||
internal fun nullabilityAnnotations(annotations: Array<out PsiAnnotation>): Collection<JavaAnnotation> =
|
||||
annotations.convert(::JavaAnnotationImpl)
|
||||
.filter { annotation ->
|
||||
val fqName = annotation.classId?.asSingleFqName() ?: return@filter false
|
||||
fqName in NULLABILITY_ANNOTATIONS
|
||||
}
|
||||
|
||||
|
||||
internal fun namedAnnotationArguments(nameValuePairs: Array<PsiNameValuePair>): Collection<JavaAnnotationArgument> =
|
||||
nameValuePairs.convert { psi ->
|
||||
val name = psi.name?.let(Name::identifier)
|
||||
|
||||
+24
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.load.java.structure.impl;
|
||||
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager;
|
||||
import com.intellij.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -25,10 +26,12 @@ import org.jetbrains.kotlin.load.java.JavaVisibilities;
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.jetbrains.kotlin.load.java.structure.impl.JavaElementCollectionFromPsiArrayUtil.annotations;
|
||||
import static org.jetbrains.kotlin.load.java.structure.impl.JavaElementCollectionFromPsiArrayUtil.nullabilityAnnotations;
|
||||
|
||||
/* package */ class JavaElementUtil {
|
||||
private JavaElementUtil() {
|
||||
@@ -70,6 +73,27 @@ import static org.jetbrains.kotlin.load.java.structure.impl.JavaElementCollectio
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiAnnotation[] getExternalAnnotations(@NotNull JavaModifierListOwnerImpl modifierListOwner) {
|
||||
PsiModifierListOwner psiModifierListOwner = modifierListOwner.getPsi();
|
||||
ExternalAnnotationsManager externalAnnotationManager = ExternalAnnotationsManager
|
||||
.getInstance(psiModifierListOwner.getProject());
|
||||
return externalAnnotationManager.findExternalAnnotations(psiModifierListOwner);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static <T extends JavaAnnotationOwnerImpl & JavaModifierListOwnerImpl>
|
||||
Collection<JavaAnnotation> getRegularAndExternalAnnotations(@NotNull T owner) {
|
||||
PsiAnnotation[] externalAnnotations = getExternalAnnotations(owner);
|
||||
if (externalAnnotations == null) {
|
||||
return getAnnotations(owner);
|
||||
}
|
||||
Collection<JavaAnnotation> annotations = new ArrayList<>(getAnnotations(owner));
|
||||
annotations.addAll(nullabilityAnnotations(externalAnnotations));
|
||||
return annotations;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public static JavaAnnotation findAnnotation(@NotNull JavaAnnotationOwnerImpl owner, @NotNull FqName fqName) {
|
||||
PsiAnnotationOwner annotationOwnerPsi = owner.getAnnotationOwnerPsi();
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ public abstract class JavaMemberImpl<Psi extends PsiMember> extends JavaElementI
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<JavaAnnotation> getAnnotations() {
|
||||
return JavaElementUtil.getAnnotations(this);
|
||||
return JavaElementUtil.getRegularAndExternalAnnotations(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ public class JavaValueParameterImpl extends JavaElementImpl<PsiParameter>
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<JavaAnnotation> getAnnotations() {
|
||||
return JavaElementUtil.getAnnotations(this);
|
||||
return JavaElementUtil.getRegularAndExternalAnnotations(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+4
-1
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.SourceElement
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
|
||||
import org.jetbrains.kotlin.load.java.descriptors.PossiblyExternalAnnotationDescriptor
|
||||
import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext
|
||||
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaAnnotationDescriptor
|
||||
import org.jetbrains.kotlin.load.java.structure.*
|
||||
@@ -103,7 +104,7 @@ open class JavaAnnotationDescriptor(
|
||||
c: LazyJavaResolverContext,
|
||||
annotation: JavaAnnotation?,
|
||||
override val fqName: FqName
|
||||
) : AnnotationDescriptor {
|
||||
) : AnnotationDescriptor, PossiblyExternalAnnotationDescriptor {
|
||||
override val source: SourceElement = annotation?.let { c.components.sourceElementFactory.source(it) } ?: SourceElement.NO_SOURCE
|
||||
|
||||
override val type: SimpleType by c.storageManager.createLazyValue { c.module.builtIns.getBuiltInClassByFqName(fqName).defaultType }
|
||||
@@ -111,6 +112,8 @@ open class JavaAnnotationDescriptor(
|
||||
protected val firstArgument: JavaAnnotationArgument? = annotation?.arguments?.firstOrNull()
|
||||
|
||||
override val allValueArguments: Map<Name, ConstantValue<*>> get() = emptyMap()
|
||||
|
||||
override val isIdeExternalAnnotation: Boolean = annotation?.isIdeExternalAnnotation == true
|
||||
}
|
||||
|
||||
class JavaDeprecatedAnnotationDescriptor(
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.load.java.descriptors
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
|
||||
interface PossiblyExternalAnnotationDescriptor : AnnotationDescriptor {
|
||||
val isIdeExternalAnnotation: Boolean
|
||||
}
|
||||
+4
-1
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.descriptors.findNonGenericClassAcrossDependencies
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.DEFAULT_ANNOTATION_MEMBER_NAME
|
||||
import org.jetbrains.kotlin.load.java.components.DescriptorResolverUtils
|
||||
import org.jetbrains.kotlin.load.java.components.TypeUsage
|
||||
import org.jetbrains.kotlin.load.java.descriptors.PossiblyExternalAnnotationDescriptor
|
||||
import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext
|
||||
import org.jetbrains.kotlin.load.java.lazy.types.toAttributes
|
||||
import org.jetbrains.kotlin.load.java.structure.*
|
||||
@@ -39,7 +40,7 @@ import org.jetbrains.kotlin.types.isError
|
||||
class LazyJavaAnnotationDescriptor(
|
||||
private val c: LazyJavaResolverContext,
|
||||
private val javaAnnotation: JavaAnnotation
|
||||
) : AnnotationDescriptor {
|
||||
) : AnnotationDescriptor, PossiblyExternalAnnotationDescriptor {
|
||||
override val fqName by c.storageManager.createNullableLazyValue {
|
||||
javaAnnotation.classId?.asSingleFqName()
|
||||
}
|
||||
@@ -112,4 +113,6 @@ class LazyJavaAnnotationDescriptor(
|
||||
ClassId.topLevel(fqName),
|
||||
c.components.deserializedDescriptorResolver.components.notFoundClasses
|
||||
)
|
||||
|
||||
override val isIdeExternalAnnotation: Boolean = javaAnnotation.isIdeExternalAnnotation
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ interface JavaTypeParameterListOwner : JavaElement {
|
||||
interface JavaAnnotation : JavaElement {
|
||||
val arguments: Collection<JavaAnnotationArgument>
|
||||
val classId: ClassId?
|
||||
val isIdeExternalAnnotation: Boolean
|
||||
get() = false
|
||||
|
||||
fun resolve(): JavaClass?
|
||||
}
|
||||
|
||||
+8
-4
@@ -18,10 +18,7 @@ package org.jetbrains.kotlin.load.java.typeEnhancement
|
||||
|
||||
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.annotations.composeAnnotations
|
||||
import org.jetbrains.kotlin.descriptors.annotations.*
|
||||
import org.jetbrains.kotlin.load.java.*
|
||||
import org.jetbrains.kotlin.load.java.descriptors.*
|
||||
import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext
|
||||
@@ -34,6 +31,7 @@ import org.jetbrains.kotlin.resolve.constants.EnumValue
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DEPRECATED_FUNCTION_KEY
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.firstArgument
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isSourceAnnotation
|
||||
import org.jetbrains.kotlin.types.*
|
||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
|
||||
@@ -103,6 +101,12 @@ class SignatureEnhancement(
|
||||
isForWarningOnly = true
|
||||
)
|
||||
else -> null
|
||||
}?.let { migrationStatus ->
|
||||
if (!migrationStatus.isForWarningOnly
|
||||
&& annotationDescriptor is PossiblyExternalAnnotationDescriptor
|
||||
&& annotationDescriptor.isIdeExternalAnnotation)
|
||||
migrationStatus.copy(isForWarningOnly = true)
|
||||
else migrationStatus
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
public class ClassWithExternalAnnotatedMembers {
|
||||
String nullableMethod() {
|
||||
return null;
|
||||
}
|
||||
|
||||
String notNullMethod() {
|
||||
return "nya";
|
||||
}
|
||||
|
||||
void methodWithNotNullParameter(Integer x) {
|
||||
}
|
||||
|
||||
String nullableField = "nya";
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<root>
|
||||
<item name='ClassWithExternalAnnotatedMembers java.lang.String notNullMethod()'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='ClassWithExternalAnnotatedMembers java.lang.String nullableMethod()'>
|
||||
<annotation name='org.jetbrains.annotations.Nullable'/>
|
||||
</item>
|
||||
<item name='ClassWithExternalAnnotatedMembers nullableField'>
|
||||
<annotation name='org.jetbrains.annotations.Nullable'/>
|
||||
</item>
|
||||
<item name='ClassWithExternalAnnotatedMembers void methodWithNotNullParameter(java.lang.Integer) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
</root>
|
||||
@@ -0,0 +1,4 @@
|
||||
fun test() {
|
||||
val x = ClassWithExternalAnnotatedMembers()
|
||||
x.notNullMethod()<warning descr="[UNNECESSARY_SAFE_CALL] Unnecessary safe call on a non-null receiver of type String!">?.</warning>toLowerCase()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
fun test() {
|
||||
val x = ClassWithExternalAnnotatedMembers()
|
||||
val y: String = <warning descr="[NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS] Type mismatch: inferred type is String? but String was expected">x.nullableField</warning>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
fun test() {
|
||||
val x = ClassWithExternalAnnotatedMembers()
|
||||
val y: String = <warning descr="[NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS] Type mismatch: inferred type is String? but String was expected">x.nullableMethod()</warning>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun test() {
|
||||
val x = ClassWithExternalAnnotatedMembers()
|
||||
x.methodWithNotNullParameter(<warning descr="[NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS] Type mismatch: inferred type is Nothing? but Int was expected">null</warning>)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.jetbrains.kotlin.idea.externalAnnotations
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.roots.JavaModuleExternalPaths
|
||||
import com.intellij.openapi.roots.ModifiableRootModel
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleSettings
|
||||
import com.intellij.testFramework.LightPlatformTestCase
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.test.TargetBackend
|
||||
import java.io.File
|
||||
|
||||
class ExternalAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
|
||||
fun testNotNullMethod() {
|
||||
KotlinTestUtils.runTest(::doTest, TargetBackend.ANY, "idea/testData/externalAnnotations/notNullMethod.kt")
|
||||
}
|
||||
|
||||
fun testNullableMethod() {
|
||||
KotlinTestUtils.runTest(::doTest, TargetBackend.ANY, "idea/testData/externalAnnotations/nullableMethod.kt")
|
||||
}
|
||||
|
||||
|
||||
fun testNullableField() {
|
||||
KotlinTestUtils.runTest(::doTest, TargetBackend.ANY, "idea/testData/externalAnnotations/nullableField.kt")
|
||||
}
|
||||
|
||||
fun testNullableMethodParameter() {
|
||||
KotlinTestUtils.runTest(::doTest, TargetBackend.ANY, "idea/testData/externalAnnotations/nullableMethodParameter.kt")
|
||||
}
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
JavaCodeStyleSettings.getInstance(project).USE_EXTERNAL_ANNOTATIONS = true
|
||||
addFile(classWithExternalAnnotatedMembers)
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
JavaCodeStyleSettings.getInstance(project).USE_EXTERNAL_ANNOTATIONS = false
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
private fun addFile(path: String) {
|
||||
val file = File(path)
|
||||
val root = LightPlatformTestCase.getSourceRoot()
|
||||
runWriteAction {
|
||||
val virtualFile = root.createChildData(null, file.name)
|
||||
virtualFile.getOutputStream(null).writer().use { it.write(FileUtil.loadFile(file)) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun doTest(kotlinFilePath: String) {
|
||||
myFixture.configureByFiles(kotlinFilePath, externalAnnotationsFile, classWithExternalAnnotatedMembers)
|
||||
myFixture.checkHighlighting()
|
||||
}
|
||||
|
||||
override fun getProjectDescriptor() = object : KotlinWithJdkAndRuntimeLightProjectDescriptor() {
|
||||
override fun configureModule(module: Module, model: ModifiableRootModel) {
|
||||
super.configureModule(module, model)
|
||||
model.getModuleExtension(JavaModuleExternalPaths::class.java)
|
||||
.setExternalAnnotationUrls(arrayOf(VfsUtilCore.pathToUrl(externalAnnotationsPath)))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val externalAnnotationsPath = "idea/testData/externalAnnotations/annotations/"
|
||||
private const val classWithExternalAnnotatedMembers = "idea/testData/externalAnnotations/ClassWithExternalAnnotatedMembers.java"
|
||||
private const val externalAnnotationsFile = "$externalAnnotationsPath/annotations.xml"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user