diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt b/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt index 158424c8d73..d6e64f5f87d 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt @@ -430,6 +430,8 @@ val KtModifierListOwner.isPublic: Boolean fun KtModifierListOwner.visibilityModifierType(): KtModifierKeywordToken? = visibilityModifier()?.node?.elementType as KtModifierKeywordToken? +fun KtModifierListOwner.visibilityModifierTypeOrDefault(): KtModifierKeywordToken = visibilityModifierType() ?: KtTokens.DEFAULT_VISIBILITY_KEYWORD + fun KtDeclaration.modalityModifier() = modifierFromTokenSet(MODALITY_MODIFIERS) fun KtStringTemplateExpression.isPlain() = entries.all { it is KtLiteralStringTemplateEntry } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt index 51e94996f75..3746e41d1e3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt @@ -65,10 +65,8 @@ class KotlinExtractInterfaceDialog( val member = memberInfo.member return !(member.hasModifier(KtTokens.INLINE_KEYWORD) || - member.hasModifier(KtTokens.EXTERNAL_KEYWORD) || - member.hasModifier(KtTokens.LATEINIT_KEYWORD) || - member.hasModifier(KtTokens.INTERNAL_KEYWORD) || - member.hasModifier(KtTokens.PROTECTED_KEYWORD)) + member.hasModifier(KtTokens.EXTERNAL_KEYWORD) || + member.hasModifier(KtTokens.LATEINIT_KEYWORD)) } override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt index bd35d9f96de..59e691fd667 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt @@ -64,6 +64,11 @@ class KotlinPullUpHelper( private val javaData: PullUpData, private val data: KotlinPullUpData ) : PullUpHelper> { + companion object { + private val MODIFIERS_TO_LIFT_IN_SUPERCLASS = listOf(KtTokens.PRIVATE_KEYWORD) + private val MODIFIERS_TO_LIFT_IN_INTERFACE = listOf(KtTokens.PRIVATE_KEYWORD, KtTokens.PROTECTED_KEYWORD, KtTokens.INTERNAL_KEYWORD) + } + private fun KtExpression.isMovable(): Boolean { return accept( object : KtVisitor() { @@ -271,13 +276,18 @@ class KotlinPullUpHelper( } } - private fun liftToProtected(declaration: KtNamedDeclaration, ignoreUsages: Boolean = false) { - if (!declaration.hasModifier(KtTokens.PRIVATE_KEYWORD)) return - if (ignoreUsages || willBeUsedInSourceClass( - declaration, - data.sourceClass, - data.membersToMove - )) declaration.addModifier(KtTokens.PROTECTED_KEYWORD) + private fun liftVisibility(declaration: KtNamedDeclaration, ignoreUsages: Boolean = false) { + val newModifier = if (data.isInterfaceTarget) KtTokens.PUBLIC_KEYWORD else KtTokens.PROTECTED_KEYWORD + val modifiersToLift = if (data.isInterfaceTarget) MODIFIERS_TO_LIFT_IN_INTERFACE else MODIFIERS_TO_LIFT_IN_SUPERCLASS + val currentModifier = declaration.visibilityModifierTypeOrDefault() + if (currentModifier !in modifiersToLift) return + if (ignoreUsages || willBeUsedInSourceClass(declaration, data.sourceClass, data.membersToMove)) { + if (newModifier != KtTokens.DEFAULT_VISIBILITY_KEYWORD) { + declaration.addModifier(newModifier) + } else { + declaration.removeModifier(currentModifier) + } + } } override fun setCorrectVisibility(info: MemberInfoBase) { @@ -285,20 +295,20 @@ class KotlinPullUpHelper( if (data.isInterfaceTarget) { member.removeModifier(KtTokens.PUBLIC_KEYWORD) - return } - if (member.hasModifier(KtTokens.PRIVATE_KEYWORD)) { + val modifiersToLift = if (data.isInterfaceTarget) MODIFIERS_TO_LIFT_IN_INTERFACE else MODIFIERS_TO_LIFT_IN_SUPERCLASS + if (member.visibilityModifierTypeOrDefault() in modifiersToLift) { member.accept( object : KtVisitorVoid() { override fun visitNamedDeclaration(declaration: KtNamedDeclaration) { when (declaration) { is KtClass -> { - liftToProtected(declaration) + liftVisibility(declaration) declaration.declarations.forEach { it.accept(this) } } is KtNamedFunction, is KtProperty -> { - liftToProtected(declaration, declaration == member && info.isToAbstract) + liftVisibility(declaration, declaration == member && info.isToAbstract) } } } diff --git a/idea/testData/refactoring/extractInterface/liftInternal.kt b/idea/testData/refactoring/extractInterface/liftInternal.kt new file mode 100644 index 00000000000..8d409d173a2 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/liftInternal.kt @@ -0,0 +1,8 @@ +// NAME: IPurePrivate +// SIBLING: +class PurePrivate { + // INFO: {checked: "true", toAbstract: "true"} + internal var internalVar = 0 + // INFO: {checked: "true", toAbstract: "true"} + internal fun internalFun() {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/liftInternal.kt.after b/idea/testData/refactoring/extractInterface/liftInternal.kt.after new file mode 100644 index 00000000000..df232793395 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/liftInternal.kt.after @@ -0,0 +1,16 @@ +interface IPurePrivate { + // INFO: {checked: "true", toAbstract: "true"} + var internalVar: Int + + // INFO: {checked: "true", toAbstract: "true"} + fun internalFun() +} + +// NAME: IPurePrivate +// SIBLING: +class PurePrivate : IPurePrivate { + // INFO: {checked: "true", toAbstract: "true"} + override var internalVar = 0 + // INFO: {checked: "true", toAbstract: "true"} + override fun internalFun() {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/liftPrivate.kt b/idea/testData/refactoring/extractInterface/liftPrivate.kt new file mode 100644 index 00000000000..9790244bc68 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/liftPrivate.kt @@ -0,0 +1,8 @@ +// NAME: IPurePrivate +// SIBLING: +class PurePrivate { + // INFO: {checked: "true", toAbstract: "true"} + private var privateVar = 0 + // INFO: {checked: "true", toAbstract: "true"} + private fun privateFun() {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/liftPrivate.kt.after b/idea/testData/refactoring/extractInterface/liftPrivate.kt.after new file mode 100644 index 00000000000..32488751a6b --- /dev/null +++ b/idea/testData/refactoring/extractInterface/liftPrivate.kt.after @@ -0,0 +1,16 @@ +interface IPurePrivate { + // INFO: {checked: "true", toAbstract: "true"} + var privateVar: Int + + // INFO: {checked: "true", toAbstract: "true"} + fun privateFun() +} + +// NAME: IPurePrivate +// SIBLING: +class PurePrivate : IPurePrivate { + // INFO: {checked: "true", toAbstract: "true"} + override var privateVar = 0 + // INFO: {checked: "true", toAbstract: "true"} + override fun privateFun() {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/liftProtected.kt b/idea/testData/refactoring/extractInterface/liftProtected.kt new file mode 100644 index 00000000000..e52bc5551ef --- /dev/null +++ b/idea/testData/refactoring/extractInterface/liftProtected.kt @@ -0,0 +1,8 @@ +// NAME: IPurePrivate +// SIBLING: +class PurePrivate { + // INFO: {checked: "true", toAbstract: "true"} + protected var protectedVar = 0 + // INFO: {checked: "true", toAbstract: "true"} + protected fun protectedFun() {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/liftProtected.kt.after b/idea/testData/refactoring/extractInterface/liftProtected.kt.after new file mode 100644 index 00000000000..f19b6fa0558 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/liftProtected.kt.after @@ -0,0 +1,16 @@ +interface IPurePrivate { + // INFO: {checked: "true", toAbstract: "true"} + var protectedVar: Int + + // INFO: {checked: "true", toAbstract: "true"} + fun protectedFun() +} + +// NAME: IPurePrivate +// SIBLING: +class PurePrivate : IPurePrivate { + // INFO: {checked: "true", toAbstract: "true"} + override var protectedVar = 0 + // INFO: {checked: "true", toAbstract: "true"} + override fun protectedFun() {} +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java index 1b67de3561e..715824d3099 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java @@ -4068,6 +4068,21 @@ public class ExtractionTestGenerated extends AbstractExtractionTest { runTest("idea/testData/refactoring/extractInterface/extractToExistingFile.kt"); } + @TestMetadata("liftInternal.kt") + public void testLiftInternal() throws Exception { + runTest("idea/testData/refactoring/extractInterface/liftInternal.kt"); + } + + @TestMetadata("liftPrivate.kt") + public void testLiftPrivate() throws Exception { + runTest("idea/testData/refactoring/extractInterface/liftPrivate.kt"); + } + + @TestMetadata("liftProtected.kt") + public void testLiftProtected() throws Exception { + runTest("idea/testData/refactoring/extractInterface/liftProtected.kt"); + } + @TestMetadata("noWarningOnVisibilityInsideAbstractedMember.kt") public void testNoWarningOnVisibilityInsideAbstractedMember() throws Exception { runTest("idea/testData/refactoring/extractInterface/noWarningOnVisibilityInsideAbstractedMember.kt");