Upgrade to Pro — share decks privately, control downloads, hide ads and more …

KSPを使ってコード生成

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

 KSPを使ってコード生成

Avatar for Takuji Nishibayashi

Takuji Nishibayashi

December 05, 2023
Tweet

More Decks by Takuji Nishibayashi

Other Decks in Technology

Transcript

  1. build.gradle.kts // ... dependencies { // KSP のAPI implementation("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15") //

    KotlinPoet implementation("com.squareup:kotlinpoet:1.15.3") // KotlinPoet のKSP 用拡張 implementation("com.squareup:kotlinpoet-ksp:1.15.3") } 15
  2. SymbolProcessor class ExampleSymbolProcessor( private val codeGenerator: CodeGenerator, private val logger:

    KSPLogger ) : SymbolProcessor { override fun process(resolver: Resolver): List<KSAnnotated> { resolver .getSymbolsWithAnnotation(SimpleGeneration::class.qualifiedName!!) .filterIsInstance<KSClassDeclaration>() .forEach { it.accept(SimpleGenerationVisitor(codeGenerator, logger), Unit) } return emptyList() } } 16
  3. Visitor class SimpleGenerationVisitor( private val codeGenerator: CodeGenerator, private val logger:

    KSPLogger ) : KSVisitorVoid() { override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { if (classDeclaration.classKind != ClassKind.INTERFACE) { logger.error("Only interface allowed", classDeclaration) return } val packageName = classDeclaration.packageName.asString() val className = ClassName(packageName, "Abstract" + classDeclaration.simpleName.asString()) val typeSpec = TypeSpec.classBuilder(className) .addModifiers(KModifier.ABSTRACT) .addSuperinterface(classDeclaration.toClassName()) FileSpec.builder(packageName, className.simpleName) .addType(typeSpec.build()) .build() .writeTo( codeGenerator, Dependencies( aggregating = false, classDeclaration.containingFile!! ) ) } } 17
  4. SymbolProcessorProvider class ExampleSymbolProcessorProvider : SymbolProcessorProvider { override fun create(environment: SymbolProcessorEnvironment):

    SymbolProcessor { return ExampleSymbolProcessor(environment.codeGenerator, environment.logger) } } 18
  5. テストコード val source = SourceFile.kotlin( "ExampleClass.kt", """ package jp.takuji31.kotlinfest2022.compiler import

    jp.takuji31.kotlinfest2022.compiler.annotation.SimpleGeneration @SimpleGeneration interface SimpleInterface { fun printHelloWorld() } """.trimIndent() ) 31
  6. テストコード val compilation = KotlinCompilation().apply { sources = listOf(source) inheritClassPath

    = true symbolProcessorProviders = listOf(ExampleSymbolProcessorProvider()) kspWithCompilation = true } val result = compilation.compile() assertThat(result.exitCode) .isEqualTo(KotlinCompilation.ExitCode.OK) 32