Refactoring

This commit is contained in:
Dmitry Gridin
2019-02-28 16:12:35 +03:00
parent b458e187af
commit 78bee70946
2 changed files with 84 additions and 95 deletions
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass
import com.intellij.codeInsight.intention.HighPriorityAction
import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.codeInsight.intention.impl.CreateClassDialog
import com.intellij.ide.util.DirectoryChooserUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
@@ -70,14 +69,14 @@ val ClassKind.actionPriority: IntentionActionPriority
get() = if (this == ANNOTATION_CLASS) IntentionActionPriority.LOW else IntentionActionPriority.NORMAL
data class ClassInfo(
val kind: ClassKind = ClassKind.DEFAULT,
val name: String,
private val targetParents: List<PsiElement>,
val expectedTypeInfo: TypeInfo,
val inner: Boolean = false,
val open: Boolean = false,
val typeArguments: List<TypeInfo> = Collections.emptyList(),
val parameterInfos: List<ParameterInfo> = Collections.emptyList()
val kind: ClassKind = ClassKind.DEFAULT,
val name: String,
private val targetParents: List<PsiElement>,
val expectedTypeInfo: TypeInfo,
val inner: Boolean = false,
val open: Boolean = false,
val typeArguments: List<TypeInfo> = Collections.emptyList(),
val parameterInfos: List<ParameterInfo> = Collections.emptyList()
) {
val applicableParents by lazy {
targetParents.filter {
@@ -87,10 +86,10 @@ data class ClassInfo(
}
}
open class CreateClassFromUsageFix<E : KtElement> protected constructor (
element: E,
private val classInfo: ClassInfo
): CreateFromUsageFixBase<E>(element) {
open class CreateClassFromUsageFix<E : KtElement> protected constructor(
element: E,
private val classInfo: ClassInfo
) : CreateFromUsageFixBase<E>(element) {
override fun getText() = "Create ${classInfo.kind.description} '${classInfo.name}'"
override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean {
@@ -114,11 +113,11 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
if (editor == null) return
val applicableParents = SmartList<PsiElement>().also { parents ->
classInfo.applicableParents.filterNotTo(parents) {
it is KtClassOrObject && it.superTypeListEntries.any {
classInfo.applicableParents.filterNotTo(parents) { element ->
element is KtClassOrObject && element.superTypeListEntries.any {
when (it) {
is KtDelegatedSuperTypeEntry, is KtSuperTypeEntry -> it.typeAsUserType == element
is KtSuperTypeCallEntry -> it == element
is KtDelegatedSuperTypeEntry, is KtSuperTypeEntry -> it.typeAsUserType == this.element
is KtSuperTypeCallEntry -> it == this.element
else -> false
}
}
@@ -129,8 +128,8 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
}
if (ApplicationManager.getApplication().isUnitTestMode) {
val targetParent = applicableParents.firstOrNull {
it.allChildren.any { it is PsiComment && it.text == "// TARGET_PARENT:" }
val targetParent = applicableParents.firstOrNull { element ->
element.allChildren.any { it is PsiComment && it.text == "// TARGET_PARENT:" }
} ?: classInfo.applicableParents.last()
return doInvoke(targetParent, editor, file)
}
@@ -141,22 +140,21 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
}
private fun createFileByPackage(
psiPackage: PsiPackage,
editor: Editor,
originalFile: KtFile
psiPackage: PsiPackage,
editor: Editor,
originalFile: KtFile
): KtFile? {
val directories = psiPackage.directories.filter { it.canRefactor() }
assert (directories.isNotEmpty()) { "Package '${psiPackage.qualifiedName}' must be refactorable" }
assert(directories.isNotEmpty()) { "Package '${psiPackage.qualifiedName}' must be refactorable" }
val currentModule = ModuleUtilCore.findModuleForPsiElement(originalFile)
val preferredDirectory =
directories.firstOrNull { ModuleUtilCore.findModuleForPsiElement(it) == currentModule }
directories.firstOrNull { ModuleUtilCore.findModuleForPsiElement(it) == currentModule }
?: directories.firstOrNull()
val targetDirectory = if (directories.size > 1 && !ApplicationManager.getApplication().isUnitTestMode) {
DirectoryChooserUtil.chooseDirectory(directories.toTypedArray(), preferredDirectory, originalFile.project, HashMap())
}
else {
} else {
preferredDirectory
} ?: return null
@@ -165,11 +163,11 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
if (targetFile == null) {
val filePath = "${targetDirectory.virtualFile.path}/$fileName"
CodeInsightUtils.showErrorHint(
targetDirectory.project,
editor,
"File $filePath already exists but does not correspond to Kotlin file",
"Create file",
null
targetDirectory.project,
editor,
"File $filePath already exists but does not correspond to Kotlin file",
"Create file",
null
)
}
return targetFile
@@ -186,13 +184,13 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
val ideaClassKind = classInfo.kind.toIdeaClassKind()
val defaultPackageFqName = file.packageFqName
val dialog = object : CreateKotlinClassDialog(
file.project,
"Create ${ideaClassKind.description.capitalize()}",
className,
defaultPackageFqName.asString(),
ideaClassKind,
false,
file.module
file.project,
"Create ${ideaClassKind.description.capitalize()}",
className,
defaultPackageFqName.asString(),
ideaClassKind,
false,
file.module
) {
override fun reportBaseInSourceSelectionInTest() = true
}
@@ -217,19 +215,19 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
runWriteAction {
with(classInfo) {
val targetParent =
when (selectedParent) {
is KtElement, is PsiClass -> selectedParent
is PsiPackage -> createFileByPackage(selectedParent, editor, file)
else -> throw AssertionError("Unexpected element: " + selectedParent.text)
} ?: return@runWriteAction
when (selectedParent) {
is KtElement, is PsiClass -> selectedParent
is PsiPackage -> createFileByPackage(selectedParent, editor, file)
else -> throw AssertionError("Unexpected element: " + selectedParent.text)
} ?: return@runWriteAction
val constructorInfo = ClassWithPrimaryConstructorInfo(classInfo, expectedTypeInfo)
val builder = CallableBuilderConfiguration(
Collections.singletonList(constructorInfo),
element,
file,
editor,
false,
kind == PLAIN_CLASS || kind == INTERFACE
Collections.singletonList(constructorInfo),
element,
file,
editor,
false,
kind == PLAIN_CLASS || kind == INTERFACE
).createBuilder()
builder.placement = CallablePlacement.NoReceiver(targetParent)
@@ -240,16 +238,15 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
if (targetPackageFqName == file.packageFqName) return@build
val reference = (element.getQualifiedElementSelector() as? KtSimpleNameExpression)?.mainReference ?: return@build
reference.bindToFqName(
targetPackageFqName.child(Name.identifier(className)),
KtSimpleNameReference.ShorteningMode.FORCED_SHORTENING
targetPackageFqName.child(Name.identifier(className)),
KtSimpleNameReference.ShorteningMode.FORCED_SHORTENING
)
}
}
if (startCommand) {
file.project.executeCommand(text, command = ::buildClass)
}
else {
} else {
buildClass()
}
}
@@ -257,13 +254,13 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
}
private class LowPriorityCreateClassFromUsageFix<E : KtElement>(
element: E,
classInfo: ClassInfo
element: E,
classInfo: ClassInfo
) : CreateClassFromUsageFix<E>(element, classInfo), LowPriorityAction
private class HighPriorityCreateClassFromUsageFix<E : KtElement>(
element: E,
classInfo: ClassInfo
element: E,
classInfo: ClassInfo
) : CreateClassFromUsageFix<E>(element, classInfo), HighPriorityAction
companion object {
@@ -379,14 +379,13 @@ fun <T> chooseContainerElement(
editor,
containers,
object : PsiElementListCellRenderer<PsiElement>() {
private fun PsiElement.renderName(): String {
if (this is KtPropertyAccessor) {
return property.renderName() + if (isGetter) ".get" else ".set"
private fun PsiElement.renderName(): String = when {
this is KtPropertyAccessor -> property.renderName() + if (isGetter) ".get" else ".set"
this is KtObjectDeclaration && isCompanion() -> {
val name = getStrictParentOfType<KtClassOrObject>()?.renderName() ?: "<anonymous>"
"Companion object of $name"
}
if (this is KtObjectDeclaration && this.isCompanion()) {
return "Companion object of ${getStrictParentOfType<KtClassOrObject>()?.renderName() ?: "<anonymous>"}"
}
return (this as? PsiNamedElement)?.name ?: "<anonymous>"
else -> (this as? PsiNamedElement)?.name ?: "<anonymous>"
}
private fun PsiElement.renderDeclaration(): String? {
@@ -399,9 +398,11 @@ fun <T> chooseContainerElement(
else -> null
} ?: return null
val name = renderName()
val params = (descriptor as? FunctionDescriptor)?.valueParameters
?.map { DescriptorRenderer.Companion.SHORT_NAMES_IN_TYPES.renderType(it.type) }
?.joinToString(", ", "(", ")") ?: ""
val params = (descriptor as? FunctionDescriptor)?.valueParameters?.joinToString(
", ",
"(",
")"
) { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it.type) } ?: ""
return "$name$params"
}
@@ -410,12 +411,10 @@ fun <T> chooseContainerElement(
return StringUtil.shortenTextWithEllipsis(text!!.collapseSpaces(), 53, 0)
}
private fun PsiElement.getRepresentativeElement(): PsiElement {
return when (this) {
is KtBlockExpression -> (parent as? KtDeclarationWithBody) ?: this
is KtClassBody -> parent as KtClassOrObject
else -> this
}
private fun PsiElement.getRepresentativeElement(): PsiElement = when (this) {
is KtBlockExpression -> (parent as? KtDeclarationWithBody) ?: this
is KtClassBody -> parent as KtClassOrObject
else -> this
}
override fun getElementText(element: PsiElement): String? {
@@ -457,19 +456,14 @@ fun <T> chooseContainerElementIfNecessary(
fun PsiElement.isTrueJavaMethod(): Boolean = this is PsiMethod && this !is KtLightMethod
fun PsiElement.canRefactor(): Boolean {
if (!this.isValid) return false
return when {
this is PsiPackage ->
directories.any { it.canRefactor() }
this is KtElement ||
this is PsiMember && language == JavaLanguage.INSTANCE ||
this is PsiDirectory ->
ProjectRootsUtil.isInProjectSource(this, includeScriptsOutsideSourceRoots = true)
else ->
false
}
fun PsiElement.canRefactor(): Boolean = when {
!isValid -> false
this is PsiPackage -> directories.any { it.canRefactor() }
this is KtElement || this is PsiMember && language == JavaLanguage.INSTANCE || this is PsiDirectory -> ProjectRootsUtil.isInProjectSource(
this,
includeScriptsOutsideSourceRoots = true
)
else -> false
}
private fun copyModifierListItems(from: PsiModifierList, to: PsiModifierList, withPsiModifiers: Boolean = true) {
@@ -645,7 +639,7 @@ fun PsiElement.j2kText(): String? {
val j2kConverter = JavaToKotlinConverter(
project,
ConverterSettings.Companion.defaultSettings,
ConverterSettings.defaultSettings,
IdeaJavaToKotlinServices
)
return j2kConverter.elementsToKotlin(listOf(this)).results.single()?.text ?: return null //TODO: insert imports
@@ -750,14 +744,12 @@ fun PsiNamedElement.isInterfaceClass(): Boolean = when (this) {
else -> false
}
fun KtNamedDeclaration.isAbstract(): Boolean {
if (hasModifier(KtTokens.ABSTRACT_KEYWORD)) return true
if (containingClassOrObject?.isInterfaceClass() != true) return false
return when (this) {
is KtProperty -> initializer == null && delegate == null && accessors.isEmpty()
is KtNamedFunction -> !hasBody()
else -> false
}
fun KtNamedDeclaration.isAbstract(): Boolean = when {
hasModifier(KtTokens.ABSTRACT_KEYWORD) -> true
containingClassOrObject?.isInterfaceClass() != true -> false
this is KtProperty -> initializer == null && delegate == null && accessors.isEmpty()
this is KtNamedFunction -> !hasBody()
else -> false
}
fun KtNamedDeclaration.isConstructorDeclaredProperty() = this is KtParameter && ownerFunction is KtPrimaryConstructor && hasValOrVar()