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

Restructuring a sugar module #804

Merged
merged 12 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions casa-processor/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@
* Please see full license: https://github.com/duckie-team/quack-quack-android/blob/main/LICENSE
*/

@file:Suppress("INLINE_FROM_HIGHER_PLATFORM")

plugins {
quackquack("jvm-kotlin")
quackquack("test-kotest")
quackquack("quack-publishing")
alias(libs.plugins.kotlin.ksp)
}

Expand All @@ -27,6 +24,7 @@ dependencies {
libs.kotlin.collections.immutable,
libs.google.autoservice.annotation,
projects.utilBackendKsp,
projects.utilBackendKotlinpoet,
)
testImplementations(
libs.test.kotlin.compilation.ksp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.google.devtools.ksp.symbol.KSAnnotated

private const val CasaPathArg = "CasaPath"

Expand All @@ -35,13 +34,12 @@ private class CasaSymbolProcessor(
logger: KSPLogger,
options: Map<String, Any>,
) : SymbolProcessor {
private val processor = CasaProcessor(
codeGenerator = codeGenerator,
logger = logger,
casaPath = options[CasaPathArg]?.toString(),
)
private val processor =
CasaProcessor(
codeGenerator = codeGenerator,
logger = logger,
casaPath = options[CasaPathArg]?.toString(),
)

override fun process(resolver: Resolver): List<KSAnnotated> {
return processor.resolve(resolver)
}
override fun process(resolver: Resolver) = processor.resolve(resolver)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,91 +18,94 @@ import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.buildCodeBlock
import com.squareup.kotlinpoet.withIndent
import kotlinx.collections.immutable.ImmutableList
import team.duckie.quackquack.util.backend.FormatterOffComment
import team.duckie.quackquack.util.backend.SuppressAnnotation
import team.duckie.quackquack.util.backend.getGeneratedFileComment
import team.duckie.quackquack.util.backend.kotlinpoet.getGeneratedFileComment
import team.duckie.quackquack.util.backend.ksp.generateBuildOrLocalFile
import team.duckie.quackquack.util.backend.ksp.requireContainingFile

private val GeneratedComment = getGeneratedFileComment("casa-processor")
private val RequiredImports = listOf(
"kotlinx.collections.immutable.persistentListOf",
"kotlinx.collections.immutable.toImmutableList",
"androidx.compose.runtime.Composable",
)
private val RequiredImports =
listOf(
"kotlinx.collections.immutable.persistentListOf",
"kotlinx.collections.immutable.toImmutableList",
"androidx.compose.runtime.Composable",
)

internal fun generateCasaModels(
codeGenerator: CodeGenerator,
logger: KSPLogger,
casas: Sequence<KSFunctionDeclaration>,
casaPath: String?,
) {
val imports = mutableListOf<String>().also { imports ->
imports += RequiredImports
}
val casasWithDomainGroup = casas.groupBy { declaration ->
declaration.requireContainingFile.fileName.removeSuffix(".kt")
}
val groupedCasasWithNameGroup = buildMap(capacity = casasWithDomainGroup.size) {
casasWithDomainGroup.forEach { (domain, casas) ->
val casasWithNameGroup = casas.groupBy { casa ->
casa.parseSugarRefer().substringAfterLast(".")
val imports =
mutableListOf<String>().also { imports ->
imports += RequiredImports
}
val casasWithDomainGroup =
casas.groupBy { declaration ->
declaration.requireContainingFile.fileName.removeSuffix(".kt")
}
val groupedCasasWithNameGroup =
buildMap(capacity = casasWithDomainGroup.size) {
casasWithDomainGroup.forEach { (domain, casas) ->
val casasWithNameGroup = casas.groupBy { casa ->
casa.parseSugarRefer().substringAfterLast(".")
}
set(domain, casasWithNameGroup)
}
set(domain, casasWithNameGroup)
}
}
val casaModelPropertySpecs = buildList {
groupedCasasWithNameGroup.forEach { (domain, casasWithNameGroup) ->
casasWithNameGroup.forEach { (name, casas) ->
val (_imports, casaModelPropertySpec) = createCasaModelPropertySpecWithImports(
domain = domain,
name = name,
casas = casas,
)
imports += _imports
add(casaModelPropertySpec)
val casaModelPropertySpecs =
buildList {
groupedCasasWithNameGroup.forEach { (domain, casasWithNameGroup) ->
casasWithNameGroup.forEach { (name, casas) ->
val (_imports, casaModelPropertySpec) = createCasaModelPropertySpecWithImports(
domain = domain,
name = name,
casas = casas,
)
imports += _imports
add(casaModelPropertySpec)
}
}
}
}
val casaModelPropertySpecsAccessor = PropertySpec
.builder(
name = "casaModels",
type = ImmutableList::class.asClassName().parameterizedBy(CasaModelCn),
)
.initializer(
codeBlock = buildCodeBlock {
addStatement("persistentListOf(")
withIndent {
casaModelPropertySpecs.forEach { casaModelPropertySpec ->
addStatement("${casaModelPropertySpec.name},")
val casaModelPropertySpecsAccessor =
PropertySpec
.builder(
name = "casaModels",
type = ImmutableList::class.asClassName().parameterizedBy(CasaModelCn),
)
.initializer(
codeBlock = buildCodeBlock {
addStatement("persistentListOf(")
withIndent {
casaModelPropertySpecs.forEach { casaModelPropertySpec ->
addStatement("${casaModelPropertySpec.name},")
}
}
}
addStatement(")")
},
)
.addModifiers(KModifier.PUBLIC)
.build()
addStatement(")")
},
)
.addModifiers(KModifier.PUBLIC)
.build()

// TODO(3): OptIn 어노테이션 자동 추가
val casaModelFileSpec = FileSpec
.builder(
packageName = "",
fileName = "CasaModels",
)
.addFileComment(GeneratedComment)
.addFileComment(FormatterOffComment)
.addAnnotation(SuppressAnnotation)
.apply {
// memberImports += if (packageName.isNotEmpty()) {
// Import("$packageName.$import")
// } else {
// Import(import)
// }
addImport(packageName = "", imports)
casaModelPropertySpecs.forEach(::addProperty)
}
.addProperty(casaModelPropertySpecsAccessor)
.build()
val casaModelFileSpec =
FileSpec
.builder(
packageName = "",
fileName = "CasaModels",
)
.addFileComment(GeneratedComment)
.apply {
// memberImports += if (packageName.isNotEmpty()) {
// Import("$packageName.$import")
// } else {
// Import(import)
// }
addImport(packageName = "", imports)
casaModelPropertySpecs.forEach(::addProperty)
}
.addProperty(casaModelPropertySpecsAccessor)
.build()

generateBuildOrLocalFile(
codeGenerator = codeGenerator,
Expand All @@ -115,29 +118,32 @@ internal fun generateCasaModels(

private fun KSFunctionDeclaration.toCasaComponentLiteralWithImport(): Pair<String, String> {
val import = "team.duckie.quackquack.ui.sugar.${simpleName.asString()}"
val parameterValueMap = parameters.mapNotNull { parameter ->
if (parameter.hasDefault) {
return@mapNotNull null
}
val name = parameter.name!!.asString()
val value = parameter.annotations.singleOrNullStrict { annotation ->
annotation.shortName.asString() == CasaValueSn
}
if (value != null) {
name to value.arguments.single().value as String
} else if (parameter.type.resolve().isMarkedNullable) {
name to "null"
} else {
error("Argument $name is non-null and no `CasaValue` was provided.")
val parameterValueMap =
parameters.mapNotNull { parameter ->
if (parameter.hasDefault) {
return@mapNotNull null
}
val name = parameter.name!!.asString()
val value =
parameter.annotations.singleOrNullStrict { annotation ->
annotation.shortName.asString() == CasaValueSn
}
if (value != null) {
name to value.arguments.single().value as String
} else if (parameter.type.resolve().isMarkedNullable) {
name to "null"
} else {
error("Argument $name is non-null and no `CasaValue` was provided.")
}
}
}
val componentLiteral = buildString {
appendLine("${simpleName.asString()}(")
parameterValueMap.forEach { (name, value) ->
appendLineWithIndent("$name = $value,")
val componentLiteral =
buildString {
appendLine("${simpleName.asString()}(")
parameterValueMap.forEach { (name, value) ->
appendLineWithIndent("$name = $value,")
}
append(")")
}
append(")")
}

return import to componentLiteral
}
Expand All @@ -154,50 +160,63 @@ private fun createCasaModelPropertySpecWithImports(
name: String,
casas: List<KSFunctionDeclaration>,
): Pair<List<String>, PropertySpec> {
val imports = casas.first().parameters.map { parameter ->
parameter.type.resolve().declaration.qualifiedName!!.asString()
}.toMutableList()
val kdocString = casas.first().docString.orEmpty()
.split("This document was automatically generated")
.first()
.trimIndent()
val components = buildString {
appendLine("persistentListOf<Pair<String, @Composable () -> Unit>>(")
casas.forEach { casa ->
appendCasaComponentPairWithImport(casa = casa).let { (import, _) ->
imports += import
val imports =
casas
.first()
.parameters
.map { parameter ->
parameter.type.resolve().declaration.qualifiedName!!.asString()
}
appendLine(",")
.toMutableList()
val kdocString =
casas
.first()
.docString
.orEmpty()
.split("This document was automatically generated")
.first()
.trimIndent()
val components =
buildString {
appendLine("persistentListOf<Pair<String, @Composable () -> Unit>>(")
casas.forEach { casa ->
appendCasaComponentPairWithImport(casa = casa).let { (import, _) ->
imports += import
}
appendLine(",")
}
append(").toImmutableList()")
}
append(").toImmutableList()")
}

val casaModelPropertySpec = PropertySpec
.builder(
name = "${domain}${name}CasaModel",
type = CasaModelCn,
)
.addModifiers(KModifier.PRIVATE)
.initializer(
codeBlock = buildCodeBlock {
addStatement("CasaModel(")
withIndent {
addStatement("name = %S,", name)
addStatement("domain = %S,", domain)
addStatement("kdocDefaultSection = %S,", kdocString)
addStatement("components = %L,", components)
}
addStatement(")")
},
)
.build()
val casaModelPropertySpec =
PropertySpec
.builder(
name = "${domain}${name}CasaModel",
type = CasaModelCn,
)
.addModifiers(KModifier.PRIVATE)
.initializer(
codeBlock = buildCodeBlock {
addStatement("CasaModel(")
withIndent {
addStatement("name = %S,", name)
addStatement("domain = %S,", domain)
addStatement("kdocDefaultSection = %S,", kdocString)
addStatement("components = %L,", components)
}
addStatement(")")
},
)
.build()

return imports to casaModelPropertySpec
}

private fun KSFunctionDeclaration.parseSugarRefer(): String {
val sugarRefer = annotations.singleOrNullStrict { annotation ->
annotation.shortName.asString() == SugarReferSn
} ?: error("casa-processor only supports sugar components.")
val sugarRefer =
annotations.singleOrNullStrict { annotation ->
annotation.shortName.asString() == SugarReferSn
} ?: error("casa-processor only supports sugar components.")

return sugarRefer.arguments.first().value as String
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,5 @@ internal fun <T> Sequence<T>.singleOrNullStrict(predicate: (T) -> Boolean): T? {
return single
}

internal fun StringBuilder.appendLineWithIndent(
value: String?,
indentSize: Int = 2,
): StringBuilder {
return appendLine("${" ".repeat(indentSize)}$value")
}
internal fun StringBuilder.appendLineWithIndent(value: String?, indentSize: Int = 2) =
appendLine("${" ".repeat(indentSize)}$value")
Loading