Adedd quickfix check if @PublishedApi stub already exists

This commit is contained in:
Mikhael Bogdanov
2016-12-19 16:51:24 +01:00
parent c38b55612b
commit 5222768ba1
8 changed files with 115 additions and 13 deletions
@@ -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)
}
@@ -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)
}
@@ -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
}
}
@@ -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() {
@@ -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");