Skip to content

Commit

Permalink
Implement basic tests for KSP (#1148)
Browse files Browse the repository at this point in the history
Co-authored-by: Egor Andreevich <egor@squareup.com>
  • Loading branch information
ZacSweers and Egorand authored Sep 17, 2021
1 parent d0fcadc commit b42fa6d
Show file tree
Hide file tree
Showing 14 changed files with 709 additions and 28 deletions.
9 changes: 5 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.dokka) apply false
alias(libs.plugins.spotless) apply false
alias(libs.plugins.mavenPublish) apply false
Expand Down Expand Up @@ -47,12 +48,12 @@ subprojects {
}

apply(plugin = "org.jetbrains.kotlin.jvm")
configure<KotlinProjectExtension> {
explicitApi()
}
if ("tests" !in name && buildFile.exists()) {
if ("test" !in name && buildFile.exists()) {
apply(plugin = "org.jetbrains.dokka")
apply(plugin = "com.vanniktech.maven.publish")
configure<KotlinProjectExtension> {
explicitApi()
}
afterEvaluate {
tasks.named<DokkaTask>("dokkaHtml") {
val projectFolder = project.path.trim(':').replace(':', '-')
Expand Down
7 changes: 6 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

[versions]
kotlin = "1.5.30"
kct = "1.4.4"
ksp = "1.5.30-1.0.0"
ktlint = "0.41.0"

Expand All @@ -29,6 +30,9 @@ autoCommon = { module = "com.google.auto:auto-common", version = "1.1.2" }
guava = { module = "com.google.guava:guava", version = "30.1.1-jre" }
javapoet = "com.squareup:javapoet:1.13.0"

autoService = "com.google.auto.service:auto-service-annotations:1.0"
autoService-ksp = "dev.zacsweers.autoservice:auto-service-ksp:1.0.0"

kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlin-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
kotlin-metadata = { module = "org.jetbrains.kotlinx:kotlinx-metadata-jvm", version = "0.3.0" }
Expand All @@ -40,4 +44,5 @@ truth = { module = "com.google.truth:truth", version = "1.1.3" }
compileTesting = { module = "com.google.testing.compile:compile-testing", version = "0.19" }
jimfs = { module = "com.google.jimfs:jimfs", version = "1.2" }
ecj = { module = "org.eclipse.jdt.core.compiler:ecj", version = "4.6.1" }
kotlinCompileTesting = { module = "com.github.tschuchortdev:kotlin-compile-testing", version = "1.4.3" }
kotlinCompileTesting = { module = "com.github.tschuchortdev:kotlin-compile-testing", version.ref = "kct" }
kotlinCompileTesting-ksp = { module = "com.github.tschuchortdev:kotlin-compile-testing-ksp", version.ref = "kct" }
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,11 @@
*/
package com.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.isLocal
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.squareup.kotlinpoet.ClassName

/** Returns the [ClassName] representation of this [KSClassDeclaration]. */
@KotlinPoetKspPreview
public fun KSClassDeclaration.toClassName(): ClassName {
require(!isLocal()) {
"Local/anonymous classes are not supported!"
}
val pkgName = packageName.asString()
val typesString = checkNotNull(qualifiedName).asString().removePrefix("$pkgName.")

val simpleNames = typesString
.split(".")
return ClassName(pkgName, simpleNames)
return toClassNameInternal()
}
44 changes: 31 additions & 13 deletions interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/ksTypes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import com.google.devtools.ksp.symbol.Variance.COVARIANT
import com.google.devtools.ksp.symbol.Variance.INVARIANT
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.STAR
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
Expand All @@ -50,27 +49,35 @@ public fun KSType.toClassName(): ClassName {
* @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
* declarations can be anything with generics that child nodes declare as
* defined by [KSType.arguments].
* @param unwrapTypeAliases optionally controls whether typealiases should be unwrapped to their
* aliased type. Useful in cases where only the aliased type matters
*/
@KotlinPoetKspPreview
public fun KSType.toTypeName(
typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY
typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
unwrapTypeAliases: Boolean = false
): TypeName {
val type = when (val decl = declaration) {
is KSClassDeclaration -> decl.toClassName().parameterizedBy(arguments.map { it.toTypeName(typeParamResolver) })
is KSClassDeclaration -> {
decl.toClassName().withTypeArguments(arguments.map { it.toTypeName(typeParamResolver) })
}
is KSTypeParameter -> typeParamResolver[decl.name.getShortName()]
is KSTypeAlias -> {
val extraResolver = if (decl.typeParameters.isEmpty()) {
typeParamResolver
} else {
decl.typeParameters.toTypeParameterResolver(typeParamResolver)
}
val args = arguments.map { it.toTypeName(typeParamResolver) }
val firstPass = decl.type.resolve().toTypeName(extraResolver).copy(nullable = isMarkedNullable)
if (args.isNotEmpty()) {
firstPass.rawType().parameterizedBy(args)
val firstPass = if (unwrapTypeAliases) {
decl.type.resolve()
.toTypeName(extraResolver, unwrapTypeAliases)
.copy(nullable = isMarkedNullable)
.rawType()
} else {
firstPass
decl.toClassNameInternal()
}
firstPass
.withTypeArguments(arguments.map { it.toTypeName(typeParamResolver) })
}
else -> error("Unsupported type: $declaration")
}
Expand All @@ -85,13 +92,16 @@ public fun KSType.toTypeName(
* @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
* declarations can be anything with generics that child nodes declare as
* defined by [KSType.arguments].
* @param unwrapTypeAliases optionally controls whether typealiases should be unwrapped to their
* aliased type. Useful in cases where only the aliased type matters
*/
@KotlinPoetKspPreview
public fun KSTypeParameter.toTypeVariableName(
typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
unwrapTypeAliases: Boolean = false
): TypeVariableName {
val typeVarName = name.getShortName()
val typeVarBounds = bounds.map { it.toTypeName(typeParamResolver) }.toList()
val typeVarBounds = bounds.map { it.toTypeName(typeParamResolver, unwrapTypeAliases) }.toList()
val typeVarVariance = when (variance) {
COVARIANT -> KModifier.OUT
CONTRAVARIANT -> KModifier.IN
Expand All @@ -107,10 +117,15 @@ public fun KSTypeParameter.toTypeVariableName(
* @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
* declarations can be anything with generics that child nodes declare as
* defined by [KSType.arguments].
* @param unwrapTypeAliases optionally controls whether typealiases should be unwrapped to their
* aliased type. Useful in cases where only the aliased type matters
*/
@KotlinPoetKspPreview
public fun KSTypeArgument.toTypeName(typeParamResolver: TypeParameterResolver): TypeName {
val typeName = type?.resolve()?.toTypeName(typeParamResolver) ?: return STAR
public fun KSTypeArgument.toTypeName(
typeParamResolver: TypeParameterResolver,
unwrapTypeAliases: Boolean = false
): TypeName {
val typeName = type?.resolve()?.toTypeName(typeParamResolver, unwrapTypeAliases) ?: return STAR
return when (variance) {
COVARIANT -> WildcardTypeName.producerOf(typeName)
CONTRAVARIANT -> WildcardTypeName.consumerOf(typeName)
Expand All @@ -126,11 +141,14 @@ public fun KSTypeArgument.toTypeName(typeParamResolver: TypeParameterResolver):
* @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
* declarations can be anything with generics that child nodes declare as
* defined by [KSType.arguments].
* @param unwrapTypeAliases optionally controls whether typealiases should be unwrapped to their
* aliased type. Useful in cases where only the aliased type matters
*/
@KotlinPoetKspPreview
public fun KSTypeReference.toTypeName(
typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY
typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
unwrapTypeAliases: Boolean = false
): TypeName {
val type = resolve()
return type.toTypeName(typeParamResolver)
return type.toTypeName(typeParamResolver, unwrapTypeAliases)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (C) 2021 Square, Inc.
*
* 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
*
* https://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.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.symbol.Modifier
import com.google.devtools.ksp.symbol.Modifier.ABSTRACT
import com.google.devtools.ksp.symbol.Modifier.ACTUAL
import com.google.devtools.ksp.symbol.Modifier.ANNOTATION
import com.google.devtools.ksp.symbol.Modifier.CROSSINLINE
import com.google.devtools.ksp.symbol.Modifier.DATA
import com.google.devtools.ksp.symbol.Modifier.ENUM
import com.google.devtools.ksp.symbol.Modifier.EXPECT
import com.google.devtools.ksp.symbol.Modifier.EXTERNAL
import com.google.devtools.ksp.symbol.Modifier.FINAL
import com.google.devtools.ksp.symbol.Modifier.FUN
import com.google.devtools.ksp.symbol.Modifier.IN
import com.google.devtools.ksp.symbol.Modifier.INFIX
import com.google.devtools.ksp.symbol.Modifier.INLINE
import com.google.devtools.ksp.symbol.Modifier.INNER
import com.google.devtools.ksp.symbol.Modifier.INTERNAL
import com.google.devtools.ksp.symbol.Modifier.LATEINIT
import com.google.devtools.ksp.symbol.Modifier.NOINLINE
import com.google.devtools.ksp.symbol.Modifier.OPEN
import com.google.devtools.ksp.symbol.Modifier.OPERATOR
import com.google.devtools.ksp.symbol.Modifier.OUT
import com.google.devtools.ksp.symbol.Modifier.OVERRIDE
import com.google.devtools.ksp.symbol.Modifier.PRIVATE
import com.google.devtools.ksp.symbol.Modifier.PROTECTED
import com.google.devtools.ksp.symbol.Modifier.PUBLIC
import com.google.devtools.ksp.symbol.Modifier.REIFIED
import com.google.devtools.ksp.symbol.Modifier.SEALED
import com.google.devtools.ksp.symbol.Modifier.SUSPEND
import com.google.devtools.ksp.symbol.Modifier.TAILREC
import com.google.devtools.ksp.symbol.Modifier.VALUE
import com.google.devtools.ksp.symbol.Modifier.VARARG
import com.squareup.kotlinpoet.KModifier

/**
* Returns the [KModifier] representation of this [Modifier] or null if this is a Java-only
* modifier (i.e. prefixed with `JAVA_`), which do not have obvious [KModifier] analogues.
*/
public fun Modifier.toKModifier(): KModifier? {
return when (this) {
PUBLIC -> KModifier.PUBLIC
PRIVATE -> KModifier.PRIVATE
INTERNAL -> KModifier.INTERNAL
PROTECTED -> KModifier.PROTECTED
IN -> KModifier.IN
OUT -> KModifier.OUT
OVERRIDE -> KModifier.OVERRIDE
LATEINIT -> KModifier.LATEINIT
ENUM -> KModifier.ENUM
SEALED -> KModifier.SEALED
ANNOTATION -> KModifier.ANNOTATION
DATA -> KModifier.DATA
INNER -> KModifier.INNER
FUN -> KModifier.FUN
VALUE -> KModifier.VALUE
SUSPEND -> KModifier.SUSPEND
TAILREC -> KModifier.TAILREC
OPERATOR -> KModifier.OPERATOR
INFIX -> KModifier.INFIX
INLINE -> KModifier.INLINE
EXTERNAL -> KModifier.EXTERNAL
ABSTRACT -> KModifier.ABSTRACT
FINAL -> KModifier.FINAL
OPEN -> KModifier.OPEN
VARARG -> KModifier.VARARG
NOINLINE -> KModifier.NOINLINE
CROSSINLINE -> KModifier.CROSSINLINE
REIFIED -> KModifier.REIFIED
EXPECT -> KModifier.EXPECT
ACTUAL -> KModifier.ACTUAL
else -> null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,24 @@ public fun FileSpec.writeTo(
originatingKSFiles: Iterable<KSFile> = originatingKSFiles()
) {
val dependencies = kspDependencies(aggregating, originatingKSFiles)
writeTo(codeGenerator, dependencies)
}

/**
* Writes this [FileSpec] to a given [codeGenerator] with the given [dependencies].
*
* See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
*
* @see FileSpec.originatingKSFiles
* @see kspDependencies
* @param codeGenerator the [CodeGenerator] to write to.
* @param dependencies the [Dependencies] to create a new file with.
*/
@KotlinPoetKspPreview
public fun FileSpec.writeTo(
codeGenerator: CodeGenerator,
dependencies: Dependencies
) {
val file = codeGenerator.createNewFile(dependencies, packageName, name)
// Don't use writeTo(file) because that tries to handle directories under the hood
OutputStreamWriter(file, StandardCharsets.UTF_8)
Expand Down
23 changes: 23 additions & 0 deletions interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
*/
package com.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.isLocal
import com.google.devtools.ksp.symbol.KSDeclaration
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.LambdaTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName

internal fun TypeName.rawType(): ClassName {
Expand All @@ -43,3 +46,23 @@ internal fun TypeName.findRawType(): ClassName? {
else -> null
}
}

internal fun ClassName.withTypeArguments(arguments: List<TypeName>): TypeName {
return if (arguments.isEmpty()) {
this
} else {
this.parameterizedBy(arguments)
}
}

internal fun KSDeclaration.toClassNameInternal(): ClassName {
require(!isLocal()) {
"Local/anonymous classes are not supported!"
}
val pkgName = packageName.asString()
val typesString = checkNotNull(qualifiedName).asString().removePrefix("$pkgName.")

val simpleNames = typesString
.split(".")
return ClassName(pkgName, simpleNames)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2021 Square, Inc.
*
* 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
*
* https://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.squareup.kotlinpoet.ksp

import com.google.devtools.ksp.symbol.Visibility
import com.google.devtools.ksp.symbol.Visibility.INTERNAL
import com.google.devtools.ksp.symbol.Visibility.JAVA_PACKAGE
import com.google.devtools.ksp.symbol.Visibility.LOCAL
import com.google.devtools.ksp.symbol.Visibility.PRIVATE
import com.google.devtools.ksp.symbol.Visibility.PROTECTED
import com.google.devtools.ksp.symbol.Visibility.PUBLIC
import com.squareup.kotlinpoet.KModifier

/**
* Returns the [KModifier] representation of this visibility or null if this is [JAVA_PACKAGE]
* or [LOCAL] (which do not have obvious [KModifier] alternatives).
*/
public fun Visibility.toKModifier(): KModifier? {
return when (this) {
PUBLIC -> KModifier.PUBLIC
PRIVATE -> KModifier.PRIVATE
PROTECTED -> KModifier.PROTECTED
INTERNAL -> KModifier.INTERNAL
else -> null
}
}
Loading

0 comments on commit b42fa6d

Please sign in to comment.