Support callable references in "implicit this" inspection

Second part of #KT-21510
This commit is contained in:
Kirill
2017-12-17 23:38:50 +03:00
committed by Mikhail Glukhikh
parent 74e5a9425a
commit 7842fa4796
6 changed files with 70 additions and 5 deletions
@@ -30,24 +30,37 @@ import org.jetbrains.kotlin.resolve.BindingContext
class ImplicitThisInspection : AbstractKotlinInspection() { class ImplicitThisInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() {
override fun visitCallableReferenceExpression(expression: KtCallableReferenceExpression) {
if (expression.receiverExpression != null) return
handle(expression, expression.callableReference, ::CallableReferenceFix)
}
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
if (expression !is KtNameReferenceExpression) return if (expression !is KtNameReferenceExpression) return
if (expression.parent is KtThisExpression) return if (expression.parent is KtThisExpression) return
if (expression.parent is KtCallableReferenceExpression) return
if (expression.isSelectorOfDotQualifiedExpression()) return if (expression.isSelectorOfDotQualifiedExpression()) return
val parent = expression.parent val parent = expression.parent
if (parent is KtCallExpression && parent.isSelectorOfDotQualifiedExpression()) return if (parent is KtCallExpression && parent.isSelectorOfDotQualifiedExpression()) return
val context = expression.analyze() handle(expression, expression, ::CallFix)
val scope = expression.getResolutionScope(context) ?: return }
val descriptor = context[BindingContext.REFERENCE_TARGET, expression] as? CallableDescriptor ?: return private fun handle(expression: KtExpression, reference: KtReferenceExpression, fixFactory: (String) -> LocalQuickFix) {
val context = reference.analyze()
val scope = reference.getResolutionScope(context) ?: return
val descriptor = context[BindingContext.REFERENCE_TARGET, reference] as? CallableDescriptor ?: return
val receiverDescriptor = descriptor.extensionReceiverParameter ?: descriptor.dispatchReceiverParameter ?: return val receiverDescriptor = descriptor.extensionReceiverParameter ?: descriptor.dispatchReceiverParameter ?: return
val receiverType = receiverDescriptor.type val receiverType = receiverDescriptor.type
val expressionFactory = scope.getFactoryForImplicitReceiverWithSubtypeOf(receiverType) ?: return val expressionFactory = scope.getFactoryForImplicitReceiverWithSubtypeOf(receiverType) ?: return
val receiverText = if (expressionFactory.isImmediate) "this" else expressionFactory.expressionText val receiverText = if (expressionFactory.isImmediate) "this" else expressionFactory.expressionText
holder.registerProblem(expression, "Add explicit '$receiverText'", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, Fix(receiverText)) val fix = fixFactory(receiverText)
holder.registerProblem(expression, "Add explicit '$receiverText'", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, fix)
} }
private fun KtExpression.isSelectorOfDotQualifiedExpression(): Boolean { private fun KtExpression.isSelectorOfDotQualifiedExpression(): Boolean {
@@ -56,7 +69,7 @@ class ImplicitThisInspection : AbstractKotlinInspection() {
} }
} }
private class Fix(private val receiverText: String) : LocalQuickFix { private class CallFix(private val receiverText: String) : LocalQuickFix {
override fun getFamilyName() = "Add explicit '$receiverText'" override fun getFamilyName() = "Add explicit '$receiverText'"
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
@@ -67,4 +80,16 @@ class ImplicitThisInspection : AbstractKotlinInspection() {
call.replace(factory.createExpressionByPattern("$0.$1", receiverText, call)) call.replace(factory.createExpressionByPattern("$0.$1", receiverText, call))
} }
} }
private class CallableReferenceFix(private val receiverText: String) : LocalQuickFix {
override fun getFamilyName() = "Add explicit '$receiverText'"
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val expression = descriptor.psiElement as? KtCallableReferenceExpression ?: return
val factory = KtPsiFactory(project)
val reference = expression.callableReference
expression.replace(factory.createExpressionByPattern("$0::$1", receiverText, reference))
}
}
} }
@@ -0,0 +1,7 @@
class Foo {
fun s() = ""
fun test() {
<caret>::s
}
}
@@ -0,0 +1,7 @@
class Foo {
fun s() = ""
fun test() {
<caret>this::s
}
}
@@ -0,0 +1,7 @@
class Foo {
val s = ""
fun test() {
<caret>::s
}
}
@@ -0,0 +1,7 @@
class Foo {
val s = ""
fun test() {
<caret>this::s
}
}
@@ -767,6 +767,12 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("functionReference.kt")
public void testFunctionReference() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/implicitThis/functionReference.kt");
doTest(fileName);
}
@TestMetadata("multipleReceivers.kt") @TestMetadata("multipleReceivers.kt")
public void testMultipleReceivers() throws Exception { public void testMultipleReceivers() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/implicitThis/multipleReceivers.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/implicitThis/multipleReceivers.kt");
@@ -797,6 +803,12 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("propertyReference.kt")
public void testPropertyReference() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/implicitThis/propertyReference.kt");
doTest(fileName);
}
@TestMetadata("topLevel.kt") @TestMetadata("topLevel.kt")
public void testTopLevel() throws Exception { public void testTopLevel() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/implicitThis/topLevel.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/implicitThis/topLevel.kt");