[kotlin k2] Support K2 the "Invert 'if' condition" intention

^KTIJ-27469

GitOrigin-RevId: c8f7bcc4eb5264e29a4e05201fadc26dd9561998
This commit is contained in:
Andrey Cherkasov
2023-11-03 20:11:16 +00:00
committed by intellij-monorepo-bot
parent 1154f1a6ca
commit d5d5b86b21
7 changed files with 597 additions and 8 deletions
@@ -235,5 +235,12 @@
<bundleName>messages.KotlinBundle</bundleName>
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.k2.codeinsight.intentions.InvertIfConditionIntention</className>
<bundleName>messages.KotlinBundle</bundleName>
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
</extensions>
</idea-plugin>
@@ -23,12 +23,14 @@ internal class ConvertBinaryExpressionWithDemorgansLawIntention :
override fun apply(element: KtBinaryExpression, context: AnalysisActionContext<DemorgansLawContext>, 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 {
@@ -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, Context>(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<KtNamedFunction>(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<KtIfExpression> = 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<Context>, 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<KtExpression>()
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<KtExpression>() == 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<KtExpression>,
val demorgansLawContext: DemorgansLawContext?,
val isParentFunUnit: Boolean,
val commentSaver: CommentSaver,
)
@@ -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");
}
}
}
@@ -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<KtExpression>): DemorgansLawContext {
val pointers = operands.asReversed().map { operand ->
operand.safeAs<KtQualifiedExpression>()?.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
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.k2.codeinsight.intentions.InvertIfConditionIntention
@@ -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<AbstractK2GotoTestOrCodeActionTest> {