diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/SelfTargetingIntention.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/SelfTargetingIntention.kt index 6606d8e5aa6..2e63be4f0ee 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/SelfTargetingIntention.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/SelfTargetingIntention.kt @@ -27,7 +27,7 @@ import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf abstract class SelfTargetingIntention( val elementType: Class, @Nls private var textGetter: () -> String, - @Nls private val familyNameGetter: () -> String = textGetter, + @Nls private var familyNameGetter: () -> String = textGetter, ) : IntentionAction { @Deprecated("Replace with primary constructor", ReplaceWith("SelfTargetingIntention(elementType, { text }, { familyName })")) constructor( @@ -48,6 +48,11 @@ abstract class SelfTargetingIntention( this.textGetter = textGetter } + protected fun setFamilyNameGetter(@Nls familyNameGetter: () -> String) { + this.familyNameGetter = familyNameGetter + } + + final override fun getText() = textGetter() final override fun getFamilyName() = familyNameGetter() @@ -55,6 +60,10 @@ abstract class SelfTargetingIntention( abstract fun applyTo(element: TElement, editor: Editor?) + open fun applyTo(element: TElement, project: Project, editor: Editor?) { + applyTo(element, editor) + } + fun getTarget(offset: Int, file: PsiFile): TElement? { val leaf1 = file.findElementAt(offset) val leaf2 = file.findElementAt(offset - 1) @@ -100,7 +109,7 @@ abstract class SelfTargetingIntention( editor ?: return val target = getTarget(editor, file) ?: return if (!preparePsiElementForWriteIfNeeded(target)) return - applyTo(target, editor) + applyTo(target, project, editor) } /** diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/AbstractHLInspection.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/AbstractHLInspection.kt new file mode 100644 index 00000000000..522eabb46c7 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/AbstractHLInspection.kt @@ -0,0 +1,111 @@ +/* + * 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.fir.api + +import com.intellij.codeInspection.* +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.fir.api.applicator.* +import org.jetbrains.kotlin.idea.frontend.api.HackToForceAllowRunningAnalyzeOnEDT +import org.jetbrains.kotlin.idea.frontend.api.analyzeWithReadAction +import org.jetbrains.kotlin.idea.frontend.api.hackyAllowRunningOnEdt +import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtVisitorVoid +import kotlin.reflect.KClass + +abstract class AbstractHLInspection( + val elementType: KClass +) : AbstractKotlinInspection() { + final override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) = + object : KtVisitorVoid() { + override fun visitKtElement(element: KtElement) { + super.visitKtElement(element) + + if (!elementType.isInstance(element) || element.textLength == 0) return + @Suppress("UNCHECKED_CAST") + visitTargetElement(element as PSI, holder, isOnTheFly) + } + } + + private fun visitTargetElement(element: PSI, holder: ProblemsHolder, isOnTheFly: Boolean) { + if (!applicator.isApplicableByPsi(element)) return + val targets = applicabilityRange.getApplicabilityRanges(element) + if (targets.isEmpty()) return + + val input = getInput(element) ?: return + require(input.isValidFor(element)) { "Input should be valid after creation" } + + registerProblems(holder, element, targets, isOnTheFly, input) + } + + + private fun registerProblems( + holder: ProblemsHolder, + element: PSI, + ranges: List, + isOnTheFly: Boolean, + input: INPUT + ) { + val highlightType = presentation.getHighlightType(element) + if (!isOnTheFly && highlightType == ProblemHighlightType.INFORMATION) return + + val description = presentation.getMessage(element) + val fix = applicator.asLocalQuickFix(input, actionName = applicator.getActionName(element, input)) + + ranges.forEach { range -> + registerProblem(holder, element, range, description, highlightType, isOnTheFly, fix) + } + } + + private fun registerProblem( + holder: ProblemsHolder, + element: PSI, + range: TextRange, + description: String, + highlightType: ProblemHighlightType, + isOnTheFly: Boolean, + fix: LocalQuickFix + ) { + with(holder) { + val problemDescriptor = manager.createProblemDescriptor(element, range, description, highlightType, isOnTheFly, fix) + registerProblem(problemDescriptor) + } + } + + @OptIn(HackToForceAllowRunningAnalyzeOnEDT::class) + private fun getInput(element: PSI): INPUT? = hackyAllowRunningOnEdt { + analyzeWithReadAction(element) { + with(inputProvider) { provideInput(element) } + } + } + + abstract val presentation: HLPresentation + abstract val applicabilityRange: HLApplicabilityRange + abstract val inputProvider: HLApplicatorInputProvider + abstract val applicator: HLApplicator +} + +private fun HLApplicator.asLocalQuickFix( + input: INPUT, + actionName: String, +): LocalQuickFix = object : LocalQuickFix { + override fun startInWriteAction() = false + + @OptIn(HackToForceAllowRunningAnalyzeOnEDT::class) + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + @Suppress("UNCHECKED_CAST") + val element = descriptor.psiElement as PSI + + if (isApplicableByPsi(element) && input.isValidFor(element)) { + applyTo(element, input, project, editor = null) + } + } + + override fun getFamilyName() = this@asLocalQuickFix.getFamilyName() + override fun getName() = actionName +} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/AbstractHLIntention.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/AbstractHLIntention.kt new file mode 100644 index 00000000000..c69ec7d216f --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/AbstractHLIntention.kt @@ -0,0 +1,60 @@ +/* + * 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.fir.api + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicabilityRange +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicator +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInput +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInputProvider +import org.jetbrains.kotlin.idea.frontend.api.HackToForceAllowRunningAnalyzeOnEDT +import org.jetbrains.kotlin.idea.frontend.api.analyzeWithReadAction +import org.jetbrains.kotlin.idea.frontend.api.hackyAllowRunningOnEdt +import org.jetbrains.kotlin.idea.intentions.SelfTargetingIntention +import org.jetbrains.kotlin.psi.KtElement +import kotlin.reflect.KClass + +abstract class AbstractHLIntention( + elementType: KClass, +) : SelfTargetingIntention(elementType.java, { "" }, { "" }) { + final override fun isApplicableTo(element: PSI, caretOffset: Int): Boolean { + if (!applicator.isApplicableByPsi(element)) return false + val ranges = applicabilityRange.getApplicabilityRanges(element) + if (ranges.isEmpty()) return false + val input = getInput(element) + if (input != null && input.isValidFor(element)) { + setFamilyNameGetter(applicator::getFamilyName) + setTextGetter { applicator.getActionName(element, input) } + return true + } + return false + } + + + final override fun applyTo(element: PSI, project: Project, editor: Editor?) { + val input = getInput(element) ?: return + if (input.isValidFor(element)) { + applicator.applyTo(element, input, project, editor) + } + } + + final override fun applyTo(element: PSI, editor: Editor?) { + applyTo(element, element.project, editor) + } + + + @OptIn(HackToForceAllowRunningAnalyzeOnEDT::class) + private fun getInput(element: PSI): INPUT? = hackyAllowRunningOnEdt { + analyzeWithReadAction(element) { + with(inputProvider) { provideInput(element) } + } + } + + abstract val applicabilityRange: HLApplicabilityRange + abstract val applicator: HLApplicator + abstract val inputProvider: HLApplicatorInputProvider +} diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicabilityRange.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicabilityRange.kt new file mode 100644 index 00000000000..5cb51b9376f --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicabilityRange.kt @@ -0,0 +1,49 @@ +/* + * 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.fir.api.applicator + +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.util.textRangeIn +import org.jetbrains.kotlin.psi.KtElement + +sealed class HLApplicabilityRange { + /** + * Returns the list of ranges on which [HLApplicator] is available + * The ranges are relative to [element] + */ + abstract fun getApplicabilityRanges(element: ELEMENT): List +} + +private class HLApplicabilityRangeImpl( + private val getApplicabilityRanges: (ELEMENT) -> List, +) : HLApplicabilityRange() { + override fun getApplicabilityRanges(element: ELEMENT): List = + getApplicabilityRanges.invoke(element) +} + + +fun applicabilityRanges( + getRanges: (ELEMENT) -> List +): HLApplicabilityRange = + HLApplicabilityRangeImpl(getRanges) + +fun applicabilityRange( + getRange: (ELEMENT) -> TextRange? +): HLApplicabilityRange = + HLApplicabilityRangeImpl { listOfNotNull(getRange(it)) } + +fun applicabilityTarget( + getTarget: (ELEMENT) -> PsiElement? +): HLApplicabilityRange = + HLApplicabilityRangeImpl { element -> + when (val target = getTarget(element)) { + null -> emptyList() + element -> listOf(TextRange(0, element.textLength)) + else -> listOf(target.textRangeIn(element)) + } + } + diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicator.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicator.kt new file mode 100644 index 00000000000..ebd053ac4e7 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicator.kt @@ -0,0 +1,128 @@ +/* + * 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.fir.api.applicator + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.fir.low.level.api.annotations.PrivateForInline +import kotlin.experimental.ExperimentalTypeInference +import kotlin.reflect.KClass + +sealed class HLApplicator { + abstract fun applyTo(psi: PSI, input: INPUT, project: Project?, editor: Editor?) + + abstract fun isApplicableByPsi(psi: PSI): Boolean + + abstract fun getActionName(psi: PSI, input: INPUT): String + abstract fun getFamilyName(): String +} + +fun HLApplicator.with( + init: HLApplicatorBuilder.(olApplicator: HLApplicator) -> Unit +): HLApplicator = when (this@with) { + is HLApplicatorImpl -> { + val builder = HLApplicatorBuilder(applyTo, isApplicableByPsi, getActionName, getFamilyName) + @Suppress("UNCHECKED_CAST") + init(builder as HLApplicatorBuilder, this) + builder.build() + } +} + +fun HLApplicator.with( + newPsiTypeTag: KClass, + init: HLApplicatorBuilder.(olApplicator: HLApplicator) -> Unit +): HLApplicator = when (this@with) { + is HLApplicatorImpl -> { + val builder = HLApplicatorBuilder(applyTo, isApplicableByPsi, getActionName, getFamilyName) + @Suppress("UNCHECKED_CAST") + init(builder as HLApplicatorBuilder, this) + builder.build() + } +} + + +internal class HLApplicatorImpl( + val applyTo: (PSI, INPUT, Project?, Editor?) -> Unit, + val isApplicableByPsi: (PSI) -> Boolean, + val getActionName: (PSI, INPUT) -> String, + val getFamilyName: () -> String, +) : HLApplicator() { + override fun applyTo(psi: PSI, input: INPUT, project: Project?, editor: Editor?) { + applyTo.invoke(psi, input, project, editor) + } + + override fun isApplicableByPsi(psi: PSI): Boolean = + isApplicableByPsi.invoke(psi) + + override fun getActionName(psi: PSI, input: INPUT): String = + getActionName.invoke(psi, input) + + override fun getFamilyName(): String = + getFamilyName.invoke() +} + + +class HLApplicatorBuilder internal constructor( + @PrivateForInline + var applyTo: ((PSI, INPUT, Project?, Editor?) -> Unit)? = null, + private var isApplicableByPsi: ((PSI) -> Boolean)? = null, + private var getActionName: ((PSI, INPUT) -> String)? = null, + private var getFamilyName: (() -> String)? = null +) { + fun familyName(name: String) { + getFamilyName = { name } + } + + fun familyName(getName: () -> String) { + getFamilyName = getName + } + + fun familyAndActionName(getName: () -> String) { + getFamilyName = getName + getActionName = { _, _ -> getName() } + } + + fun actionName(getActionName: (PSI, INPUT) -> String) { + this.getActionName = getActionName + } + + @OptIn(PrivateForInline::class) + fun applyTo(doApply: (PSI, INPUT, Project?, Editor?) -> Unit) { + applyTo = doApply + } + + + @OptIn(PrivateForInline::class) + fun applyTo(doApply: (PSI, INPUT) -> Unit) { + applyTo = { element, data, _, _ -> doApply(element, data) } + } + + @OptIn(PrivateForInline::class) + fun applyTo(doApply: (PSI, INPUT, Project?) -> Unit) { + applyTo = { element, data, project, _ -> doApply(element, data, project) } + } + + @OptIn(ExperimentalTypeInference::class) + fun isApplicableByPsi(isApplicable: ((PSI) -> Boolean)? = null) { + this.isApplicableByPsi = isApplicable + } + + + @OptIn(PrivateForInline::class) + fun build(): HLApplicator = HLApplicatorImpl( + applyTo = applyTo!!, + isApplicableByPsi = isApplicableByPsi ?: { true }, + getActionName = getActionName ?: getFamilyName?.let { familyName -> { _, _ -> familyName.invoke() } }!!, + getFamilyName = getFamilyName!! + ) +} + + +fun applicator( + init: HLApplicatorBuilder.() -> Unit, +): HLApplicator = + HLApplicatorBuilder().apply(init).build() diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicatorInput.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicatorInput.kt new file mode 100644 index 00000000000..aa7e5a954a5 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicatorInput.kt @@ -0,0 +1,12 @@ +/* + * 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.fir.api.applicator + +import com.intellij.psi.PsiElement + +interface HLApplicatorInput { + fun isValidFor(psi: PsiElement): Boolean = true +} diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicatorInputProvider.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicatorInputProvider.kt new file mode 100644 index 00000000000..199ba4463c7 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLApplicatorInputProvider.kt @@ -0,0 +1,24 @@ +/* + * 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.fir.api.applicator + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession + +abstract class HLApplicatorInputProvider { + abstract fun KtAnalysisSession.provideInput(element: PSI): INPUT? +} + +private class HLApplicatorInputProviderImpl( + private val provideInput: KtAnalysisSession.(PSI) -> INPUT? +) : HLApplicatorInputProvider() { + override fun KtAnalysisSession.provideInput(element: PSI): INPUT? = provideInput.invoke(this, element) +} + +fun inputProvider( + provideInput: KtAnalysisSession.(PSI) -> INPUT? +): HLApplicatorInputProvider = + HLApplicatorInputProviderImpl(provideInput) \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLPresentation.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLPresentation.kt new file mode 100644 index 00000000000..215b0fefb4d --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/applicator/HLPresentation.kt @@ -0,0 +1,58 @@ +/* + * 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.fir.api.applicator + +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.psi.PsiElement + +abstract class HLPresentation internal constructor() { + abstract fun getHighlightType(element: PSI): ProblemHighlightType + abstract fun getMessage(element: PSI): String +} + +private class HLPresentationImpl( + private val getHighlightType: (element: PSI) -> ProblemHighlightType, + private val getMessage: (element: PSI) -> String, +) : HLPresentation() { + override fun getHighlightType(element: PSI): ProblemHighlightType = + getHighlightType.invoke(element) + + override fun getMessage(element: PSI): String = + getMessage.invoke(element) +} + + +class HLInspectionPresentationProviderBuilder internal constructor() { + private var getHighlightType: ((element: PSI) -> ProblemHighlightType)? = null + private var getMessage: ((element: PSI) -> String)? = null + + fun highlightType(getType: (element: PSI) -> ProblemHighlightType) { + getHighlightType = getType + } + + fun highlightType(type: ProblemHighlightType) { + getHighlightType = { type } + } + + fun inspectionText(getText: (element: PSI) -> String) { + getMessage = getText + } + + fun inspectionText(text: String) { + getMessage = { text } + } + + + internal fun build(): HLPresentation = + HLPresentationImpl(getHighlightType!!, getMessage!!) +} + +fun presentation( + init: HLInspectionPresentationProviderBuilder.() -> Unit +): HLPresentation = + HLInspectionPresentationProviderBuilder().apply(init).build() + + diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/HLApplicatorTargetWithInput.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/HLApplicatorTargetWithInput.kt new file mode 100644 index 00000000000..08df55371e1 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/HLApplicatorTargetWithInput.kt @@ -0,0 +1,21 @@ +/* + * 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.fir.api.fixes + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInput + +class HLApplicatorTargetWithInput( + val target: PSI, + val input: INPUT, +) { + operator fun component1() = target + operator fun component2() = input +} + +@Suppress("NOTHING_TO_INLINE") +inline infix fun PSI.withInput(input: INPUT) = + HLApplicatorTargetWithInput(this, input) \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/HLDiagnosticFixFactory.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/HLDiagnosticFixFactory.kt new file mode 100644 index 00000000000..7ee87cd22ba --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/HLDiagnosticFixFactory.kt @@ -0,0 +1,40 @@ +/* + * 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.fir.api.fixes + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicator +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInput +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnosticWithPsi + +sealed class HLDiagnosticFixFactory, TARGET_PSI : PsiElement, INPUT : HLApplicatorInput>( + val applicator: HLApplicator +) { + abstract fun KtAnalysisSession.createTargets(diagnostic: DIAGNOSTIC): List> +} + +private class HLDiagnosticFixFactoryImpl, TARGET_PSI : PsiElement, INPUT : HLApplicatorInput>( + applicator: HLApplicator, + private val createQuickFixes: KtAnalysisSession.(DIAGNOSTIC) -> List> +) : HLDiagnosticFixFactory(applicator) { + override fun KtAnalysisSession.createTargets(diagnostic: DIAGNOSTIC): List> = + createQuickFixes.invoke(this, diagnostic) +} + +internal fun > KtAnalysisSession.createPlatformQuickFixes( + diagnostic: DIAGNOSTIC, + factory: HLDiagnosticFixFactory +): List = with(factory) { + createTargets(diagnostic).map { (target, input) -> HLQuickFix(target, input, factory.applicator) } +} + +fun , TARGET_PSI : PsiElement, INPUT : HLApplicatorInput> diagnosticFixFactory( + applicator: HLApplicator, + createQuickFixes: KtAnalysisSession.(DIAGNOSTIC) -> List> +): HLDiagnosticFixFactory = + HLDiagnosticFixFactoryImpl(applicator, createQuickFixes) \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/HLQuickFix.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/HLQuickFix.kt new file mode 100644 index 00000000000..070af6609f4 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/HLQuickFix.kt @@ -0,0 +1,39 @@ +/* + * 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.fir.api.fixes + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicator +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInput +import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction +import org.jetbrains.kotlin.psi.KtFile + +internal class HLQuickFix( + target: PSI, + private val input: INPUT, + val applicator: HLApplicator, +) : KotlinQuickFixAction(target) { + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val element = element ?: return + if (applicator.isApplicableByPsi(element) && input.isValidFor(element)) { + applicator.applyTo(element, input, project, editor) + } + } + + override fun getText(): String { + val element = element ?: return applicator.getFamilyName() + return if (input.isValidFor(element)) { + applicator.getActionName(element, input) + } else { + applicator.getFamilyName() + } + } + + override fun getFamilyName(): String = + applicator.getFamilyName() +} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixService.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/KtQuickFixService.kt similarity index 90% rename from idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixService.kt rename to idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/KtQuickFixService.kt index 8749c503256..ed6c3f51e88 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixService.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/KtQuickFixService.kt @@ -3,13 +3,12 @@ * 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 +package org.jetbrains.kotlin.idea.fir.api.fixes import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.extensions.ExtensionPointName import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnosticWithPsi -import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic class KtQuickFixService { private val list = KtQuickFixesList.createCombined(KtQuickFixRegistrar.allQuickFixesList()) diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/KtQuickFixesList.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/KtQuickFixesList.kt new file mode 100644 index 00000000000..511342d768b --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/api/fixes/KtQuickFixesList.kt @@ -0,0 +1,121 @@ +/* + * 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.fir.api.fixes + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInput +import org.jetbrains.kotlin.idea.fir.low.level.api.annotations.PrivateForInline +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnosticWithPsi +import org.jetbrains.kotlin.idea.quickfix.QuickFixesPsiBasedFactory +import kotlin.reflect.KClass + +class KtQuickFixesList @ForKtQuickFixesListBuilder @OptIn(PrivateForInline::class) constructor( + private val quickFixes: Map>, List> +) { + fun KtAnalysisSession.getQuickFixesFor(diagnostic: KtDiagnosticWithPsi<*>): List { + val factories = quickFixes[diagnostic.diagnosticClass] ?: return emptyList() + return factories.flatMap { createQuickFixes(it, diagnostic) } + } + + @OptIn(PrivateForInline::class) + private fun KtAnalysisSession.createQuickFixes( + quickFixFactory: HLQuickFixFactory, + diagnostic: KtDiagnosticWithPsi<*> + ): List = when (quickFixFactory) { + is HLQuickFixFactory.HLApplicatorBasedFactory -> { + @Suppress("UNCHECKED_CAST") + val factory = quickFixFactory.applicatorFactory + as HLDiagnosticFixFactory, PsiElement, HLApplicatorInput> + createPlatformQuickFixes(diagnostic, factory) + } + is HLQuickFixFactory.HLQuickFixesPsiBasedFactory -> quickFixFactory.psiFactory.createQuickFix(diagnostic.psi) + } + + + companion object { + @OptIn(ForKtQuickFixesListBuilder::class) + fun createCombined(registrars: List): KtQuickFixesList { + val allQuickFixes = registrars.map { it.quickFixes }.merge() + return KtQuickFixesList(allQuickFixes) + } + + fun createCombined(vararg registrars: KtQuickFixesList): KtQuickFixesList { + return createCombined(registrars.toList()) + } + } +} + + +class KtQuickFixesListBuilder private constructor() { + @OptIn(PrivateForInline::class) + private val quickFixes = mutableMapOf>, MutableList>() + + @OptIn(PrivateForInline::class) + inline fun > registerPsiQuickFix( + quickFixFactory: QuickFixesPsiBasedFactory + ) { + registerPsiQuickFix(DIAGNOSTIC::class, quickFixFactory) + } + + @OptIn(PrivateForInline::class) + inline fun , TARGET_PSI : PsiElement, INPUT : HLApplicatorInput> registerApplicator( + quickFixFactory: HLDiagnosticFixFactory + ) { + registerApplicator(DIAGNOSTIC::class, quickFixFactory) + } + + @PrivateForInline + fun > registerPsiQuickFix( + diagnosticClass: KClass, + quickFixFactory: QuickFixesPsiBasedFactory + ) { + quickFixes.getOrPut(diagnosticClass) { mutableListOf() }.add(HLQuickFixFactory.HLQuickFixesPsiBasedFactory(quickFixFactory)) + } + + + @PrivateForInline + fun , TARGET_PSI : PsiElement, INPUT : HLApplicatorInput> registerApplicator( + diagnosticClass: KClass, + quickFixFactory: HLDiagnosticFixFactory + ) { + quickFixes.getOrPut(diagnosticClass) { mutableListOf() } + .add(HLQuickFixFactory.HLApplicatorBasedFactory(quickFixFactory)) + } + + @OptIn(ForKtQuickFixesListBuilder::class) + private fun build() = KtQuickFixesList(quickFixes) + + companion object { + fun registerPsiQuickFix(init: KtQuickFixesListBuilder.() -> Unit) = KtQuickFixesListBuilder().apply(init).build() + } +} + +@PrivateForInline +sealed class HLQuickFixFactory { + class HLQuickFixesPsiBasedFactory( + val psiFactory: QuickFixesPsiBasedFactory<*> + ) : HLQuickFixFactory() + + class HLApplicatorBasedFactory( + val applicatorFactory: HLDiagnosticFixFactory<*, *, *, *> + ) : HLQuickFixFactory() +} + + +private fun List>>.merge(): Map> { + return flatMap { it.entries } + .groupingBy { it.key } + .aggregate>, K, MutableList> { _, accumulator, element, _ -> + val list = accumulator ?: mutableListOf() + list.addAll(element.value) + list + } +} + +@RequiresOptIn +annotation class ForKtQuickFixesListBuilder() \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/KotlinHighLevelDiagnosticHighlightingPass.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/KotlinHighLevelDiagnosticHighlightingPass.kt index c3d381d9634..cb70641cb23 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/KotlinHighLevelDiagnosticHighlightingPass.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/KotlinHighLevelDiagnosticHighlightingPass.kt @@ -30,7 +30,7 @@ import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnostic import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnosticWithPsi import org.jetbrains.kotlin.idea.frontend.api.diagnostics.getDefaultMessageWithFactoryName import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic -import org.jetbrains.kotlin.idea.quickfix.KtQuickFixService +import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixService import org.jetbrains.kotlin.psi.KtFile class KotlinHighLevelDiagnosticHighlightingPass( diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/AbstractHighLevelApiBasedInspection.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/AbstractHighLevelApiBasedInspection.kt deleted file mode 100644 index 072a989ec9c..00000000000 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/AbstractHighLevelApiBasedInspection.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2010-2020 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.fir.inspections - -import com.intellij.codeInspection.* -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.TextRange -import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession -import org.jetbrains.kotlin.idea.frontend.api.analyzeWithReadAction -import org.jetbrains.kotlin.idea.frontend.api.computation.ApplicableComputation -import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection -import org.jetbrains.kotlin.idea.inspections.findExistingEditor -import org.jetbrains.kotlin.psi.KtElement -import org.jetbrains.kotlin.psi.KtVisitorVoid - -abstract class AbstractHighLevelApiBasedInspection( - val elementType: Class -) : AbstractKotlinInspection() { - - final override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) = - object : KtVisitorVoid() { - override fun visitKtElement(element: KtElement) { - super.visitKtElement(element) - - if (!elementType.isInstance(element) || element.textLength == 0) return - @Suppress("UNCHECKED_CAST") - visitTargetElement(element as ELEMENT, holder, isOnTheFly) - } - } - - protected fun visitTargetElement(element: ELEMENT, holder: ProblemsHolder, isOnTheFly: Boolean) { - if (!isApplicableByPsi(element)) return - if (analyzeWithReadAction(element) { analyzeAndGetData(element) == null }) return - - holder.registerProblemWithoutOfflineInformation( - element, - inspectionText(element), - isOnTheFly, - inspectionHighlightType(element), - inspectionHighlightRangeInElement(element), - LocalFix(fixText(element)) - ) - } - - open fun inspectionHighlightRangeInElement(element: ELEMENT): TextRange? = null - - open fun inspectionHighlightType(element: ELEMENT): ProblemHighlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING - - abstract fun inspectionText(element: ELEMENT): String - - abstract val defaultFixText: String - - open fun fixText(element: ELEMENT) = defaultFixText - - abstract fun isApplicableByPsi(element: ELEMENT): Boolean - - abstract fun KtAnalysisSession.analyzeAndGetData(element: ELEMENT): DATA? - - abstract fun applyTo(element: ELEMENT, data: DATA, project: Project = element.project, editor: Editor? = null) - - private inner class LocalFix(val text: String) : LocalQuickFix { - override fun startInWriteAction() = false - - override fun applyFix(project: Project, descriptor: ProblemDescriptor) { - @Suppress("UNCHECKED_CAST") - val element = descriptor.psiElement as ELEMENT - if (!isApplicableByPsi(element)) return - - ApplicationManager.getApplication().executeOnPooledThread { - val computation = ApplicableComputation( - computation = { analyzeAndGetData(it) }, - application = { element, data -> - applyTo(element, data, project, element.findExistingEditor()) - }, - psiChecker = ::isApplicableByPsi, - computationTitle = fixText(element) - ) - computation.computeAndApply(element) - } - } - - override fun getFamilyName() = defaultFixText - - override fun getName() = text - } -} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/AbstractHighLevelApiBasedIntention.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/AbstractHighLevelApiBasedIntention.kt deleted file mode 100644 index 558e57ba725..00000000000 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/AbstractHighLevelApiBasedIntention.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2010-2020 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.fir.inspections - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.editor.Editor -import org.jetbrains.annotations.Nls -import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession -import org.jetbrains.kotlin.idea.frontend.api.analyzeWithReadAction -import org.jetbrains.kotlin.idea.frontend.api.computation.ApplicableComputation -import org.jetbrains.kotlin.idea.intentions.SelfTargetingIntention -import org.jetbrains.kotlin.psi.KtElement - -abstract class AbstractHighLevelApiBasedIntention( - elementType: Class, - @Nls private val textGetter: () -> String, - @Nls familyNameGetter: () -> String = textGetter, -) : SelfTargetingIntention(elementType, textGetter, familyNameGetter) { - - protected abstract fun isApplicableByPsi(element: ELEMENT): Boolean - - protected open fun isApplicableByPsiAtOffset(element: ELEMENT, offset: Int): Boolean = - isApplicableByPsi(element) - - protected abstract fun KtAnalysisSession.analyzeAndGetData(element: ELEMENT): DATA? - - protected abstract fun applyTo(element: ELEMENT, data: DATA, editor: Editor?) - - final override fun isApplicableTo(element: ELEMENT, caretOffset: Int): Boolean { - if (!isApplicableByPsi(element)) return false - if (!isApplicableByPsiAtOffset(element, caretOffset)) return false - val data = ApplicationManager.getApplication().executeOnPooledThread { - analyzeWithReadAction(element) { analyzeAndGetData(element) } - }.get() - return data != null - } - - final override fun applyTo(element: ELEMENT, editor: Editor?) { - if (!isApplicableByPsi(element)) return - ApplicationManager.getApplication().executeOnPooledThread { - val computation = ApplicableComputation( - computation = { analyzeAndGetData(it) }, - application = { element, data -> - applyTo(element, data, editor) - }, - psiChecker = ::isApplicableByPsi, - computationTitle = textGetter() - ) - computation.computeAndApply(element) - } - } -} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixesList.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixesList.kt deleted file mode 100644 index 51d40922c8d..00000000000 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixesList.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.idea.frontend.api.KtAnalysisSession -import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnosticWithPsi -import kotlin.reflect.KClass - -@RequiresOptIn -annotation class ForKtQuickFixesListBuilder() - - -class KtQuickFixesListBuilder private constructor() { - val quickFixes = mutableMapOf>, MutableList>() - - inline fun > register( - quickFixFactory: QuickFixesPsiBasedFactory - ) { - quickFixes.getOrPut(DIAGNOSTIC::class) { mutableListOf() }.add(quickFixFactory) - } - - inline fun > register( - quickFixFactory: QuickFixesHLApiBasedFactory - ) { - quickFixes.getOrPut(DIAGNOSTIC::class) { mutableListOf() }.add(quickFixFactory) - } - - @OptIn(ForKtQuickFixesListBuilder::class) - private fun build() = KtQuickFixesList(quickFixes) - - companion object { - fun register(init: KtQuickFixesListBuilder.() -> Unit) = KtQuickFixesListBuilder().apply(init).build() - } -} - -class KtQuickFixesList @ForKtQuickFixesListBuilder constructor(private val quickFixes: Map>, List>) { - fun KtAnalysisSession.getQuickFixesFor(diagnostic: KtDiagnosticWithPsi<*>): List { - val factories = quickFixes[diagnostic.diagnosticClass] ?: return emptyList() - return factories.flatMap { createQuickFixes(it, diagnostic) } - } - - @Suppress("UNCHECKED_CAST") - private fun KtAnalysisSession.createQuickFixes( - quickFixFactory: QuickFixFactory, - diagnostic: KtDiagnosticWithPsi - ): List = when (quickFixFactory) { - is QuickFixesPsiBasedFactory<*> -> quickFixFactory.createQuickFix(diagnostic.psi) - is QuickFixesHLApiBasedFactory<*, *> -> with(quickFixFactory as QuickFixesHLApiBasedFactory>) { - createQuickFix(diagnostic) - } - else -> error("Unsupported QuickFixFactory $quickFixFactory") - } - - companion object { - @OptIn(ForKtQuickFixesListBuilder::class) - fun createCombined(registrars: List): KtQuickFixesList { - val allQuickFixes = registrars.map { it.quickFixes }.merge() - return KtQuickFixesList(allQuickFixes) - } - - fun createCombined(vararg registrars: KtQuickFixesList): KtQuickFixesList { - return createCombined(registrars.toList()) - } - } -} - -private fun List>>.merge(): Map> { - return flatMap { it.entries } - .groupingBy { it.key } - .aggregate>, K, MutableList> { _, accumulator, element, _ -> - val list = accumulator ?: mutableListOf() - list.addAll(element.value) - list - } -} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt index 615c2dc9126..43a79ab96c5 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt @@ -6,22 +6,22 @@ package org.jetbrains.kotlin.idea.quickfix import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixRegistrar +import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixesList +import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixesListBuilder import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic import org.jetbrains.kotlin.idea.quickfix.fixes.ChangeTypeQuickFix -import org.jetbrains.kotlin.lexer.KtToken import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.KtCallableDeclaration import org.jetbrains.kotlin.psi.KtModifierListOwner -import org.jetbrains.kotlin.psi.KtNamedDeclaration class MainKtQuickFixRegistrar : KtQuickFixRegistrar() { - private val modifiers = KtQuickFixesListBuilder.register { - register(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true)) - register(RemoveModifierFix.createRemoveModifierFactory(isRedundant = false)) - register(RemoveModifierFix.createRemoveModifierFactory(isRedundant = false)) - register(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true)) - register(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true)) - register( + private val modifiers = KtQuickFixesListBuilder.registerPsiQuickFix { + registerPsiQuickFix(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true)) + registerPsiQuickFix(RemoveModifierFix.createRemoveModifierFactory(isRedundant = false)) + registerPsiQuickFix(RemoveModifierFix.createRemoveModifierFactory(isRedundant = false)) + registerPsiQuickFix(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true)) + registerPsiQuickFix(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true)) + registerPsiQuickFix( RemoveModifierFix.createRemoveModifierFromListOwnerFactoryByModifierListOwner( modifier = KtTokens.OPEN_KEYWORD, isRedundant = true @@ -29,11 +29,10 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() { ) } - private val overrides = KtQuickFixesListBuilder.register { - register(ChangeTypeQuickFix.changeFunctionReturnTypeOnOverride) - register(ChangeTypeQuickFix.changePropertyReturnTypeOnOverride) - register(ChangeTypeQuickFix.changeVariableReturnTypeOnOverride) - + private val overrides = KtQuickFixesListBuilder.registerPsiQuickFix { + registerApplicator(ChangeTypeQuickFix.changeFunctionReturnTypeOnOverride) + registerApplicator(ChangeTypeQuickFix.changePropertyReturnTypeOnOverride) + registerApplicator(ChangeTypeQuickFix.changeVariableReturnTypeOnOverride) } override val list: KtQuickFixesList = KtQuickFixesList.createCombined( diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/QuickFixesHLApiBasedFactory.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/QuickFixesHLApiBasedFactory.kt deleted file mode 100644 index 51f462df737..00000000000 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/QuickFixesHLApiBasedFactory.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.idea.frontend.api.KtAnalysisSession -import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnosticWithPsi - -abstract class QuickFixesHLApiBasedFactory> : QuickFixFactory { - final override fun asKotlinIntentionActionsFactory(): KotlinIntentionActionsFactory { - error("Should not be called. This function is not considered to bue used in FE10 plugin, from FIR plugin consider using createQuickFix") - } - - abstract fun KtAnalysisSession.createQuickFix(diagnostic: DIAGNOSTIC): List -} - - -inline fun > quickFixesHLApiBasedFactory( - crossinline createQuickFix: KtAnalysisSession.(DIAGNOSTIC) -> List -) = object : QuickFixesHLApiBasedFactory() { - override fun KtAnalysisSession.createQuickFix(diagnostic: DIAGNOSTIC): List = - createQuickFix(diagnostic) -} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeReturnTypeOnOverrideQuickFix.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeReturnTypeOnOverrideQuickFix.kt index ecb512cd7c9..96ed57e012b 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeReturnTypeOnOverrideQuickFix.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeReturnTypeOnOverrideQuickFix.kt @@ -5,113 +5,113 @@ package org.jetbrains.kotlin.idea.quickfix.fixes -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.idea.fir.api.* +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInput +import org.jetbrains.kotlin.idea.fir.api.applicator.applicator +import org.jetbrains.kotlin.idea.fir.api.fixes.HLApplicatorTargetWithInput +import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory +import org.jetbrains.kotlin.idea.fir.api.fixes.withInput +import org.jetbrains.kotlin.idea.fir.applicators.CallableReturnTypeUpdaterApplicator import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession -import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeRendererOptions import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnosticWithPsi import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic import org.jetbrains.kotlin.idea.frontend.api.symbols.* import org.jetbrains.kotlin.idea.frontend.api.types.KtType -import org.jetbrains.kotlin.idea.frontend.api.types.isUnit import org.jetbrains.kotlin.idea.quickfix.ChangeCallableReturnTypeFix -import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction -import org.jetbrains.kotlin.idea.quickfix.quickFixesHLApiBasedFactory import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.types.checker.KotlinTypeChecker +object ChangeTypeQuickFix { + val applicator = applicator { + familyName(CallableReturnTypeUpdaterApplicator.applicator.getFamilyName()) -class ChangeTypeQuickFix internal constructor( - declaration: KtCallableDeclaration, - private val typeInfo: TypeInfo, - private val updateBaseFunction: Boolean -) : KotlinQuickFixAction(declaration) { - override fun getText(): String { - val element = element ?: return "" - val functionPresentation = when { - updateBaseFunction -> { - val containerName = element.parentOfType()?.nameAsName?.takeUnless { it.isSpecial } - ChangeCallableReturnTypeFix.StringPresentation.baseFunctionOrConstructorParameterPresentation(element, containerName) - } - else -> null + actionName { declaration, (updateBaseFunction, type) -> + val presentation = getPresentation(updateBaseFunction, declaration) + getActionName(declaration, presentation, type) } - return ChangeCallableReturnTypeFix.StringPresentation.getTextForQuickFix( - element, - functionPresentation, - typeInfo.isUnit, - typeInfo.short - ) - } - override fun getFamilyName(): String = ChangeCallableReturnTypeFix.StringPresentation.familyName() - - override fun invoke(project: Project, editor: Editor?, file: KtFile) { - val element = element ?: return - if (!element.isProcedure()) { - val newTypeRef = KtPsiFactory(project).createType(typeInfo.short) - element.typeReference = newTypeRef - } else { - element.typeReference = null + applyTo { declaration, (_, type), project, editor -> + CallableReturnTypeUpdaterApplicator.applicator.applyTo(declaration, type, project, editor) } } - private fun KtCallableDeclaration.isProcedure() = - typeInfo.isUnit && this is KtFunction && hasBlockBody() - - internal data class TypeInfo( - val qualified: String, - val short: String, - val isUnit: Boolean, + private fun getActionName( + declaration: KtCallableDeclaration, + presentation: String?, + type: CallableReturnTypeUpdaterApplicator.Type + ) = ChangeCallableReturnTypeFix.StringPresentation.getTextForQuickFix( + declaration, + presentation, + type.isUnit, + type.shortTypeRepresentation ) - companion object { - val changeFunctionReturnTypeOnOverride = - ChangeTypeQuickFixFactory.changeReturnTypeOnOverride { - it.function as? KtFunctionSymbol - } - - val changePropertyReturnTypeOnOverride = - ChangeTypeQuickFixFactory.changeReturnTypeOnOverride { - it.property as? KtPropertySymbol - } - - val changeVariableReturnTypeOnOverride = - ChangeTypeQuickFixFactory.changeReturnTypeOnOverride { - it.variable as? KtPropertySymbol - } + private fun getPresentation( + updateBaseFunction: Boolean, + declaration: KtCallableDeclaration + ) = when { + updateBaseFunction -> { + val containerName = declaration.parentOfType()?.nameAsName?.takeUnless { it.isSpecial } + ChangeCallableReturnTypeFix.StringPresentation.baseFunctionOrConstructorParameterPresentation( + declaration, + containerName + ) + } + else -> null } -} -private object ChangeTypeQuickFixFactory { - inline fun > changeReturnTypeOnOverride( + data class Input( + val updateBaseFunction: Boolean, + val type: CallableReturnTypeUpdaterApplicator.Type + ) : HLApplicatorInput { + override fun isValidFor(psi: PsiElement): Boolean = type.isValidFor(psi) + } + + val changeFunctionReturnTypeOnOverride = + changeReturnTypeOnOverride { + it.function as? KtFunctionSymbol + } + + val changePropertyReturnTypeOnOverride = + changeReturnTypeOnOverride { + it.property as? KtPropertySymbol + } + + val changeVariableReturnTypeOnOverride = + changeReturnTypeOnOverride { + it.variable as? KtPropertySymbol + } + + + private inline fun > changeReturnTypeOnOverride( crossinline getCallableSymbol: (DIAGNOSTIC) -> KtCallableSymbol? - ) = quickFixesHLApiBasedFactory { diagnostic -> - val declaration = diagnostic.psi as? KtCallableDeclaration ?: return@quickFixesHLApiBasedFactory emptyList() - val callable = getCallableSymbol(diagnostic) ?: return@quickFixesHLApiBasedFactory emptyList() + ) = diagnosticFixFactory(applicator) { diagnostic -> + val declaration = diagnostic.psi as? KtCallableDeclaration ?: return@diagnosticFixFactory emptyList() + val callable = getCallableSymbol(diagnostic) ?: return@diagnosticFixFactory emptyList() listOfNotNull( createChangeCurrentDeclarationQuickFix(callable, declaration), createChangeOverriddenFunctionQuickFix(callable), ) } - fun KtAnalysisSession.createChangeCurrentDeclarationQuickFix( + private fun KtAnalysisSession.createChangeCurrentDeclarationQuickFix( callable: KtCallableSymbol, declaration: PSI - ): ChangeTypeQuickFix? { + ): HLApplicatorTargetWithInput? { val lowerSuperType = findLowerBoundOfOverriddenCallablesReturnTypes(callable) ?: return null val changeToTypeInfo = createTypeInfo(lowerSuperType) - return ChangeTypeQuickFix(declaration, changeToTypeInfo, updateBaseFunction = false) + return declaration withInput Input(updateBaseFunction = false, changeToTypeInfo) } - fun KtAnalysisSession.createChangeOverriddenFunctionQuickFix( + private fun KtAnalysisSession.createChangeOverriddenFunctionQuickFix( callable: KtCallableSymbol - ): ChangeTypeQuickFix? { + ): HLApplicatorTargetWithInput? { val type = callable.annotatedType.type val singleNonMatchingOverriddenFunction = findSingleNonMatchingOverriddenFunction(callable, type) ?: return null val singleMatchingOverriddenFunctionPsi = singleNonMatchingOverriddenFunction.psiSafe() ?: return null val changeToTypeInfo = createTypeInfo(type) - return ChangeTypeQuickFix(singleMatchingOverriddenFunctionPsi, changeToTypeInfo, updateBaseFunction = true) + return singleMatchingOverriddenFunctionPsi withInput Input(updateBaseFunction = true, changeToTypeInfo) } private fun KtAnalysisSession.findSingleNonMatchingOverriddenFunction( @@ -125,11 +125,9 @@ private object ChangeTypeQuickFixFactory { } } - fun KtAnalysisSession.createTypeInfo(ktType: KtType) = ChangeTypeQuickFix.TypeInfo( - qualified = ktType.render(), - short = ktType.render(KtTypeRendererOptions.SHORT_NAMES), - isUnit = ktType.isUnit - ) + private fun KtAnalysisSession.createTypeInfo(ktType: KtType) = with(CallableReturnTypeUpdaterApplicator.Type) { + createByKtType(ktType) + } private fun KtAnalysisSession.findLowerBoundOfOverriddenCallablesReturnTypes(symbol: KtCallableSymbol): KtType? { var lowestType: KtType? = null diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/computation/ApplicableComputation.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/computation/ApplicableComputation.kt deleted file mode 100644 index e25d2613f38..00000000000 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/computation/ApplicableComputation.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2010-2020 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.frontend.api.computation - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.command.CommandProcessor -import com.intellij.psi.util.PsiModificationTracker -import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession -import org.jetbrains.kotlin.idea.frontend.api.analyzeWithReadAction -import org.jetbrains.kotlin.idea.util.application.runWriteAction -import org.jetbrains.kotlin.psi.KtElement - - -/** - * Computation which computes some value by [computation] in context of [KtAnalysisSession], - * If computations is successful (i.e. it returns non-null value) then it tries to apply result of this computation by [application] - * [application] is ran in EDT & write action and supposed to modify given KtElement somehow - * Application happens only if world has not changed since we called [computation] - * If world changed we [computation] again and again until we success or exceed the number of attempts represented by [tryCount] - * Should be run from non-EDT thread - * [computation] 8 [psiChecker] should be pure functions - */ -class ApplicableComputation( - val computation: KtAnalysisSession.(ELEMENT) -> DATA?, - val application: (ELEMENT, DATA) -> Unit, - val psiChecker: (ELEMENT) -> Boolean = { true }, - val computationTitle: String, -) { - @Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") - fun computeAndApply(element: ELEMENT, tryCount: UInt = UInt.MAX_VALUE): ApplicableComputationResult { - if (!element.isValid) return ApplicableComputationResult.NonApplicable - val ideaApplication = ApplicationManager.getApplication() - if (ideaApplication.isDispatchThread) { - error("ApplicableComputation.apply should be called from non-EDT thread") - } - val project = element.project - val modificationTracker = PsiModificationTracker.SERVICE.getInstance(project) - var completed = false - var exception: Throwable? = null - var tries = 0u - while (!completed && tries < tryCount) { - if (!element.isValid) return ApplicableComputationResult.NonApplicable - if (!psiChecker(element)) return ApplicableComputationResult.NonApplicable - val data = analyzeWithReadAction(element) { computation(element) } ?: return ApplicableComputationResult.NonApplicable - val timestamp = modificationTracker.modificationCount - ideaApplication.invokeAndWait { - CommandProcessor.getInstance().executeCommand( - project, - { - runWriteAction { - tries++ - if (modificationTracker.modificationCount == timestamp) { - try { - application(element, data) - } catch (e: Throwable) { - exception = e - } finally { - completed = true - } - } - } - }, - computationTitle, - null - ) - } - } - return exception?.let(ApplicableComputationResult::WithException) ?: ApplicableComputationResult.Applied - } -} - -sealed class ApplicableComputationResult { - @Suppress("SpellCheckingInspection") - object NonApplicable : ApplicableComputationResult() - - @Suppress("SpellCheckingInspection") - object Applied : ApplicableComputationResult() - - data class WithException(val exception: Throwable) : ApplicableComputationResult() -} \ No newline at end of file diff --git a/idea/resources-fir/META-INF/plugin.xml b/idea/resources-fir/META-INF/plugin.xml index 1f74267464b..3b35f50be99 100644 --- a/idea/resources-fir/META-INF/plugin.xml +++ b/idea/resources-fir/META-INF/plugin.xml @@ -269,7 +269,7 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu - + @@ -289,10 +289,7 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu fieldName="INSTANCE" extensions="kotlin_module"/> - +