Add restrictions for "simplifiable call chain" inspection

Do not use on maps; do not suggest for lambdas with return inside
Related to KT-18274
This commit is contained in:
Mikhail Glukhikh
2017-06-27 14:47:31 +03:00
committed by Mikhail Glukhikh
parent 2f0159a7ab
commit fdca96634e
8 changed files with 74 additions and 3 deletions
@@ -615,6 +615,11 @@ public abstract class KotlinBuiltIns {
return getBuiltInClassByName("String");
}
@NotNull
public ClassDescriptor getCharSequence() {
return getBuiltInClassByName("CharSequence");
}
@NotNull
public ClassDescriptor getComparable() {
return getBuiltInClassByName("Comparable");
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.idea.inspections.collections
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.psi.*
@@ -29,6 +28,8 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
class SimplifiableCallChainInspection : AbstractKotlinInspection() {
@@ -46,11 +47,19 @@ class SimplifiableCallChainInspection : AbstractKotlinInspection() {
firstCallExpression.calleeExpression?.text to secondCallExpression.calleeExpression?.text
] ?: return
val context = expression.analyze(BodyResolveMode.PARTIAL)
val context = expression.analyze()
val firstResolvedCall = firstQualifiedExpression.getResolvedCall(context) ?: return
val conversion = actualConversions.firstOrNull {
firstResolvedCall.resultingDescriptor.fqNameOrNull()?.asString() == it.firstFqName
} ?: 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
// Do not apply for lambdas with return inside
val lambdaArgument = firstCallExpression.lambdaArguments.firstOrNull()
if (lambdaArgument?.anyDescendantOfType<KtReturnExpression>() == true) return
val secondResolvedCall = expression.getResolvedCall(context) ?: return
val secondResultingDescriptor = secondResolvedCall.resultingDescriptor
@@ -62,7 +71,9 @@ class SimplifiableCallChainInspection : AbstractKotlinInspection() {
if (conversion.replacement.startsWith("joinTo")) {
// Function parameter in map must have String result type
if (!firstResolvedCall.hasLastFunctionalParameterWithResult(context) { KotlinBuiltIns.isString(it) }) return
if (!firstResolvedCall.hasLastFunctionalParameterWithResult(context) {
it.isSubtypeOf(builtIns.charSequence.defaultType)
}) return
}
val descriptor = holder.manager.createProblemDescriptor(
@@ -0,0 +1,4 @@
// PROBLEM: none
// WITH_RUNTIME
val x = hashMapOf(1 to 2, 3 to 4).<caret>map { (key, value) -> key + value }.joinToString()
@@ -0,0 +1,4 @@
// PROBLEM: none
// WITH_RUNTIME
val x = mapOf(1 to 2, 3 to 4).<caret>map { (key, value) -> key + value }.joinToString()
@@ -0,0 +1,7 @@
// WITH_RUNTIME
val x = listOf(1, 2, 3).<caret>map {
val sb = StringBuilder()
sb.append(it).append(" + ").append(it)
sb
}.joinToString(prefix = "= ", separator = " + ")
@@ -0,0 +1,7 @@
// WITH_RUNTIME
val x = listOf(1, 2, 3).joinToString(prefix = "= ", separator = " + ") {
val sb = StringBuilder()
sb.append(it).append(" + ").append(it)
sb
}
@@ -0,0 +1,9 @@
// PROBLEM: none
// WITH_RUNTIME
fun test(args: List<Int>): String {
return args.<caret>map {
if (it == 0) return ""
"$it * $it"
}.joinToString(separator = " + ")
}
@@ -139,12 +139,30 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
doTest(fileName);
}
@TestMetadata("joinToForHashMap.kt")
public void testJoinToForHashMap() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToForHashMap.kt");
doTest(fileName);
}
@TestMetadata("joinToForMap.kt")
public void testJoinToForMap() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToForMap.kt");
doTest(fileName);
}
@TestMetadata("joinToString.kt")
public void testJoinToString() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToString.kt");
doTest(fileName);
}
@TestMetadata("joinToStringViaBuilder.kt")
public void testJoinToStringViaBuilder() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringViaBuilder.kt");
doTest(fileName);
}
@TestMetadata("joinToStringWithReference.kt")
public void testJoinToStringWithReference() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReference.kt");
@@ -162,6 +180,12 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapNotNull.kt");
doTest(fileName);
}
@TestMetadata("mapWithReturn.kt")
public void testMapWithReturn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapWithReturn.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/inspectionsLocal/collections/uselessCallOnCollection")