Add intention to expand boolean expression
#KT-38597 Fixed
This commit is contained in:
committed by
igoriakovlev
parent
f005091dfb
commit
343af60cb4
@@ -2212,6 +2212,7 @@ codestyle.name.kotlin=Kotlin
|
||||
add.missing.class.keyword=Add missing 'class' keyword
|
||||
fix.move.typealias.to.top.level=Move typealias to top level
|
||||
fix.change.jvm.name=Change JVM name
|
||||
expand.boolean.expression.to.if.else=Expand boolean expression to 'if else'
|
||||
inspection.logger.initialized.with.foreign.class.display.name=Logger initialized with foreign class
|
||||
logger.initialized.with.foreign.class=Logger initialized with foreign class ''{0}''
|
||||
logger.factory.method.name=Logger factory method name
|
||||
|
||||
@@ -1642,6 +1642,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.ExpandBooleanExpressionIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<lang.inspectionSuppressor language="kotlin" implementationClass="org.jetbrains.kotlin.idea.inspections.KotlinInspectionSuppressor"/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fun f(b: Boolean): Boolean {
|
||||
return if (b) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun f(b: Boolean): Boolean {
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This intention replaces a <b>boolean</b> expression with the equivalent <b>if-else</b> expression.
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.intentions
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.psi2ir.deparenthesize
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.typeUtil.isBoolean
|
||||
|
||||
class ExpandBooleanExpressionIntention : SelfTargetingRangeIntention<KtExpression>(
|
||||
KtExpression::class.java,
|
||||
KotlinBundle.lazyMessage("expand.boolean.expression.to.if.else")
|
||||
) {
|
||||
override fun applicabilityRange(element: KtExpression): TextRange? {
|
||||
val target = element.parents.takeWhile {
|
||||
it is KtCallExpression || it is KtQualifiedExpression || it is KtOperationExpression || it is KtParenthesizedExpression
|
||||
}.lastOrNull() ?: element
|
||||
if (element != target) return null
|
||||
if (element.deparenthesize() is KtConstantExpression) return null
|
||||
val parent = element.parent
|
||||
if (parent is KtValueArgument || parent is KtParameter || parent is KtStringTemplateEntry) return null
|
||||
val context = element.analyze(BodyResolveMode.PARTIAL)
|
||||
if (context[BindingContext.EXPRESSION_TYPE_INFO, element]?.type?.isBoolean() != true) return null
|
||||
return element.textRange
|
||||
}
|
||||
|
||||
override fun applyTo(element: KtExpression, editor: Editor?) {
|
||||
val ifExpression = KtPsiFactory(element).createExpressionByPattern("if ($0) {\ntrue\n} else {\nfalse\n}", element)
|
||||
val replaced = element.replace(ifExpression)
|
||||
if (replaced != null) {
|
||||
editor?.caretModel?.moveToOffset(replaced.startOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.intentions.ExpandBooleanExpressionIntention
|
||||
@@ -0,0 +1,3 @@
|
||||
fun test(i: Int): Boolean {
|
||||
return i == 1 || i == 2<caret> || i == 3
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun test(i: Int): Boolean {
|
||||
return if (i == 1 || i == 2 || i == 3) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun test(b: Boolean): Boolean {
|
||||
return <caret>foo(b)
|
||||
}
|
||||
|
||||
fun foo(b: Boolean) = true
|
||||
@@ -0,0 +1,9 @@
|
||||
fun test(b: Boolean): Boolean {
|
||||
return if (foo(b)) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun foo(b: Boolean) = true
|
||||
@@ -0,0 +1,4 @@
|
||||
// IS_APPLICABLE: false
|
||||
fun test(): Boolean {
|
||||
return true<caret>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// IS_APPLICABLE: false
|
||||
fun test(b: Boolean) {
|
||||
foo(b<caret>)
|
||||
}
|
||||
|
||||
fun foo(b: Boolean) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
// IS_APPLICABLE: false
|
||||
fun test(i: Int, b: Boolean = i == 1<caret>) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// IS_APPLICABLE: false
|
||||
fun test(b: Boolean) {
|
||||
val s = "b is $b<caret>"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun test(a: Any): Boolean {
|
||||
return a <caret>is String
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun test(a: Any): Boolean {
|
||||
return if (a is String) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun test(i: Int): Boolean {
|
||||
return (i == 1 || i == 2<caret> || i == 3)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun test(i: Int): Boolean {
|
||||
return if ((i == 1 || i == 2 || i == 3)) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun test(b: Boolean?): Boolean {
|
||||
return <caret>b!!
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun test(b: Boolean?): Boolean {
|
||||
return if (b!!) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun test(i: Int): Boolean {
|
||||
return !(i == 1 || i == 2<caret> || i == 3)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun test(i: Int): Boolean {
|
||||
return if (!(i == 1 || i == 2 || i == 3)) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun test(b: Boolean) {
|
||||
val b = Foo().bar()<caret>
|
||||
}
|
||||
|
||||
class Foo {
|
||||
fun bar() = true
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fun test(b: Boolean) {
|
||||
val b = if (Foo().bar()) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
class Foo {
|
||||
fun bar() = true
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun test(b: Boolean): Boolean {
|
||||
return b<caret>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun test(b: Boolean): Boolean {
|
||||
return <caret>if (b) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
// ACTION: Create property 'maximumSizeOfGroup'
|
||||
// ACTION: Introduce local variable
|
||||
// ACTION: Rename reference
|
||||
// ACTION: Expand boolean expression to 'if else'
|
||||
// ERROR: A 'return' expression required in a function with a block body ('{...}')
|
||||
// ERROR: The expression cannot be a selector (occur after a dot)
|
||||
// ERROR: Type inference failed: inline fun <T> Iterable<T>.firstOrNull(predicate: (T) -> Boolean): T?<br>cannot be applied to<br>receiver: Collection<List<String>> arguments: ((List<String>) -> () -> Boolean)<br>
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
// ACTION: Change type arguments to <*>
|
||||
// ACTION: Convert to block body
|
||||
// ACTION: Introduce local variable
|
||||
// ACTION: Expand boolean expression to 'if else'
|
||||
// ERROR: Cannot check for instance of erased type: List<Int>
|
||||
fun test(a: List<Any>) = a is List<Int><caret>
|
||||
@@ -2,5 +2,6 @@
|
||||
// ACTION: Change type arguments to <*>
|
||||
// ACTION: Convert to block body
|
||||
// ACTION: Introduce local variable
|
||||
// ACTION: Expand boolean expression to 'if else'
|
||||
// ERROR: Cannot check for instance of erased type: List<Int>
|
||||
fun <T> test(a: List<Any>) = a is List<Int><caret>
|
||||
@@ -3,6 +3,7 @@
|
||||
// ACTION: Add non-null asserted (!!) call
|
||||
// ACTION: Flip '>'
|
||||
// ACTION: Replace overloaded operator with function call
|
||||
// ACTION: Expand boolean expression to 'if else'
|
||||
|
||||
class SafeType {
|
||||
operator fun compareTo(other : SafeType) = 0
|
||||
|
||||
+1
@@ -2,6 +2,7 @@
|
||||
// ACTION: Add non-null asserted (!!) call
|
||||
// ACTION: Flip '<'
|
||||
// ACTION: Replace overloaded operator with function call
|
||||
// ACTION: Expand boolean expression to 'if else'
|
||||
// ERROR: Operator call corresponds to a dot-qualified call 'w?.x.compareTo(42)' which is not allowed on a nullable receiver 'w?.x'.
|
||||
|
||||
class Wrapper(val x: Int)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// ACTION: Replace '&&' with '||'
|
||||
// ACTION: Replace overloaded operator with function call
|
||||
// ACTION: Simplify boolean expression
|
||||
// ACTION: Expand boolean expression to 'if else'
|
||||
// ERROR: Operator call corresponds to a dot-qualified call 'w?.x.compareTo(42)' which is not allowed on a nullable receiver 'w?.x'.
|
||||
|
||||
class Wrapper(val x: Int)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// ACTION: Flip '<='
|
||||
// ACTION: Enable a trailing comma by default in the formatter
|
||||
// ACTION: Replace overloaded operator with function call
|
||||
// ACTION: Expand boolean expression to 'if else'
|
||||
// ERROR: Operator call corresponds to a dot-qualified call 'w?.x.compareTo(42)' which is not allowed on a nullable receiver 'w?.x'.
|
||||
|
||||
class Wrapper(val x: Int)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// ACTION: Add non-null asserted (!!) call
|
||||
// ACTION: Flip '>='
|
||||
// ACTION: Replace overloaded operator with function call
|
||||
// ACTION: Expand boolean expression to 'if else'
|
||||
// ERROR: Operator call corresponds to a dot-qualified call 'w?.x.compareTo(42)' which is not allowed on a nullable receiver 'w?.x'.
|
||||
|
||||
class Wrapper(val x: Int)
|
||||
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
// "Change return type of called function 'AA.contains' to 'Int'" "false"
|
||||
// ACTION: Change return type of enclosing function 'AAA.g' to 'Boolean'
|
||||
// ACTION: Replace overloaded operator with function call
|
||||
// ACTION: Expand boolean expression to 'if else'
|
||||
// ERROR: Type mismatch: inferred type is Boolean but Int was expected
|
||||
interface A {
|
||||
operator fun contains(i: Int): Boolean
|
||||
|
||||
@@ -8641,6 +8641,79 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/intentions/expandBooleanExpression")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ExpandBooleanExpression extends AbstractIntentionTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInExpandBooleanExpression() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/intentions/expandBooleanExpression"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true);
|
||||
}
|
||||
|
||||
@TestMetadata("binary.kt")
|
||||
public void testBinary() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/binary.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("call.kt")
|
||||
public void testCall() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/call.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("constant.kt")
|
||||
public void testConstant() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/constant.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("inArgument.kt")
|
||||
public void testInArgument() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/inArgument.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("inParameter.kt")
|
||||
public void testInParameter() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/inParameter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("inStringTemplate.kt")
|
||||
public void testInStringTemplate() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/inStringTemplate.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("is.kt")
|
||||
public void testIs() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/is.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("parenthesized.kt")
|
||||
public void testParenthesized() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/parenthesized.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("postfix.kt")
|
||||
public void testPostfix() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/postfix.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("prefix.kt")
|
||||
public void testPrefix() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/prefix.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("qualified.kt")
|
||||
public void testQualified() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/qualified.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("variable.kt")
|
||||
public void testVariable() throws Exception {
|
||||
runTest("idea/testData/intentions/expandBooleanExpression/variable.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/intentions/implementAbstractMember")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user