diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifiableCallChainInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifiableCallChainInspection.kt index 8d73ac037e8..90f75a62b69 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifiableCallChainInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifiableCallChainInspection.kt @@ -20,93 +20,94 @@ import org.jetbrains.kotlin.types.typeUtil.builtIns import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf class SimplifiableCallChainInspection : AbstractKotlinInspection() { - - override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = - qualifiedExpressionVisitor(fun(expression) { - val firstExpression = expression.receiverExpression - val firstCallExpression = getCallExpression(firstExpression) ?: return + qualifiedExpressionVisitor(fun(expression) { + val firstExpression = expression.receiverExpression + 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 secondCalleeExpression = secondCallExpression.calleeExpression ?: return - val actualConversions = conversionGroups[ - firstCalleeExpression.text to secondCalleeExpression.text - ] ?: return + val firstCalleeExpression = firstCallExpression.calleeExpression ?: return + val secondCalleeExpression = secondCallExpression.calleeExpression ?: return + val actualConversions = conversionGroups[ + firstCalleeExpression.text to secondCalleeExpression.text + ] ?: return - val context = expression.analyze() - val firstResolvedCall = firstExpression.getResolvedCall(context) ?: return - val conversion = actualConversions.firstOrNull { - firstResolvedCall.resultingDescriptor.fqNameOrNull()?.asString() == it.firstFqName - } ?: return + val context = expression.analyze() + 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 + 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 - val firstReceiverRawType = firstReceiverType?.constructor?.declarationDescriptor?.defaultType - if (firstReceiverRawType != null) { - if (firstReceiverRawType.isSubtypeOf(builtIns.map.defaultType) || - firstReceiverRawType.isSubtypeOf(builtIns.mutableMap.defaultType)) return - } + // Do not apply on maps due to lack of relevant stdlib functions + val firstReceiverType = firstResolvedCall.extensionReceiver?.type + val firstReceiverRawType = firstReceiverType?.constructor?.declarationDescriptor?.defaultType + if (firstReceiverRawType != null) { + if (firstReceiverRawType.isSubtypeOf(builtIns.map.defaultType) || + firstReceiverRawType.isSubtypeOf(builtIns.mutableMap.defaultType) + ) return + } - // Do not apply for lambdas with return inside - val lambdaArgument = firstCallExpression.lambdaArguments.firstOrNull() - if (lambdaArgument?.anyDescendantOfType() == true) return + // Do not apply for lambdas with return inside + val lambdaArgument = firstCallExpression.lambdaArguments.firstOrNull() + if (lambdaArgument?.anyDescendantOfType() == true) return - val secondResolvedCall = expression.getResolvedCall(context) ?: return - val secondResultingDescriptor = secondResolvedCall.resultingDescriptor - if (secondResultingDescriptor.fqNameOrNull()?.asString() != conversion.secondFqName) return - if (secondResolvedCall.valueArguments.any { (parameter, resolvedArgument) -> + val secondResolvedCall = expression.getResolvedCall(context) ?: return + val secondResultingDescriptor = secondResolvedCall.resultingDescriptor + if (secondResultingDescriptor.fqNameOrNull()?.asString() != conversion.secondFqName) return + if (secondResolvedCall.valueArguments.any { (parameter, resolvedArgument) -> parameter.type.isFunctionOfAnyKind() && - resolvedArgument !is DefaultValueArgument - }) return - - if (conversion.replacement.startsWith("joinTo")) { - // Function parameter in map must have String result type - if (!firstResolvedCall.hasLastFunctionalParameterWithResult(context) { - it.isSubtypeOf(builtIns.charSequence.defaultType) - }) return + resolvedArgument !is DefaultValueArgument } + ) return - val descriptor = holder.manager.createProblemDescriptor( - expression, - firstCalleeExpression.textRange.shiftRight(-expression.startOffset), - "Call chain on collection type may be simplified", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - isOnTheFly, - SimplifyCallChainFix(conversion.replacement) - ) - holder.registerProblem(descriptor) - }) + if (conversion.replacement.startsWith("joinTo")) { + // Function parameter in map must have String result type + if (!firstResolvedCall.hasLastFunctionalParameterWithResult(context) { + it.isSubtypeOf(builtIns.charSequence.defaultType) + } + ) return + } + + val descriptor = holder.manager.createProblemDescriptor( + expression, + firstCalleeExpression.textRange.shiftRight(-expression.startOffset), + "Call chain on collection type may be simplified", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + isOnTheFly, + SimplifyCallChainFix(conversion.replacement) + ) + holder.registerProblem(descriptor) + }) companion object { private val conversions = listOf( - Conversion("kotlin.collections.filter", "kotlin.collections.first", "first"), - Conversion("kotlin.collections.filter", "kotlin.collections.firstOrNull", "firstOrNull"), - Conversion("kotlin.collections.filter", "kotlin.collections.last", "last"), - Conversion("kotlin.collections.filter", "kotlin.collections.lastOrNull", "lastOrNull"), - Conversion("kotlin.collections.filter", "kotlin.collections.single", "single"), - Conversion("kotlin.collections.filter", "kotlin.collections.singleOrNull", "singleOrNull"), - Conversion("kotlin.collections.filter", "kotlin.collections.isNotEmpty", "any"), - Conversion("kotlin.collections.filter", "kotlin.collections.List.isEmpty", "none"), + Conversion("kotlin.collections.filter", "kotlin.collections.first", "first"), + Conversion("kotlin.collections.filter", "kotlin.collections.firstOrNull", "firstOrNull"), + Conversion("kotlin.collections.filter", "kotlin.collections.last", "last"), + Conversion("kotlin.collections.filter", "kotlin.collections.lastOrNull", "lastOrNull"), + Conversion("kotlin.collections.filter", "kotlin.collections.single", "single"), + Conversion("kotlin.collections.filter", "kotlin.collections.singleOrNull", "singleOrNull"), + Conversion("kotlin.collections.filter", "kotlin.collections.isNotEmpty", "any"), + Conversion("kotlin.collections.filter", "kotlin.collections.List.isEmpty", "none"), - Conversion("kotlin.text.filter", "kotlin.text.first", "first"), - Conversion("kotlin.text.filter", "kotlin.text.firstOrNull", "firstOrNull"), - Conversion("kotlin.text.filter", "kotlin.text.last", "last"), - Conversion("kotlin.text.filter", "kotlin.text.lastOrNull", "lastOrNull"), - Conversion("kotlin.text.filter", "kotlin.text.single", "single"), - Conversion("kotlin.text.filter", "kotlin.text.singleOrNull", "singleOrNull"), - Conversion("kotlin.text.filter", "kotlin.text.isNotEmpty", "any"), - Conversion("kotlin.text.filter", "kotlin.text.isEmpty", "none"), + Conversion("kotlin.text.filter", "kotlin.text.first", "first"), + Conversion("kotlin.text.filter", "kotlin.text.firstOrNull", "firstOrNull"), + Conversion("kotlin.text.filter", "kotlin.text.last", "last"), + Conversion("kotlin.text.filter", "kotlin.text.lastOrNull", "lastOrNull"), + Conversion("kotlin.text.filter", "kotlin.text.single", "single"), + Conversion("kotlin.text.filter", "kotlin.text.singleOrNull", "singleOrNull"), + Conversion("kotlin.text.filter", "kotlin.text.isNotEmpty", "any"), + Conversion("kotlin.text.filter", "kotlin.text.isEmpty", "none"), - 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.joinTo", "joinTo"), + Conversion("kotlin.collections.map", "kotlin.collections.joinToString", "joinToString"), + Conversion("kotlin.collections.map", "kotlin.collections.filterNotNull", "mapNotNull"), - Conversion("kotlin.collections.listOf", "kotlin.collections.filterNotNull", "listOfNotNull") + Conversion("kotlin.collections.listOf", "kotlin.collections.filterNotNull", "listOfNotNull") ) private val conversionGroups = conversions.groupBy { it.firstName to it.secondName } @@ -120,8 +121,8 @@ class SimplifiableCallChainInspection : AbstractKotlinInspection() { } fun getCallExpression(firstExpression: KtExpression) = - ((firstExpression as? KtQualifiedExpression)?.selectorExpression as? KtCallExpression - ?: firstExpression as? KtCallExpression) + (firstExpression as? KtQualifiedExpression)?.selectorExpression as? KtCallExpression + ?: firstExpression as? KtCallExpression } } \ No newline at end of file