Skip to content

Commit 1cff14e

Browse files
authored
Parse custom annotations that reference built-in annotations (#523)
* Parse custom annotations that reference built-in annotations * Big refactoring
1 parent d0e509a commit 1cff14e

File tree

4 files changed

+140
-229
lines changed

4 files changed

+140
-229
lines changed

gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/snapshots/transform/AnnotatedMethodVisitor.kt

Lines changed: 59 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ class AnnotatedMethodVisitor(
1717
private val customPreviewAnnotations: Map<String, CustomPreviewAnnotation> = emptyMap()
1818
) : MethodVisitor(api) {
1919

20-
private val methodNamesToAdd = mutableListOf<ComposePreviewSnapshotConfig>()
20+
private val methodNamesToAdd = mutableListOf<PreviewConfig>()
2121
private var foundIgnoreAnnotation = false
22+
private var foundAppStoreSnapshot = false
2223

2324
// Parameter and preview parameter info
2425
private val parameterPreviewInfoMap = mutableMapOf<Int, PreviewParameterInfo>()
@@ -31,161 +32,37 @@ class AnnotatedMethodVisitor(
3132
val index: Int? = null
3233
)
3334

34-
@Suppress("detekt.ReturnCount", "detekt.ThrowsCount")
35-
private fun createComposePreviewConfigs(forAnnotation: String): List<ComposePreviewSnapshotConfig> {
36-
val composeConfig = ComposePreviewSnapshotConfig(
37-
originalFqn = className.cleanName().removeClassName() + "." + methodName,
38-
sourceFileName = fileName.cleanName().cleanFileName(),
39-
fullyQualifiedClassName = className.cleanName(),
40-
)
41-
42-
when (forAnnotation) {
43-
PREVIEW_ANNOTATION_DESC -> {
44-
return listOf(composeConfig)
45-
}
46-
47-
PREVIEW_LIGHT_DARK_ANNOTATION_DESC -> {
48-
return listOf(
49-
composeConfig.copy(name = "light"),
50-
composeConfig.copy(name = "dark", uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL)
51-
)
52-
}
53-
54-
PREVIEW_SCREEN_SIZES_ANNOTATION_DESC -> {
55-
return listOf(
56-
composeConfig.copy(name = "Phone", device = PHONE, showSystemUi = true),
57-
composeConfig.copy(
58-
name = "Phone - Landscape",
59-
device = "spec:width = 411dp, height = 891dp, orientation = landscape, dpi = 420",
60-
showSystemUi = true
61-
),
62-
composeConfig.copy(name = "Unfolded Foldable", device = FOLDABLE, showSystemUi = true),
63-
composeConfig.copy(name = "Tablet", device = TABLET, showSystemUi = true),
64-
composeConfig.copy(name = "Desktop", device = DESKTOP, showSystemUi = true)
65-
)
66-
}
67-
68-
PREVIEW_FONT_SCALE_ANNOTATION_DESC -> {
69-
return listOf(
70-
composeConfig.copy(name = "85%", fontScale = 0.85f),
71-
composeConfig.copy(name = "100%", fontScale = 1.0f),
72-
composeConfig.copy(name = "115%", fontScale = 1.15f),
73-
composeConfig.copy(name = "130%", fontScale = 1.3f),
74-
composeConfig.copy(name = "150%", fontScale = 1.5f),
75-
composeConfig.copy(name = "180%", fontScale = 1.8f),
76-
composeConfig.copy(name = "200%", fontScale = 2f)
77-
)
78-
}
79-
80-
EMERGE_APP_STORE_SNAPSHOT -> {
81-
throw IllegalArgumentException("Emerge app store snapshot should should be handled elsewhere")
82-
}
83-
84-
EMERGE_IGNORE_SNAPSHOT -> {
85-
throw IllegalArgumentException("Emerge ignore snapshot should should be handled elsewhere")
86-
}
87-
88-
PREVIEW_CONTAINER_ANNOTATION_DESC -> {
89-
throw IllegalArgumentException("Preview container annotation should be handled as individual annotations")
90-
}
91-
92-
else -> {
93-
// Check if this is a custom preview annotations
94-
val customAnnotation = customPreviewAnnotations[forAnnotation]
95-
if (customAnnotation != null) {
96-
// Create configs based on the custom annotation
97-
return createConfigsFromCustomAnnotation(composeConfig, customAnnotation)
98-
}
99-
return emptyList()
100-
}
35+
private fun createComposePreviewConfigs(forAnnotation: String): List<PreviewConfig> {
36+
val previewConfigs = previewConfigForAnnotation(forAnnotation)
37+
if (previewConfigs != null) {
38+
return previewConfigs
10139
}
102-
}
103-
104-
/**
105-
* Creates ComposePreviewSnapshotConfig objects from a custom annotation
106-
*/
107-
private fun createConfigsFromCustomAnnotation(
108-
baseConfig: ComposePreviewSnapshotConfig,
109-
customAnnotation: CustomPreviewAnnotation
110-
): List<ComposePreviewSnapshotConfig> {
111-
val configs = mutableListOf<ComposePreviewSnapshotConfig>()
112-
113-
if (customAnnotation.hasPreviewArray && customAnnotation.previewConfigs.isNotEmpty()) {
114-
// Create a config for each @Preview in the array
115-
for ((index, previewConfig) in customAnnotation.previewConfigs.withIndex()) {
116-
val name = previewConfig.name ?: "preview-${index + 1}"
117-
118-
configs.add(
119-
baseConfig.copy(
120-
name = name,
121-
group = previewConfig.group,
122-
showBackground = previewConfig.showBackground,
123-
backgroundColor = previewConfig.backgroundColor,
124-
widthDp = previewConfig.widthDp,
125-
heightDp = previewConfig.heightDp,
126-
showSystemUi = previewConfig.showSystemUi,
127-
device = previewConfig.device,
128-
fontScale = previewConfig.fontScale,
129-
locale = previewConfig.locale,
130-
uiMode = previewConfig.uiMode,
131-
apiLevel = previewConfig.apiLevel,
132-
wallpaper = previewConfig.wallpaper,
133-
isAppStoreSnapshot = customAnnotation.isAppStoreSnapshot
134-
)
135-
)
136-
}
137-
} else if (customAnnotation.hasPreviewAnnotation) {
138-
// Create a single config for the direct @Preview
139-
configs.add(
140-
baseConfig.copy(
141-
name = customAnnotation.name,
142-
group = customAnnotation.group,
143-
showBackground = customAnnotation.showBackground,
144-
backgroundColor = customAnnotation.backgroundColor,
145-
widthDp = customAnnotation.widthDp,
146-
heightDp = customAnnotation.heightDp,
147-
showSystemUi = customAnnotation.showSystemUi,
148-
device = customAnnotation.device,
149-
fontScale = customAnnotation.fontScale,
150-
locale = customAnnotation.locale,
151-
uiMode = customAnnotation.uiMode,
152-
apiLevel = customAnnotation.apiLevel,
153-
wallpaper = customAnnotation.wallpaper,
154-
isAppStoreSnapshot = customAnnotation.isAppStoreSnapshot
155-
)
156-
)
157-
} else if (customAnnotation.isAppStoreSnapshot) {
158-
// Only has @EmergeAppStoreSnapshot
159-
configs.add(baseConfig.copy(isAppStoreSnapshot = true))
40+
require(forAnnotation != PREVIEW_ANNOTATION_DESC) { "$forAnnotation annotation should be handled in the visitor" }
41+
require(forAnnotation != EMERGE_APP_STORE_SNAPSHOT) { "$forAnnotation annotation should be handled in the visitor" }
42+
require(forAnnotation != EMERGE_IGNORE_SNAPSHOT) { "$forAnnotation annotation should be handled in the visitor" }
43+
require(forAnnotation != PREVIEW_ANNOTATION_DESC) { "$forAnnotation annotation should be handled in the visitor" }
44+
// Check if this is a custom preview annotations
45+
val customAnnotation = customPreviewAnnotations[forAnnotation]
46+
if (customAnnotation != null) {
47+
if (customAnnotation.isAppStoreSnapshot) {
48+
foundAppStoreSnapshot = true
49+
}
50+
return customAnnotation.previewConfigs
16051
}
161-
162-
return configs
52+
return emptyList()
16353
}
16454

16555
@Suppress("detekt.ReturnCount")
16656
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
16757
return when (descriptor) {
16858
PREVIEW_ANNOTATION_DESC -> {
169-
val previewConfig = createComposePreviewConfigs(PREVIEW_ANNOTATION_DESC).first()
59+
val previewConfig = PreviewConfig()
17060
methodNamesToAdd.add(previewConfig)
171-
return PreviewAnnotationVisitor(api, previewConfig)
172-
}
173-
174-
PREVIEW_LIGHT_DARK_ANNOTATION_DESC -> {
175-
val snapshotConfig = createComposePreviewConfigs(PREVIEW_LIGHT_DARK_ANNOTATION_DESC)
176-
methodNamesToAdd.addAll(snapshotConfig)
177-
return super.visitAnnotation(descriptor, visible)
178-
}
179-
180-
PREVIEW_FONT_SCALE_ANNOTATION_DESC -> {
181-
val snapshotConfig = createComposePreviewConfigs(PREVIEW_FONT_SCALE_ANNOTATION_DESC)
182-
methodNamesToAdd.addAll(snapshotConfig)
183-
return super.visitAnnotation(descriptor, visible)
61+
return PreviewAnnotationVisitor(previewConfig)
18462
}
18563

18664
EMERGE_APP_STORE_SNAPSHOT -> {
187-
val snapshotConfig = createComposePreviewConfigs(EMERGE_APP_STORE_SNAPSHOT)
188-
methodNamesToAdd.addAll(snapshotConfig)
65+
foundAppStoreSnapshot = true
18966
return super.visitAnnotation(descriptor, visible)
19067
}
19168

@@ -204,9 +81,9 @@ class AnnotatedMethodVisitor(
20481
descriptor: String?
20582
): AnnotationVisitor? {
20683
if (descriptor == PREVIEW_ANNOTATION_DESC) {
207-
val config = createComposePreviewConfigs(descriptor).first()
84+
val config = PreviewConfig()
20885
methodNamesToAdd.add(config)
209-
return PreviewAnnotationVisitor(api, config)
86+
return PreviewAnnotationVisitor(config)
21087
}
21188
return super.visitAnnotation(name, descriptor)
21289
}
@@ -218,11 +95,9 @@ class AnnotatedMethodVisitor(
21895
}
21996

22097
else -> {
221-
// Check if this is a custom preview annotation we discovered in the first pass
222-
// Handle null descriptor safely
223-
if (descriptor != null && customPreviewAnnotations.containsKey(descriptor)) {
224-
val configs = createComposePreviewConfigs(descriptor)
225-
methodNamesToAdd.addAll(configs)
98+
if (descriptor != null) {
99+
val previewConfig = createComposePreviewConfigs(descriptor)
100+
methodNamesToAdd.addAll(previewConfig)
226101
}
227102

228103
// This is just another annotation or it could be a custom annotation that includes a preview.
@@ -292,22 +167,21 @@ class AnnotatedMethodVisitor(
292167
}
293168

294169
override fun visitEnd() {
295-
// Apply any preview parameter information to the configs
296-
if (parameterPreviewInfoMap.isNotEmpty()) {
297-
applyPreviewParameterInfo()
298-
}
170+
val previewConfigs =
171+
applyPreviewParameterInfo(methodNamesToAdd.toComposePreviewSnapshotConfig())
299172

300173
// We only add the method names in the end in case we find the ignore annotation and then we ignore what we found.
301174
if (!foundIgnoreAnnotation) {
302-
fullPreviewConfigList.addAll(methodNamesToAdd)
175+
fullPreviewConfigList.addAll(previewConfigs)
303176
}
304177
super.visitEnd()
305178
}
306179

307-
/**
308-
* Apply the preview parameter information to all method configs.
309-
*/
310-
private fun applyPreviewParameterInfo() {
180+
private fun applyPreviewParameterInfo(composePreviews: List<ComposePreviewSnapshotConfig>):
181+
List<ComposePreviewSnapshotConfig> {
182+
if (parameterPreviewInfoMap.isEmpty()) {
183+
return composePreviews
184+
}
311185
// We only handle the first preview parameter for simplicity.
312186
require(
313187
parameterPreviewInfoMap.entries.size == 1
@@ -326,12 +200,33 @@ class AnnotatedMethodVisitor(
326200
index = previewInfo.index
327201
)
328202

329-
val updatedConfigs = methodNamesToAdd.map { config ->
203+
val updatedConfigs = composePreviews.map { config ->
330204
config.copy(previewParameter = previewParam)
331205
}
206+
return updatedConfigs
207+
}
332208

333-
// Replace the old configs with the updated ones
334-
methodNamesToAdd.clear()
335-
methodNamesToAdd.addAll(updatedConfigs)
209+
private fun List<PreviewConfig>.toComposePreviewSnapshotConfig(): List<ComposePreviewSnapshotConfig> {
210+
return map { previewConfig ->
211+
ComposePreviewSnapshotConfig(
212+
fullyQualifiedClassName = className.cleanName(),
213+
originalFqn = className.cleanName().removeClassName() + "." + methodName,
214+
sourceFileName = fileName.cleanName().cleanFileName(),
215+
name = previewConfig.name,
216+
group = previewConfig.group,
217+
uiMode = previewConfig.uiMode,
218+
locale = previewConfig.locale,
219+
fontScale = previewConfig.fontScale,
220+
heightDp = previewConfig.heightDp,
221+
widthDp = previewConfig.widthDp,
222+
showBackground = previewConfig.showBackground,
223+
backgroundColor = previewConfig.backgroundColor,
224+
showSystemUi = previewConfig.showSystemUi,
225+
device = previewConfig.device,
226+
apiLevel = previewConfig.apiLevel,
227+
wallpaper = previewConfig.wallpaper,
228+
isAppStoreSnapshot = foundAppStoreSnapshot
229+
)
230+
}
336231
}
337232
}

0 commit comments

Comments
 (0)