![JAR search and dependency download from the Maven repository](/logo.png)
com.airbnb.android.showkase.processor.writer.WriterUtils.kt Maven / Gradle / Ivy
package com.airbnb.android.showkase.processor.writer
import androidx.room.compiler.processing.XFiler
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.addOriginatingElement
import androidx.room.compiler.processing.writeTo
import com.airbnb.android.showkase.processor.exceptions.ShowkaseProcessorException
import com.airbnb.android.showkase.processor.models.ShowkaseMetadata
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
val SPACE_REGEX = "\\s".toRegex()
internal fun getFileBuilder(
rootModulePackageName: String,
showkaseComponentsListClassName: String
) = FileSpec.builder(
rootModulePackageName,
showkaseComponentsListClassName
)
.addFileComment("This is an auto-generated file. Please do not edit/modify this file.")
internal fun getPropertyList(className: ClassName, propertyName: String): PropertySpec.Builder {
val parameterizedList = className.getCodegenMetadataParameterizedList()
return PropertySpec.builder(propertyName, parameterizedList)
}
internal fun getShowkaseProviderInterfaceFunction(
methodName: String,
codeBlock: CodeBlock,
returnType: TypeName,
) = FunSpec.builder(methodName)
.addModifiers(KModifier.OVERRIDE)
.addCode(codeBlock)
.returns(returnType)
.build()
@Suppress("LongParameterList")
internal fun writeFile(
processingEnv: XProcessingEnv,
fileBuilder: FileSpec.Builder,
superInterfaceClassName: ClassName,
showkaseComponentsListClassName: String,
allShowkaseBrowserProperties: ShowkaseBrowserProperties,
componentInterfaceFunction: FunSpec,
colorInterfaceFunction: FunSpec,
typographyInterfaceFunction: FunSpec,
showkaseRootCodegenAnnotation: AnnotationSpec
) {
fileBuilder
.addType(
with(TypeSpec.classBuilder(showkaseComponentsListClassName)) {
addAnnotation(showkaseRootCodegenAnnotation)
addSuperinterface(superInterfaceClassName)
addFunction(componentInterfaceFunction)
addFunction(colorInterfaceFunction)
addFunction(typographyInterfaceFunction)
allShowkaseBrowserProperties.zip().forEach { addOriginatingElement(it.element) }
build()
}
)
fileBuilder.build().writeTo(processingEnv.filer, mode = XFiler.Mode.Aggregating)
}
internal fun ClassName.listInitializerCodeBlock(): CodeBlock.Builder {
return CodeBlock.Builder()
.addLineBreak()
.add(
"return listOf<%T>(",
this
)
.doubleIndent()
}
internal fun ClassName.mutableListInitializerCodeBlock(): CodeBlock.Builder {
return CodeBlock.Builder()
.addLineBreak()
.add(
"return mutableListOf<%T>(",
this
)
.doubleIndent()
}
internal fun ClassName.getCodegenMetadataParameterizedList() = List::class
.asClassName()
.parameterizedBy(this)
internal fun CodeBlock.Builder.addShowkaseBrowserComponent(
showkaseMetadata: ShowkaseMetadata.Component,
isPreviewParameter: Boolean = false
) {
val componentName = if (showkaseMetadata.componentIndex != null) {
"_${showkaseMetadata.showkaseName}_${showkaseMetadata.componentIndex}"
} else {
"_${showkaseMetadata.showkaseName}"
}
var componentKey = (showkaseMetadata.packageName +
"_${showkaseMetadata.enclosingClassName}" +
"_${showkaseMetadata.showkaseGroup}" +
componentName +
"_${showkaseMetadata.showkaseStyleName}").replace(
SPACE_REGEX,
""
)
if (isPreviewParameter) {
componentKey += "_\$index"
}
add(
"%T(\n",
ShowkaseBrowserWriter.SHOWKASE_BROWSER_COMPONENT_CLASS_NAME
)
doubleIndent()
add(
"group = %S,\ncomponentName = %S,\ncomponentKDoc = %S,\ncomponentKey = %P,",
showkaseMetadata.showkaseGroup,
showkaseMetadata.showkaseName,
showkaseMetadata.showkaseKDoc,
componentKey,
)
add("\nisDefaultStyle = ${showkaseMetadata.isDefaultStyle},")
showkaseMetadata.apply {
showkaseWidthDp?.let { add("\nwidthDp = %L,", it) }
showkaseHeightDp?.let { add("\nheightDp = %L,", it) }
showkaseStyleName?.let { add("\nstyleName = %S,", it) }
}
add(
composePreviewFunctionLambdaCodeBlock(
showkaseMetadata.packageName,
showkaseMetadata.enclosingClassName,
showkaseMetadata.elementName,
showkaseMetadata.insideWrapperClass,
showkaseMetadata.insideObject,
showkaseMetadata.previewParameterProviderType,
showkaseMetadata.previewParameterName
)
)
doubleUnindent()
}
@Suppress("LongParameterList")
internal fun composePreviewFunctionLambdaCodeBlock(
functionPackageName: String,
enclosingClass: ClassName? = null,
composeFunctionName: String,
insideWrapperClass: Boolean,
insideObject: Boolean,
previewParameter: TypeName? = null,
previewParameterName: String? = null
): CodeBlock {
return when {
// When enclosingClass is null, it denotes that the method was a top-level method
// declaration.
enclosingClass == null -> {
val composableFunctionString = previewParameter?.let {
"%M($previewParameterName = previewParam)"
} ?: "%M()"
val composeMember = MemberName(functionPackageName, composeFunctionName)
CodeBlock.Builder()
.add(
"\ncomponent = @%T { $composableFunctionString }",
ShowkaseBrowserWriter.COMPOSE_CLASS_NAME, composeMember
)
.build()
}
// It was declared inside a class.
insideWrapperClass -> {
val composableFunctionString = previewParameter?.let {
"${composeFunctionName}($previewParameterName = previewParam)"
} ?: "${composeFunctionName}()"
CodeBlock.Builder()
.add(
"\ncomponent = @%T {\n %T().${composableFunctionString}\n}",
ShowkaseBrowserWriter.COMPOSE_CLASS_NAME, enclosingClass
)
.build()
}
// It was declared inside an object or a companion object.
insideObject -> {
val composableFunctionString = previewParameter?.let {
"${composeFunctionName}($previewParameterName = previewParam)"
} ?: "${composeFunctionName}()"
CodeBlock.Builder()
.add(
"\ncomponent = @%T {\n %T.${composableFunctionString}\n}",
ShowkaseBrowserWriter.COMPOSE_CLASS_NAME, enclosingClass
)
.build()
}
else -> throw ShowkaseProcessorException(
"Your @ShowkaseComposable/@Preview " +
"function:${composeFunctionName} is declared in a way that is not supported by " +
"Showkase"
)
}
}
internal fun CodeBlock.Builder.addShowkaseBrowserColor(
showkaseMetadata: ShowkaseMetadata,
) {
addLineBreak()
add(
"%T(\n",
ShowkaseBrowserWriter.SHOWKASE_BROWSER_COLOR_CLASS_NAME
)
doubleIndent()
add(
"colorGroup = %S,\ncolorName = %S,\ncolorKDoc = %S,",
showkaseMetadata.showkaseGroup,
showkaseMetadata.showkaseName,
showkaseMetadata.showkaseKDoc
)
add(
showkaseBrowserPropertyValue(
showkaseMetadata.packageName,
showkaseMetadata.enclosingClassName,
"color",
showkaseMetadata.elementName,
showkaseMetadata.insideWrapperClass,
showkaseMetadata.insideObject
)
)
doubleUnindent()
}
internal fun CodeBlock.Builder.addShowkaseBrowserTypography(
showkaseMetadata: ShowkaseMetadata,
) {
addLineBreak()
add(
"%T(\n",
ShowkaseBrowserWriter.SHOWKASE_BROWSER_TYPOGRAPHY_CLASS_NAME
)
doubleIndent()
add(
"typographyGroup = %S,\ntypographyName = %S,\ntypographyKDoc = %S,",
showkaseMetadata.showkaseGroup,
showkaseMetadata.showkaseName,
showkaseMetadata.showkaseKDoc
)
add(
showkaseBrowserPropertyValue(
showkaseMetadata.packageName,
showkaseMetadata.enclosingClassName,
"textStyle",
showkaseMetadata.elementName,
showkaseMetadata.insideWrapperClass,
showkaseMetadata.insideObject
)
)
doubleUnindent()
}
@Suppress("LongParameterList")
internal fun showkaseBrowserPropertyValue(
functionPackageName: String,
enclosingClass: TypeName? = null,
fieldPropertyName: String,
fieldName: String,
insideWrapperClass: Boolean,
insideObject: Boolean
) = when {
// When enclosingClass is null, it denotes that the method was a top-level method
// declaration.
enclosingClass == null -> {
val composeMember = MemberName(functionPackageName, fieldName)
CodeBlock.Builder()
.add("\n$fieldPropertyName = %M", composeMember)
.build()
}
// It was declared inside a class.
insideWrapperClass -> {
CodeBlock.Builder()
.add("\n$fieldPropertyName = %T().${fieldName}", enclosingClass)
.build()
}
// It was declared inside an object or a companion object.
insideObject -> {
CodeBlock.Builder()
.add("\n$fieldPropertyName = %T.${fieldName}", enclosingClass)
.build()
}
else -> throw ShowkaseProcessorException(
"Your field:${fieldName} is declared in a way that " +
"is not supported by Showkase"
)
}
internal fun generatePropertyNameFromMetadata(
metadata: ShowkaseMetadata,
): String {
return when(metadata) {
is ShowkaseMetadata.Component -> {
val name =
if (metadata.componentIndex != null && metadata.componentIndex > 0
) {
"${metadata.packageName}_${metadata.showkaseGroup}_" +
"${metadata.showkaseName}_${metadata.componentIndex}"
} else {
"${metadata.packageName}_${metadata.showkaseGroup}_${metadata.showkaseName}"
}
val propertyName = if (metadata.showkaseStyleName != null) {
"${name}_${metadata.showkaseStyleName}"
} else {
name
}.filter { it.isLetterOrDigit() }
propertyName
}
else -> {
"${metadata.packageName}_${metadata.showkaseGroup}_${metadata.showkaseName}"
.filter { it.isLetterOrDigit() }
}
}
}
internal fun CodeBlock.Builder.withDoubleIndent(block: CodeBlock.Builder.() -> Unit) =
doubleIndent().also(block).doubleUnindent()
internal fun CodeBlock.Builder.doubleIndent() = indent().indent()
internal fun CodeBlock.Builder.doubleUnindent() = unindent().unindent()
internal fun CodeBlock.Builder.closeRoundBracket() = addLineBreak().add(")")
internal fun CodeBlock.Builder.closeCurlyBraces() = addLineBreak().add("}")
internal fun CodeBlock.Builder.addLineBreak() = add("\n")
© 2015 - 2025 Weber Informatics LLC | Privacy Policy