Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking PR for jitpack branch #1

Open
wants to merge 17 commits into
base: hm/add-propertied-enum-feature
Choose a base branch
from
Open
33 changes: 33 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[*.{kt,kts}]
ktlint_code_style=android
ktlint_experimental_type-argument-list-spacing=enabled
ktlint_experimental_function-signature=enabled
ktlint_experimental_block-comment-initial-star-alignment=enabled
ktlint_experimental_unnecessary-parentheses-before-trailing-lambda=enabled
ktlint_experimental_class-naming=enabled
ktlint_experimental_package-naming=enabled
ktlint_experimental_fun-keyword-spacing=enabled
ktlint_experimental_function-return-type-spacing=enabled
ktlint_experimental_function-start-of-body-spacing=enabled
ktlint_experimental_function-type-reference-spacing=enabled
ktlint_experimental_modifier-list-spacing=enabled
ktlint_experimental_nullable-type-spacing=enabled
ktlint_experimental_parameter-list-spacing=enabled
ktlint_experimental_spacing-between-function-name-and-opening-parenthesis=enabled
ktlint_experimental_type-argument-list-spacing=enabled
ktlint_experimental_type-parameter-list-spacing=enabled
ktlint_experimental_comment-wrapping=enabled
ktlint_experimental_context-receiver-wrapping=enabled
ktlint_experimental_kdoc-wrapping=enabled


ktlint_ignore_back_ticked_identifier=true
ij_kotlin_allow_trailing_comma=true
ij_kotlin_allow_trailing_comma_on_call_site=true
indent_size = 4
indent_style = space
insert_final_newline = true
# ktlint_function_signature_body_expression_wrapping = default
# ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = -1
# ktlint_ignore_back_ticked_identifier = false
# max_line_length = -1
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,41 @@ dependencies {

# Usage

## Xcode configuration

The Swift code generated from this plugin is not automatically included in the shared framework you might have.

You have 2 options to use it in your iOS project:
- Xcode direct file integration
- CocoaPods integration

### Xcode direct file integration

You can directly import the generated file in your Xcode project like it's a file you have written on your own.

To do so:
- open the Xcode project
- right click on "iosApp"
- choose "Add files to iOSApp"
- add the file from the generated folder (you might need to read the FAQ to know where the generated folder is)
- you are now good to go!

### CocoaPods integration

After you have added the moko-kswift plugin to your shared module and synced your project, a new Gradle task should appear with name `kSwiftXXXXXPodspec` where `XXXXX` is the name of your shared module (so your task might be named `kSwiftsharedPodspec`).

- Run the task doing `./gradlew kSwiftsharedPodspec` from the root of your project.
This will generate a new podspec file, `XXXXXSwift.podspec`, where `XXXXX` is still the name of your shared module (so e.g. `sharedSwift.podspec`)

- Now edit the `Podfile` inside the iOS project adding this line
`pod 'sharedSwift', :path => '../shared'`
just after the one already there for the already available shared module
`pod 'shared', :path => '../shared'`

- Now run `pod install` from the `iosApp` folder so the new framework is linked to your project.

- Whenever you need a Swift file generated from moko-kswift just import the generated module (e.g. `import sharedSwift`) and you are good to go!

## Sealed classes/interfaces to Swift enum

Enable feature in project `build.gradle`:
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ buildscript {
mavenCentral()
google()
gradlePluginPortal()
maven("https://jitpack.io")
}

dependencies {
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mokoResourcesVersion = "0.16.2"
kotlinxMetadataKLibVersion = "0.0.1"

kotlinPoetVersion = "1.6.0"
swiftPoetVersion = "1.3.1"
swiftPoetVersion = "1.5.1.4"

[libraries]
appCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidAppCompatVersion" }
Expand All @@ -30,7 +30,7 @@ mokoMvvmState = { module = "dev.icerock.moko:mvvm-state" , version.ref = "mokoMv
mokoResources = { module = "dev.icerock.moko:resources" , version.ref = "mokoResourcesVersion" }

kotlinPoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinPoetVersion" }
swiftPoet = { module = "io.outfoxx:swiftpoet", version.ref = "swiftPoetVersion" }
swiftPoet = { module = "com.github.hbmartin:swiftpoet", version.ref = "swiftPoetVersion" }

kotlinCompilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlinVersion" }
kotlinxMetadataKLib = { module = "org.jetbrains.kotlinx:kotlinx-metadata-klib", version.ref = "kotlinxMetadataKLibVersion" }
Expand Down
4 changes: 4 additions & 0 deletions jitpack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
jdk:
- openjdk11
install:
- ./gradlew -p kswift-gradle-plugin -Pgroup=com.github.hbmartin -Pversion=$VERSION -xtest assemble publishPluginJar publishToMavenLocal
1 change: 1 addition & 0 deletions kswift-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {
api(libs.kotlinxMetadataKLib)

testImplementation(libs.kotlinTestJUnit)
testImplementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5")
}

gradlePlugin {
Expand Down
3 changes: 2 additions & 1 deletion kswift-gradle-plugin/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ pluginManagement {
repositories {
mavenCentral()
google()

gradlePluginPortal()
maven("https://jitpack.io")
}

resolutionStrategy {
Expand All @@ -28,6 +28,7 @@ dependencyResolutionManagement {
repositories {
mavenCentral()
google()
maven("https://jitpack.io")
}

versionCatalogs {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlinx.metadata.Flag
import kotlinx.metadata.KmClass

fun KmClass.buildTypeVariableNames(
kotlinFrameworkName: String
kotlinFrameworkName: String,
) = this.typeParameters.map { typeParam ->
val bounds: List<TypeVariableName.Bound> = typeParam.upperBounds
.map { it.toTypeName(kotlinFrameworkName, isUsedInGenerics = true) }
Expand All @@ -25,7 +25,7 @@ fun KmClass.buildTypeVariableNames(

fun KmClass.getDeclaredTypeNameWithGenerics(
kotlinFrameworkName: String,
classes: List<KmClass>
classes: List<KmClass>,
): TypeName {
val typeVariables: List<TypeVariableName> = buildTypeVariableNames(kotlinFrameworkName)
val haveGenerics: Boolean = typeVariables.isNotEmpty()
Expand All @@ -34,21 +34,24 @@ fun KmClass.getDeclaredTypeNameWithGenerics(
@Suppress("SpreadOperator")
return getDeclaredTypeName(kotlinFrameworkName, classes)
.let { type ->
if (haveGenerics.not() || isInterface) type
else type.parameterizedBy(*typeVariables.toTypedArray())
if (haveGenerics.not() || isInterface) {
type
} else {
type.parameterizedBy(typeVariables)
}
}
}

fun KmClass.getDeclaredTypeName(
kotlinFrameworkName: String,
classes: List<KmClass>
classes: List<KmClass>,
): DeclaredTypeName {
return DeclaredTypeName(
moduleName = kotlinFrameworkName,
simpleName = getSimpleName(
className = name,
classes = classes
)
classes = classes,
),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import io.outfoxx.swiftpoet.UINT64
import io.outfoxx.swiftpoet.VOID
import io.outfoxx.swiftpoet.parameterizedBy
import kotlinx.metadata.ClassName
import kotlinx.metadata.Flag
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmType

Expand All @@ -24,15 +25,22 @@ fun KmType.toTypeName(
moduleName: String,
isUsedInGenerics: Boolean = false,
typeVariables: Map<Int, TypeVariableName> = emptyMap(),
removeTypeVariables: Boolean = false
removeTypeVariables: Boolean = false,
): TypeName {
return when (val classifier = classifier) {
is KmClassifier.TypeParameter -> {
val typeVariable: TypeVariableName? = typeVariables[classifier.id]
if (typeVariable != null) {
return if (!removeTypeVariables) typeVariable
else typeVariable.bounds.firstOrNull()?.type ?: ANY_OBJECT
} else throw IllegalArgumentException("can't read type parameter $this without type variables list")
return if (!removeTypeVariables) {
typeVariable
} else {
typeVariable.bounds.firstOrNull()?.type ?: ANY_OBJECT
}
} else {
throw IllegalArgumentException(
"can't read type parameter $this without type variables list",
)
}
}
is KmClassifier.TypeAlias -> {
classifier.name.kotlinTypeNameToSwift(moduleName, isUsedInGenerics)
Expand All @@ -45,7 +53,7 @@ fun KmType.toTypeName(
moduleName,
classifier.name,
typeVariables,
removeTypeVariables
removeTypeVariables,
)
}
}
Expand Down Expand Up @@ -75,16 +83,18 @@ fun String.kotlinTypeNameToSwift(moduleName: String, isUsedInGenerics: Boolean):
val className: String = moduleAndClass[1]

DeclaredTypeName.typeName(
listOf(module, className).joinToString(".")
listOf(module, className).joinToString("."),
).objcNameToSwift()
} else if (this.startsWith("kotlin/Function")) {
null
} else if (this.startsWith("kotlin/") && this.count { it == '/' } == 1) {
DeclaredTypeName(
moduleName = moduleName,
simpleName = "Kotlin" + this.split("/").last()
simpleName = "Kotlin" + this.split("/").last(),
)
} else null
} else {
null
}
}
}
}
Expand All @@ -93,11 +103,11 @@ fun KmType.kotlinTypeToTypeName(
moduleName: String,
classifierName: ClassName,
typeVariables: Map<Int, TypeVariableName>,
removeTypeVariables: Boolean
removeTypeVariables: Boolean,
): TypeName {
val typeName = DeclaredTypeName(
moduleName = moduleName,
simpleName = classifierName.split("/").last()
simpleName = classifierName.split("/").last(),
)
if (this.arguments.isEmpty()) return typeName

Expand All @@ -108,17 +118,17 @@ fun KmType.kotlinTypeToTypeName(
moduleName = moduleName,
isUsedInGenerics = false,
typeVariables = typeVariables,
removeTypeVariables = removeTypeVariables
removeTypeVariables = removeTypeVariables,
)!!
val outputType: TypeName = arguments[1].type?.toTypeName(
moduleName = moduleName,
isUsedInGenerics = false,
typeVariables = typeVariables,
removeTypeVariables = removeTypeVariables
removeTypeVariables = removeTypeVariables,
)!!
FunctionTypeName.get(
parameters = listOf(ParameterSpec.unnamed(inputType)),
returnType = outputType
returnType = outputType,
)
}
else -> {
Expand All @@ -127,11 +137,11 @@ fun KmType.kotlinTypeToTypeName(
moduleName = moduleName,
isUsedInGenerics = true,
typeVariables = typeVariables,
removeTypeVariables = removeTypeVariables
removeTypeVariables = removeTypeVariables,
)
}
@Suppress("SpreadOperator")
typeName.parameterizedBy(*arguments.toTypedArray())
typeName.parameterizedBy(arguments)
}
}
}
Expand All @@ -142,3 +152,9 @@ fun DeclaredTypeName.objcNameToSwift(): DeclaredTypeName {
else -> this
}
}

val KmType.isNullable: Boolean
get() = Flag.Type.IS_NULLABLE(flags)

val KmType.hasGenerics: Boolean
get() = this.arguments.isNotEmpty()
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package dev.icerock.moko.kswift.plugin.feature

import dev.icerock.moko.kswift.plugin.buildTypeVariableNames
import dev.icerock.moko.kswift.plugin.context.ClassContext
import dev.icerock.moko.kswift.plugin.context.kLibClasses
import dev.icerock.moko.kswift.plugin.feature.associatedenum.AssociatedEnumCase
import dev.icerock.moko.kswift.plugin.feature.associatedenum.buildEnumCases
import dev.icerock.moko.kswift.plugin.feature.associatedenum.buildTypeSpec
import dev.icerock.moko.kswift.plugin.getSimpleName
import io.outfoxx.swiftpoet.FileSpec
import io.outfoxx.swiftpoet.TypeSpec
import kotlinx.metadata.Flag
import kotlinx.metadata.KmClass
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import kotlin.reflect.KClass

class SealedToSwiftAssociatedEnumFeature(
override val featureContext: KClass<ClassContext>,
override val filter: Filter<ClassContext>,
) : ProcessorFeature<ClassContext>() {

@Suppress("ReturnCount")
override fun doProcess(featureContext: ClassContext, processorContext: ProcessorContext) {
val kotlinFrameworkName: String = processorContext.framework.baseName

doProcess(
featureContext = featureContext,
fileSpecBuilder = processorContext.fileSpecBuilder,
kotlinFrameworkName = kotlinFrameworkName,
)
}

fun doProcess(
featureContext: ClassContext,
fileSpecBuilder: FileSpec.Builder,
kotlinFrameworkName: String,
) {
val kmClass: KmClass = featureContext.clazz
val originalClassName: String = getSimpleName(kmClass.name, featureContext.kLibClasses)

if (!Flag.IS_PUBLIC(kmClass.flags) || featureContext.clazz.sealedSubclasses.isEmpty()) {
return
}

val sealedCases: List<AssociatedEnumCase> = buildEnumCases(
kotlinFrameworkName = kotlinFrameworkName,
featureContext = featureContext,
)
if (sealedCases.isEmpty()) {
logger.warn("No public subclasses found for sealed class $originalClassName")
return
} else {
logger.lifecycle(
"Generating enum for sealed class $originalClassName (${sealedCases.size} public subclasses)",
)
}

val enumType: TypeSpec = buildTypeSpec(
featureContext = featureContext,
typeVariables = kmClass.buildTypeVariableNames(kotlinFrameworkName),
sealedCases = sealedCases,
kotlinFrameworkName = kotlinFrameworkName,
originalClassName = originalClassName,
)

fileSpecBuilder.addType(enumType)
}

class Config : BaseConfig<ClassContext> {
override var filter: Filter<ClassContext> = Filter.Exclude(emptySet())
}

companion object : Factory<ClassContext, SealedToSwiftAssociatedEnumFeature, Config> {
override fun create(block: Config.() -> Unit): SealedToSwiftAssociatedEnumFeature {
val config = Config().apply(block)
return SealedToSwiftAssociatedEnumFeature(featureContext, config.filter)
}

override val featureContext: KClass<ClassContext> = ClassContext::class

@JvmStatic
override val factory = Companion

val logger: Logger = Logging.getLogger("SealedToSwiftAssociatedEnumFeature")
}
}
Loading