FIR IDE: introduce infrastructure for HL based inspections & intentions

This commit is contained in:
Ilya Kirillov
2021-02-07 12:39:11 +01:00
parent 5cefad1ab3
commit a6f76399e2
22 changed files with 765 additions and 436 deletions
@@ -27,7 +27,7 @@ import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
abstract class SelfTargetingIntention<TElement : PsiElement>(
val elementType: Class<TElement>,
@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<TElement>(elementType, { text }, { familyName })"))
constructor(
@@ -48,6 +48,11 @@ abstract class SelfTargetingIntention<TElement : PsiElement>(
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<TElement : PsiElement>(
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<TElement : PsiElement>(
editor ?: return
val target = getTarget(editor, file) ?: return
if (!preparePsiElementForWriteIfNeeded(target)) return
applyTo(target, editor)
applyTo(target, project, editor)
}
/**
@@ -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<PSI : KtElement, INPUT : HLApplicatorInput>(
val elementType: KClass<PSI>
) : 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<TextRange>,
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<PSI>
abstract val applicabilityRange: HLApplicabilityRange<PSI>
abstract val inputProvider: HLApplicatorInputProvider<PSI, INPUT>
abstract val applicator: HLApplicator<PSI, INPUT>
}
private fun <PSI : PsiElement, INPUT : HLApplicatorInput> HLApplicator<PSI, INPUT>.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
}
@@ -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<PSI : KtElement, INPUT : HLApplicatorInput>(
elementType: KClass<PSI>,
) : SelfTargetingIntention<PSI>(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<PSI>
abstract val applicator: HLApplicator<PSI, INPUT>
abstract val inputProvider: HLApplicatorInputProvider<PSI, INPUT>
}
@@ -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<in ELEMENT : PsiElement> {
/**
* Returns the list of ranges on which [HLApplicator] is available
* The ranges are relative to [element]
*/
abstract fun getApplicabilityRanges(element: ELEMENT): List<TextRange>
}
private class HLApplicabilityRangeImpl<ELEMENT : PsiElement>(
private val getApplicabilityRanges: (ELEMENT) -> List<TextRange>,
) : HLApplicabilityRange<ELEMENT>() {
override fun getApplicabilityRanges(element: ELEMENT): List<TextRange> =
getApplicabilityRanges.invoke(element)
}
fun <ELEMENT : KtElement> applicabilityRanges(
getRanges: (ELEMENT) -> List<TextRange>
): HLApplicabilityRange<ELEMENT> =
HLApplicabilityRangeImpl(getRanges)
fun <ELEMENT : KtElement> applicabilityRange(
getRange: (ELEMENT) -> TextRange?
): HLApplicabilityRange<ELEMENT> =
HLApplicabilityRangeImpl { listOfNotNull(getRange(it)) }
fun <ELEMENT : PsiElement> applicabilityTarget(
getTarget: (ELEMENT) -> PsiElement?
): HLApplicabilityRange<ELEMENT> =
HLApplicabilityRangeImpl { element ->
when (val target = getTarget(element)) {
null -> emptyList()
element -> listOf(TextRange(0, element.textLength))
else -> listOf(target.textRangeIn(element))
}
}
@@ -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<in PSI : PsiElement, in INPUT : HLApplicatorInput> {
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 <PSI : PsiElement, NEW_PSI : PSI, INPUT : HLApplicatorInput> HLApplicator<PSI, INPUT>.with(
init: HLApplicatorBuilder<NEW_PSI, INPUT>.(olApplicator: HLApplicator<PSI, INPUT>) -> Unit
): HLApplicator<NEW_PSI, INPUT> = when (this@with) {
is HLApplicatorImpl -> {
val builder = HLApplicatorBuilder(applyTo, isApplicableByPsi, getActionName, getFamilyName)
@Suppress("UNCHECKED_CAST")
init(builder as HLApplicatorBuilder<NEW_PSI, INPUT>, this)
builder.build()
}
}
fun <PSI : PsiElement, NEW_PSI : PSI, INPUT : HLApplicatorInput> HLApplicator<PSI, INPUT>.with(
newPsiTypeTag: KClass<NEW_PSI>,
init: HLApplicatorBuilder<NEW_PSI, INPUT>.(olApplicator: HLApplicator<PSI, INPUT>) -> Unit
): HLApplicator<NEW_PSI, INPUT> = when (this@with) {
is HLApplicatorImpl -> {
val builder = HLApplicatorBuilder(applyTo, isApplicableByPsi, getActionName, getFamilyName)
@Suppress("UNCHECKED_CAST")
init(builder as HLApplicatorBuilder<NEW_PSI, INPUT>, this)
builder.build()
}
}
internal class HLApplicatorImpl<PSI : PsiElement, INPUT : HLApplicatorInput>(
val applyTo: (PSI, INPUT, Project?, Editor?) -> Unit,
val isApplicableByPsi: (PSI) -> Boolean,
val getActionName: (PSI, INPUT) -> String,
val getFamilyName: () -> String,
) : HLApplicator<PSI, INPUT>() {
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<PSI : PsiElement, INPUT : HLApplicatorInput> 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<PSI, INPUT> = HLApplicatorImpl(
applyTo = applyTo!!,
isApplicableByPsi = isApplicableByPsi ?: { true },
getActionName = getActionName ?: getFamilyName?.let { familyName -> { _, _ -> familyName.invoke() } }!!,
getFamilyName = getFamilyName!!
)
}
fun <PSI : PsiElement, INPUT : HLApplicatorInput> applicator(
init: HLApplicatorBuilder<PSI, INPUT>.() -> Unit,
): HLApplicator<PSI, INPUT> =
HLApplicatorBuilder<PSI, INPUT>().apply(init).build()
@@ -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
}
@@ -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<PSI : PsiElement, out INPUT : HLApplicatorInput> {
abstract fun KtAnalysisSession.provideInput(element: PSI): INPUT?
}
private class HLApplicatorInputProviderImpl<PSI : PsiElement, out INPUT : HLApplicatorInput>(
private val provideInput: KtAnalysisSession.(PSI) -> INPUT?
) : HLApplicatorInputProvider<PSI, INPUT>() {
override fun KtAnalysisSession.provideInput(element: PSI): INPUT? = provideInput.invoke(this, element)
}
fun <PSI : PsiElement, INPUT : HLApplicatorInput> inputProvider(
provideInput: KtAnalysisSession.(PSI) -> INPUT?
): HLApplicatorInputProvider<PSI, INPUT> =
HLApplicatorInputProviderImpl(provideInput)
@@ -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<PSI: PsiElement> internal constructor() {
abstract fun getHighlightType(element: PSI): ProblemHighlightType
abstract fun getMessage(element: PSI): String
}
private class HLPresentationImpl<PSI: PsiElement>(
private val getHighlightType: (element: PSI) -> ProblemHighlightType,
private val getMessage: (element: PSI) -> String,
) : HLPresentation<PSI>() {
override fun getHighlightType(element: PSI): ProblemHighlightType =
getHighlightType.invoke(element)
override fun getMessage(element: PSI): String =
getMessage.invoke(element)
}
class HLInspectionPresentationProviderBuilder<PSI: PsiElement> 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<PSI> =
HLPresentationImpl(getHighlightType!!, getMessage!!)
}
fun <PSI: PsiElement> presentation(
init: HLInspectionPresentationProviderBuilder<PSI>.() -> Unit
): HLPresentation<PSI> =
HLInspectionPresentationProviderBuilder<PSI>().apply(init).build()
@@ -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<PSI : PsiElement, INPUT : HLApplicatorInput>(
val target: PSI,
val input: INPUT,
) {
operator fun component1() = target
operator fun component2() = input
}
@Suppress("NOTHING_TO_INLINE")
inline infix fun <PSI : PsiElement, INPUT : HLApplicatorInput> PSI.withInput(input: INPUT) =
HLApplicatorTargetWithInput(this, input)
@@ -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<DIAGNOSTIC_PSI : PsiElement, in DIAGNOSTIC : KtDiagnosticWithPsi<DIAGNOSTIC_PSI>, TARGET_PSI : PsiElement, INPUT : HLApplicatorInput>(
val applicator: HLApplicator<TARGET_PSI, INPUT>
) {
abstract fun KtAnalysisSession.createTargets(diagnostic: DIAGNOSTIC): List<HLApplicatorTargetWithInput<TARGET_PSI, INPUT>>
}
private class HLDiagnosticFixFactoryImpl<DIAGNOSTIC_PSI : PsiElement, DIAGNOSTIC : KtDiagnosticWithPsi<DIAGNOSTIC_PSI>, TARGET_PSI : PsiElement, INPUT : HLApplicatorInput>(
applicator: HLApplicator<TARGET_PSI, INPUT>,
private val createQuickFixes: KtAnalysisSession.(DIAGNOSTIC) -> List<HLApplicatorTargetWithInput<TARGET_PSI, INPUT>>
) : HLDiagnosticFixFactory<DIAGNOSTIC_PSI, DIAGNOSTIC, TARGET_PSI, INPUT>(applicator) {
override fun KtAnalysisSession.createTargets(diagnostic: DIAGNOSTIC): List<HLApplicatorTargetWithInput<TARGET_PSI, INPUT>> =
createQuickFixes.invoke(this, diagnostic)
}
internal fun <DIAGNOSTIC : KtDiagnosticWithPsi<PsiElement>> KtAnalysisSession.createPlatformQuickFixes(
diagnostic: DIAGNOSTIC,
factory: HLDiagnosticFixFactory<PsiElement, DIAGNOSTIC, PsiElement, HLApplicatorInput>
): List<IntentionAction> = with(factory) {
createTargets(diagnostic).map { (target, input) -> HLQuickFix(target, input, factory.applicator) }
}
fun <DIAGNOSTIC_PSI : PsiElement, DIAGNOSTIC : KtDiagnosticWithPsi<DIAGNOSTIC_PSI>, TARGET_PSI : PsiElement, INPUT : HLApplicatorInput> diagnosticFixFactory(
applicator: HLApplicator<TARGET_PSI, INPUT>,
createQuickFixes: KtAnalysisSession.(DIAGNOSTIC) -> List<HLApplicatorTargetWithInput<TARGET_PSI, INPUT>>
): HLDiagnosticFixFactory<DIAGNOSTIC_PSI, DIAGNOSTIC, TARGET_PSI, INPUT> =
HLDiagnosticFixFactoryImpl(applicator, createQuickFixes)
@@ -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<PSI : PsiElement, in INPUT : HLApplicatorInput>(
target: PSI,
private val input: INPUT,
val applicator: HLApplicator<PSI, INPUT>,
) : KotlinQuickFixAction<PSI>(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()
}
@@ -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())
@@ -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<KClass<out KtDiagnosticWithPsi<*>>, List<HLQuickFixFactory>>
) {
fun KtAnalysisSession.getQuickFixesFor(diagnostic: KtDiagnosticWithPsi<*>): List<IntentionAction> {
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<IntentionAction> = when (quickFixFactory) {
is HLQuickFixFactory.HLApplicatorBasedFactory -> {
@Suppress("UNCHECKED_CAST")
val factory = quickFixFactory.applicatorFactory
as HLDiagnosticFixFactory<PsiElement, KtDiagnosticWithPsi<PsiElement>, PsiElement, HLApplicatorInput>
createPlatformQuickFixes(diagnostic, factory)
}
is HLQuickFixFactory.HLQuickFixesPsiBasedFactory -> quickFixFactory.psiFactory.createQuickFix(diagnostic.psi)
}
companion object {
@OptIn(ForKtQuickFixesListBuilder::class)
fun createCombined(registrars: List<KtQuickFixesList>): 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<KClass<out KtDiagnosticWithPsi<*>>, MutableList<HLQuickFixFactory>>()
@OptIn(PrivateForInline::class)
inline fun <DIAGNOSTIC_PSI : PsiElement, reified DIAGNOSTIC : KtDiagnosticWithPsi<DIAGNOSTIC_PSI>> registerPsiQuickFix(
quickFixFactory: QuickFixesPsiBasedFactory<DIAGNOSTIC_PSI>
) {
registerPsiQuickFix(DIAGNOSTIC::class, quickFixFactory)
}
@OptIn(PrivateForInline::class)
inline fun <DIAGNOSTIC_PSI : PsiElement, reified DIAGNOSTIC : KtDiagnosticWithPsi<DIAGNOSTIC_PSI>, TARGET_PSI : PsiElement, INPUT : HLApplicatorInput> registerApplicator(
quickFixFactory: HLDiagnosticFixFactory<DIAGNOSTIC_PSI, DIAGNOSTIC, TARGET_PSI, INPUT>
) {
registerApplicator(DIAGNOSTIC::class, quickFixFactory)
}
@PrivateForInline
fun <DIAGNOSTIC_PSI : PsiElement, DIAGNOSTIC : KtDiagnosticWithPsi<DIAGNOSTIC_PSI>> registerPsiQuickFix(
diagnosticClass: KClass<DIAGNOSTIC>,
quickFixFactory: QuickFixesPsiBasedFactory<DIAGNOSTIC_PSI>
) {
quickFixes.getOrPut(diagnosticClass) { mutableListOf() }.add(HLQuickFixFactory.HLQuickFixesPsiBasedFactory(quickFixFactory))
}
@PrivateForInline
fun <DIAGNOSTIC_PSI : PsiElement, DIAGNOSTIC : KtDiagnosticWithPsi<DIAGNOSTIC_PSI>, TARGET_PSI : PsiElement, INPUT : HLApplicatorInput> registerApplicator(
diagnosticClass: KClass<DIAGNOSTIC>,
quickFixFactory: HLDiagnosticFixFactory<DIAGNOSTIC_PSI, DIAGNOSTIC, TARGET_PSI, INPUT>
) {
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 <K, V> List<Map<K, List<V>>>.merge(): Map<K, List<V>> {
return flatMap { it.entries }
.groupingBy { it.key }
.aggregate<Map.Entry<K, List<V>>, K, MutableList<V>> { _, accumulator, element, _ ->
val list = accumulator ?: mutableListOf()
list.addAll(element.value)
list
}
}
@RequiresOptIn
annotation class ForKtQuickFixesListBuilder()
@@ -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(
@@ -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<ELEMENT : KtElement, DATA : Any>(
val elementType: Class<ELEMENT>
) : 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
}
}
@@ -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<ELEMENT : KtElement, DATA : Any>(
elementType: Class<ELEMENT>,
@Nls private val textGetter: () -> String,
@Nls familyNameGetter: () -> String = textGetter,
) : SelfTargetingIntention<ELEMENT>(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<DATA?> {
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)
}
}
}
@@ -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<KClass<out KtDiagnosticWithPsi<*>>, MutableList<QuickFixFactory>>()
inline fun <reified PSI : PsiElement, reified DIAGNOSTIC : KtDiagnosticWithPsi<PSI>> register(
quickFixFactory: QuickFixesPsiBasedFactory<PSI>
) {
quickFixes.getOrPut(DIAGNOSTIC::class) { mutableListOf() }.add(quickFixFactory)
}
inline fun <reified PSI : PsiElement, reified DIAGNOSTIC : KtDiagnosticWithPsi<PSI>> register(
quickFixFactory: QuickFixesHLApiBasedFactory<PSI, DIAGNOSTIC>
) {
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<KClass<out KtDiagnosticWithPsi<*>>, List<QuickFixFactory>>) {
fun KtAnalysisSession.getQuickFixesFor(diagnostic: KtDiagnosticWithPsi<*>): List<IntentionAction> {
val factories = quickFixes[diagnostic.diagnosticClass] ?: return emptyList()
return factories.flatMap { createQuickFixes(it, diagnostic) }
}
@Suppress("UNCHECKED_CAST")
private fun KtAnalysisSession.createQuickFixes(
quickFixFactory: QuickFixFactory,
diagnostic: KtDiagnosticWithPsi<PsiElement>
): List<IntentionAction> = when (quickFixFactory) {
is QuickFixesPsiBasedFactory<*> -> quickFixFactory.createQuickFix(diagnostic.psi)
is QuickFixesHLApiBasedFactory<*, *> -> with(quickFixFactory as QuickFixesHLApiBasedFactory<PsiElement, KtDiagnosticWithPsi<*>>) {
createQuickFix(diagnostic)
}
else -> error("Unsupported QuickFixFactory $quickFixFactory")
}
companion object {
@OptIn(ForKtQuickFixesListBuilder::class)
fun createCombined(registrars: List<KtQuickFixesList>): KtQuickFixesList {
val allQuickFixes = registrars.map { it.quickFixes }.merge()
return KtQuickFixesList(allQuickFixes)
}
fun createCombined(vararg registrars: KtQuickFixesList): KtQuickFixesList {
return createCombined(registrars.toList())
}
}
}
private fun <K, V> List<Map<K, List<V>>>.merge(): Map<K, List<V>> {
return flatMap { it.entries }
.groupingBy { it.key }
.aggregate<Map.Entry<K, List<V>>, K, MutableList<V>> { _, accumulator, element, _ ->
val list = accumulator ?: mutableListOf()
list.addAll(element.value)
list
}
}
@@ -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<PsiElement, KtFirDiagnostic.RedundantModifier>(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true))
register<PsiElement, KtFirDiagnostic.IncompatibleModifiers>(RemoveModifierFix.createRemoveModifierFactory(isRedundant = false))
register<PsiElement, KtFirDiagnostic.RepeatedModifier>(RemoveModifierFix.createRemoveModifierFactory(isRedundant = false))
register<PsiElement, KtFirDiagnostic.DeprecatedModifierPair>(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true))
register<PsiElement, KtFirDiagnostic.TypeParametersInEnum>(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true))
register<KtModifierListOwner, KtFirDiagnostic.RedundantOpenInInterface>(
private val modifiers = KtQuickFixesListBuilder.registerPsiQuickFix {
registerPsiQuickFix<PsiElement, KtFirDiagnostic.RedundantModifier>(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true))
registerPsiQuickFix<PsiElement, KtFirDiagnostic.IncompatibleModifiers>(RemoveModifierFix.createRemoveModifierFactory(isRedundant = false))
registerPsiQuickFix<PsiElement, KtFirDiagnostic.RepeatedModifier>(RemoveModifierFix.createRemoveModifierFactory(isRedundant = false))
registerPsiQuickFix<PsiElement, KtFirDiagnostic.DeprecatedModifierPair>(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true))
registerPsiQuickFix<PsiElement, KtFirDiagnostic.TypeParametersInEnum>(RemoveModifierFix.createRemoveModifierFactory(isRedundant = true))
registerPsiQuickFix<KtModifierListOwner, KtFirDiagnostic.RedundantOpenInInterface>(
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(
@@ -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<PSI : PsiElement, DIAGNOSTIC : KtDiagnosticWithPsi<PSI>> : 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<IntentionAction>
}
inline fun <PSI : PsiElement, DIAGNOSTIC : KtDiagnosticWithPsi<PSI>> quickFixesHLApiBasedFactory(
crossinline createQuickFix: KtAnalysisSession.(DIAGNOSTIC) -> List<IntentionAction>
) = object : QuickFixesHLApiBasedFactory<PSI, DIAGNOSTIC>() {
override fun KtAnalysisSession.createQuickFix(diagnostic: DIAGNOSTIC): List<IntentionAction> =
createQuickFix(diagnostic)
}
@@ -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<KtCallableDeclaration, Input> {
familyName(CallableReturnTypeUpdaterApplicator.applicator.getFamilyName())
class ChangeTypeQuickFix internal constructor(
declaration: KtCallableDeclaration,
private val typeInfo: TypeInfo,
private val updateBaseFunction: Boolean
) : KotlinQuickFixAction<KtCallableDeclaration>(declaration) {
override fun getText(): String {
val element = element ?: return ""
val functionPresentation = when {
updateBaseFunction -> {
val containerName = element.parentOfType<KtNamedDeclaration>()?.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<KtNamedDeclaration, KtFirDiagnostic.ReturnTypeMismatchOnOverride> {
it.function as? KtFunctionSymbol
}
val changePropertyReturnTypeOnOverride =
ChangeTypeQuickFixFactory.changeReturnTypeOnOverride<KtNamedDeclaration, KtFirDiagnostic.PropertyTypeMismatchOnOverride> {
it.property as? KtPropertySymbol
}
val changeVariableReturnTypeOnOverride =
ChangeTypeQuickFixFactory.changeReturnTypeOnOverride<KtNamedDeclaration, KtFirDiagnostic.VarTypeMismatchOnOverride> {
it.variable as? KtPropertySymbol
}
private fun getPresentation(
updateBaseFunction: Boolean,
declaration: KtCallableDeclaration
) = when {
updateBaseFunction -> {
val containerName = declaration.parentOfType<KtNamedDeclaration>()?.nameAsName?.takeUnless { it.isSpecial }
ChangeCallableReturnTypeFix.StringPresentation.baseFunctionOrConstructorParameterPresentation(
declaration,
containerName
)
}
else -> null
}
}
private object ChangeTypeQuickFixFactory {
inline fun <PSI : KtNamedDeclaration, DIAGNOSTIC : KtDiagnosticWithPsi<PSI>> changeReturnTypeOnOverride(
data class Input(
val updateBaseFunction: Boolean,
val type: CallableReturnTypeUpdaterApplicator.Type
) : HLApplicatorInput {
override fun isValidFor(psi: PsiElement): Boolean = type.isValidFor(psi)
}
val changeFunctionReturnTypeOnOverride =
changeReturnTypeOnOverride<KtFirDiagnostic.ReturnTypeMismatchOnOverride> {
it.function as? KtFunctionSymbol
}
val changePropertyReturnTypeOnOverride =
changeReturnTypeOnOverride<KtFirDiagnostic.PropertyTypeMismatchOnOverride> {
it.property as? KtPropertySymbol
}
val changeVariableReturnTypeOnOverride =
changeReturnTypeOnOverride<KtFirDiagnostic.VarTypeMismatchOnOverride> {
it.variable as? KtPropertySymbol
}
private inline fun <DIAGNOSTIC : KtDiagnosticWithPsi<KtNamedDeclaration>> changeReturnTypeOnOverride(
crossinline getCallableSymbol: (DIAGNOSTIC) -> KtCallableSymbol?
) = quickFixesHLApiBasedFactory<PSI, DIAGNOSTIC> { diagnostic ->
val declaration = diagnostic.psi as? KtCallableDeclaration ?: return@quickFixesHLApiBasedFactory emptyList()
val callable = getCallableSymbol(diagnostic) ?: return@quickFixesHLApiBasedFactory emptyList()
) = diagnosticFixFactory<KtNamedDeclaration, DIAGNOSTIC, KtCallableDeclaration, Input>(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 <PSI : KtCallableDeclaration> KtAnalysisSession.createChangeCurrentDeclarationQuickFix(
private fun <PSI : KtCallableDeclaration> KtAnalysisSession.createChangeCurrentDeclarationQuickFix(
callable: KtCallableSymbol,
declaration: PSI
): ChangeTypeQuickFix? {
): HLApplicatorTargetWithInput<PSI, Input>? {
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<KtCallableDeclaration, Input>? {
val type = callable.annotatedType.type
val singleNonMatchingOverriddenFunction = findSingleNonMatchingOverriddenFunction(callable, type) ?: return null
val singleMatchingOverriddenFunctionPsi = singleNonMatchingOverriddenFunction.psiSafe<KtCallableDeclaration>() ?: 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
@@ -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<ELEMENT : KtElement, DATA : Any>(
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()
}
+2 -5
View File
@@ -269,7 +269,7 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu
<projectService serviceInterface="org.jetbrains.kotlin.idea.util.FirPluginOracleService"
serviceImplementation="org.jetbrains.kotlin.idea.util.FirPluginOracleServiceFirImpl"/>
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.quickfix.KtQuickFixService"/>
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixService"/>
<readWriteAccessDetector implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector" id="kotlin"/>
@@ -289,10 +289,7 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu
fieldName="INSTANCE"
extensions="kotlin_module"/>
<!-- <intentionAction>
<className>org.jetbrains.kotlin.idea.fir.inspections.AddFunctionReturnTypeIntention</className>
<category>Kotlin</category>
</intentionAction>-->
</extensions>
</idea-plugin>