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 renderDefaultVisibility: Boolean
var renderDefaultModality: Boolean var renderDefaultModality: Boolean
var renderConstructorDelegation: Boolean var renderConstructorDelegation: Boolean
var renderActualAnnotationPropertiesInPrimaryConstructor: Boolean var renderAnnotationPropertiesInPrimaryConstructor: Boolean
var actualPropertiesInPrimaryConstructor: Boolean
var uninferredTypeParameterAsName: Boolean var uninferredTypeParameterAsName: Boolean
var overrideRenderingPolicy: OverrideRenderingPolicy var overrideRenderingPolicy: OverrideRenderingPolicy
var valueParametersHandler: DescriptorRenderer.ValueParametersHandler var valueParametersHandler: DescriptorRenderer.ValueParametersHandler
@@ -810,8 +810,8 @@ internal class DescriptorRendererImpl(
renderModifier(builder, valueParameter.isNoinline, "noinline") renderModifier(builder, valueParameter.isNoinline, "noinline")
val containingDeclaration = valueParameter.containingDeclaration val containingDeclaration = valueParameter.containingDeclaration
if (renderActualAnnotationPropertiesInPrimaryConstructor && containingDeclaration.isAnnotationConstructor()) { if (renderAnnotationPropertiesInPrimaryConstructor && containingDeclaration.isAnnotationConstructor()) {
renderModifier(builder, true, "actual") renderModifier(builder, actualPropertiesInPrimaryConstructor, "actual")
renderModifier(builder, true, "val") renderModifier(builder, true, "val")
} }
@@ -82,7 +82,8 @@ internal class DescriptorRendererOptionsImpl : DescriptorRendererOptions {
override var renderDefaultVisibility by property(true) override var renderDefaultVisibility by property(true)
override var renderDefaultModality by property(true) override var renderDefaultModality by property(true)
override var renderConstructorDelegation by property(false) 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 uninferredTypeParameterAsName by property(false)
override var includePropertyConstant by property(false) override var includePropertyConstant by property(false)
override var withoutTypeParameters by property(false) override var withoutTypeParameters by property(false)
@@ -120,33 +120,48 @@ interface OverrideMemberChooserObject : ClassMember {
fun OverrideMemberChooserObject.generateTopLevelActual( fun OverrideMemberChooserObject.generateTopLevelActual(
copyDoc: Boolean, copyDoc: Boolean,
project: Project project: Project
) = generateMember(null, copyDoc, project, forceActual = true) ) = generateMember(null, copyDoc, project, forceActual = true, forceExpect = false)
fun OverrideMemberChooserObject.generateActualMember( fun OverrideMemberChooserObject.generateActualMember(
targetClass: KtClassOrObject, targetClass: KtClassOrObject,
copyDoc: Boolean 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( fun OverrideMemberChooserObject.generateMember(
targetClass: KtClassOrObject, targetClass: KtClassOrObject,
copyDoc: Boolean copyDoc: Boolean
) = generateMember(targetClass, copyDoc, targetClass.project, forceActual = false) ) = generateMember(targetClass, copyDoc, targetClass.project, forceActual = false, forceExpect = false)
private fun OverrideMemberChooserObject.generateMember( private fun OverrideMemberChooserObject.generateMember(
targetClass: KtClassOrObject?, targetClass: KtClassOrObject?,
copyDoc: Boolean, copyDoc: Boolean,
project: Project, project: Project,
forceActual: Boolean forceActual: Boolean,
forceExpect: Boolean
): KtCallableDeclaration { ): KtCallableDeclaration {
val descriptor = immediateSuper val descriptor = immediateSuper
val bodyType = when { val bodyType = when {
targetClass?.hasExpectModifier() == true -> NO_BODY targetClass?.hasExpectModifier() == true -> NO_BODY
descriptor.extensionReceiverParameter != null && !forceActual -> FROM_TEMPLATE descriptor.extensionReceiverParameter != null && !forceActual && !forceExpect -> FROM_TEMPLATE
else -> bodyType 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) { if (preferConstructorParameter && descriptor is PropertyDescriptor) {
return generateConstructorParameter(project, descriptor, renderer) return generateConstructorParameter(project, descriptor, renderer)
@@ -158,21 +173,27 @@ private fun OverrideMemberChooserObject.generateMember(
else -> error("Unknown member to override: $descriptor") else -> error("Unknown member to override: $descriptor")
} }
if (forceActual) { when {
newMember.addModifier(KtTokens.ACTUAL_KEYWORD) forceActual -> newMember.addModifier(KtTokens.ACTUAL_KEYWORD)
} else if (targetClass?.hasActualModifier() == true) { forceExpect -> if (targetClass == null) {
val expectClassDescriptors = newMember.addModifier(KtTokens.EXPECT_KEYWORD)
targetClass.resolveToDescriptorIfAny()?.expectedDescriptors()?.filterIsInstance<ClassDescriptor>().orEmpty() } else {
if (expectClassDescriptors.any { expectClassDescriptor -> newMember.makeNotActual()
val expectMemberDescriptor = expectClassDescriptor.findCallableMemberBySignature(immediateSuper) }
expectMemberDescriptor?.isExpect == true && expectMemberDescriptor.kind != CallableMemberDescriptor.Kind.FAKE_OVERRIDE targetClass?.hasActualModifier() == true -> {
} val expectClassDescriptors =
) { targetClass.resolveToDescriptorIfAny()?.expectedDescriptors()?.filterIsInstance<ClassDescriptor>().orEmpty()
newMember.addModifier(KtTokens.ACTUAL_KEYWORD) 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) { if (copyDoc) {
@@ -210,14 +231,18 @@ private val OVERRIDE_RENDERER = DescriptorRenderer.withOptions {
presentableUnresolvedTypes = true presentableUnresolvedTypes = true
} }
private val ACTUAL_RENDERER = OVERRIDE_RENDERER.withOptions { private val EXPECT_RENDERER = OVERRIDE_RENDERER.withOptions {
modifiers = DescriptorRendererModifier.ALL modifiers = DescriptorRendererModifier.ALL
renderConstructorKeyword = true renderConstructorKeyword = true
secondaryConstructorsAsPrimary = false secondaryConstructorsAsPrimary = false
renderDefaultVisibility = false renderDefaultVisibility = false
renderDefaultModality = false renderDefaultModality = false
renderConstructorDelegation = true renderConstructorDelegation = true
renderActualAnnotationPropertiesInPrimaryConstructor = true renderAnnotationPropertiesInPrimaryConstructor = true
}
private val ACTUAL_RENDERER = EXPECT_RENDERER.withOptions {
actualPropertiesInPrimaryConstructor = true
} }
private fun PropertyDescriptor.wrap(forceOverride: Boolean): PropertyDescriptor { 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 isExpect() = false
override fun getModality() = if (forceAbstract) Modality.ABSTRACT else if (forceOverride) Modality.OPEN else this@wrap.modality 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 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) = override fun <R : Any?, D : Any?> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D) =
visitor.visitFunctionDescriptor(this, data) visitor.visitFunctionDescriptor(this, data)
} }
@@ -260,8 +285,7 @@ private fun generateProperty(
renderer: DescriptorRenderer, renderer: DescriptorRenderer,
bodyType: OverrideMemberChooserObject.BodyType bodyType: OverrideMemberChooserObject.BodyType
): KtProperty { ): KtProperty {
val actualRendererUsed = renderer === ACTUAL_RENDERER val newDescriptor = descriptor.wrap(forceOverride = renderer === OVERRIDE_RENDERER)
val newDescriptor = descriptor.wrap(forceOverride = !actualRendererUsed)
val returnType = descriptor.returnType val returnType = descriptor.returnType
val returnsNotUnit = returnType != null && !KotlinBuiltIns.isUnit(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 { private fun generateConstructorParameter(project: Project, descriptor: PropertyDescriptor, renderer: DescriptorRenderer): KtParameter {
val actualRendererUsed = renderer === ACTUAL_RENDERER val newDescriptor = descriptor.wrap(forceOverride = renderer === OVERRIDE_RENDERER)
val newDescriptor = descriptor.wrap(forceOverride = !actualRendererUsed)
newDescriptor.setSingleOverridden(descriptor) newDescriptor.setSingleOverridden(descriptor)
return KtPsiFactory(project).createParameter(renderer.render(newDescriptor)) return KtPsiFactory(project).createParameter(renderer.render(newDescriptor))
} }
@@ -293,8 +316,9 @@ private fun generateFunction(
renderer: DescriptorRenderer, renderer: DescriptorRenderer,
bodyType: OverrideMemberChooserObject.BodyType bodyType: OverrideMemberChooserObject.BodyType
): KtFunction { ): KtFunction {
val actualRendererUsed = renderer === ACTUAL_RENDERER val newDescriptor = descriptor.wrap(
val newDescriptor = descriptor.wrap(forceAbstract = actualRendererUsed && bodyType == NO_BODY, forceOverride = !actualRendererUsed) forceAbstract = renderer === ACTUAL_RENDERER && bodyType == NO_BODY, forceOverride = renderer === OVERRIDE_RENDERER
)
val returnType = descriptor.returnType val returnType = descriptor.returnType
val returnsNotUnit = returnType != null && !KotlinBuiltIns.isUnit(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.createFromUsage.createVariable.CreateParameterByRefActionFactory
import org.jetbrains.kotlin.idea.quickfix.expectactual.AddActualFix import org.jetbrains.kotlin.idea.quickfix.expectactual.AddActualFix
import org.jetbrains.kotlin.idea.quickfix.expectactual.CreateActualFix 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.MigrateExternalExtensionFix
import org.jetbrains.kotlin.idea.quickfix.migration.MigrateTypeParameterListFix import org.jetbrains.kotlin.idea.quickfix.migration.MigrateTypeParameterListFix
import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageFix import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageFix
@@ -510,6 +511,7 @@ class QuickFixRegistrar : QuickFixContributor {
OVERLOADS_WITHOUT_DEFAULT_ARGUMENTS.registerFactory(RemoveAnnotationFix.JvmOverloads) OVERLOADS_WITHOUT_DEFAULT_ARGUMENTS.registerFactory(RemoveAnnotationFix.JvmOverloads)
ACTUAL_WITHOUT_EXPECT.registerFactory(RemoveModifierFix.createRemoveModifierFromListOwnerFactory(ACTUAL_KEYWORD)) ACTUAL_WITHOUT_EXPECT.registerFactory(RemoveModifierFix.createRemoveModifierFromListOwnerFactory(ACTUAL_KEYWORD))
ACTUAL_WITHOUT_EXPECT.registerFactory(CreateExpectedFix)
NO_ACTUAL_FOR_EXPECT.registerFactory(CreateActualFix) NO_ACTUAL_FOR_EXPECT.registerFactory(CreateActualFix)
NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS.registerFactory(AddActualFix) 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.module.Module
import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.JavaDirectoryService
import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.descriptors.* 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.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent 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.EMPTY_OR_TEMPLATE
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.BodyType.NO_BODY 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.OverrideMemberChooserObject.Companion.create
import org.jetbrains.kotlin.idea.core.overrideImplement.generateActualMember import org.jetbrains.kotlin.idea.core.overrideImplement.generateActualMember
import org.jetbrains.kotlin.idea.core.overrideImplement.generateTopLevelActual 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.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory 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.actualsForExpected
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand 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.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -110,37 +108,7 @@ sealed class CreateActualFix<out D : KtNamedDeclaration>(
return actualDeclaration.containingKtFile return actualDeclaration.containingKtFile
} }
} }
val name = declaration.name ?: return null return createFileForDeclaration(actualModule, declaration)
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)
}
}
} }
companion object : KotlinSingleIntentionActionFactory() { companion object : KotlinSingleIntentionActionFactory() {
@@ -171,19 +139,7 @@ class CreateActualClassFix(
generateClassOrObjectByExpectedClass(project, element, actualNeeded = true) generateClassOrObjectByExpectedClass(project, element, actualNeeded = true)
}) { }) {
override val elementType = run { override val elementType = element.getTypeDescription()
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"
}
}
} }
class CreateActualPropertyFix( class CreateActualPropertyFix(
@@ -242,16 +198,7 @@ internal fun KtPsiFactory.generateClassOrObjectByExpectedClass(
} }
} }
val expectedText = expectedClass.text val actualClass = createClassCopyByText(expectedClass)
val actualClass = if (expectedClass is KtObjectDeclaration) {
if (expectedClass.isCompanion()) {
createCompanionObject(expectedText)
} else {
createObject(expectedText)
}
} else {
createClass(expectedText)
}
actualClass.declarations.forEach { actualClass.declarations.forEach {
if (it.exists()) { if (it.exists()) {
it.delete() 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/"); 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") @TestMetadata("function")
public void testFunction() throws Exception { public void testFunction() throws Exception {
runTest("idea/testData/multiModuleQuickFix/function/"); runTest("idea/testData/multiModuleQuickFix/function/");