<lambda>null1 package expo.modules.annotationprocessor
2 
3 import com.google.devtools.ksp.getVisibility
4 import com.google.devtools.ksp.processing.CodeGenerator
5 import com.google.devtools.ksp.processing.Dependencies
6 import com.google.devtools.ksp.processing.KSPLogger
7 import com.google.devtools.ksp.processing.Resolver
8 import com.google.devtools.ksp.processing.SymbolProcessor
9 import com.google.devtools.ksp.symbol.FunctionKind
10 import com.google.devtools.ksp.symbol.KSAnnotated
11 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
12 import com.google.devtools.ksp.symbol.KSType
13 import com.google.devtools.ksp.symbol.KSVisitorVoid
14 import com.google.devtools.ksp.symbol.Visibility
15 import com.squareup.kotlinpoet.ClassName
16 import com.squareup.kotlinpoet.FileSpec
17 import com.squareup.kotlinpoet.FunSpec
18 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
19 import com.squareup.kotlinpoet.TypeSpec
20 import com.squareup.kotlinpoet.ksp.toClassName
21 import expo.modules.annotation.Config
22 import expo.modules.annotation.ConverterBinder
23 import java.io.OutputStreamWriter
24 import java.nio.charset.StandardCharsets
25 
26 class ExpoSymbolProcessor(
27   private val codeGenerator: CodeGenerator,
28   private val logger: KSPLogger,
29 ) : SymbolProcessor {
30   override fun process(resolver: Resolver): List<KSAnnotated> {
31     val symbols = resolver
32       .getSymbolsWithAnnotation(ConverterBinder::class.java.name)
33       .filterIsInstance<KSFunctionDeclaration>()
34 
35     if (symbols.iterator().hasNext().not()) {
36       return emptyList()
37     }
38 
39     symbols.forEach { symbol ->
40       val clazz = symbol.annotations.find {
41         it.shortName.asString() == ConverterBinder::class.java.simpleName
42       }!!.arguments[0].value as KSType
43 
44       val parsedClazz = if (clazz.toString() == "Void") {
45         null
46       } else {
47         clazz.toClassName()
48       }
49 
50       symbol.accept(ConverterBinderVisitor(parsedClazz, codeGenerator, logger), Unit)
51     }
52 
53     return emptyList()
54   }
55 }
56 
57 class ConverterBinderVisitor(
58   private val clazz: ClassName?,
59   private val codeGenerator: CodeGenerator,
60   private val logger: KSPLogger,
61 ) : KSVisitorVoid() {
62 
visitFunctionDeclarationnull63   override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) {
64     if (function.functionKind != FunctionKind.TOP_LEVEL ||
65       function.getVisibility() != Visibility.PUBLIC
66     ) {
67       logger.error("ConverterBinder has to be a public top-level function", function)
68       return
69     }
70 
71     val resolvedType = resolveConverterType(function) ?: return
72     val shouldReceiveType = shouldReceiveType(function) ?: return
73 
74     val packageName = "${Config.packageNamePrefix}${resolvedType.packageName}"
75     val className = "${resolvedType.simpleName}${Config.classNameSuffix}"
76 
77     val content = generateConverterProvider(
78       packageName,
79       className,
80       function,
81       resolvedType,
82       shouldReceiveType
83     )
84 
85     logger.info("Generating: $packageName.$className")
86 
87     val file = codeGenerator.createNewFile(
88       createFileDependencies(function),
89       packageName,
90       className
91     )
92     OutputStreamWriter(file, StandardCharsets.UTF_8).use {
93       content.writeTo(it)
94     }
95   }
96 
shouldReceiveTypenull97   private fun shouldReceiveType(function: KSFunctionDeclaration): Boolean? {
98     val argsNumber = function.parameters.size
99     if (argsNumber != 0 && argsNumber != 1) {
100       logger.error("ConverterBinder cannot receive more then one argument", function)
101       return null
102     }
103 
104     return function.parameters.firstOrNull()?.type?.toString() == "KType"
105   }
106 
createFileDependenciesnull107   private fun createFileDependencies(function: KSFunctionDeclaration): Dependencies {
108     val containingFile = function.containingFile
109     return if (containingFile == null) {
110       Dependencies(false)
111     } else {
112       Dependencies(false, containingFile)
113     }
114   }
115 
resolveConverterTypenull116   private fun resolveConverterType(function: KSFunctionDeclaration): ClassName? {
117     if (clazz != null) {
118       return clazz
119     }
120 
121     val returnType = function.returnType?.resolve()
122     if (returnType == null) {
123       logger.error("Cannot resolve return type", function)
124       return null
125     }
126 
127     if (returnType.arguments.size != 1) {
128       logger.error("Incorrect return type", function)
129       return null
130     }
131 
132     val type = returnType.arguments.first().type?.resolve()
133     if (type == null) {
134       logger.error("Cannot resolve converter inner type", function)
135       return null
136     }
137 
138     return type.toClassName()
139   }
140 
generateConverterProvidernull141   private fun generateConverterProvider(
142     packageName: String,
143     className: String,
144     function: KSFunctionDeclaration,
145     forType: ClassName,
146     receivesType: Boolean
147   ): FileSpec {
148     val typeConverter = ClassName("expo.modules.kotlin.types", "TypeConverter")
149       .parameterizedBy(forType)
150     val kType = ClassName("kotlin.reflect", "KType")
151 
152     return FileSpec
153       .builder(packageName, className)
154       .addType(
155         TypeSpec
156           .classBuilder(className)
157           .addFunction(
158             FunSpec
159               .builder(Config.converterProviderFunctionName)
160               .addParameter("type", kType)
161               .returns(typeConverter)
162               .apply {
163                 if (receivesType) {
164                   addStatement("return ${function.packageName.asString()}.${function.simpleName.asString()}(type)")
165                 } else {
166                   addStatement("return ${function.packageName.asString()}.${function.simpleName.asString()}()")
167                 }
168               }
169               .build()
170           )
171           .build()
172       )
173       .build()
174   }
175 }
176