From d7c6ff4858f838605cc81fc3bde0afb945e14e0f Mon Sep 17 00:00:00 2001 From: Jiaxiang Chen Date: Thu, 6 Jul 2023 11:39:26 -0700 Subject: [PATCH] Add Resolver.getPackageAnnotations Resolver.getPackagesWithAnnotation api. These new APIs provides capability to inspect java package level annotations. (cherry picked from commit 4f4fbe81fe21b7985c416ecdc123c8ff3771636c) --- api/api.base | 2 + .../devtools/ksp/processing/Resolver.kt | 18 +++++++++ .../ksp/processing/impl/ResolverImpl.kt | 23 ++++++++++- .../google/devtools/ksp/symbol/impl/utils.kt | 3 ++ .../ksp/test/KSPCompilerPluginTest.kt | 6 +++ .../processor/PackageAnnotationProcessor.kt | 34 ++++++++++++++++ test-utils/testData/api/packageAnnotations.kt | 40 +++++++++++++++++++ 7 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 test-utils/src/main/kotlin/com/google/devtools/ksp/processor/PackageAnnotationProcessor.kt create mode 100644 test-utils/testData/api/packageAnnotations.kt diff --git a/api/api.base b/api/api.base index 4b24bf249b..769eb17262 100644 --- a/api/api.base +++ b/api/api.base @@ -164,6 +164,8 @@ package com.google.devtools.ksp.processing { method @NonNull public kotlin.sequences.Sequence getNewFiles(); method @Nullable @com.google.devtools.ksp.KspExperimental public String getOwnerJvmClassName(@NonNull com.google.devtools.ksp.symbol.KSPropertyDeclaration declaration); method @Nullable @com.google.devtools.ksp.KspExperimental public String getOwnerJvmClassName(@NonNull com.google.devtools.ksp.symbol.KSFunctionDeclaration declaration); + method @NonNull @com.google.devtools.ksp.KspExperimental public kotlin.sequences.Sequence getPackageAnnotations(@NonNull String packageName); + method @NonNull @com.google.devtools.ksp.KspExperimental public kotlin.sequences.Sequence getPackagesWithAnnotation(@NonNull String annotationName); method @Nullable public com.google.devtools.ksp.symbol.KSPropertyDeclaration getPropertyDeclarationByName(@NonNull com.google.devtools.ksp.symbol.KSName name, boolean includeTopLevel = false); method @NonNull public kotlin.sequences.Sequence getSymbolsWithAnnotation(@NonNull String annotationName, boolean inDepth = false); method @NonNull public com.google.devtools.ksp.symbol.KSTypeArgument getTypeArgument(@NonNull com.google.devtools.ksp.symbol.KSTypeReference typeRef, @NonNull com.google.devtools.ksp.symbol.Variance variance); diff --git a/api/src/main/kotlin/com/google/devtools/ksp/processing/Resolver.kt b/api/src/main/kotlin/com/google/devtools/ksp/processing/Resolver.kt index 642b5c9009..33d0f162b6 100644 --- a/api/src/main/kotlin/com/google/devtools/ksp/processing/Resolver.kt +++ b/api/src/main/kotlin/com/google/devtools/ksp/processing/Resolver.kt @@ -300,4 +300,22 @@ interface Resolver { */ @KspExperimental fun isJavaRawType(type: KSType): Boolean + + /** + * Returns annotations applied in package-info.java (if applicable) for given package name. + * + * @param packageName package name to check. + * @return a sequence of KSAnnotations applied in corresponding package-info.java file. + */ + @KspExperimental + fun getPackageAnnotations(packageName: String): Sequence + + @KspExperimental + /** + * Returns name of packages with given annotation. + * + * @param annotationName name of the annotation to be queried. + * @return a sequence of package names with corresponding annotation name. + */ + fun getPackagesWithAnnotation(annotationName: String): Sequence } diff --git a/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processing/impl/ResolverImpl.kt b/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processing/impl/ResolverImpl.kt index f649946154..2fbbd7a037 100644 --- a/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processing/impl/ResolverImpl.kt +++ b/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/processing/impl/ResolverImpl.kt @@ -24,10 +24,9 @@ import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.symbol.* import com.google.devtools.ksp.symbol.ClassKind import com.google.devtools.ksp.symbol.Variance +import com.google.devtools.ksp.symbol.impl.* import com.google.devtools.ksp.symbol.impl.binary.* import com.google.devtools.ksp.symbol.impl.declarationsInSourceOrder -import com.google.devtools.ksp.symbol.impl.findParentAnnotated -import com.google.devtools.ksp.symbol.impl.findPsi import com.google.devtools.ksp.symbol.impl.getInstanceForCurrentRound import com.google.devtools.ksp.symbol.impl.java.* import com.google.devtools.ksp.symbol.impl.jvmAccessFlag @@ -132,6 +131,9 @@ class ResolverImpl( val psiDocumentManager = PsiDocumentManager.getInstance(project) private val nameToKSMap: MutableMap private val javaTypeParameterMap: MutableMap = mutableMapOf() + private val packageInfoFiles by lazy { + allKSFiles.filter { it.fileName == "package-info.java" }.asSequence().memoized() + } /** * Checking as member of is an expensive operation, hence we cache result values in this map. @@ -1448,6 +1450,23 @@ class ResolverImpl( return type is KSTypeImpl && type.kotlinType.unwrap() is RawType } + @KspExperimental + override fun getPackageAnnotations(packageName: String): Sequence { + return packageInfoFiles.singleOrNull { it.packageName.asString() == packageName } + ?.getPackageAnnotations()?.asSequence() ?: emptySequence() + } + + @KspExperimental + override fun getPackagesWithAnnotation(annotationName: String): Sequence { + return packageInfoFiles.filter { + it.getPackageAnnotations().any { + (it.annotationType.element as? KSClassifierReference)?.referencedName() + ?.substringAfterLast(".") == annotationName.substringAfterLast(".") && + it.annotationType.resolve().declaration.qualifiedName?.asString() == annotationName + } + }.map { it.packageName.asString() } + } + private val psiJavaFiles = allKSFiles.filterIsInstance().map { Pair(it.psi.virtualFile.path, it.psi) }.toMap() diff --git a/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/symbol/impl/utils.kt b/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/symbol/impl/utils.kt index f233798751..ff003e4fa8 100644 --- a/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/symbol/impl/utils.kt +++ b/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/symbol/impl/utils.kt @@ -557,3 +557,6 @@ fun DeclarationDescriptor.findPsi(): PsiElement? { val leaf = containingFile.findElementAt(psi.textOffset) ?: return null return leaf.parentsWithSelf.firstOrNull { psi.manager.areElementsEquivalent(it, psi) } } + +internal fun KSFile.getPackageAnnotations() = (this as? KSFileJavaImpl)?.psi?.packageStatement + ?.annotationList?.annotations?.map { KSAnnotationJavaImpl.getCached(it) } ?: emptyList() diff --git a/compiler-plugin/src/test/kotlin/com/google/devtools/ksp/test/KSPCompilerPluginTest.kt b/compiler-plugin/src/test/kotlin/com/google/devtools/ksp/test/KSPCompilerPluginTest.kt index dbc5d50d17..e604100edd 100644 --- a/compiler-plugin/src/test/kotlin/com/google/devtools/ksp/test/KSPCompilerPluginTest.kt +++ b/compiler-plugin/src/test/kotlin/com/google/devtools/ksp/test/KSPCompilerPluginTest.kt @@ -399,6 +399,12 @@ class KSPCompilerPluginTest : AbstractKSPCompilerPluginTest() { runTest("../test-utils/testData/api/overridee.kt") } + @TestMetadata("packageAnnotations.kt") + @Test + fun testPackageAnnotation() { + runTest("../test-utils/testData/api/packageAnnotations.kt") + } + @TestMetadata("parameterTypes.kt") @Test fun testParameterTypes() { diff --git a/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/PackageAnnotationProcessor.kt b/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/PackageAnnotationProcessor.kt new file mode 100644 index 0000000000..bc1fa3bb6c --- /dev/null +++ b/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/PackageAnnotationProcessor.kt @@ -0,0 +1,34 @@ +package com.google.devtools.ksp.processor + +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSFile +import com.google.devtools.ksp.symbol.KSVisitorVoid + +class PackageAnnotationProcessor : AbstractTestProcessor() { + val result = mutableListOf() + override fun toResult(): List { + return result + } + + @OptIn(KspExperimental::class) + override fun process(resolver: Resolver): List { + resolver.getAllFiles().forEach { + it.accept( + object : KSVisitorVoid() { + override fun visitFile(file: KSFile, data: Unit) { + result.add( + "${file.fileName}:${resolver.getPackageAnnotations(file.packageName.asString()) + .joinToString { it.toString() }}" + ) + } + }, + Unit + ) + } + result.sort() + result.add(resolver.getPackagesWithAnnotation("PackageAnnotation").joinToString()) + return emptyList() + } +} diff --git a/test-utils/testData/api/packageAnnotations.kt b/test-utils/testData/api/packageAnnotations.kt new file mode 100644 index 0000000000..b56d8309f9 --- /dev/null +++ b/test-utils/testData/api/packageAnnotations.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Google LLC + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// WITH_RUNTIME +// TEST PROCESSOR: PackageAnnotationProcessor +// EXPECTED: +// Foo.java:@PackageAnnotation +// annotations.kt: +// package-info.java:@PackageAnnotation +// example +// END + +// FILE: annotations.kt +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.PACKAGE) +annotation class PackageAnnotation + +// FILE: example/package-info.java +@PackageAnnotation +package example; + +import PackageAnnotation; + +// FILE: example/Foo.java +package example; +public class Foo