diff --git a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/KotlinRedundantOverrideInspection.kt b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/KotlinRedundantOverrideInspection.kt index ee66a0e0b941..8deb58394729 100644 --- a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/KotlinRedundantOverrideInspection.kt +++ b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/KotlinRedundantOverrideInspection.kt @@ -6,6 +6,7 @@ import com.intellij.codeInspection.CleanupLocalInspectionTool import com.intellij.codeInspection.LocalInspectionToolSession import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder +import com.intellij.codeInspection.util.InspectionMessage import com.intellij.modcommand.ModPsiUpdater import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange @@ -24,8 +25,9 @@ import org.jetbrains.kotlin.idea.base.analysis.api.utils.allOverriddenSymbolsWit import org.jetbrains.kotlin.idea.base.analysis.api.utils.isJavaSourceOrLibrary import org.jetbrains.kotlin.idea.base.psi.KotlinPsiHeuristics import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinApplicableInspectionBase import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinModCommandQuickFix -import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection +import org.jetbrains.kotlin.idea.codeinsight.api.applicators.ApplicabilityRange import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens.MODIFIER_KEYWORDS_ARRAY import org.jetbrains.kotlin.lexer.KtTokens.OVERRIDE_KEYWORD @@ -39,79 +41,87 @@ import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.synthetic.canBePropertyAccessor -internal class KotlinRedundantOverrideInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool { - override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): KtVisitorVoid = - namedFunctionVisitor(fun(function) { - val funKeyword = function.funKeyword ?: return - val modifierList = function.modifierList ?: return - val overrideKeyword = modifierList.getModifier(OVERRIDE_KEYWORD) - if (overrideKeyword == null) return - if (MODIFIER_EXCLUDE_OVERRIDE.any { modifierList.hasModifier(it) }) return - if (KotlinPsiHeuristics.hasNonSuppressAnnotations(function)) return +internal class KotlinRedundantOverrideInspection : KotlinApplicableInspectionBase.Simple(), CleanupLocalInspectionTool { + override fun getProblemDescription(element: KtNamedFunction, context: Unit): @InspectionMessage String = + KotlinBundle.message("redundant.overriding.method") - val qualifiedExpression = function.qualifiedExpression() ?: return - val superExpression = qualifiedExpression.receiverExpression as? KtSuperExpression ?: return - if (superExpression.superTypeQualifier != null) return + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): KtVisitor<*, *> = + namedFunctionVisitor { function -> visitTargetElement(function, holder, isOnTheFly) } - val superCallElement = qualifiedExpression.selectorExpression as? KtCallElement ?: return - if (!isSameFunctionName(superCallElement, function)) return - if (!isSameArguments(superCallElement, function)) return + override fun isApplicableByPsi(element: KtNamedFunction): Boolean { + val modifierList = element.modifierList ?: return false + val overrideKeyword = modifierList.getModifier(OVERRIDE_KEYWORD) + if (overrideKeyword == null) return false + if (MODIFIER_EXCLUDE_OVERRIDE.any { modifierList.hasModifier(it) }) return false + if (KotlinPsiHeuristics.hasNonSuppressAnnotations(element)) return false - analyze(superCallElement) { - val symbol = function.symbol - val superCallInfo = superCallElement.resolveToCall() ?: return - val superFunctionCallOrNull = superCallInfo.singleFunctionCallOrNull() ?: return - val superFunctionSymbol = superFunctionCallOrNull.symbol - val superFunctionIsAny = superFunctionSymbol.callableId in CALLABLE_IDS_OF_ANY + val qualifiedExpression = element.qualifiedExpression() ?: return false + val superExpression = qualifiedExpression.receiverExpression as? KtSuperExpression ?: return false + if (superExpression.superTypeQualifier != null) return false - if (function.containingClassOrObject?.isData() == true) { - if (superFunctionIsAny) return - val allSuperOverriddenSymbols = superFunctionCallOrNull.symbol.allOverriddenSymbolsWithSelf - if (allSuperOverriddenSymbols.any { it.callableId in CALLABLE_IDS_OF_ANY }) return + val superCallElement = qualifiedExpression.selectorExpression as? KtCallElement ?: return false + if (!isSameFunctionName(superCallElement, element)) return false + if (!isSameArguments(superCallElement, element)) return false + + return true + } + + override fun getApplicableRanges(element: KtNamedFunction): List { + val funKeyword = element.funKeyword ?: return emptyList() + val overrideKeyword = element.modifierList?.getModifier(OVERRIDE_KEYWORD) ?: return emptyList() + return ApplicabilityRange.multiple(element) { listOf(funKeyword, overrideKeyword) } + } + + override fun createQuickFix(element: KtNamedFunction, context: Unit): KotlinModCommandQuickFix = + RedundantOverrideFix + + override fun KaSession.prepareContext(element: KtNamedFunction): Unit? { + val symbol = element.symbol + val qualifiedExpression = element.qualifiedExpression() + val superCallElement = qualifiedExpression?.selectorExpression as? KtCallElement ?: return null + val superCallInfo = superCallElement.resolveToCall() ?: return null + val superFunctionCallOrNull = superCallInfo.singleFunctionCallOrNull() ?: return null + val superFunctionSymbol = superFunctionCallOrNull.symbol + val superFunctionIsAny = superFunctionSymbol.callableId in CALLABLE_IDS_OF_ANY + + if (element.containingClassOrObject?.isData() == true) { + if (superFunctionIsAny) return null + val allSuperOverriddenSymbols = superFunctionCallOrNull.symbol.allOverriddenSymbolsWithSelf + if (allSuperOverriddenSymbols.any { it.callableId in CALLABLE_IDS_OF_ANY }) return null + } + + if (element.hasDerivedProperty(symbol)) return null + + val superCallValueParameters = superFunctionCallOrNull.partiallyAppliedSymbol.signature.valueParameters + val functionValueParameters = symbol.valueParameters + + if (functionValueParameters.size == superCallValueParameters.size && + functionValueParameters.zip(superCallValueParameters) + .any { + val functionParameterType = it.first.returnType + val superParameterType = it.second.returnType + val typesMatch = functionParameterType.semanticallyEquals(superParameterType) + !typesMatch } + ) return null - if (function.hasDerivedProperty(symbol)) return + val allFunctionOverriddenSymbols: Sequence = symbol.allOverriddenSymbols + // do nothing when the overridden function is from Any (e.g. `kotlin/Any.equals`) + // and super function is abstract + if (superFunctionIsAny && allFunctionOverriddenSymbols.any { it.modality == KaSymbolModality.ABSTRACT }) { + return null + } - val superCallValueParameters = superFunctionCallOrNull.partiallyAppliedSymbol.signature.valueParameters - val functionValueParameters = symbol.valueParameters + if (allFunctionOverriddenSymbols.any { it.isPackageVisibleNonJavaSymbol() }) { + return null + } - if (functionValueParameters.size == superCallValueParameters.size && - functionValueParameters.zip(superCallValueParameters) - .any { - val functionParameterType = it.first.returnType - val superParameterType = it.second.returnType - val typesMatch = functionParameterType.semanticallyEquals(superParameterType) - !typesMatch - } - ) return + if (element.isAmbiguouslyDerived(allFunctionOverriddenSymbols)) { + return null + } - val allFunctionOverriddenSymbols: Sequence = symbol.allOverriddenSymbols - // do nothing when the overridden function is from Any (e.g. `kotlin/Any.equals`) - // and super function is abstract - if (superFunctionIsAny && allFunctionOverriddenSymbols.any { it.modality == KaSymbolModality.ABSTRACT }) { - return - } - - if (allFunctionOverriddenSymbols.any { it.isPackageVisibleNonJavaSymbol() }) { - return - } - - if (function.isAmbiguouslyDerived(allFunctionOverriddenSymbols)) { - return - } - } - - val range = TextRange(overrideKeyword.startOffset, funKeyword.endOffset).shiftLeft(function.startOffset) - val descriptor = holder.manager.createProblemDescriptor( - function, - range, - KotlinBundle.message("redundant.overriding.method"), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - isOnTheFly, - RedundantOverrideFix() - ) - holder.registerProblem(descriptor) - }) + return Unit + } private fun KaCallableSymbol.isPackageVisibleNonJavaSymbol(): Boolean { if (!origin.isJavaSourceOrLibrary()) return false @@ -204,19 +214,19 @@ internal class KotlinRedundantOverrideInspection : AbstractKotlinInspection(), C delegatedSuperDeclarationTypes.any { type.isSubtypeOf(it) } } } +} - private class RedundantOverrideFix : KotlinModCommandQuickFix() { - override fun getName(): String = KotlinBundle.message("redundant.override.fix.text") +private object RedundantOverrideFix : KotlinModCommandQuickFix() { + override fun getName(): String = KotlinBundle.message("redundant.override.fix.text") - override fun getFamilyName(): String = name + override fun getFamilyName(): String = name - override fun applyFix( - project: Project, - element: KtNamedFunction, - updater: ModPsiUpdater - ) { - element.delete() - } + override fun applyFix( + project: Project, + element: KtNamedFunction, + updater: ModPsiUpdater + ) { + element.delete() } }