From 794558ab6815add66d9d39d729dbf23ed2f56742 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Fri, 29 Jan 2021 13:38:09 +0100 Subject: [PATCH] Introduce QuickFixesPsiBasedFactory for quickfixes which can be created only by PSI --- .../kotlin/idea/quickfix/QuickFixes.kt | 4 +- .../quickfix/KotlinIntentionActionsFactory.kt | 4 +- .../quickfix/PsiElementSuitabilityChecker.kt | 22 ++++++ .../kotlin/idea/quickfix/QuickFixFactory.kt | 10 +++ .../quickfix/QuickFixesPsiBasedFactory.kt | 76 +++++++++++++++++++ .../j2k/J2KPostProcessingRegistrarImpl.kt | 4 +- .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 2 +- .../diagnosticBasedPostProcessing.kt | 6 +- 8 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/PsiElementSuitabilityChecker.kt create mode 100644 idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactory.kt create mode 100644 idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/QuickFixesPsiBasedFactory.kt diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/QuickFixes.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/QuickFixes.kt index 9d5fbf23b41..f115c3cbbab 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/QuickFixes.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/QuickFixes.kt @@ -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) { diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/KotlinIntentionActionsFactory.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/KotlinIntentionActionsFactory.kt index 263d105276c..db1b61a6eda 100644 --- a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/KotlinIntentionActionsFactory.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/KotlinIntentionActionsFactory.kt @@ -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 diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/PsiElementSuitabilityChecker.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/PsiElementSuitabilityChecker.kt new file mode 100644 index 00000000000..9a04734ea43 --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/PsiElementSuitabilityChecker.kt @@ -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 { + fun isSupported(psiElement: PSI): Boolean +} + +object PsiElementSuitabilityCheckers { + val ALWAYS_SUITABLE = PsiElementSuitabilityChecker { true } + + val MODIFIER = PsiElementSuitabilityChecker { psiElement -> + psiElement.elementType is KtModifierKeywordToken + } +} diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactory.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactory.kt new file mode 100644 index 00000000000..2a4d142f09c --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/QuickFixFactory.kt @@ -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 +} \ No newline at end of file diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/QuickFixesPsiBasedFactory.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/QuickFixesPsiBasedFactory.kt new file mode 100644 index 00000000000..e44937e1833 --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/QuickFixesPsiBasedFactory.kt @@ -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( + private val classTag: KClass, + private val suitabilityChecker: PsiElementSuitabilityChecker, +) : QuickFixFactory { + final override fun asKotlinIntentionActionsFactory(): KotlinIntentionActionsFactory = + object : KotlinIntentionActionsFactory() { + @Suppress("UNCHECKED_CAST") + override fun doCreateActions(diagnostic: Diagnostic): List { + val psiElement = diagnostic.psiElement as PSI + return createQuickFix(psiElement) + } + } + + fun createQuickFix(psiElement: PsiElement): List { + 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 +} + +inline fun quickFixesPsiBasedFactory( + suitabilityChecker: PsiElementSuitabilityChecker = PsiElementSuitabilityCheckers.ALWAYS_SUITABLE, + crossinline createQuickFix: (PSI) -> List, +) = object : QuickFixesPsiBasedFactory(PSI::class, suitabilityChecker) { + override fun doCreateQuickFix(psiElement: PSI): List = createQuickFix(psiElement) +} + +inline fun QuickFixesPsiBasedFactory.coMap( + suitabilityChecker: PsiElementSuitabilityChecker = PsiElementSuitabilityCheckers.ALWAYS_SUITABLE, + crossinline map: (PSI2) -> PSI? +) = quickFixesPsiBasedFactory(suitabilityChecker) { psiElement -> + val newPsi = map(psiElement) ?: return@quickFixesPsiBasedFactory emptyList() + createQuickFix(newPsi) +} + + +class InvalidPsiElementTypeException( + expectedPsiType: KClass, + actualPsiType: KClass, + 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") diff --git a/idea/src/org/jetbrains/kotlin/idea/j2k/J2KPostProcessingRegistrarImpl.kt b/idea/src/org/jetbrains/kotlin/idea/j2k/J2KPostProcessingRegistrarImpl.kt index 20183edfdbd..e86544c2581 100644 --- a/idea/src/org/jetbrains/kotlin/idea/j2k/J2KPostProcessingRegistrarImpl.kt +++ b/idea/src/org/jetbrains/kotlin/idea/j2k/J2KPostProcessingRegistrarImpl.kt @@ -104,7 +104,9 @@ object J2KPostProcessingRegistrarImpl : J2KPostProcessingRegistrar { } registerDiagnosticBasedProcessing(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() } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 99488a1231a..82b20256d8e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -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) } diff --git a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/diagnosticBasedPostProcessing.kt b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/diagnosticBasedPostProcessing.kt index bd6c15fa25f..3d80bb185fd 100644 --- a/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/diagnosticBasedPostProcessing.kt +++ b/nj2k/nj2k-services/src/org/jetbrains/kotlin/nj2k/postProcessing/diagnosticBasedPostProcessing.kt @@ -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 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) }