Introduce "Redundant 'inner' modifier" inspection

#KT-3262 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-09-02 14:45:42 +02:00
committed by Vladimir Dolzhenko
parent d76dc6f57e
commit 5f1cc3b152
34 changed files with 438 additions and 0 deletions
@@ -3568,6 +3568,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantInnerClassModifierInspection"
displayName="Redundant 'inner' modifier"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
level="WARNING"
language="kotlin"
/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightWorkspaceSettings"
serviceImplementation="org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightWorkspaceSettings"/>
@@ -3569,6 +3569,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantInnerClassModifierInspection"
displayName="Redundant 'inner' modifier"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
level="WARNING"
language="kotlin"
/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightWorkspaceSettings"
serviceImplementation="org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightWorkspaceSettings"/>
@@ -3569,6 +3569,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantInnerClassModifierInspection"
displayName="Redundant 'inner' modifier"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
level="WARNING"
language="kotlin"
/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightWorkspaceSettings"
serviceImplementation="org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightWorkspaceSettings"/>
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports a <b>inner</b> class modifier as redundant if no members of outer class are referenced inside.
</body>
</html>
@@ -0,0 +1,81 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.inspections
import com.intellij.codeInspection.IntentionWrapper
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiClass
import com.intellij.psi.util.parentsOfType
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor
import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
class RedundantInnerClassModifierInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = classVisitor(fun(targetClass) {
val innerModifier = targetClass.modifierList?.getModifier(KtTokens.INNER_KEYWORD) ?: return
val outerClasses = targetClass.parentsOfType<KtClass>().dropWhile { it == targetClass }.toSet()
if (outerClasses.isEmpty() || outerClasses.any { it.isLocal || it.isInner() }) return
if (targetClass.hasOuterClassMemberReference(outerClasses)) return
holder.registerProblem(
innerModifier,
"Redundant 'inner' modifier",
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
IntentionWrapper(
RemoveModifierFix(targetClass, KtTokens.INNER_KEYWORD, isRedundant = true),
targetClass.containingFile
)
)
})
private fun KtClass.hasOuterClassMemberReference(outerClasses: Set<KtClass>): Boolean {
val targetClass = this
val outerClassDescriptors by lazy {
val context = targetClass.analyze(BodyResolveMode.PARTIAL)
outerClasses.mapNotNull { context[BindingContext.DECLARATION_TO_DESCRIPTOR, it] as? ClassDescriptor }
}
val hasSuperType = outerClasses.any { it.getSuperTypeList() != null }
return anyDescendantOfType<KtExpression> { expression ->
when (expression) {
is KtNameReferenceExpression -> {
val reference = expression.mainReference.resolve()
val referenceClass = reference?.getStrictParentOfType<KtClass>()
if (expression.getStrictParentOfType<KtSuperTypeCallEntry>() != null) {
return@anyDescendantOfType reference is KtClass && reference.isInner()
|| reference is KtPrimaryConstructor && referenceClass?.isInner() == true
}
if (referenceClass != null) {
if (referenceClass == targetClass) return@anyDescendantOfType false
if (referenceClass in outerClasses) return@anyDescendantOfType true
}
if (!hasSuperType) return@anyDescendantOfType false
val referenceClassDescriptor = referenceClass?.descriptor as? ClassDescriptor
?: reference?.getStrictParentOfType<PsiClass>()?.getJavaClassDescriptor()
?: (expression.resolveToCall()?.resultingDescriptor as? SyntheticJavaPropertyDescriptor)
?.getMethod?.containingDeclaration as? ClassDescriptor
?: return@anyDescendantOfType false
outerClassDescriptors.any { outer -> outer.isSubclassOf(referenceClassDescriptor) }
}
is KtThisExpression -> {
expression.resolveToCall()?.resultingDescriptor?.returnType?.constructor?.declarationDescriptor in outerClassDescriptors
}
else -> false
}
}
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.RedundantInnerClassModifierInspection
@@ -0,0 +1,7 @@
class A {
fun a() {}
open class B
<caret>inner class C : B()
}
@@ -0,0 +1,7 @@
class A {
fun a() {}
open class B
class C : B()
}
@@ -0,0 +1,7 @@
class A {
fun a() {}
open class B(i: Int)
<caret>inner class C(i: Int) : B(i)
}
@@ -0,0 +1,7 @@
class A {
fun a() {}
open class B(i: Int)
class C(i: Int) : B(i)
}
@@ -0,0 +1,8 @@
// PROBLEM: none
class A {
fun a() {}
open inner class B
<caret>inner class C : B()
}
@@ -0,0 +1,8 @@
// PROBLEM: none
class A {
fun a() {}
open inner class B(i: Int)
<caret>inner class C(i: Int) : B(i)
}
@@ -0,0 +1,10 @@
// PROBLEM: none
class A {
fun a() {}
<caret>inner class B {
fun b() {
a()
}
}
}
@@ -0,0 +1,10 @@
// PROBLEM: none
class A {
val a = 1
<caret>inner class B {
fun b() {
a
}
}
}
@@ -0,0 +1,13 @@
// PROBLEM: none
class A {
fun a() {}
inner class B {
<caret>inner class C {
fun c() {
a()
}
}
}
}
@@ -0,0 +1,10 @@
// PROBLEM: none
class A {
fun a() {}
<caret>inner class B {
fun b() {
this@A.a()
}
}
}
@@ -0,0 +1,10 @@
// PROBLEM: none
class A {
fun a() {}
<caret>inner class B {
fun b() {
this@A
}
}
}
@@ -0,0 +1,7 @@
// PROBLEM: none
class A<T> {
<caret>inner class B {
fun b(t: T) {
}
}
}
@@ -0,0 +1,14 @@
// PROBLEM: none
open class A {
fun a() {}
}
open class B : A()
class C : B() {
<caret>inner class D {
fun d() {
a()
}
}
}
@@ -0,0 +1,5 @@
public class Java {
public String getText() {
return "";
}
}
@@ -0,0 +1,8 @@
// PROBLEM: none
class A : Java() {
<caret>inner class B {
fun b() {
getText()
}
}
}
@@ -0,0 +1,5 @@
public class Java {
public String getText() {
return "";
}
}
@@ -0,0 +1,8 @@
// PROBLEM: none
class A : Java() {
<caret>inner class B {
fun b() {
text
}
}
}
@@ -0,0 +1,6 @@
// PROBLEM: none
class A {
inner class B {
<caret>inner class C
}
}
@@ -0,0 +1,6 @@
// PROBLEM: none
fun test() {
class A {
<caret>inner class B
}
}
@@ -0,0 +1,3 @@
class A {
<caret>inner class B
}
@@ -0,0 +1,3 @@
class A {
class B
}
@@ -0,0 +1,9 @@
fun foo() {}
class A {
<caret>inner class B {
fun b() {
foo()
}
}
}
@@ -0,0 +1,9 @@
fun foo() {}
class A {
class B {
fun b() {
foo()
}
}
}
@@ -0,0 +1,12 @@
class A {
<caret>inner class B {
fun b() {}
inner class C {
fun c() {
b()
}
}
}
}
@@ -0,0 +1,12 @@
class A {
class B {
fun b() {}
inner class C {
fun c() {
b()
}
}
}
}
@@ -0,0 +1,6 @@
class A {
<caret>inner class B<T> {
fun b(t: T) {
}
}
}
@@ -0,0 +1,6 @@
class A {
class B<T> {
fun b(t: T) {
}
}
}
@@ -7376,6 +7376,114 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
}
}
@TestMetadata("idea/testData/inspectionsLocal/redundantInnerClassModifier")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class RedundantInnerClassModifier extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInRedundantInnerClassModifier() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/inspectionsLocal/redundantInnerClassModifier"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true);
}
@TestMetadata("extendClass.kt")
public void testExtendClass() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/extendClass.kt");
}
@TestMetadata("extendClass2.kt")
public void testExtendClass2() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/extendClass2.kt");
}
@TestMetadata("extendInnerClass.kt")
public void testExtendInnerClass() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/extendInnerClass.kt");
}
@TestMetadata("extendInnerClass2.kt")
public void testExtendInnerClass2() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/extendInnerClass2.kt");
}
@TestMetadata("hasOuterClassMemberReference.kt")
public void testHasOuterClassMemberReference() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/hasOuterClassMemberReference.kt");
}
@TestMetadata("hasOuterClassMemberReference2.kt")
public void testHasOuterClassMemberReference2() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/hasOuterClassMemberReference2.kt");
}
@TestMetadata("hasOuterClassMemberReference3.kt")
public void testHasOuterClassMemberReference3() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/hasOuterClassMemberReference3.kt");
}
@TestMetadata("hasOuterClassMemberReference4.kt")
public void testHasOuterClassMemberReference4() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/hasOuterClassMemberReference4.kt");
}
@TestMetadata("hasOuterClassMemberReference5.kt")
public void testHasOuterClassMemberReference5() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/hasOuterClassMemberReference5.kt");
}
@TestMetadata("hasOuterClassTypeReference.kt")
public void testHasOuterClassTypeReference() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/hasOuterClassTypeReference.kt");
}
@TestMetadata("hasSuperClassMemberReference.kt")
public void testHasSuperClassMemberReference() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/hasSuperClassMemberReference.kt");
}
@TestMetadata("hasSuperJavaClassMemberReference.kt")
public void testHasSuperJavaClassMemberReference() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/hasSuperJavaClassMemberReference.kt");
}
@TestMetadata("hasSuperJavaClassMemberReference2.kt")
public void testHasSuperJavaClassMemberReference2() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/hasSuperJavaClassMemberReference2.kt");
}
@TestMetadata("inInnerClass.kt")
public void testInInnerClass() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/inInnerClass.kt");
}
@TestMetadata("inLocalClass.kt")
public void testInLocalClass() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/inLocalClass.kt");
}
@TestMetadata("noOuterClassMemberReference.kt")
public void testNoOuterClassMemberReference() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/noOuterClassMemberReference.kt");
}
@TestMetadata("noOuterClassMemberReference2.kt")
public void testNoOuterClassMemberReference2() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/noOuterClassMemberReference2.kt");
}
@TestMetadata("noOuterClassMemberReference3.kt")
public void testNoOuterClassMemberReference3() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/noOuterClassMemberReference3.kt");
}
@TestMetadata("noOuterClassTypeReference.kt")
public void testNoOuterClassTypeReference() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantInnerClassModifier/noOuterClassTypeReference.kt");
}
}
@TestMetadata("idea/testData/inspectionsLocal/redundantLabelMigration")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)