From cad4e7a412c42565a34bb59227de0b897b6ce6bd Mon Sep 17 00:00:00 2001 From: Olga Klisho Date: Wed, 21 Jan 2026 13:35:02 +0100 Subject: [PATCH] [kotlin] KTIJ-37212 make ReplaceManualRangeWithIndicesCallsInspection support getCall array access. GitOrigin-RevId: ca0f39f29e3fc4f6aea82a611cede284e4c93cd9 --- .../kotlin/idea/base/psi/KotlinPsiUtils.kt | 9 ++ .../tests/K2LocalInspectionTestGenerated.java | 10 ++ ...ceManualRangeWithIndicesCallsInspection.kt | 93 ++++++++++--------- .../utils/LoopToCollectionTransformUtils.kt | 56 +++++++---- .../K1IntroduceVariableHandler.kt | 1 + .../LocalInspectionTestGenerated.java | 10 ++ .../getMethod.kt | 7 ++ .../getMethod.kt.after | 7 ++ .../getMethodDifferentArray.kt | 8 ++ .../getMethodDifferentArray.kt.after | 8 ++ .../KotlinIntroduceVariableHandler.kt | 6 +- .../K2IntroduceVariableHandler.kt | 1 + 12 files changed, 151 insertions(+), 65 deletions(-) create mode 100644 plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethod.kt create mode 100644 plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethod.kt.after create mode 100644 plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethodDifferentArray.kt create mode 100644 plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethodDifferentArray.kt.after diff --git a/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/KotlinPsiUtils.kt b/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/KotlinPsiUtils.kt index 2d9b946dc708..bdb77f7d8020 100644 --- a/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/KotlinPsiUtils.kt +++ b/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/KotlinPsiUtils.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtAnnotationEntry +import org.jetbrains.kotlin.psi.KtBinaryExpression import org.jetbrains.kotlin.psi.KtBlockExpression import org.jetbrains.kotlin.psi.KtCallElement import org.jetbrains.kotlin.psi.KtCallExpression @@ -430,3 +431,11 @@ fun KtSimpleNameExpression.canBeUsedInImport(): Boolean { return true } + +/** + * Checks if this element is on the left-hand side of an assignment expression. + * Traverses parent hierarchy to handle cases like `(arr[i]) = value`. + */ +fun PsiElement.isAssignmentLHS(): Boolean = parents(withSelf = false).any { + KtPsiUtil.isAssignment(it) && (it as KtBinaryExpression).left == this +} diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java index 622966e1e115..5e1fc2d3d382 100644 --- a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java +++ b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java @@ -9727,6 +9727,16 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp runTest("../../../idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/forNotTarget.kt"); } + @TestMetadata("getMethod.kt") + public void testGetMethod() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethod.kt"); + } + + @TestMetadata("getMethodDifferentArray.kt") + public void testGetMethodDifferentArray() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethodDifferentArray.kt"); + } + @TestMetadata("implicitReceiver.kt") public void testImplicitReceiver() throws Exception { runTest("../../../idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/implicitReceiver.kt"); diff --git a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/ReplaceManualRangeWithIndicesCallsInspection.kt b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/ReplaceManualRangeWithIndicesCallsInspection.kt index 672b2750fc38..97ea6fb940c6 100644 --- a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/ReplaceManualRangeWithIndicesCallsInspection.kt +++ b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/ReplaceManualRangeWithIndicesCallsInspection.kt @@ -20,6 +20,8 @@ import org.jetbrains.kotlin.analysis.api.types.KaType import org.jetbrains.kotlin.idea.base.codeInsight.KotlinDeclarationNameValidator import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.base.psi.isAssignmentLHS import org.jetbrains.kotlin.idea.base.psi.replaced import org.jetbrains.kotlin.idea.base.psi.safeDeparenthesize import org.jetbrains.kotlin.idea.base.resources.KotlinBundle @@ -28,13 +30,15 @@ import org.jetbrains.kotlin.idea.codeInsight.inspections.shared.AbstractRangeIns import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinApplicableInspectionBase import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinModCommandQuickFix import org.jetbrains.kotlin.idea.codeinsight.utils.ImplicitReceiverInfo -import org.jetbrains.kotlin.idea.codeinsight.utils.LoopToCollectionTransformUtils +import org.jetbrains.kotlin.idea.codeinsight.utils.LoopToCollectionTransformUtils.findGetCallAccess +import org.jetbrains.kotlin.idea.codeinsight.utils.getImplicitReceiverInfo +import org.jetbrains.kotlin.idea.codeinsight.utils.LoopToCollectionTransformUtils.transformLoop +import org.jetbrains.kotlin.idea.codeinsight.utils.LoopToCollectionTransformUtils.transformLoopWithIndex import org.jetbrains.kotlin.idea.codeinsight.utils.RangeKtExpressionType import org.jetbrains.kotlin.idea.codeinsight.utils.RangeKtExpressionType.DOWN_TO import org.jetbrains.kotlin.idea.codeinsight.utils.RangeKtExpressionType.RANGE_TO import org.jetbrains.kotlin.idea.codeinsight.utils.RangeKtExpressionType.RANGE_UNTIL import org.jetbrains.kotlin.idea.codeinsight.utils.RangeKtExpressionType.UNTIL -import org.jetbrains.kotlin.idea.codeinsight.utils.getImplicitReceiverInfo import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.StandardClassIds @@ -45,11 +49,11 @@ import org.jetbrains.kotlin.psi.KtContainerNode import org.jetbrains.kotlin.psi.KtDotQualifiedExpression import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtForExpression +import org.jetbrains.kotlin.psi.KtNameReferenceExpression import org.jetbrains.kotlin.psi.KtParenthesizedExpression import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtPsiUtil import org.jetbrains.kotlin.psi.KtQualifiedExpression -import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.psi.KtThisExpression import org.jetbrains.kotlin.psi.KtVisitor import org.jetbrains.kotlin.psi.KtVisitorVoid @@ -94,7 +98,7 @@ class ReplaceManualRangeWithIndicesCallsInspection : KotlinApplicableInspectionB val indexUsagePattern: IndexUsagePattern, val implicitReceiverInfo: ImplicitReceiverInfo?, val suggestedElementName: String?, - val arrayAccessUsages: List>, + val indexedAccessUsages: List>, // Either KtArrayAccessExpression or KtDotQualifiedExpression ) private fun getProblemDescription(context: Context): @InspectionMessage String { @@ -164,15 +168,15 @@ class ReplaceManualRangeWithIndicesCallsInspection : KotlinApplicableInspectionB } } else null - // Store array access usages as smart pointers for use in quick fixes - val arrayAccessPointers = indexUsageAnalysis.arrayAccessUsages.map { it.createSmartPointer() } + // Store indexed access usages as smart pointers for use in quick fixes + val indexedAccessPointers = indexUsageAnalysis.indexedAccessUsages.map { it.createSmartPointer() } return Context( explicitReceiver, indexUsageAnalysis.pattern, implicitReceiverInfo, suggestedElementName, - arrayAccessPointers + indexedAccessPointers ) } @@ -231,18 +235,19 @@ class ReplaceManualRangeWithIndicesCallsInspection : KotlinApplicableInspectionB /** * Result of analyzing how the loop index variable is used in the loop body. + * Supports both bracket notation (`arr[i]`) and `.get()` method calls (`arr.get(i)`). */ private data class IndexUsageAnalysis( val pattern: IndexUsagePattern, - val arrayAccessUsages: List, + val indexedAccessUsages: List, // Either KtArrayAccessExpression or KtDotQualifiedExpression ) /** * Analyzes how the loop index variable is used in the loop body. * * @return IndexUsageAnalysis containing: - * - pattern: ELEMENT_LOOP (all array accesses), WITH_INDEX (mixed), or INDICES_ONLY (no array accesses) - * - arrayAccessUsages: list of valid array access expressions for transformation + * - pattern: ELEMENT_LOOP (all indexed accesses), WITH_INDEX (mixed), or INDICES_ONLY (no indexed accesses) + * - indexedAccessUsages: list of valid indexed access expressions for transformation */ private fun analyzeIndexUsagePattern(range: RangeExpression, explicitReceiver: KtExpression?): IndexUsageAnalysis { val forExpression = findContainingForLoop(range) @@ -253,51 +258,55 @@ class ReplaceManualRangeWithIndicesCallsInspection : KotlinApplicableInspectionB val usages = ReferencesSearch.search(loopParam).findAll() if (usages.isEmpty()) return IndexUsageAnalysis(IndexUsagePattern.INDICES_ONLY, emptyList()) - val validArrayAccesses = mutableListOf() + val validIndexedAccesses = mutableListOf() var otherUsageCount = 0 for (reference in usages) { val usage = reference.element - val arrayAccess = usage.parents.match(KtContainerNode::class, last = KtArrayAccessExpression::class) + // Check for bracket notation: arr[i] + val arrayAccess = usage.parents.match(KtContainerNode::class, last = KtArrayAccessExpression::class) if (arrayAccess != null && isValidElementLoopCandidate(arrayAccess, explicitReceiver)) { - validArrayAccesses.add(arrayAccess) - } else { - otherUsageCount++ + validIndexedAccesses.add(arrayAccess) + continue } + + // Check for .get() method call: arr.get(i) + val getCall = findGetCallAccess(usage) + if (getCall != null && isValidGetCallCandidate(getCall, explicitReceiver)) { + validIndexedAccesses.add(getCall) + continue + } + + otherUsageCount++ } val pattern = when { - validArrayAccesses.isNotEmpty() && otherUsageCount == 0 -> IndexUsagePattern.ELEMENT_LOOP - validArrayAccesses.isNotEmpty() && otherUsageCount > 0 -> IndexUsagePattern.WITH_INDEX + validIndexedAccesses.isNotEmpty() && otherUsageCount == 0 -> IndexUsagePattern.ELEMENT_LOOP + validIndexedAccesses.isNotEmpty() && otherUsageCount > 0 -> IndexUsagePattern.WITH_INDEX else -> IndexUsagePattern.INDICES_ONLY } - return IndexUsageAnalysis(pattern, validArrayAccesses) + return IndexUsageAnalysis(pattern, validIndexedAccesses) } private fun findContainingForLoop(range: RangeExpression): KtForExpression? = range.expression.parents.match(KtContainerNode::class, last = KtForExpression::class) private fun isValidElementLoopCandidate(arrayAccess: KtArrayAccessExpression, explicitReceiver: KtExpression?): Boolean { - // Must use the loop parameter as the only index if (arrayAccess.indexExpressions.size != 1) return false + return receiversMatch(arrayAccess.arrayExpression, explicitReceiver) && !arrayAccess.isAssignmentLHS() + } - // Check if the array receiver matches the size call receiver - val receiversMatch = if (explicitReceiver != null) { - // Explicit receiver case: arr.size -> arr[i] - (arrayAccess.arrayExpression as? KtSimpleNameExpression)?.getReferencedName() == - (explicitReceiver as? KtSimpleNameExpression)?.getReferencedName() - } else { - // Implicit receiver case: size -> this[i] - arrayAccess.arrayExpression is KtThisExpression - } + private fun isValidGetCallCandidate(getCall: KtDotQualifiedExpression, explicitReceiver: KtExpression?): Boolean { + return receiversMatch(getCall.receiverExpression, explicitReceiver) && !getCall.isAssignmentLHS() + } - if (!receiversMatch) return false - - // Don't suggest if array access is being assigned to (it's being modified) - val parent = arrayAccess.parent - return parent !is KtBinaryExpression || parent.left != arrayAccess || parent.operationToken !in KtTokens.ALL_ASSIGNMENTS + private fun receiversMatch(accessReceiver: KtExpression?, explicitReceiver: KtExpression?): Boolean { + if (explicitReceiver == null) return accessReceiver?.safeDeparenthesize() is KtThisExpression + if (accessReceiver == null) return false + return (accessReceiver as? KtNameReferenceExpression)?.mainReference?.resolve() == + (explicitReceiver as? KtNameReferenceExpression)?.mainReference?.resolve() } private fun createQuickFixes(context: Context): List> { @@ -390,14 +399,14 @@ class ReplaceManualRangeWithIndicesCallsInspection : KotlinApplicableInspectionB val loopRange = forExpression.loopRange ?: return val elementName = context.suggestedElementName ?: return - // Get writable array accesses from smart pointers - val arrayAccesses = context.arrayAccessUsages.mapNotNull { pointer -> + // Get writable indexed accesses from smart pointers + val indexedAccesses = context.indexedAccessUsages.mapNotNull { pointer -> pointer.element?.let { updater.getWritable(it) } } - if (arrayAccesses.isEmpty()) return + if (indexedAccesses.isEmpty()) return val collection = resolveCollectionExpression(project) - LoopToCollectionTransformUtils.transformLoop(project, arrayAccesses, loopParameter, loopRange, collection, elementName) + transformLoop(project, indexedAccesses, loopParameter, loopRange, collection, elementName) } /** @@ -431,15 +440,15 @@ class ReplaceManualRangeWithIndicesCallsInspection : KotlinApplicableInspectionB val loopRange = forExpression.loopRange ?: return val elementName = context.suggestedElementName ?: return - // Get writable array accesses from smart pointers - val arrayAccesses = context.arrayAccessUsages.mapNotNull { pointer -> + // Get writable indexed accesses from smart pointers + val indexedAccesses = context.indexedAccessUsages.mapNotNull { pointer -> pointer.element?.let { updater.getWritable(it) } } - if (arrayAccesses.isEmpty()) return + if (indexedAccesses.isEmpty()) return val collection = resolveCollectionExpression(project) - LoopToCollectionTransformUtils.transformLoopWithIndex( - project, arrayAccesses, loopParameter, loopRange, collection, elementName + transformLoopWithIndex( + project, indexedAccesses, loopParameter, loopRange, collection, elementName ) } diff --git a/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/LoopToCollectionTransformUtils.kt b/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/LoopToCollectionTransformUtils.kt index f323d5581ce0..4fd03b0926f9 100644 --- a/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/LoopToCollectionTransformUtils.kt +++ b/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/LoopToCollectionTransformUtils.kt @@ -2,14 +2,20 @@ package org.jetbrains.kotlin.idea.codeinsight.utils import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement import org.jetbrains.annotations.ApiStatus -import org.jetbrains.kotlin.psi.KtArrayAccessExpression import org.jetbrains.kotlin.psi.KtBlockStringTemplateEntry +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtValueArgument +import org.jetbrains.kotlin.psi.KtValueArgumentList import org.jetbrains.kotlin.psi.createExpressionByPattern import org.jetbrains.kotlin.psi.psiUtil.canPlaceAfterSimpleNameEntry +import org.jetbrains.kotlin.psi.psiUtil.parents +import org.jetbrains.kotlin.util.match /** * Utility functions for transforming index-based loops to collection loops. @@ -17,14 +23,27 @@ import org.jetbrains.kotlin.psi.psiUtil.canPlaceAfterSimpleNameEntry @ApiStatus.Internal object LoopToCollectionTransformUtils { + /** + * Finds a `.get()` method call that contains the given usage element as its argument. + * + * @param usage the usage element (typically a reference to the loop parameter) + * @return the [KtDotQualifiedExpression] representing the `.get()` call, or null if not found + */ + fun findGetCallAccess(usage: PsiElement): KtDotQualifiedExpression? { + val callExpr = usage.parents.match(KtValueArgument::class, KtValueArgumentList::class, last = KtCallExpression::class) + ?: return null + val isGetCall = callExpr.calleeExpression?.text == "get" && callExpr.valueArguments.size == 1 + return (callExpr.parent as? KtDotQualifiedExpression).takeIf { isGetCall } + } + /** * Transforms an index-based loop to a collection-based loop by: - * 1. Replacing the loop parameter with the provided element name - * 2. Replacing all array access expressions with direct element references + * 1. Replacing the loop parameter with the specified element name + * 2. Replacing all indexed access expressions (both arr[i] and arr.get(i)) with direct element references * 3. Replacing the loop range with the collection expression * * @param project the current project - * @param arrayAccesses array access expressions to replace with element references + * @param indexedAccesses indexed access expressions to replace with element references (KtArrayAccessExpression or KtDotQualifiedExpression for .get()) * @param loopParameter the original loop parameter * @param loopRange the original loop range * @param newLoopRange the new collection expression to iterate over @@ -32,7 +51,7 @@ object LoopToCollectionTransformUtils { */ fun transformLoop( project: Project, - arrayAccesses: List, + indexedAccesses: List, loopParameter: KtParameter, loopRange: KtExpression, newLoopRange: KtExpression, @@ -42,9 +61,9 @@ object LoopToCollectionTransformUtils { val newParameter = factory.createLoopParameter(elementName) - // Replace all array access expressions with direct element references - arrayAccesses.forEach { arrayAccess -> - replaceArrayAccessWithElement(factory, arrayAccess, elementName) + // Replace all indexed access expressions with direct element references + indexedAccesses.forEach { accessExpr -> + replaceIndexedAccessWithElement(factory, accessExpr, elementName) } loopParameter.replace(newParameter) @@ -52,25 +71,26 @@ object LoopToCollectionTransformUtils { } /** - * Replaces an array access expression with a simple element reference. + * Replaces an indexed access expression with a simple element reference. + * Handles both bracket notation (arr[i]) and .get() method calls (arr.get(i)). * When inside a string template ${...}, creates the simplified $name form directly if possible. */ - private fun replaceArrayAccessWithElement(factory: KtPsiFactory, arrayAccess: KtArrayAccessExpression, elementName: String) { - val blockEntry = arrayAccess.parent as? KtBlockStringTemplateEntry + private fun replaceIndexedAccessWithElement(factory: KtPsiFactory, accessExpr: KtExpression, elementName: String) { + val blockEntry = accessExpr.parent as? KtBlockStringTemplateEntry if (blockEntry != null && canPlaceAfterSimpleNameEntry(blockEntry.nextSibling)) { blockEntry.replace(factory.createSimpleNameStringTemplateEntry(elementName)) } else { - arrayAccess.replace(factory.createExpression(elementName)) + accessExpr.replace(factory.createExpression(elementName)) } } /** * Transforms an index-based loop to a withIndex() loop by: * 1. Replacing the loop parameter with a destructuring declaration (indexName, elementName) - * 2. Replacing array access expressions with direct element references + * 2. Replacing indexed access expressions with direct element references * 3. Replacing the loop range with collection.withIndex() * @param project the current project - * @param arrayAccesses array access expressions to replace with element references + * @param indexedAccesses indexed access expressions to replace with element references (KtArrayAccessExpression or KtDotQualifiedExpression for .get()) * @param loopParameter the original loop parameter (the index variable) * @param loopRange the original loop range * @param collectionExpression the collection to iterate with withIndex() @@ -78,7 +98,7 @@ object LoopToCollectionTransformUtils { */ fun transformLoopWithIndex( project: Project, - arrayAccesses: List, + indexedAccesses: List, loopParameter: KtParameter, loopRange: KtExpression, collectionExpression: KtExpression, @@ -90,9 +110,9 @@ object LoopToCollectionTransformUtils { val newParameter = factory.createDestructuringParameter("($indexName, $elementName)") val newLoopRange = factory.createExpressionByPattern("$0.withIndex()", collectionExpression) - // Replace all array access expressions with direct element references - arrayAccesses.forEach { arrayAccess -> - replaceArrayAccessWithElement(factory, arrayAccess, elementName) + // Replace all indexed access expressions with direct element references + indexedAccesses.forEach { accessExpr -> + replaceIndexedAccessWithElement(factory, accessExpr, elementName) } loopParameter.replace(newParameter) diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/K1IntroduceVariableHandler.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/K1IntroduceVariableHandler.kt index 8540162a0e07..2c390ae2d1fe 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/K1IntroduceVariableHandler.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/K1IntroduceVariableHandler.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNameSuggester import org.jetbrains.kotlin.idea.base.fe10.codeInsight.newDeclaration.Fe10KotlinNewDeclarationNameValidator +import org.jetbrains.kotlin.idea.base.psi.isAssignmentLHS import org.jetbrains.kotlin.idea.base.psi.unifier.toRange import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.analyzeInContext diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index aba3ca352c6a..baf73835946e 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -14411,6 +14411,16 @@ public abstract class LocalInspectionTestGenerated extends AbstractK1LocalInspec runTest("testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/forNotTarget.kt"); } + @TestMetadata("getMethod.kt") + public void testGetMethod() throws Exception { + runTest("testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethod.kt"); + } + + @TestMetadata("getMethodDifferentArray.kt") + public void testGetMethodDifferentArray() throws Exception { + runTest("testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethodDifferentArray.kt"); + } + @TestMetadata("implicitReceiver.kt") public void testImplicitReceiver() throws Exception { runTest("testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/implicitReceiver.kt"); diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethod.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethod.kt new file mode 100644 index 000000000000..81fce2fd10d1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethod.kt @@ -0,0 +1,7 @@ +// WITH_STDLIB +// FIX: Replace with loop over elements +fun test(args: Array) { + for (index in 0..args.size - 1) { + val out = args.get(index) + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethod.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethod.kt.after new file mode 100644 index 000000000000..f2f09eede260 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethod.kt.after @@ -0,0 +1,7 @@ +// WITH_STDLIB +// FIX: Replace with loop over elements +fun test(args: Array) { + for (element in args) { + val out = element + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethodDifferentArray.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethodDifferentArray.kt new file mode 100644 index 000000000000..f16d54a8a8b5 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethodDifferentArray.kt @@ -0,0 +1,8 @@ +// WITH_STDLIB +// FIX: Replace with indices +fun test(args: Array, other: Array) { + for (index in 0..args.size - 1) { + val a = args.get(index) + val b = other.get(index) + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethodDifferentArray.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethodDifferentArray.kt.after new file mode 100644 index 000000000000..7e83afd1ea82 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/getMethodDifferentArray.kt.after @@ -0,0 +1,8 @@ +// WITH_STDLIB +// FIX: Replace with indices +fun test(args: Array, other: Array) { + for (index in args.indices) { + val a = args.get(index) + val b = other.get(index) + } +} \ No newline at end of file diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/KotlinIntroduceVariableHandler.kt b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/KotlinIntroduceVariableHandler.kt index 8e205569ae61..ed364001f699 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/KotlinIntroduceVariableHandler.kt +++ b/plugins/kotlin/refactorings/kotlin.refactorings.common/src/org/jetbrains/kotlin/idea/refactoring/introduce/KotlinIntroduceVariableHandler.kt @@ -24,9 +24,9 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.KotlinIntroduceVariableHe import org.jetbrains.kotlin.idea.refactoring.selectElement import org.jetbrains.kotlin.idea.util.ElementKind import org.jetbrains.kotlin.idea.util.application.isUnitTestMode +import org.jetbrains.kotlin.idea.base.psi.isAssignmentLHS import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtArrayAccessExpression -import org.jetbrains.kotlin.psi.KtBinaryExpression import org.jetbrains.kotlin.psi.KtBlockExpression import org.jetbrains.kotlin.psi.KtClassBody import org.jetbrains.kotlin.psi.KtConstructorCalleeExpression @@ -242,10 +242,6 @@ abstract class KotlinIntroduceVariableHandler : RefactoringActionHandler { protected companion object { val INTRODUCE_VARIABLE: String get() = KotlinBundle.message("introduce.variable") - fun PsiElement.isAssignmentLHS(): Boolean = parents.any { - KtPsiUtil.isAssignment(it) && (it as KtBinaryExpression).left == this - } - fun showErrorHint(project: Project, editor: Editor?, @NlsContexts.DialogMessage message: String) { CommonRefactoringUtil.showErrorHint(project, editor, message, INTRODUCE_VARIABLE, HelpID.INTRODUCE_VARIABLE) } diff --git a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/introduceVariable/K2IntroduceVariableHandler.kt b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/introduceVariable/K2IntroduceVariableHandler.kt index b7e165313a72..aec4e8d2bcb4 100644 --- a/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/introduceVariable/K2IntroduceVariableHandler.kt +++ b/plugins/kotlin/refactorings/kotlin.refactorings.k2/src/org/jetbrains/kotlin/idea/k2/refactoring/introduce/introduceVariable/K2IntroduceVariableHandler.kt @@ -51,6 +51,7 @@ import org.jetbrains.kotlin.idea.base.analysis.api.utils.getImplicitReceivers import org.jetbrains.kotlin.idea.base.analysis.api.utils.shortenReferences import org.jetbrains.kotlin.idea.base.codeInsight.KotlinDeclarationNameValidator import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider +import org.jetbrains.kotlin.idea.base.psi.isAssignmentLHS import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.codeinsight.utils.ConvertToBlockBodyUtils import org.jetbrains.kotlin.idea.codeinsight.utils.NamedArgumentUtils