diff --git a/idea/resources/inspectionDescriptions/ConstantConditionIf.html b/idea/resources/inspectionDescriptions/ConstantConditionIf.html new file mode 100644 index 00000000000..10f7633dc62 --- /dev/null +++ b/idea/resources/inspectionDescriptions/ConstantConditionIf.html @@ -0,0 +1,5 @@ + + +This inspection reports 'if' expressions whose condition is constant and therefore the expression can be simplified. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 9da82dee92f..12719ca678c 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2387,6 +2387,15 @@ language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ConstantConditionIfInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ConstantConditionIfInspection.kt new file mode 100644 index 00000000000..9d8b6b6525a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ConstantConditionIfInspection.kt @@ -0,0 +1,127 @@ +/* + * 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.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.intentions.branchedTransformations.unwrapBlockOrParenthesis +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression +import org.jetbrains.kotlin.resolve.calls.callUtil.getType +import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +class ConstantConditionIfInspection : AbstractKotlinInspection() { + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return object : KtVisitorVoid() { + override fun visitIfExpression(expression: KtIfExpression) { + super.visitIfExpression(expression) + + val condition = expression.condition ?: return + + val context = condition.analyze(BodyResolveMode.PARTIAL) + val constantValue = condition.constantBooleanValue(context) ?: return + + val fixes = mutableListOf() + + if (expression.branch(constantValue) != null) { + fixes += SimplifyFix(constantValue, expression.isUsedAsExpression(context)) + } + + if (!constantValue && expression.`else` == null) { + fixes += RemoveFix() + } + + holder.registerProblem(condition, + "Condition is always '$constantValue'", + *fixes.toTypedArray()) + } + } + } + + private class SimplifyFix( + private val conditionValue: Boolean, + private val isUsedAsExpression: Boolean + ) : LocalQuickFix { + override fun getFamilyName() = name + + override fun getName() = "Simplify expression" + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val ifExpression = descriptor.psiElement.getParentOfType(strict = true) ?: return + val caretModel = ifExpression.findExistingEditor()?.caretModel + + val branch = ifExpression.branch(conditionValue)?.unwrapBlockOrParenthesis() ?: return + + val lastExpression = when { + branch !is KtBlockExpression -> ifExpression.replaced(branch) + isUsedAsExpression -> { + val factory = KtPsiFactory(ifExpression) + ifExpression.replaced(factory.createExpressionByPattern("run $0", branch.text)) + } + else -> { + val firstChild = branch.firstChild.nextSibling + + if (firstChild == branch.lastChild) { + ifExpression.delete() + } + else { + val lastChild = branch.lastChild.prevSibling + val parent = ifExpression.parent + parent.addRangeAfter(firstChild, lastChild, ifExpression) + ifExpression.delete() + } + + null + } + } + + caretModel?.moveToOffset(lastExpression?.startOffset ?: return) + } + } + + private class RemoveFix : LocalQuickFix { + override fun getFamilyName() = name + + override fun getName() = "Delete expression" + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val ifExpression = descriptor.psiElement.getParentOfType(strict = true) ?: return + ifExpression.delete() + } + } + + private companion object { + private fun KtIfExpression.branch(thenBranch: Boolean) = if (thenBranch) then else `else` + + private fun KtExpression.constantBooleanValue(context: BindingContext): Boolean? { + val type = getType(context) ?: return null + + val constantValue = ConstantExpressionEvaluator.getConstant(this, context)?.toConstantValue(type) + return constantValue?.value as? Boolean + } + } +} \ No newline at end of file diff --git a/idea/testData/inspections/constantConditionIf/inspectionData/expected.xml b/idea/testData/inspections/constantConditionIf/inspectionData/expected.xml new file mode 100644 index 00000000000..d639626e8d3 --- /dev/null +++ b/idea/testData/inspections/constantConditionIf/inspectionData/expected.xml @@ -0,0 +1,42 @@ + + + test.kt + 7 + light_idea_test_case + + Condition of 'if' expression is constant + Condition is always 'true' + + + test.kt + 9 + light_idea_test_case + + Condition of 'if' expression is constant + Condition is always 'false' + + + test.kt + 14 + light_idea_test_case + + Condition of 'if' expression is constant + Condition is always 'true' + + + test.kt + 20 + light_idea_test_case + + Condition of 'if' expression is constant + Condition is always 'false' + + + test.kt + 24 + light_idea_test_case + + Condition of 'if' expression is constant + Condition is always 'true' + + \ No newline at end of file diff --git a/idea/testData/inspections/constantConditionIf/inspectionData/inspections.test b/idea/testData/inspections/constantConditionIf/inspectionData/inspections.test new file mode 100644 index 00000000000..146c170c7c4 --- /dev/null +++ b/idea/testData/inspections/constantConditionIf/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.ConstantConditionIfInspection \ No newline at end of file diff --git a/idea/testData/inspections/constantConditionIf/test.kt b/idea/testData/inspections/constantConditionIf/test.kt new file mode 100644 index 00000000000..e6b763eeffd --- /dev/null +++ b/idea/testData/inspections/constantConditionIf/test.kt @@ -0,0 +1,28 @@ +fun foo(x: Int) {} +fun baz(s: String) {} + +const val TRUE = true + +fun bar() { + foo(if (TRUE) 1 else 2) + + foo(if (false && TRUE) { + baz("a") + 1 + } else 2) + + if (true) { + //asd + baz("a") + baz("b") + } + + if (false) { + baz("a") + } + + foo(if (TRUE) { + baz("a") + 1 + } else 2) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/.inspection b/idea/testData/inspectionsLocal/constantConditionIf/.inspection new file mode 100644 index 00000000000..f06df936fa7 --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.ConstantConditionIfInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/delete.kt b/idea/testData/inspectionsLocal/constantConditionIf/delete.kt new file mode 100644 index 00000000000..191b95875ab --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/delete.kt @@ -0,0 +1,8 @@ +fun foo(x: Int) {} + +fun bar() { + if (false) { + foo(1) + foo(2) + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/delete.kt.after b/idea/testData/inspectionsLocal/constantConditionIf/delete.kt.after new file mode 100644 index 00000000000..38e0afc1da9 --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/delete.kt.after @@ -0,0 +1,4 @@ +fun foo(x: Int) {} + +fun bar() { +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/expression.kt b/idea/testData/inspectionsLocal/constantConditionIf/expression.kt new file mode 100644 index 00000000000..4c355519f65 --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/expression.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME + +fun foo(x: Int) {} + +fun bar() { + foo(if (true) { + foo(1) + foo(2) + 1 + } else 2) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/expression.kt.after b/idea/testData/inspectionsLocal/constantConditionIf/expression.kt.after new file mode 100644 index 00000000000..c0e4ab06db7 --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/expression.kt.after @@ -0,0 +1,11 @@ +// WITH_RUNTIME + +fun foo(x: Int) {} + +fun bar() { + foo(run { + foo(1) + foo(2) + 1 + }) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/noStatements.kt b/idea/testData/inspectionsLocal/constantConditionIf/noStatements.kt new file mode 100644 index 00000000000..f5f240b304a --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/noStatements.kt @@ -0,0 +1,6 @@ +fun foo(s: String) {} + +fun bar() { + if (true) { + } else 2 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/noStatements.kt.after b/idea/testData/inspectionsLocal/constantConditionIf/noStatements.kt.after new file mode 100644 index 00000000000..36d056ff2fa --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/noStatements.kt.after @@ -0,0 +1,5 @@ +fun foo(s: String) {} + +fun bar() { + +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/simpleBlock.kt b/idea/testData/inspectionsLocal/constantConditionIf/simpleBlock.kt new file mode 100644 index 00000000000..c610b52a01a --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/simpleBlock.kt @@ -0,0 +1,5 @@ +fun foo(x: Int) {} + +fun bar() { + foo(if (true) { 1 } else 2) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/simpleBlock.kt.after b/idea/testData/inspectionsLocal/constantConditionIf/simpleBlock.kt.after new file mode 100644 index 00000000000..3c9abb1728b --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/simpleBlock.kt.after @@ -0,0 +1,5 @@ +fun foo(x: Int) {} + +fun bar() { + foo(1) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/simpleFalse.kt b/idea/testData/inspectionsLocal/constantConditionIf/simpleFalse.kt new file mode 100644 index 00000000000..dec225ab02b --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/simpleFalse.kt @@ -0,0 +1,5 @@ +fun foo(x: Int) {} + +fun bar() { + foo(if (false) 1 else 2) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/simpleFalse.kt.after b/idea/testData/inspectionsLocal/constantConditionIf/simpleFalse.kt.after new file mode 100644 index 00000000000..3d6b0e57f74 --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/simpleFalse.kt.after @@ -0,0 +1,5 @@ +fun foo(x: Int) {} + +fun bar() { + foo(2) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/simpleTrue.kt b/idea/testData/inspectionsLocal/constantConditionIf/simpleTrue.kt new file mode 100644 index 00000000000..f294e957ba4 --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/simpleTrue.kt @@ -0,0 +1,5 @@ +fun foo(x: Int) {} + +fun bar() { + foo(if (true) 1 else 2) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/simpleTrue.kt.after b/idea/testData/inspectionsLocal/constantConditionIf/simpleTrue.kt.after new file mode 100644 index 00000000000..3c9abb1728b --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/simpleTrue.kt.after @@ -0,0 +1,5 @@ +fun foo(x: Int) {} + +fun bar() { + foo(1) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/statement.kt b/idea/testData/inspectionsLocal/constantConditionIf/statement.kt new file mode 100644 index 00000000000..d000660c0b5 --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/statement.kt @@ -0,0 +1,8 @@ +fun foo(s: String) {} + +fun bar() { + if (true) { + foo("a") + foo("b") + } else 2 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/constantConditionIf/statement.kt.after b/idea/testData/inspectionsLocal/constantConditionIf/statement.kt.after new file mode 100644 index 00000000000..9db0fce960c --- /dev/null +++ b/idea/testData/inspectionsLocal/constantConditionIf/statement.kt.after @@ -0,0 +1,6 @@ +fun foo(s: String) {} + +fun bar() { + foo("a") + foo("b") +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java index 6fc821ef3aa..65b33c2f867 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java @@ -161,6 +161,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest { doTest(fileName); } + @TestMetadata("constantConditionIf/inspectionData/inspections.test") + public void testConstantConditionIf_inspectionData_Inspections_test() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/constantConditionIf/inspectionData/inspections.test"); + doTest(fileName); + } + @TestMetadata("convertLambdaToReference/inspectionData/inspections.test") public void testConvertLambdaToReference_inspectionData_Inspections_test() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/convertLambdaToReference/inspectionData/inspections.test"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index e2c39f4e9fa..d7fbf2a6089 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -408,6 +408,57 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/constantConditionIf") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConstantConditionIf extends AbstractLocalInspectionTest { + public void testAllFilesPresentInConstantConditionIf() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/constantConditionIf"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("delete.kt") + public void testDelete() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/constantConditionIf/delete.kt"); + doTest(fileName); + } + + @TestMetadata("expression.kt") + public void testExpression() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/constantConditionIf/expression.kt"); + doTest(fileName); + } + + @TestMetadata("noStatements.kt") + public void testNoStatements() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/constantConditionIf/noStatements.kt"); + doTest(fileName); + } + + @TestMetadata("simpleBlock.kt") + public void testSimpleBlock() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/constantConditionIf/simpleBlock.kt"); + doTest(fileName); + } + + @TestMetadata("simpleFalse.kt") + public void testSimpleFalse() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/constantConditionIf/simpleFalse.kt"); + doTest(fileName); + } + + @TestMetadata("simpleTrue.kt") + public void testSimpleTrue() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/constantConditionIf/simpleTrue.kt"); + doTest(fileName); + } + + @TestMetadata("statement.kt") + public void testStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/constantConditionIf/statement.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/inspectionsLocal/copyWithoutNamedArguments") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)