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
+ println (symbols)
27
+ symbols
28
+ .filter { it is KSClassDeclaration }
29
+ .map { it.accept(BuilderVisitor (), Unit ) }
30
+ }
31
+
32
+ inner class BuilderVisitor : KSVisitorVoid () {
33
+ override fun visitClassDeclaration (classDeclaration : KSClassDeclaration , data : Unit ) {
34
+ classDeclaration.primaryConstructor!! .accept(this , data)
35
+ }
36
+
37
+ override fun visitFunctionDeclaration (function : KSFunctionDeclaration , data : Unit ) {
38
+ val parent = function.parentDeclaration as KSClassDeclaration
39
+ val packageName = parent.containingFile!! .packageName.asString()
40
+ val className = " ${parent.simpleName.asString()} Builder"
41
+ val file = codeGenerator.createNewFile(Dependencies (true , function.containingFile!! ), packageName , className)
42
+ file.appendText(" package $packageName \n\n " )
43
+ file.appendText(" class $className {\n " )
44
+ function.parameters.forEach {
45
+ val name = it.name!! .asString()
46
+ val typeName = StringBuilder (it.type.resolve().declaration.qualifiedName?.asString() ? : " <ERROR>" )
47
+ if (typeName.toString() == " <ERROR>" ) {
48
+ File (" /tmp/BuilderLog" ).appendText(" ${(it.name as KSName ).asString()} :${function.containingFile?.fileName} " )
49
+ }
50
+ val typeArgs = it.type.element!! .typeArguments
51
+ if (it.type.element!! .typeArguments.isNotEmpty()) {
52
+ typeName.append(" <" )
53
+ typeName.append(
54
+ typeArgs.map {
55
+ val type = it.type?.resolve()
56
+ " ${it.variance.label} ${type?.declaration?.qualifiedName?.asString()} " +
57
+ if (type?.nullability == Nullability .NULLABLE ) " ?" else " "
58
+ }.joinToString(" , " )
59
+ )
60
+ typeName.append(" >" )
61
+ }
62
+ // typeName.append(if ((it.type?.type as? KSUserType)?.isNullable == true) "?" else "")
63
+ file.appendText(" private var $name : $typeName ? = null\n " )
64
+ file.appendText(" internal fun with${name.capitalize()} ($name : $typeName ): $className {\n " )
65
+ file.appendText(" this.$name = $name \n " )
66
+ file.appendText(" return this\n " )
67
+ file.appendText(" }\n\n " )
68
+ }
69
+ file.appendText(" internal fun build(): ${parent.qualifiedName!! .asString()} {\n " )
70
+ file.appendText(" return ${parent.qualifiedName!! .asString()} (" )
71
+ file.appendText(
72
+ function.parameters.map {
73
+ " ${it.name!! .asString()} !!"
74
+ }.joinToString(" , " )
75
+ )
76
+ file.appendText(" )\n " )
77
+ file.appendText(" }\n " )
78
+ file.appendText(" }\n " )
79
+ file.close()
80
+ }
81
+ }
82
+
83
+ }
0 commit comments