Simplifiable call inspection: add filter -> filterIsInstance replacement

#KT-30501 Fixed
This commit is contained in:
Mikhail Glukhikh
2019-05-06 13:26:19 +03:00
parent 7f1643cfad
commit e934eba6e4
5 changed files with 49 additions and 18 deletions
@@ -26,13 +26,13 @@ class SimplifiableCallInspection : AbstractKotlinInspection() {
val calleeExpression = callExpression.calleeExpression ?: return val calleeExpression = callExpression.calleeExpression ?: return
val (conversion, resolvedCall) = callExpression.findConversionAndResolvedCall() ?: return val (conversion, resolvedCall) = callExpression.findConversionAndResolvedCall() ?: return
if (!conversion.callChecker(resolvedCall)) return if (!conversion.callChecker(resolvedCall)) return
val conversionSuffix = conversion.analyzer(callExpression) ?: return val replacement = conversion.analyzer(callExpression) ?: return
holder.registerProblem( holder.registerProblem(
calleeExpression, calleeExpression,
"${conversion.fqName.shortName()} call could be simplified to ${conversion.replacement}$conversionSuffix", "${conversion.fqName.shortName()} call could be simplified to $replacement",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
SimplifyCallFix(conversion, conversionSuffix) SimplifyCallFix(conversion, replacement)
) )
}) })
@@ -50,7 +50,6 @@ class SimplifiableCallInspection : AbstractKotlinInspection() {
private data class Conversion( private data class Conversion(
val callFqName: String, val callFqName: String,
val replacement: String,
val analyzer: (KtCallExpression) -> String?, val analyzer: (KtCallExpression) -> String?,
val callChecker: (ResolvedCall<*>) -> Boolean = { true } val callChecker: (ResolvedCall<*>) -> Boolean = { true }
) { ) {
@@ -79,25 +78,34 @@ class SimplifiableCallInspection : AbstractKotlinInspection() {
this is KtConstantExpression && this.node.elementType == KtNodeTypes.NULL this is KtConstantExpression && this.node.elementType == KtNodeTypes.NULL
private val conversions = listOf( private val conversions = listOf(
Conversion("kotlin.collections.flatMap", "flatten", fun(callExpression: KtCallExpression): String? { Conversion("kotlin.collections.flatMap", fun(callExpression: KtCallExpression): String? {
val lambdaExpression = callExpression.singleLambdaExpression() ?: return null val lambdaExpression = callExpression.singleLambdaExpression() ?: return null
val reference = lambdaExpression.singleStatement() ?: return null val reference = lambdaExpression.singleStatement() ?: return null
val lambdaParameterName = lambdaExpression.singleLambdaParameterName() ?: return null val lambdaParameterName = lambdaExpression.singleLambdaParameterName() ?: return null
if (!reference.isNameReferenceTo(lambdaParameterName)) return null if (!reference.isNameReferenceTo(lambdaParameterName)) return null
return "()" return "flatten()"
}), }),
Conversion("kotlin.collections.filter", "filterNotNull", analyzer = fun(callExpression: KtCallExpression): String? { Conversion("kotlin.collections.filter", analyzer = fun(callExpression: KtCallExpression): String? {
val lambdaExpression = callExpression.singleLambdaExpression() ?: return null val lambdaExpression = callExpression.singleLambdaExpression() ?: return null
val statement = lambdaExpression.singleStatement() as? KtBinaryExpression ?: return null
val lambdaParameterName = lambdaExpression.singleLambdaParameterName() ?: return null val lambdaParameterName = lambdaExpression.singleLambdaParameterName() ?: return null
if (statement.operationToken != KtTokens.EXCLEQ && statement.operationToken != KtTokens.EXCLEQEQEQ) return null when (val statement = lambdaExpression.singleStatement() ?: return null) {
val left = statement.left ?: return null is KtBinaryExpression -> {
val right = statement.right ?: return null if (statement.operationToken != KtTokens.EXCLEQ && statement.operationToken != KtTokens.EXCLEQEQEQ) return null
if (left.isNameReferenceTo(lambdaParameterName) && right.isNull()) { val left = statement.left ?: return null
return "()" val right = statement.right ?: return null
} else if (right.isNameReferenceTo(lambdaParameterName) && left.isNull()) { if (left.isNameReferenceTo(lambdaParameterName) && right.isNull()) {
return "()" return "filterNotNull()"
} else if (right.isNameReferenceTo(lambdaParameterName) && left.isNull()) {
return "filterNotNull()"
}
}
is KtIsExpression -> {
if (statement.isNegated) return null
if (!statement.leftHandSide.isNameReferenceTo(lambdaParameterName)) return null
val rightTypeReference = statement.typeReference ?: return null
return "filterIsInstance<${rightTypeReference.text}>()"
}
} }
return null return null
}, callChecker = fun(resolvedCall: ResolvedCall<*>): Boolean { }, callChecker = fun(resolvedCall: ResolvedCall<*>): Boolean {
@@ -107,14 +115,14 @@ class SimplifiableCallInspection : AbstractKotlinInspection() {
) )
} }
private class SimplifyCallFix(val conversion: Conversion, val conversionSuffix: String) : LocalQuickFix { private class SimplifyCallFix(val conversion: Conversion, val replacement: String) : LocalQuickFix {
override fun getName() = "Convert '${conversion.fqName.shortName()}' call to '${conversion.replacement}$conversionSuffix'" override fun getName() = "Convert '${conversion.fqName.shortName()}' call to '$replacement'"
override fun getFamilyName() = name override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val callExpression = descriptor.psiElement.parent as? KtCallExpression ?: return val callExpression = descriptor.psiElement.parent as? KtCallExpression ?: return
callExpression.replace(KtPsiFactory(callExpression).createExpression("${conversion.replacement}$conversionSuffix")) callExpression.replace(KtPsiFactory(callExpression).createExpression(replacement))
} }
} }
} }
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<Any>) {
list.<caret>filter { it is String }
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<Any>) {
list.filterIsInstance<String>()
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
// PROBLEM: none
fun test(list: List<Any>) {
list.<caret>filter { it !is String }
}
@@ -1108,6 +1108,16 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/explicitLambdaParameter.kt"); runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/explicitLambdaParameter.kt");
} }
@TestMetadata("filterIsInstanceSimple.kt")
public void testFilterIsInstanceSimple() throws Exception {
runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/filterIsInstanceSimple.kt");
}
@TestMetadata("filterIsNotInstance.kt")
public void testFilterIsNotInstance() throws Exception {
runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/filterIsNotInstance.kt");
}
@TestMetadata("filterNotNullBrackets.kt") @TestMetadata("filterNotNullBrackets.kt")
public void testFilterNotNullBrackets() throws Exception { public void testFilterNotNullBrackets() throws Exception {
runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/filterNotNullBrackets.kt"); runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/filterNotNullBrackets.kt");