Skip to content

Commit f2e5129

Browse files
Add ci action workflow (#3)
Adds a github action for running CI, heavily inspired by yawn https://github.com/Faire/yawn/blob/main/.github/workflows/ci.yml Note: #2 was missing a lot of the test processor code, this PR includes to show CI passes
2 parents 796b6c1 + 2f56da5 commit f2e5129

19 files changed

+291
-43
lines changed

.github/workflows/ci.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-java@v4
15+
with:
16+
java-version: "21"
17+
distribution: temurin
18+
- uses: gradle/actions/setup-gradle@v4
19+
with:
20+
cache-cleanup: on-success
21+
- run: ./gradlew assemble
22+
- run: ./gradlew check
23+
- uses: dorny/test-reporter@v1
24+
if: always()
25+
with:
26+
name: Test Results
27+
path: "**/build/test-results/test/*.xml"
28+
reporter: java-junit
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.faire.ksp.test
2+
3+
import com.faire.ksp.adapters.ksClassDeclaration
4+
import com.faire.ksp.snapshot.snapshots.toSnapshot
5+
import com.faire.ksp.test.fixtures.SimpleDataClass
6+
import com.faire.ksp.test.fixtures.SimpleDataClassKspSnapshot
7+
import com.faire.ksp.test.fixtures.SimpleInterface
8+
import com.faire.ksp.test.fixtures.SimpleInterfaceKspSnapshot
9+
import org.assertj.core.api.Assertions.assertThat
10+
import org.junit.jupiter.api.Test
11+
12+
class ReflectionEquivalenceTest {
13+
14+
@Test
15+
fun `SimpleDataClass reflection adapter is equivalent to KSP`() {
16+
val kspSnapshot = SimpleDataClassKspSnapshot.snapshot
17+
val reflectionSnapshot = SimpleDataClass::class.ksClassDeclaration.toSnapshot()
18+
19+
assertThat(reflectionSnapshot).isEqualTo(kspSnapshot)
20+
}
21+
22+
@Test
23+
fun `SimpleInterface reflection adapter is equivalent to KSP`() {
24+
val kspSnapshot = SimpleInterfaceKspSnapshot.snapshot
25+
val reflectionSnapshot = SimpleInterface::class.ksClassDeclaration.toSnapshot()
26+
27+
assertThat(reflectionSnapshot).isEqualTo(kspSnapshot)
28+
}
29+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.faire.ksp.test.fixtures
2+
3+
import com.faire.ksp.snapshot.GenerateSnapshot
4+
5+
@GenerateSnapshot
6+
data class SimpleDataClass(
7+
val name: String,
8+
val age: Int,
9+
var mutableField: Boolean,
10+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.faire.ksp.test.fixtures
2+
3+
import com.faire.ksp.snapshot.GenerateSnapshot
4+
5+
@GenerateSnapshot
6+
interface SimpleInterface {
7+
val id: String
8+
fun process(input: Int): Boolean
9+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.faire.ksp.snapshot
2+
3+
import com.google.devtools.ksp.processing.Resolver
4+
import com.google.devtools.ksp.symbol.KSType
5+
import com.squareup.kotlinpoet.CodeBlock
6+
import com.squareup.kotlinpoet.asClassName
7+
import com.squareup.kotlinpoet.buildCodeBlock
8+
9+
internal class SnapshotLiteralWriter(
10+
private val resolver: Resolver
11+
) {
12+
fun toLiteralCode(value: Any): CodeBlock {
13+
val qualifiedName = value::class.qualifiedName
14+
?: error("No qualified name for ${value::class}")
15+
16+
val ksClass = resolver.getClassDeclarationByName(resolver.getKSNameFromString(qualifiedName))
17+
?: error("Cannot resolve $qualifiedName via KSP")
18+
19+
val constructor = ksClass.primaryConstructor
20+
?: error("No primary constructor for $qualifiedName")
21+
22+
val className = value::class.asClassName()
23+
24+
return buildCodeBlock {
25+
add("%T(\n", className)
26+
indent()
27+
for (param in constructor.parameters) {
28+
val paramName = param.name!!.asString()
29+
val paramType = param.type.resolve()
30+
val propValue = readProperty(value, paramName)
31+
add("$paramName = ")
32+
add(formatValue(propValue, paramType))
33+
add(",\n")
34+
}
35+
unindent()
36+
add(")")
37+
}
38+
}
39+
40+
private fun readProperty(instance: Any, name: String): Any? {
41+
val field = instance::class.java.getDeclaredField(name)
42+
field.isAccessible = true
43+
return field.get(instance)
44+
}
45+
46+
private fun formatValue(value: Any?, type: KSType): CodeBlock {
47+
if (value == null) return CodeBlock.of("null")
48+
49+
return when (type.declaration.qualifiedName?.asString()) {
50+
"kotlin.String" -> CodeBlock.of("%S", value)
51+
"kotlin.Boolean", "kotlin.Int" -> CodeBlock.of("%L", value)
52+
"kotlin.collections.List" -> {
53+
val list = value as List<*>
54+
if (list.isEmpty()) return CodeBlock.of("listOf()")
55+
val elementType = type.arguments.first().type!!.resolve()
56+
formatList(list, elementType)
57+
}
58+
59+
else -> toLiteralCode(value)
60+
}
61+
}
62+
63+
private fun formatList(list: List<*>, elementType: KSType): CodeBlock {
64+
return buildCodeBlock {
65+
add("listOf(\n")
66+
indent()
67+
for (item in list) {
68+
add(formatValue(item, elementType))
69+
add(",\n")
70+
}
71+
unindent()
72+
add(")")
73+
}
74+
}
75+
}

test-processor/src/main/kotlin/com/faire/ksp/snapshot/SnapshotProcessor.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
package com.faire.ksp.snapshot
22

3-
import com.google.devtools.ksp.processing.*
3+
import com.faire.ksp.snapshot.handlers.ClassSnapshotHandler
4+
import com.faire.ksp.snapshot.handlers.FunctionSnapshotHandler
5+
import com.faire.ksp.snapshot.handlers.PropertySnapshotHandler
6+
import com.faire.ksp.snapshot.snapshots.DeclarationSnapshot
7+
import com.faire.ksp.snapshot.snapshots.SnapshotResult
8+
import com.google.devtools.ksp.processing.CodeGenerator
9+
import com.google.devtools.ksp.processing.Dependencies
10+
import com.google.devtools.ksp.processing.Resolver
11+
import com.google.devtools.ksp.processing.SymbolProcessor
412
import com.google.devtools.ksp.symbol.KSAnnotated
513
import com.squareup.kotlinpoet.*
614
import java.io.PrintStream
15+
import javax.annotation.processing.Generated
716

8-
class SnapshotProcessor(
17+
internal class SnapshotProcessor(
918
private val codeGenerator: CodeGenerator,
1019
) : SymbolProcessor {
1120

@@ -30,19 +39,21 @@ class SnapshotProcessor(
3039
private fun generateSnapshotFile(result: SnapshotResult, writer: SnapshotLiteralWriter) {
3140
val objectName = "${result.simpleName}KspSnapshot"
3241

42+
val generatedAnnotation = AnnotationSpec.builder(Generated::class.asClassName())
43+
.addMember("%S", SnapshotProcessor::class.qualifiedName!!)
44+
.build()
3345
val typeSpec = TypeSpec.objectBuilder(objectName)
46+
.addAnnotation(generatedAnnotation)
3447
.addModifiers(KModifier.INTERNAL)
3548
.addProperty(
3649
PropertySpec.builder("snapshot", DeclarationSnapshot::class.asClassName())
37-
.addModifiers(KModifier.INTERNAL)
3850
.initializer(writer.toLiteralCode(result.snapshot))
3951
.build(),
4052
)
4153
.build()
4254

4355
val fileSpec = FileSpec.builder(result.packageName, objectName)
4456
.addType(typeSpec)
45-
.indent(" ".repeat(4))
4657
.build()
4758

4859
val outputFile = codeGenerator.createNewFile(

test-processor/src/main/kotlin/com/faire/ksp/snapshot/SnapshotProcessorProvider.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import com.google.devtools.ksp.processing.SymbolProcessor
44
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
55
import com.google.devtools.ksp.processing.SymbolProcessorProvider
66

7-
class SnapshotProcessorProvider : SymbolProcessorProvider {
7+
internal class SnapshotProcessorProvider : SymbolProcessorProvider {
88
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
99
return SnapshotProcessor(environment.codeGenerator)
1010
}

test-processor/src/main/kotlin/com/faire/ksp/snapshot/Snapshots.kt

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.faire.ksp.snapshot.handlers
2+
3+
import com.faire.ksp.snapshot.snapshots.SnapshotResult
4+
import com.faire.ksp.snapshot.snapshots.toSnapshot
5+
import com.google.devtools.ksp.symbol.KSAnnotated
6+
import com.google.devtools.ksp.symbol.KSClassDeclaration
7+
8+
internal class ClassSnapshotHandler : SnapshotHandler {
9+
override fun process(symbol: KSAnnotated): SnapshotResult? {
10+
if (symbol !is KSClassDeclaration) return null
11+
12+
val qualifiedName = symbol.qualifiedName?.asString() ?: return null
13+
14+
return SnapshotResult(
15+
qualifiedName = qualifiedName,
16+
simpleName = symbol.simpleName.asString(),
17+
packageName = symbol.packageName.asString(),
18+
snapshot = symbol.toSnapshot(),
19+
)
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.faire.ksp.snapshot.handlers
2+
3+
import com.faire.ksp.snapshot.snapshots.SnapshotResult
4+
import com.faire.ksp.snapshot.snapshots.toSnapshot
5+
import com.google.devtools.ksp.symbol.KSAnnotated
6+
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
7+
8+
internal class FunctionSnapshotHandler : SnapshotHandler {
9+
override fun process(symbol: KSAnnotated): SnapshotResult? {
10+
if (symbol !is KSFunctionDeclaration) return null
11+
12+
val qualifiedName = symbol.qualifiedName?.asString() ?: return null
13+
14+
return SnapshotResult(
15+
qualifiedName = qualifiedName,
16+
simpleName = symbol.simpleName.asString(),
17+
packageName = symbol.packageName.asString(),
18+
snapshot = symbol.toSnapshot(),
19+
)
20+
}
21+
}

0 commit comments

Comments
 (0)