Add an intention to simplify negated binary expressions

Given an expression of the form `!(a op b)`, replace it with `a !op b`.

For example:

    !(a < b)    -> a >= b
    !(a is Int) -> a !is int
This commit is contained in:
Steven Allen
2014-03-19 12:10:28 -04:00
parent 626ec6519c
commit d198465522
31 changed files with 283 additions and 1 deletions
@@ -376,6 +376,7 @@ fun main(args: Array<String>) {
model("intentions/attributeCallReplacements/replaceBinaryInfixIntention", testMethod = "doTestReplaceBinaryInfixIntention")
model("intentions/attributeCallReplacements/replaceUnaryPrefixIntention", testMethod = "doTestReplaceUnaryPrefixIntention")
model("intentions/attributeCallReplacements/replaceInvokeIntention", testMethod = "doTestReplaceInvokeIntention")
model("intentions/simplifyNegatedBinaryExpressionIntention", testMethod = "doTestSimplifyNegatedBinaryExpressionIntention")
}
testClass(javaClass<AbstractHierarchyTest>()) {
@@ -0,0 +1,3 @@
if (a != b) {
println("Not Equal")
}
@@ -0,0 +1,3 @@
if (<spot>!(a == b)</spot>) {
println("Not Equal")
}
@@ -0,0 +1,7 @@
<html>
<body>
This intention simplifies negated binary expressions by replacing expressions such as
!(<b>a</b> <small>op</small> <b>b</b>) with <b>a</b> <small>negop</small> <b>b</b>
(where <small>op</small> and <small>negop</small> are inverse comparison operators like <b>==</b> and <b>!=</b> or <b>in</b> and <b>!in</b>).
</body>
</html>
+5
View File
@@ -533,6 +533,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.jet.plugin.intentions.SimplifyNegatedBinaryExpressionIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.jet.plugin.intentions.RemoveUnnecessaryParenthesesIntention</className>
<category>Kotlin</category>
@@ -81,6 +81,8 @@ remove.parameter=Remove parameter ''{0}''
change.signature.family=Change signature of function/constructor
cast.expression.to.type=Cast expression ''{0}'' to ''{1}''
cast.expression.family=Cast Expression
simplify.negated.binary.expression=Simplify negated ''{0}'' expression to ''{1}''
simplify.negated.binary.expression.family=Simplify Negated Binary Expression
replace.call.error.skipped.defaults=Cannot skip default arguments.
replace.call.error.undefined.returntype=Unable to determine the return type.
@@ -0,0 +1,95 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.plugin.intentions
import org.jetbrains.jet.lang.psi.JetPrefixExpression
import org.jetbrains.jet.lang.psi.JetParenthesizedExpression
import org.jetbrains.jet.lexer.JetTokens
import org.jetbrains.jet.lang.psi.JetBinaryExpression
import org.jetbrains.jet.lang.psi.JetPsiFactory
import com.intellij.openapi.editor.Editor
import org.jetbrains.jet.lang.psi.JetOperationExpression
import org.jetbrains.jet.lang.psi.JetIsExpression
import org.jetbrains.jet.lexer.JetToken
import org.jetbrains.jet.lang.psi.JetExpression
import org.jetbrains.jet.lang.psi.JetPsiUtil
import org.jetbrains.jet.lexer.JetSingleValueToken
import org.jetbrains.jet.plugin.JetBundle
public class SimplifyNegatedBinaryExpressionIntention : JetSelfTargetingIntention<JetPrefixExpression>("simplify.negated.binary.expression", javaClass()) {
private fun JetPrefixExpression.unparenthesize(): JetExpression? {
return (this.getBaseExpression() as? JetParenthesizedExpression)?.getExpression()
}
public fun JetToken.negate(): JetSingleValueToken? = when (this) {
JetTokens.IN_KEYWORD -> JetTokens.NOT_IN
JetTokens.NOT_IN -> JetTokens.IN_KEYWORD
JetTokens.IS_KEYWORD -> JetTokens.NOT_IS
JetTokens.NOT_IS -> JetTokens.IS_KEYWORD
JetTokens.EQEQ -> JetTokens.EXCLEQ
JetTokens.EXCLEQ -> JetTokens.EQEQ
JetTokens.LT -> JetTokens.GTEQ
JetTokens.GTEQ -> JetTokens.LT
JetTokens.GT -> JetTokens.LTEQ
JetTokens.LTEQ -> JetTokens.GT
else -> null
}
override fun isApplicableTo(element: JetPrefixExpression): Boolean {
if (element.getOperationReference().getReferencedNameElementType() != JetTokens.EXCL) return false
val expression = element.unparenthesize() as? JetOperationExpression
if (!(expression is JetIsExpression || expression is JetBinaryExpression)) return false
val operation = (JetPsiUtil.getOperationToken(expression) as? JetSingleValueToken) ?: return false
val negOperation = operation.negate() ?: return false
setText(JetBundle.message(key, operation.getValue(), negOperation.getValue()))
return true
}
override fun applyTo(element: JetPrefixExpression, editor: Editor) {
// Guaranteed to succeed (by isApplicableTo)
val expression = element.unparenthesize()!!
val invertedOperation = JetPsiUtil.getOperationToken(expression as JetOperationExpression)!!.negate()!!
element.replace(
when (expression) {
is JetIsExpression -> JetPsiFactory.createExpression(
expression.getProject(),
"${expression.getLeftHandSide().getText() ?: ""} ${invertedOperation.getValue()} ${expression.getTypeRef()?.getText() ?: ""}"
)
is JetBinaryExpression -> JetPsiFactory.createBinaryExpression(
expression.getProject(),
expression.getLeft(),
invertedOperation.getValue(),
expression.getRight()
)
else -> throw IllegalStateException(
"Expression is neither a JetIsExpression or JetBinaryExpression (checked by isApplicableTo): ${expression.getText()}"
)
}
)
}
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '==' expression to '!='
fun test(n: Int) {
!(0<caret> == 1)
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '==' expression to '!='
fun test(n: Int) {
0 != 1
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '>' expression to '<='
fun test(n: Int) {
!(0<caret> > 1)
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '>' expression to '<='
fun test(n: Int) {
0 <= 1
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '>=' expression to '<'
fun test(n: Int) {
!(0<caret> >= 1)
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '>=' expression to '<'
fun test(n: Int) {
0 < 1
}
@@ -0,0 +1,5 @@
// INTENTION_TEXT: Simplify negated 'in' expression to '!in'
fun test(n: Int) {
val arr = ArrayList<Int>(1)
!(0<caret> in arr)
}
@@ -0,0 +1,5 @@
// INTENTION_TEXT: Simplify negated 'in' expression to '!in'
fun test(n: Int) {
val arr = ArrayList<Int>(1)
0 !in arr
}
@@ -0,0 +1,5 @@
// IS_APPLICABLE: false
fun lt(a: Int, b: Int): Boolean = a < b
fun test(n: Int) {
!(1<caret> lt 2)
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated 'is' expression to '!is'
fun test(n: Int) {
!(0<caret> is Int)
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated 'is' expression to '!is'
fun test(n: Int) {
0 !is Int
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '<' expression to '>='
fun test(n: Int) {
!(0<caret> < 1)
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '<' expression to '>='
fun test(n: Int) {
0 >= 1
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '<=' expression to '>'
fun test(n: Int) {
!(0<caret> <= 1)
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '<=' expression to '>'
fun test(n: Int) {
0 > 1
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '!=' expression to '=='
fun test(n: Int) {
!(0<caret> != 1)
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '!=' expression to '=='
fun test(n: Int) {
0 == 1
}
@@ -0,0 +1,5 @@
// INTENTION_TEXT: Simplify negated '!in' expression to 'in'
fun test(n: Int) {
val arr = ArrayList<Int>(1)
!(0<caret> !in arr)
}
@@ -0,0 +1,5 @@
// INTENTION_TEXT: Simplify negated '!in' expression to 'in'
fun test(n: Int) {
val arr = ArrayList<Int>(1)
0 in arr
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '!is' expression to 'is'
fun test(n: Int) {
!(0<caret> !is Int)
}
@@ -0,0 +1,4 @@
// INTENTION_TEXT: Simplify negated '!is' expression to 'is'
fun test(n: Int) {
0 is Int
}
@@ -0,0 +1,4 @@
// IS_APPLICABLE: false
fun test(n: Int) {
!<caret>true
}
@@ -195,6 +195,10 @@ public abstract class AbstractCodeTransformationTest extends LightCodeInsightTes
doTestIntention(path, new TestableReplaceInvokeIntention());
}
public void doTestSimplifyNegatedBinaryExpressionIntention(@NotNull String path) throws Exception {
doTestIntention(path, new SimplifyNegatedBinaryExpressionIntention());
}
private void doTestIntention(@NotNull String path, @NotNull IntentionAction intentionAction) throws Exception {
configureByFile(path);
@@ -30,7 +30,7 @@ import org.jetbrains.jet.plugin.intentions.AbstractCodeTransformationTest;
/** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@InnerTestClasses({CodeTransformationTestGenerated.ElvisToIfThen.class, CodeTransformationTestGenerated.IfThenToElvis.class, CodeTransformationTestGenerated.SafeAccessToIfThen.class, CodeTransformationTestGenerated.IfThenToSafeAccess.class, CodeTransformationTestGenerated.IfToAssignment.class, CodeTransformationTestGenerated.IfToReturn.class, CodeTransformationTestGenerated.IfToReturnAsymmetrically.class, CodeTransformationTestGenerated.WhenToAssignment.class, CodeTransformationTestGenerated.WhenToReturn.class, CodeTransformationTestGenerated.AssignmentToIf.class, CodeTransformationTestGenerated.AssignmentToWhen.class, CodeTransformationTestGenerated.PropertyToIf.class, CodeTransformationTestGenerated.PropertyToWhen.class, CodeTransformationTestGenerated.ReturnToIf.class, CodeTransformationTestGenerated.ReturnToWhen.class, CodeTransformationTestGenerated.IfToWhen.class, CodeTransformationTestGenerated.WhenToIf.class, CodeTransformationTestGenerated.Flatten.class, CodeTransformationTestGenerated.Merge.class, CodeTransformationTestGenerated.IntroduceSubject.class, CodeTransformationTestGenerated.EliminateSubject.class, CodeTransformationTestGenerated.Split.class, CodeTransformationTestGenerated.Join.class, CodeTransformationTestGenerated.ConvertMemberToExtension.class, CodeTransformationTestGenerated.ReconstructedType.class, CodeTransformationTestGenerated.RemoveUnnecessaryParentheses.class, CodeTransformationTestGenerated.ReplaceWithDotQualifiedMethodCall.class, CodeTransformationTestGenerated.ReplaceWithInfixFunctionCall.class, CodeTransformationTestGenerated.RemoveCurlyBracesFromTemplate.class, CodeTransformationTestGenerated.MoveLambdaInsideParentheses.class, CodeTransformationTestGenerated.MoveLambdaOutsideParentheses.class, CodeTransformationTestGenerated.ReplaceExplicitFunctionLiteralParamWithIt.class, CodeTransformationTestGenerated.ReplaceItWithExplicitFunctionLiteralParam.class, CodeTransformationTestGenerated.RemoveBraces.class, CodeTransformationTestGenerated.AddBraces.class, CodeTransformationTestGenerated.ReplaceGetIntention.class, CodeTransformationTestGenerated.ReplaceContainsIntention.class, CodeTransformationTestGenerated.ReplaceBinaryInfixIntention.class, CodeTransformationTestGenerated.ReplaceUnaryPrefixIntention.class, CodeTransformationTestGenerated.ReplaceInvokeIntention.class})
@InnerTestClasses({CodeTransformationTestGenerated.ElvisToIfThen.class, CodeTransformationTestGenerated.IfThenToElvis.class, CodeTransformationTestGenerated.SafeAccessToIfThen.class, CodeTransformationTestGenerated.IfThenToSafeAccess.class, CodeTransformationTestGenerated.IfToAssignment.class, CodeTransformationTestGenerated.IfToReturn.class, CodeTransformationTestGenerated.IfToReturnAsymmetrically.class, CodeTransformationTestGenerated.WhenToAssignment.class, CodeTransformationTestGenerated.WhenToReturn.class, CodeTransformationTestGenerated.AssignmentToIf.class, CodeTransformationTestGenerated.AssignmentToWhen.class, CodeTransformationTestGenerated.PropertyToIf.class, CodeTransformationTestGenerated.PropertyToWhen.class, CodeTransformationTestGenerated.ReturnToIf.class, CodeTransformationTestGenerated.ReturnToWhen.class, CodeTransformationTestGenerated.IfToWhen.class, CodeTransformationTestGenerated.WhenToIf.class, CodeTransformationTestGenerated.Flatten.class, CodeTransformationTestGenerated.Merge.class, CodeTransformationTestGenerated.IntroduceSubject.class, CodeTransformationTestGenerated.EliminateSubject.class, CodeTransformationTestGenerated.Split.class, CodeTransformationTestGenerated.Join.class, CodeTransformationTestGenerated.ConvertMemberToExtension.class, CodeTransformationTestGenerated.ReconstructedType.class, CodeTransformationTestGenerated.RemoveUnnecessaryParentheses.class, CodeTransformationTestGenerated.ReplaceWithDotQualifiedMethodCall.class, CodeTransformationTestGenerated.ReplaceWithInfixFunctionCall.class, CodeTransformationTestGenerated.RemoveCurlyBracesFromTemplate.class, CodeTransformationTestGenerated.MoveLambdaInsideParentheses.class, CodeTransformationTestGenerated.MoveLambdaOutsideParentheses.class, CodeTransformationTestGenerated.ReplaceExplicitFunctionLiteralParamWithIt.class, CodeTransformationTestGenerated.ReplaceItWithExplicitFunctionLiteralParam.class, CodeTransformationTestGenerated.RemoveBraces.class, CodeTransformationTestGenerated.AddBraces.class, CodeTransformationTestGenerated.ReplaceGetIntention.class, CodeTransformationTestGenerated.ReplaceContainsIntention.class, CodeTransformationTestGenerated.ReplaceBinaryInfixIntention.class, CodeTransformationTestGenerated.ReplaceUnaryPrefixIntention.class, CodeTransformationTestGenerated.ReplaceInvokeIntention.class, CodeTransformationTestGenerated.SimplifyNegatedBinaryExpressionIntention.class})
public class CodeTransformationTestGenerated extends AbstractCodeTransformationTest {
@TestMetadata("idea/testData/intentions/branched/elvisToIfThen")
public static class ElvisToIfThen extends AbstractCodeTransformationTest {
@@ -2602,6 +2602,74 @@ public class CodeTransformationTestGenerated extends AbstractCodeTransformationT
}
@TestMetadata("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention")
public static class SimplifyNegatedBinaryExpressionIntention extends AbstractCodeTransformationTest {
public void testAllFilesPresentInSimplifyNegatedBinaryExpressionIntention() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("equals.kt")
public void testEquals() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/equals.kt");
}
@TestMetadata("greaterThan.kt")
public void testGreaterThan() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/greaterThan.kt");
}
@TestMetadata("greaterThanOrEquals.kt")
public void testGreaterThanOrEquals() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/greaterThanOrEquals.kt");
}
@TestMetadata("in.kt")
public void testIn() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/in.kt");
}
@TestMetadata("inapplicableBinaryOperation.kt")
public void testInapplicableBinaryOperation() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/inapplicableBinaryOperation.kt");
}
@TestMetadata("is.kt")
public void testIs() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/is.kt");
}
@TestMetadata("lessThan.kt")
public void testLessThan() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/lessThan.kt");
}
@TestMetadata("lessThanOrEquals.kt")
public void testLessThanOrEquals() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/lessThanOrEquals.kt");
}
@TestMetadata("notEquals.kt")
public void testNotEquals() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/notEquals.kt");
}
@TestMetadata("notIn.kt")
public void testNotIn() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/notIn.kt");
}
@TestMetadata("notIs.kt")
public void testNotIs() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/notIs.kt");
}
@TestMetadata("simpleInvert.kt")
public void testSimpleInvert() throws Exception {
doTestSimplifyNegatedBinaryExpressionIntention("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/simpleInvert.kt");
}
}
public static Test suite() {
TestSuite suite = new TestSuite("CodeTransformationTestGenerated");
suite.addTestSuite(ElvisToIfThen.class);
@@ -2644,6 +2712,7 @@ public class CodeTransformationTestGenerated extends AbstractCodeTransformationT
suite.addTestSuite(ReplaceBinaryInfixIntention.class);
suite.addTestSuite(ReplaceUnaryPrefixIntention.class);
suite.addTestSuite(ReplaceInvokeIntention.class);
suite.addTestSuite(SimplifyNegatedBinaryExpressionIntention.class);
return suite;
}
}