diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/ShowkaseProcessor.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/ShowkaseProcessor.kt index 86b1d837..995f648e 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/ShowkaseProcessor.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/ShowkaseProcessor.kt @@ -26,11 +26,12 @@ import com.airbnb.android.showkase.processor.models.getShowkaseMetadataFromPrevi import com.airbnb.android.showkase.processor.models.getShowkaseTypographyMetadata import com.airbnb.android.showkase.processor.writer.PaparazziShowkaseScreenshotTestWriter import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserProperties -import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserPropertyWriter +import com.airbnb.android.showkase.processor.writer.ShowkaseModuleBrowserPropertyWriter import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.CODEGEN_AUTOGEN_CLASS_NAME -import com.airbnb.android.showkase.processor.writer.ShowkaseCodegenMetadataWriter +import com.airbnb.android.showkase.processor.writer.ShowkaseModuleCodegenMetadataWriter import com.airbnb.android.showkase.processor.writer.ShowkaseExtensionFunctionsWriter +import com.airbnb.android.showkase.processor.writer.ShowkaseModuleMetadataWriter import com.airbnb.android.showkase.processor.writer.ShowkaseScreenshotTestWriter import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.processing.SymbolProcessorEnvironment @@ -262,16 +263,17 @@ class ShowkaseProcessor @JvmOverloads constructor( val aggregateMetadataList = componentMetadata + colorMetadata + typographyMetadata if (aggregateMetadataList.isEmpty()) return ShowkaseBrowserProperties() - ShowkaseCodegenMetadataWriter(environment).apply { + ShowkaseModuleCodegenMetadataWriter(environment).apply { generateShowkaseCodegenFunctions(aggregateMetadataList) } - ShowkaseBrowserPropertyWriter(environment).apply { + ShowkaseModuleBrowserPropertyWriter(environment).apply { return generateMetadataPropertyFiles( componentMetadata = componentMetadata, colorMetadata = colorMetadata, typographyMetadata = typographyMetadata, ) } + } private fun Collection.dedupeAndSort() = this.distinctBy { @@ -354,6 +356,16 @@ class ShowkaseProcessor @JvmOverloads constructor( val currentShowkaseBrowserProperties = writeMetadataFile(componentMetadata, colorMetadata, typographyMetadata) + ShowkaseModuleMetadataWriter.generateModuleLevelShowkaseProvider( + environment = environment, + moduleShowkaseBrowserProperties = currentShowkaseBrowserProperties + ) + + ShowkaseModuleMetadataWriter.generateModuleMetadataPublicApi( + environment = environment, + moduleShowkaseBrowserProperties = currentShowkaseBrowserProperties + ) + if (rootElement != null) { // This is the module that should aggregate all the other metadata files and // also use the showkaseMetadata set from the current round to write the final file. diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserWriter.kt index 6e948772..900c9390 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserWriter.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserWriter.kt @@ -71,80 +71,6 @@ internal class ShowkaseBrowserWriter(private val environment: XProcessingEnv) { ) } - private fun initializeComponentCodeBlock( - withoutParameterPropertyNames: List, - withParameterPropertyNames: List, - ): CodeBlock { - val componentListInitializerCodeBlock = if (withParameterPropertyNames.isNotEmpty()) { - SHOWKASE_BROWSER_COMPONENT_CLASS_NAME.mutableListInitializerCodeBlock() - } else { - SHOWKASE_BROWSER_COMPONENT_CLASS_NAME.listInitializerCodeBlock() - } - - componentListInitializerCodeBlock.apply { - addLineBreak() - withoutParameterPropertyNames.forEachIndexed { index, metadata -> - add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName)) - addLineBreak() - } - doubleUnindent() - add(")") - - if (withParameterPropertyNames.isNotEmpty()) { - add(".apply {") - addLineBreak() - withDoubleIndent { - withParameterPropertyNames.forEachIndexed { index, metadata -> - add( - "addAll(%M)", - MemberName(metadata.propertyPackage, metadata.propertyName) - ) - if (index != withParameterPropertyNames.lastIndex) { - addLineBreak() - } - } - } - closeCurlyBraces() - } - } - - return componentListInitializerCodeBlock.build() - } - - private fun initializeColorCodeBlock( - colorsParameterPropertyNames: List, - ): CodeBlock { - val colorListInitializerCodeBlock = - SHOWKASE_BROWSER_COLOR_CLASS_NAME.listInitializerCodeBlock() - - return colorListInitializerCodeBlock.apply { - addLineBreak() - colorsParameterPropertyNames.forEachIndexed { index, metadata -> - add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName)) - addLineBreak() - } - doubleUnindent() - add(")") - }.build() - } - - private fun initializeTypographyCodeBlock( - typographyParameterPropertyNames: List, - ): CodeBlock { - val typographyListInitializerCodeBlock = - SHOWKASE_BROWSER_TYPOGRAPHY_CLASS_NAME.listInitializerCodeBlock() - - return typographyListInitializerCodeBlock.apply { - addLineBreak() - typographyParameterPropertyNames.forEachIndexed { index, metadata -> - add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName)) - addLineBreak() - } - doubleUnindent() - add(")") - }.build() - } - private fun initializeShowkaseRootCodegenAnnotation( numComponentsWithoutPreviewParameter: Int, numComponentsWithPreviewParameter: Int, @@ -203,9 +129,9 @@ internal class ShowkaseBrowserWriter(private val environment: XProcessingEnv) { companion object { internal const val CODEGEN_AUTOGEN_CLASS_NAME = "Codegen" - private const val COMPONENT_INTERFACE_METHOD_NAME = "getShowkaseComponents" - private const val COLOR_INTERFACE_METHOD_NAME = "getShowkaseColors" - private const val TYPOGRAPHY_INTERFACE_METHOD_NAME = "getShowkaseTypography" + internal const val COMPONENT_INTERFACE_METHOD_NAME = "getShowkaseComponents" + internal const val COLOR_INTERFACE_METHOD_NAME = "getShowkaseColors" + internal const val TYPOGRAPHY_INTERFACE_METHOD_NAME = "getShowkaseTypography" internal const val SHOWKASE_MODELS_PACKAGE_NAME = "com.airbnb.android.showkase.models" internal const val COMPONENT_PROPERTY_NAME = "componentList" internal const val COLOR_PROPERTY_NAME = "colorList" @@ -220,5 +146,79 @@ internal class ShowkaseBrowserWriter(private val environment: XProcessingEnv) { ClassName(SHOWKASE_MODELS_PACKAGE_NAME, "ShowkaseBrowserTypography") internal val SHOWKASE_PROVIDER_CLASS_NAME = ClassName(SHOWKASE_MODELS_PACKAGE_NAME, "ShowkaseProvider") + + internal fun initializeComponentCodeBlock( + withoutParameterPropertyNames: List, + withParameterPropertyNames: List, + ): CodeBlock { + val componentListInitializerCodeBlock = if (withParameterPropertyNames.isNotEmpty()) { + SHOWKASE_BROWSER_COMPONENT_CLASS_NAME.mutableListInitializerCodeBlock() + } else { + SHOWKASE_BROWSER_COMPONENT_CLASS_NAME.listInitializerCodeBlock() + } + + componentListInitializerCodeBlock.apply { + addLineBreak() + withoutParameterPropertyNames.forEachIndexed { index, metadata -> + add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName)) + addLineBreak() + } + doubleUnindent() + add(")") + + if (withParameterPropertyNames.isNotEmpty()) { + add(".apply {") + addLineBreak() + withDoubleIndent { + withParameterPropertyNames.forEachIndexed { index, metadata -> + add( + "addAll(%M)", + MemberName(metadata.propertyPackage, metadata.propertyName) + ) + if (index != withParameterPropertyNames.lastIndex) { + addLineBreak() + } + } + } + closeCurlyBraces() + } + } + + return componentListInitializerCodeBlock.build() + } + + internal fun initializeColorCodeBlock( + colorsParameterPropertyNames: List, + ): CodeBlock { + val colorListInitializerCodeBlock = + SHOWKASE_BROWSER_COLOR_CLASS_NAME.listInitializerCodeBlock() + + return colorListInitializerCodeBlock.apply { + addLineBreak() + colorsParameterPropertyNames.forEachIndexed { index, metadata -> + add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName)) + addLineBreak() + } + doubleUnindent() + add(")") + }.build() + } + + internal fun initializeTypographyCodeBlock( + typographyParameterPropertyNames: List, + ): CodeBlock { + val typographyListInitializerCodeBlock = + SHOWKASE_BROWSER_TYPOGRAPHY_CLASS_NAME.listInitializerCodeBlock() + + return typographyListInitializerCodeBlock.apply { + addLineBreak() + typographyParameterPropertyNames.forEachIndexed { index, metadata -> + add("%M,", MemberName(metadata.propertyPackage, metadata.propertyName)) + addLineBreak() + } + doubleUnindent() + add(")") + }.build() + } } } diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseExtensionFunctionsWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseExtensionFunctionsWriter.kt index bf7c9029..a73964dd 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseExtensionFunctionsWriter.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseExtensionFunctionsWriter.kt @@ -116,7 +116,7 @@ internal class ShowkaseExtensionFunctionsWriter( companion object { private const val SHOWKASE_ROOT_MODULE_KEY = "SHOWKASE_ROOT_MODULE" private const val INTENT_FUNCTION_NAME = "getBrowserIntent" - private const val METADATA_FUNCTION_NAME = "getMetadata" + internal const val METADATA_FUNCTION_NAME = "getMetadata" private const val SHOWKASE_EXTENSION_FUNCTIONS_NAME = "ShowkaseExtensionFunctions" private const val SHOWKASE_METHODS_SUFFIX = "${SHOWKASE_EXTENSION_FUNCTIONS_NAME}Codegen" private const val CONTEXT_PARAMETER_NAME = "context" @@ -127,7 +127,7 @@ internal class ShowkaseExtensionFunctionsWriter( ClassName(CONTEXT_PACKAGE_NAME, "Intent") private val SHOWKASE_BROWSER_ACTIVITY_CLASS_NAME = ClassName("com.airbnb.android.showkase.ui", "ShowkaseBrowserActivity") - private val SHOWKASE_ELEMENTS_METADATA_CLASS_NAME = + internal val SHOWKASE_ELEMENTS_METADATA_CLASS_NAME = ClassName(SHOWKASE_MODELS_PACKAGE_NAME, "ShowkaseElementsMetadata") internal val SHOWKASE_OBJECT_CLASS_NAME = ClassName(SHOWKASE_MODELS_PACKAGE_NAME, "Showkase") diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserPropertyWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseModuleBrowserPropertyWriter.kt similarity index 99% rename from showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserPropertyWriter.kt rename to showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseModuleBrowserPropertyWriter.kt index 942c4580..9eaa0669 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseBrowserPropertyWriter.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseModuleBrowserPropertyWriter.kt @@ -14,7 +14,7 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.asTypeName -class ShowkaseBrowserPropertyWriter(private val environment: XProcessingEnv) { +class ShowkaseModuleBrowserPropertyWriter(private val environment: XProcessingEnv) { @Suppress("LongMethod") internal fun generateMetadataPropertyFiles( componentMetadata: Set, diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseCodegenMetadataWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseModuleCodegenMetadataWriter.kt similarity index 95% rename from showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseCodegenMetadataWriter.kt rename to showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseModuleCodegenMetadataWriter.kt index ea0abb76..b62a09f7 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseCodegenMetadataWriter.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseModuleCodegenMetadataWriter.kt @@ -14,13 +14,12 @@ import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.TypeSpec import java.util.Locale -internal class ShowkaseCodegenMetadataWriter(private val environment: XProcessingEnv) { +internal class ShowkaseModuleCodegenMetadataWriter(private val environment: XProcessingEnv) { internal fun generateShowkaseCodegenFunctions( showkaseMetadataSet: Set, ) { - val moduleName = showkaseMetadataSet.first().packageName.replace(".", "_") - val generatedClassName = "ShowkaseMetadata_${moduleName.lowercase(Locale.getDefault())}" + val generatedClassName = "ShowkaseMetadata_${showkaseMetadataSet.getNormalizedPackageName()}" val fileBuilder = FileSpec.builder( CODEGEN_PACKAGE_NAME, generatedClassName diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseModuleMetadataWriter.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseModuleMetadataWriter.kt new file mode 100644 index 00000000..b44448b7 --- /dev/null +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/ShowkaseModuleMetadataWriter.kt @@ -0,0 +1,110 @@ +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.writer.ShowkaseBrowserWriter.Companion.SHOWKASE_PROVIDER_CLASS_NAME +import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.initializeColorCodeBlock +import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.initializeComponentCodeBlock +import com.airbnb.android.showkase.processor.writer.ShowkaseBrowserWriter.Companion.initializeTypographyCodeBlock +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.LIST +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy + +object ShowkaseModuleMetadataWriter { + @Suppress("LongMethod", "LongParameterList") + internal fun generateModuleLevelShowkaseProvider( + environment: XProcessingEnv, + moduleShowkaseBrowserProperties: ShowkaseBrowserProperties, + ) { + if (moduleShowkaseBrowserProperties.isEmpty()) return + val packageName = moduleShowkaseBrowserProperties.getPackageName() + val showkaseComponentsListClassName = + "${MODULE_LEVEL_SHOWKASE_PROVIDER_CLASS_PREFIX}_${packageName.normalizePackageName()}" + val fileBuilder = getFileBuilder(packageName, showkaseComponentsListClassName) + val componentCodeBlock = initializeComponentCodeBlock( + moduleShowkaseBrowserProperties.componentsWithoutPreviewParameters, + moduleShowkaseBrowserProperties.componentsWithPreviewParameters + ) + val colorCodeBlock = initializeColorCodeBlock(moduleShowkaseBrowserProperties.colors) + val typographyCodeBlock = + initializeTypographyCodeBlock(moduleShowkaseBrowserProperties.typography) + writeFile( + environment, + fileBuilder, + ShowkaseBrowserWriter.SHOWKASE_PROVIDER_CLASS_NAME, + showkaseComponentsListClassName, + moduleShowkaseBrowserProperties, + getShowkaseProviderInterfaceFunction( + methodName = ShowkaseBrowserWriter.COMPONENT_INTERFACE_METHOD_NAME, + returnType = LIST.parameterizedBy(ShowkaseBrowserWriter.SHOWKASE_BROWSER_COMPONENT_CLASS_NAME), + codeBlock = componentCodeBlock + ), + getShowkaseProviderInterfaceFunction( + methodName = ShowkaseBrowserWriter.COLOR_INTERFACE_METHOD_NAME, + returnType = LIST.parameterizedBy(ShowkaseBrowserWriter.SHOWKASE_BROWSER_COLOR_CLASS_NAME), + codeBlock = colorCodeBlock + ), + getShowkaseProviderInterfaceFunction( + methodName = ShowkaseBrowserWriter.TYPOGRAPHY_INTERFACE_METHOD_NAME, + returnType = LIST.parameterizedBy(ShowkaseBrowserWriter.SHOWKASE_BROWSER_TYPOGRAPHY_CLASS_NAME), + codeBlock = typographyCodeBlock + ), + showkaseRootCodegenAnnotation = null + ) + } + + internal fun generateModuleMetadataPublicApi( + environment: XProcessingEnv, + moduleShowkaseBrowserProperties: ShowkaseBrowserProperties, + ) { + if (moduleShowkaseBrowserProperties.isEmpty()) return + val packageName = moduleShowkaseBrowserProperties.getPackageName() + val normalizedPackageName = packageName.normalizePackageName() + val showkaseComponentsListClassName = + "${MODULE_LEVEL_SHOWKASE_PROVIDER_CLASS_PREFIX}ExtensionFunctions_${normalizedPackageName}" + val fileBuilder = getFileBuilder(packageName, showkaseComponentsListClassName) + + fileBuilder + .addFileComment("This is an auto-generated file. Please do not edit/modify this file.") + .addFunction( + FunSpec.builder(MODULE_METADATA_FUNCTION_NAME).apply { + receiver(ShowkaseExtensionFunctionsWriter.SHOWKASE_OBJECT_CLASS_NAME) + returns(ShowkaseExtensionFunctionsWriter.SHOWKASE_ELEMENTS_METADATA_CLASS_NAME) + addKdoc( + "Helper function that gives you access to Showkase elements that are declared in a given module. " + + "This contains data about the composables, colors and typography that are meant to be rendered" + + "inside the Showkase browser. This is different from the Showkase.${ShowkaseExtensionFunctionsWriter.METADATA_FUNCTION_NAME}() " + + "function, which contains all the Showkase elements in a given ShowkaseRoot graph, whereas this function " + + "only contains metadata about the module it's generated in. Each module where Showkase is setup " + + "will have this function generated in it." + ) + addCode( + CodeBlock.Builder() + .indent() + .addStatement( + "return (%T as %T).metadata()", + ClassName( + packageName, + "${MODULE_LEVEL_SHOWKASE_PROVIDER_CLASS_PREFIX}_${normalizedPackageName}" + ), + SHOWKASE_PROVIDER_CLASS_NAME + ) + .unindent() + .build() + ) + moduleShowkaseBrowserProperties.zip() + .forEach { addOriginatingElement(it.element) } + } + .build() + ) + .build() + .writeTo(environment.filer, mode = XFiler.Mode.Aggregating) + } + + private const val MODULE_METADATA_FUNCTION_NAME = "getModuleMetadata" + private const val MODULE_LEVEL_SHOWKASE_PROVIDER_CLASS_PREFIX = "ShowkaseModuleMetadata" +} \ No newline at end of file diff --git a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/WriterUtils.kt b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/WriterUtils.kt index efe3eda3..95000092 100644 --- a/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/WriterUtils.kt +++ b/showkase-processor/src/main/java/com/airbnb/android/showkase/processor/writer/WriterUtils.kt @@ -18,6 +18,7 @@ import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.asClassName +import java.util.Locale val SPACE_REGEX = "\\s".toRegex() @@ -56,12 +57,15 @@ internal fun writeFile( componentInterfaceFunction: FunSpec, colorInterfaceFunction: FunSpec, typographyInterfaceFunction: FunSpec, - showkaseRootCodegenAnnotation: AnnotationSpec + showkaseRootCodegenAnnotation: AnnotationSpec? = null ) { fileBuilder + .addFileComment("This is an auto-generated file. Please do not edit/modify this file.") .addType( - with(TypeSpec.classBuilder(showkaseComponentsListClassName)) { - addAnnotation(showkaseRootCodegenAnnotation) + with(TypeSpec.objectBuilder(showkaseComponentsListClassName)) { + showkaseRootCodegenAnnotation?.let { + addAnnotation(it) + } addSuperinterface(superInterfaceClassName) addFunction(componentInterfaceFunction) addFunction(colorInterfaceFunction) @@ -342,6 +346,18 @@ internal fun generatePropertyNameFromMetadata( } } +internal fun ShowkaseBrowserProperties.getPackageName() = zip() + .first() + .propertyPackage + +internal fun Collection.getNormalizedPackageName() = this + .first() + .packageName + .normalizePackageName() + +internal fun String.normalizePackageName() = replace(".", "_") + .lowercase(Locale.getDefault()) + internal fun CodeBlock.Builder.withDoubleIndent(block: CodeBlock.Builder.() -> Unit) = doubleIndent().also(block).doubleUnindent()