Create Class from Usage: Support nested classes
This covers the case when original expression doesn't contains qualifier Also for local or inner containing classes: - forbid nested objects - add 'inner' to nested class declaration #KT-16404 Fixed
This commit is contained in:
+7
-2
@@ -413,6 +413,11 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
|
|||||||
typeCandidates[typeInfo]?.forEach { it.render(typeParameterNameMap, fakeFunction) }
|
typeCandidates[typeInfo]?.forEach { it.render(typeParameterNameMap, fakeFunction) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isInsideInnerOrLocalClass(): Boolean {
|
||||||
|
val classOrObject = containingElement.getNonStrictParentOfType<KtClassOrObject>()
|
||||||
|
return classOrObject is KtClass && (classOrObject.isInner() || classOrObject.isLocal)
|
||||||
|
}
|
||||||
|
|
||||||
private fun createDeclarationSkeleton(): KtNamedDeclaration {
|
private fun createDeclarationSkeleton(): KtNamedDeclaration {
|
||||||
with (config) {
|
with (config) {
|
||||||
val assignmentToReplace =
|
val assignmentToReplace =
|
||||||
@@ -490,14 +495,14 @@ class CallableBuilder(val config: CallableBuilderConfiguration) {
|
|||||||
val safeName = name.quoteIfNeeded()
|
val safeName = name.quoteIfNeeded()
|
||||||
when (kind) {
|
when (kind) {
|
||||||
ClassKind.ENUM_ENTRY -> {
|
ClassKind.ENUM_ENTRY -> {
|
||||||
val targetParent = targetParents.singleOrNull()
|
val targetParent = applicableParents.singleOrNull()
|
||||||
if (!(targetParent is KtClass && targetParent.isEnum())) throw AssertionError("Enum class expected: ${targetParent?.text}")
|
if (!(targetParent is KtClass && targetParent.isEnum())) throw AssertionError("Enum class expected: ${targetParent?.text}")
|
||||||
val hasParameters = targetParent.primaryConstructorParameters.isNotEmpty()
|
val hasParameters = targetParent.primaryConstructorParameters.isNotEmpty()
|
||||||
psiFactory.createEnumEntry("$safeName${if (hasParameters) "()" else " "}")
|
psiFactory.createEnumEntry("$safeName${if (hasParameters) "()" else " "}")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val openMod = if (open) "open " else ""
|
val openMod = if (open) "open " else ""
|
||||||
val innerMod = if (inner) "inner " else ""
|
val innerMod = if (inner || isInsideInnerOrLocalClass()) "inner " else ""
|
||||||
val typeParamList = when (kind) {
|
val typeParamList = when (kind) {
|
||||||
ClassKind.PLAIN_CLASS, ClassKind.INTERFACE -> "<>"
|
ClassKind.PLAIN_CLASS, ClassKind.INTERFACE -> "<>"
|
||||||
else -> ""
|
else -> ""
|
||||||
|
|||||||
+1
-2
@@ -53,7 +53,6 @@ object CreateClassFromCallWithConstructorCalleeActionFactory : CreateClassFromUs
|
|||||||
val isAnnotation = element is KtAnnotationEntry
|
val isAnnotation = element is KtAnnotationEntry
|
||||||
val callee = element.calleeExpression as? KtConstructorCalleeExpression ?: return null
|
val callee = element.calleeExpression as? KtConstructorCalleeExpression ?: return null
|
||||||
val calleeRef = callee.constructorReferenceExpression ?: return null
|
val calleeRef = callee.constructorReferenceExpression ?: return null
|
||||||
val file = element.containingFile as? KtFile ?: return null
|
|
||||||
val typeRef = callee.typeReference ?: return null
|
val typeRef = callee.typeReference ?: return null
|
||||||
val userType = typeRef.typeElement as? KtUserType ?: return null
|
val userType = typeRef.typeElement as? KtUserType ?: return null
|
||||||
|
|
||||||
@@ -62,7 +61,7 @@ object CreateClassFromCallWithConstructorCalleeActionFactory : CreateClassFromUs
|
|||||||
val qualifier = userType.qualifier?.referenceExpression
|
val qualifier = userType.qualifier?.referenceExpression
|
||||||
val qualifierDescriptor = qualifier?.let { context[BindingContext.REFERENCE_TARGET, it] }
|
val qualifierDescriptor = qualifier?.let { context[BindingContext.REFERENCE_TARGET, it] }
|
||||||
|
|
||||||
val targetParents = getTargetParentsByQualifier(file, qualifier != null, qualifierDescriptor).ifEmpty { return null }
|
val targetParents = getTargetParentsByQualifier(element, qualifier != null, qualifierDescriptor).ifEmpty { return null }
|
||||||
|
|
||||||
val anyType = module.builtIns.nullableAnyType
|
val anyType = module.builtIns.nullableAnyType
|
||||||
val valueArguments = element.valueArguments
|
val valueArguments = element.valueArguments
|
||||||
|
|||||||
+2
-5
@@ -42,9 +42,8 @@ object CreateClassFromConstructorCallActionFactory: CreateClassFromUsageFactory<
|
|||||||
val inAnnotationEntry = diagnostic.psiElement.getNonStrictParentOfType<KtAnnotationEntry>() != null
|
val inAnnotationEntry = diagnostic.psiElement.getNonStrictParentOfType<KtAnnotationEntry>() != null
|
||||||
|
|
||||||
val (context, moduleDescriptor) = element.analyzeFullyAndGetResult()
|
val (context, moduleDescriptor) = element.analyzeFullyAndGetResult()
|
||||||
val file = element.containingFile as? KtFile ?: return emptyList()
|
|
||||||
val call = element.getCall(context) ?: return emptyList()
|
val call = element.getCall(context) ?: return emptyList()
|
||||||
val targetParents = getTargetParentsByCall(call, file, context).ifEmpty { return emptyList() }
|
val targetParents = getTargetParentsByCall(call, context).ifEmpty { return emptyList() }
|
||||||
|
|
||||||
val classKind = if (inAnnotationEntry) ClassKind.ANNOTATION_CLASS else ClassKind.PLAIN_CLASS
|
val classKind = if (inAnnotationEntry) ClassKind.ANNOTATION_CLASS else ClassKind.PLAIN_CLASS
|
||||||
val fullCallExpr = element.getQualifiedExpressionForSelectorOrThis()
|
val fullCallExpr = element.getQualifiedExpressionForSelectorOrThis()
|
||||||
@@ -72,12 +71,10 @@ object CreateClassFromConstructorCallActionFactory: CreateClassFromUsageFactory<
|
|||||||
val fullCallExpr =
|
val fullCallExpr =
|
||||||
if (callParent is KtQualifiedExpression && callParent.selectorExpression == callExpr) callParent else callExpr
|
if (callParent is KtQualifiedExpression && callParent.selectorExpression == callExpr) callParent else callExpr
|
||||||
|
|
||||||
val file = fullCallExpr.containingFile as? KtFile ?: return null
|
|
||||||
|
|
||||||
val (context, moduleDescriptor) = callExpr.analyzeFullyAndGetResult()
|
val (context, moduleDescriptor) = callExpr.analyzeFullyAndGetResult()
|
||||||
|
|
||||||
val call = callExpr.getCall(context) ?: return null
|
val call = callExpr.getCall(context) ?: return null
|
||||||
val targetParents = getTargetParentsByCall(call, file, context).ifEmpty { return null }
|
val targetParents = getTargetParentsByCall(call, context).ifEmpty { return null }
|
||||||
val inner = isInnerClassExpected(call)
|
val inner = isInnerClassExpected(call)
|
||||||
|
|
||||||
val valueArguments = callExpr.valueArguments
|
val valueArguments = callExpr.valueArguments
|
||||||
|
|||||||
+6
-16
@@ -57,8 +57,6 @@ object CreateClassFromReferenceExpressionActionFactory : CreateClassFromUsageFac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val file = element.containingFile as? KtFile ?: return Collections.emptyList()
|
|
||||||
|
|
||||||
val name = element.getReferencedName()
|
val name = element.getReferencedName()
|
||||||
|
|
||||||
val (context, moduleDescriptor) = element.analyzeFullyAndGetResult()
|
val (context, moduleDescriptor) = element.analyzeFullyAndGetResult()
|
||||||
@@ -70,11 +68,8 @@ object CreateClassFromReferenceExpressionActionFactory : CreateClassFromUsageFac
|
|||||||
val receiverSelector = (fullCallExpr as? KtQualifiedExpression)?.receiverExpression?.getQualifiedElementSelector() as? KtReferenceExpression
|
val receiverSelector = (fullCallExpr as? KtQualifiedExpression)?.receiverExpression?.getQualifiedElementSelector() as? KtReferenceExpression
|
||||||
val qualifierDescriptor = receiverSelector?.let { context[BindingContext.REFERENCE_TARGET, it] }
|
val qualifierDescriptor = receiverSelector?.let { context[BindingContext.REFERENCE_TARGET, it] }
|
||||||
|
|
||||||
val targetParents = getTargetParentsByQualifier(
|
val targetParents = getTargetParentsByQualifier(element, receiverSelector != null, qualifierDescriptor)
|
||||||
element.containingKtFile,
|
.ifEmpty { return emptyList() }
|
||||||
receiverSelector != null,
|
|
||||||
qualifierDescriptor
|
|
||||||
).ifEmpty { return emptyList() }
|
|
||||||
|
|
||||||
targetParents.forEach {
|
targetParents.forEach {
|
||||||
if (element.getCreatePackageFixIfApplicable(it) != null) return emptyList()
|
if (element.getCreatePackageFixIfApplicable(it) != null) return emptyList()
|
||||||
@@ -100,7 +95,7 @@ object CreateClassFromReferenceExpressionActionFactory : CreateClassFromUsageFac
|
|||||||
if (fullCallExpr.getAssignmentByLHS() != null) return Collections.emptyList()
|
if (fullCallExpr.getAssignmentByLHS() != null) return Collections.emptyList()
|
||||||
|
|
||||||
val call = element.getCall(context) ?: return Collections.emptyList()
|
val call = element.getCall(context) ?: return Collections.emptyList()
|
||||||
val targetParents = getTargetParentsByCall(call, file, context).ifEmpty { return emptyList() }
|
val targetParents = getTargetParentsByCall(call, context).ifEmpty { return emptyList() }
|
||||||
if (isInnerClassExpected(call)) return Collections.emptyList()
|
if (isInnerClassExpected(call)) return Collections.emptyList()
|
||||||
|
|
||||||
val allKinds = Arrays.asList(ClassKind.OBJECT, ClassKind.ENUM_ENTRY)
|
val allKinds = Arrays.asList(ClassKind.OBJECT, ClassKind.ENUM_ENTRY)
|
||||||
@@ -119,8 +114,6 @@ object CreateClassFromReferenceExpressionActionFactory : CreateClassFromUsageFac
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun extractFixData(element: KtSimpleNameExpression, diagnostic: Diagnostic): ClassInfo? {
|
override fun extractFixData(element: KtSimpleNameExpression, diagnostic: Diagnostic): ClassInfo? {
|
||||||
val file = element.containingFile as? KtFile ?: return null
|
|
||||||
|
|
||||||
val name = element.getReferencedName()
|
val name = element.getReferencedName()
|
||||||
|
|
||||||
val (context, moduleDescriptor) = element.analyzeFullyAndGetResult()
|
val (context, moduleDescriptor) = element.analyzeFullyAndGetResult()
|
||||||
@@ -131,11 +124,8 @@ object CreateClassFromReferenceExpressionActionFactory : CreateClassFromUsageFac
|
|||||||
val receiverSelector = (fullCallExpr as? KtQualifiedExpression)?.receiverExpression?.getQualifiedElementSelector() as? KtReferenceExpression
|
val receiverSelector = (fullCallExpr as? KtQualifiedExpression)?.receiverExpression?.getQualifiedElementSelector() as? KtReferenceExpression
|
||||||
val qualifierDescriptor = receiverSelector?.let { context[BindingContext.REFERENCE_TARGET, it] }
|
val qualifierDescriptor = receiverSelector?.let { context[BindingContext.REFERENCE_TARGET, it] }
|
||||||
|
|
||||||
val targetParents = getTargetParentsByQualifier(
|
val targetParents = getTargetParentsByQualifier(element, receiverSelector != null, qualifierDescriptor)
|
||||||
element.containingKtFile,
|
.ifEmpty { return null }
|
||||||
receiverSelector != null,
|
|
||||||
qualifierDescriptor
|
|
||||||
).ifEmpty { return null }
|
|
||||||
|
|
||||||
return ClassInfo(
|
return ClassInfo(
|
||||||
name = name,
|
name = name,
|
||||||
@@ -145,7 +135,7 @@ object CreateClassFromReferenceExpressionActionFactory : CreateClassFromUsageFac
|
|||||||
}
|
}
|
||||||
|
|
||||||
val call = element.getCall(context) ?: return null
|
val call = element.getCall(context) ?: return null
|
||||||
val targetParents = getTargetParentsByCall(call, file, context).ifEmpty { return null }
|
val targetParents = getTargetParentsByCall(call, context).ifEmpty { return null }
|
||||||
|
|
||||||
val expectedTypeInfo = fullCallExpr.guessTypeForClass(context, moduleDescriptor)?.toClassTypeInfo() ?: TypeInfo.Empty
|
val expectedTypeInfo = fullCallExpr.guessTypeForClass(context, moduleDescriptor)?.toClassTypeInfo() ?: TypeInfo.Empty
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -77,13 +77,11 @@ object CreateClassFromTypeReferenceActionFactory : CreateClassFromUsageFactory<K
|
|||||||
val name = element.referenceExpression?.getReferencedName() ?: return null
|
val name = element.referenceExpression?.getReferencedName() ?: return null
|
||||||
if (element.parent.parent is KtConstructorCalleeExpression) return null
|
if (element.parent.parent is KtConstructorCalleeExpression) return null
|
||||||
|
|
||||||
val file = element.containingFile as? KtFile ?: return null
|
|
||||||
|
|
||||||
val (context, module) = element.analyzeAndGetResult()
|
val (context, module) = element.analyzeAndGetResult()
|
||||||
val qualifier = element.qualifier?.referenceExpression
|
val qualifier = element.qualifier?.referenceExpression
|
||||||
val qualifierDescriptor = qualifier?.let { context[BindingContext.REFERENCE_TARGET, it] }
|
val qualifierDescriptor = qualifier?.let { context[BindingContext.REFERENCE_TARGET, it] }
|
||||||
|
|
||||||
val targetParents = getTargetParentsByQualifier(file, qualifier != null, qualifierDescriptor).ifEmpty { return null }
|
val targetParents = getTargetParentsByQualifier(element, qualifier != null, qualifierDescriptor).ifEmpty { return null }
|
||||||
val expectedUpperBound = getExpectedUpperBound(element, context)
|
val expectedUpperBound = getExpectedUpperBound(element, context)
|
||||||
|
|
||||||
val anyType = module.builtIns.anyType
|
val anyType = module.builtIns.anyType
|
||||||
|
|||||||
+81
-59
@@ -23,10 +23,7 @@ import com.intellij.openapi.application.ApplicationManager
|
|||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.module.ModuleUtilCore
|
import com.intellij.openapi.module.ModuleUtilCore
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.psi.PsiClass
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import com.intellij.psi.PsiFile
|
|
||||||
import com.intellij.psi.PsiPackage
|
|
||||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||||
import org.jetbrains.kotlin.idea.quickfix.IntentionActionPriority
|
import org.jetbrains.kotlin.idea.quickfix.IntentionActionPriority
|
||||||
@@ -38,8 +35,10 @@ import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary
|
|||||||
import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile
|
import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.allChildren
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
enum class ClassKind(val keyword: String, val description: String) {
|
enum class ClassKind(val keyword: String, val description: String) {
|
||||||
@@ -58,13 +57,20 @@ val ClassKind.actionPriority: IntentionActionPriority
|
|||||||
data class ClassInfo(
|
data class ClassInfo(
|
||||||
val kind: ClassKind = ClassKind.DEFAULT,
|
val kind: ClassKind = ClassKind.DEFAULT,
|
||||||
val name: String,
|
val name: String,
|
||||||
val targetParents: List<PsiElement>,
|
private val targetParents: List<PsiElement>,
|
||||||
val expectedTypeInfo: TypeInfo,
|
val expectedTypeInfo: TypeInfo,
|
||||||
val inner: Boolean = false,
|
val inner: Boolean = false,
|
||||||
val open: Boolean = false,
|
val open: Boolean = false,
|
||||||
val typeArguments: List<TypeInfo> = Collections.emptyList(),
|
val typeArguments: List<TypeInfo> = Collections.emptyList(),
|
||||||
val parameterInfos: List<ParameterInfo> = Collections.emptyList()
|
val parameterInfos: List<ParameterInfo> = Collections.emptyList()
|
||||||
)
|
) {
|
||||||
|
val applicableParents by lazy {
|
||||||
|
targetParents.filter {
|
||||||
|
if (kind == ClassKind.OBJECT && it is KtClass && (it.isInner() || it.isLocal)) return@filter false
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open class CreateClassFromUsageFix<E : KtElement> protected constructor (
|
open class CreateClassFromUsageFix<E : KtElement> protected constructor (
|
||||||
element: E,
|
element: E,
|
||||||
@@ -76,7 +82,8 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
|
|||||||
if (!super.isAvailable(project, editor, file)) return false
|
if (!super.isAvailable(project, editor, file)) return false
|
||||||
with(classInfo) {
|
with(classInfo) {
|
||||||
if (kind == DEFAULT) return false
|
if (kind == DEFAULT) return false
|
||||||
targetParents.forEach {
|
if (applicableParents.isEmpty()) return false
|
||||||
|
applicableParents.forEach {
|
||||||
if (it is PsiClass) {
|
if (it is PsiClass) {
|
||||||
if (kind == OBJECT || kind == ENUM_ENTRY) return false
|
if (kind == OBJECT || kind == ENUM_ENTRY) return false
|
||||||
if (it.isInterface && inner) return false
|
if (it.isInterface && inner) return false
|
||||||
@@ -90,60 +97,75 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
|
|||||||
override fun startInWriteAction() = false
|
override fun startInWriteAction() = false
|
||||||
|
|
||||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||||
fun createFileByPackage(psiPackage: PsiPackage): KtFile? {
|
|
||||||
val directories = psiPackage.directories.filter { it.canRefactor() }
|
|
||||||
assert (directories.isNotEmpty()) { "Package '${psiPackage.qualifiedName}' must be refactorable" }
|
|
||||||
|
|
||||||
val currentModule = ModuleUtilCore.findModuleForPsiElement(file)
|
|
||||||
val preferredDirectory =
|
|
||||||
directories.firstOrNull { ModuleUtilCore.findModuleForPsiElement(it) == currentModule }
|
|
||||||
?: directories.firstOrNull()
|
|
||||||
|
|
||||||
val targetDirectory = if (directories.size > 1 && !ApplicationManager.getApplication().isUnitTestMode) {
|
|
||||||
DirectoryChooserUtil.chooseDirectory(directories.toTypedArray(), preferredDirectory, project, HashMap())
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
preferredDirectory
|
|
||||||
} ?: return null
|
|
||||||
|
|
||||||
val fileName = "${classInfo.name}.${KotlinFileType.INSTANCE.defaultExtension}"
|
|
||||||
val targetFile = getOrCreateKotlinFile(fileName, targetDirectory)
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return targetFile
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editor == null) return
|
if (editor == null) return
|
||||||
|
|
||||||
with (classInfo) {
|
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
chooseContainerElementIfNecessary(targetParents, editor, "Choose class container", true, { it }) {
|
val targetParent = classInfo.applicableParents.firstOrNull {
|
||||||
runWriteAction {
|
it.allChildren.any { it is PsiComment && it.text == "// TARGET_PARENT:" }
|
||||||
val targetParent =
|
} ?: classInfo.applicableParents.last()
|
||||||
when (it) {
|
return doInvoke(targetParent, editor, file)
|
||||||
is KtElement, is PsiClass -> it
|
}
|
||||||
is PsiPackage -> createFileByPackage(it)
|
|
||||||
else -> throw AssertionError("Unexpected element: " + it.text)
|
chooseContainerElementIfNecessary(classInfo.applicableParents, editor, "Choose class container", true, { it }) {
|
||||||
} ?: return@runWriteAction
|
doInvoke(it, editor, file)
|
||||||
val constructorInfo = PrimaryConstructorInfo(classInfo, expectedTypeInfo)
|
}
|
||||||
val builder = CallableBuilderConfiguration(
|
}
|
||||||
Collections.singletonList(constructorInfo),
|
|
||||||
element as KtElement,
|
private fun createFileByPackage(
|
||||||
file,
|
psiPackage: PsiPackage,
|
||||||
editor,
|
editor: Editor,
|
||||||
false,
|
originalFile: KtFile
|
||||||
kind == PLAIN_CLASS || kind == INTERFACE
|
): KtFile? {
|
||||||
).createBuilder()
|
val directories = psiPackage.directories.filter { it.canRefactor() }
|
||||||
builder.placement = CallablePlacement.NoReceiver(targetParent)
|
assert (directories.isNotEmpty()) { "Package '${psiPackage.qualifiedName}' must be refactorable" }
|
||||||
project.executeCommand(text) { builder.build() }
|
|
||||||
}
|
val currentModule = ModuleUtilCore.findModuleForPsiElement(originalFile)
|
||||||
|
val preferredDirectory =
|
||||||
|
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 {
|
||||||
|
preferredDirectory
|
||||||
|
} ?: return null
|
||||||
|
|
||||||
|
val fileName = "${classInfo.name}.${KotlinFileType.INSTANCE.defaultExtension}"
|
||||||
|
val targetFile = getOrCreateKotlinFile(fileName, targetDirectory)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return targetFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doInvoke(selectedParent: PsiElement, editor: Editor, file: KtFile) {
|
||||||
|
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
|
||||||
|
val constructorInfo = PrimaryConstructorInfo(classInfo, expectedTypeInfo)
|
||||||
|
val builder = CallableBuilderConfiguration(
|
||||||
|
Collections.singletonList(constructorInfo),
|
||||||
|
element as KtElement,
|
||||||
|
file,
|
||||||
|
editor,
|
||||||
|
false,
|
||||||
|
kind == PLAIN_CLASS || kind == INTERFACE
|
||||||
|
).createBuilder()
|
||||||
|
builder.placement = CallablePlacement.NoReceiver(targetParent)
|
||||||
|
file.project.executeCommand(text) { builder.build() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-6
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.guessT
|
|||||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.noSubstitutions
|
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.noSubstitutions
|
||||||
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
@@ -51,14 +52,15 @@ internal fun String.checkClassName(): Boolean = isNotEmpty() && Character.isUppe
|
|||||||
private fun String.checkPackageName(): Boolean = isNotEmpty() && Character.isLowerCase(first())
|
private fun String.checkPackageName(): Boolean = isNotEmpty() && Character.isLowerCase(first())
|
||||||
|
|
||||||
internal fun getTargetParentsByQualifier(
|
internal fun getTargetParentsByQualifier(
|
||||||
file: KtFile,
|
element: KtElement,
|
||||||
isQualified: Boolean,
|
isQualified: Boolean,
|
||||||
qualifierDescriptor: DeclarationDescriptor?
|
qualifierDescriptor: DeclarationDescriptor?
|
||||||
): List<PsiElement> {
|
): List<PsiElement> {
|
||||||
|
val file = element.containingKtFile
|
||||||
val project = file.project
|
val project = file.project
|
||||||
val targetParents: List<PsiElement> = when {
|
val targetParents: List<PsiElement> = when {
|
||||||
!isQualified ->
|
!isQualified ->
|
||||||
listOf(file)
|
element.parents.filterIsInstance<KtClassOrObject>().toList() + file
|
||||||
qualifierDescriptor is ClassDescriptor ->
|
qualifierDescriptor is ClassDescriptor ->
|
||||||
listOfNotNull(DescriptorToSourceUtilsIde.getAnyDeclaration(project, qualifierDescriptor))
|
listOfNotNull(DescriptorToSourceUtilsIde.getAnyDeclaration(project, qualifierDescriptor))
|
||||||
qualifierDescriptor is PackageViewDescriptor ->
|
qualifierDescriptor is PackageViewDescriptor ->
|
||||||
@@ -72,12 +74,13 @@ internal fun getTargetParentsByQualifier(
|
|||||||
return targetParents.filter { it.canRefactor() }
|
return targetParents.filter { it.canRefactor() }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun getTargetParentsByCall(call: Call, file: KtFile, context: BindingContext): List<PsiElement> {
|
internal fun getTargetParentsByCall(call: Call, context: BindingContext): List<PsiElement> {
|
||||||
|
val callElement = call.callElement
|
||||||
val receiver = call.explicitReceiver
|
val receiver = call.explicitReceiver
|
||||||
return when (receiver) {
|
return when (receiver) {
|
||||||
null -> getTargetParentsByQualifier(file, false, null)
|
null -> getTargetParentsByQualifier(callElement, false, null)
|
||||||
is Qualifier -> getTargetParentsByQualifier(file, true, context[BindingContext.REFERENCE_TARGET, receiver.referenceExpression])
|
is Qualifier -> getTargetParentsByQualifier(callElement, true, context[BindingContext.REFERENCE_TARGET, receiver.referenceExpression])
|
||||||
is ReceiverValue -> getTargetParentsByQualifier(file, true, receiver.type.constructor.declarationDescriptor)
|
is ReceiverValue -> getTargetParentsByQualifier(callElement, true, receiver.type.constructor.declarationDescriptor)
|
||||||
else -> throw AssertionError("Unexpected receiver: $receiver")
|
else -> throw AssertionError("Unexpected receiver: $receiver")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ object CreateTypeAliasFromTypeReferenceActionFactory : KotlinSingleIntentionActi
|
|||||||
if (!element.languageVersionSettings.supportsFeature(LanguageFeature.TypeAliases)) return null
|
if (!element.languageVersionSettings.supportsFeature(LanguageFeature.TypeAliases)) return null
|
||||||
|
|
||||||
val classInfo = CreateClassFromTypeReferenceActionFactory.extractFixData(element, diagnostic) ?: return null
|
val classInfo = CreateClassFromTypeReferenceActionFactory.extractFixData(element, diagnostic) ?: return null
|
||||||
val targetParent = classInfo.targetParents.singleOrNull { it !is KtDeclaration && it !is PsiPackage } ?: return null
|
val targetParent = classInfo.applicableParents.singleOrNull { it !is KtDeclaration && it !is PsiPackage } ?: return null
|
||||||
|
|
||||||
val expectedType = getTypeConstraintInfo(element)?.upperBound
|
val expectedType = getTypeConstraintInfo(element)?.upperBound
|
||||||
if (expectedType != null && expectedType.containsError()) return null
|
if (expectedType != null && expectedType.containsError()) return null
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// "Create class 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
class B {
|
||||||
|
val a = <caret>Nested()
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// "Create class 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
class B {
|
||||||
|
val a = Nested()
|
||||||
|
|
||||||
|
class Nested {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// "Create class 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
inner class B {
|
||||||
|
val a = <caret>Nested()
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// "Create class 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
inner class B {
|
||||||
|
val a = Nested()
|
||||||
|
|
||||||
|
inner class Nested {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// "Create object 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
class B {
|
||||||
|
val a = <caret>Nested
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// "Create object 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
class B {
|
||||||
|
val a = Nested
|
||||||
|
|
||||||
|
object Nested {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// "Create object 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
inner class B {
|
||||||
|
val a = <caret>Nested
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// "Create object 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
inner class B {
|
||||||
|
val a = Nested
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Nested {
|
||||||
|
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// "Create class 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
class B {
|
||||||
|
val a: <caret>Nested = Nested()
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// "Create class 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
class B {
|
||||||
|
val a: Nested = Nested()
|
||||||
|
|
||||||
|
class Nested {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// "Create class 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
inner class B {
|
||||||
|
val a: <caret>Nested = Nested()
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// "Create class 'Nested'" "true"
|
||||||
|
class A {
|
||||||
|
// TARGET_PARENT:
|
||||||
|
inner class B {
|
||||||
|
val a: Nested = Nested()
|
||||||
|
|
||||||
|
inner class Nested {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1647,6 +1647,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("createNestedClass.kt")
|
||||||
|
public void testCreateNestedClass() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/callExpression/createNestedClass.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("createNestedClassInInner.kt")
|
||||||
|
public void testCreateNestedClassInInner() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/callExpression/createNestedClassInInner.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("expectedTypeBySuperFunction.kt")
|
@TestMetadata("expectedTypeBySuperFunction.kt")
|
||||||
public void testExpectedTypeBySuperFunction() throws Exception {
|
public void testExpectedTypeBySuperFunction() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/callExpression/expectedTypeBySuperFunction.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/callExpression/expectedTypeBySuperFunction.kt");
|
||||||
@@ -1959,6 +1971,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("createNestedObject.kt")
|
||||||
|
public void testCreateNestedObject() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/referenceExpression/createNestedObject.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("createNestedObjectInInner.kt")
|
||||||
|
public void testCreateNestedObjectInInner() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/referenceExpression/createNestedObjectInInner.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("enumByClassLiteral.kt")
|
@TestMetadata("enumByClassLiteral.kt")
|
||||||
public void testEnumByClassLiteral() throws Exception {
|
public void testEnumByClassLiteral() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumByClassLiteral.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumByClassLiteral.kt");
|
||||||
@@ -2196,6 +2220,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("createNestedClass.kt")
|
||||||
|
public void testCreateNestedClass() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/typeReference/createNestedClass.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("createNestedClassInInner.kt")
|
||||||
|
public void testCreateNestedClassInInner() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/typeReference/createNestedClassInInner.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("enumEntryNotQualifierNoTypeArgs.kt")
|
@TestMetadata("enumEntryNotQualifierNoTypeArgs.kt")
|
||||||
public void testEnumEntryNotQualifierNoTypeArgs() throws Exception {
|
public void testEnumEntryNotQualifierNoTypeArgs() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/typeReference/enumEntryNotQualifierNoTypeArgs.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/typeReference/enumEntryNotQualifierNoTypeArgs.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user