From 9ae7bc935d123c9d16fbcdbf7eead1e77d63f6d5 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Thu, 22 Nov 2018 14:38:28 +0300 Subject: [PATCH] Create expect / actual refactoring: extract AbstractCreateDeclarationFix --- .../AbstractCreateDeclarationFix.kt | 88 +++++++++++++++++++ .../quickfix/expectactual/CreateActualFix.kt | 71 ++++----------- .../expectactual/CreateExpectedFix.kt | 82 ++++------------- 3 files changed, 120 insertions(+), 121 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt new file mode 100644 index 00000000000..562387413a5 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/AbstractCreateDeclarationFix.kt @@ -0,0 +1,88 @@ +/* + * 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.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.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction +import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.psi.* + +abstract class AbstractCreateDeclarationFix( + declaration: D, + protected val module: Module, + protected val generateIt: KtPsiFactory.(Project, D) -> D? +) : KotlinQuickFixAction(declaration) { + + override fun getFamilyName(): String = "Create expect / actual declaration" + + protected val elementType: String = element.getTypeDescription() + + override fun startInWriteAction() = false + + protected abstract fun findExistingFileToCreateDeclaration( + originalFile: KtFile, + originalDeclaration: KtNamedDeclaration + ): KtFile? + + protected fun getOrCreateImplementationFile(): KtFile? { + val declaration = element as? KtNamedDeclaration ?: return null + val parent = declaration.parent + return (parent as? KtFile)?.let { findExistingFileToCreateDeclaration(it, declaration) } + ?: createFileForDeclaration(module, declaration) + } + + protected fun doGenerate( + project: Project, + editor: Editor?, + originalFile: KtFile, + targetFile: KtFile, + targetClass: KtClassOrObject? + ) { + val element = element ?: return + val factory = KtPsiFactory(project) + DumbService.getInstance(project).runWhenSmart { + val generated = try { + factory.generateIt(project, element) ?: return@runWhenSmart + } catch (e: KotlinTypeInaccessibleException) { + if (editor != null) { + showErrorHint(project, editor, "Cannot generate expected $elementType: " + e.message, e.message) + } + return@runWhenSmart + } + + project.executeWriteCommand("Create expect / actual declaration") { + if (targetFile.packageDirective?.fqName != originalFile.packageDirective?.fqName && + targetFile.declarations.isEmpty() + ) { + val packageDirective = originalFile.packageDirective + if (packageDirective != null) { + val oldPackageDirective = targetFile.packageDirective + val newPackageDirective = factory.createPackageDirective(packageDirective.fqName) + if (oldPackageDirective != null) { + oldPackageDirective.replace(newPackageDirective) + } else { + targetFile.add(newPackageDirective) + } + } + } + val generatedDeclaration = when { + targetClass != null -> targetClass.addDeclaration(generated as KtNamedDeclaration) + else -> targetFile.add(generated) as KtElement + } + val reformatted = CodeStyleManager.getInstance(project).reformat(generatedDeclaration) + val shortened = ShortenReferences.DEFAULT.process(reformatted as KtElement) + EditorHelper.openInEditor(shortened)?.caretModel?.moveToOffset(shortened.textRange.startOffset) + } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt index 935efe5178f..bf522fde405 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateActualFix.kt @@ -17,12 +17,9 @@ 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.analyzer.ModuleInfo import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor @@ -30,75 +27,39 @@ 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.ModuleSourceInfo -import org.jetbrains.kotlin.idea.core.ShortenReferences 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.util.actualsForExpected -import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier import org.jetbrains.kotlin.resolve.MultiTargetPlatform import org.jetbrains.kotlin.resolve.getMultiTargetPlatform -sealed class CreateActualFix( +sealed class CreateActualFix( declaration: D, - private val actualModule: Module, + actualModule: Module, private val actualPlatform: MultiTargetPlatform.Specific, - private val generateIt: KtPsiFactory.(Project, D) -> D? -) : KotlinQuickFixAction(declaration) { + generateIt: KtPsiFactory.(Project, D) -> D? +) : AbstractCreateDeclarationFix(declaration, actualModule, generateIt) { - override fun getFamilyName() = text - - protected val elementType: String = element.getTypeDescription() - - override fun getText() = "Create actual $elementType for module ${actualModule.name} (${actualPlatform.platform})" - - override fun startInWriteAction() = false + override fun getText() = "Create actual $elementType for module ${module.name} (${actualPlatform.platform})" final override fun invoke(project: Project, editor: Editor?, file: KtFile) { - val element = element ?: return - val factory = KtPsiFactory(project) - val actualFile = getOrCreateImplementationFile() ?: return - DumbService.getInstance(project).runWhenSmart { - val generated = factory.generateIt(project, element) ?: return@runWhenSmart - - project.executeWriteCommand("Create actual declaration") { - if (actualFile.packageDirective?.fqName != file.packageDirective?.fqName && - actualFile.declarations.isEmpty() - ) { - val packageDirective = file.packageDirective - packageDirective?.let { - val oldPackageDirective = actualFile.packageDirective - val newPackageDirective = factory.createPackageDirective(it.fqName) - if (oldPackageDirective != null) { - oldPackageDirective.replace(newPackageDirective) - } else { - actualFile.add(newPackageDirective) - } - } - } - val actualDeclaration = actualFile.add(generated) as KtElement - val reformatted = CodeStyleManager.getInstance(project).reformat(actualDeclaration) - val shortened = ShortenReferences.DEFAULT.process(reformatted as KtElement) - EditorHelper.openInEditor(shortened)?.caretModel?.moveToOffset(shortened.textRange.startOffset) - } - } + doGenerate(project, editor, originalFile = file, targetFile = actualFile, targetClass = null) } - 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.hasExpectModifier()) continue - val actualDeclaration = otherDeclaration.actualsForExpected(actualModule).singleOrNull() ?: continue - return actualDeclaration.containingKtFile - } + override fun findExistingFileToCreateDeclaration( + originalFile: KtFile, + originalDeclaration: KtNamedDeclaration + ): KtFile? { + for (otherDeclaration in originalFile.declarations) { + if (otherDeclaration === originalDeclaration) continue + if (!otherDeclaration.hasExpectModifier()) continue + val actualDeclaration = otherDeclaration.actualsForExpected(module).singleOrNull() ?: continue + return actualDeclaration.containingKtFile } - return createFileForDeclaration(actualModule, declaration) + return null } companion object : KotlinSingleIntentionActionFactory() { diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt index 9a39c701b19..1f3609f803c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/expectactual/CreateExpectedFix.kt @@ -6,24 +6,17 @@ 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.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor 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.core.ShortenReferences 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.refactoring.introduce.showErrorHint -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.psi.* @@ -31,77 +24,34 @@ import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier -sealed class CreateExpectedFix( +sealed class CreateExpectedFix( declaration: D, targetExpectedClass: KtClassOrObject?, - private val commonModule: Module, - private val generateIt: KtPsiFactory.(Project, D) -> D? -) : KotlinQuickFixAction(declaration) { + commonModule: Module, + generateIt: KtPsiFactory.(Project, D) -> D? +) : AbstractCreateDeclarationFix(declaration, commonModule, generateIt) { private val targetExpectedClassPointer = targetExpectedClass?.createSmartPointer() - override fun getFamilyName() = text - - protected val elementType: String = element.getTypeDescription() - - override fun getText() = "Create expected $elementType in common module ${commonModule.name}" - - override fun startInWriteAction() = false + override fun getText() = "Create expected $elementType in common module ${module.name}" 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 = try { - factory.generateIt(project, element) ?: return@runWhenSmart - } catch (e: KotlinTypeInaccessibleException) { - if (editor != null) { - showErrorHint(project, editor, "Cannot generate expected $elementType: " + e.message, e.message) - } - 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) - } - } + doGenerate(project, editor, originalFile = file, targetFile = expectedFile, targetClass = targetExpectedClass) } - 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 - } + override fun findExistingFileToCreateDeclaration( + originalFile: KtFile, + originalDeclaration: KtNamedDeclaration + ): KtFile? { + for (otherDeclaration in originalFile.declarations) { + if (otherDeclaration === originalDeclaration) continue + if (!otherDeclaration.hasActualModifier()) continue + val expectedDeclaration = otherDeclaration.liftToExpected() ?: continue + return expectedDeclaration.containingKtFile } - return createFileForDeclaration(commonModule, declaration) + return null } companion object : KotlinIntentionActionsFactory() {