Add Boolean? == const to elvis conversion and vice versa #KT-15368 Fixed

Convert elvis to equality check implemented as inspection due
code style guide
This commit is contained in:
Dimach
2017-06-10 02:39:48 +03:00
committed by Mikhail Glukhikh
parent 6e9c0a0fda
commit d369fa094a
36 changed files with 437 additions and 1 deletions
@@ -316,7 +316,7 @@ public class KtPsiUtil {
return isBooleanConstant(condition) && condition.getNode().findChildByType(KtTokens.FALSE_KEYWORD) != null;
}
private static boolean isBooleanConstant(@Nullable KtExpression condition) {
public static boolean isBooleanConstant(@Nullable KtExpression condition) {
return condition != null && condition.getNode().getElementType() == KtNodeTypes.BOOLEAN_CONSTANT;
}
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports when equality check should be used instead of elvis
</body>
</html>
@@ -0,0 +1,3 @@
val v: Boolean?
if (v ?: false)
@@ -0,0 +1,3 @@
val v: Boolean?
if (v == true)
@@ -0,0 +1,5 @@
<html>
<body>
Converts nullable boolean equality check to elvis operator
</body>
</html>
+13
View File
@@ -1590,6 +1590,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.NullableBooleanEqualityCheckToElvisIntention</className>
<category>Kotlin</category>
</intentionAction>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
displayName="Object literal can be converted to lambda"
groupName="Kotlin"
@@ -2201,6 +2206,14 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NullableBooleanElvisInspection"
displayName="Equality check can be used instead of elvis"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
@@ -0,0 +1,88 @@
/*
* Copyright 2010-2017 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.kotlin.idea.inspections
import com.intellij.codeInsight.FileModificationService
import com.intellij.codeInspection.*
import com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR_OR_WARNING
import com.intellij.codeInspection.ProblemHighlightType.INFORMATION
import com.intellij.openapi.project.Project
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.intentions.SimplifyNegatedBinaryExpressionIntention
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.isBooleanOrNullableBoolean
class NullableBooleanElvisInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) =
object : KtVisitorVoid() {
override fun visitBinaryExpression(expression: KtBinaryExpression) {
if (expression.operationToken != KtTokens.ELVIS) return
val lhs = expression.left ?: return
val rhs = expression.right ?: return
if (!KtPsiUtil.isBooleanConstant(rhs)) return
val lhsType = lhs.analyze(BodyResolveMode.PARTIAL).getType(lhs) ?: return
if (TypeUtils.isNullableType(lhsType) && lhsType.isBooleanOrNullableBoolean()) {
val parentIfOrWhile = PsiTreeUtil.getParentOfType(
expression, KtIfExpression::class.java, KtWhileExpressionBase::class.java)
val condition = when (parentIfOrWhile) {
is KtIfExpression -> parentIfOrWhile.condition
is KtWhileExpressionBase -> parentIfOrWhile.condition
else -> null
}
val highlightType =
if (condition != null && condition in expression.parentsWithSelf) GENERIC_ERROR_OR_WARNING else INFORMATION
holder.registerProblem(expression,
"Equality check can be used instead of elvis",
highlightType,
ReplaceWithEqualityCheckFix())
}
}
}
private class ReplaceWithEqualityCheckFix : LocalQuickFix {
override fun getName() = "Replace with equality check"
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.psiElement as? KtBinaryExpression ?: return
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return
if (element.operationToken != KtTokens.ELVIS) return
val constPart = element.right as? KtConstantExpression ?: return
val exprPart = element.left ?: return
val constValue = if (KtPsiUtil.isTrueConstant(constPart)) true else if (KtPsiUtil.isFalseConstant(constPart)) false else return
val equalityCheckExpression = element.replaced(KtPsiFactory(constPart).buildExpression {
appendExpression(exprPart)
appendFixedText(if (constValue) " != false" else " == true")
})
val prefixExpression = equalityCheckExpression.getParentOfType<KtPrefixExpression>(strict = true) ?: return
val simplifier = SimplifyNegatedBinaryExpressionIntention()
if (simplifier.isApplicableTo(prefixExpression)) {
simplifier.applyTo(prefixExpression, null)
}
}
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2017 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.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.isBooleanOrNullableBoolean
class NullableBooleanEqualityCheckToElvisIntention : SelfTargetingIntention<KtBinaryExpression>(
KtBinaryExpression::class.java, "Convert Boolean? == const to elvis"
) {
override fun isApplicableTo(element: KtBinaryExpression, caretOffset: Int): Boolean {
if (element.operationToken != KtTokens.EQEQ && element.operationToken != KtTokens.EXCLEQ) return false
val lhs = element.left ?: return false
val rhs = element.right ?: return false
return isApplicable(lhs, rhs) || isApplicable(rhs, lhs)
}
private fun isApplicable(lhs: KtExpression, rhs: KtExpression): Boolean {
if (!KtPsiUtil.isBooleanConstant(rhs)) return false
val lhsType = lhs.analyze(BodyResolveMode.PARTIAL).getType(lhs) ?: return false
return TypeUtils.isNullableType(lhsType) && lhsType.isBooleanOrNullableBoolean()
}
override fun applyTo(element: KtBinaryExpression, editor: Editor?) {
val equality = element.operationToken == KtTokens.EQEQ
val constPart = element.left as? KtConstantExpression ?:
element.right as? KtConstantExpression ?: return
val exprPart = (if (element.right == constPart) element.left else element.right) ?: return
val constValue = if (KtPsiUtil.isTrueConstant(constPart)) true else if (KtPsiUtil.isFalseConstant(constPart)) false else return
val factory = KtPsiFactory(constPart)
val elvis = factory.createExpressionByPattern("$0 ?: ${!constValue}", exprPart)
element.replaced(if (constValue == equality) elvis else factory.createExpressionByPattern("!($0)", elvis))
}
}
@@ -0,0 +1,51 @@
<problems>
<problem>
<file>test.kt</file>
<line>4</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt"/>
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis</problem_class>
<description>Equality check can be used instead of elvis</description>
</problem>
<problem>
<file>test.kt</file>
<line>7</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt"/>
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis</problem_class>
<description>Equality check can be used instead of elvis</description>
</problem>
<problem>
<file>test.kt</file>
<line>10</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt"/>
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis</problem_class>
<description>Equality check can be used instead of elvis</description>
</problem>
<problem>
<file>test.kt</file>
<line>10</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt"/>
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis</problem_class>
<description>Equality check can be used instead of elvis</description>
</problem>
<problem>
<file>test.kt</file>
<line>13</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt"/>
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis</problem_class>
<description>Equality check can be used instead of elvis</description>
</problem>
<problem>
<file>test.kt</file>
<line>14</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt"/>
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis</problem_class>
<description>Equality check can be used instead of elvis</description>
</problem>
</problems>
@@ -0,0 +1 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.NullableBooleanElvisInspection
+15
View File
@@ -0,0 +1,15 @@
fun foo() {
var a: Boolean? = null
var b: Boolean? = null
if (a ?: false) {
}
if (!(a ?: false)) {
}
if (a ?: false || !(b ?: true)) {
}
val x = a ?: false
val y = !(b ?: true)
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.NullableBooleanElvisInspection
@@ -0,0 +1,6 @@
fun foo() {
var a: Boolean? = null
if (a <caret>?: false) {
}
}
@@ -0,0 +1,6 @@
fun foo() {
var a: Boolean? = null
if (a == true) {
}
}
@@ -0,0 +1,6 @@
fun foo() {
var a: Boolean? = null
if (!(a <caret>?: false)) {
}
}
@@ -0,0 +1,6 @@
fun foo() {
var a: Boolean? = null
if (a != true) {
}
}
@@ -0,0 +1,7 @@
fun foo() {
var a: Boolean? = null
var b: Boolean? = null
if (a ?: false || !(b<caret> ?: true)) {
}
}
@@ -0,0 +1,7 @@
fun foo() {
var a: Boolean? = null
var b: Boolean? = null
if (a ?: false || b == false) {
}
}
@@ -0,0 +1,4 @@
fun foo() {
var a: Boolean? = null
val x = a<caret> ?: false
}
@@ -0,0 +1,4 @@
fun foo() {
var a: Boolean? = null
val x = a == true
}
@@ -0,0 +1,4 @@
fun foo() {
var b: Boolean? = null
val x = !(b <caret>?: true)
}
@@ -0,0 +1,4 @@
fun foo() {
var b: Boolean? = null
val x = b == false
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.NullableBooleanEqualityCheckToElvisIntention
@@ -0,0 +1,6 @@
fun foo() {
val a: Boolean? = null
if (a<caret> == false) {
}
}
@@ -0,0 +1,6 @@
fun foo() {
val a: Boolean? = null
if (!(a ?: true)) {
}
}
@@ -0,0 +1,6 @@
fun foo() {
val a: Boolean? = null
if (a<caret> == true) {
}
}
@@ -0,0 +1,6 @@
fun foo() {
val a: Boolean? = null
if (a ?: false) {
}
}
@@ -0,0 +1,4 @@
fun foo() {
val a = java.lang.Boolean.valueOf("true")
if (a<caret> == true) {}
}
@@ -0,0 +1,4 @@
fun foo() {
val a = java.lang.Boolean.valueOf("true")
if (a ?: false) {}
}
@@ -0,0 +1,6 @@
fun foo() {
val a: Boolean? = null
if (a<caret> != false) {
}
}
@@ -0,0 +1,6 @@
fun foo() {
val a: Boolean? = null
if (a ?: true) {
}
}
@@ -0,0 +1,6 @@
fun foo() {
val a: Boolean? = null
if (a<caret> != true) {
}
}
@@ -0,0 +1,6 @@
fun foo() {
val a: Boolean? = null
if (!(a ?: false)) {
}
}
@@ -227,6 +227,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
doTest(fileName);
}
@TestMetadata("nullableBooleanElvis/inspectionData/inspections.test")
public void testNullableBooleanElvis_inspectionData_Inspections_test() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/nullableBooleanElvis/inspectionData/inspections.test");
doTest(fileName);
}
@TestMetadata("overridingDeprecatedMember/inspectionData/inspections.test")
public void testOverridingDeprecatedMember_inspectionData_Inspections_test() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/overridingDeprecatedMember/inspectionData/inspections.test");
@@ -147,6 +147,45 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
}
}
@TestMetadata("idea/testData/inspectionsLocal/nullableBooleanElvis")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class NullableBooleanElvis extends AbstractLocalInspectionTest {
public void testAllFilesPresentInNullableBooleanElvis() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/nullableBooleanElvis"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("inIf.kt")
public void testInIf() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/nullableBooleanElvis/inIf.kt");
doTest(fileName);
}
@TestMetadata("inIf2.kt")
public void testInIf2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/nullableBooleanElvis/inIf2.kt");
doTest(fileName);
}
@TestMetadata("inIf3.kt")
public void testInIf3() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/nullableBooleanElvis/inIf3.kt");
doTest(fileName);
}
@TestMetadata("notInIf.kt")
public void testNotInIf() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/nullableBooleanElvis/notInIf.kt");
doTest(fileName);
}
@TestMetadata("notInIfWithTrue.kt")
public void testNotInIfWithTrue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/nullableBooleanElvis/notInIfWithTrue.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/inspectionsLocal/removeToStringInStringTemplate")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -11868,6 +11868,45 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/nullableBooleanEqualityCheckToElvis")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class NullableBooleanEqualityCheckToElvis extends AbstractIntentionTest {
public void testAllFilesPresentInNullableBooleanEqualityCheckToElvis() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/nullableBooleanEqualityCheckToElvis"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("eqFalse.kt")
public void testEqFalse() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/nullableBooleanEqualityCheckToElvis/eqFalse.kt");
doTest(fileName);
}
@TestMetadata("eqTrue.kt")
public void testEqTrue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/nullableBooleanEqualityCheckToElvis/eqTrue.kt");
doTest(fileName);
}
@TestMetadata("flexible.kt")
public void testFlexible() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/nullableBooleanEqualityCheckToElvis/flexible.kt");
doTest(fileName);
}
@TestMetadata("notEqFalse.kt")
public void testNotEqFalse() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/nullableBooleanEqualityCheckToElvis/notEqFalse.kt");
doTest(fileName);
}
@TestMetadata("notEqTrue.kt")
public void testNotEqTrue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/nullableBooleanEqualityCheckToElvis/notEqTrue.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/objectLiteralToLambda")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)