diff --git a/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRenderer.kt b/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRenderer.kt index f26365b45f6..b292944e559 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRenderer.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRenderer.kt @@ -171,6 +171,7 @@ interface DescriptorRendererOptions { var withDefinedIn: Boolean var modifiers: Set var startFromName: Boolean + var startFromDeclarationKeyword: Boolean var debugMode: Boolean var classWithPrimaryConstructor: Boolean var verbose: Boolean diff --git a/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt index e6607b1ad66..8e851b0a7d4 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt @@ -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*/ ") + } } } diff --git a/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererOptionsImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererOptionsImpl.kt index ca4d529be27..b8d7f91ab27 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererOptionsImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererOptionsImpl.kt @@ -68,6 +68,7 @@ internal class DescriptorRendererOptionsImpl : DescriptorRendererOptions { override var withDefinedIn by property(true) override var modifiers: Set 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) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index f2c5322dfb7..9b8f50208c1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -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) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ReplaceProtectedToPublishedApiCallFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ReplaceProtectedToPublishedApiCallFix.kt new file mode 100644 index 00000000000..b1e2caa2578 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ReplaceProtectedToPublishedApiCallFix.kt @@ -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, + val originalName: String, + val paramNames: Map, + val signature: String +) : KotlinQuickFixAction(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" + } +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extension.kt b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extension.kt new file mode 100644 index 00000000000..f7c197ab427 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extension.kt @@ -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".test(1) + }() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extension.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extension.kt.after new file mode 100644 index 00000000000..cd5f7587886 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/extension.kt.after @@ -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) +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/generic.kt b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/generic.kt new file mode 100644 index 00000000000..551451d8119 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/generic.kt @@ -0,0 +1,18 @@ +// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true" +annotation class Z + +open class ABase { + @Z + protected fun test(p: T) { + } + + fun param(): T { + return null!! + } + + inline fun test() { + { + test(param()) + }() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/generic.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/generic.kt.after new file mode 100644 index 00000000000..ff1c42dd419 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/generic.kt.after @@ -0,0 +1,21 @@ +// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true" +annotation class Z + +open class ABase { + @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) +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericDerived.kt b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericDerived.kt new file mode 100644 index 00000000000..651db5f8390 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericDerived.kt @@ -0,0 +1,19 @@ +// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true" +open class ABase { + protected fun test(p: T) { + } + + fun param(): T { + return null!! + } + +} + +open class A : ABase() { + + inline fun test() { + { + test(param()) + }() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericDerived.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericDerived.kt.after new file mode 100644 index 00000000000..58e5ee093d1 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericDerived.kt.after @@ -0,0 +1,22 @@ +// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true" +open class ABase { + protected fun test(p: T) { + } + + fun param(): T { + return null!! + } + +} + +open class A : ABase() { + + inline fun test() { + { + `access$test`(param()) + }() + } + + @PublishedApi + internal fun `access$test`(p: String) = test(p) +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericFunction.kt b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericFunction.kt new file mode 100644 index 00000000000..ece5ab0dbf9 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericFunction.kt @@ -0,0 +1,17 @@ +// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true" +annotation class Z + +open class ABase { + @Z + protected fun test(p: T): T { + null!! + } + + + inline fun test() { + { + //TODO remove generic + test("123") + }() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericFunction.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericFunction.kt.after new file mode 100644 index 00000000000..971ac8c8187 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/genericFunction.kt.after @@ -0,0 +1,20 @@ +// "Replace with generated @PublishedApi bridge call '`access$test`(...)'" "true" +annotation class Z + +open class ABase { + @Z + protected fun test(p: T): T { + null!! + } + + + inline fun test() { + { + //TODO remove generic + `access$test`("123") + }() + } + + @PublishedApi + internal fun `access$test`(p: String) = test(p) +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/simple.kt b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/simple.kt new file mode 100644 index 00000000000..7a9c7e66787 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/simple.kt @@ -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() { + { + test(1) + }() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/simple.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/simple.kt.after new file mode 100644 index 00000000000..703e0a677fb --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/publishedApi/simple.kt.after @@ -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) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 8da1d8756ee..50e908922b3 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -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)