[kotlin] Port SpecifyTypeExplicitlyInDestructuringAssignmentIntention to K2 as shared

Caret is not in desired places in lambdas

KTIJ-32017

GitOrigin-RevId: 21f50626545ddb3470b461068e02c1a87bd471e2
This commit is contained in:
Victoria.Petrakovich
2025-07-14 18:40:10 +00:00
committed by intellij-monorepo-bot
parent 3e72f3b334
commit fcda28fd46
20 changed files with 174 additions and 79 deletions
@@ -413,6 +413,13 @@
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.codeInsight.intentions.shared.SpecifyTypeExplicitlyInDestructuringAssignmentIntention</className>
<bundleName>messages.KotlinBundle</bundleName>
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.codeInsight.intentions.shared.ConvertLazyPropertyToOrdinaryIntention</className>
@@ -1,44 +1,90 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.intentions
package org.jetbrains.kotlin.idea.codeInsight.intentions.shared
import com.intellij.openapi.editor.Editor
import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.modcommand.ActionContext
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.openapi.util.TextRange
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.psi.createSmartPointer
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.renderer.types.impl.KaTypeRendererForSource
import org.jetbrains.kotlin.analysis.api.types.KaErrorType
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingRangeIntention
import org.jetbrains.kotlin.idea.core.setType
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.KotlinApplicableModCommandAction
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry
import org.jetbrains.kotlin.psi.KtParameterList
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.types.isError
import org.jetbrains.kotlin.types.Variance
class SpecifyTypeExplicitlyInDestructuringAssignmentIntention : SelfTargetingRangeIntention<KtDestructuringDeclaration>(
KtDestructuringDeclaration::class.java, KotlinBundle.lazyMessage("specify.all.types.explicitly.in.destructuring.declaration")
) {
override fun applicabilityRange(element: KtDestructuringDeclaration): TextRange? {
if (element.containingFile is KtCodeFragment) return null
internal class SpecifyTypeExplicitlyInDestructuringAssignmentIntention :
KotlinApplicableModCommandAction<KtDestructuringDeclaration, SpecifyTypeExplicitlyInDestructuringAssignmentIntention.Context>(
KtDestructuringDeclaration::class
) {
internal class Context(val entries: List<SmartPsiElementPointer<KtDestructuringDeclarationEntry>>)
override fun getFamilyName(): @IntentionFamilyName String =
KotlinBundle.message("specify.all.types.explicitly.in.destructuring.declaration")
override fun isApplicableByPsi(element: KtDestructuringDeclaration): Boolean {
if (element.containingFile is KtCodeFragment) return false
val entries = element.entriesWithoutExplicitTypes()
if (entries.isEmpty()) return null
if (entries.any { SpecifyTypeExplicitlyIntention.getTypeForDeclaration(it).isError }) return null
val endOffset = element.initializer?.let { it.startOffset - 1 } ?: element.endOffset
return TextRange(element.startOffset, endOffset)
return entries.isNotEmpty()
}
override fun applyTo(element: KtDestructuringDeclaration, editor: Editor?) {
override fun getApplicableRanges(element: KtDestructuringDeclaration): List<TextRange> {
val endOffset = element.initializer?.let { it.startOffset - 1 } ?: element.endOffset
return listOf(TextRange(0, endOffset - element.startOffset))
}
@OptIn(KaExperimentalApi::class)
override fun KaSession.prepareContext(element: KtDestructuringDeclaration): Context? {
val entries = element.entriesWithoutExplicitTypes()
if (editor != null && element.getParentOfType<KtParameterList>(strict = false) == null) {
SpecifyTypeExplicitlyIntention.addTypeAnnotationWithTemplate(editor, entries.iterator())
} else {
for (entry in entries) {
entry.setType(SpecifyTypeExplicitlyIntention.getTypeForDeclaration(entry))
if (entries.any { entry ->
val symbol = entry.symbol
symbol.returnType is KaErrorType
}) return null
return Context(entries.map { it.createSmartPointer() }.toList())
}
override fun invoke(
actionContext: ActionContext,
element: KtDestructuringDeclaration,
elementContext: Context,
updater: ModPsiUpdater,
) {
val entries = elementContext.entries
for (entry in entries) {
val element = entry.element ?: continue
setTypeForDeclaration(element)
if (entries.last() == entry) {
updater.moveCaretTo(element.endOffset)
}
}
}
private fun setTypeForDeclaration(entry: KtDestructuringDeclarationEntry) {
val factory = KtPsiFactory(entry.project)
val typeReference = factory.createType(entry.getKotlinType())
entry.typeReference = typeReference
}
}
private fun KtDestructuringDeclaration.entriesWithoutExplicitTypes(): List<KtDestructuringDeclarationEntry> =
entries.filter { it.typeReference == null }
@OptIn(KaExperimentalApi::class)
private fun KtDestructuringDeclarationEntry.getKotlinType(): String {
val entry = this
return analyze(entry) {
val symbol = entry.symbol
symbol.returnType.render(renderer = KaTypeRendererForSource.WITH_SHORT_NAMES, position = Variance.OUT_VARIANCE)
}
}
@@ -3035,6 +3035,55 @@ public abstract class SharedK1IntentionTestGenerated extends AbstractSharedK1Int
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment")
public static class SpecifyTypeExplicitlyInDestructuringAssignment extends AbstractSharedK1IntentionTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K1;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("in.kt")
public void testIn() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/in.kt");
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/lambda.kt");
}
@TestMetadata("lambdaHasSignature.kt")
public void testLambdaHasSignature() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/lambdaHasSignature.kt");
}
@TestMetadata("variableHasAllTypes.kt")
public void testVariableHasAllTypes() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasAllTypes.kt");
}
@TestMetadata("variableHasNoTypes.kt")
public void testVariableHasNoTypes() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasNoTypes.kt");
}
@TestMetadata("variableHasTypes.kt")
public void testVariableHasTypes() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasTypes.kt");
}
@TestMetadata("variableHasUnderscore.kt")
public void testVariableHasUnderscore() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasUnderscore.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/intentions/splitIf")
public abstract static class SplitIf extends AbstractSharedK1IntentionTest {
@@ -3035,6 +3035,55 @@ public abstract class SharedK2IntentionTestGenerated extends AbstractSharedK2Int
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment")
public static class SpecifyTypeExplicitlyInDestructuringAssignment extends AbstractSharedK2IntentionTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K2;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("in.kt")
public void testIn() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/in.kt");
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/lambda.kt");
}
@TestMetadata("lambdaHasSignature.kt")
public void testLambdaHasSignature() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/lambdaHasSignature.kt");
}
@TestMetadata("variableHasAllTypes.kt")
public void testVariableHasAllTypes() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasAllTypes.kt");
}
@TestMetadata("variableHasNoTypes.kt")
public void testVariableHasNoTypes() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasNoTypes.kt");
}
@TestMetadata("variableHasTypes.kt")
public void testVariableHasTypes() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasTypes.kt");
}
@TestMetadata("variableHasUnderscore.kt")
public void testVariableHasUnderscore() throws Exception {
runTest("../testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasUnderscore.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../testData/intentions/splitIf")
public abstract static class SplitIf extends AbstractSharedK2IntentionTest {
@@ -17846,55 +17846,6 @@ public abstract class K1IntentionTestGenerated extends AbstractK1IntentionTest {
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/intentions/specifyTypeExplicitlyInDestructuringAssignment")
public static class SpecifyTypeExplicitlyInDestructuringAssignment extends AbstractK1IntentionTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K1;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("in.kt")
public void testIn() throws Exception {
runTest("testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/in.kt");
}
@TestMetadata("lambda.kt")
public void testLambda() throws Exception {
runTest("testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/lambda.kt");
}
@TestMetadata("lambdaHasSignature.kt")
public void testLambdaHasSignature() throws Exception {
runTest("testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/lambdaHasSignature.kt");
}
@TestMetadata("variableHasAllTypes.kt")
public void testVariableHasAllTypes() throws Exception {
runTest("testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasAllTypes.kt");
}
@TestMetadata("variableHasNoTypes.kt")
public void testVariableHasNoTypes() throws Exception {
runTest("testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasNoTypes.kt");
}
@TestMetadata("variableHasTypes.kt")
public void testVariableHasTypes() throws Exception {
runTest("testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasTypes.kt");
}
@TestMetadata("variableHasUnderscore.kt")
public void testVariableHasUnderscore() throws Exception {
runTest("testData/intentions/specifyTypeExplicitlyInDestructuringAssignment/variableHasUnderscore.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/intentions/swapStringEqualsIgnoreCase")
public static class SwapStringEqualsIgnoreCase extends AbstractK1IntentionTest {
@@ -35,13 +35,6 @@
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.codeInsight.intentions.shared.SpecifyTypeExplicitlyInDestructuringAssignmentIntention</className>
<bundleName>messages.KotlinBundle</bundleName>
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeIntention</className>