Migration to actual/expect: quick-fix CreateActualFix with tests

This commit is contained in:
Mikhail Glukhikh
2017-09-14 18:25:33 +03:00
parent 810d62bbaf
commit fba1a2a2db
41 changed files with 121 additions and 120 deletions
@@ -35,7 +35,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter.Cr
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateLocalVariableActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByNamedArgumentActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByRefActionFactory
import org.jetbrains.kotlin.idea.quickfix.createImpl.CreateHeaderImplementationFix
import org.jetbrains.kotlin.idea.quickfix.createImpl.CreateActualFix
import org.jetbrains.kotlin.idea.quickfix.migration.MigrateExternalExtensionFix
import org.jetbrains.kotlin.idea.quickfix.migration.MigrateTypeParameterListFix
import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageFix
@@ -476,7 +476,7 @@ class QuickFixRegistrar : QuickFixContributor {
OVERLOADS_LOCAL.registerFactory(RemoveAnnotationFix.JvmOverloads)
OVERLOADS_WITHOUT_DEFAULT_ARGUMENTS.registerFactory(RemoveAnnotationFix.JvmOverloads)
HEADER_WITHOUT_IMPLEMENTATION.registerFactory(CreateHeaderImplementationFix)
HEADER_WITHOUT_IMPLEMENTATION.registerFactory(CreateActualFix)
IMPL_MISSING.registerFactory(AddModifierFix.createFactory(KtTokens.IMPL_KEYWORD))
CAST_NEVER_SUCCEEDS.registerFactory(ReplacePrimitiveCastWithNumberConversionFix)
@@ -43,9 +43,9 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.MultiTargetPlatform
import org.jetbrains.kotlin.resolve.getMultiTargetPlatform
sealed class CreateHeaderImplementationFix<out D : KtNamedDeclaration>(
sealed class CreateActualFix<out D : KtNamedDeclaration>(
declaration: D,
private val implPlatform: MultiTargetPlatform.Specific,
private val actualPlatform: MultiTargetPlatform.Specific,
private val generateIt: KtPsiFactory.(Project, D) -> D?
) : KotlinQuickFixAction<D>(declaration) {
@@ -53,7 +53,7 @@ sealed class CreateHeaderImplementationFix<out D : KtNamedDeclaration>(
protected abstract val elementType: String
override fun getText() = "Create header $elementType implementation for platform ${implPlatform.platform}"
override fun getText() = "Create actual $elementType for platform ${actualPlatform.platform}"
override fun startInWriteAction() = false
@@ -61,35 +61,35 @@ sealed class CreateHeaderImplementationFix<out D : KtNamedDeclaration>(
val element = element ?: return
val factory = KtPsiFactory(project)
val implFile = getOrCreateImplementationFile(project) ?: return
val actualFile = getOrCreateImplementationFile(project) ?: return
val generated = factory.generateIt(project, element) ?: return
runWriteAction {
if (implFile.packageDirective?.fqName != file.packageDirective?.fqName &&
implFile.declarations.isEmpty()) {
if (actualFile.packageDirective?.fqName != file.packageDirective?.fqName &&
actualFile.declarations.isEmpty()) {
val packageDirective = file.packageDirective
packageDirective?.let {
val oldPackageDirective = implFile.packageDirective
val oldPackageDirective = actualFile.packageDirective
val newPackageDirective = factory.createPackageDirective(it.fqName)
if (oldPackageDirective != null) {
oldPackageDirective.replace(newPackageDirective)
}
else {
implFile.add(newPackageDirective)
actualFile.add(newPackageDirective)
}
}
}
val implDeclaration = implFile.add(generated) as KtElement
val reformatted = CodeStyleManager.getInstance(project).reformat(implDeclaration)
val actualDeclaration = actualFile.add(generated) as KtElement
val reformatted = CodeStyleManager.getInstance(project).reformat(actualDeclaration)
ShortenReferences.DEFAULT.process(reformatted as KtElement)
}
}
private fun Project.implementationModuleOf(headerModule: Module) =
private fun Project.implementationModuleOf(expectedModule: Module) =
allModules().firstOrNull {
PackageUtil.checkSourceRootsConfigured(it, false) &&
TargetPlatformDetector.getPlatform(it).multiTargetPlatform == implPlatform &&
headerModule in ModuleRootManager.getInstance(it).dependencies
TargetPlatformDetector.getPlatform(it).multiTargetPlatform == actualPlatform &&
expectedModule in ModuleRootManager.getInstance(it).dependencies
}
private fun getOrCreateImplementationFile(
@@ -98,36 +98,36 @@ sealed class CreateHeaderImplementationFix<out D : KtNamedDeclaration>(
val declaration = element as? KtNamedDeclaration ?: return null
val name = declaration.name ?: return null
val headerDir = declaration.containingFile.containingDirectory
val headerPackage = JavaDirectoryService.getInstance().getPackage(headerDir)
val expectedDir = declaration.containingFile.containingDirectory
val expectedPackage = JavaDirectoryService.getInstance().getPackage(expectedDir)
val headerModule = ModuleUtilCore.findModuleForPsiElement(declaration) ?: return null
val implModule = project.implementationModuleOf(headerModule) ?: return null
val implDirectory = PackageUtil.findOrCreateDirectoryForPackage(
implModule, headerPackage?.qualifiedName ?: "", null, false
val expectedModule = ModuleUtilCore.findModuleForPsiElement(declaration) ?: return null
val actualModule = project.implementationModuleOf(expectedModule) ?: return null
val actualDirectory = PackageUtil.findOrCreateDirectoryForPackage(
actualModule, expectedPackage?.qualifiedName ?: "", null, false
) ?: return null
return runWriteAction {
val fileName = "$name.kt"
val existingFile = implDirectory.findFile(fileName)
val existingFile = actualDirectory.findFile(fileName)
val packageDirective = declaration.containingKtFile.packageDirective
val packageName =
if (packageDirective?.packageNameExpression == null) implDirectory.getPackage()?.qualifiedName
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) {
implDirectory.findFile("$it.kt") == null
actualDirectory.findFile("$it.kt") == null
} + ".kt"
createKotlinFile(newName, implDirectory, packageName)
createKotlinFile(newName, actualDirectory, packageName)
}
else {
existingFile
}
}
else {
createKotlinFile(fileName, implDirectory, packageName)
createKotlinFile(fileName, actualDirectory, packageName)
}
}
}
@@ -138,67 +138,68 @@ sealed class CreateHeaderImplementationFix<out D : KtNamedDeclaration>(
val declaration = d.psiElement as? KtNamedDeclaration ?: return null
val compatibility = d.c
if (compatibility.isNotEmpty()) return null
val implPlatform = d.b.getMultiTargetPlatform() as? MultiTargetPlatform.Specific ?: return null
val actualPlatform = d.b.getMultiTargetPlatform() as? MultiTargetPlatform.Specific ?: return null
return when (declaration) {
is KtClassOrObject -> CreateHeaderClassImplementationFix(declaration, implPlatform)
is KtFunction -> CreateHeaderFunctionImplementationFix(declaration, implPlatform)
is KtProperty -> CreateHeaderPropertyImplementationFix(declaration, implPlatform)
is KtClassOrObject -> CreateActualClassFix(declaration, actualPlatform)
is KtFunction -> CreateActualFunctionFix(declaration, actualPlatform)
is KtProperty -> CreateActualPropertyFix(declaration, actualPlatform)
else -> null
}
}
}
}
class CreateHeaderClassImplementationFix(
class CreateActualClassFix(
klass: KtClassOrObject,
implPlatform: MultiTargetPlatform.Specific
) : CreateHeaderImplementationFix<KtClassOrObject>(klass, implPlatform, { project, element ->
generateClassOrObject(project, element, implNeeded = true)
actualPlatform: MultiTargetPlatform.Specific
) : CreateActualFix<KtClassOrObject>(klass, actualPlatform, { project, element ->
generateClassOrObject(project, element, actualNeeded = true)
}) {
override val elementType = if ((element as? KtClass)?.isInterface() ?: false) "interface" else "class"
override val elementType = if ((element as? KtClass)?.isInterface() == true) "interface" else "class"
}
class CreateHeaderPropertyImplementationFix(
class CreateActualPropertyFix(
property: KtProperty,
implPlatform: MultiTargetPlatform.Specific
) : CreateHeaderImplementationFix<KtProperty>(property, implPlatform, { project, element ->
actualPlatform: MultiTargetPlatform.Specific
) : CreateActualFix<KtProperty>(property, actualPlatform, { project, element ->
val descriptor = element.toDescriptor() as? PropertyDescriptor
descriptor?.let { generateProperty(project, element, descriptor, implNeeded = true) }
descriptor?.let { generateProperty(project, element, descriptor, actualNeeded = true) }
}) {
override val elementType = "property"
}
class CreateHeaderFunctionImplementationFix(
class CreateActualFunctionFix(
function: KtFunction,
implPlatform: MultiTargetPlatform.Specific
) : CreateHeaderImplementationFix<KtFunction>(function, implPlatform, { project, element ->
actualPlatform: MultiTargetPlatform.Specific
) : CreateActualFix<KtFunction>(function, actualPlatform, { project, element ->
val descriptor = element.toDescriptor() as? FunctionDescriptor
descriptor?.let { generateFunction(project, element, descriptor, implNeeded = true) }
descriptor?.let { generateFunction(project, element, descriptor, actualNeeded = true) }
}) {
override val elementType = "function"
}
private fun KtModifierListOwner.replaceHeaderModifier(implNeeded: Boolean) {
if (implNeeded) {
addModifier(KtTokens.IMPL_KEYWORD)
private fun KtModifierListOwner.replaceExpectModifier(actualNeeded: Boolean) {
if (actualNeeded) {
addModifier(KtTokens.ACTUAL_KEYWORD)
}
else {
removeModifier(KtTokens.HEADER_KEYWORD)
removeModifier(KtTokens.EXPECT_KEYWORD)
}
}
private fun KtPsiFactory.generateClassOrObject(
project: Project,
headerClass: KtClassOrObject,
implNeeded: Boolean
expectedClass: KtClassOrObject,
actualNeeded: Boolean
): KtClassOrObject {
val header = headerClass.text
val implClass = if (headerClass is KtObjectDeclaration) createObject(header) else createClass(header)
if (headerClass !is KtClass || !headerClass.isInterface()) {
implClass.declarations.forEach {
val expectedText = expectedClass.text
val actualClass = if (expectedClass is KtObjectDeclaration) createObject(expectedText) else createClass(expectedText)
if (expectedClass !is KtClass || !expectedClass.isInterface()) {
actualClass.declarations.forEach {
if (it !is KtEnumEntry &&
it !is KtClassOrObject &&
!it.hasModifier(KtTokens.ABSTRACT_KEYWORD)) {
@@ -206,28 +207,28 @@ private fun KtPsiFactory.generateClassOrObject(
}
}
declLoop@ for (headerDeclaration in headerClass.declarations) {
if (headerDeclaration.hasModifier(KtTokens.ABSTRACT_KEYWORD)) continue
val descriptor = headerDeclaration.toDescriptor() ?: continue
val implDeclaration: KtDeclaration = when (headerDeclaration) {
is KtFunction -> generateFunction(project, headerDeclaration, descriptor as FunctionDescriptor, implNeeded = true)
is KtProperty -> generateProperty(project, headerDeclaration, descriptor as PropertyDescriptor, implNeeded = true)
declLoop@ for (expectedDeclaration in expectedClass.declarations) {
if (expectedDeclaration.hasModifier(KtTokens.ABSTRACT_KEYWORD)) continue
val descriptor = expectedDeclaration.toDescriptor() ?: continue
val actualDeclaration: KtDeclaration = when (expectedDeclaration) {
is KtFunction -> generateFunction(project, expectedDeclaration, descriptor as FunctionDescriptor, actualNeeded = true)
is KtProperty -> generateProperty(project, expectedDeclaration, descriptor as PropertyDescriptor, actualNeeded = true)
else -> continue@declLoop
}
implClass.addDeclaration(implDeclaration)
actualClass.addDeclaration(actualDeclaration)
}
}
return implClass.apply {
replaceHeaderModifier(implNeeded)
return actualClass.apply {
replaceExpectModifier(actualNeeded)
}
}
private fun KtPsiFactory.generateFunction(
project: Project,
headerFunction: KtFunction,
expectedFunction: KtFunction,
descriptor: FunctionDescriptor,
implNeeded: Boolean
actualNeeded: Boolean
): KtFunction {
val returnType = descriptor.returnType
val body = run {
@@ -246,12 +247,12 @@ private fun KtPsiFactory.generateFunction(
}
}
return if (headerFunction is KtSecondaryConstructor) {
createSecondaryConstructor(headerFunction.text + " " + body)
return if (expectedFunction is KtSecondaryConstructor) {
createSecondaryConstructor(expectedFunction.text + " " + body)
}
else {
createFunction(headerFunction.text + " " + body).apply {
replaceHeaderModifier(implNeeded)
createFunction(expectedFunction.text + " " + body).apply {
replaceExpectModifier(actualNeeded)
if (returnType != null && KotlinBuiltIns.isUnit(returnType)) {
typeReference = null
}
@@ -261,9 +262,9 @@ private fun KtPsiFactory.generateFunction(
private fun KtPsiFactory.generateProperty(
project: Project,
headerProperty: KtProperty,
expectedProperty: KtProperty,
descriptor: PropertyDescriptor,
implNeeded: Boolean
actualNeeded: Boolean
): KtProperty {
val body = buildString {
append("\nget()")
@@ -278,8 +279,8 @@ private fun KtPsiFactory.generateProperty(
append("\nset(value) {}")
}
}
return createProperty(headerProperty.text + " " + body).apply {
replaceHeaderModifier(implNeeded)
return createProperty(expectedProperty.text + " " + body).apply {
replaceExpectModifier(actualNeeded)
}
}
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JS" "true"
// "Create actual class for platform JS" "true"
header abstract class <caret>Abstract {
fun foo(param: String): Int
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JS" "true"
// "Create actual class for platform JS" "true"
header abstract class Abstract {
fun foo(param: String): Int
@@ -1,13 +1,13 @@
// Abstract: to be implemented
impl abstract class Abstract {
actual abstract class Abstract {
abstract fun String.bar(y: Double): Boolean
abstract var status: Int
impl fun foo(param: String): Int {
actual fun foo(param: String): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
impl val isGood: Boolean
actual val isGood: Boolean
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
+1 -1
View File
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
header class <caret>My {
fun foo(param: String): Int
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
header class My {
fun foo(param: String): Int
+6 -6
View File
@@ -1,22 +1,22 @@
// My: to be implemented
impl class My {
impl fun foo(param: String): Int {
actual class My {
actual fun foo(param: String): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
impl fun String.bar(y: Double): Boolean {
actual fun String.bar(y: Double): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
impl fun baz() {}
actual fun baz() {}
constructor(flag: Boolean) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
impl val isGood: Boolean
actual val isGood: Boolean
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
impl var status: Int
actual var status: Int
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
set(value) {}
+1 -1
View File
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JS" "true"
// "Create actual class for platform JS" "true"
header enum class <caret>MyEnum {
FIRST,
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JS" "true"
// "Create actual class for platform JS" "true"
header enum class MyEnum {
FIRST,
+2 -2
View File
@@ -1,10 +1,10 @@
// MyEnum: to be implemented
impl enum class MyEnum {
actual enum class MyEnum {
FIRST,
SECOND,
LAST;
impl val num: Int
actual val num: Int
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
@@ -1,3 +1,3 @@
// "Create header function implementation for platform JVM" "true"
// "Create actual function for platform JVM" "true"
header fun <caret>foo(arg: Int): String
@@ -1,3 +1,3 @@
// "Create header function implementation for platform JVM" "true"
// "Create actual function for platform JVM" "true"
header fun foo(arg: Int): String
@@ -1,4 +1,4 @@
// foo: to be implemented
impl fun foo(arg: Int): String {
actual fun foo(arg: Int): String {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
@@ -1,4 +1,4 @@
// "Create header interface implementation for platform JVM" "true"
// "Create actual interface for platform JVM" "true"
header interface <caret>Interface {
fun foo(param: String): Int
@@ -1,4 +1,4 @@
// "Create header interface implementation for platform JVM" "true"
// "Create actual interface for platform JVM" "true"
header interface Interface {
fun foo(param: String): Int
@@ -1,5 +1,5 @@
// Interface: to be implemented
impl interface Interface {
actual interface Interface {
fun foo(param: String): Int
fun String.bar(y: Double): Boolean
+1 -1
View File
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
header class <caret>WithNested {
fun foo(): Int
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
header class WithNested {
fun foo(): Int
@@ -1,5 +1,5 @@
// WithNested: to be implemented
impl class WithNested {
actual class WithNested {
class Nested {
fun bar()
@@ -9,7 +9,7 @@ impl class WithNested {
fun baz()
}
impl fun foo(): Int {
actual fun foo(): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
header object <caret>Object {
fun foo(): String
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
header object Object {
fun foo(): String
@@ -1,6 +1,6 @@
// Object: to be implemented
impl object Object {
impl fun foo(): String {
actual object Object {
actual fun foo(): String {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
package test.inner
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
package test.inner
+1 -1
View File
@@ -1,3 +1,3 @@
package test.inner
impl class My
actual class My
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
package test.inner
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
package test.inner
@@ -1,3 +1,3 @@
package test.inner
impl class My
actual class My
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
package test.inner
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JVM" "true"
// "Create actual class for platform JVM" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
package test.inner
@@ -1,3 +1,3 @@
package test.inner
impl class My
actual class My
@@ -1,3 +1,3 @@
// "Create header property implementation for platform JVM" "true"
// "Create actual property for platform JVM" "true"
header var <caret>x: Int
@@ -1,3 +1,3 @@
// "Create header property implementation for platform JVM" "true"
// "Create actual property for platform JVM" "true"
header var x: Int
+1 -1
View File
@@ -1,4 +1,4 @@
// x: to be implemented
impl var x: Int
actual var x: Int
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
set(value) {}
+1 -1
View File
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JS" "true"
// "Create actual class for platform JS" "true"
header sealed class <caret>Sealed {
object Obj : Sealed
@@ -1,4 +1,4 @@
// "Create header class implementation for platform JS" "true"
// "Create actual class for platform JS" "true"
header sealed class Sealed {
object Obj : Sealed
@@ -1,5 +1,5 @@
// Sealed: to be implemented
impl sealed class Sealed {
actual sealed class Sealed {
object Obj : Sealed
class Klass(x: Int) : Sealed
@@ -1,4 +1,4 @@
// "Create header function implementation for platform JVM" "true"
// "Create actual function for platform JVM" "true"
package test
@@ -1,4 +1,4 @@
// "Create header function implementation for platform JVM" "true"
// "Create actual function for platform JVM" "true"
package test
@@ -2,4 +2,4 @@
package test
impl fun testHelper() {}
actual fun testHelper() {}