Skip to content

Commit 4e4fd28

Browse files
committed
add symbol validation API
1 parent 3287061 commit 4e4fd28

File tree

5 files changed

+154
-0
lines changed

5 files changed

+154
-0
lines changed

api/src/main/kotlin/com/google/devtools/ksp/utils.kt

+9
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package com.google.devtools.ksp
2020

2121
import com.google.devtools.ksp.processing.Resolver
2222
import com.google.devtools.ksp.symbol.*
23+
import com.google.devtools.ksp.visitor.KSValidateVisitor
2324

2425
/**
2526
* Try to resolve the [KSClassDeclaration] for a class using its fully qualified name.
@@ -78,6 +79,14 @@ fun KSDeclaration.isLocal(): Boolean {
7879
return this.parentDeclaration != null && this.parentDeclaration !is KSClassDeclaration
7980
}
8081

82+
/**
83+
* Perform a validation on a given symbol to check if all interested types in symbols enclosed scope are valid, i.e. resolvable.
84+
* @param predict: A lambda for filtering interested symbols for performance purpose. Default checks all.
85+
*/
86+
fun KSNode.validate(predict: (KSNode, KSNode) -> Boolean = { _, _-> true } ): Boolean {
87+
return this.accept(KSValidateVisitor(predict), Unit)
88+
}
89+
8190
/**
8291
* Find the KSClassDeclaration that the alias points to recursively.
8392
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.google.devtools.ksp.visitor
2+
3+
import com.google.devtools.ksp.symbol.*
4+
5+
class KSValidateVisitor(private val predict: (KSNode, KSNode) -> Boolean) : KSDefaultVisitor<Unit, Boolean>() {
6+
private fun validateDeclarations(declarationContainer: KSDeclarationContainer): Boolean {
7+
return !declarationContainer.declarations.any { predict(declarationContainer, it) && !it.accept(this, Unit) }
8+
}
9+
10+
private fun validateTypeParameters(declaration: KSDeclaration): Boolean {
11+
return !declaration.typeParameters.any { predict(declaration, it) && !it.accept(this, Unit) }
12+
}
13+
14+
private fun validateType(type: KSType): Boolean {
15+
return !type.isError && !type.arguments.any { it.type?.accept(this, Unit) == false }
16+
}
17+
18+
override fun defaultHandler(node: KSNode, data: Unit): Boolean {
19+
throw IllegalStateException("unhandled validation condition, please file a bug at https://github.com/google/ksp/issues/new")
20+
}
21+
22+
override fun visitTypeReference(typeReference: KSTypeReference, data: Unit): Boolean {
23+
return validateType(typeReference.resolve())
24+
}
25+
26+
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit): Boolean {
27+
if (!validateTypeParameters(classDeclaration)) {
28+
return false
29+
}
30+
if (classDeclaration.asStarProjectedType().isError) {
31+
return false
32+
}
33+
if (classDeclaration.superTypes.any { predict(classDeclaration, it) && !it.accept(this, Unit) }) {
34+
return false
35+
}
36+
if (!validateDeclarations(classDeclaration)) {
37+
return false
38+
}
39+
return true
40+
}
41+
42+
override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit): Boolean {
43+
if (function.returnType?.accept(this, data) != true) {
44+
return false
45+
}
46+
if (function.parameters.any { predict(function, it) && !it.accept(this, Unit)}) {
47+
return false
48+
}
49+
if (!validateTypeParameters(function)) {
50+
return false
51+
}
52+
if (!validateDeclarations(function)) {
53+
return false
54+
}
55+
return true
56+
}
57+
58+
override fun visitPropertyDeclaration(property: KSPropertyDeclaration, data: Unit): Boolean {
59+
if (property.type.resolve().isError) {
60+
return false
61+
}
62+
if (!validateTypeParameters(property)) {
63+
return false
64+
}
65+
return true
66+
}
67+
68+
override fun visitTypeParameter(typeParameter: KSTypeParameter, data: Unit): Boolean {
69+
if (typeParameter.bounds.any { predict(typeParameter, it) && !it.accept(this, Unit) }) {
70+
return false
71+
}
72+
return true
73+
}
74+
75+
override fun visitValueParameter(valueParameter: KSValueParameter, data: Unit): Boolean {
76+
return valueParameter.type?.accept(this, Unit) != false
77+
}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.google.devtools.ksp.processor
2+
3+
import com.google.devtools.ksp.getClassDeclarationByName
4+
import com.google.devtools.ksp.processing.Resolver
5+
import com.google.devtools.ksp.symbol.KSDeclaration
6+
import com.google.devtools.ksp.validate
7+
8+
class ValidateProcessor : AbstractTestProcessor() {
9+
val results = mutableListOf<String>()
10+
11+
private fun validate(symbol: KSDeclaration) {
12+
if (symbol.validate()) {
13+
results.add("${symbol.simpleName.asString()} valid")
14+
} else {
15+
results.add("${symbol.simpleName.asString()} invalid")
16+
}
17+
}
18+
override fun toResult(): List<String> = results
19+
20+
override fun process(resolver: Resolver) {
21+
val ErrorInMember = resolver.getClassDeclarationByName("ErrorInMember")!!
22+
val GoodClass = resolver.getClassDeclarationByName("GoodClass")!!
23+
val C = resolver.getClassDeclarationByName("C")!!
24+
val BadJavaClass = resolver.getClassDeclarationByName("BadJavaClass")!!
25+
validate(ErrorInMember)
26+
ErrorInMember.declarations.map { validate(it) }
27+
validate(GoodClass)
28+
validate(C)
29+
validate(BadJavaClass)
30+
}
31+
}

compiler-plugin/src/test/java/com/google/devtools/ksp/test/KotlinKSPTestGenerated.java

+5
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ public void testVarianceTypeCheck() throws Exception {
176176
runTest("testData/api/varianceTypeCheck.kt");
177177
}
178178

179+
@TestMetadata("validateTypes.kt")
180+
public void testValidateTypes() throws Exception {
181+
runTest("testData/api/validateTypes.kt");
182+
}
183+
179184
@TestMetadata("visibilities.kt")
180185
public void testVisibilities() throws Exception {
181186
runTest("testData/api/visibilities.kt");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// WITH_RUNTIME
2+
// TEST PROCESSOR: ValidateProcessor
3+
// EXPECTED:
4+
// ErrorInMember invalid
5+
// goodProp valid
6+
// errorFun invalid
7+
// GoodClass valid
8+
// C valid
9+
// BadJavaClass invalid
10+
// END
11+
// FILE: a.kt
12+
class ErrorInMember : C {
13+
val goodProp: Int
14+
fun errorFun(): NonExistType {
15+
16+
}
17+
}
18+
19+
open class GoodClass {
20+
val a: Int
21+
22+
fun foo(): Int = 1
23+
}
24+
25+
// FILE: C.java
26+
27+
public class C extends GoodClass {}
28+
29+
class BadJavaClass extends NonExistType {
30+
31+
}

0 commit comments

Comments
 (0)