FIR IDE: introduce infrastructure for HL based inspections & intentions
This commit is contained in:
+11
-2
@@ -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>
|
||||
}
|
||||
+49
@@ -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
|
||||
}
|
||||
+24
@@ -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()
|
||||
|
||||
|
||||
+21
@@ -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()
|
||||
}
|
||||
+1
-2
@@ -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()
|
||||
+1
-1
@@ -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(
|
||||
|
||||
-91
@@ -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
|
||||
}
|
||||
}
|
||||
-55
@@ -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)
|
||||
}
|
||||
+73
-75
@@ -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
|
||||
|
||||
-83
@@ -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()
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user