Add inspection to detect is checks for object types #KT-21741 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
247881baf9
commit
84a6ef6ac4
@@ -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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports redundant type checks for object.
|
||||
</body>
|
||||
</html>
|
||||
@@ -2617,6 +2617,15 @@
|
||||
language="JAVA"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RedundantObjectTypeCheckInspection"
|
||||
displayName="Redundant type checks for object"
|
||||
groupPath="Kotlin"
|
||||
groupName="Style issues"
|
||||
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,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<KtIsExpression>(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<KtWhenConditionIsPattern>(strict = false) ?: return
|
||||
val typeReference = element.typeReference ?: return
|
||||
val factory = KtPsiFactory(project)
|
||||
val newElement = factory.createWhenCondition(typeReference.text)
|
||||
element.replace(newElement)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.inspections.RedundantObjectTypeCheckInspection
|
||||
@@ -0,0 +1,7 @@
|
||||
// PROBLEM: none
|
||||
class C
|
||||
|
||||
fun foo(arg: Any) {
|
||||
if (arg <caret>is C) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// PROBLEM: none
|
||||
class C
|
||||
|
||||
fun foo(arg: Any) {
|
||||
when (arg) {
|
||||
<caret>is C -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
object O
|
||||
|
||||
fun foo(arg: Any) {
|
||||
if (arg <caret>!is O) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
object O
|
||||
|
||||
fun foo(arg: Any) {
|
||||
if (arg != O) {
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// PROBLEM: none
|
||||
object O
|
||||
|
||||
fun foo(arg: Any) {
|
||||
when (arg) {
|
||||
<caret>!is O -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
object O
|
||||
|
||||
fun foo(arg: Any) {
|
||||
if (arg <caret>is O) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
object O
|
||||
|
||||
fun foo(arg: Any) {
|
||||
if (arg == O) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
object O
|
||||
|
||||
fun foo(arg: Any) {
|
||||
when (arg) {
|
||||
<caret>is O -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
object O
|
||||
|
||||
fun foo(arg: Any) {
|
||||
when (arg) {
|
||||
O -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user