[kotlin] KTIJ-37212 make ReplaceManualRangeWithIndicesCallsInspection support getCall array access.

GitOrigin-RevId: ca0f39f29e3fc4f6aea82a611cede284e4c93cd9
This commit is contained in:
Olga Klisho
2026-02-16 20:13:36 +00:00
committed by intellij-monorepo-bot
parent c8b48a46eb
commit cad4e7a412
12 changed files with 151 additions and 65 deletions
@@ -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
}
@@ -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");
@@ -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<SmartPsiElementPointer<KtArrayAccessExpression>>,
val indexedAccessUsages: List<SmartPsiElementPointer<KtExpression>>, // 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<KtArrayAccessExpression>,
val indexedAccessUsages: List<KtExpression>, // 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<KtArrayAccessExpression>()
val validIndexedAccesses = mutableListOf<KtExpression>()
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<KotlinModCommandQuickFix<KtExpression>> {
@@ -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
)
}
@@ -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<KtArrayAccessExpression>,
indexedAccesses: List<KtExpression>,
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<KtArrayAccessExpression>,
indexedAccesses: List<KtExpression>,
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)
@@ -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
@@ -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");
@@ -0,0 +1,7 @@
// WITH_STDLIB
// FIX: Replace with loop over elements
fun test(args: Array<String>) {
for (index in 0..ar<caret>gs.size - 1) {
val out = args.get(index)
}
}
@@ -0,0 +1,7 @@
// WITH_STDLIB
// FIX: Replace with loop over elements
fun test(args: Array<String>) {
for (element in args) {
val out = element
}
}
@@ -0,0 +1,8 @@
// WITH_STDLIB
// FIX: Replace with indices
fun test(args: Array<String>, other: Array<String>) {
for (index in 0..<caret>args.size - 1) {
val a = args.get(index)
val b = other.get(index)
}
}
@@ -0,0 +1,8 @@
// WITH_STDLIB
// FIX: Replace with indices
fun test(args: Array<String>, other: Array<String>) {
for (index in args.indices) {
val a = args.get(index)
val b = other.get(index)
}
}
@@ -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)
}
@@ -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