ReplaceWith: suggest to replace for 'get/set' operator functions

#KT-15944 Fixed
This commit is contained in:
Toshiaki Kameyama
2020-06-15 11:02:56 +09:00
committed by Dmitry Gridin
parent 82b6ecfa64
commit e31ea0c243
12 changed files with 103 additions and 37 deletions
@@ -19,10 +19,7 @@ package org.jetbrains.kotlin.idea.codeInliner
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtOperationReferenceExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -31,7 +28,7 @@ class CallableUsageReplacementStrategy(
private val replacement: CodeToInline, private val replacement: CodeToInline,
private val inlineSetter: Boolean = false private val inlineSetter: Boolean = false
) : UsageReplacementStrategy { ) : UsageReplacementStrategy {
override fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement?)? { override fun createReplacer(usage: KtReferenceExpression): (() -> KtElement?)? {
val bindingContext = usage.analyze(BodyResolveMode.PARTIAL_WITH_CFA) val bindingContext = usage.analyze(BodyResolveMode.PARTIAL_WITH_CFA)
val resolvedCall = usage.getResolvedCall(bindingContext) ?: return null val resolvedCall = usage.getResolvedCall(bindingContext) ?: return null
if (!resolvedCall.status.isSuccess) return null if (!resolvedCall.status.isSuccess) return null
@@ -45,13 +42,25 @@ class CallableUsageReplacementStrategy(
//TODO: precheck pattern correctness for annotation entry //TODO: precheck pattern correctness for annotation entry
return { return when {
if (usage is KtOperationReferenceExpression && usage.getReferencedNameElementType() != KtTokens.IDENTIFIER) { usage is KtArrayAccessExpression -> {
val nameExpression = OperatorToFunctionIntention.convert(usage.parent as KtExpression).second {
createReplacer(nameExpression)?.invoke() val nameExpression = OperatorToFunctionIntention.convert(usage).second
} else { createReplacer(nameExpression)?.invoke()
CodeInliner(usage, bindingContext, resolvedCall, callElement, inlineSetter, replacement).doInline() }
} }
usage is KtOperationReferenceExpression && usage.getReferencedNameElementType() != KtTokens.IDENTIFIER -> {
{
val nameExpression = OperatorToFunctionIntention.convert(usage.parent as KtExpression).second
createReplacer(nameExpression)?.invoke()
}
}
usage is KtSimpleNameExpression -> {
{
CodeInliner(usage, bindingContext, resolvedCall, callElement, inlineSetter, replacement).doInline()
}
}
else -> null
} }
} }
} }
@@ -45,7 +45,7 @@ class ClassUsageReplacementStrategy(
CallableUsageReplacementStrategy(it, inlineSetter = false) CallableUsageReplacementStrategy(it, inlineSetter = false)
} }
override fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement?)? { override fun createReplacer(usage: KtReferenceExpression): (() -> KtElement?)? {
if (usage !is KtNameReferenceExpression) return null if (usage !is KtNameReferenceExpression) return null
constructorReplacementStrategy?.createReplacer(usage)?.let { return it } constructorReplacementStrategy?.createReplacer(usage)?.let { return it }
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.idea.codeInliner
import org.jetbrains.kotlin.idea.references.ReferenceAccess import org.jetbrains.kotlin.idea.references.ReferenceAccess
import org.jetbrains.kotlin.idea.references.readWriteAccess import org.jetbrains.kotlin.idea.references.readWriteAccess
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.psi.KtReferenceExpression
class PropertyUsageReplacementStrategy(readReplacement: CodeToInline?, writeReplacement: CodeToInline?) : UsageReplacementStrategy { class PropertyUsageReplacementStrategy(readReplacement: CodeToInline?, writeReplacement: CodeToInline?) : UsageReplacementStrategy {
private val readReplacementStrategy = readReplacement?.let { private val readReplacementStrategy = readReplacement?.let {
@@ -29,7 +29,7 @@ class PropertyUsageReplacementStrategy(readReplacement: CodeToInline?, writeRepl
CallableUsageReplacementStrategy(it, inlineSetter = true) CallableUsageReplacementStrategy(it, inlineSetter = true)
} }
override fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement?)? { override fun createReplacer(usage: KtReferenceExpression): (() -> KtElement?)? {
val access = usage.readWriteAccess(useResolveForReadWrite = true) val access = usage.readWriteAccess(useResolveForReadWrite = true)
return when (access) { return when (access) {
ReferenceAccess.READ -> readReplacementStrategy?.createReplacer(usage) ReferenceAccess.READ -> readReplacementStrategy?.createReplacer(usage)
@@ -24,7 +24,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.core.targetDescriptors
import org.jetbrains.kotlin.idea.intentions.ConvertReferenceToLambdaIntention import org.jetbrains.kotlin.idea.intentions.ConvertReferenceToLambdaIntention
import org.jetbrains.kotlin.idea.intentions.SpecifyExplicitLambdaSignatureIntention import org.jetbrains.kotlin.idea.intentions.SpecifyExplicitLambdaSignatureIntention
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.KtSimpleReference
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
@@ -35,7 +35,7 @@ import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
interface UsageReplacementStrategy { interface UsageReplacementStrategy {
fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement?)? fun createReplacer(usage: KtReferenceExpression): (() -> KtElement?)?
} }
private val LOG = Logger.getInstance(UsageReplacementStrategy::class.java) private val LOG = Logger.getInstance(UsageReplacementStrategy::class.java)
@@ -53,7 +53,7 @@ fun UsageReplacementStrategy.replaceUsagesInWholeProject(
val usages = runReadAction { val usages = runReadAction {
val searchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.projectScope(project), project) val searchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.projectScope(project), project)
ReferencesSearch.search(targetPsiElement, searchScope) ReferencesSearch.search(targetPsiElement, searchScope)
.filterIsInstance<KtSimpleNameReference>() .filterIsInstance<KtSimpleReference<KtReferenceExpression>>()
.map { ref -> ref.expression } .map { ref -> ref.expression }
} }
this@replaceUsagesInWholeProject.replaceUsages(usages, targetPsiElement, project, commandName, postAction) this@replaceUsagesInWholeProject.replaceUsages(usages, targetPsiElement, project, commandName, postAction)
@@ -62,7 +62,7 @@ fun UsageReplacementStrategy.replaceUsagesInWholeProject(
} }
fun UsageReplacementStrategy.replaceUsages( fun UsageReplacementStrategy.replaceUsages(
usages: Collection<KtSimpleNameExpression>, usages: Collection<KtReferenceExpression>,
targetPsiElement: PsiElement, targetPsiElement: PsiElement,
project: Project, project: Project,
commandName: String, commandName: String,
@@ -104,7 +104,7 @@ fun UsageReplacementStrategy.replaceUsages(
* @return false if some usages were invalidated * @return false if some usages were invalidated
*/ */
private fun UsageReplacementStrategy.processUsages( private fun UsageReplacementStrategy.processUsages(
usages: List<KtSimpleNameExpression>, usages: List<KtReferenceExpression>,
targetDeclaration: KtNamedDeclaration?, targetDeclaration: KtNamedDeclaration?,
importsToDelete: MutableList<KtImportDirective> importsToDelete: MutableList<KtImportDirective>
): Boolean { ): Boolean {
@@ -116,7 +116,7 @@ private fun UsageReplacementStrategy.processUsages(
continue continue
} }
if (specialUsageProcessing(usage, targetDeclaration)) continue if (usage is KtSimpleNameExpression && specialUsageProcessing(usage, targetDeclaration)) continue
//TODO: keep the import if we don't know how to replace some of the usages //TODO: keep the import if we don't know how to replace some of the usages
val importDirective = usage.getStrictParentOfType<KtImportDirective>() val importDirective = usage.getStrictParentOfType<KtImportDirective>()
@@ -28,11 +28,11 @@ import org.jetbrains.kotlin.idea.core.targetDescriptors
import org.jetbrains.kotlin.idea.quickfix.CleanupFix import org.jetbrains.kotlin.idea.quickfix.CleanupFix
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
import org.jetbrains.kotlin.psi.KtImportDirective import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
class DeprecatedSymbolUsageFix( class DeprecatedSymbolUsageFix(
element: KtSimpleNameExpression, element: KtReferenceExpression,
replaceWith: ReplaceWith replaceWith: ReplaceWith
) : DeprecatedSymbolUsageFixBase(element, replaceWith), CleanupFix, HighPriorityAction { ) : DeprecatedSymbolUsageFixBase(element, replaceWith), CleanupFix, HighPriorityAction {
@@ -51,8 +51,8 @@ class DeprecatedSymbolUsageFix(
companion object : KotlinSingleIntentionActionFactory() { companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? { override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val (nameExpression, replacement) = extractDataFromDiagnostic(diagnostic, false) ?: return null val (referenceExpression, replacement) = extractDataFromDiagnostic(diagnostic, false) ?: return null
return DeprecatedSymbolUsageFix(nameExpression, replacement) return DeprecatedSymbolUsageFix(referenceExpression, replacement)
} }
fun isImportToBeRemoved(import: KtImportDirective): Boolean { fun isImportToBeRemoved(import: KtImportDirective): Boolean {
@@ -52,9 +52,9 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
//TODO: different replacements for property accessors //TODO: different replacements for property accessors
abstract class DeprecatedSymbolUsageFixBase( abstract class DeprecatedSymbolUsageFixBase(
element: KtSimpleNameExpression, element: KtReferenceExpression,
val replaceWith: ReplaceWith val replaceWith: ReplaceWith
) : KotlinQuickFixAction<KtSimpleNameExpression>(element) { ) : KotlinQuickFixAction<KtReferenceExpression>(element) {
override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean { override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean {
val element = element ?: return false val element = element ?: return false
@@ -76,7 +76,7 @@ abstract class DeprecatedSymbolUsageFixBase(
fun fetchReplaceWithPattern( fun fetchReplaceWithPattern(
descriptor: DeclarationDescriptor, descriptor: DeclarationDescriptor,
project: Project, project: Project,
contextElement: KtSimpleNameExpression?, contextElement: KtReferenceExpression?,
replaceInWholeProject: Boolean replaceInWholeProject: Boolean
): ReplaceWith? { ): ReplaceWith? {
val annotation = descriptor.annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecated) ?: return null val annotation = descriptor.annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecated) ?: return null
@@ -102,7 +102,7 @@ abstract class DeprecatedSymbolUsageFixBase(
} }
private fun String.applyContextElement( private fun String.applyContextElement(
element: KtSimpleNameExpression?, element: KtReferenceExpression?,
descriptor: DeclarationDescriptor descriptor: DeclarationDescriptor
): String { ): String {
if (element == null) return this if (element == null) return this
@@ -155,13 +155,14 @@ abstract class DeprecatedSymbolUsageFixBase(
} }
data class Data( data class Data(
val nameExpression: KtSimpleNameExpression, val referenceExpression: KtReferenceExpression,
val replaceWith: ReplaceWith, val replaceWith: ReplaceWith,
val descriptor: DeclarationDescriptor val descriptor: DeclarationDescriptor
) )
fun extractDataFromDiagnostic(deprecatedDiagnostic: Diagnostic, replaceInWholeProject: Boolean): Data? { fun extractDataFromDiagnostic(deprecatedDiagnostic: Diagnostic, replaceInWholeProject: Boolean): Data? {
val nameExpression: KtSimpleNameExpression = when (val psiElement = deprecatedDiagnostic.psiElement) { val referenceExpression = when (val psiElement = deprecatedDiagnostic.psiElement) {
is KtArrayAccessExpression -> psiElement
is KtSimpleNameExpression -> psiElement is KtSimpleNameExpression -> psiElement
is KtConstructorCalleeExpression -> psiElement.constructorReferenceExpression is KtConstructorCalleeExpression -> psiElement.constructorReferenceExpression
else -> null else -> null
@@ -178,12 +179,12 @@ abstract class DeprecatedSymbolUsageFixBase(
} }
val replacement = val replacement =
fetchReplaceWithPattern(descriptor, nameExpression.project, nameExpression, replaceInWholeProject) ?: return null fetchReplaceWithPattern(descriptor, referenceExpression.project, referenceExpression, replaceInWholeProject) ?: return null
return Data(nameExpression, replacement, descriptor) return Data(referenceExpression, replacement, descriptor)
} }
private fun buildUsageReplacementStrategy( private fun buildUsageReplacementStrategy(
element: KtSimpleNameExpression, element: KtReferenceExpression,
replaceWith: ReplaceWith, replaceWith: ReplaceWith,
recheckAnnotation: Boolean, recheckAnnotation: Boolean,
reformat: Boolean, reformat: Boolean,
@@ -269,7 +270,7 @@ abstract class DeprecatedSymbolUsageFixBase(
project: Project, project: Project,
classifier: ClassifierDescriptorWithTypeParameters, classifier: ClassifierDescriptorWithTypeParameters,
typeAlias: PsiElement, typeAlias: PsiElement,
contextElement: KtSimpleNameExpression, contextElement: KtReferenceExpression,
replaceInWholeProject: Boolean replaceInWholeProject: Boolean
): ConstructorDescriptor? { ): ConstructorDescriptor? {
val specialReplaceWithForConstructor = classifier.constructors.filter { val specialReplaceWithForConstructor = classifier.constructors.filter {
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
class DeprecatedSymbolUsageInWholeProjectFix( class DeprecatedSymbolUsageInWholeProjectFix(
element: KtSimpleNameExpression, element: KtReferenceExpression,
replaceWith: ReplaceWith, replaceWith: ReplaceWith,
private val text: String private val text: String
) : DeprecatedSymbolUsageFixBase(element, replaceWith) { ) : DeprecatedSymbolUsageFixBase(element, replaceWith) {
@@ -68,10 +68,10 @@ class DeprecatedSymbolUsageInWholeProjectFix(
} }
override fun createAction(diagnostic: Diagnostic): IntentionAction? { override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val (nameExpression, replacement, descriptor) = extractDataFromDiagnostic(diagnostic, true) ?: return null val (referenceExpression, replacement, descriptor) = extractDataFromDiagnostic(diagnostic, true) ?: return null
val descriptorName = RENDERER.render(descriptor) val descriptorName = RENDERER.render(descriptor)
return DeprecatedSymbolUsageInWholeProjectFix( return DeprecatedSymbolUsageInWholeProjectFix(
nameExpression, referenceExpression,
replacement, replacement,
KotlinBundle.message("replace.usages.of.0.in.whole.project", descriptorName) KotlinBundle.message("replace.usages.of.0.in.whole.project", descriptorName)
) )
@@ -0,0 +1,11 @@
// "Replace with 'get2(i)'" "true"
interface T {
@Deprecated("", replaceWith = ReplaceWith("get2(i)"))
operator fun get(i: Int): String
fun get2(i: Int): String
}
fun test(t: T) {
val s = <caret>t[0]
}
@@ -0,0 +1,11 @@
// "Replace with 'get2(i)'" "true"
interface T {
@Deprecated("", replaceWith = ReplaceWith("get2(i)"))
operator fun get(i: Int): String
fun get2(i: Int): String
}
fun test(t: T) {
val s = t.get2(0)
}
@@ -0,0 +1,12 @@
// "Replace usages of 'set(Int, Int): Unit' in whole project" "true"
interface T {
@Deprecated("", replaceWith = ReplaceWith("set2(i, v)"))
operator fun set(i: Int, v: Int)
fun set2(i: Int, v: Int)
}
fun test(t: T) {
<caret>t[0] = 1
t[1] = 2
}
@@ -0,0 +1,12 @@
// "Replace usages of 'set(Int, Int): Unit' in whole project" "true"
interface T {
@Deprecated("", replaceWith = ReplaceWith("set2(i, v)"))
operator fun set(i: Int, v: Int)
fun set2(i: Int, v: Int)
}
fun test(t: T) {
t.set2(0, 1)
t.set2(1, 2)
}
@@ -6991,6 +6991,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true); KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
} }
@TestMetadata("get.kt")
public void testGet() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/get.kt");
}
@TestMetadata("in.kt") @TestMetadata("in.kt")
public void testIn() throws Exception { public void testIn() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/in.kt"); runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/in.kt");
@@ -7000,6 +7005,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
public void testPlusAssign() throws Exception { public void testPlusAssign() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/plusAssign.kt"); runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/plusAssign.kt");
} }
@TestMetadata("set.kt")
public void testSet() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/set.kt");
}
} }
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters") @TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters")