1
+ import com.google.devtools.ksp.processing.CodeGenerator
2
+ import com.google.devtools.ksp.processing.Dependencies
3
+ import com.google.devtools.ksp.processing.KSPLogger
4
+ import com.google.devtools.ksp.processing.Resolver
5
+ import com.google.devtools.ksp.processing.SymbolProcessor
6
+ import com.google.devtools.ksp.symbol.*
7
+ import java.io.File
8
+ import java.io.OutputStream
9
+
10
+ fun OutputStream.appendText (str : String ) {
11
+ this .write(str.toByteArray())
12
+ }
13
+ class BuilderProcessor : SymbolProcessor {
14
+ lateinit var codeGenerator: CodeGenerator
15
+
16
+ override fun finish () {
17
+
18
+ }
19
+
20
+ override fun init (options : Map <String , String >, kotlinVersion : KotlinVersion , codeGenerator : CodeGenerator , logger : KSPLogger ) {
21
+ this .codeGenerator = codeGenerator
22
+ }
23
+
24
+ override fun process (resolver : Resolver ) {
25
+ val symbols = resolver.getSymbolsWithAnnotation(" com.example.annotation.Builder" )
26
+ symbols
27
+ .filter { it is KSClassDeclaration }
28
+ .map { it.accept(BuilderVisitor (), Unit ) }
29
+ }
30
+
31
+ inner class BuilderVisitor : KSVisitorVoid () {
32
+ override fun visitClassDeclaration (classDeclaration : KSClassDeclaration , data : Unit ) {
33
+ classDeclaration.primaryConstructor!! .accept(this , data)
34
+ }
35
+
36
+ override fun visitFunctionDeclaration (function : KSFunctionDeclaration , data : Unit ) {
37
+ val parent = function.parentDeclaration as KSClassDeclaration
38
+ val packageName = parent.containingFile!! .packageName.asString()
39
+ val className = " ${parent.simpleName.asString()} Builder"
40
+ val file = codeGenerator.createNewFile(Dependencies (true , function.containingFile!! ), packageName , className)
41
+ file.appendText(" package $packageName \n\n " )
42
+ file.appendText(" class $className {\n " )
43
+ function.parameters.forEach {
44
+ val name = it.name!! .asString()
45
+ val typeName = StringBuilder (it.type.resolve().declaration.qualifiedName?.asString() ? : " <ERROR>" )
46
+ val typeArgs = it.type.element!! .typeArguments
47
+ if (it.type.element!! .typeArguments.isNotEmpty()) {
48
+ typeName.append(" <" )
49
+ typeName.append(
50
+ typeArgs.map {
51
+ val type = it.type?.resolve()
52
+ " ${it.variance.label} ${type?.declaration?.qualifiedName?.asString()} " +
53
+ if (type?.nullability == Nullability .NULLABLE ) " ?" else " "
54
+ }.joinToString(" , " )
55
+ )
56
+ typeName.append(" >" )
57
+ }
58
+ file.appendText(" private var $name : $typeName ? = null\n " )
59
+ file.appendText(" internal fun with${name.capitalize()} ($name : $typeName ): $className {\n " )
60
+ file.appendText(" this.$name = $name \n " )
61
+ file.appendText(" return this\n " )
62
+ file.appendText(" }\n\n " )
63
+ }
64
+ file.appendText(" internal fun build(): ${parent.qualifiedName!! .asString()} {\n " )
65
+ file.appendText(" return ${parent.qualifiedName!! .asString()} (" )
66
+ file.appendText(
67
+ function.parameters.map {
68
+ " ${it.name!! .asString()} !!"
69
+ }.joinToString(" , " )
70
+ )
71
+ file.appendText(" )\n " )
72
+ file.appendText(" }\n " )
73
+ file.appendText(" }\n " )
74
+ file.close()
75
+ }
76
+ }
77
+
78
+ }
0 commit comments