Decrease visibility fix is now active for exposed visibility errors #KT-11920 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
934c374030
commit
d5b9a336a3
@@ -21,6 +21,10 @@ import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
@@ -166,6 +170,20 @@ fun KtClass.getOrCreateCompanionObject() : KtObjectDeclaration {
|
||||
return addDeclaration(KtPsiFactory(this).createCompanionObject())
|
||||
}
|
||||
|
||||
fun KtDeclaration.toDescriptor(): DeclarationDescriptor? {
|
||||
val bindingContext = analyze()
|
||||
// TODO: temporary code
|
||||
if (this is KtPrimaryConstructor) {
|
||||
return (this.getContainingClassOrObject().resolveToDescriptor() as ClassDescriptor).unsubstitutedPrimaryConstructor
|
||||
}
|
||||
|
||||
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, this]
|
||||
if (descriptor is ValueParameterDescriptor) {
|
||||
return bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, descriptor]
|
||||
}
|
||||
return descriptor
|
||||
}
|
||||
|
||||
//TODO: code style option whether to insert redundant 'public' keyword or not
|
||||
fun KtModifierListOwner.setVisibility(visibilityModifier: KtModifierKeywordToken) {
|
||||
if (this is KtDeclaration) {
|
||||
|
||||
@@ -20,17 +20,15 @@ import com.intellij.codeInsight.intention.HighPriorityAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.canBePrivate
|
||||
import org.jetbrains.kotlin.idea.core.canBeProtected
|
||||
import org.jetbrains.kotlin.idea.core.setVisibility
|
||||
import org.jetbrains.kotlin.idea.core.toDescriptor
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifier
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
|
||||
open class ChangeVisibilityModifierIntention protected constructor(
|
||||
val modifier: KtModifierKeywordToken
|
||||
@@ -71,20 +69,6 @@ open class ChangeVisibilityModifierIntention protected constructor(
|
||||
defaultRange
|
||||
}
|
||||
|
||||
private fun KtDeclaration.toDescriptor(): DeclarationDescriptor? {
|
||||
val bindingContext = analyze()
|
||||
// TODO: temporary code
|
||||
if (this is KtPrimaryConstructor) {
|
||||
return (this.getContainingClassOrObject().resolveToDescriptor() as ClassDescriptor).unsubstitutedPrimaryConstructor
|
||||
}
|
||||
|
||||
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, this]
|
||||
if (descriptor is ValueParameterDescriptor) {
|
||||
return bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, descriptor]
|
||||
}
|
||||
return descriptor
|
||||
}
|
||||
|
||||
override fun applyTo(element: KtDeclaration, editor: Editor?) {
|
||||
element.setVisibility(modifier)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.Visibility
|
||||
import org.jetbrains.kotlin.idea.core.canBePrivate
|
||||
import org.jetbrains.kotlin.idea.core.canBeProtected
|
||||
import org.jetbrains.kotlin.idea.core.setVisibility
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
@@ -58,6 +59,13 @@ open class ChangeVisibilityFix(
|
||||
protected class ChangeToInternalFix(element: KtModifierListOwner, elementName: String) :
|
||||
ChangeVisibilityFix(element, elementName, KtTokens.INTERNAL_KEYWORD)
|
||||
|
||||
protected class ChangeToPrivateFix(element: KtModifierListOwner, elementName: String) :
|
||||
ChangeVisibilityFix(element, elementName, KtTokens.PRIVATE_KEYWORD) {
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile) =
|
||||
super.isAvailable(project, editor, file) && element.canBePrivate()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(
|
||||
declaration: KtModifierListOwner,
|
||||
@@ -69,6 +77,7 @@ open class ChangeVisibilityFix(
|
||||
val name = descriptor.name.asString()
|
||||
|
||||
return when (targetVisibility) {
|
||||
Visibilities.PRIVATE -> ChangeToPrivateFix(declaration, name)
|
||||
Visibilities.INTERNAL -> ChangeToInternalFix(declaration, name)
|
||||
Visibilities.PROTECTED -> ChangeToProtectedFix(declaration, name)
|
||||
Visibilities.PUBLIC -> ChangeToPublicFix(declaration, name)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorWithRelation
|
||||
import org.jetbrains.kotlin.descriptors.EffectiveVisibility
|
||||
import org.jetbrains.kotlin.descriptors.EffectiveVisibility.Permissiveness.LESS
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities.*
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory3
|
||||
import org.jetbrains.kotlin.idea.core.toDescriptor
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtModifierListOwner
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
|
||||
object DecreaseExposingVisibilityFactory : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val factory = diagnostic.factory as DiagnosticFactory3<*, EffectiveVisibility, DescriptorWithRelation, EffectiveVisibility>
|
||||
val exposedDiagnostic = factory.cast(diagnostic)
|
||||
val exposedDescriptor = exposedDiagnostic.b.descriptor as? DeclarationDescriptorWithVisibility ?: return null
|
||||
val exposedDeclaration = DescriptorToSourceUtils.getSourceFromDescriptor(exposedDescriptor) as? KtModifierListOwner ?: return null
|
||||
val exposedVisibility = exposedDiagnostic.c
|
||||
val exposingVisibility = exposedDiagnostic.a
|
||||
val boundVisibility = when (exposedVisibility.relation(exposingVisibility)) {
|
||||
LESS -> exposedVisibility.toVisibility()
|
||||
else -> PRIVATE
|
||||
}
|
||||
val exposingDeclaration = diagnostic.psiElement.getParentOfType<KtDeclaration>(true) ?: return null
|
||||
val targetVisibility = when (boundVisibility) {
|
||||
PUBLIC -> return null
|
||||
PROTECTED -> if (exposedDeclaration.parent == exposingDeclaration.parent) PROTECTED else PRIVATE
|
||||
else -> boundVisibility
|
||||
}
|
||||
val exposingDescriptor = exposingDeclaration.toDescriptor() as? DeclarationDescriptorWithVisibility ?: return null
|
||||
if (!Visibilities.isVisibleIgnoringReceiver(exposedDescriptor, exposingDescriptor)) return null
|
||||
return ChangeVisibilityFix.create(exposingDeclaration, exposingDescriptor, targetVisibility)
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,7 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorWithRelation
|
||||
import org.jetbrains.kotlin.descriptors.EffectiveVisibility
|
||||
import org.jetbrains.kotlin.descriptors.EffectiveVisibility.Permissiveness.LESS
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities.PROTECTED
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities.PUBLIC
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities.*
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory3
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
@@ -45,6 +44,7 @@ object IncreaseExposedVisibilityFactory : KotlinSingleIntentionActionFactory() {
|
||||
}
|
||||
val exposingDeclaration = diagnostic.psiElement.getParentOfType<KtDeclaration>(true)
|
||||
val targetVisibility = when (boundVisibility) {
|
||||
PRIVATE -> return null
|
||||
PROTECTED -> if (exposedDeclaration.parent == exposingDeclaration?.parent) PROTECTED else PUBLIC
|
||||
else -> boundVisibility
|
||||
}
|
||||
|
||||
@@ -153,13 +153,10 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
INVISIBLE_MEMBER.registerFactory(MakeVisibleFactory)
|
||||
INVISIBLE_SETTER.registerFactory(MakeVisibleFactory)
|
||||
|
||||
EXPOSED_FUNCTION_RETURN_TYPE.registerFactory(IncreaseExposedVisibilityFactory)
|
||||
EXPOSED_PARAMETER_TYPE.registerFactory(IncreaseExposedVisibilityFactory)
|
||||
EXPOSED_PROPERTY_TYPE.registerFactory(IncreaseExposedVisibilityFactory)
|
||||
EXPOSED_RECEIVER_TYPE.registerFactory(IncreaseExposedVisibilityFactory)
|
||||
EXPOSED_SUPER_CLASS.registerFactory(IncreaseExposedVisibilityFactory)
|
||||
EXPOSED_SUPER_INTERFACE.registerFactory(IncreaseExposedVisibilityFactory)
|
||||
EXPOSED_TYPE_PARAMETER_BOUND.registerFactory(IncreaseExposedVisibilityFactory)
|
||||
for (exposed in listOf(EXPOSED_FUNCTION_RETURN_TYPE, EXPOSED_PARAMETER_TYPE, EXPOSED_PROPERTY_TYPE, EXPOSED_RECEIVER_TYPE,
|
||||
EXPOSED_SUPER_CLASS, EXPOSED_SUPER_INTERFACE, EXPOSED_TYPE_PARAMETER_BOUND)) {
|
||||
exposed.registerFactory(IncreaseExposedVisibilityFactory, DecreaseExposingVisibilityFactory)
|
||||
}
|
||||
|
||||
REDUNDANT_NULLABLE.registerFactory(RemoveNullableFix.Factory(RemoveNullableFix.NullableKind.REDUNDANT))
|
||||
NULLABLE_SUPERTYPE.registerFactory(RemoveNullableFix.Factory(RemoveNullableFix.NullableKind.SUPERTYPE))
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// "Make foo private" "false"
|
||||
// ACTION: Convert parameter to receiver
|
||||
// ACTION: Make Nested internal
|
||||
// ACTION: Remove parameter 'arg'
|
||||
// ERROR: 'internal' function exposes its 'private' parameter type argument Nested
|
||||
// ERROR: Cannot access 'Nested': it is 'private' in 'Outer'
|
||||
|
||||
class Outer {
|
||||
private class Nested
|
||||
}
|
||||
|
||||
class Generic<T>
|
||||
|
||||
internal fun foo(<caret>arg: Generic<Outer.Nested>) {}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Make foo private" "true"
|
||||
|
||||
private data class Data(val x: Int)
|
||||
|
||||
class First {
|
||||
val <caret>foo = Data(13)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Make foo private" "true"
|
||||
|
||||
private data class Data(val x: Int)
|
||||
|
||||
class First {
|
||||
private val foo = Data(13)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Make foo private" "false"
|
||||
// ACTION: Convert receiver to parameter
|
||||
// ACTION: Make Private protected
|
||||
// ERROR: 'protected (in My)' member exposes its 'private' receiver type argument Private
|
||||
|
||||
class Receiver<T>
|
||||
|
||||
abstract class My {
|
||||
private class Private
|
||||
// abstract never can be private
|
||||
abstract protected fun <caret>Receiver<Private>.foo()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Make bar private" "true"
|
||||
|
||||
private data class Data(val x: Int)
|
||||
|
||||
class First {
|
||||
internal fun <caret>bar(x: Int) = Data(x)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Make bar private" "true"
|
||||
|
||||
private data class Data(val x: Int)
|
||||
|
||||
class First {
|
||||
private fun bar(x: Int) = Data(x)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Make First private" "true"
|
||||
|
||||
class Outer {
|
||||
private open class Data(val x: Int)
|
||||
|
||||
protected class First : <caret>Data(42)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Make First private" "true"
|
||||
|
||||
class Outer {
|
||||
private open class Data(val x: Int)
|
||||
|
||||
private class First : Data(42)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Make First private" "true"
|
||||
|
||||
private open class Data(val x: Int)
|
||||
|
||||
class Outer {
|
||||
protected class First : <caret>Data(42)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Make First private" "true"
|
||||
|
||||
private open class Data(val x: Int)
|
||||
|
||||
class Outer {
|
||||
private class First : Data(42)
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Make First private" "true"
|
||||
|
||||
class Other {
|
||||
internal open class Data(val x: Int)
|
||||
}
|
||||
|
||||
class Another {
|
||||
protected class First : Other.<caret>Data(42)
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Make First private" "true"
|
||||
|
||||
class Other {
|
||||
internal open class Data(val x: Int)
|
||||
}
|
||||
|
||||
class Another {
|
||||
private class First : Other.Data(42)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Make Derived internal" "true"
|
||||
|
||||
import Outer.Base
|
||||
|
||||
internal class Outer {
|
||||
interface Base
|
||||
}
|
||||
|
||||
class Container {
|
||||
interface Derived : <caret>Base
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Make Derived internal" "true"
|
||||
|
||||
import Outer.Base
|
||||
|
||||
internal class Outer {
|
||||
interface Base
|
||||
}
|
||||
|
||||
class Container {
|
||||
internal interface Derived : Base
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Make User internal" "true"
|
||||
|
||||
internal open class InternalString
|
||||
|
||||
class User<T : <caret>User<T, InternalString>, R>
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Make User internal" "true"
|
||||
|
||||
internal open class InternalString
|
||||
|
||||
internal class User<T : User<T, InternalString>, R>
|
||||
@@ -3492,6 +3492,69 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/decreaseVisibility")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class DecreaseVisibility extends AbstractQuickFixTest {
|
||||
public void testAllFilesPresentInDecreaseVisibility() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/decreaseVisibility"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("exposedParameterType.kt")
|
||||
public void testExposedParameterType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/decreaseVisibility/exposedParameterType.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("exposedPropertyType.kt")
|
||||
public void testExposedPropertyType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/decreaseVisibility/exposedPropertyType.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("exposedReceiverType.kt")
|
||||
public void testExposedReceiverType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/decreaseVisibility/exposedReceiverType.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("exposedReturnType.kt")
|
||||
public void testExposedReturnType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/decreaseVisibility/exposedReturnType.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("exposedSuperClass.kt")
|
||||
public void testExposedSuperClass() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/decreaseVisibility/exposedSuperClass.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("exposedSuperClassProtectedBase.kt")
|
||||
public void testExposedSuperClassProtectedBase() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/decreaseVisibility/exposedSuperClassProtectedBase.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("exposedSuperClassProtectedInAnother.kt")
|
||||
public void testExposedSuperClassProtectedInAnother() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/decreaseVisibility/exposedSuperClassProtectedInAnother.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("exposedSuperInterface.kt")
|
||||
public void testExposedSuperInterface() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/decreaseVisibility/exposedSuperInterface.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("exposedTypeParameterBound.kt")
|
||||
public void testExposedTypeParameterBound() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/decreaseVisibility/exposedTypeParameterBound.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user