[kotlin] Move ChangeToLabeledReturnFix to frontend independent module

...and convert it to ModCommandAction.

^KTIJ-29481

GitOrigin-RevId: bd3352014f0592c5ee133532d9d7aa4920ec05c2
This commit is contained in:
Andrey Cherkasov
2024-05-23 01:32:05 +00:00
committed by intellij-monorepo-bot
parent 40f0a6d833
commit 135e4bbd35
18 changed files with 105 additions and 130 deletions
@@ -1,16 +1,12 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.k2.codeinsight.fixes
import com.intellij.modcommand.ActionContext
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KtFirDiagnostic
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.KotlinPsiUpdateModCommandAction
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinQuickFixFactory
import org.jetbrains.kotlin.idea.codeinsight.utils.ChangeToLabeledReturnUtils
import org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
@@ -77,25 +73,4 @@ internal object ChangeToLabeledReturnFixFactory {
if (!returnType.isSubTypeOf(lambdaReturnType)) return null
return returnExpression
}
private class ChangeToLabeledReturnFix(
element: KtReturnExpression,
private val labeledReturn: String,
) : KotlinPsiUpdateModCommandAction.ElementBased<KtReturnExpression, Unit>(element, Unit) {
override fun invoke(
actionContext: ActionContext,
element: KtReturnExpression,
elementContext: Unit,
updater: ModPsiUpdater,
) = ChangeToLabeledReturnUtils.applyTo(actionContext.project, element, labeledReturn)
override fun getFamilyName(): String = KotlinBundle.message("fix.change.to.labeled.return.family")
override fun getActionName(
actionContext: ActionContext,
element: KtReturnExpression,
elementContext: Unit,
): String = KotlinBundle.message("fix.change.to.labeled.return.text", labeledReturn)
}
}
@@ -1,24 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.codeinsight.utils
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.createExpressionByPattern
object ChangeToLabeledReturnUtils {
fun applyTo(
project: Project,
returnExpression: KtReturnExpression,
labeledReturn: String,
) {
val factory = KtPsiFactory(project)
val returnedExpression = returnExpression.returnedExpression
val newExpression = if (returnedExpression == null)
factory.createExpression(labeledReturn)
else
factory.createExpressionByPattern("$0 $1", labeledReturn, returnedExpression)
returnExpression.replace(newExpression)
}
}
@@ -0,0 +1,39 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.modcommand.ActionContext
import com.intellij.modcommand.ModPsiUpdater
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.KotlinPsiUpdateModCommandAction
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.createExpressionByPattern
class ChangeToLabeledReturnFix(
element: KtReturnExpression,
private val labeledReturn: String,
) : KotlinPsiUpdateModCommandAction.ElementBased<KtReturnExpression, Unit>(element, Unit) {
override fun invoke(
actionContext: ActionContext,
element: KtReturnExpression,
elementContext: Unit,
updater: ModPsiUpdater,
) {
val factory = KtPsiFactory(actionContext.project)
val returnedExpression = element.returnedExpression
val newExpression = if (returnedExpression == null)
factory.createExpression(labeledReturn)
else
factory.createExpressionByPattern("$0 $1", labeledReturn, returnedExpression)
element.replace(newExpression)
}
override fun getFamilyName(): String = KotlinBundle.message("fix.change.to.labeled.return.family")
override fun getActionName(
actionContext: ActionContext,
element: KtReturnExpression,
elementContext: Unit,
): String = KotlinBundle.message("fix.change.to.labeled.return.text", labeledReturn)
}
@@ -9,6 +9,7 @@ import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
@@ -17,8 +18,6 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.returnExpressionVisitor
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection
class UnlabeledReturnInsideLambdaInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor =
returnExpressionVisitor(fun(returnExpression: KtReturnExpression) {
@@ -26,11 +25,12 @@ class UnlabeledReturnInsideLambdaInspection : AbstractKotlinInspection() {
val lambda = returnExpression.getParentOfType<KtLambdaExpression>(true, KtNamedFunction::class.java) ?: return
val parentFunction = lambda.getStrictParentOfType<KtNamedFunction>() ?: return
if (returnExpression.analyze().diagnostics.forElement(returnExpression).any { it.severity == Severity.ERROR }) return
val action = ChangeToLabeledReturnFix(returnExpression, labeledReturn = "return@${parentFunction.name}")
holder.registerProblem(
returnExpression.returnKeyword,
KotlinBundle.message("unlabeled.return.inside.lambda"),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
IntentionWrapper(ChangeToLabeledReturnFix(returnExpression, labeledReturn = "return@${parentFunction.name}"))
IntentionWrapper(action.asIntention())
)
})
}
@@ -3,15 +3,11 @@
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.modcommand.ModCommandAction
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.codeinsight.utils.ChangeToLabeledReturnUtils
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.findLabelAndCall
@@ -23,66 +19,55 @@ import org.jetbrains.kotlin.resolve.calls.util.getType
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
class ChangeToLabeledReturnFix(
element: KtReturnExpression, val labeledReturn: String
) : KotlinQuickFixAction<KtReturnExpression>(element) {
internal object ChangeToLabeledReturnFixFactory: KotlinIntentionActionsFactory() {
private fun findAccessibleLabels(bindingContext: BindingContext, position: KtReturnExpression): List<Name> {
val result = mutableListOf<Name>()
for (parent in position.parentsWithSelf) {
when (parent) {
is KtClassOrObject -> break
is KtFunctionLiteral -> {
val (label, call) = parent.findLabelAndCall()
if (label != null) {
result.add(label)
}
override fun getFamilyName() = KotlinBundle.message("fix.change.to.labeled.return.family")
override fun getText() = KotlinBundle.message("fix.change.to.labeled.return.text", labeledReturn)
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val returnExpression = element ?: return
return ChangeToLabeledReturnUtils.applyTo(project, returnExpression, labeledReturn)
// check if the current function literal is inlined and stop processing outer declarations if it's not
val callee = call?.calleeExpression as? KtReferenceExpression ?: break
if (!InlineUtil.isInline(bindingContext[BindingContext.REFERENCE_TARGET, callee])) break
}
else -> {}
}
}
return result
}
companion object : KotlinIntentionActionsFactory() {
private fun findAccessibleLabels(bindingContext: BindingContext, position: KtReturnExpression): List<Name> {
val result = mutableListOf<Name>()
for (parent in position.parentsWithSelf) {
when (parent) {
is KtClassOrObject -> break
is KtFunctionLiteral -> {
val (label, call) = parent.findLabelAndCall()
if (label != null) {
result.add(label)
}
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val element = diagnostic.psiElement as? KtElement ?: return emptyList()
val context by lazy { element.analyze() }
// check if the current function literal is inlined and stop processing outer declarations if it's not
val callee = call?.calleeExpression as? KtReferenceExpression ?: break
if (!InlineUtil.isInline(bindingContext[BindingContext.REFERENCE_TARGET, callee])) break
}
else -> {}
}
}
return result
}
val returnExpression = when (diagnostic.factory) {
Errors.RETURN_NOT_ALLOWED ->
diagnostic.psiElement as? KtReturnExpression
Errors.TYPE_MISMATCH,
Errors.TYPE_MISMATCH_WARNING,
Errors.CONSTANT_EXPECTED_TYPE_MISMATCH,
Errors.NULL_FOR_NONNULL_TYPE ->
getLambdaReturnExpression(diagnostic.psiElement, context)
else -> null
} ?: return emptyList()
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val element = diagnostic.psiElement as? KtElement ?: return emptyList()
val context by lazy { element.analyze() }
val candidates = findAccessibleLabels(context, returnExpression)
return candidates
.map { ChangeToLabeledReturnFix(returnExpression, labeledReturn = "return@${it.render()}") }
.map(ModCommandAction::asIntention)
}
val returnExpression = when (diagnostic.factory) {
Errors.RETURN_NOT_ALLOWED ->
diagnostic.psiElement as? KtReturnExpression
Errors.TYPE_MISMATCH,
Errors.TYPE_MISMATCH_WARNING,
Errors.CONSTANT_EXPECTED_TYPE_MISMATCH,
Errors.NULL_FOR_NONNULL_TYPE ->
getLambdaReturnExpression(diagnostic.psiElement, context)
else -> null
} ?: return emptyList()
val candidates = findAccessibleLabels(context, returnExpression)
return candidates.map { ChangeToLabeledReturnFix(returnExpression, labeledReturn = "return@${it.render()}") }
}
private fun getLambdaReturnExpression(element: PsiElement, bindingContext: BindingContext): KtReturnExpression? {
val returnExpression = element.getStrictParentOfType<KtReturnExpression>() ?: return null
val lambda = returnExpression.getStrictParentOfType<KtLambdaExpression>() ?: return null
val lambdaReturnType = bindingContext[BindingContext.FUNCTION, lambda.functionLiteral]?.returnType ?: return null
val returnType = returnExpression.returnedExpression?.getType(bindingContext) ?: return null
if (!returnType.isSubtypeOf(lambdaReturnType)) return null
return returnExpression
}
private fun getLambdaReturnExpression(element: PsiElement, bindingContext: BindingContext): KtReturnExpression? {
val returnExpression = element.getStrictParentOfType<KtReturnExpression>() ?: return null
val lambda = returnExpression.getStrictParentOfType<KtLambdaExpression>() ?: return null
val lambdaReturnType = bindingContext[BindingContext.FUNCTION, lambda.functionLiteral]?.returnType ?: return null
val returnType = returnExpression.returnedExpression?.getType(bindingContext) ?: return null
if (!returnType.isSubtypeOf(lambdaReturnType)) return null
return returnExpression
}
}
@@ -650,11 +650,11 @@ class QuickFixRegistrar : QuickFixContributor {
MUST_BE_INITIALIZED_OR_BE_ABSTRACT.registerFactory(AddModifierFixFE10.AddLateinitFactory)
RETURN_NOT_ALLOWED.registerFactory(ChangeToLabeledReturnFix)
TYPE_MISMATCH.registerFactory(ChangeToLabeledReturnFix)
TYPE_MISMATCH_WARNING.registerFactory(ChangeToLabeledReturnFix)
CONSTANT_EXPECTED_TYPE_MISMATCH.registerFactory(ChangeToLabeledReturnFix)
NULL_FOR_NONNULL_TYPE.registerFactory(ChangeToLabeledReturnFix)
RETURN_NOT_ALLOWED.registerFactory(ChangeToLabeledReturnFixFactory)
TYPE_MISMATCH.registerFactory(ChangeToLabeledReturnFixFactory)
TYPE_MISMATCH_WARNING.registerFactory(ChangeToLabeledReturnFixFactory)
CONSTANT_EXPECTED_TYPE_MISMATCH.registerFactory(ChangeToLabeledReturnFixFactory)
NULL_FOR_NONNULL_TYPE.registerFactory(ChangeToLabeledReturnFixFactory)
WRONG_ANNOTATION_TARGET.registerFactory(AddAnnotationTargetFix, AddAnnotationUseSiteTargetFix)
WRONG_ANNOTATION_TARGET_WITH_USE_SITE_TARGET.registerFactory(MoveReceiverAnnotationFix, AddAnnotationTargetFix)
@@ -8,4 +8,4 @@ fun test() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
@@ -8,4 +8,4 @@ fun test() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
@@ -17,4 +17,4 @@ fun bar() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
@@ -17,4 +17,4 @@ fun bar() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
@@ -16,4 +16,4 @@ fun bar() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
@@ -16,4 +16,4 @@ fun bar() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
@@ -8,4 +8,4 @@ fun bar() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
@@ -8,4 +8,4 @@ fun bar() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
@@ -10,4 +10,4 @@ fun test() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
@@ -10,4 +10,4 @@ fun test() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
@@ -10,4 +10,4 @@ fun test() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
@@ -10,4 +10,4 @@ fun test() {
}
}
// FUS_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.k2.codeinsight.fixes.ChangeToLabeledReturnFixFactory$ChangeToLabeledReturnFix
// FUS_K2_QUICKFIX_NAME: org.jetbrains.kotlin.idea.quickfix.ChangeToLabeledReturnFix