mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[kotlin] KTIJ-37276 ReplaceManualRangeWithIndicesCallsInspection: take names conflict into account
GitOrigin-RevId: e9a783709d4a0286def7842f6b98bdc071a3a92c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8417e280a3
commit
5792d62c48
+20
@@ -9702,6 +9702,26 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp
|
||||
runTest("../../../idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/complexReceiver.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("elementNameCollision.kt")
|
||||
public void testElementNameCollision() throws Exception {
|
||||
runTest("../../../idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/elementNameCollision.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("elementNameCollisionMultiple.kt")
|
||||
public void testElementNameCollisionMultiple() throws Exception {
|
||||
runTest("../../../idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/elementNameCollisionMultiple.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("elementNameCollisionNestedLoop.kt")
|
||||
public void testElementNameCollisionNestedLoop() throws Exception {
|
||||
runTest("../../../idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/elementNameCollisionNestedLoop.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("elementNameCollisionParameter.kt")
|
||||
public void testElementNameCollisionParameter() throws Exception {
|
||||
runTest("../../../idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/elementNameCollisionParameter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("forNotTarget.kt")
|
||||
public void testForNotTarget() throws Exception {
|
||||
runTest("../../../idea/tests/testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/forNotTarget.kt");
|
||||
|
||||
+22
-4
@@ -9,6 +9,9 @@ import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import org.jetbrains.kotlin.analysis.api.KaSession
|
||||
import org.jetbrains.kotlin.analysis.api.resolution.successfulVariableAccessCall
|
||||
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.base.psi.replaced
|
||||
import org.jetbrains.kotlin.idea.base.psi.safeDeparenthesize
|
||||
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
|
||||
@@ -51,6 +54,7 @@ class ReplaceManualRangeWithIndicesCallsInspection : AbstractRangeInspection<Rep
|
||||
val explicitReceiver: KtExpression?,
|
||||
val suggestElementLoop: Boolean,
|
||||
val implicitReceiverInfo: ImplicitReceiverInfo?,
|
||||
val suggestedElementName: String?,
|
||||
)
|
||||
|
||||
override fun getProblemDescription(
|
||||
@@ -79,7 +83,7 @@ class ReplaceManualRangeWithIndicesCallsInspection : AbstractRangeInspection<Rep
|
||||
|
||||
override fun KaSession.prepareContext(range: RangeExpression): Context? {
|
||||
val (_, right) = range.arguments
|
||||
|
||||
|
||||
// Must end with a size/length call
|
||||
val sizeCall = right?.let { rightBound(range.type, it) } ?: return null
|
||||
val explicitReceiver = (sizeCall as? KtQualifiedExpression)?.receiverExpression
|
||||
@@ -87,7 +91,20 @@ class ReplaceManualRangeWithIndicesCallsInspection : AbstractRangeInspection<Rep
|
||||
val suggestElementLoop = shouldSuggestElementLoop(range, explicitReceiver)
|
||||
val implicitReceiverInfo = if (explicitReceiver == null) sizeCall.getImplicitReceiverInfo() else null
|
||||
|
||||
return Context(explicitReceiver, suggestElementLoop, implicitReceiverInfo)
|
||||
// Pre-compute a unique element name for the loop transformation
|
||||
val suggestedElementName = if (suggestElementLoop) {
|
||||
val forExpression = findContainingForLoop(range)
|
||||
forExpression?.loopParameter?.let { loopParameter ->
|
||||
val nameValidator = KotlinDeclarationNameValidator(
|
||||
loopParameter,
|
||||
true,
|
||||
KotlinNameSuggestionProvider.ValidatorTarget.VARIABLE,
|
||||
)
|
||||
KotlinNameSuggester.suggestNameByName("element") { nameValidator.validate(it) }
|
||||
}
|
||||
} else null
|
||||
|
||||
return Context(explicitReceiver, suggestElementLoop, implicitReceiverInfo, suggestedElementName)
|
||||
}
|
||||
|
||||
private fun KaSession.rightBound(type: RangeKtExpressionType, expression: KtExpression): KtExpression? {
|
||||
@@ -245,13 +262,14 @@ class ReplaceManualRangeWithIndicesCallsInspection : AbstractRangeInspection<Rep
|
||||
val forExpression = element.getStrictParentOfType<KtForExpression>() ?: return
|
||||
val loopParameter = forExpression.loopParameter ?: return
|
||||
val loopRange = forExpression.loopRange ?: return
|
||||
val elementName = context.suggestedElementName ?: return
|
||||
|
||||
val collection = resolveCollectionExpression(project)
|
||||
|
||||
// Try a single usage first
|
||||
val singleUsageInfo = LoopToCollectionTransformUtils.findSingleArrayAccessUsage(loopParameter)
|
||||
if (singleUsageInfo != null) {
|
||||
LoopToCollectionTransformUtils.transformLoop(project, listOf(singleUsageInfo), loopParameter, loopRange, collection)
|
||||
LoopToCollectionTransformUtils.transformLoop(project, listOf(singleUsageInfo), loopParameter, loopRange, collection, elementName)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -267,7 +285,7 @@ class ReplaceManualRangeWithIndicesCallsInspection : AbstractRangeInspection<Rep
|
||||
}
|
||||
|
||||
if (usageInfos.isNotEmpty()) {
|
||||
LoopToCollectionTransformUtils.transformLoop(project, usageInfos, loopParameter, loopRange, collection)
|
||||
LoopToCollectionTransformUtils.transformLoop(project, usageInfos, loopParameter, loopRange, collection, elementName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-9
@@ -30,7 +30,7 @@ object LoopToCollectionTransformUtils {
|
||||
|
||||
/**
|
||||
* Finds the usage pattern for a loop parameter where it's used as an array index exactly once.
|
||||
*
|
||||
*
|
||||
* @param loopParameter the loop parameter (e.g., `i` in `for (i in 0..<size)`)
|
||||
* @return LoopUsageInfo if the parameter is used exactly once as an array index, null otherwise
|
||||
*/
|
||||
@@ -38,38 +38,41 @@ object LoopToCollectionTransformUtils {
|
||||
val paramElement = loopParameter.originalElement ?: return null
|
||||
val usageElement = ReferencesSearch.search(paramElement).findAll().singleOrNull()?.element ?: return null
|
||||
val arrayAccessElement = usageElement.parents.match(KtContainerNode::class, last = KtArrayAccessExpression::class) ?: return null
|
||||
|
||||
|
||||
return LoopUsageInfo(paramElement as KtParameter, usageElement, arrayAccessElement)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an index-based loop to a collection-based loop with multiple array access usages by:
|
||||
* 1. Replacing the loop parameter with "element"
|
||||
* 1. Replacing the loop parameter with the provided element name
|
||||
* 2. Replacing all array access expressions with direct element references
|
||||
* 3. Replacing the loop range with the collection expression
|
||||
*
|
||||
*
|
||||
* @param project the current project
|
||||
* @param usageInfos information about all loop usage patterns
|
||||
* @param loopParameter the original loop parameter
|
||||
* @param loopRange the original loop range
|
||||
* @param newLoopRange the new collection expression to iterate over
|
||||
* @param elementName the name for the new loop variable (should be pre-validated to avoid conflicts)
|
||||
*/
|
||||
fun transformLoop(
|
||||
project: Project,
|
||||
usageInfos: List<LoopUsageInfo>,
|
||||
loopParameter: KtParameter,
|
||||
loopRange: KtExpression,
|
||||
newLoopRange: KtExpression
|
||||
newLoopRange: KtExpression,
|
||||
elementName: String
|
||||
) {
|
||||
val factory = KtPsiFactory(project)
|
||||
val newParameter = factory.createLoopParameter("element")
|
||||
val newReferenceExpression = factory.createExpression("element")
|
||||
|
||||
|
||||
val newParameter = factory.createLoopParameter(elementName)
|
||||
val newReferenceExpression = factory.createExpression(elementName)
|
||||
|
||||
// Replace all array access expressions with direct element references
|
||||
usageInfos.forEach { usageInfo ->
|
||||
usageInfo.arrayAccessElement.replace(newReferenceExpression.copy())
|
||||
}
|
||||
|
||||
|
||||
loopParameter.replace(newParameter)
|
||||
loopRange.replace(newLoopRange)
|
||||
}
|
||||
|
||||
+20
@@ -14386,6 +14386,26 @@ public abstract class LocalInspectionTestGenerated extends AbstractK1LocalInspec
|
||||
runTest("testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/complexReceiver.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("elementNameCollision.kt")
|
||||
public void testElementNameCollision() throws Exception {
|
||||
runTest("testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/elementNameCollision.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("elementNameCollisionMultiple.kt")
|
||||
public void testElementNameCollisionMultiple() throws Exception {
|
||||
runTest("testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/elementNameCollisionMultiple.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("elementNameCollisionNestedLoop.kt")
|
||||
public void testElementNameCollisionNestedLoop() throws Exception {
|
||||
runTest("testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/elementNameCollisionNestedLoop.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("elementNameCollisionParameter.kt")
|
||||
public void testElementNameCollisionParameter() throws Exception {
|
||||
runTest("testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/elementNameCollisionParameter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("forNotTarget.kt")
|
||||
public void testForNotTarget() throws Exception {
|
||||
runTest("testData/inspectionsLocal/replaceManualRangeWithIndicesCalls/forNotTarget.kt");
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_STDLIB
|
||||
// IGNORE_K1
|
||||
fun test() {
|
||||
val element = mutableListOf<String>("hello", "world")
|
||||
for (i in 0 unt<caret>il element.size) {
|
||||
element[i].length
|
||||
element.size
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_STDLIB
|
||||
// IGNORE_K1
|
||||
fun test() {
|
||||
val element = mutableListOf<String>("hello", "world")
|
||||
for (element1 in element) {
|
||||
element1.length
|
||||
element.size
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_STDLIB
|
||||
// IGNORE_K1
|
||||
fun test() {
|
||||
val element = "first"
|
||||
val element1 = "second"
|
||||
val list = mutableListOf<String>("hello", "world")
|
||||
for (i in 0 unt<caret>il list.size) {
|
||||
list[i].length
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_STDLIB
|
||||
// IGNORE_K1
|
||||
fun test() {
|
||||
val element = "first"
|
||||
val element1 = "second"
|
||||
val list = mutableListOf<String>("hello", "world")
|
||||
for (element2 in list) {
|
||||
element2.length
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_STDLIB
|
||||
// IGNORE_K1
|
||||
fun test() {
|
||||
val outer = listOf(listOf("a", "b"), listOf("c", "d"))
|
||||
for (element in outer) {
|
||||
for (j in 0 unt<caret>il element.size) {
|
||||
element[j].length
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// WITH_STDLIB
|
||||
// IGNORE_K1
|
||||
fun test() {
|
||||
val outer = listOf(listOf("a", "b"), listOf("c", "d"))
|
||||
for (element in outer) {
|
||||
for (element1 in element) {
|
||||
element1.length
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// WITH_STDLIB
|
||||
// IGNORE_K1
|
||||
fun test(element: String, list: List<String>) {
|
||||
for (i in 0 unt<caret>il list.size) {
|
||||
list[i].length
|
||||
element.length
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// WITH_STDLIB
|
||||
// IGNORE_K1
|
||||
fun test(element: String, list: List<String>) {
|
||||
for (element1 in list) {
|
||||
element1.length
|
||||
element.length
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user