blob: 3b6161800777e733740832f387cc90b56b3b2e0e [file] [log] [blame]
/*
* Copyright 2020 Google LLC
* Copyright 2010-2020 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.
*/
package com.google.devtools.ksp
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.*
import com.google.devtools.ksp.visitor.KSValidateVisitor
/**
* Try to resolve the [KSClassDeclaration] for a class using its fully qualified name.
*
* @param T The class to resolve a [KSClassDeclaration] for.
* @return Resolved [KSClassDeclaration] if found, `null` otherwise.
*
* @see [Resolver.getClassDeclarationByName]
*/
inline fun <reified T> Resolver.getClassDeclarationByName(): KSClassDeclaration? {
return T::class.qualifiedName?.let { fqcn ->
getClassDeclarationByName(getKSNameFromString(fqcn))
}
}
/**
* Find a class in the compilation classpath for the given name.
*
* @param name fully qualified name of the class to be loaded; using '.' as separator.
* @return a KSClassDeclaration, or null if not found.
*/
fun Resolver.getClassDeclarationByName(name: String): KSClassDeclaration? = getClassDeclarationByName(getKSNameFromString(name))
/**
* Get functions directly declared inside the class declaration.
*
* What are included: member functions, extension functions declared inside it, etc.
* What are NOT included: inherited functions, extension functions declared outside it.
*/
fun KSClassDeclaration.getDeclaredFunctions(): List<KSFunctionDeclaration> {
return this.declarations.filterIsInstance<KSFunctionDeclaration>()
}
/**
* Get properties directly declared inside the class declaration.
*
* What are included: member properties, extension properties declared inside it, etc.
* What are NOT included: inherited properties, extension properties declared outside it.
*/
fun KSClassDeclaration.getDeclaredProperties(): List<KSPropertyDeclaration> {
return this.declarations.filterIsInstance<KSPropertyDeclaration>()
}
fun KSClassDeclaration.getConstructors(): List<KSFunctionDeclaration> {
val functions = if (this.origin == Origin.JAVA) this.getAllFunctions() else this.getDeclaredFunctions()
return functions.filter { it.simpleName.asString() == this.simpleName.asString() || it.simpleName.asString() == "<init>"}
.let { constructors ->
this.primaryConstructor?.let { constructors.plus(it) } ?: constructors
}
}
/**
* Check whether this is a local declaration, or namely, declared in a function.
*/
fun KSDeclaration.isLocal(): Boolean {
return this.parentDeclaration != null && this.parentDeclaration !is KSClassDeclaration
}
/**
* Perform a validation on a given symbol to check if all interested types in symbols enclosed scope are valid, i.e. resolvable.
* @param predicate: A lambda for filtering interested symbols for performance purpose. Default checks all.
*/
fun KSNode.validate(predicate: (KSNode, KSNode) -> Boolean = { _, _-> true } ): Boolean {
return this.accept(KSValidateVisitor(predicate), Unit)
}
/**
* Find the KSClassDeclaration that the alias points to recursively.
*/
fun KSTypeAlias.findActualType(): KSClassDeclaration {
val resolvedType = this.type.resolve().declaration
return if (resolvedType is KSTypeAlias) {
resolvedType.findActualType()
} else {
resolvedType as KSClassDeclaration
}
}
/**
* Determine [Visibility] of a [KSDeclaration].
*/
fun KSDeclaration.getVisibility(): Visibility {
return when {
this.modifiers.contains(Modifier.OVERRIDE) -> {
when (this) {
is KSFunctionDeclaration -> this.findOverridee()?.getVisibility()
is KSPropertyDeclaration -> this.findOverridee()?.getVisibility()
else -> null
} ?: Visibility.PUBLIC
}
this.isLocal() -> Visibility.LOCAL
this.modifiers.contains(Modifier.PRIVATE) -> Visibility.PRIVATE
this.modifiers.contains(Modifier.PROTECTED) || this.modifiers.contains(Modifier.OVERRIDE) -> Visibility.PROTECTED
this.modifiers.contains(Modifier.INTERNAL) -> Visibility.INTERNAL
this.modifiers.contains(Modifier.PUBLIC) -> Visibility.PUBLIC
else -> if (this.origin != Origin.JAVA) Visibility.PUBLIC else Visibility.JAVA_PACKAGE
}
}
/**
* get all super types for a class declaration
* Calling [getAllSuperTypes] requires type resolution therefore is expensive and should be avoided if possible.
*/
fun KSClassDeclaration.getAllSuperTypes(): Sequence<KSType> {
fun KSTypeParameter.getTypesUpperBound(): Sequence<KSClassDeclaration> =
this.bounds.asSequence().flatMap {
when (val resolvedDeclaration = it.resolve().declaration) {
is KSClassDeclaration -> sequenceOf(resolvedDeclaration)
is KSTypeAlias -> sequenceOf(resolvedDeclaration.findActualType())
is KSTypeParameter -> resolvedDeclaration.getTypesUpperBound()
else -> throw IllegalStateException("unhandled type parameter bound, $ExceptionMessage")
}
}
return this.superTypes
.asSequence()
.map { it.resolve() }
.plus(
this.superTypes
.asSequence()
.mapNotNull { it.resolve().declaration }
.flatMap {
when (it) {
is KSClassDeclaration -> it.getAllSuperTypes()
is KSTypeAlias -> it.findActualType().getAllSuperTypes()
is KSTypeParameter -> it.getTypesUpperBound().flatMap { it.getAllSuperTypes() }
else -> throw IllegalStateException("unhandled super type kind, $ExceptionMessage")
}
}
)
.distinct()
}
fun KSClassDeclaration.isAbstract() = this.classKind == ClassKind.INTERFACE || this.modifiers.contains(Modifier.ABSTRACT)
fun KSPropertyDeclaration.isAbstract() = this.modifiers.contains(Modifier.ABSTRACT)
|| ((this.parentDeclaration as? KSClassDeclaration)?.classKind == ClassKind.INTERFACE && this.getter == null && this.setter == null)
fun KSDeclaration.isOpen() = !this.isLocal()
&& ((this as? KSClassDeclaration)?.classKind == ClassKind.INTERFACE
|| this.modifiers.contains(Modifier.OVERRIDE)
|| this.modifiers.contains(Modifier.ABSTRACT)
|| this.modifiers.contains(Modifier.OPEN)
|| (this.parentDeclaration as? KSClassDeclaration)?.classKind == ClassKind.INTERFACE
|| (!this.modifiers.contains(Modifier.FINAL) && this.origin == Origin.JAVA)
)
fun KSDeclaration.isPublic() = this.getVisibility() == Visibility.PUBLIC
fun KSDeclaration.isProtected() = this.getVisibility() == Visibility.PROTECTED
fun KSDeclaration.isInternal() = this.modifiers.contains(Modifier.INTERNAL)
fun KSDeclaration.isPrivate() = this.modifiers.contains(Modifier.PRIVATE)
fun KSDeclaration.isJavaPackagePrivate() = this.getVisibility() == Visibility.JAVA_PACKAGE
fun KSDeclaration.closestClassDeclaration(): KSClassDeclaration? {
if (this is KSClassDeclaration) {
return this
} else {
return this.parentDeclaration?.closestClassDeclaration()
}
}
fun KSAnnotated.findAnnotationFromUseSiteTarget(): Collection<KSAnnotation> {
return when (this) {
is KSPropertyGetter -> this.receiver.annotations.filter { it.useSiteTarget == AnnotationUseSiteTarget.GET }
is KSPropertySetter -> this.receiver.annotations.filter { it.useSiteTarget == AnnotationUseSiteTarget.SET }
else -> emptyList()
}
}
// TODO: cross module visibility is not handled
fun KSDeclaration.isVisibleFrom(other: KSDeclaration): Boolean {
fun KSDeclaration.isSamePackage(other: KSDeclaration): Boolean = this.containingFile?.packageName == other.containingFile?.packageName
// lexical scope for local declaration.
fun KSDeclaration.parentDeclarationsForLocal(): List<KSDeclaration> {
val parents = mutableListOf<KSDeclaration>()
var parentDeclaration = this.parentDeclaration!!
while (parentDeclaration.isLocal()) {
parents.add(parentDeclaration)
parentDeclaration = parentDeclaration.parentDeclaration!!
}
parents.add(parentDeclaration)
return parents
}
fun KSDeclaration.isVisibleInPrivate(other: KSDeclaration) =
(other.isLocal() && other.parentDeclarationsForLocal().contains(this.parentDeclaration))
|| this.parentDeclaration == other.parentDeclaration
|| this.parentDeclaration == other
|| (
this.parentDeclaration == null
&& other.parentDeclaration == null
&& this.containingFile == other.containingFile
)
return when {
// locals are limited to lexical scope
this.isLocal() -> this.parentDeclarationsForLocal().contains(other)
// file visibility or member
// TODO: address nested class.
this.isPrivate() -> this.isVisibleInPrivate(other)
this.isPublic() -> true
this.isInternal() && other.containingFile != null && this.containingFile != null -> true
// Non-private symbols in Java are always visible in same package.
this.origin == Origin.JAVA -> this.isSamePackage(other)
this.isProtected() -> this.isVisibleInPrivate(other) || other.closestClassDeclaration()?.let {
this.closestClassDeclaration()!!.asStarProjectedType().isAssignableFrom(it.asStarProjectedType())
} ?: false
else -> false
}
}
const val ExceptionMessage = "please file a bug at https://github.com/google/ksp/issues/new"