diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt index ec9e39e3a5a..1ffa557fd46 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt @@ -22,7 +22,6 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.quickfix.CleanupFix import org.jetbrains.kotlin.idea.quickfix.JetSingleIntentionActionFactory @@ -60,9 +59,7 @@ public class DeprecatedSymbolUsageFix( companion object : JetSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { - val nameExpression = diagnostic.psiElement as? JetSimpleNameExpression ?: return null - val descriptor = Errors.DEPRECATED_SYMBOL_WITH_MESSAGE.cast(diagnostic).a - val replacement = DeprecatedSymbolUsageFixBase.replaceWithPattern(descriptor, nameExpression.project) ?: return null + val (nameExpression, replacement) = DeprecatedSymbolUsageFixBase.extractDataFromDiagnostic(diagnostic) ?: return null return DeprecatedSymbolUsageFix(nameExpression, replacement) } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt index 6ce0139a6b8..000564ff962 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt @@ -22,8 +22,11 @@ import com.intellij.psi.PsiFile import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.core.OptionalParametersHelper import org.jetbrains.kotlin.idea.quickfix.JetIntentionAction +import org.jetbrains.kotlin.psi.JetConstructorCalleeExpression import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetSimpleNameExpression import org.jetbrains.kotlin.resolve.DescriptorUtils @@ -56,7 +59,7 @@ public abstract class DeprecatedSymbolUsageFixBase( editor: Editor?) companion object { - public fun replaceWithPattern(descriptor: DeclarationDescriptor, project: Project): ReplaceWith? { + fun replaceWithPattern(descriptor: DeclarationDescriptor, project: Project): ReplaceWith? { val annotationClass = descriptor.builtIns.deprecatedAnnotation val annotation = descriptor.annotations.findAnnotation(DescriptorUtils.getFqNameSafe(annotationClass)) ?: return null val replaceWithValue = annotation.argumentValue(kotlin.deprecated::replaceWith.name) as? AnnotationDescriptor ?: return null @@ -72,5 +75,34 @@ public abstract class DeprecatedSymbolUsageFixBase( return ReplaceWith(pattern, *imports.toTypedArray()) } + + data class Data( + val nameExpression: JetSimpleNameExpression, + val replaceWith: ReplaceWith, + val descriptor: DeclarationDescriptor + ) + + fun extractDataFromDiagnostic(deprecatedDiagnostic: Diagnostic): Data? { + val psiElement = deprecatedDiagnostic.psiElement + + //TODO: compiler crash here + /* + val nameExpression: JetSimpleNameExpression = when (psiElement) { + is JetSimpleNameExpression -> psiElement + is JetConstructorCalleeExpression -> psiElement.constructorReferenceExpression + else -> null + } ?: return null + */ + val nameExpression: JetSimpleNameExpression = (if (psiElement is JetSimpleNameExpression) + psiElement + else if (psiElement is JetConstructorCalleeExpression) + psiElement.constructorReferenceExpression + else + null) ?: return null + + val descriptor = Errors.DEPRECATED_SYMBOL_WITH_MESSAGE.cast(deprecatedDiagnostic).a + val replacement = DeprecatedSymbolUsageFixBase.replaceWithPattern(descriptor, nameExpression.project) ?: return null + return Data(nameExpression, replacement, descriptor) + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt index e12fc558d55..d654abafb94 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt @@ -28,7 +28,6 @@ import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.util.ui.UIUtil import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.quickfix.JetSingleIntentionActionFactory import org.jetbrains.kotlin.idea.references.JetSimpleNameReference @@ -36,10 +35,7 @@ import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.stubindex.JetSourceFilterScope import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.runReadAction -import org.jetbrains.kotlin.psi.JetImportDirective -import org.jetbrains.kotlin.psi.JetNamedFunction -import org.jetbrains.kotlin.psi.JetProperty -import org.jetbrains.kotlin.psi.JetSimpleNameExpression +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.NameShortness @@ -61,12 +57,11 @@ public class DeprecatedSymbolUsageInWholeProjectFix( override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean { if (!super.isAvailable(project, editor, file)) return false - val targetPsiElement = element.mainReference.resolve() - return targetPsiElement is JetNamedFunction || targetPsiElement is JetProperty //TODO + return targetPsiElement() != null } override fun invoke(replacementStrategy: UsageReplacementStrategy, project: Project, editor: Editor?) { - val psiElement = element.mainReference.resolve()!! + val psiElement = targetPsiElement()!! ProgressManager.getInstance().run( object : Task.Modal(project, "Applying '$text'", true) { @@ -82,6 +77,16 @@ public class DeprecatedSymbolUsageInWholeProjectFix( }) } + private fun targetPsiElement(): JetDeclaration? { + val referenceTarget = element.mainReference.resolve() + return when (referenceTarget) { + is JetNamedFunction -> referenceTarget + is JetProperty -> referenceTarget + is JetConstructor<*> -> referenceTarget.getContainingClassOrObject() //TODO: constructor can be deprecated itself + else -> null + } + } + private fun replaceUsages(project: Project, usages: Collection, replacementStrategy: UsageReplacementStrategy) { UIUtil.invokeLaterIfNeeded { project.executeWriteCommand(text) { @@ -127,9 +132,7 @@ public class DeprecatedSymbolUsageInWholeProjectFix( } override fun createAction(diagnostic: Diagnostic): IntentionAction? { - val nameExpression = diagnostic.psiElement as? JetSimpleNameExpression ?: return null - val descriptor = Errors.DEPRECATED_SYMBOL_WITH_MESSAGE.cast(diagnostic).a - val replacement = DeprecatedSymbolUsageFixBase.replaceWithPattern(descriptor, nameExpression.project) ?: return null + val (nameExpression, replacement, descriptor) = DeprecatedSymbolUsageFixBase.extractDataFromDiagnostic(diagnostic) ?: return null val descriptorName = RENDERER.render(descriptor) return DeprecatedSymbolUsageInWholeProjectFix(nameExpression, replacement, "Replace usages of '$descriptorName' in whole project") } diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation1.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation1.kt new file mode 100644 index 00000000000..f503a60cdac --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation1.kt @@ -0,0 +1,10 @@ +// "Replace with 'test.Bar'" "true" + +package test + +@deprecated("Replace with bar", ReplaceWith("test.Bar")) +annotation class Foo + +annotation class Bar + +@Foo class C {} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation1.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation1.kt.after new file mode 100644 index 00000000000..c68baf84236 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation1.kt.after @@ -0,0 +1,10 @@ +// "Replace with 'test.Bar'" "true" + +package test + +@deprecated("Replace with bar", ReplaceWith("test.Bar")) +annotation class Foo + +annotation class Bar + +@Bar class C {} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation2.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation2.kt new file mode 100644 index 00000000000..ff544698230 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation2.kt @@ -0,0 +1,10 @@ +// "Replace with 'test.Bar'" "true" + +package test + +@deprecated("Replace with bar", ReplaceWith("test.Bar")) +annotation class Foo(val p1: String, val p2: Int) + +annotation class Bar(val p1: String, val p2: Int) + +@Foo("", 1) class C {} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation2.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation2.kt.after new file mode 100644 index 00000000000..e00889d6550 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation2.kt.after @@ -0,0 +1,10 @@ +// "Replace with 'test.Bar'" "true" + +package test + +@deprecated("Replace with bar", ReplaceWith("test.Bar")) +annotation class Foo(val p1: String, val p2: Int) + +annotation class Bar(val p1: String, val p2: Int) + +@Bar("", 1) class C {} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.after.Declaration.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.after.Declaration.kt new file mode 100644 index 00000000000..24b530c499b --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.after.Declaration.kt @@ -0,0 +1,8 @@ +package dependency + +@deprecated("", ReplaceWith("dependency.NewAnnotation")) +annotation class OldAnnotation(val p: Int = 0) + +annotation class NewAnnotation(val p: Int = 0, val newP: String = "") + +@NewAnnotation class C \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.after.Other.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.after.Other.kt new file mode 100644 index 00000000000..158c43ab01a --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.after.Other.kt @@ -0,0 +1,9 @@ +// "Replace with 'test.Bar'" "true" + +package x + +import dependency.* + +annotation class A(val a: NewAnnotation) + +@A(NewAnnotation()) class Y diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.after.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.after.kt new file mode 100644 index 00000000000..b6a833255ff --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.after.kt @@ -0,0 +1,10 @@ +// "Replace usages of 'OldAnnotation' in whole project" "true" + +package test + +import dependency.NewAnnotation + +fun foo(a: NewAnnotation) { +} + +@NewAnnotation(1) class X \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.before.Declaration.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.before.Declaration.kt new file mode 100644 index 00000000000..32cd503f0c2 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.before.Declaration.kt @@ -0,0 +1,8 @@ +package dependency + +@deprecated("", ReplaceWith("dependency.NewAnnotation")) +annotation class OldAnnotation(val p: Int = 0) + +annotation class NewAnnotation(val p: Int = 0, val newP: String = "") + +@OldAnnotation class C \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.before.Main.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.before.Main.kt new file mode 100644 index 00000000000..c496c964953 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.before.Main.kt @@ -0,0 +1,10 @@ +// "Replace usages of 'OldAnnotation' in whole project" "true" + +package test + +import dependency.OldAnnotation + +fun foo(a: OldAnnotation) { +} + +@OldAnnotation(1) class X \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.before.Other.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.before.Other.kt new file mode 100644 index 00000000000..f17a4ce2084 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.before.Other.kt @@ -0,0 +1,9 @@ +// "Replace with 'test.Bar'" "true" + +package x + +import dependency.* + +annotation class A(val a: OldAnnotation) + +@A(OldAnnotation()) class Y diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index c17eb8550a7..87e629eacdb 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -919,6 +919,12 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes public void testAllFilesPresentInClassUsages() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/classUsages"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true); } + + @TestMetadata("inWholeProject.before.Main.kt") + public void testInWholeProject() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/inWholeProject.before.Main.kt"); + doTestWithExtraFile(fileName); + } } @TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/imports") diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index f25d1a3dfa5..75ae8652d6d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -3312,6 +3312,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/classUsages"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); } + @TestMetadata("annotation1.kt") + public void testAnnotation1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation1.kt"); + doTest(fileName); + } + + @TestMetadata("annotation2.kt") + public void testAnnotation2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/annotation2.kt"); + doTest(fileName); + } + @TestMetadata("constructorUsage1.kt") public void testConstructorUsage1() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/constructorUsage1.kt");