Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Resolver.getSubpackagesOf() #1796

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions api/api.base
Expand Up @@ -176,6 +176,7 @@ package com.google.devtools.ksp.processing {
method @NonNull @com.google.devtools.ksp.KspExperimental public kotlin.sequences.Sequence<com.google.devtools.ksp.symbol.KSAnnotation> getPackageAnnotations(@NonNull String packageName);
method @NonNull @com.google.devtools.ksp.KspExperimental public kotlin.sequences.Sequence<java.lang.String> 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<java.lang.String> getSubpackagesOf(@NonNull String packageName);
method @NonNull public kotlin.sequences.Sequence<com.google.devtools.ksp.symbol.KSAnnotated> 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);
Expand Down
Expand Up @@ -238,6 +238,15 @@ interface Resolver {
@KspExperimental
fun getDeclarationsFromPackage(packageName: String): Sequence<KSDeclaration>

/**
* Returns a sequence of subpackage names of the given [packageName].
ZacSweers marked this conversation as resolved.
Show resolved Hide resolved
* @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<String>

/**
* Returns the corresponding Kotlin class with the given Java class.
*
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -1013,6 +1014,25 @@ class ResolverImpl(
.plus(javaPackageToClassMap.getOrDefault(packageName, emptyList()).asSequence())
}

@KspExperimental
override fun getSubpackagesOf(packageName: String): Sequence<String> {
fun PackageViewDescriptor.subPackages(): Sequence<PackageViewDescriptor> = memberScope
.getContributedDescriptors(DescriptorKindFilter.PACKAGES)
.asSequence()
.filterIsInstance<PackageViewDescriptor>()
.filterNot { it == this@subPackages }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I know why .filterIsInstance<PackageViewDescriptor>() and .filterNot { it == this@subPackages } are needed?


return generateSequence(listOf(module.getPackage(FqName(packageName)))) { subPackages ->
subPackages
.flatMap { it.subPackages() }
.ifEmpty { null }
}
.flatMap { it.asSequence() }
// Drop the input package
.drop(1)
.mapNotNull { it.fqNameOrNull()?.asString() }
}

override fun getTypeArgument(typeRef: KSTypeReference, variance: Variance): KSTypeArgument {
return KSTypeArgumentLiteImpl.getCached(typeRef, variance)
}
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -346,6 +347,26 @@ class ResolverAAImpl(
}
}

@KspExperimental
override fun getSubpackagesOf(packageName: String): Sequence<String> {
return analyze {
fun KtPackageSymbol.subPackages(): Sequence<KtPackageSymbol> = getPackageScope()
.getPackageSymbols()
.distinct()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needing to distinct() may indicate some issues in the underlying Analysis API. Have you tried removing distinct()? If it works then it works. If not, feel free to leave it there and we'll investigate.


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() }
}
}

override fun getDeclarationsInSourceOrder(container: KSDeclarationContainer): Sequence<KSDeclaration> {
if (container.origin != Origin.KOTLIN_LIB) {
return container.declarations
Expand Down
105 changes: 105 additions & 0 deletions kotlin-analysis-api/testData/getSubPackages.kt
@@ -0,0 +1,105 @@
/*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests under kotlin-analysis-api are forked version of those in test-utils. They are (ideally) only used when there is a behavior difference between KSP1 and KSP2. It doesn't seem to be necessary in this case.

* 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 {

}
@@ -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<String>()

override fun toResult(): List<String> {
return results
}

@OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> {
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()
}
}
Expand Up @@ -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")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please replace with: runTest("../test-utils/testData/api/getSubPackages.kt"). I.e., don't use the forked version.

}

@TestMetadata("getByName.kt")
@Test
fun testGetByName() {
Expand Down
Expand Up @@ -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() {
Expand Down
104 changes: 104 additions & 0 deletions 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please replace main.test.main[\..]* with 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace with // FILE: main/test/L.java

package main.test;

public class L {

}


// FILE: main/test/main/test/nested/M.java
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace with // FILE: main/test/nested/M.java

package main.test.nested;

public class M {

}