Skip to content

Commit

Permalink
Merge pull request #9 from LDRAlighieri/carbon/enum-type
Browse files Browse the repository at this point in the history
Carbon. Add enums support
  • Loading branch information
LDRAlighieri committed Jan 31, 2024
2 parents 6c6acd0 + 4f8e93f commit 0e85fc2
Show file tree
Hide file tree
Showing 18 changed files with 169 additions and 165 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[![Kotlin Version](https://img.shields.io/badge/Kotlin-v1.9.22-blue.svg?logo=kotlin)](https://kotlinlang.org)
[![Compose BOM Version](https://img.shields.io/badge/Compose-v2023.10.01-blue.svg?logo=jetpackcompose)](https://developer.android.com/jetpack/compose)
[![Compose BOM Version](https://img.shields.io/badge/Compose-v2024.01.00-blue.svg?logo=jetpackcompose)](https://developer.android.com/jetpack/compose)
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)

[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg)](https://android-arsenal.com/api?level=21)
Expand Down
5 changes: 3 additions & 2 deletions composites-carbon/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

# Carbon
# Carbon (work in progress 🚧🔧️👷⛏🚧)

Annotation processor ([Kotlin Symbol Processing, KSP][ksp]) for generating Route objects that help with navigation based on the [Navigation Component][navigation].
Allows you to significantly reduce routine and time spent on creating `Route` objects manually.
Expand All @@ -14,7 +14,8 @@ Allows you to significantly reduce routine and time spent on creating `Route` ob

- [X] KSP `Route` object generation
- [X] Default arguments (without reflection)
- [ ] Enums and Parcelable support (using parcelable is not best practice. It is recommended to use primitives)
- [X] Enums support
- [ ] Parcelable and Serializable support (using parcelable and serializable is not best practice. It is recommended to use primitives)
- [ ] Optional. Default arguments (with reflection)


Expand Down
52 changes: 0 additions & 52 deletions composites-carbon/core/api/core.api
Original file line number Diff line number Diff line change
@@ -1,60 +1,8 @@
public final class ru/ldralighieri/composites/carbon/core/ArgumentData {
public fun <init> (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;ZLru/ldralighieri/composites/carbon/core/ArgumentDefaultValue;)V
public synthetic fun <init> (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;ZLru/ldralighieri/composites/carbon/core/ArgumentDefaultValue;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lcom/squareup/kotlinpoet/TypeName;
public final fun component3 ()Z
public final fun component4 ()Lru/ldralighieri/composites/carbon/core/ArgumentDefaultValue;
public final fun copy (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;ZLru/ldralighieri/composites/carbon/core/ArgumentDefaultValue;)Lru/ldralighieri/composites/carbon/core/ArgumentData;
public static synthetic fun copy$default (Lru/ldralighieri/composites/carbon/core/ArgumentData;Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;ZLru/ldralighieri/composites/carbon/core/ArgumentDefaultValue;ILjava/lang/Object;)Lru/ldralighieri/composites/carbon/core/ArgumentData;
public fun equals (Ljava/lang/Object;)Z
public final fun getDefaultValue ()Lru/ldralighieri/composites/carbon/core/ArgumentDefaultValue;
public final fun getName ()Ljava/lang/String;
public final fun getTypeName ()Lcom/squareup/kotlinpoet/TypeName;
public fun hashCode ()I
public final fun isNullable ()Z
public fun toString ()Ljava/lang/String;
}

public final class ru/ldralighieri/composites/carbon/core/ArgumentDefaultValue {
public fun <init> (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lcom/squareup/kotlinpoet/TypeName;
public final fun copy (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)Lru/ldralighieri/composites/carbon/core/ArgumentDefaultValue;
public static synthetic fun copy$default (Lru/ldralighieri/composites/carbon/core/ArgumentDefaultValue;Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lru/ldralighieri/composites/carbon/core/ArgumentDefaultValue;
public fun equals (Ljava/lang/Object;)Z
public final fun getType ()Lcom/squareup/kotlinpoet/TypeName;
public final fun getValue ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public abstract interface annotation class ru/ldralighieri/composites/carbon/core/CarbonRoute : java/lang/annotation/Annotation {
public abstract fun deeplinkSchema ()Ljava/lang/String;
public abstract fun route ()Ljava/lang/String;
}

public final class ru/ldralighieri/composites/carbon/core/CarbonRouteData {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;Ljava/util/List;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Lcom/squareup/kotlinpoet/ClassName;
public final fun component5 ()Ljava/lang/String;
public final fun component6 ()Ljava/util/List;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;Ljava/util/List;)Lru/ldralighieri/composites/carbon/core/CarbonRouteData;
public static synthetic fun copy$default (Lru/ldralighieri/composites/carbon/core/CarbonRouteData;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lru/ldralighieri/composites/carbon/core/CarbonRouteData;
public fun equals (Ljava/lang/Object;)Z
public final fun getArguments ()Ljava/util/List;
public final fun getClassName ()Lcom/squareup/kotlinpoet/ClassName;
public final fun getDeeplinkSchema ()Ljava/lang/String;
public final fun getFileName ()Ljava/lang/String;
public final fun getPackageName ()Ljava/lang/String;
public final fun getRoute ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public abstract interface annotation class ru/ldralighieri/composites/carbon/core/DefaultValue : java/lang/annotation/Annotation {
public abstract fun value ()Ljava/lang/String;
}
Expand Down
4 changes: 4 additions & 0 deletions composites-carbon/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,9 @@ plugins {
}

dependencies {
// Ksp devtools
implementation(libs.google.ksp.api)

// KotlinPoet
implementation(libs.kotlinpoet.jvm)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,29 @@ package ru.ldralighieri.composites.carbon.processor

import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSNode
import com.google.devtools.ksp.symbol.KSType
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeName
import ru.ldralighieri.composites.carbon.core.ArgumentData
import ru.ldralighieri.composites.carbon.core.ArgumentDefaultValue
import ru.ldralighieri.composites.carbon.core.CarbonRouteData
import ru.ldralighieri.composites.carbon.processor.model.ArgumentData
import ru.ldralighieri.composites.carbon.processor.model.ArgumentDefaultValue
import ru.ldralighieri.composites.carbon.processor.model.CarbonRouteData
import ru.ldralighieri.composites.carbon.core.DefaultValue
import ru.ldralighieri.composites.carbon.processor.ext.getSimpleName
import ru.ldralighieri.composites.carbon.processor.ext.isAcceptableType
import ru.ldralighieri.composites.carbon.processor.model.ROUTE_ARGUMENT_NAME
import ru.ldralighieri.composites.carbon.processor.model.ROUTE_DEEPLINK_SCHEMA_NAME
import ru.ldralighieri.composites.carbon.processor.model.ROUTE_FILE_NAME_POSTFIX
import ru.ldralighieri.composites.carbon.processor.model.carbonAnnotationTypeName
import ru.ldralighieri.composites.carbon.processor.model.validTypes
import java.util.Locale

internal class CarbonRouteDataParser {

fun parse(symbol: KSClassDeclaration): Result {
fun parse(resolver: Resolver, symbol: KSClassDeclaration): Result {

val annotation: KSAnnotation =
symbol
Expand Down Expand Up @@ -81,7 +82,7 @@ internal class CarbonRouteDataParser {
val className: ClassName = symbol.toClassName()

val arguments: List<ArgumentData> =
try { symbol.getArguments() }
try { symbol.getArguments(resolver) }
catch (e: IllegalArgumentException) { return Result.Failure(e.message.orEmpty()) }

val data = CarbonRouteData(
Expand Down Expand Up @@ -110,18 +111,17 @@ internal class CarbonRouteDataParser {

@OptIn(KspExperimental::class)
@Throws(IllegalStateException::class)
private fun KSClassDeclaration.getArguments(): List<ArgumentData> =
private fun KSClassDeclaration.getArguments(resolver: Resolver): List<ArgumentData> =
this
.primaryConstructor
?.parameters
.orEmpty()
.map {
val name: String = it.name?.getShortName().orEmpty()

val type: KSType = it.type.resolve()
val typeName: TypeName = type.toTypeName()
if (typeName !in validTypes) error("$typeName is not a valid argument type")
if (!type.isAcceptableType(resolver))
error("${type.getSimpleName()} is not a valid argument type")

val name: String = it.name?.getShortName().orEmpty()
val isNullable: Boolean = type.isMarkedNullable

val defaultValue: ArgumentDefaultValue? = it
Expand All @@ -130,11 +130,11 @@ internal class CarbonRouteDataParser {
?.let { defaultValue ->
ArgumentDefaultValue(
value = defaultValue.value,
type = typeName,
type = type,
)
}

ArgumentData(name, typeName, isNullable, defaultValue)
ArgumentData(name, type, isNullable, defaultValue)
}

sealed interface Result {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package ru.ldralighieri.composites.carbon.processor

import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Resolver
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
Expand All @@ -27,15 +28,17 @@ import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.buildCodeBlock
import com.squareup.kotlinpoet.joinToCode
import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.ksp.writeTo
import com.squareup.kotlinpoet.withIndent
import ru.ldralighieri.composites.carbon.core.CarbonRouteData
import ru.ldralighieri.composites.carbon.processor.model.CarbonRouteData
import ru.ldralighieri.composites.carbon.core.Destination
import ru.ldralighieri.composites.carbon.processor.ext.castValue
import ru.ldralighieri.composites.carbon.processor.ext.getCreateArguments
import ru.ldralighieri.composites.carbon.processor.ext.getOptionalCreateArguments
import ru.ldralighieri.composites.carbon.processor.ext.getOptionalPathArguments
import ru.ldralighieri.composites.carbon.processor.ext.getPathArguments
import ru.ldralighieri.composites.carbon.processor.ext.isEnumType
import ru.ldralighieri.composites.carbon.processor.ext.toBackStackGetter
import ru.ldralighieri.composites.carbon.processor.ext.toNavTypeClassName
import ru.ldralighieri.composites.carbon.processor.ext.toSavedStateHandleGetter
Expand All @@ -51,10 +54,11 @@ import ru.ldralighieri.composites.carbon.processor.model.navArgumentMemberName
import ru.ldralighieri.composites.carbon.processor.model.navBackStackEntryClassName
import ru.ldralighieri.composites.carbon.processor.model.navDeepLinkClassName
import ru.ldralighieri.composites.carbon.processor.model.navDeepLinkMemberName
import ru.ldralighieri.composites.carbon.processor.model.navTypeEnumClassName
import ru.ldralighieri.composites.carbon.processor.model.savedStateHandleClassName

internal class CarbonRouteGenerator(private val codeGenerator: CodeGenerator) {
fun generate(data: CarbonRouteData) {
fun generate(resolver: Resolver, data: CarbonRouteData) {
FileSpec
.builder(
packageName = data.packageName,
Expand All @@ -63,13 +67,13 @@ internal class CarbonRouteGenerator(private val codeGenerator: CodeGenerator) {
.addType(
TypeSpec.objectBuilder(data.fileName)
.addProperty(data.routeProperty())
.addProperty(data.argumentsProperty())
.addProperty(data.argumentsProperty(resolver))
.addProperty(data.deepLinksProperty())
.addFunction(data.createFunction())
.addFunction(data.createFunction(resolver))
.apply {
if (data.arguments.isNotEmpty()) {
addFunction(data.parseArgumentsByStackEntryFunction())
addFunction(data.parseArgumentsBySavedStateHandleFunction())
addFunction(data.parseArgumentsByStackEntryFunction(resolver))
addFunction(data.parseArgumentsBySavedStateHandleFunction(resolver))
}
}
.build()
Expand Down Expand Up @@ -98,7 +102,7 @@ private fun CarbonRouteData.routeProperty(): PropertySpec =
)
.build()

private fun CarbonRouteData.argumentsProperty(): PropertySpec =
private fun CarbonRouteData.argumentsProperty(resolver: Resolver): PropertySpec =
PropertySpec
.builder(
name = ARGUMENTS_PROPERTY_NAME,
Expand All @@ -116,17 +120,26 @@ private fun CarbonRouteData.argumentsProperty(): PropertySpec =
addStatement("%M(%S) {", navArgumentMemberName, argument.name)

withIndent {
addStatement(
format = "type = %T",
argument.typeName.toNavTypeClassName()
)
when {
resolver.isEnumType(argument.type) ->
addStatement(
format = "type = %T(%T::class.java)",
navTypeEnumClassName,
argument.type.makeNotNullable().toTypeName()
)
else ->
addStatement(
format = "type = %T",
argument.type.toNavTypeClassName()
)
}

addStatement("nullable = %L", argument.isNullable)

argument.defaultValue?.let {
addStatement(
format = "defaultValue = %L",
it.castValue()
it.castValue(resolver)
)
}
}
Expand Down Expand Up @@ -175,19 +188,19 @@ private fun CarbonRouteData.deepLinksProperty(): PropertySpec =
)
.build()

private fun CarbonRouteData.createFunction() = FunSpec
private fun CarbonRouteData.createFunction(resolver: Resolver) = FunSpec
.builder(CREATE_FUNCTION_NAME)
.addParameters(
arguments
.map { data ->
ParameterSpec.builder(data.name, data.typeName)
ParameterSpec.builder(data.name, data.type.toTypeName())
.apply {
when {
data.isNullable -> defaultValue(format = "%L", null)
data.defaultValue != null ->
defaultValue(
format = "%L",
data.defaultValue?.castValue()
data.defaultValue?.castValue(resolver)
)
}
}
Expand All @@ -204,7 +217,7 @@ private fun CarbonRouteData.createFunction() = FunSpec
)
.build()

private fun CarbonRouteData.parseArgumentsByStackEntryFunction() =
private fun CarbonRouteData.parseArgumentsByStackEntryFunction(resolver: Resolver) =
FunSpec
.builder(PARSE_ARGUMENTS_FUNCTION_NAME)
.addParameter(
Expand All @@ -224,7 +237,7 @@ private fun CarbonRouteData.parseArgumentsByStackEntryFunction() =
addStatement(
format = "%L = %L,",
argument.name,
argument.toBackStackGetter()
argument.toBackStackGetter(resolver)
)
}
}
Expand All @@ -236,7 +249,7 @@ private fun CarbonRouteData.parseArgumentsByStackEntryFunction() =
)
.build()

private fun CarbonRouteData.parseArgumentsBySavedStateHandleFunction() =
private fun CarbonRouteData.parseArgumentsBySavedStateHandleFunction(resolver: Resolver) =
FunSpec
.builder(PARSE_ARGUMENTS_FUNCTION_NAME)
.addParameter(
Expand All @@ -256,7 +269,7 @@ private fun CarbonRouteData.parseArgumentsBySavedStateHandleFunction() =
addStatement(
format = "%L = %L,",
argument.name,
argument.toSavedStateHandleGetter()
argument.toSavedStateHandleGetter(resolver)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ internal class CarbonRouteProcessor(
resolver.getSymbolsWithAnnotation(CarbonRoute::class.java.canonicalName)
.toList()
.filterIsInstance<KSClassDeclaration>()
.forEach(::parse)
.forEach { symbol -> parse(resolver, symbol) }

return emptyList()
}

private fun parse(symbol: KSClassDeclaration) {
when (val result = parser.parse(symbol)) {
is CarbonRouteDataParser.Result.Success -> generator.generate(result.data)
private fun parse(resolver: Resolver, symbol: KSClassDeclaration) {
when (val result = parser.parse(resolver, symbol)) {
is CarbonRouteDataParser.Result.Success -> generator.generate(resolver, result.data)
is CarbonRouteDataParser.Result.Failure -> logger.error(result.message, result.symbol)
}
}
Expand Down
Loading

0 comments on commit 0e85fc2

Please sign in to comment.