Create expect / actual refactoring: extract AbstractCreateDeclarationFix

This commit is contained in:
Mikhail Glukhikh
2018-11-22 14:38:28 +03:00
parent 222939efe7
commit 9ae7bc935d
3 changed files with 120 additions and 121 deletions
@@ -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<D : KtNamedDeclaration>(
declaration: D,
protected val module: Module,
protected val generateIt: KtPsiFactory.(Project, D) -> D?
) : KotlinQuickFixAction<D>(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)
}
}
}
}
@@ -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<out D : KtNamedDeclaration>(
sealed class CreateActualFix<D : KtNamedDeclaration>(
declaration: D,
private val actualModule: Module,
actualModule: Module,
private val actualPlatform: MultiTargetPlatform.Specific,
private val generateIt: KtPsiFactory.(Project, D) -> D?
) : KotlinQuickFixAction<D>(declaration) {
generateIt: KtPsiFactory.(Project, D) -> D?
) : AbstractCreateDeclarationFix<D>(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() {
@@ -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<out D : KtNamedDeclaration>(
sealed class CreateExpectedFix<D : KtNamedDeclaration>(
declaration: D,
targetExpectedClass: KtClassOrObject?,
private val commonModule: Module,
private val generateIt: KtPsiFactory.(Project, D) -> D?
) : KotlinQuickFixAction<D>(declaration) {
commonModule: Module,
generateIt: KtPsiFactory.(Project, D) -> D?
) : AbstractCreateDeclarationFix<D>(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() {