Replace deprecated symbol usage: fix message in case of generic parameter
#KT-8958 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
e8e457a16c
commit
0fad3fafa8
@@ -50,7 +50,7 @@ class DeprecatedSymbolUsageFix(
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val (nameExpression, replacement) = extractDataFromDiagnostic(diagnostic) ?: return null
|
||||
val (nameExpression, replacement) = extractDataFromDiagnostic(diagnostic, false) ?: return null
|
||||
return DeprecatedSymbolUsageFix(nameExpression, replacement)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class DeprecatedSymbolUsageFix(
|
||||
if (targetDescriptors.isEmpty()) return false
|
||||
|
||||
return targetDescriptors.all {
|
||||
fetchReplaceWithPattern(it, import.project, null) != null
|
||||
fetchReplaceWithPattern(it, import.project, null, false) != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+48
-14
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.caches.KotlinShortNamesCache
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
||||
import org.jetbrains.kotlin.idea.codeInliner.ClassUsageReplacementStrategy
|
||||
@@ -45,6 +46,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isCallee
|
||||
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.annotations.argumentValue
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue
|
||||
@@ -83,7 +85,8 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
fun fetchReplaceWithPattern(
|
||||
descriptor: DeclarationDescriptor,
|
||||
project: Project,
|
||||
contextElement: KtSimpleNameExpression?
|
||||
contextElement: KtSimpleNameExpression?,
|
||||
replaceInWholeProject: Boolean
|
||||
): ReplaceWith? {
|
||||
val annotation = descriptor.annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecated) ?: return null
|
||||
val replaceWithValue =
|
||||
@@ -100,7 +103,11 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
}
|
||||
) return null
|
||||
|
||||
return ReplaceWith(pattern.applyContextElement(contextElement, descriptor), imports)
|
||||
return if (replaceInWholeProject) {
|
||||
ReplaceWith(pattern, imports, true)
|
||||
} else {
|
||||
ReplaceWith(pattern.applyContextElement(contextElement, descriptor), imports, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.applyContextElement(
|
||||
@@ -108,7 +115,29 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
descriptor: DeclarationDescriptor
|
||||
): String {
|
||||
if (element == null) return this
|
||||
val expressionFromPattern = KtPsiFactory(element).createExpressionIfPossible(this) as? KtCallExpression ?: return this
|
||||
val psiFactory = KtPsiFactory(element)
|
||||
val expressionFromPattern = psiFactory.createExpressionIfPossible(this) ?: return this
|
||||
|
||||
val classLiteral = when (expressionFromPattern) {
|
||||
is KtClassLiteralExpression -> expressionFromPattern
|
||||
is KtDotQualifiedExpression -> expressionFromPattern.receiverExpression as? KtClassLiteralExpression
|
||||
else -> null
|
||||
}
|
||||
if (classLiteral != null) {
|
||||
val receiver = classLiteral.receiverExpression ?: return this
|
||||
val typeParameterText = (descriptor as? CallableDescriptor)?.typeParameters?.firstOrNull()?.name?.asString() ?: return this
|
||||
if (receiver.text != typeParameterText) return this
|
||||
val typeReference = (element.parent as KtCallExpression).typeArguments.firstOrNull()?.typeReference ?: return this
|
||||
val type = element.analyze(BodyResolveMode.PARTIAL)[BindingContext.TYPE, typeReference]
|
||||
if (type != null && KotlinBuiltIns.isArray(type)) {
|
||||
receiver.replace(typeReference)
|
||||
} else {
|
||||
receiver.replace(psiFactory.createExpression(typeReference.text.takeWhile { it != '<' }))
|
||||
}
|
||||
return expressionFromPattern.text
|
||||
}
|
||||
|
||||
if (expressionFromPattern !is KtCallExpression) return this
|
||||
val methodFromPattern = expressionFromPattern.referenceExpression()?.let { name ->
|
||||
KotlinShortNamesCache(element.project).getMethodsByName(
|
||||
name.text,
|
||||
@@ -122,7 +151,10 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
?: return this
|
||||
|
||||
val typeArgumentList = (element.parent as? KtCallExpression)?.typeArgumentList
|
||||
return if (descriptor is CallableDescriptor && descriptor.typeParametersCount == patternTypeArgumentCount ||
|
||||
?: (element.parent as? KtUserType)?.typeArgumentList
|
||||
val descriptorTypeParameterCount = (descriptor as? CallableDescriptor)?.typeParametersCount
|
||||
?: (descriptor as? ClassDescriptor)?.declaredTypeParameters?.size
|
||||
return if (patternTypeArgumentCount == descriptorTypeParameterCount ||
|
||||
patternTypeArgumentCount == typeArgumentList?.arguments?.size
|
||||
) {
|
||||
if (typeArgumentList != null) expressionFromPattern.replaceOrCreateTypeArgumentList(typeArgumentList.copy() as KtTypeArgumentList)
|
||||
@@ -137,10 +169,8 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
val descriptor: DeclarationDescriptor
|
||||
)
|
||||
|
||||
fun extractDataFromDiagnostic(deprecatedDiagnostic: Diagnostic): Data? {
|
||||
val psiElement = deprecatedDiagnostic.psiElement
|
||||
|
||||
val nameExpression: KtSimpleNameExpression = when (psiElement) {
|
||||
fun extractDataFromDiagnostic(deprecatedDiagnostic: Diagnostic, replaceInWholeProject: Boolean): Data? {
|
||||
val nameExpression: KtSimpleNameExpression = when (val psiElement = deprecatedDiagnostic.psiElement) {
|
||||
is KtSimpleNameExpression -> psiElement
|
||||
is KtConstructorCalleeExpression -> psiElement.constructorReferenceExpression
|
||||
else -> null
|
||||
@@ -156,7 +186,8 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
else -> throw IllegalStateException("Bad QuickFixRegistrar configuration")
|
||||
}
|
||||
|
||||
val replacement = fetchReplaceWithPattern(descriptor, nameExpression.project, nameExpression) ?: return null
|
||||
val replacement =
|
||||
fetchReplaceWithPattern(descriptor, nameExpression.project, nameExpression, replaceInWholeProject) ?: return null
|
||||
return Data(nameExpression, replacement, descriptor)
|
||||
}
|
||||
|
||||
@@ -171,10 +202,12 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL)
|
||||
var target = element.mainReference.resolveToDescriptors(bindingContext).singleOrNull() ?: return null
|
||||
|
||||
var replacePatternFromSymbol = fetchReplaceWithPattern(target, resolutionFacade.project, element)
|
||||
var replacePatternFromSymbol =
|
||||
fetchReplaceWithPattern(target, resolutionFacade.project, element, replaceWith.replaceInWholeProject)
|
||||
if (replacePatternFromSymbol == null && target is ConstructorDescriptor) {
|
||||
target = target.containingDeclaration
|
||||
replacePatternFromSymbol = fetchReplaceWithPattern(target, resolutionFacade.project, element)
|
||||
replacePatternFromSymbol =
|
||||
fetchReplaceWithPattern(target, resolutionFacade.project, element, replaceWith.replaceInWholeProject)
|
||||
}
|
||||
|
||||
// check that ReplaceWith hasn't changed
|
||||
@@ -201,7 +234,7 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
?.getStrictParentOfType<KtTypeAlias>()
|
||||
if (typeAlias != null) {
|
||||
val usedConstructorWithOwnReplaceWith = usedConstructorsWithOwnReplaceWith(
|
||||
element.project, target, typeAlias, element
|
||||
element.project, target, typeAlias, element, replaceWith.replaceInWholeProject
|
||||
)
|
||||
|
||||
if (usedConstructorWithOwnReplaceWith != null) {
|
||||
@@ -243,10 +276,11 @@ abstract class DeprecatedSymbolUsageFixBase(
|
||||
project: Project,
|
||||
classifier: ClassifierDescriptorWithTypeParameters,
|
||||
typeAlias: PsiElement,
|
||||
contextElement: KtSimpleNameExpression
|
||||
contextElement: KtSimpleNameExpression,
|
||||
replaceInWholeProject: Boolean
|
||||
): ConstructorDescriptor? {
|
||||
val specialReplaceWithForConstructor = classifier.constructors.filter {
|
||||
fetchReplaceWithPattern(it, project, contextElement) != null
|
||||
fetchReplaceWithPattern(it, project, contextElement, replaceInWholeProject) != null
|
||||
}.toSet()
|
||||
|
||||
if (specialReplaceWithForConstructor.isEmpty()) {
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ class DeprecatedSymbolUsageInWholeProjectFix(
|
||||
}
|
||||
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val (nameExpression, replacement, descriptor) = extractDataFromDiagnostic(diagnostic) ?: return null
|
||||
val (nameExpression, replacement, descriptor) = extractDataFromDiagnostic(diagnostic, true) ?: return null
|
||||
val descriptorName = RENDERER.render(descriptor)
|
||||
return DeprecatedSymbolUsageInWholeProjectFix(
|
||||
nameExpression,
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
|
||||
import java.util.*
|
||||
|
||||
data class ReplaceWith(val pattern: String, val imports: List<String>)
|
||||
data class ReplaceWith(val pattern: String, val imports: List<String>, val replaceInWholeProject: Boolean)
|
||||
|
||||
object ReplaceWithAnnotationAnalyzer {
|
||||
fun analyzeCallableReplacement(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'B<N>'" "true"
|
||||
// "Replace with 'B<F<Int>>'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Deprecated(message = "renamed", replaceWith = ReplaceWith("B<N>"))
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'B<N>'" "true"
|
||||
// "Replace with 'B<F<Int>>'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Deprecated(message = "renamed", replaceWith = ReplaceWith("B<N>"))
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'B<E>'" "true"
|
||||
// "Replace with 'B<String>'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Deprecated(message = "renamed", replaceWith = ReplaceWith("B<E>"))
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace with 'B<E>'" "true"
|
||||
// "Replace with 'B<String>'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Deprecated(message = "renamed", replaceWith = ReplaceWith("B<E>"))
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'String::class'" "true"
|
||||
|
||||
@Deprecated("Use class literal", ReplaceWith("T::class"))
|
||||
fun <T> foo() {
|
||||
}
|
||||
|
||||
val x = <caret>foo<String>()
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'String::class'" "true"
|
||||
|
||||
@Deprecated("Use class literal", ReplaceWith("T::class"))
|
||||
fun <T> foo() {
|
||||
}
|
||||
|
||||
val x = String::class
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace with 'Int::class.java'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Deprecated("Use class literal", ReplaceWith("T::class.java"))
|
||||
fun <T> foo() {
|
||||
}
|
||||
|
||||
val x = <caret>foo<Int>()
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Replace with 'Int::class.java'" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Deprecated("Use class literal", ReplaceWith("T::class.java"))
|
||||
fun <T> foo() {
|
||||
}
|
||||
|
||||
val x = Int::class.java
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// "Replace usages of 'old(): Unit' in whole project" "true"
|
||||
|
||||
@Deprecated("Use new", ReplaceWith("new<T>()"))
|
||||
fun <T> old() {
|
||||
}
|
||||
|
||||
fun <T> new() {
|
||||
}
|
||||
|
||||
fun main() {
|
||||
<caret>old<String>()
|
||||
old<Int>()
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// "Replace usages of 'old(): Unit' in whole project" "true"
|
||||
|
||||
@Deprecated("Use new", ReplaceWith("new<T>()"))
|
||||
fun <T> old() {
|
||||
}
|
||||
|
||||
fun <T> new() {
|
||||
}
|
||||
|
||||
fun main() {
|
||||
new<String>()
|
||||
new<Int>()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace with 'New<String, Int>'" "true"
|
||||
|
||||
@Deprecated("Use New", replaceWith = ReplaceWith("New<T, U>"))
|
||||
class Old<T, U>
|
||||
|
||||
class New<T, U>
|
||||
|
||||
fun foo(): <caret>Old<String, Int>? = null
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Replace with 'New<String, Int>'" "true"
|
||||
|
||||
@Deprecated("Use New", replaceWith = ReplaceWith("New<T, U>"))
|
||||
class Old<T, U>
|
||||
|
||||
class New<T, U>
|
||||
|
||||
fun foo(): New<String, Int>? = null
|
||||
+1
-1
@@ -44,7 +44,7 @@ class DeprecatedSymbolUsageFixSpecialTest : KotlinLightCodeInsightFixtureTestCas
|
||||
val element = file.findElementAt(offset)
|
||||
val nameExpression = element!!.parents.firstIsInstance<KtSimpleNameExpression>()
|
||||
project.executeWriteCommand("") {
|
||||
DeprecatedSymbolUsageFix(nameExpression, ReplaceWith(pattern, emptyList())).invoke(project, editor, file)
|
||||
DeprecatedSymbolUsageFix(nameExpression, ReplaceWith(pattern, emptyList(), false)).invoke(project, editor, file)
|
||||
}
|
||||
|
||||
myFixture.checkResultByFile("$testPath.after")
|
||||
|
||||
@@ -6617,6 +6617,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("classLiteral.kt")
|
||||
public void testClassLiteral() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/classLiteral.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("classLiteral2.kt")
|
||||
public void testClassLiteral2() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/classLiteral2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("emptyVarargRuntime.kt")
|
||||
public void testEmptyVarargRuntime() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/emptyVarargRuntime.kt");
|
||||
@@ -6632,6 +6642,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/explicitTypeArg.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("explicitTypeArg2.kt")
|
||||
public void testExplicitTypeArg2() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/explicitTypeArg2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("keepInUserCodeRuntime.kt")
|
||||
public void testKeepInUserCodeRuntime() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/keepInUserCodeRuntime.kt");
|
||||
@@ -6651,6 +6666,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
public void testNonEmptyVarargRuntime() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/nonEmptyVarargRuntime.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("typeReference.kt")
|
||||
public void testTypeReference() throws Exception {
|
||||
runTest("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/typeReference.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/vararg")
|
||||
|
||||
Reference in New Issue
Block a user