From 252399b9a9aeecc11a689c045295470c04d19608 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Tue, 28 Apr 2015 13:55:20 +0300 Subject: [PATCH] "Invert if condition" intention - smaller availability range + refactored --- .idea/dictionaries/valentin.xml | 1 + .../jetbrains/kotlin/psi/JetIfExpression.java | 5 + .../kotlin/idea/JetBundle.properties | 2 - .../intentions/InvertIfConditionIntention.kt | 217 +++++++----------- 4 files changed, 88 insertions(+), 137 deletions(-) diff --git a/.idea/dictionaries/valentin.xml b/.idea/dictionaries/valentin.xml index fd8f3810478..45c154aad82 100644 --- a/.idea/dictionaries/valentin.xml +++ b/.idea/dictionaries/valentin.xml @@ -4,6 +4,7 @@ funs initializers inserter + negatable pparent processings rbracket diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetIfExpression.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetIfExpression.java index b4f7517505e..c77b8b19e70 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetIfExpression.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetIfExpression.java @@ -62,4 +62,9 @@ public class JetIfExpression extends JetExpressionImpl { public PsiElement getElseKeyword() { return findChildByType(JetTokens.ELSE_KEYWORD); } + + @NotNull + public PsiElement getIfKeyword() { + return findChildByType(JetTokens.IF_KEYWORD); + } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties index 17b34f02001..2d95d68f798 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties @@ -324,8 +324,6 @@ make.type.explicit.in.lambda=Make types explicit in lambda make.type.explicit.in.lambda.family=Make Types Explicit In Lambda make.type.implicit.in.lambda=Make types implicit in lambda (may break code) make.type.implicit.in.lambda.family=Make Types Implicit In Lambda (May Break Code) -invert.if.condition=Invert If Condition -invert.if.condition.family=Invert If Condition replace.java.class.argument=Replace javaClass() with T::class replace.java.class.argument.family=Replace javaClass() with T::class diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/InvertIfConditionIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/InvertIfConditionIntention.kt index 083f40c3a5e..53828a3d44b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/InvertIfConditionIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/InvertIfConditionIntention.kt @@ -16,153 +16,100 @@ package org.jetbrains.kotlin.idea.intentions -import org.jetbrains.kotlin.psi.JetIfExpression import com.intellij.openapi.editor.Editor -import org.jetbrains.kotlin.psi.JetExpression -import org.jetbrains.kotlin.psi.JetUnaryExpression -import org.jetbrains.kotlin.psi.JetBinaryExpression -import org.jetbrains.kotlin.lexer.JetTokens -import org.jetbrains.kotlin.lexer.JetToken import com.intellij.psi.tree.IElementType -import org.jetbrains.kotlin.psi.JetConstantExpression -import org.jetbrains.kotlin.psi.JetPsiFactory -import org.jetbrains.kotlin.psi.JetPsiUtil -import org.jetbrains.kotlin.psi.JetParenthesizedExpression -import org.jetbrains.kotlin.lexer.JetSingleValueToken -import org.jetbrains.kotlin.lexer.JetKeywordToken -import org.jetbrains.kotlin.psi.JetNamedFunction -import org.jetbrains.kotlin.psi.JetBlockExpression +import org.jetbrains.kotlin.lexer.JetTokens +import org.jetbrains.kotlin.psi.* -public class InvertIfConditionIntention : JetSelfTargetingOffsetIndependentIntention("invert.if.condition", javaClass()) { - fun checkForNegation(element: JetUnaryExpression): Boolean { - return element.getOperationReference().getReferencedName().equals("!") - } - - override fun isApplicableTo(element: JetIfExpression): Boolean { - val condition = element.getCondition() - val thenBranch = element.getThen() - - return condition != null && thenBranch != null && when (condition) { - is JetUnaryExpression -> { - when { - checkForNegation(condition) -> { - val baseExpression = condition.getBaseExpression() - - when (baseExpression) { - is JetParenthesizedExpression -> baseExpression.getExpression() != null - else -> condition.getBaseExpression() != null - } - } - else -> true - } - } - is JetBinaryExpression -> { - condition.getOperationToken() != null && condition.getLeft() != null && condition.getRight() != null - } - else -> true - } +public class InvertIfConditionIntention : JetSelfTargetingIntention(javaClass(), "Invert 'if' condition") { + override fun isApplicableTo(element: JetIfExpression, caretOffset: Int): Boolean { + if (!element.getIfKeyword().getTextRange().containsOffset(caretOffset)) return false + return element.getCondition() != null && element.getThen() != null } override fun applyTo(element: JetIfExpression, editor: Editor) { val psiFactory = JetPsiFactory(element) - fun isNegatableOperator(token: IElementType): Boolean { - return token in array(JetTokens.EQEQ, JetTokens.EXCLEQ, JetTokens.EQEQEQ, JetTokens.EXCLEQEQEQ, JetTokens.IS_KEYWORD, JetTokens.NOT_IS, JetTokens.IN_KEYWORD, JetTokens.NOT_IN, JetTokens.LT, JetTokens.LTEQ, JetTokens.GT, JetTokens.GTEQ) - } - - fun getNegatedOperator(token: IElementType): JetToken { - return when { - token == JetTokens.EQEQ -> JetTokens.EXCLEQ - token == JetTokens.EXCLEQ -> JetTokens.EQEQ - token == JetTokens.EQEQEQ -> JetTokens.EXCLEQEQEQ - token == JetTokens.EXCLEQEQEQ -> JetTokens.EQEQEQ - token == JetTokens.IS_KEYWORD -> JetTokens.NOT_IS - token == JetTokens.NOT_IS -> JetTokens.IS_KEYWORD - token == JetTokens.IN_KEYWORD -> JetTokens.NOT_IN - token == JetTokens.NOT_IN -> JetTokens.IN_KEYWORD - token == JetTokens.LT -> JetTokens.GTEQ - token == JetTokens.LTEQ -> JetTokens.GT - token == JetTokens.GT -> JetTokens.LTEQ - token == JetTokens.GTEQ -> JetTokens.LT - else -> throw IllegalArgumentException("The token $token does not have a negated equivalent.") - } - } - - fun getTokenText(token: JetToken): String { - return when (token) { - is JetSingleValueToken -> token.getValue() - is JetKeywordToken -> token.getValue() - else -> throw IllegalArgumentException("The token $token does not have an applicable string value.") - } - } - - fun negateExpressionText(element: JetExpression): String { - val negatedParenthesizedExpressionText = "!(${element.getText()})" - val possibleNewExpression = psiFactory.createExpression(negatedParenthesizedExpressionText) as JetUnaryExpression - val innerExpression = possibleNewExpression.getBaseExpression() as JetParenthesizedExpression - - return when { - JetPsiUtil.areParenthesesUseless(innerExpression) -> "!${element.getText()}" - else -> negatedParenthesizedExpressionText - } - } - - fun getNegation(element: JetExpression): JetExpression { - return psiFactory.createExpression(when (element) { - is JetBinaryExpression -> { - val operator = element.getOperationToken()!! - - when { - isNegatableOperator(operator) -> "${element.getLeft()!!.getText()} ${getTokenText(getNegatedOperator(operator))} ${element.getRight()!!.getText()}" - else -> negateExpressionText(element) - } - } - is JetConstantExpression -> { - when { - element.textMatches("true") -> "false" - element.textMatches("false") -> "true" - else -> negateExpressionText(element) - } - } - else -> negateExpressionText(element) - }) - } - - fun removeNegation(element: JetUnaryExpression): JetExpression { - val baseExpression = element.getBaseExpression()!! - - return when (baseExpression) { - is JetParenthesizedExpression -> baseExpression.getExpression()!! - else -> baseExpression - } - } - - fun getFinalExpressionOfFunction(element: JetNamedFunction): JetExpression? { - val body = element.getBodyExpression() - - return when (body) { - is JetBlockExpression -> body.getStatements().last() as JetExpression - else -> body - } - } - val condition = element.getCondition()!! - val replacementCondition = when (condition) { - is JetUnaryExpression -> { - when { - checkForNegation(condition) -> removeNegation(condition) - else -> getNegation(condition) - } - } - else -> getNegation(condition) - } + val newCondition = negate(condition) val thenBranch = element.getThen() val elseBranch = element.getElse() ?: psiFactory.createEmptyBody() - element.replace(psiFactory.createIf(replacementCondition, when (elseBranch) { - is JetIfExpression -> psiFactory.wrapInABlock(elseBranch) - else -> elseBranch - }, if (thenBranch is JetBlockExpression && thenBranch.getStatements().isEmpty()) null else thenBranch)) + val newThen = if (elseBranch is JetIfExpression) + psiFactory.wrapInABlock(elseBranch) + else + elseBranch + + val newElse = if (thenBranch is JetBlockExpression && thenBranch.getStatements().isEmpty()) + null + else + thenBranch + + element.replace(psiFactory.createIf(newCondition, newThen, newElse)) + } + + companion object { + private val NEGATABLE_OPERATORS = setOf(JetTokens.EQEQ, JetTokens.EXCLEQ, JetTokens.EQEQEQ, + JetTokens.EXCLEQEQEQ, JetTokens.IS_KEYWORD, JetTokens.NOT_IS, JetTokens.IN_KEYWORD, + JetTokens.NOT_IN, JetTokens.LT, JetTokens.LTEQ, JetTokens.GT, JetTokens.GTEQ) + + private fun getNegatedOperatorText(token: IElementType): String { + return when(token) { + JetTokens.EQEQ -> JetTokens.EXCLEQ.getValue() + JetTokens.EXCLEQ -> JetTokens.EQEQ.getValue() + JetTokens.EQEQEQ -> JetTokens.EXCLEQEQEQ.getValue() + JetTokens.EXCLEQEQEQ -> JetTokens.EQEQEQ.getValue() + JetTokens.IS_KEYWORD -> JetTokens.NOT_IS.getValue() + JetTokens.NOT_IS -> JetTokens.IS_KEYWORD.getValue() + JetTokens.IN_KEYWORD -> JetTokens.NOT_IN.getValue() + JetTokens.NOT_IN -> JetTokens.IN_KEYWORD.getValue() + JetTokens.LT -> JetTokens.GTEQ.getValue() + JetTokens.LTEQ -> JetTokens.GT.getValue() + JetTokens.GT -> JetTokens.LTEQ.getValue() + JetTokens.GTEQ -> JetTokens.LT.getValue() + else -> throw IllegalArgumentException("The token $token does not have a negated equivalent.") + } + } + + private fun negate(expression: JetExpression): JetExpression { + val specialNegation = specialNegationText(expression) + if (specialNegation != null) return specialNegation + + val negationExpr = JetPsiFactory(expression).createExpression("!a") as JetPrefixExpression + negationExpr.getBaseExpression()!!.replace(expression) + return negationExpr + } + + private fun specialNegationText(expression: JetExpression): JetExpression? { + val factory = JetPsiFactory(expression) + when (expression) { + is JetPrefixExpression -> { + if (expression.getOperationReference().getReferencedName() == "!") { + val baseExpression = expression.getBaseExpression() + if (baseExpression != null) { + return JetPsiUtil.safeDeparenthesize(baseExpression) + } + } + } + + is JetBinaryExpression -> { + val operator = expression.getOperationToken() ?: return null + if (operator !in NEGATABLE_OPERATORS) return null + val left = expression.getLeft() ?: return null + val right = expression.getRight() ?: return null + return factory.createExpression(left.getText() + " " + getNegatedOperatorText(operator) + " " + right.getText()) + } + + is JetConstantExpression -> { + return when (expression.getText()) { + "true" -> factory.createExpression("false") + "false" -> factory.createExpression("true") + else -> null + } + } + } + return null + } } }