ReplaceWith: suggest to replace for 'get/set' operator functions
#KT-15944 Fixed
This commit is contained in:
committed by
Dmitry Gridin
parent
82b6ecfa64
commit
e31ea0c243
+20
-11
@@ -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 {
|
||||
|
||||
+11
-10
@@ -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 {
|
||||
|
||||
+3
-3
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user