Create expect / actual refactoring: extract AbstractCreateDeclarationFix
This commit is contained in:
+88
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user