FIR IDE: add more KDoc to HLApplicator stuff

This commit is contained in:
Ilya Kirillov
2021-02-07 18:34:52 +01:00
parent 2554065ae9
commit cf3defbc9c
6 changed files with 96 additions and 14 deletions
@@ -84,6 +84,7 @@ abstract class AbstractHLInspection<PSI : KtElement, INPUT : HLApplicatorInput>(
} }
} }
abstract val presentation: HLPresentation<PSI> abstract val presentation: HLPresentation<PSI>
abstract val applicabilityRange: HLApplicabilityRange<PSI> abstract val applicabilityRange: HLApplicabilityRange<PSI>
abstract val inputProvider: HLApplicatorInputProvider<PSI, INPUT> abstract val inputProvider: HLApplicatorInputProvider<PSI, INPUT>
@@ -96,7 +97,6 @@ private fun <PSI : PsiElement, INPUT : HLApplicatorInput> HLApplicator<PSI, INPU
): LocalQuickFix = object : LocalQuickFix { ): LocalQuickFix = object : LocalQuickFix {
override fun startInWriteAction() = false override fun startInWriteAction() = false
@OptIn(HackToForceAllowRunningAnalyzeOnEDT::class)
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val element = descriptor.psiElement as PSI val element = descriptor.psiElement as PSI
@@ -13,19 +13,45 @@ import org.jetbrains.kotlin.idea.frontend.api.ForbidKtResolve
import kotlin.experimental.ExperimentalTypeInference import kotlin.experimental.ExperimentalTypeInference
import kotlin.reflect.KClass import kotlin.reflect.KClass
/**
* Applies a fix to the PSI, used as intention/inspection/quickfix action
* Also, knows if a fix is applicable by [isApplicableByPsi]
*
* Uses some additional information from [INPUT] to apply the element
*/
sealed class HLApplicator<in PSI : PsiElement, in INPUT : HLApplicatorInput> { sealed class HLApplicator<in PSI : PsiElement, in INPUT : HLApplicatorInput> {
/**
* Applies some fix to given [psi], can not use resolve, so all needed data should be precalculated and stored in [input]
*
* @param psi a [PsiElement] to apply fix to
* @param input additional data needed to apply the fix, the [input] can be collected by [HLApplicatorInputProvider
*/
fun applyTo(psi: PSI, input: INPUT, project: Project?, editor: Editor?) = ForbidKtResolve.forbidResolveIn("HLApplicator.applyTo") { fun applyTo(psi: PSI, input: INPUT, project: Project?, editor: Editor?) = ForbidKtResolve.forbidResolveIn("HLApplicator.applyTo") {
applyToImpl(psi, input, project, editor) applyToImpl(psi, input, project, editor)
} }
/**
* Checks if applicator is applicable to specific element, can not use resolve inside
*/
fun isApplicableByPsi(psi: PSI): Boolean = ForbidKtResolve.forbidResolveIn("HLApplicator.isApplicableByPsi") { fun isApplicableByPsi(psi: PSI): Boolean = ForbidKtResolve.forbidResolveIn("HLApplicator.isApplicableByPsi") {
isApplicableByPsiImpl(psi) isApplicableByPsiImpl(psi)
} }
/**
* Action name which will be as text in inspections/intentions
*
* @see com.intellij.codeInsight.intention.IntentionAction.getText
*/
fun getActionName(psi: PSI, input: INPUT): String = ForbidKtResolve.forbidResolveIn("HLApplicator.getActionName") { fun getActionName(psi: PSI, input: INPUT): String = ForbidKtResolve.forbidResolveIn("HLApplicator.getActionName") {
getActionNameImpl(psi, input) getActionNameImpl(psi, input)
} }
/**
* Family name which will be used in inspections/intentions
*
* @see com.intellij.codeInsight.intention.IntentionAction.getFamilyName
*/
fun getFamilyName(): String = ForbidKtResolve.forbidResolveIn("HLApplicator.getFamilyName") { fun getFamilyName(): String = ForbidKtResolve.forbidResolveIn("HLApplicator.getFamilyName") {
getFamilyNameImpl() getFamilyNameImpl()
} }
@@ -36,6 +62,9 @@ sealed class HLApplicator<in PSI : PsiElement, in INPUT : HLApplicatorInput> {
protected abstract fun getFamilyNameImpl(): String protected abstract fun getFamilyNameImpl(): String
} }
/**
* Create a copy of an applicator with some components replaced
*/
fun <PSI : PsiElement, NEW_PSI : PSI, INPUT : HLApplicatorInput> HLApplicator<PSI, INPUT>.with( fun <PSI : PsiElement, NEW_PSI : PSI, INPUT : HLApplicatorInput> HLApplicator<PSI, INPUT>.with(
init: HLApplicatorBuilder<NEW_PSI, INPUT>.(olApplicator: HLApplicator<PSI, INPUT>) -> Unit init: HLApplicatorBuilder<NEW_PSI, INPUT>.(olApplicator: HLApplicator<PSI, INPUT>) -> Unit
): HLApplicator<NEW_PSI, INPUT> = when (this@with) { ): HLApplicator<NEW_PSI, INPUT> = when (this@with) {
@@ -47,8 +76,12 @@ fun <PSI : PsiElement, NEW_PSI : PSI, INPUT : HLApplicatorInput> HLApplicator<PS
} }
} }
/**
* Create a copy of an applicator with some components replaced
* The PSI type of a new applicator will be a class passed in [newPsiTypeTag]
*/
fun <PSI : PsiElement, NEW_PSI : PSI, INPUT : HLApplicatorInput> HLApplicator<PSI, INPUT>.with( fun <PSI : PsiElement, NEW_PSI : PSI, INPUT : HLApplicatorInput> HLApplicator<PSI, INPUT>.with(
newPsiTypeTag: KClass<NEW_PSI>, @Suppress("UNUSED_PARAMETER") newPsiTypeTag: KClass<NEW_PSI>,
init: HLApplicatorBuilder<NEW_PSI, INPUT>.(olApplicator: HLApplicator<PSI, INPUT>) -> Unit init: HLApplicatorBuilder<NEW_PSI, INPUT>.(olApplicator: HLApplicator<PSI, INPUT>) -> Unit
): HLApplicator<NEW_PSI, INPUT> = when (this@with) { ): HLApplicator<NEW_PSI, INPUT> = when (this@with) {
is HLApplicatorImpl -> { is HLApplicatorImpl -> {
@@ -128,15 +161,31 @@ class HLApplicatorBuilder<PSI : PsiElement, INPUT : HLApplicatorInput> internal
@OptIn(PrivateForInline::class) @OptIn(PrivateForInline::class)
fun build(): HLApplicator<PSI, INPUT> = HLApplicatorImpl( fun build(): HLApplicator<PSI, INPUT> {
applyTo = applyTo!!, val applyTo = applyTo
isApplicableByPsi = isApplicableByPsi ?: { true }, ?: error("Please, specify applyTo")
getActionName = getActionName ?: getFamilyName?.let { familyName -> { _, _ -> familyName.invoke() } }!!, val getActionName = getActionName
getFamilyName = getFamilyName!! ?: error("Please, specify actionName or familyName via either of: actionName,familyAndActionName")
) val isApplicableByPsi = isApplicableByPsi ?: { true }
val getFamilyName = getFamilyName
?: error("Please, specify or familyName via either of: familyName, familyAndActionName")
return HLApplicatorImpl(
applyTo = applyTo,
isApplicableByPsi = isApplicableByPsi,
getActionName = getActionName,
getFamilyName = getFamilyName
)
}
} }
/**
* Builds a new applicator with HLApplicatorBuilder
*
* Should specify at least applyTo and familyAndActionName
*
* @see HLApplicatorBuilder
*/
fun <PSI : PsiElement, INPUT : HLApplicatorInput> applicator( fun <PSI : PsiElement, INPUT : HLApplicatorInput> applicator(
init: HLApplicatorBuilder<PSI, INPUT>.() -> Unit, init: HLApplicatorBuilder<PSI, INPUT>.() -> Unit,
): HLApplicator<PSI, INPUT> = ): HLApplicator<PSI, INPUT> =
@@ -7,6 +7,20 @@ package org.jetbrains.kotlin.idea.fir.api.applicator
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
/**
* Data which [HLApplicator] is needed to perform the fix
*
* Created by [HLApplicatorInputProvider] or via [org.jetbrains.kotlin.idea.fir.api.fixes.HLDiagnosticFixFactory]
*
* Should not store inside
* - Everything that came from [org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession] like :
* - [org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol] consider using [org.jetbrains.kotlin.idea.frontend.api.symbols.pointers.KtSymbolPointer] instead
* - [org.jetbrains.kotlin.idea.frontend.api.types.KtType]
* - [org.jetbrains.kotlin.idea.frontend.api.calls.KtCall]
* - [org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession] instance itself
* - [PsiElement] consider using [com.intellij.psi.SmartPsiElementPointer] instead
*
*/
interface HLApplicatorInput { interface HLApplicatorInput {
fun isValidFor(psi: PsiElement): Boolean = true fun isValidFor(psi: PsiElement): Boolean = true
} }
@@ -8,7 +8,14 @@ package org.jetbrains.kotlin.idea.fir.api.applicator
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
/**
* Resolves the code to provide [HLApplicator] some input
*/
abstract class HLApplicatorInputProvider<PSI : PsiElement, out INPUT : HLApplicatorInput> { abstract class HLApplicatorInputProvider<PSI : PsiElement, out INPUT : HLApplicatorInput> {
/**
* Provide input to the applicator, if returns `null` then the applicator is not applicable and will not be called
* Guaranteed to be executed from read action, should not be called from EDT thread
*/
abstract fun KtAnalysisSession.provideInput(element: PSI): INPUT? abstract fun KtAnalysisSession.provideInput(element: PSI): INPUT?
} }
@@ -18,6 +25,10 @@ private class HLApplicatorInputProviderImpl<PSI : PsiElement, out INPUT : HLAppl
override fun KtAnalysisSession.provideInput(element: PSI): INPUT? = provideInput.invoke(this, element) override fun KtAnalysisSession.provideInput(element: PSI): INPUT? = provideInput.invoke(this, element)
} }
/**
* Creates [HLApplicatorInputProvider]
* The [provideInput] is guaranteed to be executed from read action, should not be called from EDT thread
*/
fun <PSI : PsiElement, INPUT : HLApplicatorInput> inputProvider( fun <PSI : PsiElement, INPUT : HLApplicatorInput> inputProvider(
provideInput: KtAnalysisSession.(PSI) -> INPUT? provideInput: KtAnalysisSession.(PSI) -> INPUT?
): HLApplicatorInputProvider<PSI, INPUT> = ): HLApplicatorInputProvider<PSI, INPUT> =
@@ -9,7 +9,11 @@ import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.frontend.api.ForbidKtResolve import org.jetbrains.kotlin.idea.frontend.api.ForbidKtResolve
abstract class HLPresentation<PSI : PsiElement> internal constructor() { /**
* Provides a presentation to display an message in the editor which may be latter fixed by [HLApplicator]
* Used by [org.jetbrains.kotlin.idea.fir.api.AbstractHLInspection] to provide higlighting message and type
*/
sealed class HLPresentation<PSI : PsiElement> {
fun getHighlightType(element: PSI): ProblemHighlightType = ForbidKtResolve.forbidResolveIn("HLPresentation.getHighlightType") { fun getHighlightType(element: PSI): ProblemHighlightType = ForbidKtResolve.forbidResolveIn("HLPresentation.getHighlightType") {
getHighlightTypeImpl(element) getHighlightTypeImpl(element)
} }
@@ -54,9 +58,13 @@ class HLInspectionPresentationProviderBuilder<PSI : PsiElement> internal constru
getMessage = { text } getMessage = { text }
} }
internal fun build(): HLPresentation<PSI> {
internal fun build(): HLPresentation<PSI> = val getHighlightType = getHighlightType
HLPresentationImpl(getHighlightType!!, getMessage!!) ?: error("Please, provide highlightType")
val getMessage = getMessage
?: error("Please, provide getMessage")
return HLPresentationImpl(getHighlightType, getMessage)
}
} }
fun <PSI : PsiElement> presentation( fun <PSI : PsiElement> presentation(
@@ -20,11 +20,11 @@ import org.jetbrains.kotlin.psi.KtPsiFactory
object CallableReturnTypeUpdaterApplicator { object CallableReturnTypeUpdaterApplicator {
val applicator = applicator<KtCallableDeclaration, Type> { val applicator = applicator<KtCallableDeclaration, Type> {
familyName(KotlinBundle.message("fix.change.return.type.family")) familyAndActionName(KotlinBundle.lazyMessage("fix.change.return.type.family"))
applyTo { declaration, type, project -> applyTo { declaration, type, project ->
val newTypeRef = if (!declaration.isProcedure(type)) { val newTypeRef = if (!declaration.isProcedure(type)) {
// TODO use longTypeRepresentation and the shorten // TODO use longTypeRepresentation and then shorten
KtPsiFactory(project ?: declaration.project).createType(type.shortTypeRepresentation) KtPsiFactory(project ?: declaration.project).createType(type.shortTypeRepresentation)
} else null } else null
runWriteAction { runWriteAction {