Push Down: Conflict analysis
This commit is contained in:
@@ -29,6 +29,7 @@ import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||
import com.intellij.openapi.editor.markup.TextAttributes
|
||||
import com.intellij.openapi.options.ConfigurationException
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.JavaProjectRootsUtil
|
||||
import com.intellij.openapi.ui.popup.JBPopup
|
||||
@@ -638,4 +639,10 @@ public fun JetElement.validateElement(errorMessage: String) {
|
||||
catch(e: Exception) {
|
||||
throw ConfigurationException(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T : Any> Project.runSynchronouslyWithProgress(progressTitle: String, canBeCanceled: Boolean, action: () -> T): T? {
|
||||
var result: T? = null
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously( { result = action() }, progressTitle, canBeCanceled, this)
|
||||
return result
|
||||
}
|
||||
@@ -74,7 +74,7 @@ private val CALLABLE_RENDERER = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_I
|
||||
startFromName = false
|
||||
}
|
||||
|
||||
private fun DeclarationDescriptor.renderForConflicts(): String {
|
||||
fun DeclarationDescriptor.renderForConflicts(): String {
|
||||
return when (this) {
|
||||
is ClassDescriptor -> "${DescriptorRenderer.getClassKindPrefix(this)} ${IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(this)}"
|
||||
is FunctionDescriptor -> "function '${CALLABLE_RENDERER.render(this)}'"
|
||||
|
||||
+61
-26
@@ -17,6 +17,8 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.pushDown
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
@@ -31,10 +33,12 @@ import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.runSynchronouslyWithProgress
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.pullUp.*
|
||||
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
|
||||
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.lexer.JetTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
@@ -45,25 +49,32 @@ import org.jetbrains.kotlin.util.findCallableMemberBySignature
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import java.util.ArrayList
|
||||
|
||||
public class KotlinPushDownProcessor(
|
||||
project: Project,
|
||||
private val sourceClass: JetClass,
|
||||
private val membersToMove: List<KotlinMemberInfo>
|
||||
) : BaseRefactoringProcessor(project) {
|
||||
private val resolutionFacade = sourceClass.getResolutionFacade()
|
||||
public class KotlinPushDownContext(
|
||||
val sourceClass: JetClass,
|
||||
val membersToMove: List<KotlinMemberInfo>
|
||||
) {
|
||||
val resolutionFacade = sourceClass.getResolutionFacade()
|
||||
|
||||
private val sourceClassContext = resolutionFacade.analyzeFullyAndGetResult(listOf(sourceClass)).bindingContext
|
||||
val sourceClassContext = resolutionFacade.analyzeFullyAndGetResult(listOf(sourceClass)).bindingContext
|
||||
|
||||
private val sourceClassDescriptor = sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, sourceClass] as ClassDescriptor
|
||||
val sourceClassDescriptor = sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, sourceClass] as ClassDescriptor
|
||||
|
||||
private val memberDescriptors = membersToMove
|
||||
val memberDescriptors = membersToMove
|
||||
.map { it.member }
|
||||
.keysToMap { sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]!! }
|
||||
}
|
||||
|
||||
public class KotlinPushDownProcessor(
|
||||
project: Project,
|
||||
sourceClass: JetClass,
|
||||
membersToMove: List<KotlinMemberInfo>
|
||||
) : BaseRefactoringProcessor(project) {
|
||||
private val context = KotlinPushDownContext(sourceClass, membersToMove)
|
||||
|
||||
inner class UsageViewDescriptorImpl : UsageViewDescriptor {
|
||||
override fun getProcessedElementsHeader() = RefactoringBundle.message("push.down.members.elements.header")
|
||||
|
||||
override fun getElements() = arrayOf(sourceClass)
|
||||
override fun getElements() = arrayOf(context.sourceClass)
|
||||
|
||||
override fun getCodeReferencesText(usagesCount: Int, filesCount: Int) =
|
||||
RefactoringBundle.message("classes.to.push.down.members.to", UsageViewBundle.getReferencesString(usagesCount, filesCount))
|
||||
@@ -78,8 +89,8 @@ public class KotlinPushDownProcessor(
|
||||
override fun createUsageViewDescriptor(usages: Array<out UsageInfo>) = UsageViewDescriptorImpl()
|
||||
|
||||
override fun getBeforeData() = RefactoringEventData().apply {
|
||||
addElement(sourceClass)
|
||||
addElements(membersToMove.map { it.member }.toTypedArray())
|
||||
addElement(context.sourceClass)
|
||||
addElements(context.membersToMove.map { it.member }.toTypedArray())
|
||||
}
|
||||
|
||||
override fun getAfterData(usages: Array<out UsageInfo>) = RefactoringEventData().apply {
|
||||
@@ -87,7 +98,7 @@ public class KotlinPushDownProcessor(
|
||||
}
|
||||
|
||||
override fun findUsages(): Array<out UsageInfo> {
|
||||
return HierarchySearchRequest(sourceClass, sourceClass.useScope, false)
|
||||
return HierarchySearchRequest(context.sourceClass, context.sourceClass.useScope, false)
|
||||
.searchInheritors()
|
||||
.map { it.unwrapped }
|
||||
.filterNotNull()
|
||||
@@ -95,13 +106,29 @@ public class KotlinPushDownProcessor(
|
||||
.toTypedArray()
|
||||
}
|
||||
|
||||
override fun preprocessUsages(refUsages: Ref<Array<UsageInfo>>): Boolean {
|
||||
val usages = refUsages.get() ?: UsageInfo.EMPTY_ARRAY
|
||||
if (usages.isEmpty()) {
|
||||
val message = "${context.sourceClassDescriptor.renderForConflicts()} doesn't have inheritors\n" +
|
||||
"Pushing members down will result in them being deleted. Would you like to proceed?"
|
||||
val answer = Messages.showYesNoDialog(message.capitalize(), PUSH_MEMBERS_DOWN, Messages.getWarningIcon())
|
||||
if (answer == Messages.NO) return false
|
||||
}
|
||||
|
||||
val conflicts = myProject.runSynchronouslyWithProgress(RefactoringBundle.message("detecting.possible.conflicts"), true) {
|
||||
runReadAction { analyzePushDownConflicts(context, usages) }
|
||||
} ?: return false
|
||||
|
||||
return showConflicts(conflicts, usages)
|
||||
}
|
||||
|
||||
private fun pushDownToClass(targetClass: JetClassOrObject) {
|
||||
val targetClassDescriptor = resolutionFacade.resolveToDescriptor(targetClass) as ClassDescriptor
|
||||
val substitutor = getTypeSubstitutor(sourceClassDescriptor.defaultType, targetClassDescriptor.defaultType)
|
||||
val targetClassDescriptor = context.resolutionFacade.resolveToDescriptor(targetClass) as ClassDescriptor
|
||||
val substitutor = getTypeSubstitutor(context.sourceClassDescriptor.defaultType, targetClassDescriptor.defaultType)
|
||||
?: TypeSubstitutor.EMPTY
|
||||
members@ for (memberInfo in membersToMove) {
|
||||
members@ for (memberInfo in context.membersToMove) {
|
||||
val member = memberInfo.member
|
||||
val memberDescriptor = memberDescriptors[member] ?: continue
|
||||
val memberDescriptor = context.memberDescriptors[member] ?: continue
|
||||
|
||||
val movedMember = when (member) {
|
||||
is JetProperty, is JetNamedFunction -> {
|
||||
@@ -122,7 +149,7 @@ public class KotlinPushDownProcessor(
|
||||
addModifierWithSpace(JetTokens.OVERRIDE_KEYWORD)
|
||||
}
|
||||
} ?: addMemberToTarget(member, targetClass).apply {
|
||||
if (sourceClassDescriptor.kind == ClassKind.INTERFACE) {
|
||||
if (context.sourceClassDescriptor.kind == ClassKind.INTERFACE) {
|
||||
if (targetClassDescriptor.kind != ClassKind.INTERFACE && memberDescriptor.modality == Modality.ABSTRACT) {
|
||||
addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD)
|
||||
}
|
||||
@@ -138,8 +165,11 @@ public class KotlinPushDownProcessor(
|
||||
|
||||
is JetClassOrObject -> {
|
||||
if (memberInfo.overrides != null) {
|
||||
sourceClass.getDelegatorToSuperClassByDescriptor(memberDescriptor as ClassDescriptor, sourceClassContext)?.let {
|
||||
addDelegatorToSuperClass(it, targetClass, targetClassDescriptor, sourceClassContext, substitutor)
|
||||
context.sourceClass.getDelegatorToSuperClassByDescriptor(
|
||||
memberDescriptor as ClassDescriptor,
|
||||
context.sourceClassContext
|
||||
)?.let {
|
||||
addDelegatorToSuperClass(it, targetClass, targetClassDescriptor, context.sourceClassContext, substitutor)
|
||||
}
|
||||
continue@members
|
||||
}
|
||||
@@ -155,9 +185,9 @@ public class KotlinPushDownProcessor(
|
||||
}
|
||||
|
||||
private fun removeOriginalMembers() {
|
||||
for (memberInfo in membersToMove) {
|
||||
for (memberInfo in context.membersToMove) {
|
||||
val member = memberInfo.member
|
||||
val memberDescriptor = memberDescriptors[member] ?: continue
|
||||
val memberDescriptor = context.memberDescriptors[member] ?: continue
|
||||
when (member) {
|
||||
is JetProperty, is JetNamedFunction -> {
|
||||
member as JetCallableDeclaration
|
||||
@@ -167,7 +197,7 @@ public class KotlinPushDownProcessor(
|
||||
if (member.hasModifier(JetTokens.PRIVATE_KEYWORD)) {
|
||||
member.addModifierWithSpace(JetTokens.PROTECTED_KEYWORD)
|
||||
}
|
||||
makeAbstract(member, memberDescriptor, TypeSubstitutor.EMPTY, sourceClass)
|
||||
makeAbstract(member, memberDescriptor, TypeSubstitutor.EMPTY, context.sourceClass)
|
||||
member.typeReference?.addToShorteningWaitSet()
|
||||
}
|
||||
else {
|
||||
@@ -176,8 +206,11 @@ public class KotlinPushDownProcessor(
|
||||
}
|
||||
is JetClassOrObject -> {
|
||||
if (memberInfo.overrides != null) {
|
||||
sourceClass.getDelegatorToSuperClassByDescriptor(memberDescriptor as ClassDescriptor, sourceClassContext)?.let {
|
||||
sourceClass.removeDelegationSpecifier(it)
|
||||
context.sourceClass.getDelegatorToSuperClassByDescriptor(
|
||||
memberDescriptor as ClassDescriptor,
|
||||
context.sourceClassContext
|
||||
)?.let {
|
||||
context.sourceClass.removeDelegationSpecifier(it)
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -191,7 +224,9 @@ public class KotlinPushDownProcessor(
|
||||
override fun performRefactoring(usages: Array<out UsageInfo>) {
|
||||
val markedElements = ArrayList<JetElement>()
|
||||
try {
|
||||
membersToMove.forEach { markedElements += markElements(it.member, sourceClassContext, sourceClassDescriptor, null) }
|
||||
context.membersToMove.forEach {
|
||||
markedElements += markElements(it.member, context.sourceClassContext, context.sourceClassDescriptor, null)
|
||||
}
|
||||
usages.forEach { (it.element as? JetClassOrObject)?.let { pushDownToClass(it) } }
|
||||
removeOriginalMembers()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.refactoring.pushDown
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import com.intellij.refactoring.util.RefactoringUIUtil
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.asJava.unwrapped
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.pullUp.renderForConflicts
|
||||
import org.jetbrains.kotlin.idea.references.JetReference
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.lexer.JetTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.QualifierReceiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.types.TypeSubstitutor
|
||||
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitutor
|
||||
import org.jetbrains.kotlin.util.findCallableMemberBySignature
|
||||
import java.util.ArrayList
|
||||
|
||||
fun analyzePushDownConflicts(context: KotlinPushDownContext,
|
||||
usages: Array<out UsageInfo>): MultiMap<PsiElement, String> {
|
||||
val targetClasses = usages.map { it.element?.unwrapped }.filterNotNull()
|
||||
|
||||
val conflicts = MultiMap<PsiElement, String>()
|
||||
|
||||
val membersToPush = ArrayList<JetNamedDeclaration>()
|
||||
val membersToKeepAbstract = ArrayList<JetNamedDeclaration>()
|
||||
for (info in context.membersToMove) {
|
||||
val member = info.member
|
||||
if (!info.isChecked || (member is JetClassOrObject && info.overrides != null)) continue
|
||||
|
||||
membersToPush += member
|
||||
if ((member is JetNamedFunction || member is JetProperty)
|
||||
&& info.isToAbstract
|
||||
&& (context.memberDescriptors[member] as CallableMemberDescriptor).modality != Modality.ABSTRACT) {
|
||||
membersToKeepAbstract += member
|
||||
}
|
||||
}
|
||||
|
||||
for (targetClass in targetClasses) {
|
||||
checkConflicts(conflicts, context, targetClass, membersToKeepAbstract, membersToPush)
|
||||
}
|
||||
|
||||
return conflicts
|
||||
}
|
||||
|
||||
private fun checkConflicts(
|
||||
conflicts: MultiMap<PsiElement, String>,
|
||||
context: KotlinPushDownContext,
|
||||
targetClass: PsiElement,
|
||||
membersToKeepAbstract: List<JetNamedDeclaration>,
|
||||
membersToPush: ArrayList<JetNamedDeclaration>
|
||||
) {
|
||||
if (targetClass !is JetClassOrObject) {
|
||||
conflicts.putValue(
|
||||
targetClass,
|
||||
"Non-Kotlin ${RefactoringUIUtil.getDescription(targetClass, false)} won't be affected by the refactoring"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val targetClassDescriptor = context.resolutionFacade.resolveToDescriptor(targetClass) as ClassDescriptor
|
||||
val substitutor = getTypeSubstitutor(context.sourceClassDescriptor.defaultType, targetClassDescriptor.defaultType)
|
||||
?: TypeSubstitutor.EMPTY
|
||||
|
||||
if (!context.sourceClass.isInterface() && targetClass is JetClass && targetClass.isInterface()) {
|
||||
val message = "${targetClassDescriptor.renderForConflicts()} " +
|
||||
"inherits from ${context.sourceClassDescriptor.renderForConflicts()}.\n" +
|
||||
"It won't be affected by the refactoring"
|
||||
conflicts.putValue(targetClass, message.capitalize())
|
||||
}
|
||||
|
||||
for (member in membersToPush) {
|
||||
checkMemberClashing(conflicts, context, member, membersToKeepAbstract, substitutor, targetClass, targetClassDescriptor)
|
||||
checkSuperCalls(conflicts, context, member, membersToPush)
|
||||
checkExternalUsages(conflicts, context, member, targetClassDescriptor)
|
||||
checkVisibility(conflicts, context, member, targetClassDescriptor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkMemberClashing(
|
||||
conflicts: MultiMap<PsiElement, String>,
|
||||
context: KotlinPushDownContext,
|
||||
member: JetNamedDeclaration,
|
||||
membersToKeepAbstract: List<JetNamedDeclaration>,
|
||||
substitutor: TypeSubstitutor,
|
||||
targetClass: JetClassOrObject,
|
||||
targetClassDescriptor: ClassDescriptor) {
|
||||
when (member) {
|
||||
is JetNamedFunction, is JetProperty -> {
|
||||
val memberDescriptor = context.memberDescriptors[member] as CallableMemberDescriptor
|
||||
val clashingDescriptor = targetClassDescriptor.findCallableMemberBySignature(memberDescriptor.substitute(substitutor) as CallableMemberDescriptor)
|
||||
val clashingDeclaration = clashingDescriptor?.source?.getPsi() as? JetNamedDeclaration
|
||||
if (clashingDescriptor != null && clashingDeclaration != null) {
|
||||
if (memberDescriptor.modality != Modality.ABSTRACT && member !in membersToKeepAbstract) {
|
||||
val message = "${targetClassDescriptor.renderForConflicts()} already contains ${clashingDescriptor.renderForConflicts()}"
|
||||
conflicts.putValue(clashingDeclaration, CommonRefactoringUtil.capitalize(message))
|
||||
}
|
||||
if (!clashingDeclaration.hasModifier(JetTokens.OVERRIDE_KEYWORD)) {
|
||||
val message = "${clashingDescriptor.renderForConflicts()} in ${targetClassDescriptor.renderForConflicts()} " +
|
||||
"will override corresponding member of ${context.sourceClassDescriptor.renderForConflicts()} " +
|
||||
"after refactoring"
|
||||
conflicts.putValue(clashingDeclaration, CommonRefactoringUtil.capitalize(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is JetClassOrObject -> {
|
||||
targetClass.declarations
|
||||
.filterIsInstance<JetClassOrObject>()
|
||||
.firstOrNull() { it.name == member.name }
|
||||
?.let {
|
||||
val message = "${targetClassDescriptor.renderForConflicts()} " +
|
||||
"already contains nested class named ${CommonRefactoringUtil.htmlEmphasize(member.name)}"
|
||||
conflicts.putValue(it, message.capitalize())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSuperCalls(
|
||||
conflicts: MultiMap<PsiElement, String>,
|
||||
context: KotlinPushDownContext,
|
||||
member: JetNamedDeclaration,
|
||||
membersToPush: ArrayList<JetNamedDeclaration>
|
||||
) {
|
||||
member.accept(
|
||||
object : JetTreeVisitorVoid() {
|
||||
override fun visitSuperExpression(expression: JetSuperExpression) {
|
||||
val qualifiedExpression = expression.getQualifiedExpressionForReceiver() ?: return
|
||||
val refExpr = qualifiedExpression.selectorExpression.getCalleeExpressionIfAny() as? JetSimpleNameExpression ?: return
|
||||
for (descriptor in refExpr.mainReference.resolveToDescriptors(context.sourceClassContext)) {
|
||||
val memberDescriptor = descriptor as? CallableMemberDescriptor ?: continue
|
||||
val containingClass = memberDescriptor.containingDeclaration as? ClassDescriptor ?: continue
|
||||
if (!DescriptorUtils.isSubclass(context.sourceClassDescriptor, containingClass)) continue
|
||||
val memberInSource = context.sourceClassDescriptor.findCallableMemberBySignature(memberDescriptor)?.source?.getPsi()
|
||||
?: continue
|
||||
if (memberInSource !in membersToPush) {
|
||||
conflicts.putValue(qualifiedExpression,
|
||||
"Pushed member won't be available in '${qualifiedExpression.text}'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun checkExternalUsages(
|
||||
conflicts: MultiMap<PsiElement, String>,
|
||||
context: KotlinPushDownContext,
|
||||
member: JetNamedDeclaration,
|
||||
targetClassDescriptor: ClassDescriptor
|
||||
) {
|
||||
for (ref in ReferencesSearch.search(member, member.resolveScope, false)) {
|
||||
val calleeExpr = ref.element as? JetSimpleNameExpression ?: continue
|
||||
val resolvedCall = calleeExpr.getResolvedCall(context.resolutionFacade.analyze(calleeExpr)) ?: continue
|
||||
val callElement = resolvedCall.call.callElement
|
||||
val dispatchReceiver = resolvedCall.dispatchReceiver
|
||||
if (!dispatchReceiver.exists() || dispatchReceiver is QualifierReceiver) continue
|
||||
val receiverClassDescriptor = dispatchReceiver.type.constructor.declarationDescriptor as? ClassDescriptor ?: continue
|
||||
if (!DescriptorUtils.isSubclass(receiverClassDescriptor, targetClassDescriptor)) {
|
||||
conflicts.putValue(callElement, "Pushed member won't be available in '${callElement.text}'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkVisibility(
|
||||
conflicts: MultiMap<PsiElement, String>,
|
||||
context: KotlinPushDownContext,
|
||||
member: JetNamedDeclaration,
|
||||
targetClassDescriptor: ClassDescriptor
|
||||
) {
|
||||
fun reportConflictIfAny(targetDescriptor: DeclarationDescriptor) {
|
||||
val target = (targetDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() ?: return
|
||||
if (targetDescriptor is DeclarationDescriptorWithVisibility
|
||||
&& !Visibilities.isVisible(ReceiverValue.IRRELEVANT_RECEIVER, targetDescriptor, targetClassDescriptor)) {
|
||||
val message = "${context.memberDescriptors[member]!!.renderForConflicts()} " +
|
||||
"uses ${targetDescriptor.renderForConflicts()}, " +
|
||||
"which is not accessible from the ${targetClassDescriptor.renderForConflicts()}"
|
||||
conflicts.putValue(target, message.capitalize())
|
||||
}
|
||||
}
|
||||
|
||||
member.accept(
|
||||
object : JetTreeVisitorVoid() {
|
||||
override fun visitReferenceExpression(expression: JetReferenceExpression) {
|
||||
super.visitReferenceExpression(expression)
|
||||
|
||||
expression.references
|
||||
.flatMap { (it as? JetReference)?.resolveToDescriptors(context.sourceClassContext) ?: emptyList() }
|
||||
.forEach(::reportConflictIfAny)
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
abstract class <caret>A {
|
||||
// INFO: {"checked": "true"}
|
||||
abstract val y: Boolean
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
val z: Int = 1
|
||||
// INFO: {"checked": "true"}
|
||||
abstract fun foo(n: Int, m: Int)
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
fun foo(b: Boolean) = !b
|
||||
}
|
||||
|
||||
class B : A {
|
||||
val x: Int = 2
|
||||
|
||||
val y: Boolean get() = x > 0
|
||||
|
||||
val z: Int = 3
|
||||
|
||||
fun foo(n: Int) = n + 2
|
||||
|
||||
fun foo(n: Int, m: Int) = n + m
|
||||
|
||||
fun foo(b: Boolean) = true
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Function 'fun foo(Boolean): Boolean' in class B will override corresponding member of class A after refactoring
|
||||
Function 'fun foo(Int, Int): Int' in class B will override corresponding member of class A after refactoring
|
||||
Property 'val y: Boolean' in class B will override corresponding member of class A after refactoring
|
||||
Property 'val z: Int' in class B will override corresponding member of class A after refactoring
|
||||
@@ -0,0 +1,32 @@
|
||||
abstract class <caret>A {
|
||||
// INFO: {"checked": "true"}
|
||||
open val x: Int = 1
|
||||
// INFO: {"checked": "true"}
|
||||
abstract val y: Boolean
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
open val z: Int = 1
|
||||
// INFO: {"checked": "true"}
|
||||
open fun foo(n: Int) = n + 1
|
||||
// INFO: {"checked": "true"}
|
||||
abstract fun foo(n: Int, m: Int)
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
open fun foo(b: Boolean) = !b
|
||||
// INFO: {"checked": "true"}
|
||||
class X
|
||||
}
|
||||
|
||||
class B : A {
|
||||
override val x: Int = 2
|
||||
|
||||
override val y: Boolean get() = x > 0
|
||||
|
||||
override val z: Int = 3
|
||||
|
||||
override fun foo(n: Int) = n + 2
|
||||
|
||||
override fun foo(n: Int, m: Int) = n + m
|
||||
|
||||
override fun foo(b: Boolean) = true
|
||||
|
||||
class X
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Class B already contains function 'fun foo(Int): Int'
|
||||
Class B already contains nested class named X
|
||||
Class B already contains property 'val x: Int'
|
||||
@@ -0,0 +1,6 @@
|
||||
open class <caret>A {
|
||||
// INFO: {"checked": "true"}
|
||||
val x: Int
|
||||
}
|
||||
|
||||
interface I : A
|
||||
@@ -0,0 +1,2 @@
|
||||
Interface I inherits from class A.
|
||||
It won't be affected by the refactoring
|
||||
@@ -0,0 +1,30 @@
|
||||
open class A {
|
||||
open fun foo(n: Int) {
|
||||
|
||||
}
|
||||
|
||||
open fun bar(n: Int) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
open class <caret>B : A() {
|
||||
// INFO: {"checked": "true"}
|
||||
fun foo() {
|
||||
super.foo(1)
|
||||
super.bar(1)
|
||||
}
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
override fun foo(n: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun bar(n: Int) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class C : B() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Pushed member won't be available in 'super.bar(1)'
|
||||
@@ -0,0 +1,3 @@
|
||||
class B extends A {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
open class <caret>A {
|
||||
val x: Int = 1
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Non-Kotlin class B won't be affected by the refactoring
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
open class <caret>A {
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
private fun foo() {
|
||||
private open fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class B : A {
|
||||
fun foo() {
|
||||
inner class B : A() {
|
||||
override fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
open class A {
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
protected abstract fun foo()
|
||||
}
|
||||
|
||||
class B : A {
|
||||
override fun foo() {
|
||||
inner class B : A() {
|
||||
override fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
open class <caret>A {
|
||||
private fun foo() {
|
||||
|
||||
}
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
fun bar() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
|
||||
class B : A() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Function 'fun bar()' uses function 'fun foo()', which is not accessible from the class B
|
||||
@@ -0,0 +1,37 @@
|
||||
open class <caret>A {
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
fun foo(n: Int) {
|
||||
|
||||
}
|
||||
|
||||
fun test() {
|
||||
foo()
|
||||
foo(1)
|
||||
}
|
||||
}
|
||||
|
||||
open class B : A() {
|
||||
|
||||
}
|
||||
|
||||
class C : B() {
|
||||
|
||||
}
|
||||
|
||||
fun A.test() {
|
||||
foo()
|
||||
foo(2)
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A().foo()
|
||||
A().foo(3)
|
||||
B().foo()
|
||||
B().foo(4)
|
||||
C().foo()
|
||||
C().foo(5)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Pushed member won't be available in 'foo(1)'
|
||||
Pushed member won't be available in 'foo(2)'
|
||||
Pushed member won't be available in 'foo(3)'
|
||||
@@ -31,10 +31,34 @@ import java.util.regex.Pattern;
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class PushDownTestGenerated extends AbstractPushDownTest {
|
||||
@TestMetadata("accidentalOverrides.kt")
|
||||
public void testAccidentalOverrides() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/accidentalOverrides.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInPushDown() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/refactoring/pushDown"), Pattern.compile("^(.+)\\.kt$"));
|
||||
}
|
||||
|
||||
@TestMetadata("clashingMembers.kt")
|
||||
public void testClashingMembers() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/clashingMembers.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("classToInterface.kt")
|
||||
public void testClassToInterface() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/classToInterface.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("conflictingSuperCall.kt")
|
||||
public void testConflictingSuperCall() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/conflictingSuperCall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("finalClass.kt")
|
||||
public void testFinalClass() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/finalClass.kt");
|
||||
@@ -47,6 +71,12 @@ public class PushDownTestGenerated extends AbstractPushDownTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kotlinToJava.kt")
|
||||
public void testKotlinToJava() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/kotlinToJava.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("liftPrivate.kt")
|
||||
public void testLiftPrivate() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/liftPrivate.kt");
|
||||
@@ -101,6 +131,18 @@ public class PushDownTestGenerated extends AbstractPushDownTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("pushMembersUsingPrivates.kt")
|
||||
public void testPushMembersUsingPrivates() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushMembersUsingPrivates.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("pushMembersWithExternalUsages.kt")
|
||||
public void testPushMembersWithExternalUsages() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushMembersWithExternalUsages.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("pushSuperInterfaces.kt")
|
||||
public void testPushSuperInterfaces() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushSuperInterfaces.kt");
|
||||
|
||||
Reference in New Issue
Block a user