Introduce "Redundant 'inner' modifier" inspection
#KT-3262 Fixed
This commit is contained in:
committed by
Vladimir Dolzhenko
parent
d76dc6f57e
commit
5f1cc3b152
@@ -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>
|
||||
+81
@@ -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()
|
||||
}
|
||||
+7
@@ -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)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class A {
|
||||
fun a() {}
|
||||
|
||||
open class B(i: Int)
|
||||
|
||||
class C(i: Int) : B(i)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// PROBLEM: none
|
||||
class A {
|
||||
fun a() {}
|
||||
|
||||
open inner class B
|
||||
|
||||
<caret>inner class C : B()
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// PROBLEM: none
|
||||
class A {
|
||||
fun a() {}
|
||||
|
||||
open inner class B(i: Int)
|
||||
|
||||
<caret>inner class C(i: Int) : B(i)
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// PROBLEM: none
|
||||
class A {
|
||||
fun a() {}
|
||||
|
||||
<caret>inner class B {
|
||||
fun b() {
|
||||
a()
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
// PROBLEM: none
|
||||
class A {
|
||||
val a = 1
|
||||
|
||||
<caret>inner class B {
|
||||
fun b() {
|
||||
a
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
// PROBLEM: none
|
||||
class A {
|
||||
fun a() {}
|
||||
|
||||
inner class B {
|
||||
|
||||
<caret>inner class C {
|
||||
fun c() {
|
||||
a()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
// PROBLEM: none
|
||||
class A {
|
||||
fun a() {}
|
||||
|
||||
<caret>inner class B {
|
||||
fun b() {
|
||||
this@A.a()
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
// PROBLEM: none
|
||||
class A {
|
||||
fun a() {}
|
||||
|
||||
<caret>inner class B {
|
||||
fun b() {
|
||||
this@A
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// PROBLEM: none
|
||||
class A<T> {
|
||||
<caret>inner class B {
|
||||
fun b(t: T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
public class Java {
|
||||
public String getText() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// PROBLEM: none
|
||||
class A : Java() {
|
||||
<caret>inner class B {
|
||||
fun b() {
|
||||
getText()
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
public class Java {
|
||||
public String getText() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
Vendored
+8
@@ -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
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
<caret>inner class B
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
class B
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
fun foo() {}
|
||||
|
||||
class A {
|
||||
<caret>inner class B {
|
||||
fun b() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
fun foo() {}
|
||||
|
||||
class A {
|
||||
class B {
|
||||
fun b() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
class A {
|
||||
<caret>inner class B {
|
||||
|
||||
fun b() {}
|
||||
|
||||
inner class C {
|
||||
fun c() {
|
||||
b()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
class A {
|
||||
class B {
|
||||
|
||||
fun b() {}
|
||||
|
||||
inner class C {
|
||||
fun c() {
|
||||
b()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
class A {
|
||||
<caret>inner class B<T> {
|
||||
fun b(t: T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
class A {
|
||||
class B<T> {
|
||||
fun b(t: T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+108
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user