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

Support Groovy outputs #8

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fixtures-annotations/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies {
}

tasks.dokkaHtml.configure {
outputDirectory.set(buildDir.resolve("dokka"))
outputDirectory.set(layout.buildDirectory.asFile.get().resolve("dokka"))
}

plugins.withId("com.vanniktech.maven.publish") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
plugins {
kotlin("jvm")
id("org.jetbrains.dokka")
id("com.vanniktech.maven.publish")
}

repositories {
mavenCentral()
}

apply(plugin = "com.vanniktech.maven.publish")

dependencies {
// Kotlin Dependencies
implementation(Dependencies.Kotlin.KOTLIN)
Expand All @@ -25,11 +22,5 @@ dependencies {
}

tasks.dokkaHtml.configure {
outputDirectory.set(buildDir.resolve("dokka"))
}

plugins.withId("com.vanniktech.maven.publish") {
mavenPublish {
sonatypeHost = com.vanniktech.maven.publish.SonatypeHost.S01
}
outputDirectory.set(layout.buildDirectory.asFile.get().resolve("dokka"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.theblueground.fixtures

import com.google.devtools.ksp.symbol.KSFile
import com.squareup.kotlinpoet.TypeName

/**
* It uses the information that was extracted from [FixtureVisitor] to generate a file that contains
* a helper function which will create test data.
*/
interface FixtureBuilderGenerator {

fun generate(
randomize: Boolean,
containingFile: KSFile,
processedFixtures: List<ProcessedFixture>,
fixtureAdapters: Map<TypeName, ProcessedFixtureAdapter>,
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.theblueground.fixtures

import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
Expand All @@ -19,9 +18,9 @@ import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview
* to generate the functions.
*/
@KotlinPoetKspPreview
internal class FixtureProcessor(
class FixtureProcessor(
private val fixtureBuilderGenerator: FixtureBuilderGenerator,
options: Map<String, String>,
codeGenerator: CodeGenerator,
private val logger: KSPLogger,
) : SymbolProcessor {

Expand All @@ -33,8 +32,6 @@ internal class FixtureProcessor(
private val FIXTURE_ANNOTATION_FULLY_QUALIFIED_NAME = Fixture::class.java.name
}

private val fixtureBuilderGenerator = FixtureBuilderGenerator(codeGenerator = codeGenerator)

private val processedFixtureAdapters = mutableMapOf<TypeName, ProcessedFixtureAdapter>()

private val fixtureAdapterVisitor = FixtureAdapterVisitor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import com.squareup.kotlinpoet.ClassName
* [Fixture] annotation. The information will be used in order to generate a helper function which
* will create test data.
*/
internal data class ProcessedFixture(
data class ProcessedFixture(
val parentName: String,
val classType: ClassName,
val parameters: List<ProcessedFixtureParameter>,
)

internal val ProcessedFixture.simpleName
val ProcessedFixture.simpleName
get() = this.classType.simpleName

internal val ProcessedFixture.qualifiedName
val ProcessedFixture.qualifiedName
get() = this.classType.canonicalName

internal val ProcessedFixture.packageName
val ProcessedFixture.packageName
get() = this.classType.packageName
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.theblueground.fixtures

internal data class ProcessedFixtureAdapter(
data class ProcessedFixtureAdapter(
val packageName: String,
val functionName: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import com.squareup.kotlinpoet.WildcardTypeName
* should be annotated with the [Fixture] annotation. The information will be used in order to
* generate a helper function which will create test data.
*/
internal sealed class ProcessedFixtureParameter(
sealed class ProcessedFixtureParameter(
open val name: String,
open val type: TypeName,
) {
Expand Down Expand Up @@ -121,7 +121,7 @@ internal sealed class ProcessedFixtureParameter(
)
}

internal val ProcessedFixtureParameter.packageName: String
val ProcessedFixtureParameter.packageName: String
get() = when (val type = this.type) {
is ClassName -> type.packageName
is ParameterizedTypeName -> type.rawType.packageName
Expand All @@ -132,7 +132,7 @@ internal val ProcessedFixtureParameter.packageName: String
-> ""
}

internal val ProcessedFixtureParameter.typeName: String
val ProcessedFixtureParameter.typeName: String
get() = when (val type = this.type) {
is ClassName -> type.canonicalName.removePrefix(type.packageName).replace(".", "")
is ParameterizedTypeName -> type.rawType.simpleName
Expand Down
46 changes: 46 additions & 0 deletions fixtures-processor-groovy/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
plugins {
kotlin("jvm")
id("org.jetbrains.dokka")
id("com.vanniktech.maven.publish")
}

repositories {
mavenCentral()
}

apply(plugin = "com.vanniktech.maven.publish")

dependencies {
// Kotlin Dependencies
implementation(Dependencies.Kotlin.KOTLIN)
implementation(Dependencies.Kotlin.KSP)
implementation(Dependencies.Square.Poet.KSP)

compileOnly(project(":fixtures-core"))

testImplementation(TestDependencies.JUnit.JUNIT)
testImplementation(TestDependencies.Google.TRUTH)
testImplementation(project(":fixtures-test-utils"))
}

tasks.dokkaHtml.configure {
outputDirectory.set(layout.buildDirectory.asFile.get().resolve("dokka"))
}

plugins.withId("com.vanniktech.maven.publish") {
mavenPublish {
sonatypeHost = com.vanniktech.maven.publish.SonatypeHost.S01
}
}
tasks.named<Jar>("jar") {
from({
project(":fixtures-annotations").sourceSets["main"].output
})
from({
project(":fixtures-core").sourceSets["main"].output
})

from(sourceSets["main"].output)

destinationDirectory.set(file("${layout.buildDirectory.get()}/libs"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package com.theblueground.fixtures

import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.symbol.KSFile
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.TypeName
import java.io.File
import java.util.Locale

/**
* It uses the information that was extracted from [FixtureVisitor] to generate a file that contains
* a helper function which will create test data.
*/
internal class FixtureBuilderGroovyGenerator(
private val codeGenerator: CodeGenerator,
) : FixtureBuilderGenerator {

companion object {

private const val TAB = "\t"

const val OUTPUT_FIXTURE_FILENAME_SUFFIX = "Fixture"
}

private val valueGenerator = ParameterGroovyValueGenerator()

override fun generate(
randomize: Boolean,
containingFile: KSFile,
processedFixtures: List<ProcessedFixture>,
fixtureAdapters: Map<TypeName, ProcessedFixtureAdapter>,
) {
val fileNameWithoutExtension = File(containingFile.fileName).nameWithoutExtension
val filename = fileNameWithoutExtension + OUTPUT_FIXTURE_FILENAME_SUFFIX
val dependencies = Dependencies(aggregating = true, containingFile)
val packageName = containingFile.packageName.asString()
val output = codeGenerator.createNewFile(
dependencies = dependencies,
packageName = packageName,
fileName = filename,
extensionName = "groovy",
)

output.write("package $packageName\n\n".toByteArray())
output.write(buildImports(processedFixtures = processedFixtures).toByteArray())
processedFixtures.forEach {
val builderClass = it.buildBuilderClass(
randomize = randomize,
fixtureAdapters = fixtureAdapters,
)
output.write(builderClass.toByteArray())
}

output.close()
}

private fun buildImports(
processedFixtures: List<ProcessedFixture>,
): String = processedFixtures
.flatMap { it.parameters }
.filterNot { it is ProcessedFixtureParameter.PrimitiveParameter }
.distinct()
.joinToString("\n") {
// To support default value assignment of ZonedDateTime
if (it.javaTypeName == "ZonedDateTime") {
"import ${it.packageName}.${it.javaTypeName}\n" +
"import ${it.packageName}.ZoneId"
} else {
"import ${it.packageName}.${it.javaTypeName}"
}
} + "\n"

private fun ProcessedFixture.buildBuilderClass(
randomize: Boolean,
fixtureAdapters: Map<TypeName, ProcessedFixtureAdapter>,
): String {
val className = "$parentName${simpleName.replaceFirstChar { it.uppercaseChar() }}Builder"
val parametersPart = buildParametersPart(
randomize = randomize,
fixtureAdapters = fixtureAdapters,
parameters = parameters,
)
val functionsPart = buildFunctionsPart(
className = className,
parameters = parameters,
)
val instantiationPart = buildInstantiationPart(processedFixture = this)

return """
|
|class $className {
|
|$parametersPart
|
|${TAB}private $className() { }
|
|${TAB}static $className a$className() {
|$TAB${TAB}new $className()
|$TAB}
$functionsPart
|
$instantiationPart
|}
|
""".trimMargin()
}

private fun buildParametersPart(
randomize: Boolean,
fixtureAdapters: Map<TypeName, ProcessedFixtureAdapter>,
parameters: List<ProcessedFixtureParameter>,
): String = parameters.joinToString("\n") {
val value = valueGenerator.generateDefaultValue(
randomize = randomize,
parameter = it,
fixtureAdapters = fixtureAdapters,
)

"${TAB}private ${it.javaTypeName} ${it.name} = $value"
}

private fun buildFunctionsPart(
className: String,
parameters: List<ProcessedFixtureParameter>,
): String = parameters.joinToString("\n") {
val type = it.javaTypeName
val name = it.name
val capitalizeName = name.replaceFirstChar { char -> char.titlecase(Locale.getDefault()) }
"""|
|$TAB$className with$capitalizeName($type $name) {
|$TAB${TAB}this.$name = $name
|$TAB${TAB}this
|$TAB}"""
}

private fun buildInstantiationPart(
processedFixture: ProcessedFixture,
): String {
val parameters = processedFixture.parameters.joinToString("\n") {
"|$TAB$TAB${TAB}this.${it.name},"
}

val parent = if (processedFixture.parentName.isNotBlank()) {
"${processedFixture.parentName}."
} else {
""
}

val name = processedFixture.simpleName
return """|$TAB$parent$name build() {
|$TAB${TAB}new $parent$name(
$parameters
|$TAB$TAB)
|$TAB}"""
}

private val ProcessedFixtureParameter.javaTypeName: String
get() = with(type) {
if (this is ClassName && this.simpleName == "Int") {
"Integer"
} else {
typeName
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.theblueground.fixtures

import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview

@KotlinPoetKspPreview
internal class FixtureGroovyProcessorProvider : SymbolProcessorProvider {

override fun create(
environment: SymbolProcessorEnvironment,
): SymbolProcessor = FixtureProcessor(
fixtureBuilderGenerator = FixtureBuilderGroovyGenerator(codeGenerator = environment.codeGenerator),
options = environment.options,
logger = environment.logger,
)
}
Loading
Loading