Introduce QuickFixesPsiBasedFactory for quickfixes which can be created only by PSI

This commit is contained in:
Ilya Kirillov
2021-01-29 13:38:09 +01:00
parent 6dd2037f85
commit 794558ab68
8 changed files with 121 additions and 7 deletions
@@ -23,8 +23,8 @@ class QuickFixes {
Extensions.getExtensions(QuickFixContributor.EP_NAME).forEach { it.registerQuickFixes(this) }
}
fun register(diagnosticFactory: DiagnosticFactory<*>, vararg factory: KotlinIntentionActionsFactory) {
factories.putAll(diagnosticFactory, factory.toList())
fun register(diagnosticFactory: DiagnosticFactory<*>, vararg factory: QuickFixFactory) {
factories.putAll(diagnosticFactory, factory.map { it.asKotlinIntentionActionsFactory() })
}
fun register(diagnosticFactory: DiagnosticFactory<*>, vararg action: IntentionAction) {
@@ -9,7 +9,9 @@ import com.intellij.codeInsight.intention.IntentionAction
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.psi.KtCodeFragment
abstract class KotlinIntentionActionsFactory: QuickFixFactory {
abstract class KotlinIntentionActionsFactory : QuickFixFactory {
override fun asKotlinIntentionActionsFactory(): KotlinIntentionActionsFactory = this
protected open fun isApplicableForCodeFragment(): Boolean = false
protected abstract fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction>
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
fun interface PsiElementSuitabilityChecker<in PSI: PsiElement> {
fun isSupported(psiElement: PSI): Boolean
}
object PsiElementSuitabilityCheckers {
val ALWAYS_SUITABLE = PsiElementSuitabilityChecker<PsiElement> { true }
val MODIFIER = PsiElementSuitabilityChecker<LeafPsiElement> { psiElement ->
psiElement.elementType is KtModifierKeywordToken
}
}
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.quickfix
interface QuickFixFactory {
fun asKotlinIntentionActionsFactory(): KotlinIntentionActionsFactory
}
@@ -0,0 +1,76 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.Diagnostic
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
abstract class QuickFixesPsiBasedFactory<PSI : PsiElement>(
private val classTag: KClass<PSI>,
private val suitabilityChecker: PsiElementSuitabilityChecker<PSI>,
) : QuickFixFactory {
final override fun asKotlinIntentionActionsFactory(): KotlinIntentionActionsFactory =
object : KotlinIntentionActionsFactory() {
@Suppress("UNCHECKED_CAST")
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val psiElement = diagnostic.psiElement as PSI
return createQuickFix(psiElement)
}
}
fun createQuickFix(psiElement: PsiElement): List<IntentionAction> {
checkIfPsiElementIsSupported(psiElement)
@Suppress("UNCHECKED_CAST")
return doCreateQuickFix(psiElement as PSI)
}
private fun checkIfPsiElementIsSupported(psiElement: PsiElement) {
if (!psiElement::class.isSubclassOf(classTag)) {
throw InvalidPsiElementTypeException(
expectedPsiType = psiElement::class,
actualPsiType = classTag,
factoryName = this::class.toString()
)
}
@Suppress("UNCHECKED_CAST")
if (!suitabilityChecker.isSupported(psiElement as PSI)) {
throw UnsupportedPsiElementException(psiElement, this::class.toString())
}
}
protected abstract fun doCreateQuickFix(psiElement: PSI): List<IntentionAction>
}
inline fun <reified PSI : PsiElement> quickFixesPsiBasedFactory(
suitabilityChecker: PsiElementSuitabilityChecker<PSI> = PsiElementSuitabilityCheckers.ALWAYS_SUITABLE,
crossinline createQuickFix: (PSI) -> List<IntentionAction>,
) = object : QuickFixesPsiBasedFactory<PSI>(PSI::class, suitabilityChecker) {
override fun doCreateQuickFix(psiElement: PSI): List<IntentionAction> = createQuickFix(psiElement)
}
inline fun <reified PSI : PsiElement, reified PSI2 : PsiElement> QuickFixesPsiBasedFactory<PSI>.coMap(
suitabilityChecker: PsiElementSuitabilityChecker<PSI2> = PsiElementSuitabilityCheckers.ALWAYS_SUITABLE,
crossinline map: (PSI2) -> PSI?
) = quickFixesPsiBasedFactory(suitabilityChecker) { psiElement ->
val newPsi = map(psiElement) ?: return@quickFixesPsiBasedFactory emptyList()
createQuickFix(newPsi)
}
class InvalidPsiElementTypeException(
expectedPsiType: KClass<out PsiElement>,
actualPsiType: KClass<out PsiElement>,
factoryName: String,
) : Exception("PsiElement with type $expectedPsiType is expected but $actualPsiType found for $factoryName")
class UnsupportedPsiElementException(
psiElement: PsiElement,
factoryName: String
) : Exception("PsiElement $psiElement is unsopported for $factoryName")
@@ -104,7 +104,9 @@ object J2KPostProcessingRegistrarImpl : J2KPostProcessingRegistrar {
}
registerDiagnosticBasedProcessing<KtTypeProjection>(Errors.REDUNDANT_PROJECTION) { _, diagnostic ->
val fix = RemoveModifierFix.createRemoveProjectionFactory(true).createActions(diagnostic).single() as RemoveModifierFix
val fix = RemoveModifierFix.createRemoveProjectionFactory(true)
.asKotlinIntentionActionsFactory()
.createActions(diagnostic).single() as RemoveModifierFix
fix.invoke()
}
@@ -56,7 +56,7 @@ import org.jetbrains.kotlin.resolve.konan.diagnostics.ErrorsNative.THROWS_LIST_E
class QuickFixRegistrar : QuickFixContributor {
override fun registerQuickFixes(quickFixes: QuickFixes) {
fun DiagnosticFactory<*>.registerFactory(vararg factory: KotlinIntentionActionsFactory) {
fun DiagnosticFactory<*>.registerFactory(vararg factory: QuickFixFactory) {
quickFixes.register(this, *factory)
}
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.idea.core.util.range
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.quickfix.QuickFixFactory
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
@@ -86,11 +87,12 @@ inline fun <reified T : PsiElement> diagnosticBasedProcessing(
}
}
fun diagnosticBasedProcessing(fixFactory: KotlinIntentionActionsFactory, vararg diagnosticFactory: DiagnosticFactory<*>) =
fun diagnosticBasedProcessing(fixFactory: QuickFixFactory, vararg diagnosticFactory: DiagnosticFactory<*>) =
object : DiagnosticBasedProcessing {
override val diagnosticFactories = diagnosticFactory.toList()
override fun fix(diagnostic: Diagnostic) {
val fix = runReadAction { fixFactory.createActions(diagnostic).singleOrNull() } ?: return
val actionFactory = fixFactory.asKotlinIntentionActionsFactory()
val fix = runReadAction { actionFactory.createActions(diagnostic).singleOrNull() } ?: return
runUndoTransparentActionInEdt(inWriteAction = true) {
fix.invoke(diagnostic.psiElement.project, null, diagnostic.psiFile)
}