From 71cd66fca2a2c484faac07835d40f16e9c8dfdcd Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Thu, 14 Mar 2024 15:58:54 -0400 Subject: [PATCH 1/3] Implement Resolver.getSubpackagesOf() Resolves #1795 --- .../devtools/ksp/processing/Resolver.kt | 9 ++++++++ .../ksp/processing/impl/ResolverImpl.kt | 17 ++++++++++++++ .../devtools/ksp/impl/ResolverAAImpl.kt | 22 +++++++++++++++++++ 3 files changed, 48 insertions(+) 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 33d0f162b6..23b720d344 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 @@ -238,6 +238,15 @@ interface Resolver { @KspExperimental fun getDeclarationsFromPackage(packageName: String): Sequence + /** + * Returns a sequence of subpackage names of the given [packageName]. + * @param packageName the root package name to search from. + * @return A sequence of subpackage names, if any. Does not include the input package name. + * This will return declarations from both dependencies and source. + */ + @KspExperimental + fun getSubpackagesOf(packageName: String): Sequence + /** * Returns the corresponding Kotlin class with the given Java class. * 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 bff3a89699..b3439d24bc 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 @@ -95,6 +95,7 @@ import org.jetbrains.kotlin.resolve.constants.ConstantValue import org.jetbrains.kotlin.resolve.constants.KClassValue import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator import org.jetbrains.kotlin.resolve.descriptorUtil.classId +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.descriptorUtil.propertyIfAccessor @@ -1013,6 +1014,22 @@ class ResolverImpl( .plus(javaPackageToClassMap.getOrDefault(packageName, emptyList()).asSequence()) } + @KspExperimental + override fun getSubpackagesOf(packageName: String): Sequence { + fun PackageViewDescriptor.subPackages(): Sequence = memberScope + .getContributedDescriptors(DescriptorKindFilter.PACKAGES) + .asSequence() + .filterIsInstance() + + return generateSequence(listOf(module.getPackage(FqName(packageName)))) { subPackages -> + subPackages + .flatMap { it.subPackages() } + .ifEmpty { null } + } + .flatMap { it.asSequence() } + .mapNotNull { it.fqNameOrNull()?.asString() } + } + override fun getTypeArgument(typeRef: KSTypeReference, variance: Variance): KSTypeArgument { return KSTypeArgumentLiteImpl.getCached(typeRef, variance) } diff --git a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt index 10c7da4df8..980ecbded5 100644 --- a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt +++ b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt @@ -55,6 +55,7 @@ import org.jetbrains.kotlin.analysis.api.symbols.KtEnumEntrySymbol import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtJavaFieldSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtPackageSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtPropertyAccessorSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol @@ -346,6 +347,27 @@ class ResolverAAImpl( } } + @KspExperimental + override fun getSubpackagesOf(packageName: String): Sequence { + return analyze { + fun KtPackageSymbol.subPackages(): Sequence = getPackageScope() + .getPackageSymbols() + .distinct() + + val packageNames = FqName(packageName).pathSegments().map { it.asString() } + var packages = listOf(analysisSession.ROOT_PACKAGE_SYMBOL) + for (curName in packageNames) { + packages = packages + .flatMap { it.getPackageScope().getPackageSymbols { it.asString() == curName } } + .distinct() + } + packages + .asSequence() + .flatMap { it.subPackages() } + .map { it.fqName.asString() } + } + } + override fun getDeclarationsInSourceOrder(container: KSDeclarationContainer): Sequence { if (container.origin != Origin.KOTLIN_LIB) { return container.declarations From f02ed44f4136e61be98fc4bbb330a77afec5e51e Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Fri, 15 Mar 2024 16:20:20 -0400 Subject: [PATCH 2/3] Implement tests --- .../ksp/processing/impl/ResolverImpl.kt | 3 + .../devtools/ksp/impl/ResolverAAImpl.kt | 19 ++-- .../testData/getSubPackages.kt | 105 ++++++++++++++++++ .../ksp/processor/GetSubPackagesProcessor.kt | 28 +++++ .../com/google/devtools/ksp/test/KSPAATest.kt | 6 + .../ksp/test/KSPCompilerPluginTest.kt | 7 ++ test-utils/testData/api/getSubPackages.kt | 104 +++++++++++++++++ 7 files changed, 262 insertions(+), 10 deletions(-) create mode 100644 kotlin-analysis-api/testData/getSubPackages.kt create mode 100644 test-utils/src/main/kotlin/com/google/devtools/ksp/processor/GetSubPackagesProcessor.kt create mode 100644 test-utils/testData/api/getSubPackages.kt 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 b3439d24bc..d64a1787b4 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 @@ -1020,6 +1020,7 @@ class ResolverImpl( .getContributedDescriptors(DescriptorKindFilter.PACKAGES) .asSequence() .filterIsInstance() + .filterNot { it == this@subPackages } return generateSequence(listOf(module.getPackage(FqName(packageName)))) { subPackages -> subPackages @@ -1027,6 +1028,8 @@ class ResolverImpl( .ifEmpty { null } } .flatMap { it.asSequence() } + // Drop the input package + .drop(1) .mapNotNull { it.fqNameOrNull()?.asString() } } diff --git a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt index 980ecbded5..cb7301d93d 100644 --- a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt +++ b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt @@ -354,16 +354,15 @@ class ResolverAAImpl( .getPackageSymbols() .distinct() - val packageNames = FqName(packageName).pathSegments().map { it.asString() } - var packages = listOf(analysisSession.ROOT_PACKAGE_SYMBOL) - for (curName in packageNames) { - packages = packages - .flatMap { it.getPackageScope().getPackageSymbols { it.asString() == curName } } - .distinct() - } - packages - .asSequence() - .flatMap { it.subPackages() } + val packages = listOfNotNull(analysisSession.getPackageSymbolIfPackageExists(FqName(packageName))) + generateSequence(packages) { subPackages -> + subPackages + .flatMap { it.subPackages() } + .ifEmpty { null } + } + .flatMap { it.asSequence() } + // Drop the input package + .drop(1) .map { it.fqName.asString() } } } diff --git a/kotlin-analysis-api/testData/getSubPackages.kt b/kotlin-analysis-api/testData/getSubPackages.kt new file mode 100644 index 0000000000..5d36b51735 --- /dev/null +++ b/kotlin-analysis-api/testData/getSubPackages.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2010-2021 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. + */ + +// TEST PROCESSOR: GetSubPackagesProcessor +// EXPECTED: +// subpackages of lib1 +// subpackages of lib2 +// subpackages of main +// main.test +// main.test.nested +// main.test.main +// main.test.main.test +// main.test.main.test.nested +// subpackages of non.exist +// END + +// MODULE: lib1 +// FILE: foo.kt +package lib1 + +class Foo + +fun funcFoo(): Int { + return 1 +} + +// FILE: Bar.java +package lib1; + +class Bar {} + +// MODULE: lib2 +// FILE: foo.kt +package lib2 + +class Foo + +typealias FooTypeAlias = Foo + +val a = 0 + +// FILE: Bar.java + +class Bar {} + +// MODULE: main(lib1, lib2) +// FILE: a.kt +package lib1 +class FooInSource + +val propInSource = 1 +// FILE: main.kt +package main.test + +class KotlinMain + +// FILE: main/test/C.java +package main.test; + +public class C { + +} + +class D { + +} + +// FILE: wrongDir/K.java +package main; + +public class K { + +} + +class KK {} + + +// FILE: main/test/main/test/L.java +package main.test; + +public class L { + +} + + +// FILE: main/test/main/test/nested/M.java +package main.test.nested; + +public class M { + +} diff --git a/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/GetSubPackagesProcessor.kt b/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/GetSubPackagesProcessor.kt new file mode 100644 index 0000000000..c58025bec6 --- /dev/null +++ b/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/GetSubPackagesProcessor.kt @@ -0,0 +1,28 @@ +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 + +class GetSubPackagesProcessor : AbstractTestProcessor() { + val results = mutableListOf() + + override fun toResult(): List { + return results + } + + @OptIn(KspExperimental::class) + override fun process(resolver: Resolver): List { + addPackage("lib1", resolver) + addPackage("lib2", resolver) + addPackage("main", resolver) + addPackage("non.exist", resolver) + return emptyList() + } + + @KspExperimental + private fun addPackage(name: String, resolver: Resolver) { + results.add("subpackages of $name") + results += resolver.getSubpackagesOf(name).toList() + } +} diff --git a/test-utils/src/test/kotlin/com/google/devtools/ksp/test/KSPAATest.kt b/test-utils/src/test/kotlin/com/google/devtools/ksp/test/KSPAATest.kt index f5ba4e124c..89ed7bb2c4 100644 --- a/test-utils/src/test/kotlin/com/google/devtools/ksp/test/KSPAATest.kt +++ b/test-utils/src/test/kotlin/com/google/devtools/ksp/test/KSPAATest.kt @@ -265,6 +265,12 @@ class KSPAATest : AbstractKSPAATest() { runTest("../kotlin-analysis-api/testData/getPackage.kt") } + @TestMetadata("getSubPackages.kt") + @Test + fun testGetSubPackages() { + runTest("../kotlin-analysis-api/testData/getSubPackages.kt") + } + @TestMetadata("getByName.kt") @Test fun testGetByName() { diff --git a/test-utils/src/test/kotlin/com/google/devtools/ksp/test/KSPCompilerPluginTest.kt b/test-utils/src/test/kotlin/com/google/devtools/ksp/test/KSPCompilerPluginTest.kt index 706db0d2fc..78950be461 100644 --- a/test-utils/src/test/kotlin/com/google/devtools/ksp/test/KSPCompilerPluginTest.kt +++ b/test-utils/src/test/kotlin/com/google/devtools/ksp/test/KSPCompilerPluginTest.kt @@ -255,6 +255,13 @@ class KSPCompilerPluginTest : AbstractKSPCompilerPluginTest() { runTest("../test-utils/testData/api/getPackage.kt") } + @DisabledOnOs(OS.WINDOWS) + @TestMetadata("getSubPackages.kt") + @Test + fun testGetSubPackages() { + runTest("../test-utils/testData/api/getSubPackages.kt") + } + @TestMetadata("getByName.kt") @Test fun testGetByName() { diff --git a/test-utils/testData/api/getSubPackages.kt b/test-utils/testData/api/getSubPackages.kt new file mode 100644 index 0000000000..3b7feeef0b --- /dev/null +++ b/test-utils/testData/api/getSubPackages.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2010-2021 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. + */ + +// TEST PROCESSOR: GetSubPackagesProcessor +// EXPECTED: +// subpackages of lib1 +// subpackages of lib2 +// subpackages of main +// main.test +// main.test.main +// main.test.main.test +// main.test.main.test.nested +// subpackages of non.exist +// END + +// MODULE: lib1 +// FILE: foo.kt +package lib1 + +class Foo + +fun funcFoo(): Int { + return 1 +} + +// FILE: Bar.java +package lib1; + +class Bar {} + +// MODULE: lib2 +// FILE: foo.kt +package lib2 + +class Foo + +typealias FooTypeAlias = Foo + +val a = 0 + +// FILE: Bar.java + +class Bar {} + +// MODULE: main(lib1, lib2) +// FILE: a.kt +package lib1 +class FooInSource + +val propInSource = 1 +// FILE: main.kt +package main.test + +class KotlinMain + +// FILE: main/test/C.java +package main.test; + +public class C { + +} + +class D { + +} + +// FILE: wrongDir/K.java +package main; + +public class K { + +} + +class KK {} + + +// FILE: main/test/main/test/L.java +package main.test; + +public class L { + +} + + +// FILE: main/test/main/test/nested/M.java +package main.test.nested; + +public class M { + +} From 8689773650f9295f9ff92f3b7e1f1e218376f058 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Fri, 15 Mar 2024 21:27:48 -0400 Subject: [PATCH 3/3] Update API --- api/api.base | 1 + 1 file changed, 1 insertion(+) diff --git a/api/api.base b/api/api.base index d5e0890062..e19e6a43c9 100644 --- a/api/api.base +++ b/api/api.base @@ -176,6 +176,7 @@ package com.google.devtools.ksp.processing { 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 @com.google.devtools.ksp.KspExperimental public kotlin.sequences.Sequence getSubpackagesOf(@NonNull String packageName); 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); method @com.google.devtools.ksp.KspExperimental public boolean isJavaRawType(@NonNull com.google.devtools.ksp.symbol.KSType type);