Replace deprecated symbol usage: fix message in case of generic parameter

#KT-8958 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-04-15 17:03:12 +09:00
committed by Mikhail Glukhikh
parent e8e457a16c
commit 0fad3fafa8
18 changed files with 149 additions and 23 deletions
@@ -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
}
}
}
@@ -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()) {
@@ -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,
@@ -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,4 +1,4 @@
// "Replace with 'B<N>'" "true"
// "Replace with 'B<F<Int>>'" "true"
// WITH_RUNTIME
@Deprecated(message = "renamed", replaceWith = ReplaceWith("B<N>"))
@@ -1,4 +1,4 @@
// "Replace with 'B<E>'" "true"
// "Replace with 'B<String>'" "true"
// WITH_RUNTIME
@Deprecated(message = "renamed", replaceWith = ReplaceWith("B<E>"))
@@ -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>()
@@ -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>()
@@ -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
@@ -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>()
}
@@ -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
@@ -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
@@ -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")