Quickfix for published api

This commit is contained in:
Mikhael Bogdanov
2016-12-07 12:44:48 +01:00
parent f4259c5f82
commit 5ffc0b36b0
16 changed files with 340 additions and 15 deletions
@@ -171,6 +171,7 @@ interface DescriptorRendererOptions {
var withDefinedIn: Boolean var withDefinedIn: Boolean
var modifiers: Set<DescriptorRendererModifier> var modifiers: Set<DescriptorRendererModifier>
var startFromName: Boolean var startFromName: Boolean
var startFromDeclarationKeyword: Boolean
var debugMode: Boolean var debugMode: Boolean
var classWithPrimaryConstructor: Boolean var classWithPrimaryConstructor: Boolean
var verbose: Boolean var verbose: Boolean
@@ -643,24 +643,26 @@ internal class DescriptorRendererImpl(
/* FUNCTIONS */ /* FUNCTIONS */
private fun renderFunction(function: FunctionDescriptor, builder: StringBuilder) { private fun renderFunction(function: FunctionDescriptor, builder: StringBuilder) {
if (!startFromName) { if (!startFromName) {
builder.renderAnnotations(function) if (!startFromDeclarationKeyword) {
renderVisibility(function.visibility, builder) builder.renderAnnotations(function)
renderModalityForCallable(function, builder) renderVisibility(function.visibility, builder)
renderModalityForCallable(function, builder)
if (includeAdditionalModifiers) { if (includeAdditionalModifiers) {
renderAdditionalModifiers(function, builder) renderAdditionalModifiers(function, builder)
}
renderOverride(function, builder)
renderMemberKind(function, builder)
if (verbose) {
if (function.isHiddenToOvercomeSignatureClash) {
builder.append("/*isHiddenToOvercomeSignatureClash*/ ")
} }
if (function.isHiddenForResolutionEverywhereBesideSupercalls) { renderOverride(function, builder)
builder.append("/*isHiddenForResolutionEverywhereBesideSupercalls*/ ") renderMemberKind(function, builder)
if (verbose) {
if (function.isHiddenToOvercomeSignatureClash) {
builder.append("/*isHiddenToOvercomeSignatureClash*/ ")
}
if (function.isHiddenForResolutionEverywhereBesideSupercalls) {
builder.append("/*isHiddenForResolutionEverywhereBesideSupercalls*/ ")
}
} }
} }
@@ -68,6 +68,7 @@ internal class DescriptorRendererOptionsImpl : DescriptorRendererOptions {
override var withDefinedIn by property(true) override var withDefinedIn by property(true)
override var modifiers: Set<DescriptorRendererModifier> by property(DescriptorRendererModifier.DEFAULTS) override var modifiers: Set<DescriptorRendererModifier> by property(DescriptorRendererModifier.DEFAULTS)
override var startFromName by property(false) override var startFromName by property(false)
override var startFromDeclarationKeyword by property(false)
override var debugMode by property(false) override var debugMode by property(false)
override var classWithPrimaryConstructor by property(false) override var classWithPrimaryConstructor by property(false)
override var verbose by property(false) override var verbose by property(false)
@@ -37,6 +37,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateP
import org.jetbrains.kotlin.idea.quickfix.migration.MigrateTypeParameterListFix import org.jetbrains.kotlin.idea.quickfix.migration.MigrateTypeParameterListFix
import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageFix import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageFix
import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageInWholeProjectFix import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageInWholeProjectFix
import org.jetbrains.kotlin.idea.quickfix.replaceWith.ReplaceProtectedToPublishedApiCallFix
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.lexer.KtTokens.* import org.jetbrains.kotlin.lexer.KtTokens.*
import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClass
@@ -379,6 +380,7 @@ class QuickFixRegistrar : QuickFixContributor {
DEPRECATION.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix) DEPRECATION.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix)
DEPRECATION_ERROR.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix) DEPRECATION_ERROR.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix)
PROTECTED_CALL_FROM_PUBLIC_INLINE.registerFactory(ReplaceProtectedToPublishedApiCallFix)
POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION.registerFactory(ReplaceJavaAnnotationPositionedArgumentsFix) POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION.registerFactory(ReplaceJavaAnnotationPositionedArgumentsFix)
@@ -0,0 +1,97 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.quickfix.replaceWith
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.SmartPsiElementPointer
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
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.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.resolve.source.getPsi
class ReplaceProtectedToPublishedApiCallFix(
element: KtExpression,
val classOwnerPointer: SmartPsiElementPointer<KtClass>,
val originalName: String,
val paramNames: Map<String, String>,
val signature: String
) : KotlinQuickFixAction<KtExpression>(element) {
override fun getFamilyName() = "Replace with @PublishedApi bridge call"
val newFunctionName = "`${originalName.newName}`"
override fun getText() = "Replace with generated @PublishedApi bridge call '$newFunctionName(...)'"
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return
val isPublishedFunctionAlreadyExists = false/*TODO*/
if (!isPublishedFunctionAlreadyExists) {
val classOwner = classOwnerPointer.element ?: return
val function = KtPsiFactory(classOwner).createFunction(
"@kotlin.PublishedApi\n" +
"internal "+//${extensionType?.let { it + "." } ?: ""}$newFunctionName(${paramNames.entries.map { it.key + " :" + it.value }.joinToString(", ")}) = " +
signature.replaceFirst("$originalName(", "$newFunctionName(") +
" = $originalName(${paramNames.keys.map { it }.joinToString(", ")})"
)
val newFunction = classOwner.addDeclaration(function)
ShortenReferences.DEFAULT.process(newFunction)
}
element.replace(KtPsiFactory(element).createExpression(newFunctionName))
}
companion object : KotlinSingleIntentionActionFactory() {
val signatureRenderer = IdeDescriptorRenderers.SOURCE_CODE.withOptions {
renderDefaultValues = false
startFromDeclarationKeyword = true
withoutReturnType = true
}
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val psiElement = diagnostic.psiElement as? KtExpression ?: return null
val descriptor = DiagnosticFactory.cast(diagnostic, Errors.PROTECTED_CALL_FROM_PUBLIC_INLINE).a
val isProperty = descriptor is PropertyAccessorDescriptor || descriptor is PropertyDescriptor
if (isProperty) return null/*TODO support properties*/
val signature = signatureRenderer.render(descriptor)
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
return ReplaceProtectedToPublishedApiCallFix(psiElement, source.createSmartPointer(), descriptor.name.asString(), paramNameAndType, signature)
}
val String.newName: String
get() = "access\$$this"
}
}
@@ -0,0 +1,15 @@
// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true"
annotation class Z
open class ABase {
@Z
protected fun String.test(p: Int) {
}
inline fun test() {
{
"123".<caret>test(1)
}()
}
}
@@ -0,0 +1,18 @@
// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true"
annotation class Z
open class ABase {
@Z
protected fun String.test(p: Int) {
}
inline fun test() {
{
"123".`access$test`(1)
}()
}
@PublishedApi
internal fun String.`access$test`(p: Int) = test(p)
}
@@ -0,0 +1,18 @@
// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true"
annotation class Z
open class ABase<T> {
@Z
protected fun test(p: T) {
}
fun param(): T {
return null!!
}
inline fun test() {
{
<caret>test(param())
}()
}
}
@@ -0,0 +1,21 @@
// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true"
annotation class Z
open class ABase<T> {
@Z
protected fun test(p: T) {
}
fun param(): T {
return null!!
}
inline fun test() {
{
`access$test`(param())
}()
}
@PublishedApi
internal fun `access$test`(p: T) = test(p)
}
@@ -0,0 +1,19 @@
// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true"
open class ABase<T> {
protected fun test(p: T) {
}
fun param(): T {
return null!!
}
}
open class A : ABase<String>() {
inline fun test() {
{
<caret>test(param())
}()
}
}
@@ -0,0 +1,22 @@
// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true"
open class ABase<T> {
protected fun test(p: T) {
}
fun param(): T {
return null!!
}
}
open class A : ABase<String>() {
inline fun test() {
{
`access$test`(param())
}()
}
@PublishedApi
internal fun `access$test`(p: String) = test(p)
}
@@ -0,0 +1,17 @@
// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true"
annotation class Z
open class ABase {
@Z
protected fun <T> test(p: T): T {
null!!
}
inline fun test() {
{
//TODO remove generic
<caret>test<String>("123")
}()
}
}
@@ -0,0 +1,20 @@
// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true"
annotation class Z
open class ABase {
@Z
protected fun <T> test(p: T): T {
null!!
}
inline fun test() {
{
//TODO remove generic
`access$test`<String>("123")
}()
}
@PublishedApi
internal fun <T> `access$test`(p: String) = test(p)
}
@@ -0,0 +1,15 @@
// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true"
annotation class Z
open class ABase {
@Z
protected fun test(p: Int) {
}
inline fun test() {
{
<caret>test(1)
}()
}
}
@@ -0,0 +1,18 @@
// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true"
annotation class Z
open class ABase {
@Z
protected fun test(p: Int) {
}
inline fun test() {
{
`access$test`(1)
}()
}
@PublishedApi
internal fun `access$test`(p: Int) = test(p)
}
@@ -4855,6 +4855,45 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
} }
} }
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class PublishedApi extends AbstractQuickFixTest {
public void testAllFilesPresentInPublishedApi() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("extension.kt")
public void testExtension() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extension.kt");
doTest(fileName);
}
@TestMetadata("generic.kt")
public void testGeneric() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/generic.kt");
doTest(fileName);
}
@TestMetadata("genericDerived.kt")
public void testGenericDerived() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericDerived.kt");
doTest(fileName);
}
@TestMetadata("genericFunction.kt")
public void testGenericFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericFunction.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/simple.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/safeCall") @TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/safeCall")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)