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 package org.jetbrains.kotlin.idea.quickfix.expectactual
import com.intellij.codeInsight.intention.IntentionAction import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.ide.util.EditorHelper
import com.intellij.openapi.editor.Editor 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.Project import com.intellij.openapi.project.Project
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor 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.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.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.toDescriptor 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.quickfix.KotlinSingleIntentionActionFactory
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.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier
import org.jetbrains.kotlin.resolve.MultiTargetPlatform import org.jetbrains.kotlin.resolve.MultiTargetPlatform
import org.jetbrains.kotlin.resolve.getMultiTargetPlatform import org.jetbrains.kotlin.resolve.getMultiTargetPlatform
sealed class CreateActualFix<out D : KtNamedDeclaration>( sealed class CreateActualFix<D : KtNamedDeclaration>(
declaration: D, declaration: D,
private val actualModule: Module, actualModule: Module,
private val actualPlatform: MultiTargetPlatform.Specific, private val actualPlatform: MultiTargetPlatform.Specific,
private val generateIt: KtPsiFactory.(Project, D) -> D? generateIt: KtPsiFactory.(Project, D) -> D?
) : KotlinQuickFixAction<D>(declaration) { ) : AbstractCreateDeclarationFix<D>(declaration, actualModule, generateIt) {
override fun getFamilyName() = text override fun getText() = "Create actual $elementType for module ${module.name} (${actualPlatform.platform})"
protected val elementType: String = element.getTypeDescription()
override fun getText() = "Create actual $elementType for module ${actualModule.name} (${actualPlatform.platform})"
override fun startInWriteAction() = false
final override fun invoke(project: Project, editor: Editor?, file: KtFile) { final override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return
val factory = KtPsiFactory(project)
val actualFile = getOrCreateImplementationFile() ?: return val actualFile = getOrCreateImplementationFile() ?: return
DumbService.getInstance(project).runWhenSmart { doGenerate(project, editor, originalFile = file, targetFile = actualFile, targetClass = null)
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)
}
}
} }
private fun getOrCreateImplementationFile(): KtFile? { override fun findExistingFileToCreateDeclaration(
val declaration = element as? KtNamedDeclaration ?: return null originalFile: KtFile,
val parent = declaration.parent originalDeclaration: KtNamedDeclaration
if (parent is KtFile) { ): KtFile? {
for (otherDeclaration in parent.declarations) { for (otherDeclaration in originalFile.declarations) {
if (otherDeclaration === declaration) continue if (otherDeclaration === originalDeclaration) continue
if (!otherDeclaration.hasExpectModifier()) continue if (!otherDeclaration.hasExpectModifier()) continue
val actualDeclaration = otherDeclaration.actualsForExpected(actualModule).singleOrNull() ?: continue val actualDeclaration = otherDeclaration.actualsForExpected(module).singleOrNull() ?: continue
return actualDeclaration.containingKtFile return actualDeclaration.containingKtFile
}
} }
return createFileForDeclaration(actualModule, declaration) return null
} }
companion object : KotlinSingleIntentionActionFactory() { companion object : KotlinSingleIntentionActionFactory() {
@@ -6,24 +6,17 @@
package org.jetbrains.kotlin.idea.quickfix.expectactual package org.jetbrains.kotlin.idea.quickfix.expectactual
import com.intellij.codeInsight.intention.IntentionAction import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.ide.util.EditorHelper
import com.intellij.openapi.editor.Editor 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.Project import com.intellij.openapi.project.Project
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory 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.implementedModules 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.core.toDescriptor
import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory 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.liftToExpected
import org.jetbrains.kotlin.idea.util.module import org.jetbrains.kotlin.idea.util.module
import org.jetbrains.kotlin.psi.* 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.createSmartPointer
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
sealed class CreateExpectedFix<out D : KtNamedDeclaration>( sealed class CreateExpectedFix<D : KtNamedDeclaration>(
declaration: D, declaration: D,
targetExpectedClass: KtClassOrObject?, targetExpectedClass: KtClassOrObject?,
private val commonModule: Module, commonModule: Module,
private val generateIt: KtPsiFactory.(Project, D) -> D? generateIt: KtPsiFactory.(Project, D) -> D?
) : KotlinQuickFixAction<D>(declaration) { ) : AbstractCreateDeclarationFix<D>(declaration, commonModule, generateIt) {
private val targetExpectedClassPointer = targetExpectedClass?.createSmartPointer() private val targetExpectedClassPointer = targetExpectedClass?.createSmartPointer()
override fun getFamilyName() = text override fun getText() = "Create expected $elementType in common module ${module.name}"
protected val elementType: String = element.getTypeDescription()
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) { final override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return
val factory = KtPsiFactory(project)
val targetExpectedClass = targetExpectedClassPointer?.element val targetExpectedClass = targetExpectedClassPointer?.element
val expectedFile = targetExpectedClass?.containingKtFile ?: getOrCreateImplementationFile() ?: return val expectedFile = targetExpectedClass?.containingKtFile ?: getOrCreateImplementationFile() ?: return
DumbService.getInstance(project).runWhenSmart { doGenerate(project, editor, originalFile = file, targetFile = expectedFile, targetClass = targetExpectedClass)
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)
}
}
} }
private fun getOrCreateImplementationFile(): KtFile? { override fun findExistingFileToCreateDeclaration(
val declaration = element as? KtNamedDeclaration ?: return null originalFile: KtFile,
val parent = declaration.parent originalDeclaration: KtNamedDeclaration
if (parent is KtFile) { ): KtFile? {
for (otherDeclaration in parent.declarations) { for (otherDeclaration in originalFile.declarations) {
if (otherDeclaration === declaration) continue if (otherDeclaration === originalDeclaration) continue
if (!otherDeclaration.hasActualModifier()) continue if (!otherDeclaration.hasActualModifier()) continue
val expectedDeclaration = otherDeclaration.liftToExpected() ?: continue val expectedDeclaration = otherDeclaration.liftToExpected() ?: continue
return expectedDeclaration.containingKtFile return expectedDeclaration.containingKtFile
}
} }
return createFileForDeclaration(commonModule, declaration) return null
} }
companion object : KotlinIntentionActionsFactory() { companion object : KotlinIntentionActionsFactory() {