Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifierList
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypeElement
import com.intellij.psi.PsiTypeParameter
import org.jetbrains.kotlin.psi.KtAnnotatedExpression
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtDeclarationModifierList
import org.jetbrains.kotlin.psi.KtExpression
Expand All @@ -29,38 +32,80 @@ class CollectAnnotatedSymbolsPsiVisitor : PsiRecursiveElementWalkingVisitor() {
return
}
when (element) {
// For all annotated elements in Kotlin sources, we expect the structure:
// KtAnnotationEntry -> KtDeclarationModifierList / KtFileAnnotationList -> Annotated Element
is KtAnnotationEntry -> when (val parent = element.parent) {
// Annotations on declarations. Examples:
// @MyAnnotation class MyClass
// @MyAnnotation val myVal = "";
// @MyAnnotation fun myFun() {}
// fun myMethodWithParameter(@MyAnnotation myParam: Int) {}
is KtDeclarationModifierList -> {
result.add(parent.parent)
return
}

// Annotations at the file level. Example:
// @file:MyAnnotation
is KtFileAnnotationList -> {
result.add(parent.parent)
return
}

// Annotations on expressions. Example:
// val x = @Suppress("UNCHECKED_CAST") list as List<String>
is KtAnnotatedExpression ->
// Do nothing - KSP does not consider expressions / values
return

else ->
// Explicit crash
error("Unexpected Kotlin Psi element at ${parent.toLocation()}: ${parent.javaClass}")
}

// For Java sources, we expect annotated type parameters to have the structure:
// PsiAnnotation -> Annotated Element
// For all other annotated elements, we expect the structure:
// PsiAnnotation -> PsiModifierList -> Annotated Element
is PsiAnnotation -> when (val parent = element.parent) {
is PsiAnnotation -> when (val owner = element.owner) {
// Annotations on declarations. Examples:
// @MyAnnotation class MyClass {}
// @MyAnnotation String myField = "";
// @MyAnnotation void myMethod() {}
// void myMethodWithParameter(@MyAnnotation int myParam) {}
is PsiModifierList -> {
result.add(owner.parent)
return
}

// Type parameter annotations. Examples:
// class MyClass<@MyAnnotation A>
// fun <@MyAnnotation A> myFun() {}
is PsiTypeParameter -> {
result.add(parent)
result.add(owner)
return
}
is PsiModifierList -> {
result.add(parent.parent)

is PsiTypeElement -> {
result.add(owner)
return
}

// Type argument annotation / type application. Example:
// interface <A> Foo<A> {}
// abstract class Abs {
// Foo<String> bar(Foo2<@MyAnnotation Boolean> baz)
// Foo<@MyAnnotation A> baz(Foo2<A> baz)
// }
is PsiType ->
// Do nothing - Analysis API implementation does not collect these
return

// Annotations used as values. Example with other annotation used as default value
// @interface MyAnnotation {
// MyOtherAnnotation value() default @MyOtherAnnotation("default value here");
// }
null ->
// Do nothing - Analysis API implementation does not collect these
return

else ->
// Explicit crash
error("Unexpected Java Psi element at ${parent.toLocation()}: ${parent.javaClass}")
error("Unexpected Java Psi element at ${(owner as? PsiElement).toLocation()}: ${owner.javaClass}")
}
}
super.visitElement(element)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
// RGB.G
// JavaEnum.ONE
// 31
// IsEmailClass
// user@example.com
// [warning1, warning 2]
// Sub: [i:42]
// TestJavaLib: OtherAnnotation
Expand Down Expand Up @@ -83,6 +85,11 @@ public @interface JavaAnnotationWithDefaults {
OtherAnnotation otherAnnotationVal() default @OtherAnnotation("def");
}

// FILE: Email.java
public @interface Email {
String value ();
}

// FILE: KotlinAnnotationWithDefaults.kt
annotation class KotlinAnnotationWithDefaults(val otherAnnotation: OtherAnnotation = OtherAnnotation("hij"))

Expand Down Expand Up @@ -117,6 +124,11 @@ annotation class Bar(
class C {

}

// FILE: IsEmailClass.java
@Email(value = "user@example.com")
class IsEmailClass {}

// FILE: JavaAnnotated.java
@Default
@Bar(argStr = "Str",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
// RGB.G
// JavaEnum.ONE
// 31
// IsEmailClass
// user@example.com
// Throws
// Sub: [i:42]
// Cls: argToA: b
Expand Down Expand Up @@ -77,6 +79,9 @@ annotation class ClsA(val b: ClsB)
@ClsA(b = ClsB(i = 42))
class Cls

// FILE: Email.kt
annotation class Email(val value: String)

// FILE: OtherAnnotation.java
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
Expand Down Expand Up @@ -192,3 +197,7 @@ class TestNestedAnnotationDefaults {}
@JavaAnnotationWithDefaults
@KotlinAnnotationWithDefaults
class TestValueArgEquals {}

// FILE: IsEmailClass.kt
@Email(value = "user@example.com")
class IsEmailClass
20 changes: 20 additions & 0 deletions kotlin-analysis-api/testData/locations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,23 @@ class L {
}

fun @receiver:Location String.extFun() = Unit

interface <A> Foo<A>

abstract class Abs {
fun <A> bar1(baz: Foo<@Location A>): Foo<A>
fun <A> bar2(baz: Foo<A>): Foo<@Location A>
}

// FILE: J2.java

interface <A> Foo2<A> {}

abstract class Abs2 {
Foo2<String> bar1(Foo2<@Location Boolean> baz)
Foo2<@Location A> bar2(Foo2<A> baz)
}

// FILE: ExpressionAnnotation.kt

val x = @Location listOf(1, 2, 3)
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ class AnnotationArgumentProcessor : AbstractTestProcessor() {
it.annotations.forEach { it.arguments.forEach { it.accept(visitor, Unit) } }
}

resolver.getSymbolsWithAnnotation("Email").forEach {
results.add(it.toString())
it.annotations.forEach { it.arguments.forEach { it.accept(visitor, Unit) } }
}

val C = resolver.getClassDeclarationByName("C")
C?.annotations?.first()?.arguments?.forEach { results.add(it.value.toString()) }
val ThrowsClass = resolver.getClassDeclarationByName("ThrowsClass")
Expand Down
Loading