Support listOfNotNull in SimplifiableCallChainInspection #KT-20410 Fixed

This commit is contained in:
Kirill Rakhman
2017-10-07 17:37:26 +03:00
committed by Mikhail Glukhikh
parent 82a9c35194
commit 91c35a6c59
5 changed files with 49 additions and 14 deletions
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
@@ -37,8 +38,9 @@ class SimplifiableCallChainInspection : AbstractKotlinInspection() {
override fun visitQualifiedExpression(expression: KtQualifiedExpression) { override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
super.visitQualifiedExpression(expression) super.visitQualifiedExpression(expression)
val firstQualifiedExpression = expression.receiverExpression as? KtQualifiedExpression ?: return val firstExpression = expression.receiverExpression
val firstCallExpression = firstQualifiedExpression.selectorExpression as? KtCallExpression ?: return val firstCallExpression = getCallExpression(firstExpression) ?: return
val secondCallExpression = expression.selectorExpression as? KtCallExpression ?: return val secondCallExpression = expression.selectorExpression as? KtCallExpression ?: return
val firstCalleeExpression = firstCallExpression.calleeExpression ?: return val firstCalleeExpression = firstCallExpression.calleeExpression ?: return
@@ -48,15 +50,20 @@ class SimplifiableCallChainInspection : AbstractKotlinInspection() {
] ?: return ] ?: return
val context = expression.analyze() val context = expression.analyze()
val firstResolvedCall = firstQualifiedExpression.getResolvedCall(context) ?: return val firstResolvedCall = firstExpression.getResolvedCall(context) ?: return
val conversion = actualConversions.firstOrNull { val conversion = actualConversions.firstOrNull {
firstResolvedCall.resultingDescriptor.fqNameOrNull()?.asString() == it.firstFqName firstResolvedCall.resultingDescriptor.fqNameOrNull()?.asString() == it.firstFqName
} ?: return } ?: return
val builtIns = context[BindingContext.EXPRESSION_TYPE_INFO, expression]?.type?.builtIns ?: return
// Do not apply on maps due to lack of relevant stdlib functions // Do not apply on maps due to lack of relevant stdlib functions
val firstReceiverType = firstResolvedCall.extensionReceiver?.type ?: return val firstReceiverType = firstResolvedCall.extensionReceiver?.type
val builtIns = firstReceiverType.builtIns if (firstReceiverType != null) {
val mapType = builtIns.map.defaultType val mapType = builtIns.map.defaultType
if (firstReceiverType.isSubtypeOf(mapType)) return if (firstReceiverType.isSubtypeOf(mapType)) return
}
// Do not apply for lambdas with return inside // Do not apply for lambdas with return inside
val lambdaArgument = firstCallExpression.lambdaArguments.firstOrNull() val lambdaArgument = firstCallExpression.lambdaArguments.firstOrNull()
if (lambdaArgument?.anyDescendantOfType<KtReturnExpression>() == true) return if (lambdaArgument?.anyDescendantOfType<KtReturnExpression>() == true) return
@@ -111,7 +118,9 @@ class SimplifiableCallChainInspection : AbstractKotlinInspection() {
Conversion("kotlin.collections.map", "kotlin.collections.joinTo", "joinTo"), Conversion("kotlin.collections.map", "kotlin.collections.joinTo", "joinTo"),
Conversion("kotlin.collections.map", "kotlin.collections.joinToString", "joinToString"), Conversion("kotlin.collections.map", "kotlin.collections.joinToString", "joinToString"),
Conversion("kotlin.collections.map", "kotlin.collections.filterNotNull", "mapNotNull") Conversion("kotlin.collections.map", "kotlin.collections.filterNotNull", "mapNotNull"),
Conversion("kotlin.collections.listOf", "kotlin.collections.filterNotNull", "listOfNotNull")
) )
private val conversionGroups = conversions.groupBy { it.firstName to it.secondName } private val conversionGroups = conversions.groupBy { it.firstName to it.secondName }
@@ -124,5 +133,9 @@ class SimplifiableCallChainInspection : AbstractKotlinInspection() {
val secondName = secondFqName.convertToShort() val secondName = secondFqName.convertToShort()
} }
fun getCallExpression(firstExpression: KtExpression) =
((firstExpression as? KtQualifiedExpression)?.selectorExpression as? KtCallExpression
?: firstExpression as? KtCallExpression)
} }
} }
@@ -31,20 +31,28 @@ class SimplifyCallChainFix(val newName: String) : LocalQuickFix {
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
(descriptor.psiElement as? KtQualifiedExpression)?.let { secondQualifiedExpression -> (descriptor.psiElement as? KtQualifiedExpression)?.let { secondQualifiedExpression ->
val factory = KtPsiFactory(secondQualifiedExpression) val factory = KtPsiFactory(secondQualifiedExpression)
val firstQualifiedExpression = secondQualifiedExpression.receiverExpression as? KtQualifiedExpression ?: return val firstExpression = secondQualifiedExpression.receiverExpression
val operationSign = if (firstQualifiedExpression is KtSafeQualifiedExpression) "?." else "."
val firstCallExpression = firstQualifiedExpression.selectorExpression as? KtCallExpression ?: return val operationSign = when (firstExpression) {
is KtSafeQualifiedExpression -> "?."
is KtQualifiedExpression -> "."
else -> ""
}
val receiverExpression = (firstExpression as? KtQualifiedExpression)?.receiverExpression
val firstCallExpression = SimplifiableCallChainInspection.getCallExpression(firstExpression) ?: return
val secondCallExpression = secondQualifiedExpression.selectorExpression as? KtCallExpression ?: return val secondCallExpression = secondQualifiedExpression.selectorExpression as? KtCallExpression ?: return
val lastArgumentPrefix = if (newName.startsWith("joinTo")) "transform = " else "" val lastArgumentPrefix = if (newName.startsWith("joinTo")) "transform = " else ""
val arguments = secondCallExpression.valueArgumentList?.arguments.orEmpty().map { it.text } + val arguments = secondCallExpression.valueArgumentList?.arguments.orEmpty().map { it.text } +
firstCallExpression.valueArgumentList?.arguments.orEmpty().map { "$lastArgumentPrefix${it.text}"} firstCallExpression.valueArgumentList?.arguments.orEmpty().map { "$lastArgumentPrefix${it.text}" }
val lambdaArgument = firstCallExpression.lambdaArguments.singleOrNull() val lambdaArgument = firstCallExpression.lambdaArguments.singleOrNull()
val argumentsText = arguments.ifNotEmpty { joinToString(prefix = "(", postfix = ")") } ?: "" val argumentsText = arguments.ifNotEmpty { joinToString(prefix = "(", postfix = ")") } ?: ""
val newQualifiedExpression = if (lambdaArgument != null) factory.createExpressionByPattern( val newQualifiedExpression = if (lambdaArgument != null) factory.createExpressionByPattern(
"$0$1$2 $3 $4", "$0$1$2 $3 $4",
firstQualifiedExpression.receiverExpression, receiverExpression ?: "",
operationSign, operationSign,
newName, newName,
argumentsText, argumentsText,
@@ -52,7 +60,7 @@ class SimplifyCallChainFix(val newName: String) : LocalQuickFix {
) )
else factory.createExpressionByPattern( else factory.createExpressionByPattern(
"$0$1$2 $3", "$0$1$2 $3",
firstQualifiedExpression.receiverExpression, receiverExpression ?: "",
operationSign, operationSign,
newName, newName,
argumentsText argumentsText
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val s: String? = null
val x = <caret>listOf(s).filterNotNull()
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val s: String? = null
val x = <caret>listOfNotNull(s)
@@ -250,6 +250,12 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("listOfNotNull.kt")
public void testListOfNotNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/listOfNotNull.kt");
doTest(fileName);
}
@TestMetadata("mapNotNull.kt") @TestMetadata("mapNotNull.kt")
public void testMapNotNull() throws Exception { public void testMapNotNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapNotNull.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapNotNull.kt");