Introduce quick-fix "create expected class / function / property"

#KT-27075 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-11-15 16:43:33 +03:00
parent 5c6f776c09
commit 2277dcc76f
47 changed files with 706 additions and 92 deletions
@@ -188,7 +188,8 @@ interface DescriptorRendererOptions {
var renderDefaultVisibility: Boolean
var renderDefaultModality: Boolean
var renderConstructorDelegation: Boolean
var renderActualAnnotationPropertiesInPrimaryConstructor: Boolean
var renderAnnotationPropertiesInPrimaryConstructor: Boolean
var actualPropertiesInPrimaryConstructor: Boolean
var uninferredTypeParameterAsName: Boolean
var overrideRenderingPolicy: OverrideRenderingPolicy
var valueParametersHandler: DescriptorRenderer.ValueParametersHandler
@@ -810,8 +810,8 @@ internal class DescriptorRendererImpl(
renderModifier(builder, valueParameter.isNoinline, "noinline")
val containingDeclaration = valueParameter.containingDeclaration
if (renderActualAnnotationPropertiesInPrimaryConstructor && containingDeclaration.isAnnotationConstructor()) {
renderModifier(builder, true, "actual")
if (renderAnnotationPropertiesInPrimaryConstructor && containingDeclaration.isAnnotationConstructor()) {
renderModifier(builder, actualPropertiesInPrimaryConstructor, "actual")
renderModifier(builder, true, "val")
}
@@ -82,7 +82,8 @@ internal class DescriptorRendererOptionsImpl : DescriptorRendererOptions {
override var renderDefaultVisibility by property(true)
override var renderDefaultModality by property(true)
override var renderConstructorDelegation by property(false)
override var renderActualAnnotationPropertiesInPrimaryConstructor by property(false)
override var renderAnnotationPropertiesInPrimaryConstructor by property(false)
override var actualPropertiesInPrimaryConstructor: Boolean by property(false)
override var uninferredTypeParameterAsName by property(false)
override var includePropertyConstant by property(false)
override var withoutTypeParameters by property(false)
@@ -120,33 +120,48 @@ interface OverrideMemberChooserObject : ClassMember {
fun OverrideMemberChooserObject.generateTopLevelActual(
copyDoc: Boolean,
project: Project
) = generateMember(null, copyDoc, project, forceActual = true)
) = generateMember(null, copyDoc, project, forceActual = true, forceExpect = false)
fun OverrideMemberChooserObject.generateActualMember(
targetClass: KtClassOrObject,
copyDoc: Boolean
) = generateMember(targetClass, copyDoc, targetClass.project, forceActual = true)
) = generateMember(targetClass, copyDoc, targetClass.project, forceActual = true, forceExpect = false)
fun OverrideMemberChooserObject.generateTopLevelExpect(
copyDoc: Boolean,
project: Project
) = generateMember(null, copyDoc, project, forceActual = false, forceExpect = true)
fun OverrideMemberChooserObject.generateExpectMember(
targetClass: KtClassOrObject,
copyDoc: Boolean
) = generateMember(targetClass, copyDoc, targetClass.project, forceActual = false, forceExpect = true)
fun OverrideMemberChooserObject.generateMember(
targetClass: KtClassOrObject,
copyDoc: Boolean
) = generateMember(targetClass, copyDoc, targetClass.project, forceActual = false)
) = generateMember(targetClass, copyDoc, targetClass.project, forceActual = false, forceExpect = false)
private fun OverrideMemberChooserObject.generateMember(
targetClass: KtClassOrObject?,
copyDoc: Boolean,
project: Project,
forceActual: Boolean
forceActual: Boolean,
forceExpect: Boolean
): KtCallableDeclaration {
val descriptor = immediateSuper
val bodyType = when {
targetClass?.hasExpectModifier() == true -> NO_BODY
descriptor.extensionReceiverParameter != null && !forceActual -> FROM_TEMPLATE
descriptor.extensionReceiverParameter != null && !forceActual && !forceExpect -> FROM_TEMPLATE
else -> bodyType
}
val renderer = if (forceActual) ACTUAL_RENDERER else OVERRIDE_RENDERER
val renderer = when {
forceActual -> ACTUAL_RENDERER
forceExpect -> EXPECT_RENDERER
else -> OVERRIDE_RENDERER
}
if (preferConstructorParameter && descriptor is PropertyDescriptor) {
return generateConstructorParameter(project, descriptor, renderer)
@@ -158,21 +173,27 @@ private fun OverrideMemberChooserObject.generateMember(
else -> error("Unknown member to override: $descriptor")
}
if (forceActual) {
newMember.addModifier(KtTokens.ACTUAL_KEYWORD)
} else if (targetClass?.hasActualModifier() == true) {
val expectClassDescriptors =
targetClass.resolveToDescriptorIfAny()?.expectedDescriptors()?.filterIsInstance<ClassDescriptor>().orEmpty()
if (expectClassDescriptors.any { expectClassDescriptor ->
val expectMemberDescriptor = expectClassDescriptor.findCallableMemberBySignature(immediateSuper)
expectMemberDescriptor?.isExpect == true && expectMemberDescriptor.kind != CallableMemberDescriptor.Kind.FAKE_OVERRIDE
}
) {
newMember.addModifier(KtTokens.ACTUAL_KEYWORD)
when {
forceActual -> newMember.addModifier(KtTokens.ACTUAL_KEYWORD)
forceExpect -> if (targetClass == null) {
newMember.addModifier(KtTokens.EXPECT_KEYWORD)
} else {
newMember.makeNotActual()
}
targetClass?.hasActualModifier() == true -> {
val expectClassDescriptors =
targetClass.resolveToDescriptorIfAny()?.expectedDescriptors()?.filterIsInstance<ClassDescriptor>().orEmpty()
if (expectClassDescriptors.any { expectClassDescriptor ->
val expectMemberDescriptor = expectClassDescriptor.findCallableMemberBySignature(immediateSuper)
expectMemberDescriptor?.isExpect == true && expectMemberDescriptor.kind != CallableMemberDescriptor.Kind.FAKE_OVERRIDE
}
) {
newMember.addModifier(KtTokens.ACTUAL_KEYWORD)
}
}
else -> {
newMember.makeNotActual()
}
} else {
newMember.removeModifier(KtTokens.IMPL_KEYWORD)
newMember.removeModifier(KtTokens.ACTUAL_KEYWORD)
}
if (copyDoc) {
@@ -210,14 +231,18 @@ private val OVERRIDE_RENDERER = DescriptorRenderer.withOptions {
presentableUnresolvedTypes = true
}
private val ACTUAL_RENDERER = OVERRIDE_RENDERER.withOptions {
private val EXPECT_RENDERER = OVERRIDE_RENDERER.withOptions {
modifiers = DescriptorRendererModifier.ALL
renderConstructorKeyword = true
secondaryConstructorsAsPrimary = false
renderDefaultVisibility = false
renderDefaultModality = false
renderConstructorDelegation = true
renderActualAnnotationPropertiesInPrimaryConstructor = true
renderAnnotationPropertiesInPrimaryConstructor = true
}
private val ACTUAL_RENDERER = EXPECT_RENDERER.withOptions {
actualPropertiesInPrimaryConstructor = true
}
private fun PropertyDescriptor.wrap(forceOverride: Boolean): PropertyDescriptor {
@@ -237,7 +262,7 @@ private fun FunctionDescriptor.wrap(forceOverride: Boolean, forceAbstract: Boole
override fun isExpect() = false
override fun getModality() = if (forceAbstract) Modality.ABSTRACT else if (forceOverride) Modality.OPEN else this@wrap.modality
override fun getReturnType() = this@wrap.returnType?.approximateFlexibleTypes(preferNotNull = true, preferStarForRaw = true)
override fun getOverriddenDescriptors() = if (forceOverride) listOf(this@wrap) else emptyList()
override fun getOverriddenDescriptors() = if (forceOverride) listOf(this@wrap) else this@wrap.overriddenDescriptors
override fun <R : Any?, D : Any?> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D) =
visitor.visitFunctionDescriptor(this, data)
}
@@ -260,8 +285,7 @@ private fun generateProperty(
renderer: DescriptorRenderer,
bodyType: OverrideMemberChooserObject.BodyType
): KtProperty {
val actualRendererUsed = renderer === ACTUAL_RENDERER
val newDescriptor = descriptor.wrap(forceOverride = !actualRendererUsed)
val newDescriptor = descriptor.wrap(forceOverride = renderer === OVERRIDE_RENDERER)
val returnType = descriptor.returnType
val returnsNotUnit = returnType != null && !KotlinBuiltIns.isUnit(returnType)
@@ -281,8 +305,7 @@ private fun generateProperty(
}
private fun generateConstructorParameter(project: Project, descriptor: PropertyDescriptor, renderer: DescriptorRenderer): KtParameter {
val actualRendererUsed = renderer === ACTUAL_RENDERER
val newDescriptor = descriptor.wrap(forceOverride = !actualRendererUsed)
val newDescriptor = descriptor.wrap(forceOverride = renderer === OVERRIDE_RENDERER)
newDescriptor.setSingleOverridden(descriptor)
return KtPsiFactory(project).createParameter(renderer.render(newDescriptor))
}
@@ -293,8 +316,9 @@ private fun generateFunction(
renderer: DescriptorRenderer,
bodyType: OverrideMemberChooserObject.BodyType
): KtFunction {
val actualRendererUsed = renderer === ACTUAL_RENDERER
val newDescriptor = descriptor.wrap(forceAbstract = actualRendererUsed && bodyType == NO_BODY, forceOverride = !actualRendererUsed)
val newDescriptor = descriptor.wrap(
forceAbstract = renderer === ACTUAL_RENDERER && bodyType == NO_BODY, forceOverride = renderer === OVERRIDE_RENDERER
)
val returnType = descriptor.returnType
val returnsNotUnit = returnType != null && !KotlinBuiltIns.isUnit(returnType)
@@ -363,4 +387,7 @@ fun generateUnsupportedOrSuperCall(
}
}
fun KtNamedDeclaration.makeNotActual() {
removeModifier(KtTokens.ACTUAL_KEYWORD)
removeModifier(KtTokens.IMPL_KEYWORD)
}
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateP
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByRefActionFactory
import org.jetbrains.kotlin.idea.quickfix.expectactual.AddActualFix
import org.jetbrains.kotlin.idea.quickfix.expectactual.CreateActualFix
import org.jetbrains.kotlin.idea.quickfix.expectactual.CreateExpectedFix
import org.jetbrains.kotlin.idea.quickfix.migration.MigrateExternalExtensionFix
import org.jetbrains.kotlin.idea.quickfix.migration.MigrateTypeParameterListFix
import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageFix
@@ -510,6 +511,7 @@ class QuickFixRegistrar : QuickFixContributor {
OVERLOADS_WITHOUT_DEFAULT_ARGUMENTS.registerFactory(RemoveAnnotationFix.JvmOverloads)
ACTUAL_WITHOUT_EXPECT.registerFactory(RemoveModifierFix.createRemoveModifierFromListOwnerFactory(ACTUAL_KEYWORD))
ACTUAL_WITHOUT_EXPECT.registerFactory(CreateExpectedFix)
NO_ACTUAL_FOR_EXPECT.registerFactory(CreateActualFix)
NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS.registerFactory(AddActualFix)
@@ -22,7 +22,6 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaDirectoryService
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.descriptors.*
@@ -31,18 +30,17 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.BodyType.EMPTY_OR_TEMPLATE
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.BodyType.NO_BODY
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.Companion.create
import org.jetbrains.kotlin.idea.core.overrideImplement.generateActualMember
import org.jetbrains.kotlin.idea.core.overrideImplement.generateTopLevelActual
import org.jetbrains.kotlin.idea.core.toDescriptor
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.refactoring.createKotlinFile
import org.jetbrains.kotlin.idea.util.actualsForExpected
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
@@ -110,37 +108,7 @@ sealed class CreateActualFix<out D : KtNamedDeclaration>(
return actualDeclaration.containingKtFile
}
}
val name = declaration.name ?: return null
val expectedDir = declaration.containingFile.containingDirectory
val expectedPackage = JavaDirectoryService.getInstance().getPackage(expectedDir)
val actualDirectory = findOrCreateDirectoryForPackage(
actualModule, expectedPackage?.qualifiedName ?: ""
) ?: return null
return runWriteAction {
val fileName = "$name.kt"
val existingFile = actualDirectory.findFile(fileName)
val packageDirective = declaration.containingKtFile.packageDirective
val packageName =
if (packageDirective?.packageNameExpression == null) actualDirectory.getPackage()?.qualifiedName
else packageDirective.fqName.asString()
if (existingFile is KtFile) {
val existingPackageDirective = existingFile.packageDirective
if (existingFile.declarations.isNotEmpty() &&
existingPackageDirective?.fqName != packageDirective?.fqName
) {
val newName = KotlinNameSuggester.suggestNameByName(name) {
actualDirectory.findFile("$it.kt") == null
} + ".kt"
createKotlinFile(newName, actualDirectory, packageName)
} else {
existingFile
}
} else {
createKotlinFile(fileName, actualDirectory, packageName)
}
}
return createFileForDeclaration(actualModule, declaration)
}
companion object : KotlinSingleIntentionActionFactory() {
@@ -171,19 +139,7 @@ class CreateActualClassFix(
generateClassOrObjectByExpectedClass(project, element, actualNeeded = true)
}) {
override val elementType = run {
val element = element
when (element) {
is KtObjectDeclaration -> "object"
is KtClass -> when {
element.isInterface() -> "interface"
element.isEnum() -> "enum class"
element.isAnnotation() -> "annotation class"
else -> "class"
}
else -> "class"
}
}
override val elementType = element.getTypeDescription()
}
class CreateActualPropertyFix(
@@ -242,16 +198,7 @@ internal fun KtPsiFactory.generateClassOrObjectByExpectedClass(
}
}
val expectedText = expectedClass.text
val actualClass = if (expectedClass is KtObjectDeclaration) {
if (expectedClass.isCompanion()) {
createCompanionObject(expectedText)
} else {
createObject(expectedText)
}
} else {
createClass(expectedText)
}
val actualClass = createClassCopyByText(expectedClass)
actualClass.declarations.forEach {
if (it.exists()) {
it.delete()
@@ -0,0 +1,260 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.quickfix.expectactual
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.ide.util.EditorHelper
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.project.implementedModules
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.overrideImplement.*
import org.jetbrains.kotlin.idea.core.toDescriptor
import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.liftToExpected
import org.jetbrains.kotlin.idea.util.module
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
import org.jetbrains.kotlin.resolve.BindingContext
sealed class CreateExpectedFix<out D : KtNamedDeclaration>(
declaration: D,
targetExpectedClass: KtClassOrObject?,
private val commonModule: Module,
private val generateIt: KtPsiFactory.(Project, D) -> D?
) : KotlinQuickFixAction<D>(declaration) {
private val targetExpectedClassPointer = targetExpectedClass?.createSmartPointer()
override fun getFamilyName() = text
protected abstract val elementType: String
override fun getText() = "Create expected $elementType in common module ${commonModule.name}"
override fun startInWriteAction() = false
final override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return
val factory = KtPsiFactory(project)
val targetExpectedClass = targetExpectedClassPointer?.element
val expectedFile = targetExpectedClass?.containingKtFile ?: getOrCreateImplementationFile() ?: return
DumbService.getInstance(project).runWhenSmart {
val generated = factory.generateIt(project, element) ?: return@runWhenSmart
project.executeWriteCommand("Create expected declaration") {
if (expectedFile.packageDirective?.fqName != file.packageDirective?.fqName &&
expectedFile.declarations.isEmpty()
) {
val packageDirective = file.packageDirective
packageDirective?.let {
val oldPackageDirective = expectedFile.packageDirective
val newPackageDirective = factory.createPackageDirective(it.fqName)
if (oldPackageDirective != null) {
oldPackageDirective.replace(newPackageDirective)
} else {
expectedFile.add(newPackageDirective)
}
}
}
val expectedDeclaration = when {
targetExpectedClass != null -> targetExpectedClass.addDeclaration(generated as KtNamedDeclaration)
else -> expectedFile.add(generated) as KtElement
}
val reformatted = CodeStyleManager.getInstance(project).reformat(expectedDeclaration)
val shortened = ShortenReferences.DEFAULT.process(reformatted as KtElement)
EditorHelper.openInEditor(shortened)?.caretModel?.moveToOffset(shortened.textRange.startOffset)
}
}
}
private fun getOrCreateImplementationFile(): KtFile? {
val declaration = element as? KtNamedDeclaration ?: return null
val parent = declaration.parent
if (parent is KtFile) {
for (otherDeclaration in parent.declarations) {
if (otherDeclaration === declaration) continue
if (!otherDeclaration.hasActualModifier()) continue
val expectedDeclaration = otherDeclaration.liftToExpected() ?: continue
return expectedDeclaration.containingKtFile
}
}
return createFileForDeclaration(commonModule, declaration)
}
companion object : KotlinIntentionActionsFactory() {
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val d = DiagnosticFactory.cast(diagnostic, Errors.ACTUAL_WITHOUT_EXPECT)
val declaration = d.psiElement as? KtNamedDeclaration ?: return emptyList()
val compatibility = d.b
// For function we allow it, because overloads are possible
if (compatibility.isNotEmpty() && declaration !is KtFunction) return emptyList()
val containingClass = declaration.containingClassOrObject
val expectedContainingClass = containingClass?.liftToExpected() as? KtClassOrObject
// If there is already an expected class, we suggest only for its module,
// otherwise we suggest for all relevant expected modules
val expectedModules = expectedContainingClass?.module?.let { listOf(it) }
?: declaration.module?.implementedModules
?: return emptyList()
return when (declaration) {
is KtClassOrObject -> expectedModules.map { CreateExpectedClassFix(declaration, expectedContainingClass, it) }
is KtFunction -> expectedModules.map { CreateExpectedFunctionFix(declaration, expectedContainingClass, it) }
is KtProperty, is KtParameter -> expectedModules.map { CreateExpectedPropertyFix(declaration, expectedContainingClass, it) }
else -> emptyList()
}
}
}
}
class CreateExpectedClassFix(
klass: KtClassOrObject,
outerExpectedClass: KtClassOrObject?,
commonModule: Module
) : CreateExpectedFix<KtClassOrObject>(klass, outerExpectedClass, commonModule, { project, element ->
generateClassOrObjectByActualClass(project, element, isNested = outerExpectedClass != null)
}) {
override val elementType = element.getTypeDescription()
}
class CreateExpectedPropertyFix(
property: KtNamedDeclaration,
targetExpectedClass: KtClassOrObject?,
commonModule: Module
) : CreateExpectedFix<KtNamedDeclaration>(property, targetExpectedClass, commonModule, { project, element ->
val descriptor = element.toDescriptor() as? PropertyDescriptor
descriptor?.let { generateProperty(project, element, descriptor, targetExpectedClass) }
}) {
override val elementType = "property"
}
class CreateExpectedFunctionFix(
function: KtFunction,
targetExpectedClass: KtClassOrObject?,
commonModule: Module
) : CreateExpectedFix<KtFunction>(function, targetExpectedClass, commonModule, { project, element ->
val descriptor = element.toDescriptor() as? FunctionDescriptor
descriptor?.let { generateFunction(project, element, descriptor, targetExpectedClass) }
}) {
override val elementType = "function"
}
private fun KtPsiFactory.generateClassOrObjectByActualClass(
project: Project,
actualClass: KtClassOrObject,
isNested: Boolean
): KtClassOrObject {
val expectedClass = createClassCopyByText(actualClass)
expectedClass.declarations.forEach {
when (it) {
is KtEnumEntry -> return@forEach
is KtClassOrObject -> it.delete()
is KtCallableDeclaration -> it.delete()
}
}
expectedClass.primaryConstructor?.delete()
val context = actualClass.analyzeWithContent()
expectedClass.superTypeListEntries.zip(actualClass.superTypeListEntries).forEach { (expectedEntry, actualEntry) ->
if (expectedEntry !is KtSuperTypeCallEntry) return@forEach
val superType = context[BindingContext.TYPE, actualEntry.typeReference]
val superClassDescriptor = superType?.constructor?.declarationDescriptor as? ClassDescriptor ?: return@forEach
if (superClassDescriptor.kind == ClassKind.CLASS || superClassDescriptor.kind == ClassKind.ENUM_CLASS) {
expectedEntry.replace(createSuperTypeEntry(expectedEntry.typeReference!!.text))
}
}
if (!isNested) {
expectedClass.addModifier(KtTokens.EXPECT_KEYWORD)
} else {
expectedClass.makeNotActual()
}
declLoop@ for (actualDeclaration in actualClass.declarations) {
val descriptor = actualDeclaration.toDescriptor() ?: continue
val expectedDeclaration: KtDeclaration = when (actualDeclaration) {
is KtClassOrObject ->
if (actualDeclaration !is KtEnumEntry) {
generateClassOrObjectByActualClass(project, actualDeclaration, isNested = true)
} else {
continue@declLoop
}
is KtCallableDeclaration -> {
when (actualDeclaration) {
is KtFunction -> generateFunction(project, actualDeclaration, descriptor as FunctionDescriptor, expectedClass)
is KtProperty -> generateProperty(project, actualDeclaration, descriptor as PropertyDescriptor, expectedClass)
else -> continue@declLoop
}
}
else -> continue@declLoop
}
expectedClass.addDeclaration(expectedDeclaration)
}
val actualPrimaryConstructor = actualClass.primaryConstructor
if (expectedClass is KtClass && actualPrimaryConstructor != null) {
val descriptor = actualPrimaryConstructor.toDescriptor()
if (descriptor is FunctionDescriptor) {
val expectedPrimaryConstructor = generateFunction(project, actualPrimaryConstructor, descriptor, expectedClass)
expectedClass.createPrimaryConstructorIfAbsent().replace(expectedPrimaryConstructor)
}
}
return expectedClass
}
private fun generateFunction(
project: Project,
actualFunction: KtFunction,
descriptor: FunctionDescriptor,
targetClass: KtClassOrObject? = null
): KtFunction {
val memberChooserObject = OverrideMemberChooserObject.create(
actualFunction, descriptor, descriptor,
OverrideMemberChooserObject.BodyType.NO_BODY
)
return if (targetClass != null) {
memberChooserObject.generateExpectMember(targetClass = targetClass, copyDoc = true)
} else {
memberChooserObject.generateTopLevelExpect(copyDoc = true, project = project)
} as KtFunction
}
private fun generateProperty(
project: Project,
actualProperty: KtNamedDeclaration,
descriptor: PropertyDescriptor,
targetClass: KtClassOrObject? = null
): KtProperty {
val memberChooserObject = OverrideMemberChooserObject.create(
actualProperty, descriptor, descriptor,
OverrideMemberChooserObject.BodyType.NO_BODY
)
return if (targetClass != null) {
memberChooserObject.generateExpectMember(targetClass = targetClass, copyDoc = true)
} else {
memberChooserObject.generateTopLevelExpect(copyDoc = true, project = project)
} as KtProperty
}
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.quickfix.expectactual
import com.intellij.openapi.module.Module
import com.intellij.psi.JavaDirectoryService
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.findOrCreateDirectoryForPackage
import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.refactoring.createKotlinFile
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.*
fun createFileForDeclaration(module: Module, declaration: KtNamedDeclaration): KtFile? {
val fileName = declaration.name ?: return null
val originalDir = declaration.containingFile.containingDirectory
val containerPackage = JavaDirectoryService.getInstance().getPackage(originalDir)
val packageDirective = declaration.containingKtFile.packageDirective
val directory = findOrCreateDirectoryForPackage(
module, containerPackage?.qualifiedName ?: ""
) ?: return null
return runWriteAction {
val fileNameWithExtension = "$fileName.kt"
val existingFile = directory.findFile(fileNameWithExtension)
val packageName =
if (packageDirective?.packageNameExpression == null) directory.getPackage()?.qualifiedName
else packageDirective.fqName.asString()
if (existingFile is KtFile) {
val existingPackageDirective = existingFile.packageDirective
if (existingFile.declarations.isNotEmpty() &&
existingPackageDirective?.fqName != packageDirective?.fqName
) {
val newName = KotlinNameSuggester.suggestNameByName(fileName) {
directory.findFile("$it.kt") == null
} + ".kt"
createKotlinFile(newName, directory, packageName)
} else {
existingFile
}
} else {
createKotlinFile(fileNameWithExtension, directory, packageName)
}
}
}
fun KtPsiFactory.createClassCopyByText(originalClass: KtClassOrObject): KtClassOrObject {
val text = originalClass.text
return if (originalClass is KtObjectDeclaration) {
if (originalClass.isCompanion()) {
createCompanionObject(text)
} else {
createObject(text)
}
} else {
createClass(text)
}
}
fun KtClassOrObject?.getTypeDescription(): String = when (this) {
is KtObjectDeclaration -> "object"
is KtClass -> when {
isInterface() -> "interface"
isEnum() -> "enum class"
isAnnotation() -> "annotation class"
else -> "class"
}
else -> "class"
}
@@ -0,0 +1,2 @@
// My: to be implemented
// DISABLE-ERRORS
@@ -0,0 +1,3 @@
// My: to be implemented
// DISABLE-ERRORS
expect annotation class My(val x: Int, val y: Double)
@@ -0,0 +1,4 @@
// "Create expected annotation class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual annotation class <caret>My(actual val x: Int, actual val y: Double)
@@ -0,0 +1,4 @@
// "Create expected annotation class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual annotation class My(actual val x: Int, actual val y: Double)
@@ -0,0 +1,2 @@
// My: to be implemented
// DISABLE-ERRORS
@@ -0,0 +1,13 @@
// My: to be implemented
// DISABLE-ERRORS
expect class My {
fun foo(param: String): Int
fun String.bar(y: Double): Boolean
fun baz()
constructor(flag: Boolean)
val isGood: Boolean
var status: Int
}
+19
View File
@@ -0,0 +1,19 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class <caret>My {
actual fun foo(param: String): Int = 42
actual fun String.bar(y: Double): Boolean = true
actual fun baz() {}
actual constructor(flag: Boolean) {}
actual val isGood: Boolean
get() = true
actual var status: Int
get() = 0
set(value) {}
}
@@ -0,0 +1,19 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My {
actual fun foo(param: String): Int = 42
actual fun String.bar(y: Double): Boolean = true
actual fun baz() {}
actual constructor(flag: Boolean) {}
actual val isGood: Boolean
get() = true
actual var status: Int
get() = 0
set(value) {}
}
@@ -0,0 +1,3 @@
// DISABLE-ERRORS
expect class My
@@ -0,0 +1,5 @@
// DISABLE-ERRORS
expect class My {
fun foo(param: String): Int
}
@@ -0,0 +1,6 @@
// "Create expected function in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My {
actual fun <caret>foo(param: String) = param.length
}
@@ -0,0 +1,6 @@
// "Create expected function in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My {
actual fun <caret>foo(param: String) = param.length
}
@@ -0,0 +1,3 @@
// DISABLE-ERRORS
expect class My
@@ -0,0 +1,5 @@
// DISABLE-ERRORS
expect class My {
var s: String
}
@@ -0,0 +1,6 @@
// "Create expected property in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My {
actual var <caret>s: String = "Hello"
}
@@ -0,0 +1,6 @@
// "Create expected property in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My {
actual var <caret>s: String = "Hello"
}
@@ -0,0 +1,3 @@
// DISABLE-ERRORS
expect class My
@@ -0,0 +1,5 @@
// DISABLE-ERRORS
expect class My {
val s: String
}
@@ -0,0 +1,4 @@
// "Create expected property in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My(actual val <caret>s: String)
@@ -0,0 +1,4 @@
// "Create expected property in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My(actual val <caret>s: String)
@@ -0,0 +1,5 @@
// DISABLE-ERRORS
expect abstract class Base {
abstract fun foo(param: String): Int
}
@@ -0,0 +1,9 @@
// DISABLE-ERRORS
expect abstract class Base {
abstract fun foo(param: String): Int
}
expect class My : Base {
override fun foo(param: String): Int
}
@@ -0,0 +1,10 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual abstract class Base {
actual abstract fun foo(param: String): Int
}
actual class <caret>My : Base() {
actual override fun foo(param: String) = param.length
}
@@ -0,0 +1,10 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual abstract class Base {
actual abstract fun foo(param: String): Int
}
actual class My : Base() {
actual override fun foo(param: String) = param.length
}
@@ -0,0 +1,2 @@
// My: to be implemented
// DISABLE-ERRORS
@@ -0,0 +1,10 @@
// My: to be implemented
// DISABLE-ERRORS
expect enum class My {
FIRST,
SECOND,
LAST;
val num: Int
}
+10
View File
@@ -0,0 +1,10 @@
// "Create expected enum class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual enum class <caret>My {
FIRST,
SECOND,
LAST;
val num: Int get() = 42
}
@@ -0,0 +1,10 @@
// "Create expected enum class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual enum class My {
FIRST,
SECOND,
LAST;
val num: Int get() = 42
}
@@ -0,0 +1 @@
expect fun foo(i: Int, d: Double, s: String): Boolean
@@ -0,0 +1,4 @@
// "Create expected function in common module testModule_Common" "true"
// DISABLE-ERRORS
actual fun <caret>foo(i: Int, d: Double, s: String) = s == "$i$d"
@@ -0,0 +1,4 @@
// "Create expected function in common module testModule_Common" "true"
// DISABLE-ERRORS
actual fun foo(i: Int, d: Double, s: String) = s == "$i$d"
@@ -0,0 +1,3 @@
// DISABLE-ERRORS
expect class My
@@ -0,0 +1,13 @@
// DISABLE-ERRORS
expect class My {
inner class Nested(s: String) {
fun hello(): String
var ss: String
class OtherNested(d: Double) {
val dd: Double
}
}
}
@@ -0,0 +1,14 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My {
actual inner class <caret>Nested(actual val s: String) {
actual fun hello() = s
actual var ss = s
actual class OtherNested(actual var d: Double) {
actual val dd = d
}
}
}
@@ -0,0 +1,14 @@
// "Create expected class in common module testModule_Common" "true"
// DISABLE-ERRORS
actual class My {
actual inner class <caret>Nested(actual val s: String) {
actual fun hello() = s
actual var ss = s
actual class OtherNested(actual var d: Double) {
actual val dd = d
}
}
}
@@ -0,0 +1 @@
expect var foo: String
@@ -0,0 +1,6 @@
// "Create expected property in common module testModule_Common" "true"
// DISABLE-ERRORS
actual var <caret>foo: String
get() = field * field
set(value) { field = value }
@@ -0,0 +1,6 @@
// "Create expected property in common module testModule_Common" "true"
// DISABLE-ERRORS
actual var foo: String
get() = field * field
set(value) { field = value }
@@ -254,6 +254,56 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul
runTest("idea/testData/multiModuleQuickFix/enum/");
}
@TestMetadata("expectAnnotation")
public void testExpectAnnotation() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectAnnotation/");
}
@TestMetadata("expectClass")
public void testExpectClass() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectClass/");
}
@TestMetadata("expectClassFunction")
public void testExpectClassFunction() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectClassFunction/");
}
@TestMetadata("expectClassProperty")
public void testExpectClassProperty() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectClassProperty/");
}
@TestMetadata("expectClassPropertyInConstructor")
public void testExpectClassPropertyInConstructor() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectClassPropertyInConstructor/");
}
@TestMetadata("expectClassWithSupertype")
public void testExpectClassWithSupertype() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectClassWithSupertype/");
}
@TestMetadata("expectEnum")
public void testExpectEnum() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectEnum/");
}
@TestMetadata("expectFunction")
public void testExpectFunction() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectFunction/");
}
@TestMetadata("expectNestedClass")
public void testExpectNestedClass() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectNestedClass/");
}
@TestMetadata("expectProperty")
public void testExpectProperty() throws Exception {
runTest("idea/testData/multiModuleQuickFix/expectProperty/");
}
@TestMetadata("function")
public void testFunction() throws Exception {
runTest("idea/testData/multiModuleQuickFix/function/");