[kotlin] Move methods to JavaCollectionsStaticMethodInspectionUtils,simplify calculating if type is list/mutableList

^KTIJ-31955 Fixed

GitOrigin-RevId: 429d26cd2a10313b369fdae64aae1135409709d4
This commit is contained in:
Victoria.Petrakovich
2025-08-28 19:16:02 +00:00
committed by intellij-monorepo-bot
parent 009e4f2444
commit da65c14e88
3 changed files with 63 additions and 88 deletions
@@ -3,99 +3,84 @@ package org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections
import com.intellij.openapi.module.ModuleUtilCore
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.components.expandedSymbol
import org.jetbrains.kotlin.analysis.api.components.expressionType
import org.jetbrains.kotlin.analysis.api.components.isSubtypeOf
import org.jetbrains.kotlin.analysis.api.components.resolveToCall
import org.jetbrains.kotlin.analysis.api.resolution.singleFunctionCallOrNull
import org.jetbrains.kotlin.analysis.api.resolution.symbol
import org.jetbrains.kotlin.analysis.api.types.KaType
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
import org.jetbrains.kotlin.idea.codeinsight.utils.callExpression
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtValueArgument
class JavaCollectionsStaticMethodInspectionUtils {
object JavaCollectionsStaticMethodInspectionUtils {
companion object Utils {
context(_: KaSession)
fun getTargetMethod(
expression: KtDotQualifiedExpression,
isValidFirstArgument: (KaType?) -> Boolean
): Pair<String, KtValueArgument>? {
val callExpression = expression.callExpression ?: return null
val args = callExpression.valueArguments
val firstArg = args.firstOrNull() ?: return null
val firstArgType = firstArg.getArgumentExpression()?.expressionType
if (!isValidFirstArgument(firstArgType)) return null
val call = callExpression.resolveToCall()?.singleFunctionCallOrNull() ?: return null
val callableId = call.partiallyAppliedSymbol.symbol.callableId ?: return null
val fqName = callableId.asSingleFqName().asString()
if (!canReplaceWithStdLib(expression, fqName, args)) return null
val methodName = fqName.split(".").last()
return methodName to firstArg
context(_: KaSession)
fun getMethodIfItsArgumentIsMutableList(expression: KtDotQualifiedExpression): Pair<String, KtValueArgument>? =
getMethodIfCanReplaceItWithStdlib(expression) { methodArgument ->
isMutableListOrSubtype(methodArgument)
}
private fun checkApiVersion(expression: KtDotQualifiedExpression): Boolean {
val module = ModuleUtilCore.findModuleForPsiElement(expression) ?: return true
return module.languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_2
context(_: KaSession)
fun getMethodIfItsArgumentIsImmutableList(expression: KtDotQualifiedExpression): Pair<String, KtValueArgument>? =
getMethodIfCanReplaceItWithStdlib(expression) { methodArgument ->
isListOrSubtype(methodArgument) && !isMutableListOrSubtype(
methodArgument
)
}
private fun canReplaceWithStdLib(expression: KtDotQualifiedExpression, fqName: String, args: List<KtValueArgument>): Boolean {
if (!fqName.startsWith("java.util.Collections.")) return false
val size = args.size
return when (fqName) {
"java.util.Collections.fill" -> checkApiVersion(expression) && size == 2
"java.util.Collections.reverse" -> size == 1
"java.util.Collections.shuffle" -> checkApiVersion(expression) && (size == 1 || size == 2)
"java.util.Collections.sort" -> {
size == 1 || (size == 2 && args.getOrNull(1)?.getArgumentExpression() is KtLambdaExpression)
}
context(_: KaSession)
private fun getMethodIfCanReplaceItWithStdlib(
expression: KtDotQualifiedExpression,
isValidFirstArgument: (KaType?) -> Boolean
): Pair<String, KtValueArgument>? {
val callExpression = expression.callExpression ?: return null
val args = callExpression.valueArguments
val firstArg = args.firstOrNull() ?: return null
val firstArgType = firstArg.getArgumentExpression()?.expressionType
if (!isValidFirstArgument(firstArgType)) return null
else -> false
val call = callExpression.resolveToCall()?.singleFunctionCallOrNull() ?: return null
val callableId = call.partiallyAppliedSymbol.symbol.callableId ?: return null
val fqName = callableId.asSingleFqName().asString()
if (!canReplaceWithStdLib(expression, fqName, args)) return null
val methodName = fqName.split(".").last()
return methodName to firstArg
}
private fun checkApiVersion(expression: KtDotQualifiedExpression): Boolean {
val module = ModuleUtilCore.findModuleForPsiElement(expression) ?: return true
return module.languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_2
}
private fun canReplaceWithStdLib(expression: KtDotQualifiedExpression, fqName: String, args: List<KtValueArgument>): Boolean {
if (!fqName.startsWith("java.util.Collections.")) return false
val size = args.size
return when (fqName) {
"java.util.Collections.fill" -> checkApiVersion(expression) && size == 2
"java.util.Collections.reverse" -> size == 1
"java.util.Collections.shuffle" -> checkApiVersion(expression) && (size == 1 || size == 2)
"java.util.Collections.sort" -> {
size == 1 || (size == 2 && args.getOrNull(1)?.getArgumentExpression() is KtLambdaExpression)
}
}
context(_: KaSession)
fun isMutableListOrSubtype(type: KaType?): Boolean {
return isListOrSubtype(type, isMutable = true)
}
context(_: KaSession)
fun isListOrSubtype(type: KaType?): Boolean {
return isListOrSubtype(type, isMutable = false)
}
context(_: KaSession)
private fun isListOrSubtype(type: KaType?, isMutable: Boolean): Boolean {
if (type == null) return false
val fqName = getSingleFqName(type) ?: return false
val qualifiedName = if (isMutable) {
if (fqName == StandardNames.FqNames.mutableList) return true
StandardNames.FqNames.mutableList
} else {
if (fqName == StandardNames.FqNames.list) return true
StandardNames.FqNames.list
}
val classSymbol = type.expandedSymbol ?: return false
return classSymbol.superTypes.reversed().any { superType ->
val fqNameOfSuperType = getSingleFqName(superType)
fqNameOfSuperType == qualifiedName
}
}
context(_: KaSession)
private fun getSingleFqName(type: KaType): FqName? {
val classSymbol = type.expandedSymbol ?: return null
return classSymbol.classId?.asSingleFqName()
else -> false
}
}
context(_: KaSession)
private fun isMutableListOrSubtype(type: KaType?): Boolean {
return type?.isSubtypeOf(StandardClassIds.MutableList) == true
}
context(_: KaSession)
private fun isListOrSubtype(type: KaType?): Boolean {
return type?.isSubtypeOf(StandardClassIds.List) == true
}
}
@@ -9,7 +9,7 @@ import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinApplicableInspectionBase
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.ApplicabilityRange
import org.jetbrains.kotlin.idea.codeinsight.utils.callExpression
import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.JavaCollectionsStaticMethodInspectionUtils.Utils
import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.JavaCollectionsStaticMethodInspectionUtils.getMethodIfItsArgumentIsImmutableList
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.KtVisitor
@@ -18,7 +18,7 @@ import org.jetbrains.kotlin.psi.dotQualifiedExpressionVisitor
internal class JavaCollectionsStaticMethodOnImmutableListInspection :
KotlinApplicableInspectionBase<KtDotQualifiedExpression, JavaCollectionsStaticMethodOnImmutableListInspection.Context>() {
internal class Context(
class Context(
val methodName: String,
val firstArg: KtValueArgument
)
@@ -43,16 +43,10 @@ internal class JavaCollectionsStaticMethodOnImmutableListInspection :
)
override fun KaSession.prepareContext(element: KtDotQualifiedExpression): Context? {
val (methodName, firstArg) = getTargetMethodOnImmutableList(element) ?: return null
val (methodName, firstArg) = getMethodIfItsArgumentIsImmutableList(element) ?: return null
return Context(methodName, firstArg)
}
context(_: KaSession)
private fun getTargetMethodOnImmutableList(expression: KtDotQualifiedExpression): Pair<String, KtValueArgument>? =
Utils.getTargetMethod(expression) { type ->
Utils.isListOrSubtype(type) && !Utils.isMutableListOrSubtype(type)
}
override fun getApplicableRanges(element: KtDotQualifiedExpression): List<TextRange> =
ApplicabilityRange.single(element) { it.callExpression?.calleeExpression }
@@ -16,7 +16,7 @@ import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinAp
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinModCommandQuickFix
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.ApplicabilityRange
import org.jetbrains.kotlin.idea.codeinsight.utils.callExpression
import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.JavaCollectionsStaticMethodInspectionUtils.Utils
import org.jetbrains.kotlin.idea.codeinsights.impl.base.inspections.JavaCollectionsStaticMethodInspectionUtils.getMethodIfItsArgumentIsMutableList
import org.jetbrains.kotlin.psi.*
// In K2, it's ReplaceJavaStaticMethodWithKotlinAnalogInspection
@@ -52,17 +52,13 @@ internal class JavaCollectionsStaticMethodInspection :
}
override fun KaSession.prepareContext(element: KtDotQualifiedExpression): Context? {
val (methodName, firstArg) = getTargetMethodOnMutableList(element) ?: return null
val (methodName, firstArg) = getMethodIfItsArgumentIsMutableList(element) ?: return null
return Context(methodName, firstArg)
}
override fun getApplicableRanges(element: KtDotQualifiedExpression): List<TextRange> =
ApplicabilityRange.self(element)
context(_: KaSession)
private fun getTargetMethodOnMutableList(expression: KtDotQualifiedExpression): Pair<String, KtValueArgument>? =
Utils.getTargetMethod(expression) { type -> Utils.isMutableListOrSubtype(type) }
private class ReplaceWithStdLibFix(private val methodName: String, private val receiver: String) :
KotlinModCommandQuickFix<KtDotQualifiedExpression>() {