<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