diff --git a/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions-k2.xml b/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions-k2.xml
index 5c52f9032136..ecc6bedb0bc7 100644
--- a/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions-k2.xml
+++ b/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions-k2.xml
@@ -235,5 +235,12 @@
messages.KotlinBundle
group.names.kotlin
+
+
+ kotlin
+ org.jetbrains.kotlin.idea.k2.codeinsight.intentions.InvertIfConditionIntention
+ messages.KotlinBundle
+ group.names.kotlin
+
diff --git a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertBinaryExpressionWithDemorgansLawIntention.kt b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertBinaryExpressionWithDemorgansLawIntention.kt
index a3c7a4215313..88d5cf3778dc 100644
--- a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertBinaryExpressionWithDemorgansLawIntention.kt
+++ b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertBinaryExpressionWithDemorgansLawIntention.kt
@@ -23,12 +23,14 @@ internal class ConvertBinaryExpressionWithDemorgansLawIntention :
override fun apply(element: KtBinaryExpression, context: AnalysisActionContext, updater: ModPsiUpdater) {
val expr = element.topmostBinaryExpression()
if (splitBooleanSequence(expr) == null) return
- applyDemorgansLaw(element, context.analyzeContext)
+ applyDemorgansLaw(expr, context.analyzeContext)
}
context(KtAnalysisSession)
override fun prepareContext(element: KtBinaryExpression): DemorgansLawContext? {
- return prepareDemorgansLawContext(element)
+ val operands = element.topmostBinaryExpression().let(::splitBooleanSequence) ?: return null
+ if (operands.any { !it.isBoolean }) return null
+ return prepareDemorgansLawContext(operands)
}
override fun getActionName(element: KtBinaryExpression, context: DemorgansLawContext): String {
diff --git a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/InvertIfConditionIntention.kt b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/InvertIfConditionIntention.kt
new file mode 100644
index 000000000000..5303a5bcd1f5
--- /dev/null
+++ b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/InvertIfConditionIntention.kt
@@ -0,0 +1,289 @@
+// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package org.jetbrains.kotlin.idea.k2.codeinsight.intentions
+
+import com.intellij.modcommand.ModPsiUpdater
+import com.intellij.psi.PsiComment
+import com.intellij.psi.PsiElement
+import com.intellij.psi.SmartPsiElementPointer
+import com.intellij.refactoring.suggested.createSmartPointer
+import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.idea.base.psi.getLineNumber
+import org.jetbrains.kotlin.idea.base.psi.replaced
+import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
+import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.AbstractKotlinModCommandWithContext
+import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.AnalysisActionContext
+import org.jetbrains.kotlin.idea.codeinsight.api.applicators.KotlinApplicabilityRange
+import org.jetbrains.kotlin.idea.codeinsight.api.applicators.applicabilityTarget
+import org.jetbrains.kotlin.idea.codeinsight.utils.*
+import org.jetbrains.kotlin.idea.util.CommentSaver
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.*
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
+import org.jetbrains.kotlin.utils.addToStdlib.lastIsInstanceOrNull
+
+internal class InvertIfConditionIntention : AbstractKotlinModCommandWithContext(KtIfExpression::class) {
+ override fun getFamilyName(): String = KotlinBundle.message("invert.if.condition")
+ override fun getActionName(element: KtIfExpression, context: Context): String = familyName
+
+ context(KtAnalysisSession)
+ override fun prepareContext(element: KtIfExpression): Context {
+ val rBrace = parentBlockRBrace(element)
+ val commentSavingRange = if (rBrace != null)
+ PsiChildRange(element, rBrace)
+ else
+ PsiChildRange.singleElement(element)
+
+ val commentSaver = CommentSaver(commentSavingRange)
+
+ val condition = element.condition!!
+ val areAllOperandsBoolean =
+ (condition is KtBinaryExpression && splitBooleanSequence(condition)?.all { it.isBoolean } == true) || condition.isBoolean
+ val newCondition = (condition as? KtQualifiedExpression)?.invertSelectorFunction() ?: condition.negate()
+
+ val isParentFunUnit = element.getParentOfType(true)
+ val isUnit = isParentFunUnit != null && isParentFunUnit.getReturnKtType().isUnit
+
+ val demorgansLawContext = if (areAllOperandsBoolean) {
+ getBinaryExpression(newCondition)?.let(::splitBooleanSequence)?.let { expressions ->
+ prepareDemorgansLawContext(expressions)
+ }
+ } else null
+
+ return Context(newCondition.createSmartPointer(), demorgansLawContext, isUnit, commentSaver)
+ }
+
+ private fun getBinaryExpression(expression: KtExpression): KtBinaryExpression? {
+ (expression as? KtPrefixExpression)?.let {
+ if (it.operationReference.getReferencedNameElementType() == KtTokens.EXCL) {
+ val binaryExpr = (it.baseExpression as? KtParenthesizedExpression)?.expression as? KtBinaryExpression
+ return binaryExpr?.topmostBinaryExpression()
+ }
+ }
+ return null
+ }
+
+ override fun getApplicabilityRange(): KotlinApplicabilityRange = applicabilityTarget { ifExpression: KtIfExpression ->
+ ifExpression.ifKeyword
+ }
+
+ override fun isApplicableByPsi(element: KtIfExpression): Boolean {
+ return element.condition != null && element.then != null
+ }
+
+ override fun apply(element: KtIfExpression, context: AnalysisActionContext, updater: ModPsiUpdater) {
+ val rBrace = parentBlockRBrace(element)
+ if (rBrace != null) element.nextEolCommentOnSameLine()?.delete()
+
+ val analyzeContext = context.analyzeContext
+ val newIf = handleSpecialCases(element, analyzeContext) ?: handleStandardCase(element, analyzeContext)
+
+ val commentRestoreRange = if (rBrace != null)
+ PsiChildRange(newIf, rBrace)
+ else
+ PsiChildRange(newIf, parentBlockRBrace(newIf) ?: newIf)
+
+ context.analyzeContext.commentSaver.restore(commentRestoreRange)
+
+ val binaryExpr = newIf.condition?.let(::getBinaryExpression)
+ if (binaryExpr != null) {
+ context.analyzeContext.demorgansLawContext?.let { demorgansLawContext ->
+ applyDemorgansLaw(binaryExpr, demorgansLawContext)
+ }
+ }
+
+ updater.moveTo(newIf)
+ }
+
+ private fun handleStandardCase(ifExpression: KtIfExpression, context: Context): KtIfExpression {
+ val psiFactory = KtPsiFactory(ifExpression.project)
+
+ val thenBranch = ifExpression.then!!
+ val elseBranch = ifExpression.`else` ?: psiFactory.createEmptyBody()
+
+ val newThen = if (elseBranch is KtIfExpression)
+ psiFactory.createSingleStatementBlock(elseBranch)
+ else
+ elseBranch
+
+ val newElse = if (thenBranch is KtBlockExpression && thenBranch.statements.isEmpty())
+ null
+ else
+ thenBranch
+
+ val conditionLineNumber = ifExpression.condition?.getLineNumber(false)
+ val thenBranchLineNumber = thenBranch.getLineNumber(false)
+ val elseKeywordLineNumber = ifExpression.elseKeyword?.getLineNumber()
+ val afterCondition = if (newThen !is KtBlockExpression && elseKeywordLineNumber != elseBranch.getLineNumber(false)) "\n" else ""
+ val beforeElse = if (newThen !is KtBlockExpression && conditionLineNumber != elseKeywordLineNumber) "\n" else " "
+ val afterElse = if (newElse !is KtBlockExpression && conditionLineNumber != thenBranchLineNumber) "\n" else " "
+
+ val newCondition = context.newCondition.element!!
+ val newIf = if (newElse == null) {
+ psiFactory.createExpressionByPattern("if ($0)$afterCondition$1", newCondition, newThen)
+ } else {
+ psiFactory.createExpressionByPattern("if ($0)$afterCondition$1${beforeElse}else$afterElse$2", newCondition, newThen, newElse)
+ } as KtIfExpression
+
+ return ifExpression.replaced(newIf)
+ }
+
+ private fun handleSpecialCases(ifExpression: KtIfExpression, context: Context): KtIfExpression? {
+ val elseBranch = ifExpression.`else`
+ if (elseBranch != null) return null
+
+ val psiFactory = KtPsiFactory(ifExpression.project)
+
+ val thenBranch = ifExpression.then!!
+ val lastThenStatement = thenBranch.lastBlockStatementOrThis()
+ val newCondition = context.newCondition.element!!
+ if (lastThenStatement.isExitStatement()) {
+ val block = ifExpression.parent as? KtBlockExpression
+ if (block != null) {
+ val rBrace = block.rBrace
+ val afterIfInBlock = ifExpression.siblings(withItself = false).takeWhile { it != rBrace }.toList()
+ val lastStatementInBlock = afterIfInBlock.lastIsInstanceOrNull()
+ if (lastStatementInBlock != null) {
+ val exitStatementAfterIf = if (lastStatementInBlock.isExitStatement())
+ lastStatementInBlock
+ else
+ exitStatementExecutedAfter(lastStatementInBlock, context)
+ if (exitStatementAfterIf != null) {
+ val first = afterIfInBlock.first()
+ val last = afterIfInBlock.last()
+ // build new then branch from statements after if (we will add exit statement if necessary later)
+ val newThenRange = if (isEmptyReturn(lastThenStatement) && isEmptyReturn(lastStatementInBlock)) {
+ PsiChildRange(first, lastStatementInBlock.prevSibling).trimWhiteSpaces()
+ } else {
+ PsiChildRange(first, last).trimWhiteSpaces()
+ }
+ val newIf =
+ psiFactory.createExpressionByPattern("if ($0) { $1 }", newCondition, newThenRange) as KtIfExpression
+
+ // remove statements after if as they are moving under if
+ block.deleteChildRange(first, last)
+
+ if (isEmptyReturn(lastThenStatement)) {
+ if (block.parent is KtDeclarationWithBody && block.parent !is KtFunctionLiteral) {
+ lastThenStatement.delete()
+ }
+ }
+ val updatedIf = copyThenBranchAfter(ifExpression)
+
+ // check if we need to add exit statement to then branch
+ if (exitStatementAfterIf != lastStatementInBlock) {
+ // don't insert the exit statement, if the new if statement placement has the same exit statement executed after it
+ val exitAfterNewIf = exitStatementExecutedAfter(updatedIf, context)
+ if (exitAfterNewIf == null || !matches(exitAfterNewIf, exitStatementAfterIf)) {
+ val newThen = newIf.then as KtBlockExpression
+ newThen.addBefore(exitStatementAfterIf, newThen.rBrace)
+ }
+ }
+
+ return updatedIf.replace(newIf) as KtIfExpression
+ }
+ }
+ }
+ }
+
+ val exitStatement = exitStatementExecutedAfter(ifExpression, context) ?: return null
+
+ val updatedIf = copyThenBranchAfter(ifExpression)
+ val newIf = psiFactory.createExpressionByPattern("if ($0) $1", newCondition, exitStatement)
+ return updatedIf.replace(newIf) as KtIfExpression
+ }
+
+ private fun matches(exitExpr1: KtExpression, exitExpr2: KtExpression): Boolean {
+ return if (exitExpr1 is KtReturnExpression && exitExpr2 is KtReturnExpression) {
+ return exitExpr1 == exitExpr2 || (exitExpr1.returnedExpression == null && exitExpr2.returnedExpression == null)
+ } else exitExpr1.javaClass == exitExpr2.javaClass
+ }
+
+ private fun isEmptyReturn(statement: KtExpression) =
+ statement is KtReturnExpression && statement.returnedExpression == null && statement.labeledExpression == null
+
+ private fun copyThenBranchAfter(ifExpression: KtIfExpression): KtIfExpression {
+ val psiFactory = KtPsiFactory(ifExpression.project)
+ val thenBranch = ifExpression.then ?: return ifExpression
+
+ val parent = ifExpression.parent
+ if (parent !is KtBlockExpression) {
+ assert(parent is KtContainerNode)
+ val block = psiFactory.createEmptyBody()
+ block.addAfter(ifExpression, block.lBrace)
+ val newBlock = ifExpression.replaced(block)
+ val newIf = newBlock.statements.single() as KtIfExpression
+ return copyThenBranchAfter(newIf)
+ }
+
+ if (thenBranch is KtBlockExpression) {
+ (thenBranch.statements.lastOrNull() as? KtContinueExpression)?.delete()
+ val range = thenBranch.contentRange()
+ if (!range.isEmpty) {
+ parent.addRangeAfter(range.first, range.last, ifExpression)
+ parent.addAfter(psiFactory.createNewLine(), ifExpression)
+ }
+ } else if (thenBranch !is KtContinueExpression) {
+ parent.addAfter(thenBranch, ifExpression)
+ parent.addAfter(psiFactory.createNewLine(), ifExpression)
+ }
+ return ifExpression
+ }
+
+ private fun exitStatementExecutedAfter(expression: KtExpression, context: Context): KtExpression? {
+ when (val parent = expression.parent) {
+ is KtBlockExpression -> {
+ val lastStatement = parent.statements.last()
+ return if (expression == lastStatement) {
+ exitStatementExecutedAfter(parent, context)
+ } else if (lastStatement.isExitStatement() &&
+ expression.siblings(withItself = false).firstIsInstance() == lastStatement
+ ) {
+ lastStatement
+ } else {
+ null
+ }
+ }
+ is KtNamedFunction -> {
+ if (parent.bodyExpression == expression && parent.hasBlockBody() && context.isParentFunUnit) {
+ return KtPsiFactory(expression.project).createExpression("return")
+ }
+ }
+
+ is KtContainerNode -> when (val pparent = parent.parent) {
+ is KtLoopExpression -> {
+ if (expression == pparent.body) {
+ return KtPsiFactory(expression.project).createExpression("continue")
+ }
+ }
+
+ is KtIfExpression -> {
+ if (expression == pparent.then || expression == pparent.`else`) {
+ return exitStatementExecutedAfter(pparent, context)
+ }
+ }
+ }
+ }
+ return null
+ }
+
+ private fun KtExpression.isExitStatement(): Boolean = when (this) {
+ is KtContinueExpression, is KtBreakExpression, is KtThrowExpression, is KtReturnExpression -> true
+ else -> false
+ }
+
+ private fun parentBlockRBrace(element: KtIfExpression): PsiElement? = (element.parent as? KtBlockExpression)?.rBrace
+
+ private fun KtIfExpression.nextEolCommentOnSameLine(): PsiElement? = getLineNumber(false).let { lastLineNumber ->
+ siblings(withItself = false)
+ .takeWhile { it.getLineNumber() == lastLineNumber }
+ .firstOrNull { it is PsiComment && it.node.elementType == KtTokens.EOL_COMMENT }
+ }
+}
+
+internal class Context(
+ val newCondition: SmartPsiElementPointer,
+ val demorgansLawContext: DemorgansLawContext?,
+ val isParentFunUnit: Boolean,
+ val commentSaver: CommentSaver,
+)
diff --git a/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java b/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java
index 2cd88b869637..b30f413c3ad2 100644
--- a/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java
+++ b/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java
@@ -4676,4 +4676,297 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest {
runTest("../../../idea/tests/testData/intentions/convertBinaryExpressionWithDemorgansLaw/retainedParens.kt");
}
}
+
+ @RunWith(JUnit3RunnerWithInners.class)
+ @TestMetadata("../../../idea/tests/testData/intentions/invertIfCondition")
+ public static class InvertIfCondition extends AbstractK2IntentionTest {
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
+ }
+
+ @TestMetadata("addSurroundingBlock_preserveComments.kt")
+ public void testAddSurroundingBlock_preserveComments() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/addSurroundingBlock_preserveComments.kt");
+ }
+
+ @TestMetadata("assignedToValue.kt")
+ public void testAssignedToValue() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/assignedToValue.kt");
+ }
+
+ @TestMetadata("binaryExpression.kt")
+ public void testBinaryExpression() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/binaryExpression.kt");
+ }
+
+ @TestMetadata("booleanLiteral.kt")
+ public void testBooleanLiteral() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/booleanLiteral.kt");
+ }
+
+ @TestMetadata("branchingIfStatements.kt")
+ public void testBranchingIfStatements() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/branchingIfStatements.kt");
+ }
+
+ @TestMetadata("endOfLineCommentBug.kt")
+ public void testEndOfLineCommentBug() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/endOfLineCommentBug.kt");
+ }
+
+ @TestMetadata("forLoopWithMultipleExpressions.kt")
+ public void testForLoopWithMultipleExpressions() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/forLoopWithMultipleExpressions.kt");
+ }
+
+ @TestMetadata("functionWithReturnExpression.kt")
+ public void testFunctionWithReturnExpression() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/functionWithReturnExpression.kt");
+ }
+
+ @TestMetadata("ifExpressionInsideForLoop.kt")
+ public void testIfExpressionInsideForLoop() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/ifExpressionInsideForLoop.kt");
+ }
+
+ @TestMetadata("ifExpressionWithReturn.kt")
+ public void testIfExpressionWithReturn() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/ifExpressionWithReturn.kt");
+ }
+
+ @TestMetadata("ifThenReturn.kt")
+ public void testIfThenReturn() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/ifThenReturn.kt");
+ }
+
+ @TestMetadata("ifThenReturn2.kt")
+ public void testIfThenReturn2() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/ifThenReturn2.kt");
+ }
+
+ @TestMetadata("ifThenReturn3.kt")
+ public void testIfThenReturn3() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/ifThenReturn3.kt");
+ }
+
+ @TestMetadata("ifThenReturn4.kt")
+ public void testIfThenReturn4() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/ifThenReturn4.kt");
+ }
+
+ @TestMetadata("ifWithBothBranchesReturn.kt")
+ public void testIfWithBothBranchesReturn() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/ifWithBothBranchesReturn.kt");
+ }
+
+ @TestMetadata("ifWithBothBranchesSetter.kt")
+ public void testIfWithBothBranchesSetter() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/ifWithBothBranchesSetter.kt");
+ }
+
+ @TestMetadata("in.kt")
+ public void testIn() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/in.kt");
+ }
+
+ @TestMetadata("invertableOperator.kt")
+ public void testInvertableOperator() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/invertableOperator.kt");
+ }
+
+ @TestMetadata("is.kt")
+ public void testIs() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/is.kt");
+ }
+
+ @TestMetadata("isBlank.kt")
+ public void testIsBlank() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/isBlank.kt");
+ }
+
+ @TestMetadata("isEmpty.kt")
+ public void testIsEmpty() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/isEmpty.kt");
+ }
+
+ @TestMetadata("isNotBlank.kt")
+ public void testIsNotBlank() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/isNotBlank.kt");
+ }
+
+ @TestMetadata("isNotEmpty.kt")
+ public void testIsNotEmpty() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/isNotEmpty.kt");
+ }
+
+ @TestMetadata("lambdaNonLocalAndLocalReturn.kt")
+ public void testLambdaNonLocalAndLocalReturn() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lambdaNonLocalAndLocalReturn.kt");
+ }
+
+ @TestMetadata("lambdaNonLocalReturn.kt")
+ public void testLambdaNonLocalReturn() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lambdaNonLocalReturn.kt");
+ }
+
+ @TestMetadata("lastStatement1.kt")
+ public void testLastStatement1() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lastStatement1.kt");
+ }
+
+ @TestMetadata("lastStatement2.kt")
+ public void testLastStatement2() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lastStatement2.kt");
+ }
+
+ @TestMetadata("lastStatement3.kt")
+ public void testLastStatement3() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lastStatement3.kt");
+ }
+
+ @TestMetadata("lastStatementBeforeBreak.kt")
+ public void testLastStatementBeforeBreak() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lastStatementBeforeBreak.kt");
+ }
+
+ @TestMetadata("lastStatementBeforeContinue.kt")
+ public void testLastStatementBeforeContinue() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lastStatementBeforeContinue.kt");
+ }
+
+ @TestMetadata("lastStatementBeforeReturn.kt")
+ public void testLastStatementBeforeReturn() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lastStatementBeforeReturn.kt");
+ }
+
+ @TestMetadata("lastStatementInLambda.kt")
+ public void testLastStatementInLambda() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lastStatementInLambda.kt");
+ }
+
+ @TestMetadata("lastStatementInLoop.kt")
+ public void testLastStatementInLoop() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lastStatementInLoop.kt");
+ }
+
+ @TestMetadata("lastStatementInLoop2.kt")
+ public void testLastStatementInLoop2() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lastStatementInLoop2.kt");
+ }
+
+ @TestMetadata("lastStatementNonUnitMethod.kt")
+ public void testLastStatementNonUnitMethod() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/lastStatementNonUnitMethod.kt");
+ }
+
+ @TestMetadata("negatedExpression.kt")
+ public void testNegatedExpression() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/negatedExpression.kt");
+ }
+
+ @TestMetadata("negatedIsBlank.kt")
+ public void testNegatedIsBlank() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/negatedIsBlank.kt");
+ }
+
+ @TestMetadata("negatedIsEmpty.kt")
+ public void testNegatedIsEmpty() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/negatedIsEmpty.kt");
+ }
+
+ @TestMetadata("negatedIsNotBlank.kt")
+ public void testNegatedIsNotBlank() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/negatedIsNotBlank.kt");
+ }
+
+ @TestMetadata("negatedIsNotEmpty.kt")
+ public void testNegatedIsNotEmpty() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/negatedIsNotEmpty.kt");
+ }
+
+ @TestMetadata("nestedIfWithReturn.kt")
+ public void testNestedIfWithReturn() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/nestedIfWithReturn.kt");
+ }
+
+ @TestMetadata("notBlock.kt")
+ public void testNotBlock() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/notBlock.kt");
+ }
+
+ @TestMetadata("notBlock2.kt")
+ public void testNotBlock2() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/notBlock2.kt");
+ }
+
+ @TestMetadata("notBlock3.kt")
+ public void testNotBlock3() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/notBlock3.kt");
+ }
+
+ @TestMetadata("notBlock4.kt")
+ public void testNotBlock4() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/notBlock4.kt");
+ }
+
+ @TestMetadata("notBlock5.kt")
+ public void testNotBlock5() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/notBlock5.kt");
+ }
+
+ @TestMetadata("notBlock6.kt")
+ public void testNotBlock6() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/notBlock6.kt");
+ }
+
+ @TestMetadata("notBlock7.kt")
+ public void testNotBlock7() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/notBlock7.kt");
+ }
+
+ @TestMetadata("notBlock8.kt")
+ public void testNotBlock8() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/notBlock8.kt");
+ }
+
+ @TestMetadata("notIn.kt")
+ public void testNotIn() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/notIn.kt");
+ }
+
+ @TestMetadata("notIs.kt")
+ public void testNotIs() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/notIs.kt");
+ }
+
+ @TestMetadata("returnIfExpression.kt")
+ public void testReturnIfExpression() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/returnIfExpression.kt");
+ }
+
+ @TestMetadata("simple.kt")
+ public void testSimple() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/simple.kt");
+ }
+
+ @TestMetadata("unnecessaryContinue.kt")
+ public void testUnnecessaryContinue() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/unnecessaryContinue.kt");
+ }
+
+ @TestMetadata("unnecessaryContinue2.kt")
+ public void testUnnecessaryContinue2() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/unnecessaryContinue2.kt");
+ }
+
+ @TestMetadata("unnecessaryContinue3.kt")
+ public void testUnnecessaryContinue3() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/unnecessaryContinue3.kt");
+ }
+
+ @TestMetadata("valueAndReturnBranches.kt")
+ public void testValueAndReturnBranches() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/invertIfCondition/valueAndReturnBranches.kt");
+ }
+ }
}
diff --git a/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/DemorgansLawUtils.kt b/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/DemorgansLawUtils.kt
index caa8ba3b0112..3aeaeaaa5cb1 100644
--- a/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/DemorgansLawUtils.kt
+++ b/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/DemorgansLawUtils.kt
@@ -14,18 +14,14 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
context(KtAnalysisSession)
@OptIn(UnsafeCastFunction::class)
-fun prepareDemorgansLawContext(element: KtBinaryExpression): DemorgansLawContext? {
- val expr = element.topmostBinaryExpression()
- val operands = splitBooleanSequence(expr) ?: return null
- if (!expr.left.isBoolean || !expr.right.isBoolean) return null
+fun prepareDemorgansLawContext(operands: List): DemorgansLawContext {
val pointers = operands.asReversed().map { operand ->
operand.safeAs()?.invertSelectorFunction() ?: operand.negate(false) { it.isBoolean }
}.map { it.createSmartPointer() }
return DemorgansLawContext(pointers)
}
-fun applyDemorgansLaw(element: KtBinaryExpression, context: DemorgansLawContext) {
- val expression = element.topmostBinaryExpression()
+fun applyDemorgansLaw(expression: KtBinaryExpression, context: DemorgansLawContext) {
val operatorText = when (expression.operationToken) {
KtTokens.ANDAND -> KtTokens.OROR.value
KtTokens.OROR -> KtTokens.ANDAND.value
diff --git a/plugins/kotlin/idea/tests/testData/intentions/invertIfCondition/.k2Intention b/plugins/kotlin/idea/tests/testData/intentions/invertIfCondition/.k2Intention
new file mode 100644
index 000000000000..452ef239015d
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/intentions/invertIfCondition/.k2Intention
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.k2.codeinsight.intentions.InvertIfConditionIntention
diff --git a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt
index ee61adeda933..a6fb4e31f95b 100644
--- a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt
+++ b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt
@@ -48,6 +48,7 @@ internal fun MutableTWorkspace.generateK2IntentionTests() {
model("${idea}intentions/branched/ifWhen/whenToIf", pattern = pattern)
model("code-insight/intentions-k2/tests/testData/intentions", pattern = pattern)
model("${idea}intentions/convertBinaryExpressionWithDemorgansLaw", pattern = pattern)
+ model("${idea}intentions/invertIfCondition", pattern = pattern)
}
testClass {