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