From 3e8016ed25cf5e7259dcc13e0468d2bf72acfcc9 Mon Sep 17 00:00:00 2001 From: Andrei Klunnyi Date: Mon, 14 Dec 2020 18:32:23 +0100 Subject: [PATCH] KTIJ-717 [Java side inspection]: "implementation of Kotlin sealed" --- .../KotlinSealedInheritorsInJava.html | 5 ++ .../messages/KotlinBundle.properties | 2 + idea/resources/META-INF/inspections.xml | 8 +++ .../KotlinSealedInheritorsInJavaInspection.kt | 57 +++++++++++++++ .../before/JavaTriesToExtendKotlinSealed.java | 18 +++++ .../before/KotlinSealedDeclarations.kt | 5 ++ .../kotlinSealedInJavaTest/expected.xml | 72 +++++++++++++++++++ .../kotlinSealedInJavaTest.test | 3 + .../MultiFileInspectionTestGenerated.java | 5 ++ 9 files changed, 175 insertions(+) create mode 100644 idea/resources-en/inspectionDescriptions/KotlinSealedInheritorsInJava.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/KotlinSealedInheritorsInJavaInspection.kt create mode 100644 idea/testData/multiFileInspections/kotlinSealedInJavaTest/before/JavaTriesToExtendKotlinSealed.java create mode 100644 idea/testData/multiFileInspections/kotlinSealedInJavaTest/before/KotlinSealedDeclarations.kt create mode 100644 idea/testData/multiFileInspections/kotlinSealedInJavaTest/expected.xml create mode 100644 idea/testData/multiFileInspections/kotlinSealedInJavaTest/kotlinSealedInJavaTest.test diff --git a/idea/resources-en/inspectionDescriptions/KotlinSealedInheritorsInJava.html b/idea/resources-en/inspectionDescriptions/KotlinSealedInheritorsInJava.html new file mode 100644 index 00000000000..a81e027b794 --- /dev/null +++ b/idea/resources-en/inspectionDescriptions/KotlinSealedInheritorsInJava.html @@ -0,0 +1,5 @@ + + +This inspection reports attempt to inherit Kotlin sealed interface/class in Java code. + + \ No newline at end of file diff --git a/idea/resources-en/messages/KotlinBundle.properties b/idea/resources-en/messages/KotlinBundle.properties index 8503a39be15..a5b57965757 100644 --- a/idea/resources-en/messages/KotlinBundle.properties +++ b/idea/resources-en/messages/KotlinBundle.properties @@ -1343,6 +1343,7 @@ double.negation.fix.text=Remove redundant negations redundant.double.negation=Redundant double negation equals.between.objects.of.inconvertible.types='equals()' between objects of inconvertible types usage.of.kotlin.internal.declaration.from.different.module=Usage of Kotlin internal declaration from different module +inheritance.of.kotlin.sealed=Java {0,choice,0#interface|1#class} cannot be a part of Kotlin sealed hierarchy junit.static.methods=JUnit static methods redundant.override.fix.text=Remove redundant overriding method redundant.overriding.method=Redundant overriding method @@ -2064,6 +2065,7 @@ inspection.replace.array.of.with.literal.display.name='arrayOf' call can be repl inspection.copy.without.named.arguments.display.name='copy' method of data class is called without named arguments inspection.move.suspicious.callable.reference.into.parentheses.display.name=Suspicious callable reference used as lambda result inspection.kotlin.internal.in.java.display.name=Usage of Kotlin internal declarations from Java +inspection.kotlin.sealed.in.java.display.name=Inheritance of Kotlin sealed interface/class from Java inspection.unused.lambda.expression.body.display.name=Unused return value of a function with lambda expression body inspection.destructuring.wrong.name.display.name=Variable in destructuring declaration uses name of a wrong data class property inspection.data.class.private.constructor.display.name=Private data class constructor is exposed via the 'copy' method diff --git a/idea/resources/META-INF/inspections.xml b/idea/resources/META-INF/inspections.xml index eb699fcedf2..4717ddc6426 100644 --- a/idea/resources/META-INF/inspections.xml +++ b/idea/resources/META-INF/inspections.xml @@ -1504,6 +1504,14 @@ language="JAVA" key="inspection.kotlin.internal.in.java.display.name" bundle="messages.KotlinBundle"/> + + { + if (this is PsiAnonymousClass && baseClassType.isKotlinSealed()) + return listOf(baseClassReference) + + val sealedBaseClasses = extendsList?.listSealedMembers() + val sealedBaseInterfaces = implementsList?.listSealedMembers() + + return sealedBaseClasses.orEmpty() + sealedBaseInterfaces.orEmpty() + } + + private fun PsiReferenceList.listSealedMembers(): List? = referencedTypes + ?.filter { it.isKotlinSealed() } + ?.mapNotNull { it as? PsiClassReferenceType } + ?.map { it.reference } + + private fun PsiClassType.isKotlinSealed(): Boolean = resolve()?.isKotlinSealed() == true + + private fun PsiClass.isKotlinSealed(): Boolean = this is KtUltraLightClass && getDescriptor()?.isSealed() == true + + private val PsiClass.abstractionTypeName: String + get() = if (isInterface) "interface" else "class" + } + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return object : JavaElementVisitor() { + override fun visitClass(aClass: PsiClass?) { + aClass?.listSealedParentReferences()?.forEach { + holder.registerProblem( + it, KotlinBundle.message("inheritance.of.kotlin.sealed", 0.takeIf { aClass.isInterface } ?: 1), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING + ) + } + } + + override fun visitAnonymousClass(aClass: PsiAnonymousClass?) = visitClass(aClass) + } + } +} \ No newline at end of file diff --git a/idea/testData/multiFileInspections/kotlinSealedInJavaTest/before/JavaTriesToExtendKotlinSealed.java b/idea/testData/multiFileInspections/kotlinSealedInJavaTest/before/JavaTriesToExtendKotlinSealed.java new file mode 100644 index 00000000000..2ab7d257214 --- /dev/null +++ b/idea/testData/multiFileInspections/kotlinSealedInJavaTest/before/JavaTriesToExtendKotlinSealed.java @@ -0,0 +1,18 @@ +public class JavaTriesToExtendKotlinSealed { + + private static class TryToImplement implements KotlinSealedInterface {} + private static interface TryToExtend extends KotlinSealedInterface {} + private static class TryToExtendClass extends KotlinSealedClass {} + + class OkToImplement implements KotlinInterface {} + interface OkToExtend extends KotlinInterface {} + class OkToExtendClass extends KotlinClass{} + + public static void main(String[] args) { + KotlinSealedInterface sealedInterface = new KotlinSealedInterface() {}; // anonymouns class implements interface + KotlinSealedClass sealedClass = new KotlinSealedClass() {}; + + new KotlinInterface() {}; + new KotlinClass() {}; + } +} \ No newline at end of file diff --git a/idea/testData/multiFileInspections/kotlinSealedInJavaTest/before/KotlinSealedDeclarations.kt b/idea/testData/multiFileInspections/kotlinSealedInJavaTest/before/KotlinSealedDeclarations.kt new file mode 100644 index 00000000000..7e9097cee71 --- /dev/null +++ b/idea/testData/multiFileInspections/kotlinSealedInJavaTest/before/KotlinSealedDeclarations.kt @@ -0,0 +1,5 @@ +sealed interface KotlinSealedInterface +sealed class KotlinSealedClass + +interface KotlinInterface: KotlinSealedInterface +class KotlinClass: KotlinSealedClass() \ No newline at end of file diff --git a/idea/testData/multiFileInspections/kotlinSealedInJavaTest/expected.xml b/idea/testData/multiFileInspections/kotlinSealedInJavaTest/expected.xml new file mode 100644 index 00000000000..585da3710b0 --- /dev/null +++ b/idea/testData/multiFileInspections/kotlinSealedInJavaTest/expected.xml @@ -0,0 +1,72 @@ + + + JavaTriesToExtendKotlinSealed.java + 3 + testKotlinSealedInJavaTest_KotlinSealedInJavaTest + <default> + + Inheritance of Kotlin sealed + interface/class from Java + + Java class cannot be a part of Kotlin sealed hierarchy + KotlinSealedInterface + 4 + 71 + + + + JavaTriesToExtendKotlinSealed.java + 4 + testKotlinSealedInJavaTest_KotlinSealedInJavaTest + <default> + + Inheritance of Kotlin sealed + interface/class from Java + + Java interface cannot be a part of Kotlin sealed hierarchy + KotlinSealedInterface + 4 + 69 + + + + JavaTriesToExtendKotlinSealed.java + 5 + testKotlinSealedInJavaTest_KotlinSealedInJavaTest + <default> + + Inheritance of Kotlin sealed + interface/class from Java + + Java class cannot be a part of Kotlin sealed hierarchy + KotlinSealedClass + 4 + 66 + + + + JavaTriesToExtendKotlinSealed.java + 12 + testKotlinSealedInJavaTest_KotlinSealedInJavaTest + <default> + + Inheritance of Kotlin sealed interface/class from Java + Java class cannot be a part of Kotlin sealed hierarchy + KotlinSealedInterface + 52 + 26 + + + + JavaTriesToExtendKotlinSealed.java + 13 + testKotlinSealedInJavaTest_KotlinSealedInJavaTest + <default> + + Inheritance of Kotlin sealed interface/class from Java + Java class cannot be a part of Kotlin sealed hierarchy + KotlinSealedClass() + 44 + 22 + + \ No newline at end of file diff --git a/idea/testData/multiFileInspections/kotlinSealedInJavaTest/kotlinSealedInJavaTest.test b/idea/testData/multiFileInspections/kotlinSealedInJavaTest/kotlinSealedInJavaTest.test new file mode 100644 index 00000000000..ef7ba9896b7 --- /dev/null +++ b/idea/testData/multiFileInspections/kotlinSealedInJavaTest/kotlinSealedInJavaTest.test @@ -0,0 +1,3 @@ +{ + "inspectionClass": "org.jetbrains.kotlin.idea.inspections.KotlinSealedInheritorsInJavaInspection" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/MultiFileInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/MultiFileInspectionTestGenerated.java index 8c893b2d52e..edc26e85601 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/MultiFileInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/MultiFileInspectionTestGenerated.java @@ -49,6 +49,11 @@ public class MultiFileInspectionTestGenerated extends AbstractMultiFileInspectio runTest("idea/testData/multiFileInspections/kotlinInternalInJavaTest/kotlinInternalInJavaTest.test"); } + @TestMetadata("kotlinSealedInJavaTest/kotlinSealedInJavaTest.test") + public void testKotlinSealedInJavaTest_KotlinSealedInJavaTest() throws Exception { + runTest("idea/testData/multiFileInspections/kotlinSealedInJavaTest/kotlinSealedInJavaTest.test"); + } + @TestMetadata("mainInTwoModules/mainInTwoModules.test") public void testMainInTwoModules_MainInTwoModules() throws Exception { runTest("idea/testData/multiFileInspections/mainInTwoModules/mainInTwoModules.test");