Adedd quickfix check if @PublishedApi stub already exists
This commit is contained in:
+32
-10
@@ -29,11 +29,14 @@ import org.jetbrains.kotlin.diagnostics.Errors
|
|||||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||||
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||||
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
|
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.idea.util.IdeDescriptorRenderers
|
||||||
import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
|
import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
|
||||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||||
|
|
||||||
class ReplaceProtectedToPublishedApiCallFix(
|
class ReplaceProtectedToPublishedApiCallFix(
|
||||||
@@ -41,27 +44,25 @@ class ReplaceProtectedToPublishedApiCallFix(
|
|||||||
val classOwnerPointer: SmartPsiElementPointer<KtClass>,
|
val classOwnerPointer: SmartPsiElementPointer<KtClass>,
|
||||||
val originalName: String,
|
val originalName: String,
|
||||||
val paramNames: Map<String, String>,
|
val paramNames: Map<String, String>,
|
||||||
val signature: String,
|
val newSignature: String,
|
||||||
val isProperty: Boolean,
|
val isProperty: Boolean,
|
||||||
val isVar: Boolean
|
val isVar: Boolean,
|
||||||
|
val isPublishedMemberAlreadyExists: Boolean
|
||||||
) : KotlinQuickFixAction<KtExpression>(element) {
|
) : KotlinQuickFixAction<KtExpression>(element) {
|
||||||
|
|
||||||
override fun getFamilyName() = "Replace with @PublishedApi bridge call"
|
override fun getFamilyName() = "Replace with @PublishedApi bridge call"
|
||||||
|
|
||||||
val newName = "`${originalName.newName}`"
|
override fun getText() = "Replace with generated @PublishedApi bridge call '${originalName.newNameQuoted}${if (!isProperty) "(...)" else ""}'"
|
||||||
|
|
||||||
override fun getText() = "Replace with generated @PublishedApi bridge call '$newName${if (!isProperty) "(...)" else ""}'"
|
|
||||||
|
|
||||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||||
val element = element ?: return
|
val element = element ?: return
|
||||||
val isPublishedMemberAlreadyExists = false/*TODO*/
|
|
||||||
if (!isPublishedMemberAlreadyExists) {
|
if (!isPublishedMemberAlreadyExists) {
|
||||||
val classOwner = classOwnerPointer.element ?: return
|
val classOwner = classOwnerPointer.element ?: return
|
||||||
val newMember: KtDeclaration =
|
val newMember: KtDeclaration =
|
||||||
if (isProperty) {
|
if (isProperty) {
|
||||||
KtPsiFactory(classOwner).createProperty(
|
KtPsiFactory(classOwner).createProperty(
|
||||||
"@kotlin.PublishedApi\n" +
|
"@kotlin.PublishedApi\n" +
|
||||||
"internal " + signature.replaceFirst("$originalName:", "$newName:") +
|
"internal " + newSignature +
|
||||||
"\n" +
|
"\n" +
|
||||||
"get() = $originalName\n" +
|
"get() = $originalName\n" +
|
||||||
if (isVar) "set(value) { $originalName = value }" else ""
|
if (isVar) "set(value) { $originalName = value }" else ""
|
||||||
@@ -71,14 +72,14 @@ class ReplaceProtectedToPublishedApiCallFix(
|
|||||||
else {
|
else {
|
||||||
KtPsiFactory(classOwner).createFunction(
|
KtPsiFactory(classOwner).createFunction(
|
||||||
"@kotlin.PublishedApi\n" +
|
"@kotlin.PublishedApi\n" +
|
||||||
"internal " + signature.replaceFirst("$originalName(", "$newName(") +
|
"internal " + newSignature +
|
||||||
" = $originalName(${paramNames.keys.map { it }.joinToString(", ")})"
|
" = $originalName(${paramNames.keys.map { it }.joinToString(", ")})"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ShortenReferences.DEFAULT.process(classOwner.addDeclaration(newMember))
|
ShortenReferences.DEFAULT.process(classOwner.addDeclaration(newMember))
|
||||||
}
|
}
|
||||||
element.replace(KtPsiFactory(element).createExpression(newName))
|
element.replace(KtPsiFactory(element).createExpression(originalName.newNameQuoted))
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : KotlinSingleIntentionActionFactory() {
|
companion object : KotlinSingleIntentionActionFactory() {
|
||||||
@@ -97,15 +98,36 @@ class ReplaceProtectedToPublishedApiCallFix(
|
|||||||
val isVar = descriptor is PropertyDescriptor && descriptor.isVar
|
val isVar = descriptor is PropertyDescriptor && descriptor.isVar
|
||||||
|
|
||||||
val signature = signatureRenderer.render(descriptor)
|
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 paramNameAndType = descriptor.valueParameters.associate { it.name.asString() to it.type.getJetTypeFqName(false)}
|
||||||
val classDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return null
|
val classDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return null
|
||||||
val source = classDescriptor.source.getPsi() as? KtClass ?: 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<CallableMemberDescriptor>().any {
|
||||||
|
signatureRenderer.render(it) == newSignature
|
||||||
|
}
|
||||||
|
|
||||||
return ReplaceProtectedToPublishedApiCallFix(
|
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
|
val String.newName: String
|
||||||
get() = "access\$$this"
|
get() = "access\$$this"
|
||||||
|
|
||||||
|
val String.newNameQuoted: String
|
||||||
|
get() = "`$newName`"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true"
|
||||||
|
|
||||||
|
open class ABase {
|
||||||
|
protected fun test(p: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline fun test() {
|
||||||
|
{
|
||||||
|
<caret>test(1)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal fun `access$test`(p: Int) = test(p)
|
||||||
|
}
|
||||||
+16
@@ -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)
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
// "Replace with generated @PublishedApi bridge call '`access$prop`'" "true"
|
||||||
|
|
||||||
|
open class ABase {
|
||||||
|
protected var prop = 1
|
||||||
|
|
||||||
|
inline fun test() {
|
||||||
|
{
|
||||||
|
<caret>prop
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal var `access$prop`: Int
|
||||||
|
get() = prop
|
||||||
|
set(value) {
|
||||||
|
prop = value
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ open class ABase {
|
|||||||
@Z
|
@Z
|
||||||
protected var String.prop: Int
|
protected var String.prop: Int
|
||||||
get() = 1
|
get() = 1
|
||||||
set(field) = {}
|
set(field) {}
|
||||||
|
|
||||||
|
|
||||||
inline fun test() {
|
inline fun test() {
|
||||||
|
|||||||
+2
-2
@@ -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
|
annotation class Z
|
||||||
|
|
||||||
open class ABase {
|
open class ABase {
|
||||||
@Z
|
@Z
|
||||||
protected var String.prop: Int
|
protected var String.prop: Int
|
||||||
get() = 1
|
get() = 1
|
||||||
set(field) = {}
|
set(field) {}
|
||||||
|
|
||||||
|
|
||||||
inline fun test() {
|
inline fun test() {
|
||||||
|
|||||||
@@ -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);
|
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")
|
@TestMetadata("extension.kt")
|
||||||
public void testExtension() throws Exception {
|
public void testExtension() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extension.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extension.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user