diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ReplaceProtectedToPublishedApiCallFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ReplaceProtectedToPublishedApiCallFix.kt index fe48b266514..476a0ca1aa6 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ReplaceProtectedToPublishedApiCallFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ReplaceProtectedToPublishedApiCallFix.kt @@ -29,11 +29,14 @@ import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory +import org.jetbrains.kotlin.idea.quickfix.replaceWith.ReplaceProtectedToPublishedApiCallFix.Companion.newName import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered import org.jetbrains.kotlin.resolve.source.getPsi class ReplaceProtectedToPublishedApiCallFix( @@ -41,27 +44,25 @@ class ReplaceProtectedToPublishedApiCallFix( val classOwnerPointer: SmartPsiElementPointer, val originalName: String, val paramNames: Map, - val signature: String, + val newSignature: String, val isProperty: Boolean, - val isVar: Boolean + val isVar: Boolean, + val isPublishedMemberAlreadyExists: Boolean ) : KotlinQuickFixAction(element) { override fun getFamilyName() = "Replace with @PublishedApi bridge call" - val newName = "`${originalName.newName}`" - - override fun getText() = "Replace with generated @PublishedApi bridge call '$newName${if (!isProperty) "(...)" else ""}'" + override fun getText() = "Replace with generated @PublishedApi bridge call '${originalName.newNameQuoted}${if (!isProperty) "(...)" else ""}'" override fun invoke(project: Project, editor: Editor?, file: KtFile) { val element = element ?: return - val isPublishedMemberAlreadyExists = false/*TODO*/ if (!isPublishedMemberAlreadyExists) { val classOwner = classOwnerPointer.element ?: return val newMember: KtDeclaration = if (isProperty) { KtPsiFactory(classOwner).createProperty( "@kotlin.PublishedApi\n" + - "internal " + signature.replaceFirst("$originalName:", "$newName:") + + "internal " + newSignature + "\n" + "get() = $originalName\n" + if (isVar) "set(value) { $originalName = value }" else "" @@ -71,14 +72,14 @@ class ReplaceProtectedToPublishedApiCallFix( else { KtPsiFactory(classOwner).createFunction( "@kotlin.PublishedApi\n" + - "internal " + signature.replaceFirst("$originalName(", "$newName(") + + "internal " + newSignature + " = $originalName(${paramNames.keys.map { it }.joinToString(", ")})" ) } ShortenReferences.DEFAULT.process(classOwner.addDeclaration(newMember)) } - element.replace(KtPsiFactory(element).createExpression(newName)) + element.replace(KtPsiFactory(element).createExpression(originalName.newNameQuoted)) } companion object : KotlinSingleIntentionActionFactory() { @@ -97,15 +98,36 @@ class ReplaceProtectedToPublishedApiCallFix( val isVar = descriptor is PropertyDescriptor && descriptor.isVar val signature = signatureRenderer.render(descriptor) + val originalName = descriptor.name.asString() + val newSignature = + if (isProperty) { + signature.replaceFirst("$originalName:", "${originalName.newNameQuoted}:") + } + else { + signature.replaceFirst("$originalName(", "${originalName.newNameQuoted}(") + } val paramNameAndType = descriptor.valueParameters.associate { it.name.asString() to it.type.getJetTypeFqName(false)} val classDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return null val source = classDescriptor.source.getPsi() as? KtClass ?: return null + + val newName = Name.identifier(originalName.newName) + val contributedDescriptors = classDescriptor.unsubstitutedMemberScope.getDescriptorsFiltered { + it == newName + } + val isPublishedMemberAlreadyExists = contributedDescriptors.filterIsInstance().any { + signatureRenderer.render(it) == newSignature + } + return ReplaceProtectedToPublishedApiCallFix( - psiElement, source.createSmartPointer(), descriptor.name.asString(), paramNameAndType, signature, isProperty, isVar + psiElement, source.createSmartPointer(), originalName, paramNameAndType, newSignature, + isProperty, isVar, isPublishedMemberAlreadyExists ) } val String.newName: String get() = "access\$$this" + + val String.newNameQuoted: String + get() = "`$newName`" } } diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStub.kt b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStub.kt new file mode 100644 index 00000000000..0ea521d83f0 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStub.kt @@ -0,0 +1,16 @@ +// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true" + +open class ABase { + protected fun test(p: Int) { + } + + + inline fun test() { + { + test(1) + }() + } + + @PublishedApi + internal fun `access$test`(p: Int) = test(p) +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStub.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStub.kt.after new file mode 100644 index 00000000000..3ebab8ec277 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStub.kt.after @@ -0,0 +1,16 @@ +// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true" + +open class ABase { + protected fun test(p: Int) { + } + + + inline fun test() { + { + `access$test`(1) + }() + } + + @PublishedApi + internal fun `access$test`(p: Int) = test(p) +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStubForVar.kt b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStubForVar.kt new file mode 100644 index 00000000000..72555b00487 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStubForVar.kt @@ -0,0 +1,18 @@ +// "Replace with generated @PublishedApi bridge call '`access$prop`'" "true" + +open class ABase { + protected var prop = 1 + + inline fun test() { + { + prop + }() + } + + @PublishedApi + internal var `access$prop`: Int + get() = prop + set(value) { + prop = value + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStubForVar.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStubForVar.kt.after new file mode 100644 index 00000000000..1384c9904d8 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStubForVar.kt.after @@ -0,0 +1,18 @@ +// "Replace with generated @PublishedApi bridge call '`access$prop`'" "true" + +open class ABase { + protected var prop = 1 + + inline fun test() { + { + `access$prop` + }() + } + + @PublishedApi + internal var `access$prop`: Int + get() = prop + set(value) { + prop = value + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extensionVar.kt b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extensionVar.kt index 19081e838c3..632a7262d88 100644 --- a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extensionVar.kt +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extensionVar.kt @@ -5,7 +5,7 @@ open class ABase { @Z protected var String.prop: Int get() = 1 - set(field) = {} + set(field) {} inline fun test() { diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extensionVar.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extensionVar.kt.after index 440a7586e25..f9e6a4d947a 100644 --- a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extensionVar.kt.after +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extensionVar.kt.after @@ -1,11 +1,11 @@ -// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true" +// "Replace with generated @PublishedApi bridge call '`access$prop`'" "true" annotation class Z open class ABase { @Z protected var String.prop: Int get() = 1 - set(field) = {} + set(field) {} inline fun test() { diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 1c3560ea75b..f171ebfe9ee 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -4863,6 +4863,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("existingStub.kt") + public void testExistingStub() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStub.kt"); + doTest(fileName); + } + + @TestMetadata("existingStubForVar.kt") + public void testExistingStubForVar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/existingStubForVar.kt"); + doTest(fileName); + } + @TestMetadata("extension.kt") public void testExtension() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extension.kt");