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 modifiers: Set<DescriptorRendererModifier>
var startFromName: Boolean
var startFromDeclarationKeyword: Boolean
var debugMode: Boolean
var classWithPrimaryConstructor: Boolean
var verbose: Boolean
@@ -643,24 +643,26 @@ internal class DescriptorRendererImpl(
/* FUNCTIONS */
private fun renderFunction(function: FunctionDescriptor, builder: StringBuilder) {
if (!startFromName) {
builder.renderAnnotations(function)
renderVisibility(function.visibility, builder)
renderModalityForCallable(function, builder)
if (!startFromDeclarationKeyword) {
builder.renderAnnotations(function)
renderVisibility(function.visibility, builder)
renderModalityForCallable(function, builder)
if (includeAdditionalModifiers) {
renderAdditionalModifiers(function, builder)
}
renderOverride(function, builder)
renderMemberKind(function, builder)
if (verbose) {
if (function.isHiddenToOvercomeSignatureClash) {
builder.append("/*isHiddenToOvercomeSignatureClash*/ ")
if (includeAdditionalModifiers) {
renderAdditionalModifiers(function, builder)
}
if (function.isHiddenForResolutionEverywhereBesideSupercalls) {
builder.append("/*isHiddenForResolutionEverywhereBesideSupercalls*/ ")
renderOverride(function, builder)
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 modifiers: Set<DescriptorRendererModifier> by property(DescriptorRendererModifier.DEFAULTS)
override var startFromName by property(false)
override var startFromDeclarationKeyword by property(false)
override var debugMode by property(false)
override var classWithPrimaryConstructor 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.replaceWith.DeprecatedSymbolUsageFix
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.psi.KtClass
@@ -379,6 +380,7 @@ class QuickFixRegistrar : QuickFixContributor {
DEPRECATION.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix)
DEPRECATION_ERROR.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix)
PROTECTED_CALL_FROM_PUBLIC_INLINE.registerFactory(ReplaceProtectedToPublishedApiCallFix)
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")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)