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.intentions.OperatorToFunctionIntention
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtOperationReferenceExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -31,7 +28,7 @@ class CallableUsageReplacementStrategy(
private val replacement: CodeToInline,
private val inlineSetter: Boolean = false
) : UsageReplacementStrategy {
override fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement?)? {
override fun createReplacer(usage: KtReferenceExpression): (() -> KtElement?)? {
val bindingContext = usage.analyze(BodyResolveMode.PARTIAL_WITH_CFA)
val resolvedCall = usage.getResolvedCall(bindingContext) ?: return null
if (!resolvedCall.status.isSuccess) return null
@@ -45,13 +42,25 @@ class CallableUsageReplacementStrategy(
//TODO: precheck pattern correctness for annotation entry
return {
if (usage is KtOperationReferenceExpression && usage.getReferencedNameElementType() != KtTokens.IDENTIFIER) {
val nameExpression = OperatorToFunctionIntention.convert(usage.parent as KtExpression).second
createReplacer(nameExpression)?.invoke()
} else {
CodeInliner(usage, bindingContext, resolvedCall, callElement, inlineSetter, replacement).doInline()
return when {
usage is KtArrayAccessExpression -> {
{
val nameExpression = OperatorToFunctionIntention.convert(usage).second
createReplacer(nameExpression)?.invoke()
}
}
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)
}
override fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement?)? {
override fun createReplacer(usage: KtReferenceExpression): (() -> KtElement?)? {
if (usage !is KtNameReferenceExpression) return null
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.readWriteAccess
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 {
private val readReplacementStrategy = readReplacement?.let {
@@ -29,7 +29,7 @@ class PropertyUsageReplacementStrategy(readReplacement: CodeToInline?, writeRepl
CallableUsageReplacementStrategy(it, inlineSetter = true)
}
override fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement?)? {
override fun createReplacer(usage: KtReferenceExpression): (() -> KtElement?)? {
val access = usage.readWriteAccess(useResolveForReadWrite = true)
return when (access) {
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.intentions.ConvertReferenceToLambdaIntention
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.stubindex.KotlinSourceFilterScope
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
interface UsageReplacementStrategy {
fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement?)?
fun createReplacer(usage: KtReferenceExpression): (() -> KtElement?)?
}
private val LOG = Logger.getInstance(UsageReplacementStrategy::class.java)
@@ -53,7 +53,7 @@ fun UsageReplacementStrategy.replaceUsagesInWholeProject(
val usages = runReadAction {
val searchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.projectScope(project), project)
ReferencesSearch.search(targetPsiElement, searchScope)
.filterIsInstance<KtSimpleNameReference>()
.filterIsInstance<KtSimpleReference<KtReferenceExpression>>()
.map { ref -> ref.expression }
}
this@replaceUsagesInWholeProject.replaceUsages(usages, targetPsiElement, project, commandName, postAction)
@@ -62,7 +62,7 @@ fun UsageReplacementStrategy.replaceUsagesInWholeProject(
}
fun UsageReplacementStrategy.replaceUsages(
usages: Collection<KtSimpleNameExpression>,
usages: Collection<KtReferenceExpression>,
targetPsiElement: PsiElement,
project: Project,
commandName: String,
@@ -104,7 +104,7 @@ fun UsageReplacementStrategy.replaceUsages(
* @return false if some usages were invalidated
*/
private fun UsageReplacementStrategy.processUsages(
usages: List<KtSimpleNameExpression>,
usages: List<KtReferenceExpression>,
targetDeclaration: KtNamedDeclaration?,
importsToDelete: MutableList<KtImportDirective>
): Boolean {
@@ -116,7 +116,7 @@ private fun UsageReplacementStrategy.processUsages(
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
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.KotlinSingleIntentionActionFactory
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
class DeprecatedSymbolUsageFix(
element: KtSimpleNameExpression,
element: KtReferenceExpression,
replaceWith: ReplaceWith
) : DeprecatedSymbolUsageFixBase(element, replaceWith), CleanupFix, HighPriorityAction {
@@ -51,8 +51,8 @@ class DeprecatedSymbolUsageFix(
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val (nameExpression, replacement) = extractDataFromDiagnostic(diagnostic, false) ?: return null
return DeprecatedSymbolUsageFix(nameExpression, replacement)
val (referenceExpression, replacement) = extractDataFromDiagnostic(diagnostic, false) ?: return null
return DeprecatedSymbolUsageFix(referenceExpression, replacement)
}
fun isImportToBeRemoved(import: KtImportDirective): Boolean {
@@ -52,9 +52,9 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
//TODO: different replacements for property accessors
abstract class DeprecatedSymbolUsageFixBase(
element: KtSimpleNameExpression,
element: KtReferenceExpression,
val replaceWith: ReplaceWith
) : KotlinQuickFixAction<KtSimpleNameExpression>(element) {
) : KotlinQuickFixAction<KtReferenceExpression>(element) {
override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean {
val element = element ?: return false
@@ -76,7 +76,7 @@ abstract class DeprecatedSymbolUsageFixBase(
fun fetchReplaceWithPattern(
descriptor: DeclarationDescriptor,
project: Project,
contextElement: KtSimpleNameExpression?,
contextElement: KtReferenceExpression?,
replaceInWholeProject: Boolean
): ReplaceWith? {
val annotation = descriptor.annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecated) ?: return null
@@ -102,7 +102,7 @@ abstract class DeprecatedSymbolUsageFixBase(
}
private fun String.applyContextElement(
element: KtSimpleNameExpression?,
element: KtReferenceExpression?,
descriptor: DeclarationDescriptor
): String {
if (element == null) return this
@@ -155,13 +155,14 @@ abstract class DeprecatedSymbolUsageFixBase(
}
data class Data(
val nameExpression: KtSimpleNameExpression,
val referenceExpression: KtReferenceExpression,
val replaceWith: ReplaceWith,
val descriptor: DeclarationDescriptor
)
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 KtConstructorCalleeExpression -> psiElement.constructorReferenceExpression
else -> null
@@ -178,12 +179,12 @@ abstract class DeprecatedSymbolUsageFixBase(
}
val replacement =
fetchReplaceWithPattern(descriptor, nameExpression.project, nameExpression, replaceInWholeProject) ?: return null
return Data(nameExpression, replacement, descriptor)
fetchReplaceWithPattern(descriptor, referenceExpression.project, referenceExpression, replaceInWholeProject) ?: return null
return Data(referenceExpression, replacement, descriptor)
}
private fun buildUsageReplacementStrategy(
element: KtSimpleNameExpression,
element: KtReferenceExpression,
replaceWith: ReplaceWith,
recheckAnnotation: Boolean,
reformat: Boolean,
@@ -269,7 +270,7 @@ abstract class DeprecatedSymbolUsageFixBase(
project: Project,
classifier: ClassifierDescriptorWithTypeParameters,
typeAlias: PsiElement,
contextElement: KtSimpleNameExpression,
contextElement: KtReferenceExpression,
replaceInWholeProject: Boolean
): ConstructorDescriptor? {
val specialReplaceWithForConstructor = classifier.constructors.filter {
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
class DeprecatedSymbolUsageInWholeProjectFix(
element: KtSimpleNameExpression,
element: KtReferenceExpression,
replaceWith: ReplaceWith,
private val text: String
) : DeprecatedSymbolUsageFixBase(element, replaceWith) {
@@ -68,10 +68,10 @@ class DeprecatedSymbolUsageInWholeProjectFix(
}
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)
return DeprecatedSymbolUsageInWholeProjectFix(
nameExpression,
referenceExpression,
replacement,
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);
}
@TestMetadata("get.kt")
public void testGet() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/get.kt");
}
@TestMetadata("in.kt")
public void testIn() throws Exception {
runTest("idea/testData/quickfix/deprecatedSymbolUsage/operatorCalls/in.kt");
@@ -7000,6 +7005,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
public void testPlusAssign() throws Exception {
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")