Show warnings for Java methods with external annotations when they called from Kotlin code

This commit is contained in:
Ilya Kirillov
2019-02-22 15:36:19 +03:00
parent ba9b3397d0
commit f1f6740ec9
17 changed files with 193 additions and 8 deletions
@@ -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);
}
}
@@ -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)
@@ -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();
@@ -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
@@ -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
@@ -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(
@@ -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
}
@@ -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?
}
@@ -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>
+4
View File
@@ -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()
}
+5
View File
@@ -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>
}
+5
View File
@@ -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"
}
}