FIR IDE: Ensure all fixes provided by HLDiagnosticFixFactory are of type
QuickFixActionBase. This allows us to verify in the quickfix tests that the action comes from a diagnostic-based quickfix, and not an available intention. We are adding APIs that wrap IntentionActions as QuickFixActionBase.
This commit is contained in:
committed by
TeamCityServer
parent
43a8299f48
commit
783e5972d0
+17
-13
@@ -12,7 +12,7 @@ import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.KtImplementMembersHandler.Companion.getUnimplementedMembers
|
||||
import org.jetbrains.kotlin.idea.core.util.KotlinIdeaCoreBundle
|
||||
import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory
|
||||
import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactoryFromIntentionActions
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.frontend.api.analyse
|
||||
import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic
|
||||
@@ -120,21 +120,25 @@ internal class KtImplementAsConstructorParameterQuickfix(private val members: Co
|
||||
|
||||
object MemberNotImplementedQuickfixFactories {
|
||||
|
||||
val abstractMemberNotImplemented = diagnosticFixFactory(KtFirDiagnostic.AbstractMemberNotImplemented::class) { diagnostic ->
|
||||
getUnimplementedMemberFixes(diagnostic.psi)
|
||||
}
|
||||
val abstractMemberNotImplemented =
|
||||
diagnosticFixFactoryFromIntentionActions(KtFirDiagnostic.AbstractMemberNotImplemented::class) { diagnostic ->
|
||||
getUnimplementedMemberFixes(diagnostic.psi)
|
||||
}
|
||||
|
||||
val abstractClassMemberNotImplemented = diagnosticFixFactory(KtFirDiagnostic.AbstractClassMemberNotImplemented::class) { diagnostic ->
|
||||
getUnimplementedMemberFixes(diagnostic.psi)
|
||||
}
|
||||
val abstractClassMemberNotImplemented =
|
||||
diagnosticFixFactoryFromIntentionActions(KtFirDiagnostic.AbstractClassMemberNotImplemented::class) { diagnostic ->
|
||||
getUnimplementedMemberFixes(diagnostic.psi)
|
||||
}
|
||||
|
||||
val manyInterfacesMemberNotImplemented = diagnosticFixFactory(KtFirDiagnostic.ManyInterfacesMemberNotImplemented::class) { diagnostic ->
|
||||
getUnimplementedMemberFixes(diagnostic.psi)
|
||||
}
|
||||
val manyInterfacesMemberNotImplemented =
|
||||
diagnosticFixFactoryFromIntentionActions(KtFirDiagnostic.ManyInterfacesMemberNotImplemented::class) { diagnostic ->
|
||||
getUnimplementedMemberFixes(diagnostic.psi)
|
||||
}
|
||||
|
||||
val manyImplMemberNotImplemented = diagnosticFixFactory(KtFirDiagnostic.ManyImplMemberNotImplemented::class) { diagnostic ->
|
||||
getUnimplementedMemberFixes(diagnostic.psi, false)
|
||||
}
|
||||
val manyImplMemberNotImplemented =
|
||||
diagnosticFixFactoryFromIntentionActions(KtFirDiagnostic.ManyImplMemberNotImplemented::class) { diagnostic ->
|
||||
getUnimplementedMemberFixes(diagnostic.psi, false)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun KtAnalysisSession.getUnimplementedMemberFixes(
|
||||
|
||||
@@ -20,7 +20,8 @@ import kotlin.reflect.KClass
|
||||
|
||||
abstract class AbstractHLIntention<PSI : KtElement, INPUT : HLApplicatorInput>(
|
||||
elementType: KClass<PSI>,
|
||||
) : SelfTargetingIntention<PSI>(elementType.java, { "" }, { "" }) {
|
||||
val applicator: HLApplicator<PSI, INPUT>
|
||||
) : SelfTargetingIntention<PSI>(elementType.java, applicator::getFamilyName) {
|
||||
final override fun isApplicableTo(element: PSI, caretOffset: Int): Boolean {
|
||||
val project = element.project// TODO expensive operation, may require traversing the tree up to containing PsiFile
|
||||
if (!applicator.isApplicableByPsi(element, project)) return false
|
||||
@@ -61,6 +62,5 @@ abstract class AbstractHLIntention<PSI : KtElement, INPUT : HLApplicatorInput>(
|
||||
}
|
||||
|
||||
abstract val applicabilityRange: HLApplicabilityRange<PSI>
|
||||
abstract val applicator: HLApplicator<PSI, INPUT>
|
||||
abstract val inputProvider: HLApplicatorInputProvider<PSI, INPUT>
|
||||
}
|
||||
|
||||
+37
-10
@@ -6,15 +6,19 @@
|
||||
package org.jetbrains.kotlin.idea.fir.api.fixes
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.idea.api.applicator.HLApplicator
|
||||
import org.jetbrains.kotlin.idea.api.applicator.HLApplicatorInput
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnosticWithPsi
|
||||
import org.jetbrains.kotlin.idea.quickfix.QuickFixActionBase
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
sealed class HLDiagnosticFixFactory<DIAGNOSTIC : KtDiagnosticWithPsi<*>> {
|
||||
abstract fun KtAnalysisSession.createQuickFixes(diagnostic: DIAGNOSTIC): List<IntentionAction>
|
||||
abstract fun KtAnalysisSession.createQuickFixes(diagnostic: DIAGNOSTIC): List<QuickFixActionBase<*>>
|
||||
abstract val diagnosticClass: KClass<DIAGNOSTIC>
|
||||
}
|
||||
|
||||
@@ -27,11 +31,11 @@ private class HLDiagnosticFixFactoryWithFixedApplicator<DIAGNOSTIC : KtDiagnosti
|
||||
createTargets.invoke(this, diagnostic).map { (target, input) -> HLQuickFix(target, input, applicator) }
|
||||
}
|
||||
|
||||
private class HLDiagnosticFixFactoryWithVariableApplicator<DIAGNOSTIC : KtDiagnosticWithPsi<*>>(
|
||||
private class HLDiagnosticFixFactoryUsingQuickFixActionBase<DIAGNOSTIC : KtDiagnosticWithPsi<*>>(
|
||||
override val diagnosticClass: KClass<DIAGNOSTIC>,
|
||||
private val createQuickFixes: KtAnalysisSession.(DIAGNOSTIC) -> List<IntentionAction>
|
||||
private val createQuickFixes: KtAnalysisSession.(DIAGNOSTIC) -> List<QuickFixActionBase<*>>
|
||||
) : HLDiagnosticFixFactory<DIAGNOSTIC>() {
|
||||
override fun KtAnalysisSession.createQuickFixes(diagnostic: DIAGNOSTIC): List<IntentionAction> =
|
||||
override fun KtAnalysisSession.createQuickFixes(diagnostic: DIAGNOSTIC): List<QuickFixActionBase<*>> =
|
||||
createQuickFixes.invoke(this, diagnostic)
|
||||
}
|
||||
|
||||
@@ -52,19 +56,42 @@ fun <DIAGNOSTIC : KtDiagnosticWithPsi<*>, TARGET_PSI : PsiElement, INPUT : HLApp
|
||||
HLDiagnosticFixFactoryWithFixedApplicator(diagnosticClass, applicator, createTargets)
|
||||
|
||||
/**
|
||||
* Returns a [HLDiagnosticFixFactory] that creates [HLQuickFix]es from a diagnostic.
|
||||
* Returns a [HLDiagnosticFixFactory] that creates [QuickFixActionBase]s from a diagnostic.
|
||||
*/
|
||||
fun <DIAGNOSTIC : KtDiagnosticWithPsi<*>> diagnosticFixFactory(
|
||||
diagnosticClass: KClass<DIAGNOSTIC>,
|
||||
createQuickFixes: KtAnalysisSession.(DIAGNOSTIC) -> List<IntentionAction>
|
||||
createQuickFixes: KtAnalysisSession.(DIAGNOSTIC) -> List<QuickFixActionBase<*>>
|
||||
): HLDiagnosticFixFactory<DIAGNOSTIC> =
|
||||
HLDiagnosticFixFactoryWithVariableApplicator(diagnosticClass, createQuickFixes)
|
||||
HLDiagnosticFixFactoryUsingQuickFixActionBase(diagnosticClass, createQuickFixes)
|
||||
|
||||
/**
|
||||
* Returns a [Collection] of [HLDiagnosticFixFactory] that creates [HLQuickFix]es from diagnostics that have the same type of [PsiElement].
|
||||
* Returns a [Collection] of [HLDiagnosticFixFactory] that creates [QuickFixActionBase]s from diagnostics that have the same type of
|
||||
* [PsiElement].
|
||||
*/
|
||||
fun <DIAGNOSTIC_PSI : PsiElement, DIAGNOSTIC : KtDiagnosticWithPsi<DIAGNOSTIC_PSI>> diagnosticFixFactories(
|
||||
vararg diagnosticClasses: KClass<out DIAGNOSTIC>,
|
||||
createQuickFixes: KtAnalysisSession.(DIAGNOSTIC) -> List<IntentionAction>
|
||||
createQuickFixes: KtAnalysisSession.(DIAGNOSTIC) -> List<QuickFixActionBase<*>>
|
||||
): Collection<HLDiagnosticFixFactory<out DIAGNOSTIC>> =
|
||||
diagnosticClasses.map { HLDiagnosticFixFactoryWithVariableApplicator(it, createQuickFixes) }
|
||||
diagnosticClasses.map { HLDiagnosticFixFactoryUsingQuickFixActionBase(it, createQuickFixes) }
|
||||
|
||||
/**
|
||||
* Returns a [HLDiagnosticFixFactory] that creates [IntentionAction]s from a diagnostic.
|
||||
*/
|
||||
fun <DIAGNOSTIC : KtDiagnosticWithPsi<*>> diagnosticFixFactoryFromIntentionActions(
|
||||
diagnosticClass: KClass<DIAGNOSTIC>,
|
||||
createIntentionActions: KtAnalysisSession.(DIAGNOSTIC) -> List<IntentionAction>
|
||||
): HLDiagnosticFixFactory<DIAGNOSTIC> {
|
||||
// Wrap the IntentionActions as QuickFixActionBase. This ensures all fixes are of type QuickFixActionBase.
|
||||
val createQuickFixes: KtAnalysisSession.(DIAGNOSTIC) -> List<QuickFixActionBase<*>> = { diagnostic ->
|
||||
val intentionActions = createIntentionActions.invoke(this, diagnostic)
|
||||
intentionActions.map { IntentionActionAsQuickFixWrapper(it, diagnostic.psi) }
|
||||
}
|
||||
return HLDiagnosticFixFactoryUsingQuickFixActionBase(diagnosticClass, createQuickFixes)
|
||||
}
|
||||
|
||||
private class IntentionActionAsQuickFixWrapper<T : PsiElement>(val intentionAction: IntentionAction, element: T) :
|
||||
QuickFixActionBase<T>(element) {
|
||||
override fun getText(): String = intentionAction.text
|
||||
override fun getFamilyName(): String = intentionAction.familyName
|
||||
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) = intentionAction.invoke(project, editor, file)
|
||||
}
|
||||
|
||||
+1
-8
@@ -25,18 +25,11 @@ import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier
|
||||
|
||||
abstract class HLAddAccessorIntention(private val addGetter: Boolean, private val addSetter: Boolean) :
|
||||
AbstractHLIntention<KtProperty, HLApplicatorInput.Empty>(KtProperty::class) {
|
||||
AbstractHLIntention<KtProperty, HLApplicatorInput.Empty>(KtProperty::class, applicator(addGetter, addSetter)) {
|
||||
override val applicabilityRange: HLApplicabilityRange<KtProperty> = applicabilityTarget { ktProperty ->
|
||||
if (ktProperty.hasInitializer()) ktProperty.nameIdentifier else ktProperty
|
||||
}
|
||||
|
||||
override val applicator: HLApplicator<KtProperty, HLApplicatorInput.Empty> = applicator {
|
||||
applyTo { ktProperty, _, _, editor ->
|
||||
AbstractAddAccessorsIntention.applyTo(ktProperty, editor, addGetter, addSetter)
|
||||
}
|
||||
familyAndActionName(AbstractAddAccessorsIntention.createFamilyName(addGetter, addSetter))
|
||||
}
|
||||
|
||||
override val inputProvider: HLApplicatorInputProvider<KtProperty, HLApplicatorInput.Empty> = inputProvider { ktProperty ->
|
||||
val symbol = ktProperty.getVariableSymbol() as? KtPropertySymbol ?: return@inputProvider null
|
||||
if (symbol.containsAnnotation(JVM_FIELD_CLASS_ID)) return@inputProvider null
|
||||
|
||||
+12
-11
@@ -6,8 +6,8 @@
|
||||
package org.jetbrains.kotlin.idea.fir.intentions.declarations
|
||||
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.fir.api.*
|
||||
import org.jetbrains.kotlin.idea.api.applicator.with
|
||||
import org.jetbrains.kotlin.idea.fir.api.*
|
||||
import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicabilityRange
|
||||
import org.jetbrains.kotlin.idea.fir.api.applicator.inputProvider
|
||||
import org.jetbrains.kotlin.idea.fir.applicators.ApplicabilityRanges
|
||||
@@ -15,16 +15,7 @@ import org.jetbrains.kotlin.idea.fir.applicators.CallableReturnTypeUpdaterApplic
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class HLSpecifyExplicitTypeForCallableDeclarationIntention :
|
||||
AbstractHLIntention<KtCallableDeclaration, CallableReturnTypeUpdaterApplicator.Type>(
|
||||
KtCallableDeclaration::class
|
||||
) {
|
||||
override val applicator = CallableReturnTypeUpdaterApplicator.applicator.with {
|
||||
isApplicableByPsi { declaration: KtCallableDeclaration ->
|
||||
if (declaration is KtConstructor<*> || declaration is KtFunctionLiteral) return@isApplicableByPsi false
|
||||
declaration.typeReference == null && (declaration as? KtNamedFunction)?.hasBlockBody() != true
|
||||
}
|
||||
familyAndActionName(KotlinBundle.lazyMessage("specify.return.type.explicitly"))
|
||||
}
|
||||
AbstractHLIntention<KtCallableDeclaration, CallableReturnTypeUpdaterApplicator.Type>(KtCallableDeclaration::class, applicator) {
|
||||
override val applicabilityRange: HLApplicabilityRange<KtCallableDeclaration> = ApplicabilityRanges.SELF
|
||||
|
||||
override val inputProvider = inputProvider<KtCallableDeclaration, CallableReturnTypeUpdaterApplicator.Type> { declaration ->
|
||||
@@ -32,4 +23,14 @@ class HLSpecifyExplicitTypeForCallableDeclarationIntention :
|
||||
val denotableType = returnType.approximateToSuperPublicDenotable() ?: returnType
|
||||
with(CallableReturnTypeUpdaterApplicator.Type) { createByKtType(denotableType) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val applicator = CallableReturnTypeUpdaterApplicator.applicator.with {
|
||||
isApplicableByPsi { declaration: KtCallableDeclaration ->
|
||||
if (declaration is KtConstructor<*> || declaration is KtFunctionLiteral) return@isApplicableByPsi false
|
||||
declaration.typeReference == null && (declaration as? KtNamedFunction)?.hasBlockBody() != true
|
||||
}
|
||||
familyAndActionName(KotlinBundle.lazyMessage("specify.return.type.explicitly"))
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-11
@@ -5,7 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix.fixes
|
||||
|
||||
import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory
|
||||
import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactoryFromIntentionActions
|
||||
import org.jetbrains.kotlin.idea.fir.intentions.HLAddGetterAndSetterIntention
|
||||
import org.jetbrains.kotlin.idea.fir.intentions.HLAddGetterIntention
|
||||
import org.jetbrains.kotlin.idea.fir.intentions.HLAddSetterIntention
|
||||
@@ -13,15 +13,16 @@ import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
|
||||
object AddAccessorsFactories {
|
||||
val addAccessorsToUninitializedProperty = diagnosticFixFactory(KtFirDiagnostic.MustBeInitializedOrBeAbstract::class) { diagnostic ->
|
||||
val property: KtProperty = diagnostic.psi
|
||||
val addGetter = property.getter == null
|
||||
val addSetter = property.isVar && property.setter == null
|
||||
when {
|
||||
addGetter && addSetter -> listOf(HLAddGetterAndSetterIntention())
|
||||
addGetter -> listOf(HLAddGetterIntention())
|
||||
addSetter -> listOf(HLAddSetterIntention())
|
||||
else -> emptyList()
|
||||
val addAccessorsToUninitializedProperty =
|
||||
diagnosticFixFactoryFromIntentionActions(KtFirDiagnostic.MustBeInitializedOrBeAbstract::class) { diagnostic ->
|
||||
val property: KtProperty = diagnostic.psi
|
||||
val addGetter = property.getter == null
|
||||
val addSetter = property.isVar && property.setter == null
|
||||
when {
|
||||
addGetter && addSetter -> listOf(HLAddGetterAndSetterIntention())
|
||||
addGetter -> listOf(HLAddGetterIntention())
|
||||
addSetter -> listOf(HLAddSetterIntention())
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix.fixes
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
@@ -30,7 +29,7 @@ object AddExclExclCallFixFactories {
|
||||
getFixForUnsafeCall(diagnostic.psi)
|
||||
}
|
||||
|
||||
private fun KtAnalysisSession.getFixForUnsafeCall(psi: PsiElement): List<IntentionAction> {
|
||||
private fun KtAnalysisSession.getFixForUnsafeCall(psi: PsiElement): List<AddExclExclCallFix> {
|
||||
val (target, hasImplicitReceiver) = when (val unwrapped = psi.unwrapParenthesesLabelsAndAnnotations()) {
|
||||
// `foo.bar` -> `foo!!.bar`
|
||||
is KtDotQualifiedExpression -> unwrapped.receiverExpression to false
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix.fixes
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic
|
||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
|
||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeNullability
|
||||
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
@@ -36,7 +36,7 @@ object TypeMismatchFactories {
|
||||
psi: PsiElement,
|
||||
expectedType: KtType,
|
||||
actualType: KtType
|
||||
): List<IntentionAction> {
|
||||
): List<AddExclExclCallFix> {
|
||||
// TODO: Add more fixes than just AddExclExclCallFix when available.
|
||||
if (!expectedType.canBeNull && actualType.canBeNull) {
|
||||
// We don't want to offer AddExclExclCallFix if we know the expression is definitely null, e.g.:
|
||||
|
||||
+2
-3
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix.fixes
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.SmartPsiElementPointer
|
||||
@@ -180,7 +179,7 @@ object WrapWithSafeLetCallFixFactories {
|
||||
private fun KtAnalysisSession.createWrapWithSafeLetCallInputForNullableExpressionIfMoreThanImmediateParentIsWrapped(
|
||||
nullableExpression: KtExpression?,
|
||||
isImplicitInvokeCallToMemberProperty: Boolean = false,
|
||||
): List<IntentionAction> {
|
||||
): List<HLQuickFix<KtExpression, Input>> {
|
||||
val surroundingExpression = nullableExpression?.surroundingExpression
|
||||
if (
|
||||
surroundingExpression == null ||
|
||||
@@ -205,7 +204,7 @@ object WrapWithSafeLetCallFixFactories {
|
||||
isImplicitInvokeCallToMemberProperty: Boolean = false,
|
||||
surroundingExpression: KtExpression? = findParentExpressionAtNullablePosition(nullableExpression)
|
||||
?: nullableExpression?.surroundingExpression
|
||||
): List<IntentionAction> {
|
||||
): List<HLQuickFix<KtExpression, Input>> {
|
||||
if (nullableExpression == null || surroundingExpression == null) return emptyList()
|
||||
val existingNames =
|
||||
nullableExpression.containingKtFile.getScopeContextForPosition(nullableExpression).scopes.getPossibleCallableNames()
|
||||
|
||||
Reference in New Issue
Block a user