KTIJ-30535 [kotlin] Migrate quickfixes used in SuspiciousCollectionReassignmentInspection to ModCommand API

This allows to exclude the corresponding fixes from the `allowResolveInWriteAction` whitelist,
because ModCommand actions are performed in the background

GitOrigin-RevId: b551e1381d196e3b0e3d5078fe91c3445dbb81ff
This commit is contained in:
Roman Golyshev
2024-07-13 15:32:42 +00:00
committed by intellij-monorepo-bot
parent de33eef5e2
commit 03fa8a890f
4 changed files with 42 additions and 34 deletions
@@ -4,17 +4,19 @@ package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInsight.intention.FileModifier.SafeFieldForPreview
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.base.psi.replaced
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinModCommandQuickFix
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.project.builtIns
import org.jetbrains.kotlin.idea.quickfix.ChangeToMutableCollectionFix
import org.jetbrains.kotlin.idea.references.mainReference
@@ -33,9 +35,6 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.SimpleType
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.codeinsight.utils.findExistingEditor
class SuspiciousCollectionReassignmentInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor =
binaryExpressionVisitor(fun(binaryExpression) {
@@ -75,19 +74,19 @@ class SuspiciousCollectionReassignmentInspection : AbstractKotlinInspection() {
)
})
private class ChangeTypeToMutableFix(@SafeFieldForPreview private val type: KotlinType) : LocalQuickFix {
private class ChangeTypeToMutableFix(@SafeFieldForPreview private val type: KotlinType) : KotlinModCommandQuickFix<KtOperationReferenceExpression>() {
override fun getName() = KotlinBundle.message("change.type.to.mutable.fix.text")
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val operationReference = descriptor.psiElement as? KtOperationReferenceExpression ?: return
override fun applyFix(project: Project, element: KtOperationReferenceExpression, updater: ModPsiUpdater) {
val operationReference = element
val binaryExpression = operationReference.parent as? KtBinaryExpression ?: return
val left = binaryExpression.left ?: return
val property = left.mainReference?.resolve() as? KtProperty ?: return
ChangeToMutableCollectionFix.applyFix(property, type)
property.valOrVarKeyword.replace(KtPsiFactory(project).createValKeyword())
binaryExpression.findExistingEditor()?.caretModel?.moveToOffset(property.endOffset)
updater.moveCaretTo(property.endOffset)
}
companion object {
@@ -97,13 +96,13 @@ class SuspiciousCollectionReassignmentInspection : AbstractKotlinInspection() {
}
}
private class ReplaceWithFilterFix : LocalQuickFix {
private class ReplaceWithFilterFix : KotlinModCommandQuickFix<KtOperationReferenceExpression>() {
override fun getName() = KotlinBundle.message("replace.with.filter.fix.text")
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val operationReference = descriptor.psiElement as? KtOperationReferenceExpression ?: return
override fun applyFix(project: Project, element: KtOperationReferenceExpression, updater: ModPsiUpdater) {
val operationReference = element
val binaryExpression = operationReference.parent as? KtBinaryExpression ?: return
val left = binaryExpression.left ?: return
val right = binaryExpression.right ?: return
@@ -121,13 +120,13 @@ class SuspiciousCollectionReassignmentInspection : AbstractKotlinInspection() {
}
}
private class ReplaceWithAssignmentFix : LocalQuickFix {
private class ReplaceWithAssignmentFix : KotlinModCommandQuickFix<KtOperationReferenceExpression>() {
override fun getName() = KotlinBundle.message("replace.with.assignment.fix.text")
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val operationReference = descriptor.psiElement as? KtOperationReferenceExpression ?: return
override fun applyFix(project: Project, element: KtOperationReferenceExpression, updater: ModPsiUpdater) {
val operationReference = element
val psiFactory = KtPsiFactory(project)
operationReference.replace(psiFactory.createOperationName(KtTokens.EQ.value))
}
@@ -161,13 +160,13 @@ class SuspiciousCollectionReassignmentInspection : AbstractKotlinInspection() {
}
}
private class JoinWithInitializerFix(@SafeFieldForPreview private val op: KtSingleValueToken) : LocalQuickFix {
private class JoinWithInitializerFix(@SafeFieldForPreview private val op: KtSingleValueToken) : KotlinModCommandQuickFix<KtOperationReferenceExpression>() {
override fun getName() = KotlinBundle.message("join.with.initializer.fix.text")
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val operationReference = descriptor.psiElement as? KtOperationReferenceExpression ?: return
override fun applyFix(project: Project, element: KtOperationReferenceExpression, updater: ModPsiUpdater) {
val operationReference = element
val binaryExpression = operationReference.parent as? KtBinaryExpression ?: return
val left = binaryExpression.left ?: return
val right = binaryExpression.right ?: return
@@ -178,7 +177,7 @@ class SuspiciousCollectionReassignmentInspection : AbstractKotlinInspection() {
val newOp = if (op == KtTokens.PLUSEQ) KtTokens.PLUS else KtTokens.MINUS
val replaced = initializer.replaced(psiFactory.createExpressionByPattern("$0 $1 $2", initializer, newOp.value, right))
binaryExpression.delete()
property.findExistingEditor()?.caretModel?.moveToOffset(replaced.endOffset)
updater.moveCaretTo(replaced.endOffset)
}
companion object {
@@ -2,8 +2,11 @@
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.modcommand.ActionContext
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.modcommand.Presentation
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
@@ -11,7 +14,7 @@ import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.base.psi.replaced
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.KotlinPsiUpdateModCommandAction
import org.jetbrains.kotlin.idea.project.builtIns
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.*
@@ -21,21 +24,30 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
class ChangeToMutableCollectionFix(property: KtProperty, private val type: String) : KotlinQuickFixAction<KtProperty>(property) {
override fun getText() = KotlinBundle.message("fix.change.to.mutable.type.text", "Mutable$type")
class ChangeToMutableCollectionFix(property: KtProperty, context: ElementContext) :
KotlinPsiUpdateModCommandAction.ElementBased<KtProperty, ChangeToMutableCollectionFix.ElementContext>(property, context) {
override fun getFamilyName() = text
class ElementContext(
val typeName: String,
)
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val property = element ?: return
override fun getPresentation(context: ActionContext, element: KtProperty): Presentation {
val elementContext = getElementContext(context, element)
return Presentation.of(KotlinBundle.message("fix.change.to.mutable.type.text", "Mutable${elementContext.typeName}"))
}
override fun getFamilyName(): @IntentionFamilyName String = KotlinBundle.message("fix.change.to.mutable.type.family")
override fun invoke(actionContext: ActionContext, element: KtProperty, elementContext: ElementContext, updater: ModPsiUpdater) {
val property = element
val context = property.analyze(BodyResolveMode.PARTIAL)
val type = property.initializer?.getType(context) ?: return
applyFix(property, type)
editor?.caretModel?.moveToOffset(property.endOffset)
updater.moveCaretTo(property.endOffset)
}
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtProperty>? {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val element = Errors.NO_SET_METHOD.cast(diagnostic).psiElement as? KtArrayAccessExpression ?: return null
val arrayExpr = element.arrayExpression ?: return null
val context = arrayExpr.analyze(BodyResolveMode.PARTIAL)
@@ -44,7 +56,7 @@ class ChangeToMutableCollectionFix(property: KtProperty, private val type: Strin
val property = arrayExpr.mainReference?.resolve() as? KtProperty ?: return null
if (!isApplicable(property)) return null
val typeName = type.constructor.declarationDescriptor?.name?.asString() ?: return null
return ChangeToMutableCollectionFix(property, typeName)
return ChangeToMutableCollectionFix(property, ElementContext(typeName)).asIntention()
}
private fun KotlinType.isReadOnlyListOrMap(builtIns: KotlinBuiltIns): Boolean {
@@ -3,6 +3,6 @@
// WITH_STDLIB
fun test() {
val list = mutableListOf(1)<caret>
list <selection>+=</selection> 2
list += 2
}
// OFFLINE_REPORT: "'+=' on a read-only list creates a new list under the hood"
@@ -9,8 +9,6 @@ org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.IntentionBasedInsp
org.jetbrains.kotlin.idea.codeInsight.inspections.shared.KotlinOptimizeImportsQuickFix
org.jetbrains.kotlin.idea.inspections.migration.ObsoleteCoroutinesDelegateQuickFix
org.jetbrains.kotlin.idea.inspections.SafeDeleteFix
org.jetbrains.kotlin.idea.inspections.SuspiciousCollectionReassignmentInspection$ChangeTypeToMutableFix
org.jetbrains.kotlin.idea.inspections.SuspiciousCollectionReassignmentInspection$JoinWithInitializerFix
org.jetbrains.kotlin.idea.inspections.UnusedReceiverParameterInspection$RemoveReceiverFix
org.jetbrains.kotlin.idea.intentions.AddPropertyAccessorsIntention
org.jetbrains.kotlin.idea.intentions.AddPropertyGetterIntention
@@ -37,7 +35,6 @@ org.jetbrains.kotlin.idea.quickfix.ChangeFunctionLiteralSignatureFix
org.jetbrains.kotlin.idea.quickfix.ChangeFunctionLiteralReturnTypeFix
org.jetbrains.kotlin.idea.quickfix.ChangeFunctionSignatureFix$Companion$RemoveParameterFix
org.jetbrains.kotlin.idea.quickfix.ChangeParameterTypeFix
org.jetbrains.kotlin.idea.quickfix.ChangeToMutableCollectionFix
org.jetbrains.kotlin.idea.quickfix.ChangeVariableTypeFix
org.jetbrains.kotlin.idea.quickfix.ChangeVariableTypeFix$ForOverridden
org.jetbrains.kotlin.idea.quickfix.ChangeVisibilityFix$ChangeToInternalFix