Wizard: add ability for wizard to generate expected/actual declarations in MPP module
This commit is contained in:
committed by
Kirill Shmakov
parent
7d1036ee7e
commit
66c756ad0f
@@ -9,11 +9,10 @@ dependencies {
|
||||
api("org.apache.velocity:velocity:1.7") // we have to use the old version as it is the same as bundled into IntelliJ
|
||||
compileOnly(project(":kotlin-reflect-api"))
|
||||
|
||||
//needed only for message bundles
|
||||
implementation(intellijDep()) { includeJars("util") }
|
||||
implementation(intellijDep()) { includeJars("util") } //needed only for message bundles
|
||||
testImplementation(intellijDep()) { includeJars("trove4j") } //needed only for message bundles
|
||||
|
||||
testImplementation(project(":kotlin-test:kotlin-test-junit"))
|
||||
testImplementation(commonDep("junit:junit"))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:${property("versions.kotlinx-collections-immutable")}")
|
||||
}
|
||||
|
||||
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.tools.projectWizard.moduleConfigurators
|
||||
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.kotlin.tools.projectWizard.KotlinNewProjectWizardBundle
|
||||
import org.jetbrains.kotlin.tools.projectWizard.core.*
|
||||
import org.jetbrains.kotlin.tools.projectWizard.core.entity.properties.ModuleConfiguratorProperty
|
||||
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.KotlinBuildSystemPluginIR
|
||||
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
|
||||
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData
|
||||
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectPath
|
||||
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
|
||||
import org.jetbrains.kotlin.tools.projectWizard.settings.JavaPackage
|
||||
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
|
||||
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
|
||||
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplate
|
||||
import org.jetbrains.kotlin.tools.projectWizard.templates.FileTextDescriptor
|
||||
import java.nio.file.Path
|
||||
|
||||
object MppModuleConfigurator : ModuleConfigurator,
|
||||
ModuleConfiguratorWithSettings,
|
||||
ModuleConfiguratorSettings(),
|
||||
ModuleConfiguratorProperties,
|
||||
ModuleConfiguratorWithProperties {
|
||||
|
||||
override val moduleKind = ModuleKind.multiplatform
|
||||
|
||||
@NonNls
|
||||
override val suggestedModuleName = "shared"
|
||||
|
||||
@NonNls
|
||||
override val id = "multiplatform"
|
||||
override val text = KotlinNewProjectWizardBundle.message("module.configurator.mpp")
|
||||
override val canContainSubModules = true
|
||||
|
||||
override fun createKotlinPluginIR(configurationData: ModulesToIrConversionData, module: Module): KotlinBuildSystemPluginIR? =
|
||||
KotlinBuildSystemPluginIR(
|
||||
KotlinBuildSystemPluginIR.Type.multiplatform,
|
||||
version = configurationData.kotlinVersion
|
||||
)
|
||||
|
||||
val mppFiles by listProperty<MppFile>()
|
||||
|
||||
override fun getConfiguratorProperties(): List<ModuleConfiguratorProperty<*>> =
|
||||
listOf(mppFiles)
|
||||
|
||||
override fun Writer.runArbitraryTask(
|
||||
configurationData: ModulesToIrConversionData,
|
||||
module: Module,
|
||||
modulePath: Path,
|
||||
): TaskResult<Unit> = inContextOfModuleConfigurator(module) {
|
||||
val mppFiles = mppFiles.reference.propertyValue.distinctBy { it.fileNameWithPackage } // todo merge files with the same name here
|
||||
mppFiles.mapSequenceIgnore { file ->
|
||||
module.subModules.mapSequenceIgnore mapTargets@{ target ->
|
||||
val moduleSubType = //TODO handle for non-simple target configurator
|
||||
target.configurator.safeAs<SimpleTargetConfigurator>()?.moduleSubType ?: return@mapTargets UNIT_SUCCESS
|
||||
val fileTemplate = FileTemplate(
|
||||
FileTextDescriptor(file.printForModuleSubType(moduleSubType), pathForFileInTarget(modulePath, module, file, target)),
|
||||
projectPath,
|
||||
)
|
||||
TemplatesPlugin.fileTemplatesToRender.addValues(fileTemplate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun pathForFileInTarget(mppModulePath: Path, mppModule: Module, file: MppFile, target: Module) =
|
||||
mppModulePath /
|
||||
Defaults.SRC_DIR /
|
||||
"${target.name}Main" /
|
||||
mppModule.configurator.kotlinDirectoryName /
|
||||
file.javaPackage?.asPath() /
|
||||
file.filename
|
||||
}
|
||||
|
||||
|
||||
@DslMarker
|
||||
annotation class ExpectFileDSL
|
||||
|
||||
data class MppFile(
|
||||
@NonNls val filename: String,
|
||||
@NonNls val javaPackage: JavaPackage? = null,
|
||||
val declarations: List<MppDeclaration>
|
||||
) {
|
||||
|
||||
val fileNameWithPackage
|
||||
get() = javaPackage?.let { "${it.asCodePackage()}." }.orEmpty() + filename
|
||||
|
||||
fun printForModuleSubType(moduleSubType: ModuleSubType): String = buildString {
|
||||
javaPackage?.let { appendLine("package ${it.asCodePackage()}"); appendLine() }
|
||||
if (moduleSubType == ModuleSubType.common) {
|
||||
declarations.joinTo(this, separator = "\n\n") { it.printExpect() }
|
||||
} else {
|
||||
printImports(declarations.flatMap { it.importsFor(moduleSubType) })
|
||||
declarations.joinTo(this, separator = "\n\n") { it.printActualFor(moduleSubType) }
|
||||
}
|
||||
}.trim()
|
||||
|
||||
private fun StringBuilder.printImports(imports: List<String>) {
|
||||
if (imports.isEmpty()) return
|
||||
imports.distinct().joinTo(this, separator = "\n", prefix = "\n", postfix = "\n\n") { import ->
|
||||
"import $import"
|
||||
}
|
||||
}
|
||||
|
||||
@ExpectFileDSL
|
||||
class Builder(private val filename: String) {
|
||||
var `package`: String? = null
|
||||
private val declarations = mutableListOf<MppDeclaration>()
|
||||
|
||||
fun function(signature: String, init: MppFunction.Builder.() -> Unit = {}) {
|
||||
declarations += MppFunction.Builder(signature).apply(init).build()
|
||||
}
|
||||
|
||||
fun `class`(name: String, init: MppClass.Builder.() -> Unit = {}) {
|
||||
declarations += MppClass.Builder(name).apply(init).build()
|
||||
}
|
||||
|
||||
fun build() = MppFile(filename, `package`?.let(::JavaPackage), declarations)
|
||||
}
|
||||
}
|
||||
|
||||
@ExpectFileDSL
|
||||
class MppFilesListBuilder {
|
||||
private val expectFiles = mutableListOf<MppFile>()
|
||||
|
||||
fun mppfile(filename: String, init: MppFile.Builder.() -> Unit) {
|
||||
expectFiles += MppFile.Builder(filename).apply(init).build()
|
||||
}
|
||||
|
||||
fun build(): List<MppFile> = expectFiles
|
||||
}
|
||||
|
||||
fun mppFiles(init: MppFilesListBuilder.() -> Unit): List<MppFile> =
|
||||
MppFilesListBuilder().apply(init).build()
|
||||
|
||||
sealed class MppDeclaration {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
abstract val actuals: Actuals
|
||||
|
||||
abstract fun printExpect(): String
|
||||
|
||||
abstract fun printActualFor(moduleSubType: ModuleSubType): String
|
||||
|
||||
fun importsFor(moduleSubType: ModuleSubType): List<String> = actuals.getBodyFor(moduleSubType).imports
|
||||
|
||||
@ExpectFileDSL
|
||||
abstract class Builder {
|
||||
private var defaultActualDeclarationBody = DefaultActualDeclarationBody(text = "", imports = emptyList())
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private val actuals = mutableMapOf<ModuleSubType, ActualDeclarationBody>()
|
||||
|
||||
fun default(defaultText: String, init: DefaultActualDeclarationBody.Builder.() -> Unit = {}) {
|
||||
defaultActualDeclarationBody = DefaultActualDeclarationBody.Builder(defaultText).also(init).build()
|
||||
}
|
||||
|
||||
fun actualFor(vararg moduleSubTypes: ModuleSubType, actualBody: String, init: ActualDeclarationBody.Builder.() -> Unit = {}) {
|
||||
moduleSubTypes.forEach { moduleSubType ->
|
||||
actuals[moduleSubType] = ActualDeclarationBody.Builder(actualBody).apply(init).build()
|
||||
}
|
||||
}
|
||||
|
||||
protected fun buildActuals() = Actuals(defaultActualDeclarationBody, actuals)
|
||||
}
|
||||
}
|
||||
|
||||
data class MppFunction(
|
||||
val signature: String,
|
||||
@Suppress("SpellCheckingInspection")
|
||||
override val actuals: Actuals,
|
||||
) : MppDeclaration() {
|
||||
override fun printExpect(): String =
|
||||
"expect fun $signature"
|
||||
|
||||
override fun printActualFor(moduleSubType: ModuleSubType): String {
|
||||
val body = actuals.getBodyFor(moduleSubType)
|
||||
return buildString {
|
||||
appendLine("actual fun $signature {")
|
||||
appendLine(body.text.indented(4))
|
||||
append("}")
|
||||
}
|
||||
}
|
||||
|
||||
@ExpectFileDSL
|
||||
class Builder(private val signature: String) : MppDeclaration.Builder() {
|
||||
fun build(): MppFunction = MppFunction(
|
||||
signature,
|
||||
buildActuals(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class MppClass(
|
||||
val name: String,
|
||||
val expectBody: String?,
|
||||
@Suppress("SpellCheckingInspection")
|
||||
override val actuals: Actuals,
|
||||
) : MppDeclaration() {
|
||||
override fun printExpect(): String = buildString {
|
||||
append("expect class $name()")
|
||||
expectBody?.let { body ->
|
||||
appendLine(" {")
|
||||
appendLine(body.indented(4))
|
||||
append("}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun printActualFor(moduleSubType: ModuleSubType): String {
|
||||
val body = actuals.getBodyFor(moduleSubType)
|
||||
return buildString {
|
||||
appendLine("actual class $name actual constructor() {")
|
||||
appendLine(body.text.indented(4))
|
||||
append("}")
|
||||
}
|
||||
}
|
||||
|
||||
@ExpectFileDSL
|
||||
class Builder(private val name: String) : MppDeclaration.Builder() {
|
||||
var expectBody: String? = null
|
||||
|
||||
fun build(): MppClass = MppClass(
|
||||
name,
|
||||
expectBody,
|
||||
buildActuals(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun String.indented(indentValue: Int): String {
|
||||
val indent = " ".repeat(indentValue)
|
||||
return split('\n').joinToString(separator = "\n") { line -> indent + line.trim() }
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
data class Actuals(
|
||||
val defaultActualDeclarationBody: DefaultActualDeclarationBody,
|
||||
val actuals: Map<ModuleSubType, ActualDeclarationBody>,
|
||||
) {
|
||||
fun getBodyFor(moduleSubType: ModuleSubType): DeclarationBody =
|
||||
actuals[moduleSubType] ?: defaultActualDeclarationBody
|
||||
}
|
||||
|
||||
sealed class DeclarationBody {
|
||||
abstract val text: String
|
||||
abstract val imports: List<String>
|
||||
|
||||
abstract class Builder {
|
||||
protected val imports = mutableListOf<String>()
|
||||
|
||||
fun import(import: String) {
|
||||
imports += import
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ActualDeclarationBody(override val text: String, override val imports: List<String>) : DeclarationBody() {
|
||||
class Builder(private val text: String) : DeclarationBody.Builder() {
|
||||
fun build() = ActualDeclarationBody(text, imports)
|
||||
}
|
||||
}
|
||||
|
||||
data class DefaultActualDeclarationBody(override val text: String, override val imports: List<String>) : DeclarationBody() {
|
||||
class Builder(private val text: String) : DeclarationBody.Builder() {
|
||||
fun build() = DefaultActualDeclarationBody(text, imports)
|
||||
}
|
||||
}
|
||||
+1
-18
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.isGradle
|
||||
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
|
||||
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData
|
||||
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
|
||||
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData
|
||||
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
|
||||
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
|
||||
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
|
||||
@@ -58,24 +59,6 @@ interface ModuleConfiguratorWithModuleType : ModuleConfigurator {
|
||||
val ModuleConfigurator.moduleType: ModuleType?
|
||||
get() = safeAs<ModuleConfiguratorWithModuleType>()?.moduleType
|
||||
|
||||
object MppModuleConfigurator : ModuleConfigurator {
|
||||
override val moduleKind = ModuleKind.multiplatform
|
||||
|
||||
@NonNls
|
||||
override val suggestedModuleName = "shared"
|
||||
|
||||
@NonNls
|
||||
override val id = "multiplatform"
|
||||
override val text = KotlinNewProjectWizardBundle.message("module.configurator.mpp")
|
||||
override val canContainSubModules = true
|
||||
|
||||
override fun createKotlinPluginIR(configurationData: ModulesToIrConversionData, module: Module): KotlinBuildSystemPluginIR? =
|
||||
KotlinBuildSystemPluginIR(
|
||||
KotlinBuildSystemPluginIR.Type.multiplatform,
|
||||
version = configurationData.kotlinVersion
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
interface SinglePlatformModuleConfigurator : ModuleConfigurator {
|
||||
val needCreateBuildFile: Boolean get() = true
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
|
||||
import java.nio.file.Paths
|
||||
|
||||
class JavaPackage(val parts: List<String>) {
|
||||
constructor(vararg parts: String) : this(parts.toList())
|
||||
constructor(`package`: String) : this(`package`.split('.'))
|
||||
|
||||
fun asPath() = Paths.get(parts.joinToString(separator = "/"))
|
||||
fun asCodePackage() = parts.joinToString(separator = ".")
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.tools.projectWizard.core
|
||||
|
||||
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.mppFiles
|
||||
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ExpectedFileTest {
|
||||
@Test
|
||||
fun `it should generate valid expect functions`() {
|
||||
val file = mppFiles {
|
||||
mppfile("file") {
|
||||
function("f(x: Int): String")
|
||||
function("y(x: Int): String")
|
||||
}
|
||||
}.first()
|
||||
val generatedFileText = file.printForModuleSubType(ModuleSubType.common)
|
||||
val expectedFileText = """
|
||||
expect fun f(x: Int): String
|
||||
|
||||
expect fun y(x: Int): String
|
||||
""".trimIndent().trim()
|
||||
assertEquals(expectedFileText, generatedFileText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `it should generate valid expect classes`() {
|
||||
val file = mppFiles {
|
||||
mppfile("file") {
|
||||
`class`("A")
|
||||
`class`("B")
|
||||
}
|
||||
}.first()
|
||||
val generatedFileText = file.printForModuleSubType(ModuleSubType.common)
|
||||
val expectedFileText = """
|
||||
expected class A expected constructor()
|
||||
|
||||
expected class B expected constructor()
|
||||
""".trimIndent().trim()
|
||||
assertEquals(expectedFileText, generatedFileText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `it should generate valid actual functions`() {
|
||||
val file = mppFiles {
|
||||
mppfile("file") {
|
||||
function("foo()") {
|
||||
actualFor(
|
||||
ModuleSubType.jvm,
|
||||
actualBody = "println(\"jvm\")\nval a = 10"
|
||||
)
|
||||
actualFor(ModuleSubType.js, actualBody = """println("js")""")
|
||||
default("TODO()")
|
||||
}
|
||||
}
|
||||
}.first()
|
||||
run {
|
||||
val generatedFileText = file.printForModuleSubType(ModuleSubType.jvm)
|
||||
val expectedFileText = """
|
||||
actual fun foo() {
|
||||
println("jvm")
|
||||
val a = 10
|
||||
}
|
||||
""".trimIndent().trim()
|
||||
assertEquals(expectedFileText, generatedFileText)
|
||||
}
|
||||
|
||||
run {
|
||||
val generatedFileText = file.printForModuleSubType(ModuleSubType.js)
|
||||
val expectedFileText = """
|
||||
actual fun foo() {
|
||||
println("js")
|
||||
}
|
||||
""".trimIndent().trim()
|
||||
assertEquals(expectedFileText, generatedFileText)
|
||||
}
|
||||
run {
|
||||
val generatedFileText = file.printForModuleSubType(ModuleSubType.android)
|
||||
val expectedFileText = """
|
||||
actual fun foo() {
|
||||
TODO()
|
||||
}
|
||||
""".trimIndent().trim()
|
||||
assertEquals(expectedFileText, generatedFileText)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `it should generate valid actual classes`() {
|
||||
val file = mppFiles {
|
||||
mppfile("file") {
|
||||
`class`("A") {
|
||||
actualFor(
|
||||
ModuleSubType.jvm,
|
||||
actualBody = "val a = 10\n fun x(): String"
|
||||
)
|
||||
actualFor(ModuleSubType.js, actualBody = "val b = 10\n fun y(): String")
|
||||
default("// nothing here")
|
||||
}
|
||||
}
|
||||
}.first()
|
||||
run {
|
||||
val generatedFileText = file.printForModuleSubType(ModuleSubType.jvm)
|
||||
val expectedFileText = """
|
||||
actual class A actual constructor() {
|
||||
val a = 10
|
||||
fun x(): String
|
||||
}
|
||||
""".trimIndent().trim()
|
||||
assertEquals(expectedFileText, generatedFileText)
|
||||
}
|
||||
|
||||
run {
|
||||
val generatedFileText = file.printForModuleSubType(ModuleSubType.js)
|
||||
val expectedFileText = """
|
||||
actual class A actual constructor() {
|
||||
val b = 10
|
||||
fun y(): String
|
||||
}
|
||||
""".trimIndent().trim()
|
||||
assertEquals(expectedFileText, generatedFileText)
|
||||
}
|
||||
run {
|
||||
val generatedFileText = file.printForModuleSubType(ModuleSubType.android)
|
||||
val expectedFileText = """
|
||||
actual class A actual constructor() {
|
||||
// nothing here
|
||||
}
|
||||
""".trimIndent().trim()
|
||||
assertEquals(expectedFileText, generatedFileText)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user