Pull Up: Initial support

#KT-7330 Fixed
This commit is contained in:
Alexey Sedunov
2015-07-16 16:08:20 +03:00
parent e303a38f9e
commit 39ff3c3000
47 changed files with 1709 additions and 23 deletions
@@ -21,9 +21,11 @@ import com.intellij.lang.ASTNode
import com.intellij.navigation.ItemPresentation
import com.intellij.navigation.ItemPresentationProviders
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.impl.CheckUtil
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil
import com.intellij.psi.stubs.IStubElementType
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.JetNodeTypes
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.lexer.JetTokens
@@ -46,6 +48,22 @@ abstract public class JetClassOrObject : JetTypeParameterListOwnerStub<KotlinCla
public fun getOrCreateBody(): JetClassBody = getBody() ?: add(JetPsiFactory(this).createEmptyClassBody()) as JetClassBody
public fun addDeclaration(declaration: JetDeclaration): JetDeclaration {
val body = getOrCreateBody()
val anchor = PsiTreeUtil.skipSiblingsBackward(body.getRBrace() ?: body.getLastChild()!!, javaClass<PsiWhiteSpace>())
return body.addAfter(declaration, anchor) as JetDeclaration
}
public fun addDeclarationAfter(declaration: JetDeclaration, anchor: PsiElement?): JetDeclaration {
val anchorBefore = anchor ?: getDeclarations().lastOrNull() ?: return addDeclaration(declaration)
return getOrCreateBody().addAfter(declaration, anchorBefore) as JetDeclaration
}
public fun addDeclarationBefore(declaration: JetDeclaration, anchor: PsiElement?): JetDeclaration {
val anchorAfter = anchor ?: getDeclarations().firstOrNull() ?: return addDeclaration(declaration)
return getOrCreateBody().addBefore(declaration, anchorAfter) as JetDeclaration
}
public fun isTopLevel(): Boolean = getStub()?.isTopLevel() ?: (getParent() is JetFile)
public fun isLocal(): Boolean = getStub()?.isLocal() ?: JetPsiUtil.isLocal(this)
@@ -157,6 +157,11 @@ public class JetProperty extends JetTypeParameterListOwnerStub<KotlinPropertyStu
return findChildByType(JetTokens.COLON);
}
@Nullable
public PsiElement getEqualsToken() {
return findChildByType(JetTokens.EQ);
}
@NotNull
public List<JetPropertyAccessor> getAccessors() {
return getStubOrPsiChildrenAsList(JetStubElementTypes.PROPERTY_ACCESSOR);
@@ -134,6 +134,15 @@ public fun JetExpression.getQualifiedExpressionForSelectorOrThis(): JetExpressio
return getQualifiedExpressionForSelector() ?: this
}
public fun JetExpression.getQualifiedExpressionForReceiver(): JetQualifiedExpression? {
val parent = getParent()
return if (parent is JetQualifiedExpression && parent.getReceiverExpression() == this) parent else null
}
public fun JetExpression.getQualifiedExpressionForReceiverOrThis(): JetExpression {
return getQualifiedExpressionForReceiver() ?: this
}
public fun JetExpression.isDotReceiver(): Boolean =
(getParent() as? JetDotQualifiedExpression)?.getReceiverExpression() == this
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.psi
import kotlin.properties.ReadWriteProperty
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder
import com.intellij.psi.PsiElement
public class UserDataProperty<in R: UserDataHolder, T : Any>(val key: Key<T>, val default: T? = null) : ReadWriteProperty<R, T?> {
override fun get(thisRef: R, desc: kotlin.PropertyMetadata): T? {
@@ -39,3 +40,13 @@ public class NotNullableUserDataProperty<in R: UserDataHolder, T : Any>(val key:
thisRef.putUserData(key, value)
}
}
public class CopyableUserDataProperty<in R: PsiElement, T : Any>(val key: Key<T>, val default: T? = null) : ReadWriteProperty<R, T?> {
override fun get(thisRef: R, property: PropertyMetadata): T? {
return thisRef.getCopyableUserData(key)
}
override fun set(thisRef: R, property: PropertyMetadata, value: T?) {
thisRef.putCopyableUserData(key, value)
}
}
@@ -16,14 +16,17 @@
package org.jetbrains.kotlin.test.util
import com.intellij.codeInspection.SmartHashMap
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.SmartFMap
import org.jetbrains.kotlin.psi.*
import java.io.File
import java.util.*
public fun String.trimTrailingWhitespacesAndAddNewlineAtEOF(): String =
this.split('\n').map { it.trimEnd() }.joinToString(separator = "\n").let {
@@ -74,28 +77,29 @@ public fun String.trimIndent(): String {
}.joinToString(separator = "\n")
}
public fun JetFile.findElementByComment(commentText: String): JetElement? {
var result: JetElement? = null
public fun JetFile.findElementByCommentPrefix(commentText: String): JetElement? =
findElementsByCommentPrefix(commentText).keySet().singleOrNull()
public fun JetFile.findElementsByCommentPrefix(prefix: String): Map<JetElement, String> {
var result = SmartFMap.emptyMap<JetElement, String>()
accept(
object : JetTreeVisitorVoid() {
override fun visitComment(comment: PsiComment) {
if (comment.getText() == commentText) {
val commentText = comment.getText()
if (commentText.startsWith(prefix)) {
val parent = comment.getParent()
if (parent is JetDeclaration) {
result = parent
}
else {
result = PsiTreeUtil.skipSiblingsForward(
val elementToAdd = when (parent) {
is JetDeclaration -> parent
else -> PsiTreeUtil.skipSiblingsForward(
comment,
javaClass<PsiWhiteSpace>(),
javaClass<PsiComment>(),
javaClass<JetPackageDirective>()
) as? JetElement
}
javaClass<PsiWhiteSpace>(), javaClass<PsiComment>(), javaClass<JetPackageDirective>()
)
} as? JetElement ?: return
result = result.plus(elementToAdd, commentText.substring(prefix.length()).trim())
}
}
}
)
return result
}
@@ -83,6 +83,7 @@ import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixTest
import org.jetbrains.kotlin.idea.refactoring.inline.AbstractInlineTest
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractJetExtractionTest
import org.jetbrains.kotlin.idea.refactoring.move.AbstractJetMoveTest
import org.jetbrains.kotlin.idea.refactoring.pullUp.AbstractPullUpTest
import org.jetbrains.kotlin.idea.refactoring.rename.AbstractRenameTest
import org.jetbrains.kotlin.idea.resolve.*
import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest
@@ -653,6 +654,10 @@ fun main(args: Array<String>) {
model("refactoring/introduceJavaParameter", extension = "java", testMethod = "doIntroduceJavaParameterTest")
}
testClass(javaClass<AbstractPullUpTest>()) {
model("refactoring/pullUp", extension = "kt", singleClass = true)
}
testClass(javaClass<AbstractSelectExpressionForDebuggerTest>()) {
model("debugger/selectExpression", recursive = false)
model("debugger/selectExpression/disallowMethodCalls", testMethod = "doTestWoMethodCalls")
@@ -16,11 +16,13 @@
package org.jetbrains.kotlin.util
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.OverridingUtil
import org.jetbrains.kotlin.resolve.OverridingUtil.OverrideCompatibilityInfo.Result.OVERRIDABLE
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.types.TypeConstructor
import org.jetbrains.kotlin.types.checker.JetTypeChecker
import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure
import org.jetbrains.kotlin.types.typeUtil.equalTypesOrNulls
public fun descriptorsEqualWithSubstitution(descriptor1: DeclarationDescriptor?, descriptor2: DeclarationDescriptor?): Boolean {
@@ -56,3 +58,12 @@ public fun descriptorsEqualWithSubstitution(descriptor1: DeclarationDescriptor?,
return true
}
public fun ClassDescriptor.findCallableMemberBySignature(signature: CallableMemberDescriptor): CallableMemberDescriptor? {
val descriptorKind = if (signature is FunctionDescriptor) DescriptorKindFilter.FUNCTIONS else DescriptorKindFilter.VARIABLES
return getDefaultType().getMemberScope()
.getDescriptors(descriptorKind)
.firstOrNull {
it.getContainingDeclaration() == this
&& OverridingUtil.DEFAULT.isOverridableBy(it as CallableDescriptor, signature).getResult() == OVERRIDABLE
} as? CallableMemberDescriptor
}
+2
View File
@@ -527,6 +527,8 @@
language="jet"
implementationClass="org.jetbrains.kotlin.idea.search.ideaExtensions.JetTargetElementEvaluator" />
<refactoring.pullUpHelperFactory language="jet" implementationClass="org.jetbrains.kotlin.idea.refactoring.pullUp.KotlinPullUpHelperFactory"/>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.IfNullToElvisIntention</className>
<category>Kotlin</category>
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.Kotlin
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceParameterHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
import org.jetbrains.kotlin.idea.refactoring.pullUp.KotlinPullUpHandler
import org.jetbrains.kotlin.idea.refactoring.safeDelete.*
import org.jetbrains.kotlin.psi.*
@@ -70,4 +71,6 @@ public class KotlinRefactoringSupportProvider : RefactoringSupportProvider() {
}
override fun getChangeSignatureHandler() = JetChangeSignatureHandler()
override fun getPullUpHandler() = KotlinPullUpHandler()
}
@@ -0,0 +1,33 @@
/*
* 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.memberInfo
import com.intellij.openapi.util.Iconable
import com.intellij.ui.ListCellRendererWrapper
import org.jetbrains.kotlin.psi.JetClassOrObject
import javax.swing.JList
public class JetClassOrObjectCellRenderer: ListCellRendererWrapper<JetClassOrObject>() {
override fun customize(list: JList, value: JetClassOrObject?, index: Int, selected: Boolean, hasFocus: Boolean) {
if (value == null) return
setText(value.qualifiedNameForRendering())
value.getIcon(Iconable.ICON_FLAG_VISIBILITY or Iconable.ICON_FLAG_READ_STATUS)?.let { setIcon(it) }
}
}
public fun JetClassOrObject.qualifiedNameForRendering(): String = getFqName()?.asString() ?: getName() ?: "[Anonymous]"
@@ -16,13 +16,16 @@
package org.jetbrains.kotlin.idea.refactoring.memberInfo
import com.intellij.psi.PsiMember
import com.intellij.refactoring.classMembers.MemberInfoBase
import com.intellij.refactoring.util.classMembers.MemberInfo
import org.jetbrains.kotlin.asJava.getRepresentativeLightMethod
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.*
public class KotlinMemberInfo(member: JetNamedDeclaration) : MemberInfoBase<JetNamedDeclaration>(member) {
init {
@@ -36,3 +39,17 @@ public class KotlinMemberInfo(member: JetNamedDeclaration) : MemberInfoBase<JetN
}
}
}
public fun KotlinMemberInfo.toJavaMemberInfo(): MemberInfo? {
val declaration = getMember()
val psiMember: PsiMember? = when (declaration) {
is JetNamedFunction, is JetProperty -> declaration.getRepresentativeLightMethod()
is JetClassOrObject -> declaration.toLightClass()
else -> null
}
if (psiMember == null) return null
val info = MemberInfo(psiMember)
info.setToAbstract(isToAbstract())
return info
}
@@ -0,0 +1,62 @@
/*
* 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.pullUp
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiSubstitutor
import com.intellij.refactoring.classMembers.MemberInfoBase
import com.intellij.refactoring.memberPullUp.PullUpData
import com.intellij.refactoring.memberPullUp.PullUpHelper
import com.intellij.refactoring.memberPullUp.PullUpHelperFactory
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitutor
import java.util.LinkedHashSet
object EmptyPullUpHelper : PullUpHelper<MemberInfoBase<PsiMember>> {
override fun postProcessMember(member: PsiMember) {
}
override fun moveFieldInitializations(movedFields: LinkedHashSet<PsiField>) {
}
override fun encodeContextInfo(info: MemberInfoBase<PsiMember>) {
}
override fun setCorrectVisibility(info: MemberInfoBase<PsiMember>) {
}
override fun move(info: MemberInfoBase<PsiMember>, substitutor: PsiSubstitutor) {
}
override fun updateUsage(element: PsiElement) {
}
}
@@ -0,0 +1,73 @@
/*
* 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.pullUp
import com.intellij.refactoring.memberPullUp.PullUpData
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.getResolutionScope
import org.jetbrains.kotlin.psi.JetClass
import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitution
import org.jetbrains.kotlin.utils.keysToMap
import java.util.*
class KotlinPullUpData(val sourceClass: JetClassOrObject,
val targetClass: JetClass,
val membersToMove: Collection<JetNamedDeclaration>) {
val resolutionFacade = sourceClass.getResolutionFacade()
val sourceClassContext = resolutionFacade.analyzeFullyAndGetResult(listOf(sourceClass)).bindingContext
val sourceClassDescriptor = sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, sourceClass] as ClassDescriptor
val memberDescriptors = membersToMove.keysToMap { sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]!! }
val targetClassDescriptor = resolutionFacade.resolveToDescriptor(targetClass) as ClassDescriptor
val typeParametersInSourceClassContext by lazy {
sourceClassDescriptor.getTypeConstructor().getParameters() + sourceClass.getResolutionScope(sourceClassContext, resolutionFacade)
.getDescriptors(DescriptorKindFilter.NON_SINGLETON_CLASSIFIERS)
.filterIsInstance<TypeParameterDescriptor>()
}
val sourceToTargetClassSubstitutor: TypeSubstitutor by lazy {
val substitution = LinkedHashMap<TypeConstructor, TypeProjection>()
typeParametersInSourceClassContext.forEach {
substitution[it.getTypeConstructor()] = TypeProjectionImpl(it.getUpperBoundsAsType())
}
val superClassSubstitution = getTypeSubstitution(targetClassDescriptor.getDefaultType(), sourceClassDescriptor.getDefaultType())
?: emptyMap<TypeConstructor, TypeProjection>()
for ((typeConstructor, typeProjection) in superClassSubstitution) {
val subClassTypeParameter = typeProjection.getType().getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor
?: continue
val superClassTypeParameter = typeConstructor.getDeclarationDescriptor()
?: continue
substitution[subClassTypeParameter.getTypeConstructor()] = TypeProjectionImpl(superClassTypeParameter.getDefaultType())
}
TypeSubstitutor.create(substitution)
}
}
@@ -0,0 +1,114 @@
/*
* 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.pullUp
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiComment
import com.intellij.refactoring.JavaRefactoringSettings
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel
import com.intellij.refactoring.memberPullUp.PullUpDialogBase
import com.intellij.refactoring.memberPullUp.PullUpProcessor
import com.intellij.refactoring.util.DocCommentPolicy
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.idea.refactoring.memberInfo.*
import org.jetbrains.kotlin.psi.*
import java.awt.event.ItemEvent
import javax.swing.JComboBox
public class KotlinPullUpDialog(
project: Project,
private val classOrObject: JetClassOrObject,
superClasses: List<JetClass>,
memberInfoStorage: KotlinMemberInfoStorage
) : PullUpDialogBase<KotlinMemberInfoStorage, KotlinMemberInfo, JetNamedDeclaration, JetClassOrObject>(
project, classOrObject, superClasses, memberInfoStorage, PULL_MEMBERS_UP
) {
init {
init()
}
private inner class MemberInfoModelImpl : AbstractMemberInfoModel<JetNamedDeclaration, KotlinMemberInfo>() {
// Abstract members remain abstract
override fun isFixedAbstract(memberInfo: KotlinMemberInfo?) = true
/*
* Any non-abstract function can change abstractness.
*
* Non-abstract property with initializer or delegate is always made abstract.
* Any other non-abstract property can change abstractness.
*
* Classes do not have abstractness
*/
override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean {
val superClass = getSuperClass() ?: return false
if (!superClass.isInterface()) return true
val member = memberInfo.getMember()
return member is JetNamedFunction || (member is JetProperty && !member.mustBeAbstractInInterface())
}
override fun isAbstractWhenDisabled(memberInfo: KotlinMemberInfo): Boolean {
return memberInfo.getMember() is JetProperty
}
override fun isMemberEnabled(memberInfo: KotlinMemberInfo): Boolean {
val superClass = getSuperClass() ?: return false
if (memberInfo in memberInfoStorage.getDuplicatedMemberInfos(superClass)) return false
return true
}
}
protected val memberInfoStorage: KotlinMemberInfoStorage get() = myMemberInfoStorage
override fun getDimensionServiceKey() = "#" + javaClass.getName()
override fun getSuperClass() = super.getSuperClass() as? JetClass
override fun initClassCombo(classCombo: JComboBox) {
classCombo.setRenderer(JetClassOrObjectCellRenderer())
classCombo.addItemListener { event ->
if (event.getStateChange() == ItemEvent.SELECTED) {
myMemberSelectionPanel?.getTable()?.let {
it.setMemberInfos(myMemberInfos)
it.fireExternalDataChange()
}
}
}
}
override fun createMemberInfoModel() = MemberInfoModelImpl()
override fun getPreselection() = mySuperClasses.firstOrNull()
override fun createMemberSelectionTable(infos: MutableList<KotlinMemberInfo>) =
KotlinMemberSelectionTable(infos, null, "Make abstract")
override fun doAction() {
invokeRefactoring(createProcessor(myClass, getSuperClass()!!, getSelectedMemberInfos()))
}
companion object {
fun createProcessor(sourceClass: JetClassOrObject,
targetClass: JetClass,
memberInfos: List<KotlinMemberInfo>): PullUpProcessor {
return PullUpProcessor(sourceClass.toLightClass(),
targetClass.toLightClass(),
memberInfos.map { it.toJavaMemberInfo() }.filterNotNull().toTypedArray(),
DocCommentPolicy<PsiComment>(JavaRefactoringSettings.getInstance().PULL_UP_MEMBERS_JAVADOC))
}
}
}
@@ -0,0 +1,175 @@
/*
* 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.pullUp
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.refactoring.HelpID
import com.intellij.refactoring.RefactoringActionHandler
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.lang.ElementsHandler
import com.intellij.refactoring.util.CommonRefactoringUtil
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.refactoring.canRefactor
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfoStorage
import org.jetbrains.kotlin.idea.refactoring.memberInfo.qualifiedNameForRendering
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.typeUtil.supertypes
import java.util.HashSet
public class KotlinPullUpHandler : RefactoringActionHandler, ElementsHandler {
companion object {
val PULLUP_TEST_HELPER_KEY = "PULLUP_TEST_HELPER_KEY"
}
interface TestHelper {
fun adjustMembers(members: List<KotlinMemberInfo>): List<KotlinMemberInfo>
fun chooseSuperClass(superClasses: List<JetClass>): JetClass
}
private fun reportWrongPosition(project: Project, editor: Editor?) {
val message = RefactoringBundle.getCannotRefactorMessage(
RefactoringBundle.message("the.caret.should.be.positioned.inside.a.class.to.pull.members.from")
)
CommonRefactoringUtil.showErrorHint(project, editor, message, PULL_MEMBERS_UP, "refactoring.pullMembersUp")
}
private fun reportWrongContext(project: Project, editor: Editor?) {
val message = RefactoringBundle.getCannotRefactorMessage(
RefactoringBundle.message("is.not.supported.in.the.current.context", PULL_MEMBERS_UP)
)
CommonRefactoringUtil.showErrorHint(project, editor, message, PULL_MEMBERS_UP, HelpID.MEMBERS_PULL_UP)
}
private fun reportNoSuperClasses(project: Project, editor: Editor?, classOrObject: JetClassOrObject) {
val message = RefactoringBundle.getCannotRefactorMessage(
RefactoringBundle.message("class.does.not.have.base.classes.interfaces.in.current.project",
classOrObject.qualifiedNameForRendering())
)
CommonRefactoringUtil.showErrorHint(project, editor, message, PULL_MEMBERS_UP, HelpID.MEMBERS_PULL_UP)
}
override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) {
val offset = editor.getCaretModel().getOffset()
editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE)
val target = (file.findElementAt(offset) ?: return).parentsWithSelf.firstOrNull {
it is JetClassOrObject || ((it is JetNamedFunction || it is JetProperty) && it.getParent() is JetClassBody)
}
if (target == null) {
reportWrongPosition(project, editor)
return
}
if (!target.canRefactor()) return
invoke(project, arrayOf(target), dataContext)
}
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
val element = elements.singleOrNull() ?: return
val editor = dataContext?.let { CommonDataKeys.EDITOR.getData(it) }
val (classOrObject, member) = when (element) {
is JetNamedFunction, is JetProperty -> element.getStrictParentOfType<JetClassOrObject>() to element as JetNamedDeclaration?
is JetClassOrObject -> element to null
else -> {
reportWrongPosition(project, editor)
return
}
}
invoke(project, editor, classOrObject, member, dataContext)
}
private fun invoke(project: Project,
editor: Editor?,
classOrObject: JetClassOrObject?,
member: JetNamedDeclaration?,
dataContext: DataContext?) {
if (classOrObject == null) {
reportWrongContext(project, editor)
return
}
val classDescriptor = classOrObject.resolveToDescriptor() as ClassDescriptor
val superClasses = classDescriptor.getDefaultType()
.supertypes()
.asSequence()
.map {
val descriptor = it.getConstructor().getDeclarationDescriptor()
val declaration = descriptor?.let { DescriptorToSourceUtilsIde.getAnyDeclaration(project, it) }
if (declaration is JetClass && declaration.canRefactor()) declaration else null
}
.filterNotNull()
.toSortedListBy { it.qualifiedNameForRendering() }
if (superClasses.isEmpty()) {
val containingClass = classOrObject.getStrictParentOfType<JetClassOrObject>()
if (containingClass != null) {
invoke(project, editor, containingClass, classOrObject, dataContext)
}
else {
reportNoSuperClasses(project, editor, classOrObject)
}
return
}
val memberInfoStorage = KotlinMemberInfoStorage(classOrObject)
val members = memberInfoStorage.getClassMemberInfos(classOrObject)
if (ApplicationManager.getApplication().isUnitTestMode()) {
val helper = dataContext?.getData(PULLUP_TEST_HELPER_KEY) as TestHelper
KotlinPullUpDialog.createProcessor(classOrObject,
helper.chooseSuperClass(superClasses),
helper.adjustMembers(members)).run()
}
else {
val manager = classOrObject.getManager()
members.filter { manager.areElementsEquivalent(it.getMember(), member) }.forEach { it.setChecked(true) }
KotlinPullUpDialog(project, classOrObject, superClasses, memberInfoStorage).show()
}
}
override fun isEnabledOnElements(elements: Array<out PsiElement>): Boolean {
return elements.mapTo(HashSet<PsiElement>()) {
when (it) {
is JetNamedFunction, is JetProperty ->
(it.getParent() as? JetClassBody)?.getParent() as? JetClassOrObject
is JetClassOrObject -> it
else -> null
} ?: return false
}.size() == 1
}
}
val PULL_MEMBERS_UP = "Pull Members Up"
@@ -0,0 +1,393 @@
/*
* 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.pullUp
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiSubstitutor
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.classMembers.MemberInfoBase
import com.intellij.refactoring.memberPullUp.PullUpData
import com.intellij.refactoring.memberPullUp.PullUpHelper
import org.jetbrains.kotlin.asJava.getRepresentativeLightMethod
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.refactoring.safeDelete.removeOverrideModifier
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.lexer.JetModifierKeywordToken
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.allChildren
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiverOrThis
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.util.findCallableMemberBySignature
import java.util.ArrayList
import java.util.LinkedHashSet
class KotlinPullUpHelper(
private val javaData: PullUpData,
private val data: KotlinPullUpData
) : PullUpHelper<MemberInfoBase<PsiMember>> {
companion object {
private var JetElement.newFqName: FqName? by CopyableUserDataProperty(Key.create("NEW_FQ_NAME"))
private var JetElement.replaceWithTargetThis: Boolean? by CopyableUserDataProperty(Key.create("REPLACE_WITH_TARGET_THIS"))
private var JetElement.newTypeText: String? by CopyableUserDataProperty(Key.create("NEW_TYPE_TEXT"))
}
private fun willBeUsedInSourceClass(member: PsiElement): Boolean {
return !ReferencesSearch
.search(member, LocalSearchScope(data.sourceClass), false)
.all { it.getElement().parentsWithSelf.any { it in data.membersToMove } }
}
private fun liftToProtected(declaration: JetNamedDeclaration, ignoreUsages: Boolean = false) {
if (!declaration.hasModifier(JetTokens.PRIVATE_KEYWORD)) return
if (ignoreUsages || willBeUsedInSourceClass(declaration)) declaration.addModifierWithSpace(JetTokens.PROTECTED_KEYWORD)
}
override fun setCorrectVisibility(info: MemberInfoBase<PsiMember>) {
val member = info.getMember().namedUnwrappedElement as? JetNamedDeclaration ?: return
if (data.targetClass.isInterface()) {
member.removeModifier(JetTokens.PUBLIC_KEYWORD)
return
}
if (member.hasModifier(JetTokens.PRIVATE_KEYWORD)) {
member.accept(
object: JetVisitorVoid() {
override fun visitNamedDeclaration(declaration: JetNamedDeclaration) {
when (declaration) {
is JetClass -> {
liftToProtected(declaration)
declaration.getDeclarations().forEach { it.accept(this) }
}
is JetNamedFunction, is JetProperty -> {
liftToProtected(declaration, declaration == member && info.isToAbstract())
}
}
}
}
)
}
}
override fun encodeContextInfo(info: MemberInfoBase<PsiMember>) {
}
private fun getClashingMemberInTargetClass(memberDescriptor: CallableMemberDescriptor): CallableMemberDescriptor? {
val memberInSuper = memberDescriptor.substitute(data.sourceToTargetClassSubstitutor) ?: return null
return data.targetClassDescriptor.findCallableMemberBySignature(memberInSuper as CallableMemberDescriptor)
}
private fun fixOverrideAndGetClashingSuper(sourceMember: JetCallableDeclaration,
targetMember: JetCallableDeclaration): JetCallableDeclaration? {
val memberDescriptor = data.memberDescriptors[sourceMember] as CallableMemberDescriptor
if (memberDescriptor.getOverriddenDescriptors().isEmpty()) {
targetMember.removeOverrideModifier()
return null
}
val clashingSuperDescriptor = getClashingMemberInTargetClass(memberDescriptor) ?: return null
if (clashingSuperDescriptor.getOverriddenDescriptors().isEmpty()) {
targetMember.removeOverrideModifier()
}
return clashingSuperDescriptor.getSource().getPsi() as? JetCallableDeclaration
}
private fun makeAbstract(sourceMember: JetCallableDeclaration, targetMember: JetCallableDeclaration) {
if (!data.targetClass.isInterface()) {
targetMember.addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD)
}
if (sourceMember.getTypeReference() == null) {
var type = (data.memberDescriptors[sourceMember] as CallableMemberDescriptor).getReturnType()
if (type == null || type.isError()) {
type = KotlinBuiltIns.getInstance().getNullableAnyType()
}
else {
type = data.sourceToTargetClassSubstitutor.substitute(type, Variance.INVARIANT)
?: KotlinBuiltIns.getInstance().getNullableAnyType()
}
if (sourceMember is JetProperty || !type.isUnit()) {
val typeRef = JetPsiFactory(sourceMember).createType(IdeDescriptorRenderers.SOURCE_CODE.renderType(type))
targetMember.setTypeReference(typeRef)
}
}
val deleteFrom = when (sourceMember) {
is JetProperty -> {
targetMember as JetProperty
val accessors = targetMember.getAccessors()
targetMember.getEqualsToken() ?: targetMember.getDelegate() ?: accessors.firstOrNull()
}
is JetNamedFunction -> {
targetMember as JetNamedFunction
targetMember.getEqualsToken() ?: targetMember.getBodyExpression()
}
else -> null
}
if (deleteFrom != null) {
targetMember.deleteChildRange(deleteFrom, targetMember.getLastChild())
}
}
private fun addMemberToTarget(targetMember: JetNamedDeclaration): JetNamedDeclaration {
val anchor = data.targetClass.getDeclarations().filterIsInstance(targetMember.javaClass).lastOrNull()
val movedMember = when {
anchor == null && targetMember is JetProperty -> data.targetClass.addDeclarationBefore(targetMember, null)
else -> data.targetClass.addDeclarationAfter(targetMember, anchor)
}
return movedMember as JetNamedDeclaration
}
private fun doAddCallableMember(memberCopy: JetCallableDeclaration, clashingSuper: JetCallableDeclaration?): JetCallableDeclaration {
if (clashingSuper != null && clashingSuper.hasModifier(JetTokens.ABSTRACT_KEYWORD)) {
return clashingSuper.replaced(memberCopy)
}
return addMemberToTarget(memberCopy) as JetCallableDeclaration
}
private fun markElements(member: JetNamedDeclaration): List<JetElement> {
val affectedElements = ArrayList<JetElement>()
member.accept(
object: JetVisitorVoid() {
private fun visitSuperOrThis(expression: JetInstanceExpressionWithLabel) {
val referenceTarget = data.sourceClassContext[BindingContext.REFERENCE_TARGET, expression.getInstanceReference()]
if (referenceTarget == data.targetClassDescriptor) {
expression.replaceWithTargetThis = true
affectedElements.add(expression)
}
}
override fun visitElement(element: PsiElement) {
element.allChildren.forEach { it.accept(this) }
}
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
val resolvedCall = expression.getResolvedCall(data.sourceClassContext) ?: return
var receiver = resolvedCall.getExplicitReceiverValue()
if (!receiver.exists()) {
receiver = resolvedCall.getExtensionReceiver()
}
if (!receiver.exists()) {
receiver = resolvedCall.getDispatchReceiver()
}
if (!receiver.exists()) return
val implicitThis = receiver.getType().getConstructor().getDeclarationDescriptor() as? ClassDescriptor ?: return
if (implicitThis.isCompanionObject()
&& DescriptorUtils.isAncestor(data.sourceClassDescriptor, implicitThis, true)) {
val qualifierFqName = implicitThis.importableFqName ?: return
expression.newFqName = FqName("${qualifierFqName.asString()}.${expression.getReferencedName()}")
affectedElements.add(expression)
}
}
override fun visitThisExpression(expression: JetThisExpression) {
visitSuperOrThis(expression)
}
override fun visitSuperExpression(expression: JetSuperExpression) {
visitSuperOrThis(expression)
}
override fun visitTypeReference(typeReference: JetTypeReference) {
val oldType = data.sourceClassContext[BindingContext.TYPE, typeReference] ?: return
val newType = data.sourceToTargetClassSubstitutor.substitute(oldType, Variance.INVARIANT) ?: return
typeReference.newTypeText = IdeDescriptorRenderers.SOURCE_CODE.renderType(newType)
affectedElements.add(typeReference)
}
}
)
return affectedElements
}
private fun processMarkedElements(member: JetNamedDeclaration) {
val psiFactory = JetPsiFactory(member)
val targetThis = psiFactory.createExpression("this@${data.targetClassDescriptor.getName().asString()}")
val shorteningOptionsForThis = ShortenReferences.Options(removeThisLabels = true, removeThis = true)
member.accept(
object: JetVisitorVoid() {
private fun visitSuperOrThis(expression: JetInstanceExpressionWithLabel) {
expression.replaceWithTargetThis?.let {
expression.replaceWithTargetThis = null
val newThisExpression = expression.replace(targetThis) as JetExpression
newThisExpression.getQualifiedExpressionForReceiverOrThis().addToShorteningWaitSet(shorteningOptionsForThis)
}
}
override fun visitElement(element: PsiElement) {
for (it in element.allChildren.toList()) {
it.accept(this)
}
}
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
expression.newFqName?.let {
expression.newFqName = null
expression.mainReference.bindToFqName(it)
}
}
override fun visitThisExpression(expression: JetThisExpression) {
visitSuperOrThis(expression)
}
override fun visitSuperExpression(expression: JetSuperExpression) {
visitSuperOrThis(expression)
}
override fun visitTypeReference(typeReference: JetTypeReference) {
typeReference.newTypeText?.let {
typeReference.newTypeText = null
(typeReference.replace(psiFactory.createType(it)) as JetElement).addToShorteningWaitSet()
}
}
}
)
}
private fun clearMarking(markedElements: List<JetElement>) {
markedElements.forEach {
it.newFqName = null
it.newTypeText = null
it.replaceWithTargetThis = null
}
}
// TODO: Formatting rules don't apply here for some reason
private fun JetNamedDeclaration.addModifierWithSpace(modifier: JetModifierKeywordToken) {
addModifier(modifier)
addAfter(JetPsiFactory(this).createWhiteSpace(), getModifierList())
}
override fun move(info: MemberInfoBase<PsiMember>, substitutor: PsiSubstitutor) {
val member = info.getMember().namedUnwrappedElement as? JetNamedDeclaration ?: return
val markedElements = markElements(member)
val memberCopy = member.copy() as JetNamedDeclaration
try {
var movedMember: JetNamedDeclaration
when (member) {
is JetCallableDeclaration -> {
val clashingSuper = fixOverrideAndGetClashingSuper(member, memberCopy as JetCallableDeclaration)
val originalIsAbstract = member.hasModifier(JetTokens.ABSTRACT_KEYWORD)
val toAbstract = when {
info.isToAbstract() -> true
!data.targetClass.isInterface() -> false
member is JetProperty -> member.mustBeAbstractInInterface()
else -> false
}
if (toAbstract) {
if (!originalIsAbstract) {
makeAbstract(member, memberCopy)
}
movedMember = doAddCallableMember(memberCopy, clashingSuper)
if (member.getTypeReference() == null) {
movedMember.getTypeReference()?.addToShorteningWaitSet()
}
if (originalIsAbstract) {
member.delete()
}
else if (!member.hasModifier(JetTokens.OVERRIDE_KEYWORD)) {
member.addModifierWithSpace(JetTokens.OVERRIDE_KEYWORD)
}
}
else {
movedMember = doAddCallableMember(memberCopy, clashingSuper)
member.delete()
}
if (originalIsAbstract && data.targetClass.isInterface()) {
movedMember.removeModifier(JetTokens.ABSTRACT_KEYWORD)
}
if (!data.targetClass.isInterface()
&& !data.targetClass.hasModifier(JetTokens.ABSTRACT_KEYWORD)
&& (movedMember.hasModifier(JetTokens.ABSTRACT_KEYWORD))) {
data.targetClass.addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD)
}
movedMember.getRepresentativeLightMethod()?.let { javaData.getMovedMembers().add(it) }
}
is JetClassOrObject -> {
if (data.targetClass.isInterface()) {
memberCopy.removeModifier(JetTokens.INNER_KEYWORD)
}
movedMember = addMemberToTarget(memberCopy) as JetClassOrObject
member.delete()
movedMember.toLightClass()?.let { javaData.getMovedMembers().add(it) }
}
else -> return
}
processMarkedElements(movedMember)
}
finally {
clearMarking(markedElements)
}
}
override fun postProcessMember(member: PsiMember) {
}
override fun moveFieldInitializations(movedFields: LinkedHashSet<PsiField>) {
}
override fun updateUsage(element: PsiElement) {
}
}
@@ -0,0 +1,45 @@
/*
* 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.pullUp
import com.intellij.refactoring.memberPullUp.PullUpData
import com.intellij.refactoring.memberPullUp.PullUpHelper
import com.intellij.refactoring.memberPullUp.PullUpHelperFactory
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.psi.JetClass
import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.startOffset
public class KotlinPullUpHelperFactory : PullUpHelperFactory {
private fun PullUpData.toKotlinPullUpData(): KotlinPullUpData? {
if (!getSourceClass().isInheritor(getTargetClass(), true)) return null
val sourceClass = getSourceClass().unwrapped as? JetClassOrObject ?: return null
val targetClass = getTargetClass().unwrapped as? JetClass ?: return null
val membersToMove = getMembersToMove()
.map { it.namedUnwrappedElement as? JetNamedDeclaration }
.filterNotNull()
.sortBy { it.startOffset }
return KotlinPullUpData(sourceClass, targetClass, membersToMove)
}
override fun createPullUpHelper(data: PullUpData): PullUpHelper<*> {
val kotlinPullUpData = data.toKotlinPullUpData() ?: return EmptyPullUpHelper
return KotlinPullUpHelper(data, kotlinPullUpData)
}
}
@@ -0,0 +1,22 @@
/*
* 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.pullUp
import org.jetbrains.kotlin.psi.JetProperty
fun JetProperty.mustBeAbstractInInterface() =
hasInitializer() || hasDelegate() || (!hasInitializer() && !hasDelegate() && getAccessors().isEmpty())
+27
View File
@@ -0,0 +1,27 @@
// WITH_RUNTIME
open class A
abstract class <caret>B: A() {
// INFO: {"checked": "true"}
val x = 1
// INFO: {"checked": "true"}
val y: Int get() = 2
// INFO: {"checked": "true"}
val z: Int by lazy { 3 }
// INFO: {"checked": "true"}
abstract val t: Int
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true"}
abstract fun bar(s: String)
// INFO: {"checked": "true"}
inner class X {
}
// INFO: {"checked": "true"}
class Y {
}
}
@@ -0,0 +1,31 @@
// WITH_RUNTIME
abstract class A {
// INFO: {"checked": "true"}
val x = 1
// INFO: {"checked": "true"}
val y: Int get() = 2
// INFO: {"checked": "true"}
val z: Int by lazy { 3 }
// INFO: {"checked": "true"}
abstract val t: Int
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true"}
abstract fun bar(s: String)
// INFO: {"checked": "true"}
inner class X {
}
// INFO: {"checked": "true"}
class Y {
}
}
abstract class B: A() {
}
@@ -0,0 +1,27 @@
// WITH_RUNTIME
open class A
abstract class <caret>B: A() {
// INFO: {"checked": "true", "toAbstract": "true"}
val x = 1
// INFO: {"checked": "true", "toAbstract": "true"}
val y: Int get() = 2
// INFO: {"checked": "true", "toAbstract": "true"}
val z: Int by lazy { 3 }
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val t: Int
// INFO: {"checked": "true", "toAbstract": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true", "toAbstract": "true"}
abstract fun bar(s: String)
// INFO: {"checked": "true", "toAbstract": "true"}
inner class X {
}
// INFO: {"checked": "true", "toAbstract": "true"}
class Y {
}
}
@@ -0,0 +1,40 @@
// WITH_RUNTIME
abstract class A {
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val x: Int
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val y: Int
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val z: Int
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val t: Int
// INFO: {"checked": "true", "toAbstract": "true"}
abstract fun foo(n: Int): Boolean
// INFO: {"checked": "true", "toAbstract": "true"}
abstract fun bar(s: String)
// INFO: {"checked": "true", "toAbstract": "true"}
inner class X {
}
// INFO: {"checked": "true", "toAbstract": "true"}
class Y {
}
}
abstract class B: A() {
// INFO: {"checked": "true", "toAbstract": "true"}
override val x = 1
// INFO: {"checked": "true", "toAbstract": "true"}
override val y: Int get() = 2
// INFO: {"checked": "true", "toAbstract": "true"}
override val z: Int by lazy { 3 }
// INFO: {"checked": "true", "toAbstract": "true"}
override fun foo(n: Int): Boolean = n > 0
}
@@ -0,0 +1,36 @@
// WITH_RUNTIME
interface I
interface Z<T>
open class A<T: I, U: I, V>
class C<W: I> {
inner class <caret>B<X: I, Y>(x: X, y: Y): A<X, I, Z<Y>>() {
// INFO: {"checked": "true"}
fun foo<S>(x1: X, x2: Z<X>, y1: Y, y2: Z<Y>, w1: W, w2: Z<W>, s1: S, s2: Z<S>) {
}
// INFO: {"checked": "true"}
inner class Foo<S>(x1: X, x2: Z<X>, y1: Y, y2: Z<Y>, w1: W, w2: Z<W>, s1: S, s2: Z<S>): A<X, I, Z<Y>>(), Z<W> {
}
// INFO: {"checked": "true"}
val foo1: X
// INFO: {"checked": "true"}
val foo2: Z<X>
// INFO: {"checked": "true"}
val foo3: Y
// INFO: {"checked": "true"}
val foo4: Z<Y>
// INFO: {"checked": "true", "toAbstract": "true"}
val foo5 = x
// INFO: {"checked": "true", "toAbstract": "true"}
val foo6 = object: Z<X> {}
// INFO: {"checked": "true", "toAbstract": "true"}
val foo7 = y
// INFO: {"checked": "true", "toAbstract": "true"}
val foo8 = object: Z<Y> {}
}
}
@@ -0,0 +1,47 @@
// WITH_RUNTIME
interface I
interface Z<T>
abstract class A<T: I, U: I, V> {
// INFO: {"checked": "true"}
val foo1: T
// INFO: {"checked": "true"}
val foo2: Z<T>
// INFO: {"checked": "true"}
val foo3: Any?
// INFO: {"checked": "true"}
val foo4: Z<Any?>
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val foo5: T
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val foo6: Z<T>
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val foo7: Any?
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val foo8: Z<Any?>
// INFO: {"checked": "true"}
fun foo<S>(x1: T, x2: Z<T>, y1: Any?, y2: Z<Any?>, w1: I, w2: Z<I>, s1: S, s2: Z<S>) {
}
// INFO: {"checked": "true"}
inner class Foo<S>(x1: T, x2: Z<T>, y1: Any?, y2: Z<Any?>, w1: I, w2: Z<I>, s1: S, s2: Z<S>): A<T, I, Z<Any?>>(), Z<I> {
}
}
class C<W: I> {
inner class B<X: I, Y>(x: X, y: Y): A<X, I, Z<Y>>() {
// INFO: {"checked": "true", "toAbstract": "true"}
override val foo5 = x
// INFO: {"checked": "true", "toAbstract": "true"}
override val foo6 = object: Z<X> {}
// INFO: {"checked": "true", "toAbstract": "true"}
override val foo7 = y
// INFO: {"checked": "true", "toAbstract": "true"}
override val foo8 = object: Z<Y> {}
}
}
@@ -0,0 +1,27 @@
// WITH_RUNTIME
interface T
abstract class <caret>B: T {
// INFO: {"checked": "true"}
val x = 1
// INFO: {"checked": "true"}
val y: Int get() = 2
// INFO: {"checked": "true"}
val z: Int by lazy { 3 }
// INFO: {"checked": "true"}
abstract val t: Int
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true"}
abstract fun bar(s: String)
// INFO: {"checked": "true"}
inner class X {
}
// INFO: {"checked": "true"}
class Y {
}
}
@@ -0,0 +1,35 @@
// WITH_RUNTIME
interface T {
// INFO: {"checked": "true"}
val x: Int
// INFO: {"checked": "true"}
val y: Int get() = 2
// INFO: {"checked": "true"}
val z: Int
// INFO: {"checked": "true"}
val t: Int
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true"}
fun bar(s: String)
// INFO: {"checked": "true"}
class X {
}
// INFO: {"checked": "true"}
class Y {
}
}
abstract class B: T {
// INFO: {"checked": "true"}
override val x = 1
// INFO: {"checked": "true"}
override val z: Int by lazy { 3 }
}
@@ -0,0 +1,27 @@
// WITH_RUNTIME
interface T
abstract class <caret>B: T {
// INFO: {"checked": "true", "toAbstract": "true"}
val x = 1
// INFO: {"checked": "true", "toAbstract": "true"}
val y: Int get() = 2
// INFO: {"checked": "true", "toAbstract": "true"}
val z: Int by lazy { 3 }
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val t: Int
// INFO: {"checked": "true", "toAbstract": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true", "toAbstract": "true"}
abstract fun bar(s: String)
// INFO: {"checked": "true", "toAbstract": "true"}
inner class X {
}
// INFO: {"checked": "true", "toAbstract": "true"}
class Y {
}
}
@@ -0,0 +1,40 @@
// WITH_RUNTIME
interface T {
// INFO: {"checked": "true", "toAbstract": "true"}
val x: Int
// INFO: {"checked": "true", "toAbstract": "true"}
val y: Int
// INFO: {"checked": "true", "toAbstract": "true"}
val z: Int
// INFO: {"checked": "true", "toAbstract": "true"}
val t: Int
// INFO: {"checked": "true", "toAbstract": "true"}
fun foo(n: Int): Boolean
// INFO: {"checked": "true", "toAbstract": "true"}
fun bar(s: String)
// INFO: {"checked": "true", "toAbstract": "true"}
class X {
}
// INFO: {"checked": "true", "toAbstract": "true"}
class Y {
}
}
abstract class B: T {
// INFO: {"checked": "true", "toAbstract": "true"}
override val x = 1
// INFO: {"checked": "true", "toAbstract": "true"}
override val y: Int get() = 2
// INFO: {"checked": "true", "toAbstract": "true"}
override val z: Int by lazy { 3 }
// INFO: {"checked": "true", "toAbstract": "true"}
override fun foo(n: Int): Boolean = n > 0
}
@@ -0,0 +1,12 @@
open class A
class X {
class <caret>B: A() {
// INFO: {"checked": "true"}
fun foo(): Int = bar() + B.bar() + X.B.bar()
companion object {
fun bar() = 1
}
}
}
@@ -0,0 +1,13 @@
open class A {
// INFO: {"checked": "true"}
fun foo(): Int = X.B.bar() + X.B.bar() + X.B.bar()
}
class X {
class B: A() {
companion object {
fun bar() = 1
}
}
}
+4
View File
@@ -0,0 +1,4 @@
class A {
val x: Int = 1
}
+2
View File
@@ -0,0 +1,2 @@
Cannot perform refactoring.
Caret should be positioned inside a class to pull members from
+3
View File
@@ -0,0 +1,3 @@
class <caret>A {
val x: Int = 1
}
@@ -0,0 +1,2 @@
Cannot perform refactoring.
A does not have base classes/interfaces in current project
+3
View File
@@ -0,0 +1,3 @@
fun <caret>test() {
}
@@ -0,0 +1,2 @@
Cannot perform refactoring.
Caret should be positioned inside a class to pull members from
+6
View File
@@ -0,0 +1,6 @@
interface I
class <caret>A: I {
// INFO: {"checked": "true"}
public fun foo(): Int = 1
}
@@ -0,0 +1,7 @@
interface I {
// INFO: {"checked": "true"}
fun foo(): Int = 1
}
class A: I {
}
+10
View File
@@ -0,0 +1,10 @@
open class A {
open fun bar() = 1
}
class <caret>B: A() {
override fun bar() = 1
// INFO: {"checked": "true"}
fun foo() = super.bar()
}
+10
View File
@@ -0,0 +1,10 @@
open class A {
open fun bar() = 1
// INFO: {"checked": "true"}
fun foo() = bar()
}
class B: A() {
override fun bar() = 1
}
@@ -0,0 +1,13 @@
// TARGET_CLASS: A
open class A {
}
open class B: A() {
}
class <caret>C: B() {
// INFO: {"checked": "true"}
val x: Int
}
@@ -0,0 +1,13 @@
// TARGET_CLASS: A
open class A {
// INFO: {"checked": "true"}
val x: Int
}
open class B: A() {
}
class C: B() {
}
@@ -0,0 +1,8 @@
open class A
class <caret>B: A() {
// INFO: {"checked": "true"}
private fun foo(): Int = 1
fun bar() = foo() + 1
}
@@ -0,0 +1,9 @@
open class A {
// INFO: {"checked": "true"}
protected fun foo(): Int = 1
}
class B: A() {
fun bar() = foo() + 1
}
@@ -52,7 +52,7 @@ import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.JetTestUtils
import org.jetbrains.kotlin.test.util.findElementByComment
import org.jetbrains.kotlin.test.util.findElementByCommentPrefix
import org.jetbrains.kotlin.utils.emptyOrSingletonList
import java.io.File
import java.util.Collections
@@ -106,7 +106,7 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
KotlinIntroduceParameterHandler(HelperImpl())
}
with (handler) {
val target = (file as JetFile).findElementByComment("// TARGET:") as? JetNamedDeclaration
val target = (file as JetFile).findElementByCommentPrefix("// TARGET:") as? JetNamedDeclaration
if (target != null) {
JetRefactoringUtil.selectExpression(fixture.getEditor(), file, true) { expression ->
invoke(fixture.getProject(), fixture.getEditor(), expression!!, target)
@@ -208,7 +208,7 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
val extractionTarget = propertyTargets.single {
it.name == InTextDirectivesUtils.findStringWithPrefixes(file.getText(), "// EXTRACTION_TARGET: ")
}
val explicitPreviousSibling = file.findElementByComment("// SIBLING:")
val explicitPreviousSibling = file.findElementByCommentPrefix("// SIBLING:")
val helper = object : ExtractionEngineHelper(INTRODUCE_PROPERTY) {
override fun configureAndRun(
project: Project,
@@ -237,7 +237,7 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
doTest(path) { file ->
file as JetFile
val explicitPreviousSibling = file.findElementByComment("// SIBLING:")
val explicitPreviousSibling = file.findElementByCommentPrefix("// SIBLING:")
val fileText = file.getText() ?: ""
val expectedNames = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_NAMES: ")
val expectedReturnTypes = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_RETURN_TYPES: ")
@@ -0,0 +1,115 @@
/*
* 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.pullUp
import com.google.gson.JsonParser
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.io.FileUtil
import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.util.CommonRefactoringUtil
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
import org.jetbrains.kotlin.idea.stubindex.JetFullClassNameIndex
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinMultiFileTestCase
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.JetTestUtils
import org.jetbrains.kotlin.test.util.findElementByCommentPrefix
import org.jetbrains.kotlin.test.util.findElementsByCommentPrefix
import java.io.File
public abstract class AbstractPullUpTest : JetLightCodeInsightFixtureTestCase() {
private data class ElementInfo(val checked: Boolean, val toAbstract: Boolean)
companion object {
private var JetElement.elementInfo: ElementInfo
by NotNullableUserDataProperty(Key.create("ELEMENT_INFO"), ElementInfo(false, false))
}
override fun getProjectDescriptor() = LightCodeInsightFixtureTestCase.JAVA_LATEST
val fixture: JavaCodeInsightTestFixture get() = myFixture
protected override fun getTestDataPath() = PluginTestCaseBase.getTestDataPathBase()
protected fun doTest(path: String) {
val mainFile = File(path)
val afterFile = File("$path.after")
val conflictFile = File("$path.messages")
fixture.setTestDataPath("${JetTestUtils.getHomeDirectory()}/${mainFile.getParent()}")
val mainFileName = mainFile.getName()
val file = fixture.configureByFile(mainFileName) as JetFile
val addKotlinRuntime = InTextDirectivesUtils.findStringWithPrefixes(file.getText(), "// WITH_RUNTIME") != null
if (addKotlinRuntime) {
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
try {
for ((element, info) in file.findElementsByCommentPrefix("// INFO: ")) {
val parsedInfo = JsonParser().parse(info).getAsJsonObject()
element.elementInfo = ElementInfo(parsedInfo["checked"]?.getAsBoolean() ?: false,
parsedInfo["toAbstract"]?.getAsBoolean() ?: false)
}
val targetClassName = InTextDirectivesUtils.findStringWithPrefixes(file.getText(), "// TARGET_CLASS: ")
val helper = object: KotlinPullUpHandler.TestHelper {
override fun adjustMembers(members: List<KotlinMemberInfo>): List<KotlinMemberInfo> {
members.forEach {
val info = it.getMember().elementInfo
it.setChecked(info.checked)
it.setToAbstract(info.toAbstract)
}
return members.filter { it.isChecked() }
}
override fun chooseSuperClass(superClasses: List<JetClass>): JetClass {
if (targetClassName != null) {
return superClasses.single { it.getFqName()?.asString() ?: it.getName() == targetClassName }
}
return superClasses.first()
}
}
KotlinPullUpHandler().invoke(getProject(), getEditor(), file) {
if (it == KotlinPullUpHandler.PULLUP_TEST_HELPER_KEY) helper else null
}
assert(!conflictFile.exists()) { "Conflict file $conflictFile should not exist" }
JetTestUtils.assertEqualsToFile(afterFile, file.getText()!!)
}
catch(e: Exception) {
val message = when (e) {
is BaseRefactoringProcessor.ConflictsInTestsException -> e.getMessages().sort().joinToString("\n")
is CommonRefactoringUtil.RefactoringErrorHintException -> e.getMessage()!!
else -> throw e
}
JetTestUtils.assertEqualsToFile(conflictFile, message)
}
finally {
if (addKotlinRuntime) {
ConfigLibraryUtil.unConfigureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
}
}
}
@@ -0,0 +1,115 @@
/*
* 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.pullUp;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.JetTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/refactoring/pullUp")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class PullUpTestGenerated extends AbstractPullUpTest {
public void testAllFilesPresentInPullUp() throws Exception {
JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/refactoring/pullUp"), Pattern.compile("^(.+)\\.kt$"));
}
@TestMetadata("fromClassToClass.kt")
public void testFromClassToClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/fromClassToClass.kt");
doTest(fileName);
}
@TestMetadata("fromClassToClassMakeAbstract.kt")
public void testFromClassToClassMakeAbstract() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/fromClassToClassMakeAbstract.kt");
doTest(fileName);
}
@TestMetadata("fromClassToClassWithGenerics.kt")
public void testFromClassToClassWithGenerics() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/fromClassToClassWithGenerics.kt");
doTest(fileName);
}
@TestMetadata("fromClassToInterface.kt")
public void testFromClassToInterface() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/fromClassToInterface.kt");
doTest(fileName);
}
@TestMetadata("fromClassToInterfaceMakeAbstract.kt")
public void testFromClassToInterfaceMakeAbstract() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/fromClassToInterfaceMakeAbstract.kt");
doTest(fileName);
}
@TestMetadata("implicitCompanionUsages.kt")
public void testImplicitCompanionUsages() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/implicitCompanionUsages.kt");
doTest(fileName);
}
@TestMetadata("noCaret.kt")
public void testNoCaret() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/noCaret.kt");
doTest(fileName);
}
@TestMetadata("noSuperClass.kt")
public void testNoSuperClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/noSuperClass.kt");
doTest(fileName);
}
@TestMetadata("outsideOfClass.kt")
public void testOutsideOfClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/outsideOfClass.kt");
doTest(fileName);
}
@TestMetadata("publicToInterface.kt")
public void testPublicToInterface() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/publicToInterface.kt");
doTest(fileName);
}
@TestMetadata("superToThis.kt")
public void testSuperToThis() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/superToThis.kt");
doTest(fileName);
}
@TestMetadata("toIndirectSuperClass.kt")
public void testToIndirectSuperClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/toIndirectSuperClass.kt");
doTest(fileName);
}
@TestMetadata("usedPrivateToClass.kt")
public void testUsedPrivateToClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/usedPrivateToClass.kt");
doTest(fileName);
}
}