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.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<KtClass>,
val originalName: String,
val paramNames: Map<String, String>,
val signature: String,
val newSignature: String,
val isProperty: Boolean,
val isVar: Boolean
val isVar: Boolean,
val isPublishedMemberAlreadyExists: Boolean
) : KotlinQuickFixAction<KtExpression>(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<CallableMemberDescriptor>().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`"
}
}
@@ -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
protected var String.prop: Int
get() = 1
set(field) = {}
set(field) {}
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
open class ABase {
@Z
protected var String.prop: Int
get() = 1
set(field) = {}
set(field) {}
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);
}
@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");