Add intention to expand boolean expression

#KT-38597 Fixed
This commit is contained in:
Toshiaki Kameyama
2020-05-05 14:25:13 +09:00
committed by igoriakovlev
parent f005091dfb
commit 343af60cb4
37 changed files with 257 additions and 0 deletions
@@ -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"
@@ -0,0 +1,7 @@
fun f(b: Boolean): Boolean {
return if (b) {
true
} else {
false
}
}
@@ -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
@@ -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,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)