diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantInnerClassModifierInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantInnerClassModifierInspection.kt index e7be1b41d26d..4a2e0e57eae4 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantInnerClassModifierInspection.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantInnerClassModifierInspection.kt @@ -2,11 +2,11 @@ package org.jetbrains.kotlin.idea.inspections -import com.intellij.codeInspection.IntentionWrapper -import com.intellij.codeInspection.ProblemHighlightType -import com.intellij.codeInspection.ProblemsHolder +import com.intellij.codeInspection.* +import com.intellij.openapi.project.Project import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement +import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.psi.util.parentsOfType import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor @@ -15,8 +15,8 @@ import org.jetbrains.kotlin.idea.KotlinBundle 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.core.ShortenReferences import org.jetbrains.kotlin.idea.intentions.receiverType -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.idea.util.getThisReceiverOwner @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor @@ -41,10 +42,7 @@ class RedundantInnerClassModifierInspection : AbstractKotlinInspection() { innerModifier, KotlinBundle.message("inspection.redundant.inner.class.modifier.descriptor"), ProblemHighlightType.LIKE_UNUSED_SYMBOL, - IntentionWrapper( - RemoveModifierFix(targetClass, KtTokens.INNER_KEYWORD, isRedundant = true), - targetClass.containingFile - ) + RemoveInnerModifierFix() ) }) @@ -115,4 +113,42 @@ class RedundantInnerClassModifierInspection : AbstractKotlinInspection() { private fun PsiElement.receiverTypeReference(): KtTypeReference? { return safeAs()?.receiverTypeReference ?: safeAs()?.receiverTypeReference } + + private class RemoveInnerModifierFix : LocalQuickFix { + override fun getName() = KotlinBundle.message("remove.redundant.0.modifier", KtTokens.INNER_KEYWORD.value) + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val targetClass = descriptor.psiElement.getStrictParentOfType() ?: return + val fixedElements = fixReceiverOnCallSite(targetClass) + targetClass.removeModifier(KtTokens.INNER_KEYWORD) + fixedElements.filterIsInstance().forEach { ShortenReferences.DEFAULT.process(it) } + } + + private fun fixReceiverOnCallSite(targetClass: KtClass): List { + val containingClass = targetClass.getStrictParentOfType() ?: return emptyList() + val bindingContext = containingClass.analyze(BodyResolveMode.PARTIAL) + val fqName = + bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, containingClass]?.fqNameOrNull()?.asString() ?: return emptyList() + val psiFactory = KtPsiFactory(targetClass) + val newReceiver = psiFactory.createExpression(fqName) + return ReferencesSearch.search(targetClass, targetClass.useScope).mapNotNull { + val callExpression = it.element.parent as? KtCallExpression ?: return@mapNotNull null + val qualifiedExpression = callExpression.getQualifiedExpressionForSelector() + val parentClass = callExpression.getStrictParentOfType() + when { + // Explicit receiver + qualifiedExpression != null -> + if (parentClass == containingClass) { + qualifiedExpression.replace(callExpression) + } else { + qualifiedExpression.receiverExpression.replace(newReceiver) + } + // Implicit receiver + else -> callExpression.replace(psiFactory.createExpressionByPattern("$0.$1", newReceiver, callExpression)) + } + } + } + } } diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 85cf9d8bf430..50c2859ebf38 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -8184,6 +8184,36 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/redundantInnerClassModifier/inLocalClass.kt"); } + @TestMetadata("innerClassConstructorCall.kt") + public void testInnerClassConstructorCall() throws Exception { + runTest("testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall.kt"); + } + + @TestMetadata("innerClassConstructorCall2.kt") + public void testInnerClassConstructorCall2() throws Exception { + runTest("testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall2.kt"); + } + + @TestMetadata("innerClassConstructorCall3.kt") + public void testInnerClassConstructorCall3() throws Exception { + runTest("testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall3.kt"); + } + + @TestMetadata("innerClassConstructorCall4.kt") + public void testInnerClassConstructorCall4() throws Exception { + runTest("testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall4.kt"); + } + + @TestMetadata("innerClassConstructorCall5.kt") + public void testInnerClassConstructorCall5() throws Exception { + runTest("testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall5.kt"); + } + + @TestMetadata("innerClassConstructorCall6.kt") + public void testInnerClassConstructorCall6() throws Exception { + runTest("testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall6.kt"); + } + @TestMetadata("noOuterClassMemberReference.kt") public void testNoOuterClassMemberReference() throws Exception { runTest("testData/inspectionsLocal/redundantInnerClassModifier/noOuterClassMemberReference.kt"); diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall.kt new file mode 100644 index 000000000000..45a697006af6 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall.kt @@ -0,0 +1,9 @@ +package test + +class Test { + inner class Inner + + fun foo() { + Inner() + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall.kt.after new file mode 100644 index 000000000000..b7032d226526 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall.kt.after @@ -0,0 +1,9 @@ +package test + +class Test { + class Inner + + fun foo() { + Inner() + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall2.kt new file mode 100644 index 000000000000..2dfc1d8e81c7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall2.kt @@ -0,0 +1,9 @@ +package test + +class Test { + inner class Inner + + fun foo() { + this.Inner() + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall2.kt.after new file mode 100644 index 000000000000..b7032d226526 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall2.kt.after @@ -0,0 +1,9 @@ +package test + +class Test { + class Inner + + fun foo() { + Inner() + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall3.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall3.kt new file mode 100644 index 000000000000..8115ef13e97f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall3.kt @@ -0,0 +1,9 @@ +package test + +class Test { + inner class Inner + + fun foo(t: Test) { + t.Inner() + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall3.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall3.kt.after new file mode 100644 index 000000000000..b7b121e537c7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall3.kt.after @@ -0,0 +1,9 @@ +package test + +class Test { + class Inner + + fun foo(t: Test) { + Inner() + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall4.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall4.kt new file mode 100644 index 000000000000..350c37f2fb48 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall4.kt @@ -0,0 +1,9 @@ +package test + +class Test { + inner class Inner +} + +fun foo(t: Test) { + t.Inner() +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall4.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall4.kt.after new file mode 100644 index 000000000000..e7f533f66e49 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall4.kt.after @@ -0,0 +1,9 @@ +package test + +class Test { + class Inner +} + +fun foo(t: Test) { + Test.Inner() +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall5.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall5.kt new file mode 100644 index 000000000000..34fc5371d33e --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall5.kt @@ -0,0 +1,11 @@ +package test + +class Test { + inner class Foo { + inner class Bar + } +} + +fun Test.foo(t: Test) { + Foo().Bar() +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall5.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall5.kt.after new file mode 100644 index 000000000000..c981cafd6f03 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall5.kt.after @@ -0,0 +1,11 @@ +package test + +class Test { + class Foo { + inner class Bar + } +} + +fun Test.foo(t: Test) { + Test.Foo().Bar() +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall6.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall6.kt new file mode 100644 index 000000000000..8604801a13a4 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall6.kt @@ -0,0 +1,11 @@ +package test + +class Test { + class Foo { + inner class Bar + } +} + +fun Test.foo(t: Test) { + Test.Foo().Bar() +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall6.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall6.kt.after new file mode 100644 index 000000000000..405d7ffb6954 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/redundantInnerClassModifier/innerClassConstructorCall6.kt.after @@ -0,0 +1,11 @@ +package test + +class Test { + class Foo { + class Bar + } +} + +fun Test.foo(t: Test) { + Test.Foo.Bar() +} \ No newline at end of file