diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt index 9a2dff7ff55..37b3cbab23f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt @@ -379,6 +379,11 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m return whenEntry } + fun createWhenCondition(conditionText: String): KtWhenCondition { + val whenEntry = createWhenEntry("$conditionText -> {}") + return whenEntry.conditions[0] + } + fun createBlockStringTemplateEntry(expression: KtExpression): KtStringTemplateEntryWithExpression { // We don't want reformatting here as it can potentially change something in raw strings val stringTemplateExpression = createExpressionByPattern("\"$\${$0}\"", expression, reformat = false) as KtStringTemplateExpression diff --git a/idea/resources/inspectionDescriptions/RedundantObjectTypeCheck.html b/idea/resources/inspectionDescriptions/RedundantObjectTypeCheck.html new file mode 100644 index 00000000000..cededb0d4d5 --- /dev/null +++ b/idea/resources/inspectionDescriptions/RedundantObjectTypeCheck.html @@ -0,0 +1,5 @@ + + +This inspection reports redundant type checks for object. + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 3f179d9123f..bf9a50f0b32 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2617,6 +2617,15 @@ language="JAVA" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantObjectTypeCheckInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantObjectTypeCheckInspection.kt new file mode 100644 index 00000000000..4b78eeca7ca --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantObjectTypeCheckInspection.kt @@ -0,0 +1,98 @@ +/* + * 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.openapi.util.TextRange +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorUtils + +class RedundantObjectTypeCheckInspection : AbstractKotlinInspection() { + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return object : KtVisitorVoid() { + + override fun visitIsExpression(expression: KtIsExpression) { + super.visitIsExpression(expression) + val typeReference = expression.typeReference ?: return + if (!typeReference.isObject()) return + registerProblem(expression.operationReference, ReplaceWithEqualityFix(expression.isNegated)) + } + + override fun visitWhenConditionIsPattern(condition: KtWhenConditionIsPattern) { + super.visitWhenConditionIsPattern(condition) + val typeReference = condition.typeReference ?: return + if (!typeReference.isObject()) return + if (condition.isNegated) return + registerProblem(condition, RemoveIsOperator()) + } + + private fun registerProblem(element: KtElement, quickFix: LocalQuickFix) { + holder.registerProblem(element, + TextRange(0, 2), + "Redundant type checks for object", + quickFix) + } + } + } +} + +private fun KtTypeReference.isObject(): Boolean { + val descriptor = this.analyze()[BindingContext.TYPE, this]?.constructor?.declarationDescriptor as? ClassDescriptor + return DescriptorUtils.isObject(descriptor) +} + +private class ReplaceWithEqualityFix(isNegated: Boolean) : LocalQuickFix { + private val isOperator = if (isNegated) "!is" else "is" + + private val equality = if (isNegated) "!=" else "==" + + override fun getName() = "Replace '$isOperator' with '$equality'" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val element = descriptor.psiElement.getParentOfType(strict = false) ?: return + val typeReference = element.typeReference ?: return + val factory = KtPsiFactory(project) + val newElement = factory.createExpressionByPattern("$0 $1 $2", element.leftHandSide, equality, typeReference.text) + element.replace(newElement) + } +} + +private class RemoveIsOperator : LocalQuickFix { + override fun getName() = "Remove 'is' operator" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val element = descriptor.psiElement.getParentOfType(strict = false) ?: return + val typeReference = element.typeReference ?: return + val factory = KtPsiFactory(project) + val newElement = factory.createWhenCondition(typeReference.text) + element.replace(newElement) + } +} + diff --git a/idea/testData/inspectionsLocal/redundantObjectTypeCheck/.inspection b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/.inspection new file mode 100644 index 00000000000..74ecb7f3637 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.RedundantObjectTypeCheckInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isClass.kt b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isClass.kt new file mode 100644 index 00000000000..3c9301cc6c9 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isClass.kt @@ -0,0 +1,7 @@ +// PROBLEM: none +class C + +fun foo(arg: Any) { + if (arg is C) { + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isClassWhenEntry.kt b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isClassWhenEntry.kt new file mode 100644 index 00000000000..9eeb00081e3 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isClassWhenEntry.kt @@ -0,0 +1,9 @@ +// PROBLEM: none +class C + +fun foo(arg: Any) { + when (arg) { + is C -> { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isNotObject.kt b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isNotObject.kt new file mode 100644 index 00000000000..cf7842ba8e7 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isNotObject.kt @@ -0,0 +1,6 @@ +object O + +fun foo(arg: Any) { + if (arg !is O) { + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isNotObject.kt.after b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isNotObject.kt.after new file mode 100644 index 00000000000..f8f7b58faff --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isNotObject.kt.after @@ -0,0 +1,6 @@ +object O + +fun foo(arg: Any) { + if (arg != O) { + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isNotObjectWhenEntry.kt b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isNotObjectWhenEntry.kt new file mode 100644 index 00000000000..b25a2b7687b --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isNotObjectWhenEntry.kt @@ -0,0 +1,9 @@ +// PROBLEM: none +object O + +fun foo(arg: Any) { + when (arg) { + !is O -> { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObject.kt b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObject.kt new file mode 100644 index 00000000000..0173d4676c6 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObject.kt @@ -0,0 +1,6 @@ +object O + +fun foo(arg: Any) { + if (arg is O) { + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObject.kt.after b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObject.kt.after new file mode 100644 index 00000000000..7b5c390ee16 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObject.kt.after @@ -0,0 +1,6 @@ +object O + +fun foo(arg: Any) { + if (arg == O) { + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObjectWhenEntry.kt b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObjectWhenEntry.kt new file mode 100644 index 00000000000..8f76847811b --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObjectWhenEntry.kt @@ -0,0 +1,8 @@ +object O + +fun foo(arg: Any) { + when (arg) { + is O -> { + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObjectWhenEntry.kt.after b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObjectWhenEntry.kt.after new file mode 100644 index 00000000000..405e54d79c7 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObjectWhenEntry.kt.after @@ -0,0 +1,8 @@ +object O + +fun foo(arg: Any) { + when (arg) { + O -> { + } + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 7a05520aacd..5ce7befa2e5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -2646,6 +2646,51 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/redundantObjectTypeCheck") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RedundantObjectTypeCheck extends AbstractLocalInspectionTest { + public void testAllFilesPresentInRedundantObjectTypeCheck() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/redundantObjectTypeCheck"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("isClass.kt") + public void testIsClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantObjectTypeCheck/isClass.kt"); + doTest(fileName); + } + + @TestMetadata("isClassWhenEntry.kt") + public void testIsClassWhenEntry() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantObjectTypeCheck/isClassWhenEntry.kt"); + doTest(fileName); + } + + @TestMetadata("isNotObject.kt") + public void testIsNotObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantObjectTypeCheck/isNotObject.kt"); + doTest(fileName); + } + + @TestMetadata("isNotObjectWhenEntry.kt") + public void testIsNotObjectWhenEntry() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantObjectTypeCheck/isNotObjectWhenEntry.kt"); + doTest(fileName); + } + + @TestMetadata("isObject.kt") + public void testIsObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObject.kt"); + doTest(fileName); + } + + @TestMetadata("isObjectWhenEntry.kt") + public void testIsObjectWhenEntry() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantObjectTypeCheck/isObjectWhenEntry.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/inspectionsLocal/redundantOverride") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)