Redundant companion reference: do not report 'values/valueOf' function in enum
#KT-34285 Fixed
This commit is contained in:
committed by
Yan Zhulanow
parent
2a841550d4
commit
55d55446c8
+111
-98
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.descriptors.*
|
|||||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||||
import org.jetbrains.kotlin.idea.analysis.analyzeAsReplacement
|
import org.jetbrains.kotlin.idea.analysis.analyzeAsReplacement
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.isReferenceToBuiltInEnumFunction
|
||||||
import org.jetbrains.kotlin.idea.references.mainReference
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.idea.references.resolveToDescriptors
|
import org.jetbrains.kotlin.idea.references.resolveToDescriptors
|
||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
@@ -50,112 +51,124 @@ class RedundantCompanionReferenceInspection : AbstractKotlinInspection() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
private fun isRedundantCompanionReference(reference: KtReferenceExpression): Boolean {
|
||||||
fun isRedundantCompanionReference(reference: KtReferenceExpression): Boolean {
|
val parent = reference.parent as? KtDotQualifiedExpression ?: return false
|
||||||
val parent = reference.parent as? KtDotQualifiedExpression ?: return false
|
val grandParent = parent.parent as? KtElement
|
||||||
val grandParent = parent.parent
|
val selectorExpression = parent.selectorExpression
|
||||||
val selectorExpression = parent.selectorExpression
|
if (reference == selectorExpression && grandParent !is KtDotQualifiedExpression) return false
|
||||||
if (reference == selectorExpression && grandParent !is KtDotQualifiedExpression) return false
|
if (parent.getStrictParentOfType<KtImportDirective>() != null) return false
|
||||||
if (parent.getStrictParentOfType<KtImportDirective>() != null) return false
|
|
||||||
|
|
||||||
val objectDeclaration = reference.mainReference.resolve() as? KtObjectDeclaration ?: return false
|
val objectDeclaration = reference.mainReference.resolve() as? KtObjectDeclaration ?: return false
|
||||||
if (!objectDeclaration.isCompanion()) return false
|
if (!objectDeclaration.isCompanion()) return false
|
||||||
val referenceText = reference.text
|
val referenceText = reference.text
|
||||||
if (referenceText != objectDeclaration.name) return false
|
if (referenceText != objectDeclaration.name) return false
|
||||||
if (reference != selectorExpression && referenceText == (selectorExpression as? KtNameReferenceExpression)?.text) return false
|
if (reference != selectorExpression && referenceText == (selectorExpression as? KtNameReferenceExpression)?.text) return false
|
||||||
|
|
||||||
val containingClass = objectDeclaration.containingClass() ?: return false
|
val containingClass = objectDeclaration.containingClass() ?: return false
|
||||||
if (reference.containingClass() != containingClass && reference == parent.receiverExpression) return false
|
if (reference.containingClass() != containingClass && reference == parent.receiverExpression) return false
|
||||||
val context = reference.analyze()
|
if (parent.isReferenceToBuildInEnumFunctionInEnumClass(containingClass)) return false
|
||||||
val containingClassDescriptor =
|
|
||||||
context[BindingContext.DECLARATION_TO_DESCRIPTOR, containingClass] as? ClassDescriptor ?: return false
|
|
||||||
when (val selectorDescriptor = selectorExpression?.getResolvedCall(context)?.resultingDescriptor) {
|
|
||||||
is PropertyDescriptor -> {
|
|
||||||
val name = selectorDescriptor.name
|
|
||||||
if (containingClassDescriptor.findMemberVariable(name) != null) return false
|
|
||||||
|
|
||||||
val type = selectorDescriptor.type
|
val context = reference.analyze()
|
||||||
val javaGetter = containingClassDescriptor.findMemberFunction(
|
if (grandParent.isReferenceToClassOrObject(context)) return false
|
||||||
Name.identifier(JvmAbi.getterName(name.asString()))
|
|
||||||
)?.takeIf { f -> f is JavaMethodDescriptor || f.overriddenDescriptors.any { it is JavaMethodDescriptor } }
|
|
||||||
if (javaGetter?.valueParameters?.isEmpty() == true && javaGetter.returnType?.makeNotNullable() == type) return false
|
|
||||||
|
|
||||||
val variable = reference.getResolutionScope().findVariable(name, NoLookupLocation.FROM_IDE)
|
val containingClassDescriptor =
|
||||||
if (variable != null && variable.isLocalOrExtension(containingClassDescriptor)) return false
|
context[BindingContext.DECLARATION_TO_DESCRIPTOR, containingClass] as? ClassDescriptor ?: return false
|
||||||
}
|
if (containingClassDescriptor.hasSameNameMemberAs(selectorExpression, context)) return false
|
||||||
is FunctionDescriptor -> {
|
|
||||||
val name = selectorDescriptor.name
|
(reference as? KtSimpleNameExpression)?.getReceiverExpression()?.getQualifiedElementSelector()
|
||||||
if (containingClassDescriptor.findMemberFunction(name) != null) return false
|
?.mainReference?.resolveToDescriptors(context)?.firstOrNull()
|
||||||
val function = reference.getResolutionScope().findFunction(name, NoLookupLocation.FROM_IDE)
|
?.let { if (it != containingClassDescriptor) return false }
|
||||||
if (function != null && function.isLocalOrExtension(containingClassDescriptor)) return false
|
|
||||||
}
|
if (selectorExpression is KtCallExpression && referenceText == selectorExpression.calleeExpression?.text) {
|
||||||
|
val newExpression = KtPsiFactory(reference).createExpressionByPattern("$0", selectorExpression)
|
||||||
|
val newContext = newExpression.analyzeAsReplacement(parent, context)
|
||||||
|
val descriptor = newExpression.getResolvedCall(newContext)?.resultingDescriptor as? FunctionDescriptor
|
||||||
|
if (descriptor?.isOperator == true) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtDotQualifiedExpression.isReferenceToBuildInEnumFunctionInEnumClass(containingClass: KtClass): Boolean {
|
||||||
|
return containingClass.isEnum()
|
||||||
|
&& (isReferenceToBuiltInEnumFunction() || (parent as? KtElement)?.isReferenceToBuiltInEnumFunction() == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtElement?.isReferenceToClassOrObject(context: BindingContext): Boolean {
|
||||||
|
if (this !is KtQualifiedExpression) return false
|
||||||
|
val descriptor = getResolvedCall(context)?.resultingDescriptor
|
||||||
|
return descriptor == null || descriptor is ConstructorDescriptor || descriptor is FakeCallableDescriptorForObject
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ClassDescriptor.hasSameNameMemberAs(expression: KtExpression?, context: BindingContext): Boolean {
|
||||||
|
when (val descriptor = expression?.getResolvedCall(context)?.resultingDescriptor) {
|
||||||
|
is PropertyDescriptor -> {
|
||||||
|
val name = descriptor.name
|
||||||
|
if (findMemberVariable(name) != null) return true
|
||||||
|
|
||||||
|
val type = descriptor.type
|
||||||
|
val javaGetter = findMemberFunction(Name.identifier(JvmAbi.getterName(name.asString())))
|
||||||
|
?.takeIf { f -> f is JavaMethodDescriptor || f.overriddenDescriptors.any { it is JavaMethodDescriptor } }
|
||||||
|
if (javaGetter?.valueParameters?.isEmpty() == true && javaGetter.returnType?.makeNotNullable() == type) return true
|
||||||
|
|
||||||
|
val variable = expression.getResolutionScope().findVariable(name, NoLookupLocation.FROM_IDE)
|
||||||
|
if (variable != null && variable.isLocalOrExtension(this)) return true
|
||||||
}
|
}
|
||||||
|
is FunctionDescriptor -> {
|
||||||
(reference as? KtSimpleNameExpression)?.getReceiverExpression()?.getQualifiedElementSelector()
|
val name = descriptor.name
|
||||||
?.mainReference?.resolveToDescriptors(context)?.firstOrNull()
|
if (findMemberFunction(name) != null) return true
|
||||||
?.let { if (it != containingClassDescriptor) return false }
|
val function = expression.getResolutionScope().findFunction(name, NoLookupLocation.FROM_IDE)
|
||||||
|
if (function != null && function.isLocalOrExtension(this)) return true
|
||||||
if (grandParent is KtQualifiedExpression) {
|
|
||||||
val grandParentDescriptor = grandParent.getResolvedCall(context)?.resultingDescriptor ?: return false
|
|
||||||
if (grandParentDescriptor is ConstructorDescriptor || grandParentDescriptor is FakeCallableDescriptorForObject) return false
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (selectorExpression is KtCallExpression && referenceText == selectorExpression.calleeExpression?.text) {
|
private fun <D : MemberDescriptor> ClassDescriptor.findMemberByName(name: Name, find: ClassDescriptor.(Name) -> D?): D? {
|
||||||
val newExpression = KtPsiFactory(reference).createExpressionByPattern("$0", selectorExpression)
|
val member = find(name)
|
||||||
val newContext = newExpression.analyzeAsReplacement(parent, context)
|
if (member != null) return member
|
||||||
val descriptor = newExpression.getResolvedCall(newContext)?.resultingDescriptor as? FunctionDescriptor
|
|
||||||
if (descriptor?.isOperator == true) return false
|
val memberInSuperClass = getSuperClassNotAny()?.findMemberByName(name, find)
|
||||||
|
if (memberInSuperClass != null) return memberInSuperClass
|
||||||
|
|
||||||
|
getSuperInterfaces().forEach {
|
||||||
|
val memberInInterface = it.findMemberByName(name, find)
|
||||||
|
if (memberInInterface != null) return memberInInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ClassDescriptor.findMemberVariable(name: Name): PropertyDescriptor? = findMemberByName(name) {
|
||||||
|
unsubstitutedMemberScope.getContributedVariables(it, NoLookupLocation.FROM_IDE).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ClassDescriptor.findMemberFunction(name: Name): FunctionDescriptor? = findMemberByName(name) {
|
||||||
|
unsubstitutedMemberScope.getContributedFunctions(it, NoLookupLocation.FROM_IDE).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CallableDescriptor.isLocalOrExtension(extensionClassDescriptor: ClassDescriptor): Boolean {
|
||||||
|
return visibility == DescriptorVisibilities.LOCAL ||
|
||||||
|
extensionReceiverParameter?.type?.constructor?.declarationDescriptor == extensionClassDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RemoveRedundantCompanionReferenceFix : LocalQuickFix {
|
||||||
|
override fun getName() = KotlinBundle.message("remove.redundant.companion.reference.fix.text")
|
||||||
|
|
||||||
|
override fun getFamilyName() = name
|
||||||
|
|
||||||
|
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||||
|
val expression = descriptor.psiElement as? KtReferenceExpression ?: return
|
||||||
|
removeRedundantCompanionReference(expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun removeRedundantCompanionReference(expression: KtReferenceExpression) {
|
||||||
|
val parent = expression.parent as? KtDotQualifiedExpression ?: return
|
||||||
|
val selector = parent.selectorExpression ?: return
|
||||||
|
val receiver = parent.receiverExpression
|
||||||
|
if (expression == receiver) parent.replace(selector) else parent.replace(receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <D : MemberDescriptor> ClassDescriptor.findMemberByName(name: Name, find: ClassDescriptor.(Name) -> D?): D? {
|
|
||||||
val member = find(name)
|
|
||||||
if (member != null) return member
|
|
||||||
|
|
||||||
val memberInSuperClass = getSuperClassNotAny()?.findMemberByName(name, find)
|
|
||||||
if (memberInSuperClass != null) return memberInSuperClass
|
|
||||||
|
|
||||||
getSuperInterfaces().forEach {
|
|
||||||
val memberInInterface = it.findMemberByName(name, find)
|
|
||||||
if (memberInInterface != null) return memberInInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ClassDescriptor.findMemberVariable(name: Name): PropertyDescriptor? = findMemberByName(name) {
|
|
||||||
unsubstitutedMemberScope.getContributedVariables(it, NoLookupLocation.FROM_IDE).firstOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ClassDescriptor.findMemberFunction(name: Name): FunctionDescriptor? = findMemberByName(name) {
|
|
||||||
unsubstitutedMemberScope.getContributedFunctions(it, NoLookupLocation.FROM_IDE).firstOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun CallableDescriptor.isLocalOrExtension(extensionClassDescriptor: ClassDescriptor): Boolean {
|
|
||||||
return visibility == DescriptorVisibilities.LOCAL ||
|
|
||||||
extensionReceiverParameter?.type?.constructor?.declarationDescriptor == extensionClassDescriptor
|
|
||||||
}
|
|
||||||
|
|
||||||
class RemoveRedundantCompanionReferenceFix : LocalQuickFix {
|
|
||||||
override fun getName() = KotlinBundle.message("remove.redundant.companion.reference.fix.text")
|
|
||||||
|
|
||||||
override fun getFamilyName() = name
|
|
||||||
|
|
||||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
|
||||||
val expression = descriptor.psiElement as? KtReferenceExpression ?: return
|
|
||||||
removeRedundantCompanionReference(expression)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun removeRedundantCompanionReference(expression: KtReferenceExpression) {
|
|
||||||
val parent = expression.parent as? KtDotQualifiedExpression ?: return
|
|
||||||
val selector = parent.selectorExpression ?: return
|
|
||||||
val receiver = parent.receiverExpression
|
|
||||||
if (expression == receiver) parent.replace(selector) else parent.replace(receiver)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-8
@@ -19,7 +19,7 @@ import org.jetbrains.kotlin.idea.core.ShortenReferences
|
|||||||
import org.jetbrains.kotlin.idea.core.compareDescriptors
|
import org.jetbrains.kotlin.idea.core.compareDescriptors
|
||||||
import org.jetbrains.kotlin.idea.core.unwrapIfFakeOverride
|
import org.jetbrains.kotlin.idea.core.unwrapIfFakeOverride
|
||||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||||
import org.jetbrains.kotlin.idea.intentions.callExpression
|
import org.jetbrains.kotlin.idea.intentions.isReferenceToBuiltInEnumFunction
|
||||||
import org.jetbrains.kotlin.idea.references.mainReference
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.idea.references.resolveToDescriptors
|
import org.jetbrains.kotlin.idea.references.resolveToDescriptors
|
||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
@@ -36,10 +36,6 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
|||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
|
|
||||||
class RemoveRedundantQualifierNameInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
|
class RemoveRedundantQualifierNameInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
|
||||||
companion object {
|
|
||||||
private val ENUM_STATIC_METHODS = listOf("values", "valueOf")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In order to detect that `foo()` and `GrandBase.foo()` point to the same method,
|
* In order to detect that `foo()` and `GrandBase.foo()` point to the same method,
|
||||||
* we need to unwrap fake overrides from descriptors. If we don't do that, they will
|
* we need to unwrap fake overrides from descriptors. If we don't do that, they will
|
||||||
@@ -77,7 +73,7 @@ class RemoveRedundantQualifierNameInspection : AbstractKotlinInspection(), Clean
|
|||||||
|
|
||||||
if (receiverReference?.safeAs<KtClass>()?.isEnum() == true
|
if (receiverReference?.safeAs<KtClass>()?.isEnum() == true
|
||||||
&& expressionForAnalyze.getParentOfTypesAndPredicate(true, KtClass::class.java) { it.isEnum() } != receiverReference
|
&& expressionForAnalyze.getParentOfTypesAndPredicate(true, KtClass::class.java) { it.isEnum() } != receiverReference
|
||||||
&& (expressionForAnalyze.isEnumStaticMethodCall() || expressionForAnalyze.isCompanionObjectReference())
|
&& (expressionForAnalyze.isReferenceToBuiltInEnumFunction() || expressionForAnalyze.isCompanionObjectReference())
|
||||||
) return
|
) return
|
||||||
|
|
||||||
val context = originalExpression.analyze()
|
val context = originalExpression.analyze()
|
||||||
@@ -107,8 +103,6 @@ class RemoveRedundantQualifierNameInspection : AbstractKotlinInspection(), Clean
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtDotQualifiedExpression.isEnumStaticMethodCall() = callExpression?.calleeExpression?.text in ENUM_STATIC_METHODS
|
|
||||||
|
|
||||||
private fun KtDotQualifiedExpression.isCompanionObjectReference(): Boolean {
|
private fun KtDotQualifiedExpression.isCompanionObjectReference(): Boolean {
|
||||||
val selector = receiverExpression.safeAs<KtDotQualifiedExpression>()?.selectorExpression ?: selectorExpression
|
val selector = receiverExpression.safeAs<KtDotQualifiedExpression>()?.selectorExpression ?: selectorExpression
|
||||||
return selector?.referenceExpression()?.mainReference?.resolve()?.safeAs<KtObjectDeclaration>()?.isCompanion() == true
|
return selector?.referenceExpression()?.mainReference?.resolve()?.safeAs<KtObjectDeclaration>()?.isCompanion() == true
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSuppo
|
|||||||
import org.jetbrains.kotlin.idea.core.toDescriptor
|
import org.jetbrains.kotlin.idea.core.toDescriptor
|
||||||
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory
|
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory
|
||||||
import org.jetbrains.kotlin.idea.findUsages.handlers.KotlinFindClassUsagesHandler
|
import org.jetbrains.kotlin.idea.findUsages.handlers.KotlinFindClassUsagesHandler
|
||||||
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
|
import org.jetbrains.kotlin.idea.intentions.isReferenceToBuiltInEnumFunction
|
||||||
import org.jetbrains.kotlin.idea.intentions.callExpression
|
|
||||||
import org.jetbrains.kotlin.idea.intentions.isFinalizeMethod
|
import org.jetbrains.kotlin.idea.intentions.isFinalizeMethod
|
||||||
import org.jetbrains.kotlin.idea.isMainFunction
|
import org.jetbrains.kotlin.idea.isMainFunction
|
||||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||||
@@ -69,15 +68,12 @@ import org.jetbrains.kotlin.idea.util.hasActualsFor
|
|||||||
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
|
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||||
import org.jetbrains.kotlin.resolve.isInlineClassType
|
import org.jetbrains.kotlin.resolve.isInlineClassType
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
|
||||||
import org.jetbrains.kotlin.util.findCallableMemberBySignature
|
import org.jetbrains.kotlin.util.findCallableMemberBySignature
|
||||||
import java.awt.GridBagConstraints
|
import java.awt.GridBagConstraints
|
||||||
import java.awt.GridBagLayout
|
import java.awt.GridBagLayout
|
||||||
@@ -92,10 +88,6 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
|||||||
|
|
||||||
private val KOTLIN_ADDITIONAL_ANNOTATIONS = listOf("kotlin.test.*", "kotlin.js.JsExport")
|
private val KOTLIN_ADDITIONAL_ANNOTATIONS = listOf("kotlin.test.*", "kotlin.js.JsExport")
|
||||||
|
|
||||||
private val KOTLIN_BUILTIN_ENUM_FUNCTIONS = listOf(FqName("kotlin.enumValues"), FqName("kotlin.enumValueOf"))
|
|
||||||
|
|
||||||
private val ENUM_STATIC_METHODS = listOf("values", "valueOf")
|
|
||||||
|
|
||||||
private fun KtDeclaration.hasKotlinAdditionalAnnotation() =
|
private fun KtDeclaration.hasKotlinAdditionalAnnotation() =
|
||||||
this is KtNamedDeclaration && checkAnnotatedUsingPatterns(this, KOTLIN_ADDITIONAL_ANNOTATIONS)
|
this is KtNamedDeclaration && checkAnnotatedUsingPatterns(this, KOTLIN_ADDITIONAL_ANNOTATIONS)
|
||||||
|
|
||||||
@@ -470,30 +462,13 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun hasBuiltInEnumFunctionReference(reference: PsiReference): Boolean {
|
private fun hasBuiltInEnumFunctionReference(reference: PsiReference): Boolean {
|
||||||
return when (val parent = reference.element.getParentOfTypes(
|
val parent = reference.element.getParentOfTypes(
|
||||||
true,
|
true,
|
||||||
KtTypeReference::class.java,
|
KtTypeReference::class.java,
|
||||||
KtQualifiedExpression::class.java,
|
KtQualifiedExpression::class.java,
|
||||||
KtCallableReferenceExpression::class.java
|
KtCallableReferenceExpression::class.java
|
||||||
)) {
|
) ?: return false
|
||||||
is KtTypeReference -> {
|
return parent.isReferenceToBuiltInEnumFunction()
|
||||||
val target = (parent.getStrictParentOfType<KtTypeArgumentList>() ?: parent)
|
|
||||||
.getParentOfTypes(true, KtCallExpression::class.java, KtCallableDeclaration::class.java)
|
|
||||||
when (target) {
|
|
||||||
is KtCallExpression -> target.isCalling(KOTLIN_BUILTIN_ENUM_FUNCTIONS)
|
|
||||||
is KtCallableDeclaration -> {
|
|
||||||
target.anyDescendantOfType<KtCallExpression> {
|
|
||||||
val context = it.analyze(BodyResolveMode.PARTIAL_WITH_CFA)
|
|
||||||
it.isCalling(KOTLIN_BUILTIN_ENUM_FUNCTIONS, context) && it.isUsedAsExpression(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is KtQualifiedExpression -> parent.callExpression?.calleeExpression?.text in ENUM_STATIC_METHODS
|
|
||||||
is KtCallableReferenceExpression -> parent.callableReference.text in ENUM_STATIC_METHODS
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkPrivateDeclaration(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor?): Boolean {
|
private fun checkPrivateDeclaration(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor?): Boolean {
|
||||||
|
|||||||
@@ -17,18 +17,19 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
|||||||
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
|
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
|
||||||
import org.jetbrains.kotlin.idea.core.replaced
|
import org.jetbrains.kotlin.idea.core.replaced
|
||||||
import org.jetbrains.kotlin.idea.core.setType
|
import org.jetbrains.kotlin.idea.core.setType
|
||||||
|
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
|
||||||
import org.jetbrains.kotlin.idea.references.mainReference
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.idea.references.resolveToDescriptors
|
import org.jetbrains.kotlin.idea.references.resolveToDescriptors
|
||||||
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
|
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.containingClass
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
|
||||||
import org.jetbrains.kotlin.resolve.ArrayFqNames
|
import org.jetbrains.kotlin.resolve.ArrayFqNames
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
@@ -368,3 +369,30 @@ fun KotlinType.reflectToRegularFunctionType(): KotlinType {
|
|||||||
|
|
||||||
val CallableDescriptor.isInvokeOperator: Boolean
|
val CallableDescriptor.isInvokeOperator: Boolean
|
||||||
get() = this is FunctionDescriptor && isOperator && name == OperatorNameConventions.INVOKE
|
get() = this is FunctionDescriptor && isOperator && name == OperatorNameConventions.INVOKE
|
||||||
|
|
||||||
|
private val KOTLIN_BUILTIN_ENUM_FUNCTIONS = listOf(FqName("kotlin.enumValues"), FqName("kotlin.enumValueOf"))
|
||||||
|
|
||||||
|
private val ENUM_STATIC_METHODS = listOf("values", "valueOf")
|
||||||
|
|
||||||
|
fun KtElement.isReferenceToBuiltInEnumFunction(): Boolean {
|
||||||
|
return when (this) {
|
||||||
|
is KtTypeReference -> {
|
||||||
|
val target = (parent.getStrictParentOfType<KtTypeArgumentList>() ?: this)
|
||||||
|
.getParentOfTypes(true, KtCallExpression::class.java, KtCallableDeclaration::class.java)
|
||||||
|
when (target) {
|
||||||
|
is KtCallExpression -> target.isCalling(KOTLIN_BUILTIN_ENUM_FUNCTIONS)
|
||||||
|
is KtCallableDeclaration -> {
|
||||||
|
target.anyDescendantOfType<KtCallExpression> {
|
||||||
|
val context = it.analyze(BodyResolveMode.PARTIAL_WITH_CFA)
|
||||||
|
it.isCalling(KOTLIN_BUILTIN_ENUM_FUNCTIONS, context) && it.isUsedAsExpression(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is KtQualifiedExpression -> this.callExpression?.calleeExpression?.text in ENUM_STATIC_METHODS
|
||||||
|
is KtCallExpression -> this.calleeExpression?.text in ENUM_STATIC_METHODS
|
||||||
|
is KtCallableReferenceExpression -> this.callableReference.text in ENUM_STATIC_METHODS
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
enum class A {
|
||||||
|
TEST;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun valueOf(name: String) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
A.<caret>Companion.valueOf("TEST")
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
enum class A {
|
||||||
|
TEST;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun valueOf(name: String) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
<caret>Companion.valueOf("TEST")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
enum class A {
|
||||||
|
TEST;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun values() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
A.<caret>Companion.values()
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
enum class A {
|
||||||
|
TEST;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun values() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
<caret>Companion.values()
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -7417,6 +7417,26 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/directCompanion.kt");
|
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/directCompanion.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("enumValueOf.kt")
|
||||||
|
public void testEnumValueOf() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/enumValueOf.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("enumValueOf2.kt")
|
||||||
|
public void testEnumValueOf2() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/enumValueOf2.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("enumValues.kt")
|
||||||
|
public void testEnumValues() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/enumValues.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("enumValues2.kt")
|
||||||
|
public void testEnumValues2() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/enumValues2.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("functionReference.kt")
|
@TestMetadata("functionReference.kt")
|
||||||
public void testFunctionReference() throws Exception {
|
public void testFunctionReference() throws Exception {
|
||||||
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/functionReference.kt");
|
runTest("idea/testData/inspectionsLocal/redundantCompanionReference/functionReference.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user