Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3ca6821

Browse files
committedJan 19, 2021
Add the exampel project to integration tests
1 parent 0edf778 commit 3ca6821

File tree

10 files changed

+476
-0
lines changed

10 files changed

+476
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.google.devtools.ksp.test
2+
3+
import org.gradle.testkit.runner.GradleRunner
4+
import org.gradle.testkit.runner.TaskOutcome
5+
import org.junit.Assert
6+
import org.junit.Rule
7+
import org.junit.Test
8+
import java.io.File
9+
import java.util.jar.*
10+
11+
class PlaygroundIT {
12+
@Rule
13+
@JvmField
14+
val project: TemporaryTestProject = TemporaryTestProject("playground")
15+
16+
@Test
17+
fun testPlayground() {
18+
val gradleRunner = GradleRunner.create().withProjectDir(project.root)
19+
20+
val resultCleanBuild = gradleRunner.withArguments("clean", "build").build()
21+
22+
Assert.assertEquals(TaskOutcome.SUCCESS, resultCleanBuild.task(":workload:build")?.outcome)
23+
24+
val artifact = File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar")
25+
Assert.assertTrue(artifact.exists())
26+
27+
JarFile(artifact).use { jarFile ->
28+
Assert.assertTrue(jarFile.getEntry("TestProcessor.log").size > 0)
29+
Assert.assertTrue(jarFile.getEntry("HELLO.class").size > 0)
30+
Assert.assertTrue(jarFile.getEntry("com/example/AClassBuilder.class").size > 0)
31+
}
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pluginManagement {
2+
val kotlinVersion: String by settings
3+
val kspVersion: String by settings
4+
val testRepo: String by settings
5+
plugins {
6+
id("com.google.devtools.ksp") version kspVersion
7+
kotlin("jvm") version kotlinVersion
8+
}
9+
repositories {
10+
maven(testRepo)
11+
gradlePluginPortal()
12+
google()
13+
}
14+
}
15+
16+
rootProject.name = "playground"
17+
18+
include(":workload")
19+
include(":test-processor")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
val kspVersion: String by project
2+
val testRepo: String by project
3+
4+
plugins {
5+
kotlin("jvm")
6+
}
7+
8+
group = "com.example"
9+
version = "1.0-SNAPSHOT"
10+
11+
repositories {
12+
maven(testRepo)
13+
mavenCentral()
14+
google()
15+
}
16+
17+
dependencies {
18+
implementation(kotlin("stdlib"))
19+
implementation("com.squareup:javapoet:1.12.1")
20+
implementation("com.google.devtools.ksp:symbol-processing-api:$kspVersion")
21+
}
22+
23+
sourceSets.main {
24+
java.srcDirs("src/main/kotlin")
25+
}
26+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.example.annotation
2+
3+
annotation class Builder {
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
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+
11+
class TestProcessor : SymbolProcessor {
12+
lateinit var codeGenerator: CodeGenerator
13+
lateinit var file: OutputStream
14+
15+
fun emit(s: String, indent: String) {
16+
file.appendText("$indent$s\n")
17+
}
18+
19+
override fun finish() {
20+
emit("TestProcessor: finish()", "")
21+
file.close()
22+
}
23+
24+
override fun init(options: Map<String, String>, kotlinVersion: KotlinVersion, codeGenerator: CodeGenerator, logger: KSPLogger) {
25+
this.codeGenerator = codeGenerator
26+
file = codeGenerator.createNewFile(Dependencies(false), "", "TestProcessor", "log")
27+
emit("TestProcessor: init($options)", "")
28+
29+
val javaFile = codeGenerator.createNewFile(Dependencies(false), "", "Generated", "java")
30+
javaFile.appendText("class Generated {}")
31+
}
32+
33+
override fun process(resolver: Resolver) {
34+
val fileKt = codeGenerator.createNewFile(Dependencies(false), "", "HELLO", "java")
35+
fileKt.appendText("public class HELLO{\n")
36+
fileKt.appendText("public int foo() { return 1234; }\n")
37+
fileKt.appendText("}")
38+
39+
val files = resolver.getAllFiles()
40+
emit("TestProcessor: process()", "")
41+
val visitor = TestVisitor()
42+
for (file in files) {
43+
emit("TestProcessor: processing ${file.fileName}", "")
44+
file.accept(visitor, "")
45+
}
46+
}
47+
48+
inner class TestVisitor : KSVisitor<String, Unit> {
49+
50+
override fun visitReferenceElement(element: KSReferenceElement, data: String) {
51+
}
52+
53+
override fun visitModifierListOwner(modifierListOwner: KSModifierListOwner, data: String) {
54+
TODO("Not yet implemented")
55+
}
56+
57+
override fun visitNode(node: KSNode, data: String) {
58+
TODO("Not yet implemented")
59+
}
60+
61+
override fun visitPropertyAccessor(accessor: KSPropertyAccessor, data: String) {
62+
TODO("Not yet implemented")
63+
}
64+
65+
override fun visitDynamicReference(reference: KSDynamicReference, data: String) {
66+
TODO("Not yet implemented")
67+
}
68+
val visited = HashSet<Any>()
69+
70+
private fun checkVisited(symbol: Any): Boolean {
71+
return if (visited.contains(symbol)) {
72+
true
73+
} else {
74+
visited.add(symbol)
75+
false
76+
}
77+
}
78+
79+
private fun invokeCommonDeclarationApis(declaration: KSDeclaration, indent: String) {
80+
emit(
81+
"${declaration.modifiers.joinToString(" ")} ${declaration.simpleName.asString()}", indent
82+
)
83+
declaration.annotations.map { it.accept(this, "$indent ") }
84+
if (declaration.parentDeclaration != null)
85+
emit(" enclosing: ${declaration.parentDeclaration!!.qualifiedName?.asString()}", indent)
86+
declaration.containingFile?.let { emit("${it.packageName.asString()}.${it.fileName}", indent) }
87+
declaration.typeParameters.map { it.accept(this, "$indent ") }
88+
}
89+
90+
override fun visitFile(file: KSFile, data: String) {
91+
// if (!file.packageName.asString().startsWith("eu.kanade.tachiyomi.data")) {
92+
// return
93+
// }
94+
if (checkVisited(file)) return
95+
file.annotations.map{ it.accept(this, "$data ") }
96+
emit(file.packageName.asString(), data)
97+
for (declaration in file.declarations) {
98+
declaration.accept(this, data)
99+
}
100+
}
101+
102+
override fun visitAnnotation(annotation: KSAnnotation, data: String) {
103+
if (checkVisited(annotation)) return
104+
emit("annotation", data)
105+
annotation.annotationType.accept(this, "$data ")
106+
annotation.arguments.map { it.accept(this, "$data ") }
107+
}
108+
109+
override fun visitCallableReference(reference: KSCallableReference, data: String) {
110+
if (checkVisited(reference)) return
111+
emit("element: ", data)
112+
reference.functionParameters.map { it.accept(this, "$data ") }
113+
reference.receiverType?.accept(this, "$data receiver")
114+
reference.returnType.accept(this, "$data ")
115+
}
116+
117+
override fun visitPropertyGetter(getter: KSPropertyGetter, data: String) {
118+
if (checkVisited(getter)) return
119+
emit("propertyGetter: ", data)
120+
getter.annotations.map { it.accept(this, "$data ") }
121+
emit(getter.modifiers.joinToString(" "), data)
122+
getter.returnType?.accept(this, "$data ")
123+
}
124+
125+
override fun visitPropertySetter(setter: KSPropertySetter, data: String) {
126+
if (checkVisited(setter)) return
127+
emit("propertySetter: ", data)
128+
setter.annotations.map { it.accept(this, "$data ") }
129+
emit(setter.modifiers.joinToString(" "), data)
130+
// setter.parameter.accept(this, "$data ")
131+
}
132+
133+
override fun visitTypeArgument(typeArgument: KSTypeArgument, data: String) {
134+
if (checkVisited(typeArgument)) return
135+
typeArgument.annotations.map{ it.accept(this, "$data ") }
136+
emit(
137+
when (typeArgument.variance) {
138+
Variance.STAR -> "*"
139+
Variance.COVARIANT -> "out"
140+
Variance.CONTRAVARIANT -> "in"
141+
else -> ""
142+
}, data
143+
)
144+
typeArgument.type?.accept(this, "$data ")
145+
}
146+
147+
override fun visitTypeParameter(typeParameter: KSTypeParameter, data: String) {
148+
if (checkVisited(typeParameter)) return
149+
typeParameter.annotations.map{ it.accept(this, "$data ") }
150+
if (typeParameter.isReified) {
151+
emit("reified ", data)
152+
}
153+
emit(
154+
when (typeParameter.variance) {
155+
Variance.COVARIANT -> "out "
156+
Variance.CONTRAVARIANT -> "in "
157+
else -> ""
158+
} + typeParameter.name.asString(), data
159+
)
160+
if (typeParameter.bounds.isNotEmpty()) {
161+
typeParameter.bounds.map { it.accept(this, "$data ") }
162+
}
163+
}
164+
165+
override fun visitValueParameter(valueParameter: KSValueParameter, data: String) {
166+
if (checkVisited(valueParameter)) return
167+
valueParameter.annotations.map { it.accept(this, "$data ") }
168+
if (valueParameter.isVararg) {
169+
emit("vararg", "$data ")
170+
}
171+
if (valueParameter.isNoInline) {
172+
emit("noinline", "$data ")
173+
}
174+
if (valueParameter.isCrossInline) {
175+
emit("crossinline ", "$data ")
176+
}
177+
emit(valueParameter.name?.asString() ?: "_", "$data ")
178+
valueParameter.type.accept(this, "$data ")
179+
}
180+
181+
override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: String) {
182+
if (checkVisited(function)) return
183+
invokeCommonDeclarationApis(function, data)
184+
for (declaration in function.declarations) {
185+
declaration.accept(this, "$data ")
186+
}
187+
function.parameters.map { it.accept(this, "$data ") }
188+
function.typeParameters.map { it.accept(this, "$data ") }
189+
function.extensionReceiver?.accept(this, "$data extension:")
190+
emit("returnType:", data)
191+
function.returnType?.accept(this, "$data ")
192+
}
193+
194+
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: String) {
195+
if (checkVisited(classDeclaration)) return
196+
invokeCommonDeclarationApis(classDeclaration, data)
197+
emit(classDeclaration.classKind.type, data)
198+
for (declaration in classDeclaration.declarations) {
199+
declaration.accept(this, "$data ")
200+
}
201+
classDeclaration.superTypes.map { it.accept(this, "$data ") }
202+
classDeclaration.primaryConstructor?.accept(this, "$data ")
203+
}
204+
205+
override fun visitPropertyDeclaration(property: KSPropertyDeclaration, data: String) {
206+
if (checkVisited(property)) return
207+
invokeCommonDeclarationApis(property, data)
208+
property.type.accept(this, "$data ")
209+
property.extensionReceiver?.accept(this, "$data extension:")
210+
property.setter?.accept(this, "$data ")
211+
property.getter?.accept(this, "$data ")
212+
}
213+
214+
override fun visitTypeReference(typeReference: KSTypeReference, data: String) {
215+
if (checkVisited(typeReference)) return
216+
typeReference.annotations.map{ it.accept(this, "$data ") }
217+
val type = typeReference.resolve()
218+
type.let {
219+
emit("resolved to: ${it.declaration.qualifiedName?.asString()}", data)
220+
}
221+
//resolved.accept(this, "$data ")
222+
// TODO: KSTypeReferenceJavaImpl hasn't completed yet.
223+
try {
224+
typeReference.element?.accept(this, "$data ")
225+
} catch (e: IllegalStateException) {
226+
emit("TestProcessor: exception: $e", data)
227+
}
228+
}
229+
230+
override fun visitAnnotated(annotated: KSAnnotated, data: String) {
231+
}
232+
233+
override fun visitDeclaration(declaration: KSDeclaration, data: String) {
234+
}
235+
236+
override fun visitDeclarationContainer(declarationContainer: KSDeclarationContainer, data: String) {
237+
}
238+
239+
override fun visitParenthesizedReference(reference: KSParenthesizedReference, data: String) {
240+
}
241+
242+
override fun visitClassifierReference(reference: KSClassifierReference, data: String) {
243+
if (checkVisited(reference)) return
244+
if (reference.typeArguments.isNotEmpty()) {
245+
reference.typeArguments.map { it.accept(this, "$data ") }
246+
}
247+
}
248+
249+
override fun visitTypeAlias(typeAlias: KSTypeAlias, data: String) {
250+
}
251+
252+
override fun visitValueArgument(valueArgument: KSValueArgument, data: String) {
253+
if (checkVisited(valueArgument)) return
254+
val name = valueArgument.name?.asString() ?: "<no name>"
255+
emit("$name: ${valueArgument.value}", data)
256+
valueArgument.annotations.map { it.accept(this, "$data ") }
257+
}
258+
}
259+
260+
}
261+
262+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
TestProcessor
2+
BuilderProcessor
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
val testRepo: String by project
2+
3+
plugins {
4+
id("com.google.devtools.ksp")
5+
kotlin("jvm")
6+
}
7+
8+
version = "1.0-SNAPSHOT"
9+
10+
repositories {
11+
maven(testRepo)
12+
mavenCentral()
13+
google()
14+
}
15+
16+
dependencies {
17+
implementation(kotlin("stdlib"))
18+
implementation(project(":test-processor"))
19+
ksp(project(":test-processor"))
20+
}
21+
22+
ksp {
23+
arg("option1", "value1")
24+
arg("option2", "value2")
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.example
2+
3+
import HELLO
4+
5+
fun main() {
6+
val hello = HELLO()
7+
println(hello.foo())
8+
9+
val builder = AClassBuilder()
10+
builder
11+
.withA(1)
12+
.withB("foo")
13+
.withC(2.3)
14+
val aClass : AClass = builder.build()
15+
println(aClass.foo())
16+
}
17+
18+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.example
2+
3+
import com.example.annotation.Builder
4+
5+
@Builder
6+
class AClass(private val a: Int, val b: String, val c: Double) {
7+
val p = "$a, $b, $c"
8+
fun foo() = p
9+
}

0 commit comments

Comments
 (0)
Please sign in to comment.