Refactoring
This commit is contained in:
+54
-57
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user