diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt index a1217e7f39a..a747b22f5f5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt @@ -32,18 +32,9 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression import org.jetbrains.kotlin.resolve.calls.callUtil.getType -class IfThenToSafeAccessInspection( - private val stableElementNeeded: Boolean -) : AbstractApplicabilityBasedInspection(KtIfExpression::class.java) { +class IfThenToSafeAccessInspection : AbstractApplicabilityBasedInspection(KtIfExpression::class.java) { - constructor() : this(stableElementNeeded = true) - - override fun isApplicable(element: KtIfExpression): Boolean { - val ifThenToSelectData = element.buildSelectTransformationData() ?: return false - if (stableElementNeeded && !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false - - return ifThenToSelectData.clausesReplaceableBySafeCall() - } + override fun isApplicable(element: KtIfExpression): Boolean = isApplicableTo(element, expressionShouldBeStable = true) override fun inspectionTarget(element: KtIfExpression) = element.ifKeyword @@ -54,50 +45,64 @@ class IfThenToSafeAccessInspection( override val defaultFixText = "Simplify foldable if-then" - override fun fixText(element: KtIfExpression): String { - val ifThenToSelectData = element.buildSelectTransformationData() - return if (ifThenToSelectData?.baseClauseEvaluatesToReceiver() == true) { - if (ifThenToSelectData.condition is KtIsExpression) { - "Replace 'if' expression with safe cast expression" - } else { - "Remove redundant 'if' expression" - } - } else { - "Replace 'if' expression with safe access expression" - } - } + override fun fixText(element: KtIfExpression): String = fixTextFor(element) override val startFixInWriteAction = false override fun applyTo(element: PsiElement, project: Project, editor: Editor?) { val ifExpression = element.getParentOfType(true) ?: return - val ifThenToSelectData = ifExpression.buildSelectTransformationData() ?: return - - val factory = KtPsiFactory(ifExpression) - val resultExpr = runWriteAction { - val replacedBaseClause = ifThenToSelectData.replacedBaseClause(factory) - val newExpr = ifExpression.replaced(replacedBaseClause) - KtPsiUtil.deparenthesize(newExpr) - } - - if (editor != null && resultExpr is KtSafeQualifiedExpression) { - resultExpr.inlineReceiverIfApplicableWithPrompt(editor) - resultExpr.renameLetParameter(editor) - } - } - - private fun IfThenToSelectData.clausesReplaceableBySafeCall(): Boolean = when { - baseClause == null -> false - negatedClause == null && baseClause.isUsedAsExpression(context) -> false - negatedClause != null && !negatedClause.isNullExpression() -> false - baseClause.evaluatesTo(receiverExpression) -> true - baseClause.hasFirstReceiverOf(receiverExpression) -> true - baseClause.anyArgumentEvaluatesTo(receiverExpression) -> true - receiverExpression is KtThisExpression -> getImplicitReceiver()?.let { it.type == receiverExpression.getType(context) } == true - else -> false + convert(ifExpression, editor) } companion object { + + private fun IfThenToSelectData.clausesReplaceableBySafeCall(): Boolean = when { + baseClause == null -> false + negatedClause == null && baseClause.isUsedAsExpression(context) -> false + negatedClause != null && !negatedClause.isNullExpression() -> false + baseClause.evaluatesTo(receiverExpression) -> true + baseClause.hasFirstReceiverOf(receiverExpression) -> true + baseClause.anyArgumentEvaluatesTo(receiverExpression) -> true + receiverExpression is KtThisExpression -> getImplicitReceiver()?.let { it.type == receiverExpression.getType(context) } == true + else -> false + } + + fun fixTextFor(element: KtIfExpression): String { + val ifThenToSelectData = element.buildSelectTransformationData() + return if (ifThenToSelectData?.baseClauseEvaluatesToReceiver() == true) { + if (ifThenToSelectData.condition is KtIsExpression) { + "Replace 'if' expression with safe cast expression" + } else { + "Remove redundant 'if' expression" + } + } else { + "Replace 'if' expression with safe access expression" + } + } + + fun convert(ifExpression: KtIfExpression, editor: Editor?) { + val ifThenToSelectData = ifExpression.buildSelectTransformationData() ?: return + + val factory = KtPsiFactory(ifExpression) + val resultExpr = runWriteAction { + val replacedBaseClause = ifThenToSelectData.replacedBaseClause(factory) + val newExpr = ifExpression.replaced(replacedBaseClause) + KtPsiUtil.deparenthesize(newExpr) + } + + if (editor != null && resultExpr is KtSafeQualifiedExpression) { + resultExpr.inlineReceiverIfApplicableWithPrompt(editor) + resultExpr.renameLetParameter(editor) + } + } + + fun isApplicableTo(element: KtIfExpression, expressionShouldBeStable: Boolean): Boolean { + val ifThenToSelectData = element.buildSelectTransformationData() ?: return false + if (expressionShouldBeStable && !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false + + return ifThenToSelectData.clausesReplaceableBySafeCall() + } + internal fun KtSafeQualifiedExpression.renameLetParameter(editor: Editor) { val callExpression = selectorExpression as? KtCallExpression ?: return if (callExpression.calleeExpression?.text != "let") return diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt index 14350d892bd..7a19a46ab76 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt @@ -42,67 +42,70 @@ class IfThenToElvisInspection : IntentionBasedInspection( if (element.shouldBeTransformed()) super.problemHighlightType(element) else ProblemHighlightType.INFORMATION } -class IfThenToElvisIntention (private val stableElementNeeded: Boolean) : SelfTargetingOffsetIndependentIntention( - KtIfExpression::class.java, - "Replace 'if' expression with elvis expression" -) { +class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention(KtIfExpression::class.java, intentionText) { - constructor() : this(stableElementNeeded = true) - - private fun IfThenToSelectData.clausesReplaceableByElvis(): Boolean = - when { - baseClause == null || negatedClause == null || negatedClause.isNullOrBlockExpression() -> - false - negatedClause is KtThrowExpression && negatedClause.throwsNullPointerExceptionWithNoArguments() -> - false - baseClause.evaluatesTo(receiverExpression) -> - true - baseClause.anyArgumentEvaluatesTo(receiverExpression) -> - true - hasImplicitReceiverReplaceableBySafeCall() || baseClause.hasFirstReceiverOf(receiverExpression) -> - !baseClause.hasNullableType(context) - else -> - false - } - - override fun isApplicableTo(element: KtIfExpression): Boolean { - val ifThenToSelectData = element.buildSelectTransformationData() ?: return false - if (stableElementNeeded && !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false - - val type = element.getType(ifThenToSelectData.context) ?: return false - if (KotlinBuiltIns.isUnit(type)) return false - - return ifThenToSelectData.clausesReplaceableByElvis() - } - - private fun KtExpression.isNullOrBlockExpression(): Boolean { - val innerExpression = this.unwrapBlockOrParenthesis() - return innerExpression is KtBlockExpression || innerExpression.node.elementType == KtNodeTypes.NULL - } + override fun isApplicableTo(element: KtIfExpression): Boolean = isApplicableTo(element, expressionShouldBeStable = true) override fun startInWriteAction() = false - override fun applyTo(element: KtIfExpression, editor: Editor?) { - val ifThenToSelectData = element.buildSelectTransformationData() ?: return + override fun applyTo(element: KtIfExpression, editor: Editor?) = convert(element, editor) - val factory = KtPsiFactory(element) - val elvis = runWriteAction { - val replacedBaseClause = ifThenToSelectData.replacedBaseClause(factory) - val newExpr = element.replaced( - factory.createExpressionByPattern( - "$0 ?: $1", - replacedBaseClause, - ifThenToSelectData.negatedClause!! - ) - ) - KtPsiUtil.deparenthesize(newExpr) as KtBinaryExpression + companion object { + private fun KtExpression.isNullOrBlockExpression(): Boolean { + val innerExpression = this.unwrapBlockOrParenthesis() + return innerExpression is KtBlockExpression || innerExpression.node.elementType == KtNodeTypes.NULL } - if (editor != null) { - elvis.inlineLeftSideIfApplicableWithPrompt(editor) - with(IfThenToSafeAccessInspection) { - (elvis.left as? KtSafeQualifiedExpression)?.renameLetParameter(editor) + private fun IfThenToSelectData.clausesReplaceableByElvis(): Boolean = + when { + baseClause == null || negatedClause == null || negatedClause.isNullOrBlockExpression() -> + false + negatedClause is KtThrowExpression && negatedClause.throwsNullPointerExceptionWithNoArguments() -> + false + baseClause.evaluatesTo(receiverExpression) -> + true + baseClause.anyArgumentEvaluatesTo(receiverExpression) -> + true + hasImplicitReceiverReplaceableBySafeCall() || baseClause.hasFirstReceiverOf(receiverExpression) -> + !baseClause.hasNullableType(context) + else -> + false } + + val intentionText = "Replace 'if' expression with elvis expression" + + fun convert(element: KtIfExpression, editor: Editor?) { + val ifThenToSelectData = element.buildSelectTransformationData() ?: return + + val factory = KtPsiFactory(element) + val elvis = runWriteAction { + val replacedBaseClause = ifThenToSelectData.replacedBaseClause(factory) + val newExpr = element.replaced( + factory.createExpressionByPattern( + "$0 ?: $1", + replacedBaseClause, + ifThenToSelectData.negatedClause!! + ) + ) + KtPsiUtil.deparenthesize(newExpr) as KtBinaryExpression + } + + if (editor != null) { + elvis.inlineLeftSideIfApplicableWithPrompt(editor) + with(IfThenToSafeAccessInspection) { + (elvis.left as? KtSafeQualifiedExpression)?.renameLetParameter(editor) + } + } + } + + fun isApplicableTo(element: KtIfExpression, expressionShouldBeStable: Boolean): Boolean { + val ifThenToSelectData = element.buildSelectTransformationData() ?: return false + if (expressionShouldBeStable && !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false + + val type = element.getType(ifThenToSelectData.context) ?: return false + if (KotlinBuiltIns.isUnit(type)) return false + + return ifThenToSelectData.clausesReplaceableByElvis() } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/SmartCastImpossibleInIfThenFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/SmartCastImpossibleInIfThenFactory.kt index af49a6c62ab..2330ea105ba 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/SmartCastImpossibleInIfThenFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/SmartCastImpossibleInIfThenFactory.kt @@ -23,21 +23,18 @@ object SmartCastImpossibleInIfThenFactory : KotlinIntentionActionsFactory() { val ifExpression = element.getStrictParentOfType()?.parent as? KtIfExpression ?: return emptyList() - val ifThenToSafeAccess = IfThenToSafeAccessInspection(stableElementNeeded = false) - val ifThenToElvis = IfThenToElvisIntention(stableElementNeeded = false) - return listOf( createQuickFix( ifExpression, - { ifThenToSafeAccess.fixText(it) }, - { ifThenToSafeAccess.isApplicable(it) }, - { ifExpr, project, editor -> ifThenToSafeAccess.applyTo(ifExpr.ifKeyword, project, editor) } + { IfThenToSafeAccessInspection.fixTextFor(it) }, + { IfThenToSafeAccessInspection.isApplicableTo(it, expressionShouldBeStable = false) }, + { ifExpr, _, editor -> IfThenToSafeAccessInspection.convert(ifExpr, editor) } ), createQuickFix( ifExpression, - { ifThenToElvis.text }, - { ifThenToElvis.isApplicableTo(it) }, - { ifExpr, _, editor -> ifThenToElvis.applyTo(ifExpr, editor) } + { IfThenToElvisIntention.intentionText }, + { IfThenToElvisIntention.isApplicableTo(it, expressionShouldBeStable = false) }, + { ifExpr, _, editor -> IfThenToElvisIntention.convert(ifExpr, editor) } ) ) } @@ -49,7 +46,9 @@ object SmartCastImpossibleInIfThenFactory : KotlinIntentionActionsFactory() { applyTo: (KtIfExpression, project: Project, editor: Editor?) -> Unit ): KotlinQuickFixAction { return object : KotlinQuickFixAction(ifExpression) { - override fun getText() = fixText(ifExpression) + private val text = fixText(ifExpression) + + override fun getText() = text override fun getFamilyName() = text