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.psiUtil.anyDescendantOfType
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.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
@@ -37,8 +38,9 @@ class SimplifiableCallChainInspection : AbstractKotlinInspection() {
override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
super.visitQualifiedExpression(expression)
val firstQualifiedExpression = expression.receiverExpression as? KtQualifiedExpression ?: return
val firstCallExpression = firstQualifiedExpression.selectorExpression as? KtCallExpression ?: return
val firstExpression = expression.receiverExpression
val firstCallExpression = getCallExpression(firstExpression) ?: return
val secondCallExpression = expression.selectorExpression as? KtCallExpression ?: return
val firstCalleeExpression = firstCallExpression.calleeExpression ?: return
@@ -48,15 +50,20 @@ class SimplifiableCallChainInspection : AbstractKotlinInspection() {
] ?: return
val context = expression.analyze()
val firstResolvedCall = firstQualifiedExpression.getResolvedCall(context) ?: return
val firstResolvedCall = firstExpression.getResolvedCall(context) ?: return
val conversion = actualConversions.firstOrNull {
firstResolvedCall.resultingDescriptor.fqNameOrNull()?.asString() == it.firstFqName
} ?: return
val builtIns = context[BindingContext.EXPRESSION_TYPE_INFO, expression]?.type?.builtIns ?: return
// Do not apply on maps due to lack of relevant stdlib functions
val firstReceiverType = firstResolvedCall.extensionReceiver?.type ?: return
val builtIns = firstReceiverType.builtIns
val mapType = builtIns.map.defaultType
if (firstReceiverType.isSubtypeOf(mapType)) return
val firstReceiverType = firstResolvedCall.extensionReceiver?.type
if (firstReceiverType != null) {
val mapType = builtIns.map.defaultType
if (firstReceiverType.isSubtypeOf(mapType)) return
}
// Do not apply for lambdas with return inside
val lambdaArgument = firstCallExpression.lambdaArguments.firstOrNull()
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.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 }
@@ -124,5 +133,9 @@ class SimplifiableCallChainInspection : AbstractKotlinInspection() {
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) {
(descriptor.psiElement as? KtQualifiedExpression)?.let { secondQualifiedExpression ->
val factory = KtPsiFactory(secondQualifiedExpression)
val firstQualifiedExpression = secondQualifiedExpression.receiverExpression as? KtQualifiedExpression ?: return
val operationSign = if (firstQualifiedExpression is KtSafeQualifiedExpression) "?." else "."
val firstCallExpression = firstQualifiedExpression.selectorExpression as? KtCallExpression ?: return
val firstExpression = secondQualifiedExpression.receiverExpression
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 lastArgumentPrefix = if (newName.startsWith("joinTo")) "transform = " else ""
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 argumentsText = arguments.ifNotEmpty { joinToString(prefix = "(", postfix = ")") } ?: ""
val newQualifiedExpression = if (lambdaArgument != null) factory.createExpressionByPattern(
"$0$1$2 $3 $4",
firstQualifiedExpression.receiverExpression,
receiverExpression ?: "",
operationSign,
newName,
argumentsText,
@@ -52,7 +60,7 @@ class SimplifyCallChainFix(val newName: String) : LocalQuickFix {
)
else factory.createExpressionByPattern(
"$0$1$2 $3",
firstQualifiedExpression.receiverExpression,
receiverExpression ?: "",
operationSign,
newName,
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);
}
@TestMetadata("listOfNotNull.kt")
public void testListOfNotNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/listOfNotNull.kt");
doTest(fileName);
}
@TestMetadata("mapNotNull.kt")
public void testMapNotNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapNotNull.kt");