From 1a14877f20f5f3dde8ce8a77efd153cf19edd080 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Wed, 4 May 2022 00:07:55 -0400 Subject: [PATCH 01/23] conditionally sign --- build.gradle.kts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 187f44d..511b678 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -137,9 +137,11 @@ subprojects { val signingKey by auth { it?.replace("\\n", "\n") } - val signingPassword by auth - useInMemoryPgpKeys(signingKey, signingPassword) - sign(extensions.findByType(PublishingExtension::class.java)!!.publications) + signingKey?.let { + val signingPassword by auth + useInMemoryPgpKeys(signingKey, signingPassword) + sign(extensions.findByType(PublishingExtension::class.java)!!.publications) + } } tasks { From 4a04b8f6f8a09e72bfbe495747585b14d0787757 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Wed, 4 May 2022 23:37:49 -0400 Subject: [PATCH 02/23] wip --- compiler-plugin/build.gradle.kts | 2 +- .../kotlin/com/intuit/hooks/plugin/Hooks.kt | 9 ++++++--- .../com/intuit/hooks/plugin/HooksMetaPlugin.kt | 3 +++ ....kotlin.compiler.plugin.CommandLineProcessor | 1 + .../com/intuit/hooks/plugin/TestUtilities.kt | 2 +- example-library/build.gradle.kts | 7 +++---- gradle.properties | 17 ++++++++++------- 7 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor diff --git a/compiler-plugin/build.gradle.kts b/compiler-plugin/build.gradle.kts index e9081b8..46399fc 100644 --- a/compiler-plugin/build.gradle.kts +++ b/compiler-plugin/build.gradle.kts @@ -12,10 +12,10 @@ dependencies { testImplementation(platform("org.junit:junit-bom:5.7.0")) testImplementation("org.junit.jupiter", "junit-jupiter") - testImplementation("io.arrow-kt:meta-test:$ARROW_META_VERSION") testImplementation(project(":hooks")) testImplementation("io.arrow-kt:arrow-meta:$ARROW_META_VERSION") + testImplementation("io.arrow-kt:arrow-meta-test:$ARROW_META_VERSION") testImplementation("com.pinterest.ktlint:ktlint-core:$KTLINT_VERSION") testImplementation("com.pinterest.ktlint:ktlint-ruleset-standard:$KTLINT_VERSION") testRuntimeOnly("io.arrow-kt:arrow-meta-prelude:$ARROW_META_VERSION") diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Hooks.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Hooks.kt index 629678e..c190f6b 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Hooks.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Hooks.kt @@ -1,10 +1,12 @@ package com.intuit.hooks.plugin -import arrow.core.* +import arrow.core.sequenceValidated +import arrow.core.unzip +import arrow.core.valueOr import arrow.meta.CliPlugin import arrow.meta.Meta import arrow.meta.invoke -import arrow.meta.phases.analysis.DefaultElementScope.Companion.DEFAULT_GENERATED_SRC_PATH +import arrow.meta.phases.analysis.getOrCreateBaseDirectory import arrow.meta.quotes.Transform import arrow.meta.quotes.classDeclaration import arrow.meta.quotes.classorobject.ClassDeclaration @@ -27,12 +29,13 @@ internal val Meta.hooks: CliPlugin val `package` = file.packageDirective?.text ?: "" val (classes, properties) = codeGen.map(::generateHookClass).unzip() val imports = createImportDirectives(`class`, codeGen) - val filePath = DEFAULT_GENERATED_SRC_PATH.resolve( + val filePath = getOrCreateBaseDirectory(configuration).toPath().resolve( Paths.get( "", *file.packageFqName.asString().split(".").toTypedArray() ) ) + val name = "${if (`class`.isTopLevel()) "" else `class`.containingClassOrObject?.name ?: ""}${name}Impl" diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HooksMetaPlugin.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HooksMetaPlugin.kt index a45bbb3..c1c7887 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HooksMetaPlugin.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HooksMetaPlugin.kt @@ -2,8 +2,11 @@ package com.intuit.hooks.plugin import arrow.meta.CliPlugin import arrow.meta.Meta +import arrow.meta.MetaCliProcessor import arrow.meta.phases.CompilerContext public open class HooksMetaPlugin : Meta { override fun intercept(ctx: CompilerContext): List = listOf(hooks) } + +public class HooksMetaCliProcessor : MetaCliProcessor("hooks") diff --git a/compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor b/compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor new file mode 100644 index 0000000..a1703b3 --- /dev/null +++ b/compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor @@ -0,0 +1 @@ +com.intuit.hooks.plugin.HooksMetaCliProcessor \ No newline at end of file diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/TestUtilities.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/TestUtilities.kt index 3acb673..da35536 100644 --- a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/TestUtilities.kt +++ b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/TestUtilities.kt @@ -7,7 +7,7 @@ import arrow.meta.plugin.testing.Dependency fun CompilerTest.Companion.hookDependencies(): List { val hooks = Dependency("hooks") val prelude = Dependency("arrow-meta-prelude") - val coroutines = Dependency("kotlinx-coroutines-core-jvm") + val coroutines = Dependency("kotlinx-coroutines-core") return addDependencies(hooks, prelude, coroutines) + addMetaPlugins(HooksMetaPlugin()) } diff --git a/example-library/build.gradle.kts b/example-library/build.gradle.kts index 1ffa440..b097103 100644 --- a/example-library/build.gradle.kts +++ b/example-library/build.gradle.kts @@ -15,12 +15,11 @@ dependencies { testImplementation("io.mockk", "mockk", "1.10.2") } -val generatedSourcesRoot: String = buildDir.absolutePath - +val generatedSourcesRoot: String = "${buildDir.absolutePath}/generated/source/kapt/main" sourceSets { main { java { - srcDir("$generatedSourcesRoot/generated/source/kapt/main") + srcDir(generatedSourcesRoot) } } } @@ -43,7 +42,7 @@ tasks { freeCompilerArgs += listOf( "-Xplugin=${compilerPlugin.resolve().first()}", "-P", - "plugin:arrow.meta.plugin.compiler:generatedSrcOutputDir=$buildDir" + "plugin:arrow.meta.plugin.compiler.hooks:generatedSrcOutputDir=$generatedSourcesRoot" ) } } diff --git a/gradle.properties b/gradle.properties index 1edd380..7abd9ba 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,16 +1,18 @@ JVM_TARGET_VERSION=1.8 -KOTLIN_VERSION=1.5.31 -COROUTINES_VERSION=1.5.2 -ARROW_META_VERSION=1.5.0-SNAPSHOT -KTLINT_VERSION=0.42.1 +KOTLIN_VERSION=1.6.20 +COROUTINES_VERSION=1.6.1 +ARROW_META_VERSION=1.6.1-alpha.5 +KTLINT_VERSION=0.45.2 -ARROW_VERSION=1.0.0 +ARROW_VERSION=1.0.1 +# 1.1.2 + +# TODO: Swap to Kotlin testing library JUNIT_VERSION=5.7.0 -ASSERTJ_VERSION=3.17.2 NEXUS_PUBLISH_PLUGIN_VERSION=1.0.0 RELEASE_PLUGIN_VERSION=2.6.0 KTLINT_PLUGIN_VERSION=9.4.1 -VALIDATOR_VERSION=0.5.0 +VALIDATOR_VERSION=0.9.0 DOKKA_VERSION=1.4.20 ORCHID_VERSION=0.21.1 KNIT_VERSION=0.2.3 @@ -18,3 +20,4 @@ KNIT_VERSION=0.2.3 org.gradle.jvmargs=-XX:MaxMetaspaceSize=1G group=com.intuit.hooks version=0.11.2-SNAPSHOT +arrow.meta.generate.source.dir=generated/src/kapt/main From 69fc4f50dabc1fbc582e02e3c4ad92a475e23f43 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Thu, 5 May 2022 01:42:56 -0400 Subject: [PATCH 03/23] wip on version catalog --- .circleci/config.yml | 2 +- build.gradle.kts | 18 ++++---- compiler-plugin/build.gradle.kts | 46 +++++++++---------- gradle.properties | 20 -------- gradle/wrapper/gradle-wrapper.properties | 2 +- hooks/build.gradle.kts | 11 ++--- settings.gradle.kts | 58 ++++++++++++++---------- 7 files changed, 73 insertions(+), 84 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index aeaee4c..b9228cf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,7 @@ jobs: - image: circleci/openjdk:8-jdk steps: - - run: curl -vkL -o - https://github.com/intuit/auto/releases/download/v10.32.1/auto-linux.gz | gunzip > ~/auto + - run: curl -vkL -o - https://github.com/intuit/auto/releases/download/v10.36.5/auto-linux.gz | gunzip > ~/auto - run: chmod a+x ~/auto - checkout - run: ~/auto shipit -vvv diff --git a/build.gradle.kts b/build.gradle.kts index 511b678..9ff31e3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,19 +16,19 @@ allprojects { } plugins { - kotlin("jvm") apply false - id("jacoco") + alias(libs.plugins.kotlin.jvm) apply false + jacoco - id("net.researchgate.release") - id("io.github.gradle-nexus.publish-plugin") + alias(libs.plugins.release) + alias(libs.plugins.nexus) - id("org.jlleitschuh.gradle.ktlint") - id("org.jetbrains.kotlinx.binary-compatibility-validator") + alias(libs.plugins.ktlint) + alias(libs.plugins.api) - id("org.jetbrains.dokka") + alias(libs.plugins.dokka) } -val shouldntPublish = listOf("docs", "example-library", "example-application") +val shouldntPublish = listOf("hooks") //listOf("docs", "example-library", "example-application") val publishModules = subprojects.map { it.name }.subtract(shouldntPublish) val isSnapshot = (version as? String)?.contains("-SNAPSHOT") ?: true @@ -174,8 +174,6 @@ subprojects { tasks { val configure: KotlinCompile.() -> Unit = { kotlinOptions { - val JVM_TARGET_VERSION: String by project - jvmTarget = JVM_TARGET_VERSION freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" } } diff --git a/compiler-plugin/build.gradle.kts b/compiler-plugin/build.gradle.kts index 46399fc..91944e9 100644 --- a/compiler-plugin/build.gradle.kts +++ b/compiler-plugin/build.gradle.kts @@ -1,27 +1,27 @@ dependencies { - val ARROW_VERSION: String by project - val ARROW_META_VERSION: String by project - val KTLINT_VERSION: String by project - - compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable") - compileOnly("io.arrow-kt:arrow-meta:$ARROW_META_VERSION") - compileOnly("io.arrow-kt:arrow-core:$ARROW_VERSION") - compileOnly("io.arrow-kt:arrow-annotations:$ARROW_VERSION") - compileOnly("com.pinterest.ktlint:ktlint-core:$KTLINT_VERSION") - compileOnly("com.pinterest.ktlint:ktlint-ruleset-standard:$KTLINT_VERSION") - - testImplementation(platform("org.junit:junit-bom:5.7.0")) - testImplementation("org.junit.jupiter", "junit-jupiter") - - testImplementation(project(":hooks")) - testImplementation("io.arrow-kt:arrow-meta:$ARROW_META_VERSION") - testImplementation("io.arrow-kt:arrow-meta-test:$ARROW_META_VERSION") - testImplementation("com.pinterest.ktlint:ktlint-core:$KTLINT_VERSION") - testImplementation("com.pinterest.ktlint:ktlint-ruleset-standard:$KTLINT_VERSION") - testRuntimeOnly("io.arrow-kt:arrow-meta-prelude:$ARROW_META_VERSION") - testRuntimeOnly("io.arrow-kt:arrow-core:$ARROW_VERSION") { - exclude("org.jetbrains.kotlin") - } +// val ARROW_VERSION: String by project +// val ARROW_META_VERSION: String by project +// val KTLINT_VERSION: String by project +// +// compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable") +// compileOnly("io.arrow-kt:arrow-meta:$ARROW_META_VERSION") +// compileOnly("io.arrow-kt:arrow-core:$ARROW_VERSION") +// compileOnly("io.arrow-kt:arrow-annotations:$ARROW_VERSION") +// compileOnly("com.pinterest.ktlint:ktlint-core:$KTLINT_VERSION") +// compileOnly("com.pinterest.ktlint:ktlint-ruleset-standard:$KTLINT_VERSION") +// +// testImplementation(platform("org.junit:junit-bom:5.7.0")) +// testImplementation("org.junit.jupiter", "junit-jupiter") +// +// testImplementation(project(":hooks")) +// testImplementation("io.arrow-kt:arrow-meta:$ARROW_META_VERSION") +// testImplementation("io.arrow-kt:arrow-meta-test:$ARROW_META_VERSION") +// testImplementation("com.pinterest.ktlint:ktlint-core:$KTLINT_VERSION") +// testImplementation("com.pinterest.ktlint:ktlint-ruleset-standard:$KTLINT_VERSION") +// testRuntimeOnly("io.arrow-kt:arrow-meta-prelude:$ARROW_META_VERSION") +// testRuntimeOnly("io.arrow-kt:arrow-core:$ARROW_VERSION") { +// exclude("org.jetbrains.kotlin") +// } } tasks { diff --git a/gradle.properties b/gradle.properties index 7abd9ba..8350de2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,23 +1,3 @@ -JVM_TARGET_VERSION=1.8 -KOTLIN_VERSION=1.6.20 -COROUTINES_VERSION=1.6.1 -ARROW_META_VERSION=1.6.1-alpha.5 -KTLINT_VERSION=0.45.2 - -ARROW_VERSION=1.0.1 -# 1.1.2 - -# TODO: Swap to Kotlin testing library -JUNIT_VERSION=5.7.0 -NEXUS_PUBLISH_PLUGIN_VERSION=1.0.0 -RELEASE_PLUGIN_VERSION=2.6.0 -KTLINT_PLUGIN_VERSION=9.4.1 -VALIDATOR_VERSION=0.9.0 -DOKKA_VERSION=1.4.20 -ORCHID_VERSION=0.21.1 -KNIT_VERSION=0.2.3 - org.gradle.jvmargs=-XX:MaxMetaspaceSize=1G group=com.intuit.hooks version=0.11.2-SNAPSHOT -arrow.meta.generate.source.dir=generated/src/kapt/main diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 14e30f7..92f06b5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/hooks/build.gradle.kts b/hooks/build.gradle.kts index bf097fc..78d54fc 100644 --- a/hooks/build.gradle.kts +++ b/hooks/build.gradle.kts @@ -1,14 +1,13 @@ plugins { - id("kotlinx-knit") + alias(libs.plugins.knit) } val COROUTINES_VERSION: String by project dependencies { - implementation(kotlin("stdlib")) - implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", COROUTINES_VERSION) + implementation(libs.kotlin.stdlib) + implementation(libs.coroutines.core) - testImplementation(platform("org.junit:junit-bom:5.7.0")) - testImplementation("org.junit.jupiter", "junit-jupiter") - testImplementation("io.mockk", "mockk", "1.10.2") + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.bundles.testing) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 14a9aa0..ee75733 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,34 +1,46 @@ rootProject.name = "hooks-project" -include(":hooks", ":compiler-plugin", ":gradle-plugin", ":maven-plugin", ":docs", ":example-library", ":example-application") +include(":hooks")//, ":compiler-plugin", ":gradle-plugin", ":maven-plugin", ":docs", ":example-library", ":example-application") enableFeaturePreview("ONE_LOCKFILE_PER_PROJECT") -pluginManagement { - val KOTLIN_VERSION: String by settings - val RELEASE_PLUGIN_VERSION: String by settings - val NEXUS_PUBLISH_PLUGIN_VERSION: String by settings - val KTLINT_PLUGIN_VERSION: String by settings - val VALIDATOR_VERSION: String by settings - val DOKKA_VERSION: String by settings - val ORCHID_VERSION: String by settings - val KNIT_VERSION: String by settings - - plugins { - kotlin("jvm") version KOTLIN_VERSION - - id("net.researchgate.release") version RELEASE_PLUGIN_VERSION - id("io.github.gradle-nexus.publish-plugin") version NEXUS_PUBLISH_PLUGIN_VERSION - - id("org.jlleitschuh.gradle.ktlint") version KTLINT_PLUGIN_VERSION - id("org.jetbrains.kotlinx.binary-compatibility-validator") version VALIDATOR_VERSION - - id("org.jetbrains.dokka") version DOKKA_VERSION - id("com.eden.orchidPlugin") version ORCHID_VERSION +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + version("kotlin", "1.6.21") + version("ktlint", "0.45.2") + version("arrow.core", "1.0.1") // 1.1.2 + version("ksp", "1.6.21-1.0.5") + version("junit", "5.7.0") + + plugin("kotlin.jvm", "org.jetbrains.kotlin.jvm").versionRef("kotlin") + + plugin("release", "net.researchgate.release").version("2.6.0") + plugin("nexus", "io.github.gradle-nexus.publish-plugin").version("1.0.0") + + plugin("ktlint", "org.jlleitschuh.gradle.ktlint").version("10.3.0") + plugin("api", "org.jetbrains.kotlinx.binary-compatibility-validator").version("0.9.0") + + plugin("knit", "kotlinx-knit").version("0.2.3") + plugin("dokka", "org.jetbrains.dokka").versionRef("kotlin") + plugin("orchid", "com.eden.orchidPlugin").version("0.21.1") + + library("kotlin.stdlib", "org.jetbrains.kotlin", "kotlin-stdlib").withoutVersion() + library("coroutines.core", "org.jetbrains.kotlinx", "kotlinx-coroutines-core").version("1.6.1") + + // TODO: Swap to Kotlin testing library + library("junit.bom", "org.junit", "junit-bom").version("5.7.0") + library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").withoutVersion() + library("mockk", "io.mockk", "mockk").version("1.10.2") + + bundle("testing", listOf("junit.jupiter", "mockk")) + } } +} +pluginManagement { resolutionStrategy { eachPlugin { when (val id = requested.id.id) { - "kotlinx-knit" -> useModule("org.jetbrains.kotlinx:$id:${requested.version ?: KNIT_VERSION}") + "kotlinx-knit" -> useModule("org.jetbrains.kotlinx:$id:${requested.version!!}") } } } From a9b3423b534f6ff444aadbb69e33deb584e3fb60 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Thu, 12 May 2022 01:32:35 -0400 Subject: [PATCH 04/23] wip ksp --- build.gradle.kts | 8 +- compiler-plugin/build.gradle.kts | 47 +- .../com/intuit/hooks/plugin/HookClassInfo.kt | 30 -- .../intuit/hooks/plugin/HookValidations.kt | 67 --- .../kotlin/com/intuit/hooks/plugin/Hooks.kt | 97 ----- .../intuit/hooks/plugin/HooksMetaPlugin.kt | 12 - .../kotlin/com/intuit/hooks/plugin/Utility.kt | 38 -- .../plugin/{ => codegen}/CodeGeneration.kt | 25 +- .../com/intuit/hooks/plugin/ksp/HookInfo.kt | 40 ++ .../intuit/hooks/plugin/ksp/HooksProcessor.kt | 157 +++++++ .../com/intuit/hooks/plugin/ksp/Text.kt | 23 + .../plugin/{ => validation}/HookProperty.kt | 11 +- .../plugin/validation/HookValidations.kt | 94 ++++ ...ols.ksp.processing.SymbolProcessorProvider | 1 + ...otlin.compiler.plugin.CommandLineProcessor | 1 - ....kotlin.compiler.plugin.ComponentRegistrar | 1 - .../com/intuit/hooks/plugin/HookTest.kt | 406 ++++++++++-------- .../hooks/plugin/HookValidationErrors.kt | 342 +++++++-------- .../com/intuit/hooks/plugin/TestUtilities.kt | 13 - docs/build.gradle.kts | 4 +- example-library/build.gradle.kts | 49 +-- .../intuit/hooks/example/library/car/Car.kt | 21 +- .../example/library/generic/GenericHooks.kt | 20 +- .../src/test/kotlin/CarHooksTest.kt | 10 + .../src/test/kotlin/CompilerPluginTest.kt | 4 +- hooks/build.gradle.kts | 4 +- .../main/kotlin/com/intuit/hooks/dsl/Hooks.kt | 104 ++++- settings.gradle.kts | 28 +- 28 files changed, 927 insertions(+), 730 deletions(-) delete mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HookClassInfo.kt delete mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HookValidations.kt delete mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Hooks.kt delete mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HooksMetaPlugin.kt delete mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Utility.kt rename compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/{ => codegen}/CodeGeneration.kt (89%) create mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HookInfo.kt create mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt create mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt rename compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/{ => validation}/HookProperty.kt (82%) create mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt create mode 100644 compiler-plugin/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider delete mode 100644 compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor delete mode 100644 compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar delete mode 100644 compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/TestUtilities.kt diff --git a/build.gradle.kts b/build.gradle.kts index 9ff31e3..ffcce1a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -83,6 +83,7 @@ subprojects { plugin("jacoco") plugin("org.jlleitschuh.gradle.ktlint") } + jacoco { toolVersion = "0.8.7" } @@ -159,13 +160,6 @@ subprojects { } } - ktlint { - filter { - exclude("**/*Impl.kt") - exclude("**/example/**/*.kt") - } - } - configure { sourceCompatibility = JavaVersion.VERSION_1_8 withSourcesJar() diff --git a/compiler-plugin/build.gradle.kts b/compiler-plugin/build.gradle.kts index 91944e9..7d79372 100644 --- a/compiler-plugin/build.gradle.kts +++ b/compiler-plugin/build.gradle.kts @@ -1,41 +1,12 @@ dependencies { -// val ARROW_VERSION: String by project -// val ARROW_META_VERSION: String by project -// val KTLINT_VERSION: String by project -// -// compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable") -// compileOnly("io.arrow-kt:arrow-meta:$ARROW_META_VERSION") -// compileOnly("io.arrow-kt:arrow-core:$ARROW_VERSION") -// compileOnly("io.arrow-kt:arrow-annotations:$ARROW_VERSION") -// compileOnly("com.pinterest.ktlint:ktlint-core:$KTLINT_VERSION") -// compileOnly("com.pinterest.ktlint:ktlint-ruleset-standard:$KTLINT_VERSION") -// -// testImplementation(platform("org.junit:junit-bom:5.7.0")) -// testImplementation("org.junit.jupiter", "junit-jupiter") -// -// testImplementation(project(":hooks")) -// testImplementation("io.arrow-kt:arrow-meta:$ARROW_META_VERSION") -// testImplementation("io.arrow-kt:arrow-meta-test:$ARROW_META_VERSION") -// testImplementation("com.pinterest.ktlint:ktlint-core:$KTLINT_VERSION") -// testImplementation("com.pinterest.ktlint:ktlint-ruleset-standard:$KTLINT_VERSION") -// testRuntimeOnly("io.arrow-kt:arrow-meta-prelude:$ARROW_META_VERSION") -// testRuntimeOnly("io.arrow-kt:arrow-core:$ARROW_VERSION") { -// exclude("org.jetbrains.kotlin") -// } -} + implementation(libs.ksp.spa) + implementation(libs.ksp.poet) + implementation(libs.ktlint.core) + implementation(libs.ktlint.ruleset.standard) + implementation(libs.arrow.core) -tasks { - jar { - from( - sourceSets.main.get().compileClasspath.filter { dependency -> - listOf( - "arrow-kt", - "ktlint", - "ec4j" - ).any { - dependency.absolutePath.contains(it) - } - }.map(::zipTree) - ) - } + testImplementation(project(":hooks")) + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.bundles.testing) + testImplementation(libs.ksp.testing) } diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HookClassInfo.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HookClassInfo.kt deleted file mode 100644 index e6fcab5..0000000 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HookClassInfo.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.intuit.hooks.plugin - -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.visibilityModifier - -internal data class HookSignature(val typeReference: KtTypeReference) { - val functionType get() = typeReference.typeElement as KtFunctionType - // todo: we should potentially validate anything with a !! in it - val returnType get() = functionType.returnTypeReference?.text!! - val returnTypeType = functionType.returnTypeReference?.typeElement?.typeArgumentsAsTypes?.firstOrNull()?.text - val nullableReturnTypeType = "${returnTypeType}${if (returnTypeType?.last() == '?') "" else "?"}" - - override fun toString(): String = typeReference.text -} - -internal data class HookClassInfo( - val property: KtProperty, - val hookSignature: HookSignature, - val hookType: HookType, - val params: List -) { - val zeroArity get() = params.isEmpty() - - fun toCodeGen(): HookCodeGen { - // todo: we should potentially validate anything with a !! in it - val propertyName = property.name!! - val visibility = property.visibilityModifier()?.text ?: "" - return HookCodeGen(hookType, propertyName, params, hookSignature, zeroArity, visibility) - } -} diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HookValidations.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HookValidations.kt deleted file mode 100644 index 2b48e0b..0000000 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HookValidations.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.intuit.hooks.plugin - -import arrow.core.* -import org.jetbrains.kotlin.com.intellij.psi.PsiElement -import org.jetbrains.kotlin.psi.KtCallExpression -import org.jetbrains.kotlin.psi.KtFunctionType -import org.jetbrains.kotlin.psi.KtParameter -import org.jetbrains.kotlin.psi.KtProperty - -internal sealed class HookValidationError(val msg: String, val property: PsiElement) { - data class AsyncHookWithoutSuspend(val h: HookClassInfo) : HookValidationError("Async hooks must be defined with a suspend function signature", h.property) - data class WaterfallMustHaveParameters(val h: HookClassInfo) : HookValidationError("Waterfall hooks must take at least one parameter", h.property) - data class WaterfallParameterTypeMustMatch(val h: HookClassInfo) : HookValidationError("Waterfall hooks must specify the same types for the first parameter and the return type", h.property) - data class MustBeInitializedWithDSLMethod(val p: KtProperty) : HookValidationError("${p.name} property needs to be initialized with a DSL method", p) - data class MustBeHookTypeSignature(val p: KtProperty) : HookValidationError("${p.name} property requires a hook type signature", p) - data class NoCodeGenerator(val superType: String, val p: KtProperty) : HookValidationError("This hook plugin has no code generator for $superType", p) -} - -internal fun validateHook(property: KtProperty): Validated, HookCodeGen> = - validateHookType(property).withEither { e -> - e.flatMap { h -> validateHookProperties(h).toEither() } - } - -private fun validateHookProperties(hook: HookClassInfo) = - hook.hookType.properties.map { it.validate(hook) } - .sequenceValidated() - .map { hook.toCodeGen() } - -private fun validateHookType(property: KtProperty): ValidatedNel { - val ktCallExpression = property.initializer as? KtCallExpression - return mustBeInitializedWithDSLMethod(property, ktCallExpression).zip( - mustBeHookType(property, ktCallExpression), - hasCodeGenerator(property, ktCallExpression), - validateParameters(property, ktCallExpression), - ) { _, signature, hookType, parameters -> - HookClassInfo(property, signature, hookType, parameters) - } -} - -private fun validateParameters(property: KtProperty, ktCallExpression: KtCallExpression?): ValidatedNel> { - val typeReference = ktCallExpression?.typeArguments?.firstOrNull()?.typeReference - val functionType = typeReference?.typeElement as? KtFunctionType - val params = functionType?.parameters?.mapIndexed { i: Int, p: KtParameter -> - val name = p.name - val type = p.typeReference!!.text - HookParameter(name, type, i) - } - return params?.valid() ?: HookValidationError.MustBeHookTypeSignature(property).invalidNel() -} - -private fun hasCodeGenerator(property: KtProperty, ktCallExpression: KtCallExpression?): ValidatedNel { - val superType = ktCallExpression?.calleeExpression?.text?.capitalizeFirstLetter() ?: "" - return try { - HookType.valueOf(superType).valid() - } catch (e: Exception) { - HookValidationError.NoCodeGenerator(superType, property).invalidNel() - } -} - -private fun mustBeHookType(property: KtProperty, ktCallExpression: KtCallExpression?): ValidatedNel { - val typeReference = ktCallExpression?.typeArguments?.firstOrNull()?.typeReference - val hookSignature = typeReference?.let(::HookSignature) - return hookSignature?.valid() ?: HookValidationError.MustBeHookTypeSignature(property).invalidNel() -} - -private fun mustBeInitializedWithDSLMethod(property: KtProperty, ktCallExpression: KtCallExpression?): ValidatedNel = - ktCallExpression?.valid() ?: HookValidationError.MustBeInitializedWithDSLMethod(property).invalidNel() diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Hooks.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Hooks.kt deleted file mode 100644 index c190f6b..0000000 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Hooks.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.intuit.hooks.plugin - -import arrow.core.sequenceValidated -import arrow.core.unzip -import arrow.core.valueOr -import arrow.meta.CliPlugin -import arrow.meta.Meta -import arrow.meta.invoke -import arrow.meta.phases.analysis.getOrCreateBaseDirectory -import arrow.meta.quotes.Transform -import arrow.meta.quotes.classDeclaration -import arrow.meta.quotes.classorobject.ClassDeclaration -import com.pinterest.ktlint.core.KtLint.Params -import com.pinterest.ktlint.core.KtLint.format -import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider -import org.jetbrains.kotlin.psi.KtClass -import org.jetbrains.kotlin.psi.KtImportList -import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject -import java.nio.file.Paths - -internal val Meta.hooks: CliPlugin - get() = - "Hooks" { - meta( - classDeclaration(this, { this.element.isHooksDslClass }) { c -> - findHooks().map> { codeGen -> - val `class` = c.element - val file = `class`.containingKtFile - val `package` = file.packageDirective?.text ?: "" - val (classes, properties) = codeGen.map(::generateHookClass).unzip() - val imports = createImportDirectives(`class`, codeGen) - val filePath = getOrCreateBaseDirectory(configuration).toPath().resolve( - Paths.get( - "", - *file.packageFqName.asString().split(".").toTypedArray() - ) - ) - - val name = "${if (`class`.isTopLevel()) "" else - `class`.containingClassOrObject?.name ?: ""}${name}Impl" - - val newSource = - """|${`package`} - | - |$imports - | - |${visibility ?: ""} $kind $name$`(typeParameters)` : ${value.fqName}$`(typeParameters)`() { - | ${properties.map { it.property(null) }.joinToString("\n")} - | ${classes.map { it.`class` }.joinToString("\n")} - |}""".trimMargin() - - val formatted = Params( - text = newSource, - ruleSets = listOf(StandardRuleSetProvider().get()), - cb = { _, _ -> } - ) - .let(::format) - .file(name, filePath.toString()) - - Transform.newSources(formatted) - }.valueOr { - reportHookErrors(it) - Transform.empty - } - } - ) - } - -private fun generateHookClass(hookCodeGen: HookCodeGen): Pair { - val classDefinition = hookCodeGen.generateClass() - val propertyDefinition = hookCodeGen.generateProperty() - - return classDefinition to propertyDefinition -} - -private fun createImportDirectives(c: KtClass, codeGens: List): String { - val existingImports = c.containingKtFile.importList - ?.removeHooksDslImport() - ?.map { it.text ?: "" } - ?: emptyList() - - val newImports = codeGens.flatMap { it.generateImports() } - - val hookStarImport = listOf("import com.intuit.hooks.*") - - return (hookStarImport + existingImports + newImports) - .distinct() - .joinToString("\n") -} - -private fun KtImportList.removeHooksDslImport() = - imports.filter { !it.text.contains("com.intuit.hooks.dsl.") } - -private fun ClassDeclaration.findHooks() = - body.properties.value - .map(::validateHook) - .sequenceValidated() diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HooksMetaPlugin.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HooksMetaPlugin.kt deleted file mode 100644 index c1c7887..0000000 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HooksMetaPlugin.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.intuit.hooks.plugin - -import arrow.meta.CliPlugin -import arrow.meta.Meta -import arrow.meta.MetaCliProcessor -import arrow.meta.phases.CompilerContext - -public open class HooksMetaPlugin : Meta { - override fun intercept(ctx: CompilerContext): List = listOf(hooks) -} - -public class HooksMetaCliProcessor : MetaCliProcessor("hooks") diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Utility.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Utility.kt deleted file mode 100644 index 9e722d3..0000000 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/Utility.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.intuit.hooks.plugin - -import arrow.core.NonEmptyList -import arrow.meta.phases.CompilerContext -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity -import org.jetbrains.kotlin.cli.common.messages.MessageCollector -import org.jetbrains.kotlin.com.intellij.psi.PsiElement -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getSuperNames - -internal val KtClass.isHooksDslClass get() = hasHooksDslSupertype && containsHooksDslImport - -private val KtClass.hasHooksDslSupertype get() = getSuperNames().any { - it == "Hooks" || it == "HooksDsl" -} - -private val KtClass.containsHooksDslImport get() = containingKtFile.importList?.imports?.any { - it.text.contains("com.intuit.hooks.dsl.") -} ?: false - -internal fun String.capitalizeFirstLetter(): String = this.mapIndexed { i, c -> if (i == 0) c.uppercaseChar() else c }.joinToString(separator = "") - -internal fun CompilerContext.reportHookErrors(invalid: NonEmptyList) = - invalid.forEach { messageCollector?.report(it) } - -private fun MessageCollector.report(hookValidationError: HookValidationError) = - report(CompilerMessageSeverity.ERROR, hookValidationError.msg, hookValidationError.property) - -private fun MessageCollector.report(severity: CompilerMessageSeverity, message: String, psiElement: PsiElement) { - val location = CompilerMessageLocation.create( - psiElement.containingFile.virtualFile.path, - psiElement.textRangeInParent.startOffset, // todo: this might not be right...like at all? - psiElement.textRangeInParent.endOffset, - psiElement.text - ) - this.report(severity, message, location) -} diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/CodeGeneration.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/CodeGeneration.kt similarity index 89% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/CodeGeneration.kt rename to compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/CodeGeneration.kt index 5bd95a8..0fcc006 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/CodeGeneration.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/CodeGeneration.kt @@ -1,4 +1,19 @@ -package com.intuit.hooks.plugin +package com.intuit.hooks.plugin.codegen + +import com.intuit.hooks.plugin.validation.HookProperty + + +internal data class HookSignature( + val text: String, + val isSuspend: Boolean, + val returnType: String, + /** For hooks that return a wrapped result, like [BailResult], this is the inner type */ + val returnTypeType: String?, +) { + val nullableReturnTypeType = "${returnTypeType}${if (returnTypeType?.last() == '?') "" else "?"}" + + override fun toString() = text +} internal class HookParameter(private val name: String?, val type: String, private val position: Int) { val withType get() = "$withoutType: $type" @@ -14,8 +29,8 @@ internal data class HookCodeGen( val visibility: String, ) { val tapMethod get() = if (!zeroArity) """ - public fun tap(name: String, f: ($hookSignature)): String? = tap(name, generateRandomId(), f) - public fun tap(name: String, id: String, f: ($hookSignature)): String? = super.tap(name, id) { _: HookContext, $paramsWithTypes -> f($paramsWithoutTypes) } + public fun tap(name: String, f: $hookSignature): String? = tap(name, generateRandomId(), f) + public fun tap(name: String, id: String, f: $hookSignature): String? = super.tap(name, id) { _: HookContext, $paramsWithTypes -> f($paramsWithoutTypes) } """.trimIndent() else "" val paramsWithTypes get() = params.joinToString(", ") { it.withType } val paramsWithoutTypes get() = params.joinToString(", ") { it.withoutType } @@ -26,7 +41,7 @@ internal data class HookCodeGen( private val isAsync get() = this.hookType.properties.contains(HookProperty.Async) val superType get() = this.hookType.toString() - val className get() = "${this.propertyName.capitalizeFirstLetter()}$superType" + val className get() = "${this.propertyName.replaceFirstChar(Char::titlecase)}$superType" val typeParameter get() = "(${if (isAsync) "suspend " else ""}(HookContext, $paramsWithTypes) -> ${hookSignature.returnType})" val interceptParameter get() = "${if (isAsync) "suspend " else ""}(HookContext, $paramsWithTypes) -> Unit" } @@ -137,5 +152,5 @@ internal enum class HookType(vararg val properties: HookProperty) { } }; - open fun generateClass(codeGen: HookCodeGen): String = TODO() + abstract fun generateClass(codeGen: HookCodeGen): String } diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HookInfo.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HookInfo.kt new file mode 100644 index 0000000..f31078c --- /dev/null +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HookInfo.kt @@ -0,0 +1,40 @@ +package com.intuit.hooks.plugin.ksp + +import com.google.devtools.ksp.getVisibility +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSCallableReference +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.intuit.hooks.plugin.codegen.HookCodeGen +import com.intuit.hooks.plugin.codegen.HookParameter +import com.intuit.hooks.plugin.codegen.HookSignature +import com.intuit.hooks.plugin.codegen.HookType + + +/** Intermediate KSP validation holder for aggregated hook info */ +internal data class HookClassInfo( + val property: KSPropertyDeclaration, + val hookSignature: HookSignature, + val hookType: HookType, + val params: List +) { + val zeroArity get() = params.isEmpty() + + fun toCodeGen(): HookCodeGen { + val propertyName = property.simpleName.asString() + val visibility = property.getVisibility().name.lowercase() + return HookCodeGen(hookType, propertyName, params, hookSignature, zeroArity, visibility) + } +} + +/** Wrapper for [KSAnnotation] when we're sure that the annotation is a hook annotation */ +@JvmInline internal value class HookAnnotation(val symbol: KSAnnotation) { + val hookFunctionSignatureType get() = symbol.annotationType.element?.typeArguments?.single()?.type + ?: throw HooksProcessor.Exception("Could not determine hook function signature type for $symbol") + + val hookFunctionSignatureReference get() = hookFunctionSignatureType.element as? KSCallableReference + ?: throw HooksProcessor.Exception("Hook type argument must be a function for $symbol") + + val type get() = toString().let(HookType::valueOf) + + override fun toString() = "${symbol.shortName.asString()}Hook" +} diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt new file mode 100644 index 0000000..3805b24 --- /dev/null +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt @@ -0,0 +1,157 @@ +package com.intuit.hooks.plugin.ksp + +import arrow.core.sequenceValidated +import arrow.core.valueOr +import arrow.typeclasses.Semigroup +import com.google.devtools.ksp.getVisibility +import com.google.devtools.ksp.processing.* +import com.google.devtools.ksp.symbol.* +import com.google.devtools.ksp.validate +import com.intuit.hooks.plugin.validation.annotationDslMarkers +import com.intuit.hooks.plugin.codegen.HookCodeGen +import com.intuit.hooks.plugin.validation.validateHook +import com.pinterest.ktlint.core.KtLint +import com.pinterest.ktlint.core.KtLint.format +import com.pinterest.ktlint.core.api.FeatureInAlphaState +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider + +public class HooksProcessor( + private val codeGenerator: CodeGenerator, + private val logger: KSPLogger, +) : SymbolProcessor { + + override fun process(resolver: Resolver): List { + resolver.getNewFiles().forEach { + it.accept(HooksVisitor(), Unit) + } + + return emptyList() + } + + private inner class HooksVisitor : KSVisitorVoid() { + + override fun visitFile(file: KSFile, data: Unit) { + file.declarations.filter { + it is KSClassDeclaration + }.forEach { + it.accept(this, Unit) + } + } + + @OptIn(FeatureInAlphaState::class) + override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { + // TODO: This should really be restructured to follow KSP visitor pattern for members + val superTypeNames = classDeclaration.superTypes + .map(KSTypeReference::element) + .filterIsInstance() + .map(KSClassifierReference::referencedName) + + // TODO: Account for import aliases :P + how to avoid false positives? + if (!superTypeNames.contains("Hooks") && !superTypeNames.contains("HooksDsl")) { + classDeclaration.declarations.filter { + it is KSClassDeclaration && it.validate() + }.forEach { + it.accept(this, Unit) + } + } else classDeclaration.findHooks().map { codeGen -> + if (codeGen.firstOrNull() == null) return@map + + val packageName = classDeclaration.packageName + val (classes, properties) = codeGen.map(::generateHookClass).unzip() + val imports = createImportDirectives(classDeclaration, codeGen.toList()) + + val visibility = classDeclaration.getVisibility().name.lowercase() + val kind = classDeclaration.classKind.name.lowercase() + val name = + "${classDeclaration.parentDeclaration?.simpleName?.asString() ?: ""}${classDeclaration.simpleName.asString()}Impl" + val typeParameters = if (classDeclaration.typeParameters.isEmpty()) "" else "<${ + classDeclaration.typeParameters.joinToString(separator = ", ") { it.simpleName.asString() } + }>" + val fqName = classDeclaration.qualifiedName!!.asString() + + val newSource = + """|${packageName.asString().takeIf(String::isNotEmpty)?.let { "package $it" } ?: ""} + | + |$imports + | + |${visibility} $kind $name$typeParameters : ${fqName}$typeParameters() { + | ${properties.joinToString("\n", "\n", "\n") { it }} + | ${classes.joinToString("\n", "\n", "\n") { it }} + |}""".trimMargin() + + val file = codeGenerator.createNewFile( + Dependencies(true, classDeclaration.containingFile!!), + packageName.asString(), + name, + ) + + logger.logging("""raw generated source: + |$newSource + """.trimMargin(), classDeclaration) + + KtLint.ExperimentalParams( + text = newSource, + ruleSets = listOf(StandardRuleSetProvider().get()), + cb = { _, _ -> } + ) + .let(::format) + .also { + logger.logging("""formatted generated source: + |$it + """.trimMargin(), classDeclaration) + } + .let(String::toByteArray) + .let(file::write) + + file.close() + }.valueOr { errors -> + errors.forEach { logger.error(it.message, it.symbol) } + } + } + } + + private fun KSClassDeclaration.findHooks() = getAllProperties() + .filter { + // Only generate hooks for properties that use our annotation + it.annotations.map(KSAnnotation::shortName) + .map(KSName::asString) + .filter(annotationDslMarkers::contains) + .toList() + .isNotEmpty() + } + .map(::validateHook) + .sequenceValidated(Semigroup.nonEmptyList()) + + private fun generateHookClass(hookCodeGen: HookCodeGen): Pair { + val classDefinition = hookCodeGen.generateClass() + val propertyDefinition = hookCodeGen.generateProperty() + + return classDefinition to propertyDefinition + } + + private fun createImportDirectives(classDeclaration: KSClassDeclaration, codeGens: List): String { + val existingImports = emptyList() // TODO: Get imports -- this might be fixed with kotlin poet? +// classDeclaration.containingFile?. +// ?.removeHooksDslImport() +// ?.map { it.text ?: "" } +// ?: emptyList() + + val newImports = codeGens.flatMap { it.generateImports() } + + val hookStarImport = listOf("import com.intuit.hooks.*") + + return (hookStarImport + existingImports + newImports) + .distinct() + .joinToString("\n") + } + + public class Provider : SymbolProcessorProvider { + + override fun create(environment: SymbolProcessorEnvironment): HooksProcessor = HooksProcessor( + environment.codeGenerator, + environment.logger, + ) + } + + public class Exception(message: String, cause: Throwable? = null) : kotlin.Exception(message, cause) +} diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt new file mode 100644 index 0000000..ee1ca3a --- /dev/null +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt @@ -0,0 +1,23 @@ +package com.intuit.hooks.plugin.ksp + +import com.google.devtools.ksp.symbol.* + +internal val KSTypeArgument.text: String get() = when (variance) { + Variance.STAR -> variance.label + // type should always be defined if not star projected + Variance.INVARIANT -> type!!.text + else -> "${variance.label} ${type!!.text}" +} + +internal val List.text: String get() = if (isEmpty()) "" else + "<${joinToString(transform = KSTypeArgument::text)}>" + +internal val KSTypeReference.text: String get() = element?.let { + when (it) { + // Use lambda type shorthand + is KSCallableReference -> "${if (this.modifiers.contains(Modifier.SUSPEND)) "suspend " else ""}(${ + it.functionParameters.map(KSValueParameter::type).joinToString(transform = KSTypeReference::text) + }) -> ${it.returnType.text}" + else -> "$it${it.typeArguments.text}" + } +} ?: throw HooksProcessor.Exception("element was null, cannot translate KSTypeReference to code text: $this") diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HookProperty.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookProperty.kt similarity index 82% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HookProperty.kt rename to compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookProperty.kt index e6ababc..0b7fe1b 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/HookProperty.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookProperty.kt @@ -1,14 +1,17 @@ -package com.intuit.hooks.plugin +package com.intuit.hooks.plugin.validation -import arrow.core.* -import org.jetbrains.kotlin.psi.psiUtil.hasSuspendModifier +import arrow.core.ValidatedNel +import arrow.core.invalidNel +import arrow.core.valid +import arrow.core.zip +import com.intuit.hooks.plugin.ksp.HookClassInfo internal enum class HookProperty { Bail, Loop, Async { override fun validate(hookClassInfo: HookClassInfo): ValidatedNel { - return if (hookClassInfo.hookSignature.typeReference.modifierList?.hasSuspendModifier() == true) this.valid() + return if (hookClassInfo.hookSignature.isSuspend) this.valid() else HookValidationError.AsyncHookWithoutSuspend(hookClassInfo).invalidNel() } }, diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt new file mode 100644 index 0000000..e555872 --- /dev/null +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt @@ -0,0 +1,94 @@ +package com.intuit.hooks.plugin.validation + +import arrow.core.* +import com.google.devtools.ksp.symbol.* +import com.intuit.hooks.plugin.codegen.HookCodeGen +import com.intuit.hooks.plugin.codegen.HookParameter +import com.intuit.hooks.plugin.codegen.HookSignature +import com.intuit.hooks.plugin.codegen.HookType +import com.intuit.hooks.plugin.ksp.HookAnnotation +import com.intuit.hooks.plugin.ksp.HookClassInfo +import com.intuit.hooks.plugin.ksp.text + +// TODO: It'd be nice if the validations were compiler plugin framework agnostic +internal sealed class HookValidationError(val message: String, var symbol: KSNode? = null) { + class AsyncHookWithoutSuspend(hookClassInfo: HookClassInfo) : HookValidationError("Async hooks must be defined with a suspend function signature", hookClassInfo.property) + class WaterfallMustHaveParameters(hookClassInfo: HookClassInfo) : HookValidationError("Waterfall hooks must take at least one parameter", hookClassInfo.property) + class WaterfallParameterTypeMustMatch(hookClassInfo: HookClassInfo) : HookValidationError("Waterfall hooks must specify the same types for the first parameter and the return type", hookClassInfo.property) + class MustBeHookTypeSignature(annotation: HookAnnotation) : HookValidationError("$annotation property requires a hook type signature", annotation.symbol) + class NoCodeGenerator(annotation: HookAnnotation) : HookValidationError("This hook plugin has no code generator for $annotation", annotation.symbol) + class MustOnlyHaveSingleDslAnnotation(annotations: List, property: KSPropertyDeclaration) : HookValidationError("This hook has more than a single hook DSL annotation: $annotations", property) +} + +internal fun validateHook(property: KSPropertyDeclaration): Validated, HookCodeGen> = + validateHookType(property).withEither { e -> + e.flatMap { h -> validateHookProperties(h).toEither() } + }.mapLeft { invalid -> + // Attach property to validations that don't have a symbol attached + invalid.onEach { + it.symbol = it.symbol ?: property + } + } + +private fun validateHookProperties(hook: HookClassInfo) = + hook.hookType.properties.map { it.validate(hook) } + .sequenceValidated() + .map { hook.toCodeGen() } + +internal val annotationDslMarkers = listOf( + "Sync", + "SyncBail", + "SyncWaterfall", + "SyncLoop", + "AsyncParallel", + "AsyncParallelBail", + "AsyncSeries", + "AsyncSeriesBail", + "AsyncSeriesWaterfall", + "AsyncSeriesLoop", +) + +private fun validateHookType(property: KSPropertyDeclaration): ValidatedNel = onlyHasASingleDslAnnotation(property).withEither { + val hookClassInfo = ::HookClassInfo.partially1(property) + it.flatMap { annotation -> + validateHookAnnotation(annotation, hookClassInfo).toEither() + } +} + +private fun validateHookAnnotation(annotation: HookAnnotation, factory: (HookSignature, HookType, List) -> HookClassInfo): ValidatedNel = mustBeHookType(annotation).zip( + hasCodeGenerator(annotation), + validateParameters(annotation), + factory, +) + +private fun validateParameters(annotation: HookAnnotation): ValidatedNel> = try { + annotation.hookFunctionSignatureReference.functionParameters.mapIndexed { index: Int, parameter: KSValueParameter -> + HookParameter(parameter.name?.asString(), parameter.type.text, index) + }.valid() +} catch (exception: Exception) { + HookValidationError.MustBeHookTypeSignature(annotation).invalidNel() +} + +private fun hasCodeGenerator(annotation: HookAnnotation): ValidatedNel = try { + annotation.type.valid() +} catch (e: Exception) { + HookValidationError.NoCodeGenerator(annotation).invalidNel() +} + +private fun mustBeHookType(annotation: HookAnnotation): ValidatedNel = try { + HookSignature( + annotation.hookFunctionSignatureType.text, + annotation.hookFunctionSignatureType.modifiers.contains(Modifier.SUSPEND), + annotation.hookFunctionSignatureReference.returnType.text, + annotation.hookFunctionSignatureReference.returnType.element?.typeArguments?.firstOrNull()?.text, + ).valid() +} catch (exception: Exception) { + HookValidationError.MustBeHookTypeSignature(annotation).invalidNel() +} + + +private fun onlyHasASingleDslAnnotation(property: KSPropertyDeclaration): ValidatedNel { + val annotations = property.annotations.filter { it.shortName.asString() in annotationDslMarkers }.toList() + if (annotations.size != 1) return HookValidationError.MustOnlyHaveSingleDslAnnotation(annotations, property).invalidNel() + return annotations.single().let(::HookAnnotation).valid() +} \ No newline at end of file diff --git a/compiler-plugin/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/compiler-plugin/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 0000000..8a872b3 --- /dev/null +++ b/compiler-plugin/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +com.intuit.hooks.plugin.ksp.HooksProcessor$Provider \ No newline at end of file diff --git a/compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor b/compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor deleted file mode 100644 index a1703b3..0000000 --- a/compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor +++ /dev/null @@ -1 +0,0 @@ -com.intuit.hooks.plugin.HooksMetaCliProcessor \ No newline at end of file diff --git a/compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar b/compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar deleted file mode 100644 index 3d8d3b8..0000000 --- a/compiler-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +++ /dev/null @@ -1 +0,0 @@ -com.intuit.hooks.plugin.HooksMetaPlugin diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt index c4fdc50..ee133ea 100644 --- a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt +++ b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt @@ -1,189 +1,245 @@ package com.intuit.hooks.plugin -import arrow.meta.plugin.testing.CompilerTest -import arrow.meta.plugin.testing.assertThis +import com.intuit.hooks.plugin.ksp.HooksProcessor +import com.tschuchort.compiletesting.KotlinCompilation +import com.tschuchort.compiletesting.SourceFile +import com.tschuchort.compiletesting.addPreviousResultToClasspath +import com.tschuchort.compiletesting.symbolProcessorProviders +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test -class HookTest { + +class HooksProcessorTest { + + fun compile(vararg sources: SourceFile, block: KotlinCompilation.() -> Unit = { + symbolProcessorProviders = listOf(HooksProcessor.Provider()) + inheritClassPath = true + }): KotlinCompilation.Result = KotlinCompilation().apply { + this.sources = sources.toList() + block() + }.compile() + @Test fun testSyncHookCalled() { - val testHookClass = - """ -|package com.intuit.hooks.plugin.test -|import com.intuit.hooks.dsl.Hooks -| -|abstract class TestHooks : Hooks() { -| open val testSyncHook = syncHook<(String) -> Unit>() -|} -""" - val testHookCall = - """ -|fun testHook() : Boolean { -| var tapCalled = false -| val hooks = TestHooksImpl() -| hooks.testSyncHook.tap("test") { _, x -> tapCalled = true } -| hooks.testSyncHook.call("hello") -| return tapCalled -|}""" - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { - (testHookClass + testHookCall).source - }, - - assert = { - "testHook()".source.evalsTo(true) - } - ) - ) - } + val testHookClass = SourceFile.kotlin("TestHooks.kt", """ + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @Sync<(String) -> Unit>() + abstract val testSyncHook: SyncHook<*> + } + """.trimIndent()) - @Test - fun testAsyncSeriesWaterfallHookCalled() { - val testHookClass = - """ -|package com.intuit.hooks.plugin.test -|import com.intuit.hooks.dsl.Hooks -|import kotlinx.coroutines.runBlocking -| -|abstract class TestHooks : Hooks() { -| open val testAsyncSeriesWaterfallHook = asyncSeriesWaterfallHook String>() -|} -""" - val testHookCall = - """ -|fun testHook() : Boolean { -| var tapCalled = false -| val hooks = TestHooksImpl() -| hooks.testAsyncSeriesWaterfallHook.tap("test") { x -> tapCalled = true; "asdf" } -| runBlocking { -| hooks.testAsyncSeriesWaterfallHook.call("someVal") -| } -| return tapCalled -|}""" - - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { - (testHookClass + testHookCall).source - }, - - assert = { - "testHook()".source.evalsTo(true) - } - ) - ) - } + val result = KotlinCompilation().apply { + symbolProcessorProviders = listOf(HooksProcessor.Provider()) + sources = listOf(testHookClass) + inheritClassPath = true + }.compile() - @Test - fun smokeTest() { - val allHooks = - """ - |package com.intuit.hooks.plugin.test - |import com.intuit.hooks.BailResult - |import com.intuit.hooks.LoopResult - |import com.intuit.hooks.dsl.Hooks - |import kotlinx.coroutines.ExperimentalCoroutinesApi - | - |abstract class GenericHooks : Hooks() { - | open val sync = syncHook<(newSpeed: Int) -> Unit>() - | open val syncBail = syncBailHook<(Boolean) -> BailResult>() - | open val syncLoop = syncLoopHook<(foo: Boolean) -> LoopResult>() - | open val syncWaterfall = syncWaterfallHook<(name: String) -> String>() - | @ExperimentalCoroutinesApi - | open val asyncParallelBail = asyncParallelBailHook BailResult>() - | open val asyncParallel = asyncParallelHook Int>() - | open val asyncSeries = asyncSeriesHook Int>() - | open val asyncSeriesBail = asyncSeriesBailHook BailResult>() - | open val asyncSeriesLoop = asyncSeriesLoopHook LoopResult>() - | open val asyncSeriesWaterfall = asyncSeriesWaterfallHook String>() - |} - """.trimIndent() - - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { (allHooks).source }, - assert = { - compiles - } - ) - ) - } + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) +// assertTrue(result.generatedFiles.map { it.name }.contains("TestHooksImpl.kt")) - @Test - fun testHookWithTypeParameter() { - val testHookClass = - """ -|package com.intuit.hooks.plugin.test -|import com.intuit.hooks.dsl.Hooks -| -|abstract class TestHooks : Hooks() { -| open val testSyncHook = syncHook<(T) -> Unit>() -|} -""" - val testHookCall = - """ -|fun testHook() : Boolean { -| var tapCalled = false -| val hooks = TestHooksImpl() -| hooks.testSyncHook.tap("test") { _, x -> tapCalled = true } -| hooks.testSyncHook.call("hello") -| return tapCalled -|}""" - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { - (testHookClass + testHookCall).source - }, - - assert = { - "testHook()".source.evalsTo(true) + val testHookCall = SourceFile.kotlin("Assertions.kt", """ + import com.intuit.hooks.* + + internal class TestHooksImpl : TestHooks() { + + override val testSyncHook: TestSyncHookSyncHook = TestSyncHookSyncHook() + + public inner class TestSyncHookSyncHook : SyncHook<((HookContext, p0: String) -> Unit)>() { + public fun call(p0: String): Unit = super.call { f, context -> f(context, p0) } + public fun tap(name: String, f: ((String) -> Unit)): String? = tap(name, generateRandomId(), f) + public fun tap(name: String, id: String, f: ((String) -> Unit)): String? = super.tap(name, id) { _: HookContext, p0: String -> f(p0) } } - ) - ) + } + + fun testHook(): Boolean { + var tapCalled = false + val hooks = TestHooksImpl() + hooks.testSyncHook.tap("test") { _, x -> tapCalled = true } + hooks.testSyncHook.call("hello") + return tapCalled + } + """) + + val assertionResult = KotlinCompilation().apply { + sources = listOf(testHookClass, testHookCall) + inheritClassPath = true + addPreviousResultToClasspath(result) + }.compile() + + assertEquals(KotlinCompilation.ExitCode.OK, assertionResult.exitCode) + val assertions = assertionResult.classLoader.loadClass("AssertionsKt") + assertions.declaredMethods.forEach { + it.isAccessible = true + assertTrue(it.invoke(null) as Boolean) + } } - @Test - fun `test nested hook class`() { - val controllerClass = - """ -|package com.intuit.hooks.plugin.test -|import com.intuit.hooks.dsl.Hooks -| -|class Controller { -| abstract class TestHooks : Hooks() { -| open val testSyncHook = syncHook<() -> Unit>() -| } -| -| val hooks = ControllerTestHooksImpl() -|} -""" - - val testScript = - """ -|fun testHook() : Boolean { -| var tapCalled = false -| val controller = Controller() -| controller.hooks.testSyncHook.tap("test") { _ -> tapCalled = true } -| controller.hooks.testSyncHook.call() -| return tapCalled -|}""" - - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { - (controllerClass + testScript).source - }, - - assert = { - "testHook()".source.evalsTo(true) - } - ) - ) + @Test fun `hook params are generic`() { + val testHookClass = SourceFile.kotlin("TestHooks.kt", """ + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @Sync<(Map, List>) -> Unit>() + abstract val testSyncHook: SyncHook<*> + } + """) + + val processorResult = compile(testHookClass) + assertEquals(KotlinCompilation.ExitCode.OK, processorResult.exitCode) + + TODO() } + +// @Test +// fun testAsyncSeriesWaterfallHookCalled() { +// val testHookClass = +// """ +//|package com.intuit.hooks.plugin.test +//|import com.intuit.hooks.dsl.Hooks +//|import kotlinx.coroutines.runBlocking +//| +//|abstract class TestHooks : Hooks() { +//| open val testAsyncSeriesWaterfallHook = asyncSeriesWaterfallHook String>() +//|} +//""" +// val testHookCall = +// """ +//|fun testHook() : Boolean { +//| var tapCalled = false +//| val hooks = TestHooksImpl() +//| hooks.testAsyncSeriesWaterfallHook.tap("test") { x -> tapCalled = true; "asdf" } +//| runBlocking { +//| hooks.testAsyncSeriesWaterfallHook.call("someVal") +//| } +//| return tapCalled +//|}""" +// +// assertThis( +// CompilerTest( +// config = { hookDependencies() }, +// code = { +// (testHookClass + testHookCall).source +// }, +// +// assert = { +// "testHook()".source.evalsTo(true) +// } +// ) +// ) +// } +// +// @Test +// fun smokeTest() { +// val allHooks = +// """ +// |package com.intuit.hooks.plugin.test +// |import com.intuit.hooks.BailResult +// |import com.intuit.hooks.LoopResult +// |import com.intuit.hooks.dsl.Hooks +// |import kotlinx.coroutines.ExperimentalCoroutinesApi +// | +// |abstract class GenericHooks : Hooks() { +// | open val sync = syncHook<(newSpeed: Int) -> Unit>() +// | open val syncBail = syncBailHook<(Boolean) -> BailResult>() +// | open val syncLoop = syncLoopHook<(foo: Boolean) -> LoopResult>() +// | open val syncWaterfall = syncWaterfallHook<(name: String) -> String>() +// | @ExperimentalCoroutinesApi +// | open val asyncParallelBail = asyncParallelBailHook BailResult>() +// | open val asyncParallel = asyncParallelHook Int>() +// | open val asyncSeries = asyncSeriesHook Int>() +// | open val asyncSeriesBail = asyncSeriesBailHook BailResult>() +// | open val asyncSeriesLoop = asyncSeriesLoopHook LoopResult>() +// | open val asyncSeriesWaterfall = asyncSeriesWaterfallHook String>() +// |} +// """.trimIndent() +// +// assertThis( +// CompilerTest( +// config = { hookDependencies() }, +// code = { (allHooks).source }, +// assert = { +// compiles +// } +// ) +// ) +// } +// +// @Test +// fun testHookWithTypeParameter() { +// val testHookClass = +// """ +//|package com.intuit.hooks.plugin.test +//|import com.intuit.hooks.dsl.Hooks +//| +//|abstract class TestHooks : Hooks() { +//| open val testSyncHook = syncHook<(T) -> Unit>() +//|} +//""" +// val testHookCall = +// """ +//|fun testHook() : Boolean { +//| var tapCalled = false +//| val hooks = TestHooksImpl() +//| hooks.testSyncHook.tap("test") { _, x -> tapCalled = true } +//| hooks.testSyncHook.call("hello") +//| return tapCalled +//|}""" +// assertThis( +// CompilerTest( +// config = { hookDependencies() }, +// code = { +// (testHookClass + testHookCall).source +// }, +// +// assert = { +// "testHook()".source.evalsTo(true) +// } +// ) +// ) +// } +// +// @Test +// fun `test nested hook class`() { +// val controllerClass = +// """ +//|package com.intuit.hooks.plugin.test +//|import com.intuit.hooks.dsl.Hooks +//| +//|class Controller { +//| abstract class TestHooks : Hooks() { +//| open val testSyncHook = syncHook<() -> Unit>() +//| } +//| +//| val hooks = ControllerTestHooksImpl() +//|} +//""" +// +// val testScript = +// """ +//|fun testHook() : Boolean { +//| var tapCalled = false +//| val controller = Controller() +//| controller.hooks.testSyncHook.tap("test") { _ -> tapCalled = true } +//| controller.hooks.testSyncHook.call() +//| return tapCalled +//|}""" +// +// assertThis( +// CompilerTest( +// config = { hookDependencies() }, +// code = { +// (controllerClass + testScript).source +// }, +// +// assert = { +// "testHook()".source.evalsTo(true) +// } +// ) +// ) +// } } diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt index ad65345..7cf62cf 100644 --- a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt +++ b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt @@ -1,171 +1,171 @@ -package com.intuit.hooks.plugin - -import arrow.meta.plugin.testing.CompilerTest -import arrow.meta.plugin.testing.assertThis -import org.junit.jupiter.api.Test - -class HookValidationErrors { - @Test - fun testNonInitializedHook() { - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { - """ - |package com.intuit.hooks.plugin.test - |import com.intuit.hooks.dsl.Hooks - |import com.intuit.hooks.SyncHook - | - |abstract class TestHooks : Hooks() { - | abstract var testAbstractHook: SyncHook<() -> Int> - |} - """.source - }, - assert = { - failsWith { it.contains("testAbstractHook property needs to be initialized") } - } - ) - ) - } - - @Test - fun testNonHookSignature() { - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { - """ - |package com.intuit.hooks.plugin.test - |import com.intuit.hooks.dsl.Hooks - |import com.intuit.hooks.SyncHook - | - |abstract class TestHooks : Hooks() { - | open val notAHook = false - |} - """.source - }, - assert = { - failsWith { it.contains("property needs to be initialized with a DSL method") } - } - ) - ) - } - - @Test - fun testMissingCodeGen() { - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { - """ - |package com.intuit.hooks.plugin.test - |import com.intuit.hooks.dsl.Hooks - |import com.intuit.hooks.SyncHook - | - |class NotActuallyAHook> : SyncHook() - |fun > Hooks.notActuallyAHook() : NotActuallyAHook = TODO() - | - |abstract class TestHooks : Hooks() { - | open val foo = notActuallyAHook<() -> Unit>() - |} - """.source - }, - assert = { - failsWith { it.contains("This hook plugin has no code generator for NotActuallyAHook") } - } - ) - ) - } - - @Test - fun testNonSuspendAsync() { - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { - """ - |package com.intuit.hooks.plugin.test - |import com.intuit.hooks.dsl.Hooks - | - |abstract class TestHooks : Hooks() { - | open val asyncSeries = asyncSeriesHook<(String) -> Int>() - |} - """.source - }, - assert = { - failsWith { it.contains("Async hooks must be defined with a suspend function signature") } - } - ) - ) - } - - @Test - fun testWaterfallZeroArity() { - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { - """ - |package com.intuit.hooks.plugin.test - |import com.intuit.hooks.dsl.Hooks - | - |abstract class TestHooks : Hooks() { - | open val syncWaterfall = syncWaterfallHook<() -> String>() - |} - """.source - }, - assert = { - failsWith { it.contains("Waterfall hooks must take at least one parameter") } - } - ) - ) - } - - @Test - fun multipleCompilerErrors() { - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { - """ - |package com.intuit.hooks.plugin.test - |import com.intuit.hooks.dsl.Hooks - | - |abstract class TestHooks : Hooks() { - | open val realBad = asyncSeriesWaterfallHook<() -> String>() - |} - """.source - }, - assert = { - allOf( - failsWith { it.contains("Async hooks must be defined with a suspend function signature") }, - failsWith { it.contains("Waterfall hooks must take at least one parameter") }, - failsWith { it.contains("Waterfall hooks must specify the same types for the first parameter and the return type") } - ) - } - ) - ) - } - - @Test - fun testWaterfallParameterReturnEquality() { - assertThis( - CompilerTest( - config = { hookDependencies() }, - code = { - """ - |package com.intuit.hooks.plugin.test - |import com.intuit.hooks.dsl.Hooks - | - |abstract class TestHooks : Hooks() { - | open val syncWaterfall = syncWaterfallHook<(String) -> Boolean>() - |} - """.source - }, - assert = { - failsWith { it.contains("Waterfall hooks must specify the same types for the first parameter and the return type") } - } - ) - ) - } -} +//package com.intuit.hooks.plugin +// +//import arrow.meta.plugin.testing.CompilerTest +//import arrow.meta.plugin.testing.assertThis +//import org.junit.jupiter.api.Test +// +//class HookValidationErrors { +// @Test +// fun testNonInitializedHook() { +// assertThis( +// CompilerTest( +// config = { hookDependencies() }, +// code = { +// """ +// |package com.intuit.hooks.plugin.test +// |import com.intuit.hooks.dsl.Hooks +// |import com.intuit.hooks.SyncHook +// | +// |abstract class TestHooks : Hooks() { +// | abstract var testAbstractHook: SyncHook<() -> Int> +// |} +// """.source +// }, +// assert = { +// failsWith { it.contains("testAbstractHook property needs to be initialized") } +// } +// ) +// ) +// } +// +// @Test +// fun testNonHookSignature() { +// assertThis( +// CompilerTest( +// config = { hookDependencies() }, +// code = { +// """ +// |package com.intuit.hooks.plugin.test +// |import com.intuit.hooks.dsl.Hooks +// |import com.intuit.hooks.SyncHook +// | +// |abstract class TestHooks : Hooks() { +// | open val notAHook = false +// |} +// """.source +// }, +// assert = { +// failsWith { it.contains("property needs to be initialized with a DSL method") } +// } +// ) +// ) +// } +// +// @Test +// fun testMissingCodeGen() { +// assertThis( +// CompilerTest( +// config = { hookDependencies() }, +// code = { +// """ +// |package com.intuit.hooks.plugin.test +// |import com.intuit.hooks.dsl.Hooks +// |import com.intuit.hooks.SyncHook +// | +// |class NotActuallyAHook> : SyncHook() +// |fun > Hooks.notActuallyAHook() : NotActuallyAHook = TODO() +// | +// |abstract class TestHooks : Hooks() { +// | open val foo = notActuallyAHook<() -> Unit>() +// |} +// """.source +// }, +// assert = { +// failsWith { it.contains("This hook plugin has no code generator for NotActuallyAHook") } +// } +// ) +// ) +// } +// +// @Test +// fun testNonSuspendAsync() { +// assertThis( +// CompilerTest( +// config = { hookDependencies() }, +// code = { +// """ +// |package com.intuit.hooks.plugin.test +// |import com.intuit.hooks.dsl.Hooks +// | +// |abstract class TestHooks : Hooks() { +// | open val asyncSeries = asyncSeriesHook<(String) -> Int>() +// |} +// """.source +// }, +// assert = { +// failsWith { it.contains("Async hooks must be defined with a suspend function signature") } +// } +// ) +// ) +// } +// +// @Test +// fun testWaterfallZeroArity() { +// assertThis( +// CompilerTest( +// config = { hookDependencies() }, +// code = { +// """ +// |package com.intuit.hooks.plugin.test +// |import com.intuit.hooks.dsl.Hooks +// | +// |abstract class TestHooks : Hooks() { +// | open val syncWaterfall = syncWaterfallHook<() -> String>() +// |} +// """.source +// }, +// assert = { +// failsWith { it.contains("Waterfall hooks must take at least one parameter") } +// } +// ) +// ) +// } +// +// @Test +// fun multipleCompilerErrors() { +// assertThis( +// CompilerTest( +// config = { hookDependencies() }, +// code = { +// """ +// |package com.intuit.hooks.plugin.test +// |import com.intuit.hooks.dsl.Hooks +// | +// |abstract class TestHooks : Hooks() { +// | open val realBad = asyncSeriesWaterfallHook<() -> String>() +// |} +// """.source +// }, +// assert = { +// allOf( +// failsWith { it.contains("Async hooks must be defined with a suspend function signature") }, +// failsWith { it.contains("Waterfall hooks must take at least one parameter") }, +// failsWith { it.contains("Waterfall hooks must specify the same types for the first parameter and the return type") } +// ) +// } +// ) +// ) +// } +// +// @Test +// fun testWaterfallParameterReturnEquality() { +// assertThis( +// CompilerTest( +// config = { hookDependencies() }, +// code = { +// """ +// |package com.intuit.hooks.plugin.test +// |import com.intuit.hooks.dsl.Hooks +// | +// |abstract class TestHooks : Hooks() { +// | open val syncWaterfall = syncWaterfallHook<(String) -> Boolean>() +// |} +// """.source +// }, +// assert = { +// failsWith { it.contains("Waterfall hooks must specify the same types for the first parameter and the return type") } +// } +// ) +// ) +// } +//} diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/TestUtilities.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/TestUtilities.kt deleted file mode 100644 index da35536..0000000 --- a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/TestUtilities.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.intuit.hooks.plugin - -import arrow.meta.plugin.testing.CompilerTest -import arrow.meta.plugin.testing.Config -import arrow.meta.plugin.testing.Dependency - -fun CompilerTest.Companion.hookDependencies(): List { - val hooks = Dependency("hooks") - val prelude = Dependency("arrow-meta-prelude") - val coroutines = Dependency("kotlinx-coroutines-core") - - return addDependencies(hooks, prelude, coroutines) + addMetaPlugins(HooksMetaPlugin()) -} diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts index 406a27f..c656e87 100644 --- a/docs/build.gradle.kts +++ b/docs/build.gradle.kts @@ -2,8 +2,8 @@ val ORCHID_VERSION: String by project // 1. Apply Orchid plugin plugins { - id("com.eden.orchidPlugin") - id("kotlinx-knit") + alias(libs.plugins.orchid) + alias(libs.plugins.knit) } fun DependencyHandlerScope.orchidImplementation(name: String, version: String = ORCHID_VERSION) = diff --git a/example-library/build.gradle.kts b/example-library/build.gradle.kts index b097103..9a7c7e4 100644 --- a/example-library/build.gradle.kts +++ b/example-library/build.gradle.kts @@ -1,49 +1,26 @@ -val COROUTINES_VERSION: String by project -val ARROW_VERSION: String by project - -val compilerPlugin by configurations.creating +plugins { + alias(libs.plugins.ksp) +} dependencies { - implementation(kotlin("stdlib")) - implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", COROUTINES_VERSION) + implementation(libs.kotlin.stdlib) + implementation(libs.kotlin.coroutines.core) api(project(":hooks")) - compilerPlugin(project(":compiler-plugin")) + implementation(project(":compiler-plugin")) + ksp(project(":compiler-plugin")) - testImplementation(platform("org.junit:junit-bom:5.7.0")) - testImplementation("org.junit.jupiter", "junit-jupiter") - testImplementation("io.mockk", "mockk", "1.10.2") -} - -val generatedSourcesRoot: String = "${buildDir.absolutePath}/generated/source/kapt/main" -sourceSets { - main { - java { - srcDir(generatedSourcesRoot) - } - } + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.bundles.testing) } kotlin { explicitApi() -} -tasks { - val cleanGenerated by registering { - group = "build" - delete(generatedSourcesRoot) + sourceSets.main { + kotlin.srcDir("build/generated/ksp/main/kotlin") } - - compileKotlin { - dependsOn(compilerPlugin) - dependsOn(cleanGenerated) - kotlinOptions { - verbose = true - freeCompilerArgs += listOf( - "-Xplugin=${compilerPlugin.resolve().first()}", - "-P", - "plugin:arrow.meta.plugin.compiler.hooks:generatedSrcOutputDir=$generatedSourcesRoot" - ) - } + sourceSets.test { + kotlin.srcDir("build/generated/ksp/test/kotlin") } } diff --git a/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt b/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt index a0e7299..f3795df 100644 --- a/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt +++ b/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt @@ -1,7 +1,10 @@ package com.intuit.hooks.example.library.car import com.intuit.hooks.AsyncSeriesWaterfallHook +import com.intuit.hooks.HookContext import com.intuit.hooks.SyncHook +import com.intuit.hooks.dsl.Hooks.AsyncSeriesWaterfall +import com.intuit.hooks.dsl.Hooks.Sync import com.intuit.hooks.dsl.HooksDsl @@ -9,13 +12,23 @@ public abstract class Location public class Route + public class Car { public abstract class Hooks : HooksDsl() { - public open val accelerate: SyncHook<*> = syncHook<(newSpeed: Int) -> Unit>() - public open val brake: SyncHook<*> = syncHook<() -> Unit>() - public open val calculateRoutes: AsyncSeriesWaterfallHook<*, *> = - asyncSeriesWaterfallHook, source: Location, target: Location) -> List>() + + @Sync<(newSpeed: Int) -> Unit>() + public abstract val accelerate: SyncHook<*> + + @Sync<() -> Unit>() + public abstract val brake: SyncHook<*> + + @AsyncSeriesWaterfall, + source: Location, + target: Location + ) -> List> + public abstract val calculateRoutes: AsyncSeriesWaterfallHook<*, *> } public val hooks: CarHooksImpl = CarHooksImpl() diff --git a/example-library/src/main/kotlin/com/intuit/hooks/example/library/generic/GenericHooks.kt b/example-library/src/main/kotlin/com/intuit/hooks/example/library/generic/GenericHooks.kt index 2bb2b83..90cc774 100644 --- a/example-library/src/main/kotlin/com/intuit/hooks/example/library/generic/GenericHooks.kt +++ b/example-library/src/main/kotlin/com/intuit/hooks/example/library/generic/GenericHooks.kt @@ -5,15 +5,15 @@ import com.intuit.hooks.dsl.Hooks import kotlinx.coroutines.ExperimentalCoroutinesApi internal abstract class GenericHooks : Hooks() { - open val sync = syncHook<(newSpeed: Int) -> Unit>() - open val syncBail = syncBailHook<(Boolean) -> BailResult>() - open val syncLoop = syncLoopHook<(foo: Boolean) -> LoopResult>() - open val syncWaterfall = syncWaterfallHook<(name: String) -> String>() + @Sync<(newSpeed: Int) -> Unit>() abstract val sync: SyncHook<*> + @SyncBail<(Boolean) -> BailResult>() abstract val syncBail: SyncBailHook<*, *> + @SyncLoop<(foo: Boolean) -> LoopResult>() abstract val syncLoop: SyncLoopHook<*, *> + @SyncWaterfall<(name: String) -> String>() abstract val syncWaterfall: SyncWaterfallHook<*, *> @ExperimentalCoroutinesApi - open val asyncParallelBail = asyncParallelBailHook BailResult>() - open val asyncParallel = asyncParallelHook Int>() - open val asyncSeries = asyncSeriesHook Int>() - open val asyncSeriesBail = asyncSeriesBailHook BailResult>() - open val asyncSeriesLoop = asyncSeriesLoopHook LoopResult>() - open val asyncSeriesWaterfall = asyncSeriesWaterfallHook String>() + @AsyncParallelBail BailResult>() abstract val asyncParallelBail: AsyncParallelBailHook<*, *> + @AsyncParallel Int>() abstract val asyncParallel: AsyncParallelHook<*> + @AsyncSeries Int>() abstract val asyncSeries: AsyncSeriesHook<*> + @AsyncSeriesBail BailResult>() abstract val asyncSeriesBail: AsyncSeriesBailHook<*, *> + @AsyncSeriesLoop LoopResult>() abstract val asyncSeriesLoop: AsyncSeriesLoopHook<*, *> + @AsyncSeriesWaterfall String>() abstract val asyncSeriesWaterfall: AsyncSeriesWaterfallHook<*, *> } diff --git a/example-library/src/test/kotlin/CarHooksTest.kt b/example-library/src/test/kotlin/CarHooksTest.kt index e44caf6..fd42b12 100644 --- a/example-library/src/test/kotlin/CarHooksTest.kt +++ b/example-library/src/test/kotlin/CarHooksTest.kt @@ -1,5 +1,8 @@ package com.intuit.hooks.example.library +import com.intuit.hooks.example.library.car.Car +import com.intuit.hooks.example.library.car.Location +import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test @@ -7,5 +10,12 @@ internal class CarHooksTest { @Test fun testCarAccelerateHooks() { + val car = Car() + + var accelerateTo: Int? = null + car.hooks.accelerate.tap("LoggerPlugin") { newSpeed -> accelerateTo = newSpeed } + + car.speed = 88 + assertEquals(88, accelerateTo) } } diff --git a/example-library/src/test/kotlin/CompilerPluginTest.kt b/example-library/src/test/kotlin/CompilerPluginTest.kt index f026072..9ec7d30 100644 --- a/example-library/src/test/kotlin/CompilerPluginTest.kt +++ b/example-library/src/test/kotlin/CompilerPluginTest.kt @@ -17,9 +17,9 @@ class CompilerPluginTest { }.map { Paths.get( generatedDirPath.toString(), - "source", - "kapt", + "ksp", "main", + "kotlin", ).resolve("${it}Impl.kt") }.forEach { assertTrue(it.exists()) { "$it does not exist" } diff --git a/hooks/build.gradle.kts b/hooks/build.gradle.kts index 78d54fc..d000162 100644 --- a/hooks/build.gradle.kts +++ b/hooks/build.gradle.kts @@ -2,11 +2,9 @@ plugins { alias(libs.plugins.knit) } -val COROUTINES_VERSION: String by project - dependencies { implementation(libs.kotlin.stdlib) - implementation(libs.coroutines.core) + implementation(libs.kotlin.coroutines.core) testImplementation(platform(libs.junit.bom)) testImplementation(libs.bundles.testing) diff --git a/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt b/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt index 9a052e3..3fa1a66 100644 --- a/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt +++ b/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt @@ -3,18 +3,100 @@ package com.intuit.hooks.dsl import com.intuit.hooks.* import kotlinx.coroutines.ExperimentalCoroutinesApi +private const val DEPRECATION_MESSAGE = "The migration to KSP requires DSL markers to be done outside of expression code." +private inline fun stub(): Nothing = throw NotImplementedError("Compiler stub called!") + public abstract class Hooks { - protected fun > syncHook(): SyncHook<*> = object : SyncHook() {} - protected fun >> syncBailHook(): SyncBailHook<*, *> = object : SyncBailHook<() -> BailResult, Any?>() {} - protected fun > syncWaterfallHook(): SyncWaterfallHook<*, *> = object : SyncWaterfallHook() {} - protected fun > syncLoopHook(): SyncLoopHook<*, *> = object : SyncLoopHook() {} - protected fun > asyncParallelHook(): AsyncParallelHook<*> = object : AsyncParallelHook() {} - @ExperimentalCoroutinesApi - protected fun >> asyncParallelBailHook(): AsyncParallelBailHook<*, *> = object : AsyncParallelBailHook<() -> BailResult, Any?>() {} - protected fun > asyncSeriesHook(): AsyncSeriesHook<*> = object : AsyncSeriesHook() {} - protected fun >> asyncSeriesBailHook(): AsyncSeriesBailHook<*, *> = object : AsyncSeriesBailHook<() -> BailResult, Any?>() {} - protected fun > asyncSeriesWaterfallHook(): AsyncSeriesWaterfallHook<*, *> = object : AsyncSeriesWaterfallHook() {} - protected fun > asyncSeriesLoopHook(): AsyncSeriesLoopHook<*, *> = object : AsyncSeriesLoopHook() {} + public annotation class Sync> + + @Deprecated( + DEPRECATION_MESSAGE, + ReplaceWith("@Hooks.Sync()"), + DeprecationLevel.ERROR, + ) + protected fun > syncHook(): SyncHook<*> = stub() + + public annotation class SyncBail> + + @Deprecated( + DEPRECATION_MESSAGE, + ReplaceWith("@Hooks.SyncBail()"), + DeprecationLevel.ERROR, + ) + protected fun >> syncBailHook(): SyncBailHook<*, *> = stub() + + public annotation class SyncWaterfall> + + @Deprecated( + DEPRECATION_MESSAGE, + ReplaceWith("@Hooks.SyncWaterfall()"), + DeprecationLevel.ERROR, + ) + protected fun > syncWaterfallHook(): SyncWaterfallHook<*, *> = stub() + + public annotation class SyncLoop> + + @Deprecated( + DEPRECATION_MESSAGE, + ReplaceWith("@Hooks.SyncLoop()"), + DeprecationLevel.ERROR, + ) + protected fun > syncLoopHook(): SyncLoopHook<*, *> = stub() + + public annotation class AsyncParallel> + + @Deprecated( + DEPRECATION_MESSAGE, + ReplaceWith("@Hooks.AsyncParallel()"), + DeprecationLevel.ERROR, + ) + protected fun > asyncParallelHook(): AsyncParallelHook<*> = stub() + + public annotation class AsyncParallelBail> + + @Deprecated( + DEPRECATION_MESSAGE, + ReplaceWith("@Hooks.AsyncParallelBail()"), + DeprecationLevel.ERROR, + ) + @ExperimentalCoroutinesApi protected fun >> asyncParallelBailHook(): AsyncParallelBailHook<*, *> = stub() + + public annotation class AsyncSeries> + + @Deprecated( + DEPRECATION_MESSAGE, + ReplaceWith("@Hooks.AsyncSeries()"), + DeprecationLevel.ERROR, + ) + protected fun > asyncSeriesHook(): AsyncSeriesHook<*> = stub() + + public annotation class AsyncSeriesBail> + + @Deprecated( + DEPRECATION_MESSAGE, + ReplaceWith("@Hooks.AsyncSeriesBail()"), + DeprecationLevel.ERROR, + ) + protected fun >> asyncSeriesBailHook(): AsyncSeriesBailHook<*, *> = stub() + + public annotation class AsyncSeriesWaterfall> + + @Deprecated( + DEPRECATION_MESSAGE, + ReplaceWith("@Hooks.AsyncSeriesWaterfall()"), + DeprecationLevel.ERROR, + ) + protected fun > asyncSeriesWaterfallHook(): AsyncSeriesWaterfallHook<*, *> = stub() + + public annotation class AsyncSeriesLoop> + + @Deprecated( + DEPRECATION_MESSAGE, + ReplaceWith("@Hooks.AsyncSeriesLoop()"), + DeprecationLevel.ERROR, + ) + protected fun > asyncSeriesLoopHook(): AsyncSeriesLoopHook<*, *> = stub() + } public typealias HooksDsl = Hooks diff --git a/settings.gradle.kts b/settings.gradle.kts index ee75733..b3d65fc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,13 @@ rootProject.name = "hooks-project" -include(":hooks")//, ":compiler-plugin", ":gradle-plugin", ":maven-plugin", ":docs", ":example-library", ":example-application") +include( + ":hooks", + ":compiler-plugin", +// ":gradle-plugin", +// ":maven-plugin", +// ":docs", + ":example-library", +// ":example-application" +) enableFeaturePreview("ONE_LOCKFILE_PER_PROJECT") dependencyResolutionManagement { @@ -7,11 +15,13 @@ dependencyResolutionManagement { create("libs") { version("kotlin", "1.6.21") version("ktlint", "0.45.2") - version("arrow.core", "1.0.1") // 1.1.2 + version("arrow", "1.0.1") // 1.1.2 version("ksp", "1.6.21-1.0.5") + version("poet", "1.11.0") version("junit", "5.7.0") plugin("kotlin.jvm", "org.jetbrains.kotlin.jvm").versionRef("kotlin") + plugin("ksp", "com.google.devtools.ksp").versionRef("ksp") plugin("release", "net.researchgate.release").version("2.6.0") plugin("nexus", "io.github.gradle-nexus.publish-plugin").version("1.0.0") @@ -23,13 +33,25 @@ dependencyResolutionManagement { plugin("dokka", "org.jetbrains.dokka").versionRef("kotlin") plugin("orchid", "com.eden.orchidPlugin").version("0.21.1") + // Kotlin library("kotlin.stdlib", "org.jetbrains.kotlin", "kotlin-stdlib").withoutVersion() - library("coroutines.core", "org.jetbrains.kotlinx", "kotlinx-coroutines-core").version("1.6.1") + library("kotlin.coroutines.core", "org.jetbrains.kotlinx", "kotlinx-coroutines-core").version("1.6.1") + // KSP + library("ksp.spa", "com.google.devtools.ksp", "symbol-processing-api").versionRef("ksp") + library("ksp.poet", "com.squareup", "kotlinpoet-ksp").versionRef("poet") + library("ktlint.core", "com.pinterest.ktlint", "ktlint-core").versionRef("ktlint") + library("ktlint.ruleset.standard", "com.pinterest.ktlint", "ktlint-ruleset-standard").versionRef("ktlint") + + // Arrow + library("arrow.core", "io.arrow-kt", "arrow-core").versionRef("arrow") + + // Testing // TODO: Swap to Kotlin testing library library("junit.bom", "org.junit", "junit-bom").version("5.7.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").withoutVersion() library("mockk", "io.mockk", "mockk").version("1.10.2") + library("ksp.testing", "com.github.tschuchortdev", "kotlin-compile-testing-ksp").version("1.4.8") bundle("testing", listOf("junit.jupiter", "mockk")) } From b2f2d7c8c2ad57e66819751c6ea705151d05e800 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Thu, 12 May 2022 03:04:20 -0400 Subject: [PATCH 05/23] finish compiler plugin re-org --- build.gradle.kts | 2 +- compiler-plugin/api/compiler-plugin.api | 128 ++------------ .../hooks/plugin/codegen/CodeGeneration.kt | 157 +++++++----------- .../intuit/hooks/plugin/codegen/HookInfo.kt | 54 ++++++ .../com/intuit/hooks/plugin/ksp/HookInfo.kt | 40 ----- .../intuit/hooks/plugin/ksp/HooksProcessor.kt | 35 ++-- .../com/intuit/hooks/plugin/ksp/Text.kt | 2 +- .../validation/AnnotationValidations.kt | 73 ++++++++ .../hooks/plugin/validation/HookProperty.kt | 37 ----- .../validation/HookPropertyValidations.kt | 50 ++++++ .../plugin/validation/HookValidations.kt | 92 ++-------- .../com/intuit/hooks/plugin/HookTest.kt | 131 ++++++++------- .../hooks/plugin/HookValidationErrors.kt | 12 +- example-library/api/example-library.api | 52 ++++++ .../intuit/hooks/example/library/car/Car.kt | 3 - .../src/test/kotlin/CarHooksTest.kt | 2 - .../main/kotlin/com/intuit/hooks/dsl/Hooks.kt | 1 - 17 files changed, 419 insertions(+), 452 deletions(-) create mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookInfo.kt delete mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HookInfo.kt create mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/AnnotationValidations.kt delete mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookProperty.kt create mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookPropertyValidations.kt create mode 100644 example-library/api/example-library.api diff --git a/build.gradle.kts b/build.gradle.kts index ffcce1a..ceaea88 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,7 @@ plugins { alias(libs.plugins.dokka) } -val shouldntPublish = listOf("hooks") //listOf("docs", "example-library", "example-application") +val shouldntPublish = listOf("hooks") // listOf("docs", "example-library", "example-application") val publishModules = subprojects.map { it.name }.subtract(shouldntPublish) val isSnapshot = (version as? String)?.contains("-SNAPSHOT") ?: true diff --git a/compiler-plugin/api/compiler-plugin.api b/compiler-plugin/api/compiler-plugin.api index 4163ba1..eaa64b0 100644 --- a/compiler-plugin/api/compiler-plugin.api +++ b/compiler-plugin/api/compiler-plugin.api @@ -1,116 +1,18 @@ -public class com/intuit/hooks/plugin/HooksMetaPlugin : arrow/meta/Meta { +public final class com/intuit/hooks/plugin/ksp/HooksProcessor : com/google/devtools/ksp/processing/SymbolProcessor { + public fun (Lcom/google/devtools/ksp/processing/CodeGenerator;Lcom/google/devtools/ksp/processing/KSPLogger;)V + public fun finish ()V + public fun onError ()V + public fun process (Lcom/google/devtools/ksp/processing/Resolver;)Ljava/util/List; +} + +public final class com/intuit/hooks/plugin/ksp/HooksProcessor$Exception : java/lang/Exception { + public fun (Ljava/lang/String;Ljava/lang/Throwable;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class com/intuit/hooks/plugin/ksp/HooksProcessor$Provider : com/google/devtools/ksp/processing/SymbolProcessorProvider { public fun ()V - public fun IrGeneration (Larrow/meta/dsl/codegen/ir/IrSyntax;Lkotlin/jvm/functions/Function3;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun additionalSources (Lkotlin/jvm/functions/Function4;)Larrow/meta/phases/analysis/CollectAdditionalSources; - public fun analysis (Lkotlin/jvm/functions/Function7;Lkotlin/jvm/functions/Function5;)Larrow/meta/phases/analysis/AnalysisHandler; - public fun callChecker (Lkotlin/jvm/functions/Function4;)Larrow/meta/phases/config/StorageComponentContainer; - public fun codegen (Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/asm/Codegen; - public fun compilerContextService ()Larrow/meta/phases/config/StorageComponentContainer; - public fun declarationAttributeAlterer (Lkotlin/jvm/functions/Function7;)Larrow/meta/phases/resolve/DeclarationAttributeAlterer; - public fun declarationChecker (Lkotlin/jvm/functions/Function4;)Larrow/meta/phases/config/StorageComponentContainer; - public fun enableIr ()Larrow/meta/phases/ExtensionPhase; - public fun extraImports (Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/analysis/ExtraImports; - public fun intercept (Larrow/meta/phases/CompilerContext;)Ljava/util/List; - public fun irAnonymousInitializer (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irBlock (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irBlockBody (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irBody (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irBranch (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irBreak (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irBreakContinue (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irCall (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irCallableReference (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irCatch (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irClass (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irClassReference (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irComposite (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irConst (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irConstructor (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irConstructorCall (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irContainerExpression (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irContinue (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irDeclaration (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irDeclarationReference (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irDelegatingConstructorCall (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irDoWhileLoop (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irDump (Larrow/meta/Meta;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irDumpKotlinLike (Larrow/meta/Meta;Lorg/jetbrains/kotlin/ir/util/KotlinLikeDumpOptions;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irDynamicExpression (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irDynamicMemberExpression (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irDynamicOperatorExpression (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irElseBranch (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irEnumConstructorCall (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irEnumEntry (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irErrorCallExpression (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irErrorDeclaration (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irErrorExpression (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irExpression (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irExpressionBody (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irField (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irFieldAccess (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irFile (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irFunction (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irFunctionAccess (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irFunctionReference (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irGetClass (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irGetEnumValue (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irGetField (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irGetObjectValue (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irGetValue (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irInstanceInitializerCall (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irLocalDelegatedProperty (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irLocalDelegatedPropertyReference (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irLoop (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irMemberAccess (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irModuleFragment (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irProperty (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irPropertyReference (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irReturn (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irSetField (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irSetValue (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irSimpleFunction (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irSingletonReference (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irSpreadElement (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irStringConcatenation (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irSuspendableExpression (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irSuspensionPoint (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irSyntheticBody (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irThrow (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irTry (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irTypeAlias (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irTypeOperator (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irTypeParameter (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irValueAccess (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irValueParameter (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irVararg (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irVariable (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irWhen (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun irWhileLoop (Larrow/meta/Meta;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/codegen/ir/IRGeneration; - public fun meta ([Larrow/meta/phases/ExtensionPhase;)Ljava/util/List; - public fun packageFragmentProvider (Lkotlin/jvm/functions/Function7;)Larrow/meta/phases/resolve/PackageProvider; - public fun packageFragmentProvider (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/resolve/PackageProvider;Larrow/meta/phases/CompilerContext;)V - public fun preprocessedVirtualFileFactory (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/analysis/PreprocessedVirtualFileFactory; - public fun registerAnalysisHandler (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/analysis/AnalysisHandler;Larrow/meta/phases/CompilerContext;)V - public fun registerClassBuilder (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/codegen/asm/ClassBuilder;Larrow/meta/phases/CompilerContext;)V - public fun registerCodegen (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/codegen/asm/Codegen;Larrow/meta/phases/CompilerContext;)V - public fun registerCollectAdditionalSources (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/analysis/CollectAdditionalSources;Larrow/meta/phases/CompilerContext;)V - public fun registerCompilerConfiguration (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/config/Config;Larrow/meta/phases/CompilerContext;)V - public fun registerDeclarationAttributeAlterer (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/resolve/DeclarationAttributeAlterer;Larrow/meta/phases/CompilerContext;)V - public fun registerExtraImports (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/analysis/ExtraImports;Larrow/meta/phases/CompilerContext;)V - public fun registerIRGeneration (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/codegen/ir/IRGeneration;Larrow/meta/phases/CompilerContext;)V - public fun registerMetaAnalyzer ()Larrow/meta/phases/ExtensionPhase; - public fun registerMetaComponents (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Lorg/jetbrains/kotlin/config/CompilerConfiguration;Larrow/meta/phases/CompilerContext;)V - public fun registerPreprocessedVirtualFileFactory (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/analysis/PreprocessedVirtualFileFactory;Larrow/meta/phases/CompilerContext;)V - public fun registerProjectComponents (Lorg/jetbrains/kotlin/com/intellij/mock/MockProject;Lorg/jetbrains/kotlin/config/CompilerConfiguration;)V - public fun registerStorageComponentContainer (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/config/StorageComponentContainer;Larrow/meta/phases/CompilerContext;)V - public fun registerSyntheticResolver (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/resolve/synthetics/SyntheticResolver;Larrow/meta/phases/CompilerContext;)V - public fun registerSyntheticScopeProvider (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;Larrow/meta/phases/resolve/synthetics/SyntheticScopeProvider;Larrow/meta/phases/CompilerContext;)V - public fun registerSyntheticScopeProviderIfNeeded (Lorg/jetbrains/kotlin/com/intellij/openapi/project/Project;)V - public fun storageComponent (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;)Larrow/meta/phases/config/StorageComponentContainer; - public fun suppressDiagnostic (Lkotlin/jvm/functions/Function1;)Larrow/meta/phases/ExtensionPhase; - public fun suppressDiagnosticWithTrace (Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/ExtensionPhase; - public fun syntheticResolver (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/resolve/synthetics/SyntheticResolver; - public fun syntheticScopes (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Larrow/meta/phases/ExtensionPhase; - public fun updateConfig (Lkotlin/jvm/functions/Function2;)Larrow/meta/phases/config/Config; + public synthetic fun create (Lcom/google/devtools/ksp/processing/SymbolProcessorEnvironment;)Lcom/google/devtools/ksp/processing/SymbolProcessor; + public fun create (Lcom/google/devtools/ksp/processing/SymbolProcessorEnvironment;)Lcom/intuit/hooks/plugin/ksp/HooksProcessor; } diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/CodeGeneration.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/CodeGeneration.kt index 0fcc006..e90d0e0 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/CodeGeneration.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/CodeGeneration.kt @@ -1,156 +1,123 @@ package com.intuit.hooks.plugin.codegen -import com.intuit.hooks.plugin.validation.HookProperty - - -internal data class HookSignature( - val text: String, - val isSuspend: Boolean, - val returnType: String, - /** For hooks that return a wrapped result, like [BailResult], this is the inner type */ - val returnTypeType: String?, -) { - val nullableReturnTypeType = "${returnTypeType}${if (returnTypeType?.last() == '?') "" else "?"}" - - override fun toString() = text -} - -internal class HookParameter(private val name: String?, val type: String, private val position: Int) { - val withType get() = "$withoutType: $type" - val withoutType get() = name ?: "p$position" -} - -internal data class HookCodeGen( - private val hookType: HookType, - private val propertyName: String, - val params: List, - val hookSignature: HookSignature, - private val zeroArity: Boolean, - val visibility: String, -) { - val tapMethod get() = if (!zeroArity) """ - public fun tap(name: String, f: $hookSignature): String? = tap(name, generateRandomId(), f) - public fun tap(name: String, id: String, f: $hookSignature): String? = super.tap(name, id) { _: HookContext, $paramsWithTypes -> f($paramsWithoutTypes) } - """.trimIndent() else "" - val paramsWithTypes get() = params.joinToString(", ") { it.withType } - val paramsWithoutTypes get() = params.joinToString(", ") { it.withoutType } - fun generateClass() = this.hookType.generateClass(this) - fun generateProperty() = (if (hookType == HookType.AsyncParallelBailHook) "@kotlinx.coroutines.ExperimentalCoroutinesApi\n" else "") + - "override val $propertyName: $className = $className()" - fun generateImports(): List = emptyList() - private val isAsync get() = this.hookType.properties.contains(HookProperty.Async) - val superType get() = this.hookType.toString() - - val className get() = "${this.propertyName.replaceFirstChar(Char::titlecase)}$superType" - val typeParameter get() = "(${if (isAsync) "suspend " else ""}(HookContext, $paramsWithTypes) -> ${hookSignature.returnType})" - val interceptParameter get() = "${if (isAsync) "suspend " else ""}(HookContext, $paramsWithTypes) -> Unit" +internal sealed class HookProperty { + object Bail : HookProperty() + object Loop : HookProperty() + object Async : HookProperty() + object Waterfall : HookProperty() } internal enum class HookType(vararg val properties: HookProperty) { SyncHook { - override fun generateClass(codeGen: HookCodeGen): String { + override fun generateClass(info: HookInfo): String { // todo: potentially protected - return """|${codeGen.visibility} inner class ${codeGen.className} : ${codeGen.superType}<${codeGen.typeParameter}>() { - | public fun call(${codeGen.paramsWithTypes}): Unit = super.call { f, context -> f(context, ${codeGen.paramsWithoutTypes}) } - | ${codeGen.tapMethod} + return """|${info.property.visibility} inner class ${info.className} : ${info.superType}<${info.typeParameter}>() { + | public fun call(${info.paramsWithTypes}): Unit = super.call { f, context -> f(context, ${info.paramsWithoutTypes}) } + | ${info.tapMethod} |}""" } }, SyncBailHook(HookProperty.Bail) { - override fun generateClass(codeGen: HookCodeGen): String { + override fun generateClass(info: HookInfo): String { // todo: Potentially protected - return """|${codeGen.visibility} inner class ${codeGen.className} : ${codeGen.superType}<${codeGen.typeParameter}, ${codeGen.hookSignature.returnTypeType}>() { - | public fun call(${codeGen.paramsWithTypes}): ${codeGen.hookSignature.nullableReturnTypeType} = super.call { f, context -> f(context, ${codeGen.paramsWithoutTypes}) } - | ${codeGen.tapMethod} + return """|${info.property.visibility} inner class ${info.className} : ${info.superType}<${info.typeParameter}, ${info.hookSignature.returnTypeType}>() { + | public fun call(${info.paramsWithTypes}): ${info.hookSignature.nullableReturnTypeType} = super.call { f, context -> f(context, ${info.paramsWithoutTypes}) } + | ${info.tapMethod} |}""" } }, SyncWaterfallHook(HookProperty.Waterfall) { - override fun generateClass(codeGen: HookCodeGen): String { - val accumulatorName = codeGen.params.first().withoutType - return """|${codeGen.visibility} inner class ${codeGen.className} : ${codeGen.superType}<${codeGen.typeParameter}, ${codeGen.params.first().type}>() { - | public fun call(${codeGen.paramsWithTypes}): ${codeGen.hookSignature.returnType} = super.call($accumulatorName, - | invokeTap = { f, $accumulatorName, context -> f(context, ${codeGen.paramsWithoutTypes}) }, - | invokeInterceptor = { f, context -> f(context, ${codeGen.paramsWithoutTypes})} + override fun generateClass(info: HookInfo): String { + val accumulatorName = info.params.first().withoutType + return """|${info.property.visibility} inner class ${info.className} : ${info.superType}<${info.typeParameter}, ${info.params.first().type}>() { + | public fun call(${info.paramsWithTypes}): ${info.hookSignature.returnType} = super.call($accumulatorName, + | invokeTap = { f, $accumulatorName, context -> f(context, ${info.paramsWithoutTypes}) }, + | invokeInterceptor = { f, context -> f(context, ${info.paramsWithoutTypes})} | ) - | ${codeGen.tapMethod} + | ${info.tapMethod} |}""" } }, SyncLoopHook(HookProperty.Loop) { - override fun generateClass(codeGen: HookCodeGen): String { - return """|${codeGen.visibility} inner class ${codeGen.className}: ${codeGen.superType}<${codeGen.typeParameter}, ${codeGen.interceptParameter}>() { - | public fun call(${codeGen.paramsWithTypes}): Unit = super.call( - | invokeTap = { f, context -> f(context, ${codeGen.paramsWithoutTypes}) }, - | invokeInterceptor = { f, context -> f(context, ${codeGen.paramsWithoutTypes}) } + override fun generateClass(info: HookInfo): String { + return """|${info.property.visibility} inner class ${info.className}: ${info.superType}<${info.typeParameter}, ${info.interceptParameter}>() { + | public fun call(${info.paramsWithTypes}): Unit = super.call( + | invokeTap = { f, context -> f(context, ${info.paramsWithoutTypes}) }, + | invokeInterceptor = { f, context -> f(context, ${info.paramsWithoutTypes}) } | ) - | ${codeGen.tapMethod} + | ${info.tapMethod} |}""" } }, AsyncParallelHook(HookProperty.Async) { - override fun generateClass(codeGen: HookCodeGen): String { - return """|${codeGen.visibility} inner class ${codeGen.className}: ${codeGen.superType}<${codeGen.typeParameter}>() { - | public suspend fun call(${codeGen.paramsWithTypes}): Unit = super.call { f, context -> f(context, ${codeGen.paramsWithoutTypes}) } - | ${codeGen.tapMethod} + override fun generateClass(info: HookInfo): String { + return """|${info.property.visibility} inner class ${info.className}: ${info.superType}<${info.typeParameter}>() { + | public suspend fun call(${info.paramsWithTypes}): Unit = super.call { f, context -> f(context, ${info.paramsWithoutTypes}) } + | ${info.tapMethod} |}""" } }, AsyncParallelBailHook(HookProperty.Async, HookProperty.Bail) { - override fun generateClass(codeGen: HookCodeGen): String { + override fun generateClass(info: HookInfo): String { return """|@kotlinx.coroutines.ExperimentalCoroutinesApi - |${codeGen.visibility} inner class ${codeGen.className}: ${codeGen.superType}<${codeGen.typeParameter}, ${codeGen.hookSignature.returnTypeType}>() { - | public suspend fun call(concurrency: Int, ${codeGen.paramsWithTypes}): ${codeGen.hookSignature.nullableReturnTypeType} = super.call(concurrency) { f, context -> f(context, ${codeGen.paramsWithoutTypes}) } - | ${codeGen.tapMethod} + |${info.property.visibility} inner class ${info.className}: ${info.superType}<${info.typeParameter}, ${info.hookSignature.returnTypeType}>() { + | public suspend fun call(concurrency: Int, ${info.paramsWithTypes}): ${info.hookSignature.nullableReturnTypeType} = super.call(concurrency) { f, context -> f(context, ${info.paramsWithoutTypes}) } + | ${info.tapMethod} |}""" } }, AsyncSeriesHook(HookProperty.Async) { - override fun generateClass(codeGen: HookCodeGen): String { - return """|${codeGen.visibility} inner class ${codeGen.className}: ${codeGen.superType}<${codeGen.typeParameter}>() { - | public suspend fun call(${codeGen.paramsWithTypes}): Unit = super.call { f, context -> f(context, ${codeGen.paramsWithoutTypes}) } - | ${codeGen.tapMethod} + override fun generateClass(info: HookInfo): String { + return """|${info.property.visibility} inner class ${info.className}: ${info.superType}<${info.typeParameter}>() { + | public suspend fun call(${info.paramsWithTypes}): Unit = super.call { f, context -> f(context, ${info.paramsWithoutTypes}) } + | ${info.tapMethod} |}""" } }, AsyncSeriesBailHook(HookProperty.Async, HookProperty.Bail) { - override fun generateClass(codeGen: HookCodeGen): String { - return """|${codeGen.visibility} inner class ${codeGen.className}: ${codeGen.superType}<${codeGen.typeParameter}, ${codeGen.hookSignature.returnTypeType}>() { - | public suspend fun call(${codeGen.paramsWithTypes}): ${codeGen.hookSignature.nullableReturnTypeType} = super.call { f, context -> f(context, ${codeGen.paramsWithoutTypes}) } - | ${codeGen.tapMethod} + override fun generateClass(info: HookInfo): String { + return """|${info.property.visibility} inner class ${info.className}: ${info.superType}<${info.typeParameter}, ${info.hookSignature.returnTypeType}>() { + | public suspend fun call(${info.paramsWithTypes}): ${info.hookSignature.nullableReturnTypeType} = super.call { f, context -> f(context, ${info.paramsWithoutTypes}) } + | ${info.tapMethod} |}""" } }, AsyncSeriesWaterfallHook(HookProperty.Async, HookProperty.Waterfall) { - override fun generateClass(codeGen: HookCodeGen): String { - val accumulatorName = codeGen.params.first().withoutType - return """|${codeGen.visibility} inner class ${codeGen.className} : ${codeGen.superType}<${codeGen.typeParameter}, ${codeGen.params.first().type}>() { - | public suspend fun call(${codeGen.paramsWithTypes}): ${codeGen.hookSignature.returnType} = super.call($accumulatorName, - | invokeTap = { f, $accumulatorName, context -> f(context, ${codeGen.paramsWithoutTypes}) }, - | invokeInterceptor = { f, context -> f(context, ${codeGen.paramsWithoutTypes})} + override fun generateClass(info: HookInfo): String { + val accumulatorName = info.params.first().withoutType + return """|${info.property.visibility} inner class ${info.className} : ${info.superType}<${info.typeParameter}, ${info.params.first().type}>() { + | public suspend fun call(${info.paramsWithTypes}): ${info.hookSignature.returnType} = super.call($accumulatorName, + | invokeTap = { f, $accumulatorName, context -> f(context, ${info.paramsWithoutTypes}) }, + | invokeInterceptor = { f, context -> f(context, ${info.paramsWithoutTypes})} | ) - | ${codeGen.tapMethod} + | ${info.tapMethod} |}""" } }, AsyncSeriesLoopHook(HookProperty.Async, HookProperty.Loop) { - override fun generateClass(codeGen: HookCodeGen): String { - return """|${codeGen.visibility} inner class ${codeGen.className}: ${codeGen.superType}<${codeGen.typeParameter}, ${codeGen.interceptParameter}>() { - | public suspend fun call(${codeGen.paramsWithTypes}): Unit = super.call( - | invokeTap = { f, context -> f(context, ${codeGen.paramsWithoutTypes}) }, - | invokeInterceptor = { f, context -> f(context, ${codeGen.paramsWithoutTypes}) } + override fun generateClass(info: HookInfo): String { + return """|${info.property.visibility} inner class ${info.className}: ${info.superType}<${info.typeParameter}, ${info.interceptParameter}>() { + | public suspend fun call(${info.paramsWithTypes}): Unit = super.call( + | invokeTap = { f, context -> f(context, ${info.paramsWithoutTypes}) }, + | invokeInterceptor = { f, context -> f(context, ${info.paramsWithoutTypes}) } | ) - | ${codeGen.tapMethod} + | ${info.tapMethod} |}""" } }; - abstract fun generateClass(codeGen: HookCodeGen): String + abstract fun generateClass(info: HookInfo): String + + companion object { + val annotationDslMarkers = values().map { + it.name.dropLast(4) + } + } } diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookInfo.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookInfo.kt new file mode 100644 index 0000000..7ee8a40 --- /dev/null +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookInfo.kt @@ -0,0 +1,54 @@ +package com.intuit.hooks.plugin.codegen + +internal data class HookMember( + val name: String, + val visibility: String, +) + +internal data class HookSignature( + val text: String, + val isSuspend: Boolean, + val returnType: String, + /** For hooks that return a wrapped result, like [BailResult], this is the inner type */ + val returnTypeType: String?, +) { + val nullableReturnTypeType = "${returnTypeType}${if (returnTypeType?.last() == '?') "" else "?"}" + + override fun toString() = text +} + +internal class HookParameter( + val name: String?, + val type: String, + val position: Int, +) + +internal val HookParameter.withType get() = "$withoutType: $type" +internal val HookParameter.withoutType get() = name ?: "p$position" + +internal data class HookInfo( + val property: HookMember, + val hookType: HookType, + val hookSignature: HookSignature, + val params: List, +) { + val zeroArity = params.isEmpty() + val isAsync = hookType.properties.contains(HookProperty.Async) +} + +internal val HookInfo.tapMethod get() = if (!zeroArity) """ + public fun tap(name: String, f: $hookSignature): String? = tap(name, generateRandomId(), f) + public fun tap(name: String, id: String, f: $hookSignature): String? = super.tap(name, id) { _: HookContext, $paramsWithTypes -> f($paramsWithoutTypes) } +""".trimIndent() else "" +internal val HookInfo.paramsWithTypes get() = params.joinToString(transform = HookParameter::withType) +internal val HookInfo.paramsWithoutTypes get() = params.joinToString(transform = HookParameter::withoutType) +internal fun HookInfo.generateClass() = this.hookType.generateClass(this) +internal fun HookInfo.generateProperty() = (if (hookType == HookType.AsyncParallelBailHook) "@kotlinx.coroutines.ExperimentalCoroutinesApi\n" else "") + + "override val ${property.name}: $className = $className()" +internal fun HookInfo.generateImports(): List = emptyList() + +internal val HookInfo.superType get() = this.hookType.toString() + +internal val HookInfo.className get() = "${property.name.replaceFirstChar(Char::titlecase)}$superType" +internal val HookInfo.typeParameter get() = "(${if (isAsync) "suspend " else ""}(HookContext, $paramsWithTypes) -> ${hookSignature.returnType})" +internal val HookInfo.interceptParameter get() = "${if (isAsync) "suspend " else ""}(HookContext, $paramsWithTypes) -> Unit" diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HookInfo.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HookInfo.kt deleted file mode 100644 index f31078c..0000000 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HookInfo.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.intuit.hooks.plugin.ksp - -import com.google.devtools.ksp.getVisibility -import com.google.devtools.ksp.symbol.KSAnnotation -import com.google.devtools.ksp.symbol.KSCallableReference -import com.google.devtools.ksp.symbol.KSPropertyDeclaration -import com.intuit.hooks.plugin.codegen.HookCodeGen -import com.intuit.hooks.plugin.codegen.HookParameter -import com.intuit.hooks.plugin.codegen.HookSignature -import com.intuit.hooks.plugin.codegen.HookType - - -/** Intermediate KSP validation holder for aggregated hook info */ -internal data class HookClassInfo( - val property: KSPropertyDeclaration, - val hookSignature: HookSignature, - val hookType: HookType, - val params: List -) { - val zeroArity get() = params.isEmpty() - - fun toCodeGen(): HookCodeGen { - val propertyName = property.simpleName.asString() - val visibility = property.getVisibility().name.lowercase() - return HookCodeGen(hookType, propertyName, params, hookSignature, zeroArity, visibility) - } -} - -/** Wrapper for [KSAnnotation] when we're sure that the annotation is a hook annotation */ -@JvmInline internal value class HookAnnotation(val symbol: KSAnnotation) { - val hookFunctionSignatureType get() = symbol.annotationType.element?.typeArguments?.single()?.type - ?: throw HooksProcessor.Exception("Could not determine hook function signature type for $symbol") - - val hookFunctionSignatureReference get() = hookFunctionSignatureType.element as? KSCallableReference - ?: throw HooksProcessor.Exception("Hook type argument must be a function for $symbol") - - val type get() = toString().let(HookType::valueOf) - - override fun toString() = "${symbol.shortName.asString()}Hook" -} diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt index 3805b24..fbcd9a7 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt @@ -7,8 +7,11 @@ import com.google.devtools.ksp.getVisibility import com.google.devtools.ksp.processing.* import com.google.devtools.ksp.symbol.* import com.google.devtools.ksp.validate -import com.intuit.hooks.plugin.validation.annotationDslMarkers -import com.intuit.hooks.plugin.codegen.HookCodeGen +import com.intuit.hooks.plugin.codegen.HookInfo +import com.intuit.hooks.plugin.codegen.HookType.Companion.annotationDslMarkers +import com.intuit.hooks.plugin.codegen.generateClass +import com.intuit.hooks.plugin.codegen.generateImports +import com.intuit.hooks.plugin.codegen.generateProperty import com.intuit.hooks.plugin.validation.validateHook import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.KtLint.format @@ -65,7 +68,7 @@ public class HooksProcessor( val name = "${classDeclaration.parentDeclaration?.simpleName?.asString() ?: ""}${classDeclaration.simpleName.asString()}Impl" val typeParameters = if (classDeclaration.typeParameters.isEmpty()) "" else "<${ - classDeclaration.typeParameters.joinToString(separator = ", ") { it.simpleName.asString() } + classDeclaration.typeParameters.joinToString(separator = ", ") { it.simpleName.asString() } }>" val fqName = classDeclaration.qualifiedName!!.asString() @@ -74,7 +77,7 @@ public class HooksProcessor( | |$imports | - |${visibility} $kind $name$typeParameters : ${fqName}$typeParameters() { + |$visibility $kind $name$typeParameters : ${fqName}$typeParameters() { | ${properties.joinToString("\n", "\n", "\n") { it }} | ${classes.joinToString("\n", "\n", "\n") { it }} |}""".trimMargin() @@ -85,9 +88,12 @@ public class HooksProcessor( name, ) - logger.logging("""raw generated source: + logger.logging( + """raw generated source: |$newSource - """.trimMargin(), classDeclaration) + """.trimMargin(), + classDeclaration + ) KtLint.ExperimentalParams( text = newSource, @@ -96,9 +102,12 @@ public class HooksProcessor( ) .let(::format) .also { - logger.logging("""formatted generated source: + logger.logging( + """formatted generated source: |$it - """.trimMargin(), classDeclaration) + """.trimMargin(), + classDeclaration + ) } .let(String::toByteArray) .let(file::write) @@ -122,21 +131,21 @@ public class HooksProcessor( .map(::validateHook) .sequenceValidated(Semigroup.nonEmptyList()) - private fun generateHookClass(hookCodeGen: HookCodeGen): Pair { - val classDefinition = hookCodeGen.generateClass() - val propertyDefinition = hookCodeGen.generateProperty() + private fun generateHookClass(hookInfo: HookInfo): Pair { + val classDefinition = hookInfo.generateClass() + val propertyDefinition = hookInfo.generateProperty() return classDefinition to propertyDefinition } - private fun createImportDirectives(classDeclaration: KSClassDeclaration, codeGens: List): String { + private fun createImportDirectives(classDeclaration: KSClassDeclaration, hooks: List): String { val existingImports = emptyList() // TODO: Get imports -- this might be fixed with kotlin poet? // classDeclaration.containingFile?. // ?.removeHooksDslImport() // ?.map { it.text ?: "" } // ?: emptyList() - val newImports = codeGens.flatMap { it.generateImports() } + val newImports = hooks.flatMap(HookInfo::generateImports) val hookStarImport = listOf("import com.intuit.hooks.*") diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt index ee1ca3a..eef71f6 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt @@ -16,7 +16,7 @@ internal val KSTypeReference.text: String get() = element?.let { when (it) { // Use lambda type shorthand is KSCallableReference -> "${if (this.modifiers.contains(Modifier.SUSPEND)) "suspend " else ""}(${ - it.functionParameters.map(KSValueParameter::type).joinToString(transform = KSTypeReference::text) + it.functionParameters.map(KSValueParameter::type).joinToString(transform = KSTypeReference::text) }) -> ${it.returnType.text}" else -> "$it${it.typeArguments.text}" } diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/AnnotationValidations.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/AnnotationValidations.kt new file mode 100644 index 0000000..51a6a06 --- /dev/null +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/AnnotationValidations.kt @@ -0,0 +1,73 @@ +package com.intuit.hooks.plugin.validation + +import arrow.core.* +import com.google.devtools.ksp.getVisibility +import com.google.devtools.ksp.symbol.* +import com.intuit.hooks.plugin.codegen.HookInfo +import com.intuit.hooks.plugin.codegen.HookMember +import com.intuit.hooks.plugin.codegen.HookParameter +import com.intuit.hooks.plugin.codegen.HookSignature +import com.intuit.hooks.plugin.codegen.HookType +import com.intuit.hooks.plugin.codegen.HookType.Companion.annotationDslMarkers +import com.intuit.hooks.plugin.ksp.HooksProcessor +import com.intuit.hooks.plugin.ksp.text + +/** Wrapper for [KSAnnotation] when we're sure that the annotation is a hook annotation */ +@JvmInline internal value class HookAnnotation(val symbol: KSAnnotation) { + val hookFunctionSignatureType get() = symbol.annotationType.element?.typeArguments?.single()?.type + ?: throw HooksProcessor.Exception("Could not determine hook function signature type for $symbol") + + val hookFunctionSignatureReference get() = hookFunctionSignatureType.element as? KSCallableReference + ?: throw HooksProcessor.Exception("Hook type argument must be a function for $symbol") + + val type get() = toString().let(HookType::valueOf) + + override fun toString() = "${symbol.shortName.asString()}Hook" +} + +/** Build [HookInfo] from the validated [HookAnnotation] found on the [property] */ +internal fun validateHookAnnotation( + property: KSPropertyDeclaration, +): ValidatedNel = onlyHasASingleDslAnnotation(property).withEither { + it.flatMap { annotation -> + hasCodeGenerator(annotation).zip( + mustBeHookType(annotation), + validateParameters(annotation), + HookMember( + property.simpleName.asString(), + property.getVisibility().name.lowercase(), + ).let(::HookInfo::partially1), + ).toEither() + } +} + +private fun onlyHasASingleDslAnnotation(property: KSPropertyDeclaration): ValidatedNel { + val annotations = property.annotations.filter { it.shortName.asString() in annotationDslMarkers }.toList() + if (annotations.size != 1) return HookValidationError.MustOnlyHaveSingleDslAnnotation(annotations, property).invalidNel() + return annotations.single().let(::HookAnnotation).valid() +} + +private fun validateParameters(annotation: HookAnnotation): ValidatedNel> = try { + annotation.hookFunctionSignatureReference.functionParameters.mapIndexed { index: Int, parameter: KSValueParameter -> + HookParameter(parameter.name?.asString(), parameter.type.text, index) + }.valid() +} catch (exception: Exception) { + HookValidationError.MustBeHookTypeSignature(annotation).invalidNel() +} + +private fun hasCodeGenerator(annotation: HookAnnotation): ValidatedNel = try { + annotation.type.valid() +} catch (e: Exception) { + HookValidationError.NoCodeGenerator(annotation).invalidNel() +} + +private fun mustBeHookType(annotation: HookAnnotation): ValidatedNel = try { + HookSignature( + annotation.hookFunctionSignatureType.text, + annotation.hookFunctionSignatureType.modifiers.contains(Modifier.SUSPEND), + annotation.hookFunctionSignatureReference.returnType.text, + annotation.hookFunctionSignatureReference.returnType.element?.typeArguments?.firstOrNull()?.text, + ).valid() +} catch (exception: Exception) { + HookValidationError.MustBeHookTypeSignature(annotation).invalidNel() +} diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookProperty.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookProperty.kt deleted file mode 100644 index 0b7fe1b..0000000 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookProperty.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.intuit.hooks.plugin.validation - -import arrow.core.ValidatedNel -import arrow.core.invalidNel -import arrow.core.valid -import arrow.core.zip -import com.intuit.hooks.plugin.ksp.HookClassInfo - -internal enum class HookProperty { - Bail, - Loop, - Async { - override fun validate(hookClassInfo: HookClassInfo): ValidatedNel { - return if (hookClassInfo.hookSignature.isSuspend) this.valid() - else HookValidationError.AsyncHookWithoutSuspend(hookClassInfo).invalidNel() - } - }, - Waterfall { - override fun validate(hookClassInfo: HookClassInfo): ValidatedNel { - return arity(hookClassInfo).zip( - parameters(hookClassInfo), - ) { _, _ -> this } - } - - private fun arity(hookClassInfo: HookClassInfo): ValidatedNel { - return if (!hookClassInfo.zeroArity) this.valid() - else HookValidationError.WaterfallMustHaveParameters(hookClassInfo).invalidNel() - } - - private fun parameters(hookClassInfo: HookClassInfo): ValidatedNel { - return if (hookClassInfo.hookSignature.returnType == hookClassInfo.params.firstOrNull()?.type) this.valid() - else HookValidationError.WaterfallParameterTypeMustMatch(hookClassInfo).invalidNel() - } - }; - - open fun validate(hookClassInfo: HookClassInfo): ValidatedNel = this.valid() -} diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookPropertyValidations.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookPropertyValidations.kt new file mode 100644 index 0000000..179fee7 --- /dev/null +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookPropertyValidations.kt @@ -0,0 +1,50 @@ +package com.intuit.hooks.plugin.validation + +import arrow.core.ValidatedNel +import arrow.core.invalidNel +import arrow.core.valid +import arrow.core.zip +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.intuit.hooks.plugin.codegen.HookInfo +import com.intuit.hooks.plugin.codegen.HookProperty + +internal fun HookProperty.validate( + info: HookInfo, + property: KSPropertyDeclaration, +): ValidatedNel = when (this) { + is HookProperty.Bail -> valid() + is HookProperty.Loop -> valid() + is HookProperty.Async -> validate(info, property) + is HookProperty.Waterfall -> validate(info, property) +} + +private fun HookProperty.Async.validate( + info: HookInfo, + property: KSPropertyDeclaration, +): ValidatedNel = + if (info.hookSignature.isSuspend) valid() + else HookValidationError.AsyncHookWithoutSuspend(property).invalidNel() + +private fun HookProperty.Waterfall.validate( + info: HookInfo, + property: KSPropertyDeclaration, +): ValidatedNel = + arity(info, property).zip( + parameters(info, property), + ) { _, _ -> this } + +private fun HookProperty.Waterfall.arity( + info: HookInfo, + property: KSPropertyDeclaration, +): ValidatedNel { + return if (!info.zeroArity) valid() + else HookValidationError.WaterfallMustHaveParameters(property).invalidNel() +} + +private fun HookProperty.Waterfall.parameters( + info: HookInfo, + property: KSPropertyDeclaration, +): ValidatedNel { + return if (info.hookSignature.returnType == info.params.firstOrNull()?.type) valid() + else HookValidationError.WaterfallParameterTypeMustMatch(property).invalidNel() +} diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt index e555872..0910f0f 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt @@ -2,93 +2,25 @@ package com.intuit.hooks.plugin.validation import arrow.core.* import com.google.devtools.ksp.symbol.* -import com.intuit.hooks.plugin.codegen.HookCodeGen -import com.intuit.hooks.plugin.codegen.HookParameter -import com.intuit.hooks.plugin.codegen.HookSignature -import com.intuit.hooks.plugin.codegen.HookType -import com.intuit.hooks.plugin.ksp.HookAnnotation -import com.intuit.hooks.plugin.ksp.HookClassInfo -import com.intuit.hooks.plugin.ksp.text +import com.intuit.hooks.plugin.codegen.HookInfo // TODO: It'd be nice if the validations were compiler plugin framework agnostic -internal sealed class HookValidationError(val message: String, var symbol: KSNode? = null) { - class AsyncHookWithoutSuspend(hookClassInfo: HookClassInfo) : HookValidationError("Async hooks must be defined with a suspend function signature", hookClassInfo.property) - class WaterfallMustHaveParameters(hookClassInfo: HookClassInfo) : HookValidationError("Waterfall hooks must take at least one parameter", hookClassInfo.property) - class WaterfallParameterTypeMustMatch(hookClassInfo: HookClassInfo) : HookValidationError("Waterfall hooks must specify the same types for the first parameter and the return type", hookClassInfo.property) +internal sealed class HookValidationError(val message: String, val symbol: KSNode) { + class AsyncHookWithoutSuspend(symbol: KSNode) : HookValidationError("Async hooks must be defined with a suspend function signature", symbol) + class WaterfallMustHaveParameters(symbol: KSNode) : HookValidationError("Waterfall hooks must take at least one parameter", symbol) + class WaterfallParameterTypeMustMatch(symbol: KSNode) : HookValidationError("Waterfall hooks must specify the same types for the first parameter and the return type", symbol) class MustBeHookTypeSignature(annotation: HookAnnotation) : HookValidationError("$annotation property requires a hook type signature", annotation.symbol) class NoCodeGenerator(annotation: HookAnnotation) : HookValidationError("This hook plugin has no code generator for $annotation", annotation.symbol) class MustOnlyHaveSingleDslAnnotation(annotations: List, property: KSPropertyDeclaration) : HookValidationError("This hook has more than a single hook DSL annotation: $annotations", property) } -internal fun validateHook(property: KSPropertyDeclaration): Validated, HookCodeGen> = - validateHookType(property).withEither { e -> - e.flatMap { h -> validateHookProperties(h).toEither() } - }.mapLeft { invalid -> - // Attach property to validations that don't have a symbol attached - invalid.onEach { - it.symbol = it.symbol ?: property - } +/** main entrypoint for validating [KSPropertyDeclaration]s as valid annotated hook members */ +internal fun validateHook(property: KSPropertyDeclaration): Validated, HookInfo> = + validateHookAnnotation(property).withEither { + it.flatMap { hookInfo -> validateHookProperties(property, hookInfo).toEither() } } -private fun validateHookProperties(hook: HookClassInfo) = - hook.hookType.properties.map { it.validate(hook) } +private fun validateHookProperties(property: KSPropertyDeclaration, hookInfo: HookInfo) = + hookInfo.hookType.properties.map { it.validate(hookInfo, property) } .sequenceValidated() - .map { hook.toCodeGen() } - -internal val annotationDslMarkers = listOf( - "Sync", - "SyncBail", - "SyncWaterfall", - "SyncLoop", - "AsyncParallel", - "AsyncParallelBail", - "AsyncSeries", - "AsyncSeriesBail", - "AsyncSeriesWaterfall", - "AsyncSeriesLoop", -) - -private fun validateHookType(property: KSPropertyDeclaration): ValidatedNel = onlyHasASingleDslAnnotation(property).withEither { - val hookClassInfo = ::HookClassInfo.partially1(property) - it.flatMap { annotation -> - validateHookAnnotation(annotation, hookClassInfo).toEither() - } -} - -private fun validateHookAnnotation(annotation: HookAnnotation, factory: (HookSignature, HookType, List) -> HookClassInfo): ValidatedNel = mustBeHookType(annotation).zip( - hasCodeGenerator(annotation), - validateParameters(annotation), - factory, -) - -private fun validateParameters(annotation: HookAnnotation): ValidatedNel> = try { - annotation.hookFunctionSignatureReference.functionParameters.mapIndexed { index: Int, parameter: KSValueParameter -> - HookParameter(parameter.name?.asString(), parameter.type.text, index) - }.valid() -} catch (exception: Exception) { - HookValidationError.MustBeHookTypeSignature(annotation).invalidNel() -} - -private fun hasCodeGenerator(annotation: HookAnnotation): ValidatedNel = try { - annotation.type.valid() -} catch (e: Exception) { - HookValidationError.NoCodeGenerator(annotation).invalidNel() -} - -private fun mustBeHookType(annotation: HookAnnotation): ValidatedNel = try { - HookSignature( - annotation.hookFunctionSignatureType.text, - annotation.hookFunctionSignatureType.modifiers.contains(Modifier.SUSPEND), - annotation.hookFunctionSignatureReference.returnType.text, - annotation.hookFunctionSignatureReference.returnType.element?.typeArguments?.firstOrNull()?.text, - ).valid() -} catch (exception: Exception) { - HookValidationError.MustBeHookTypeSignature(annotation).invalidNel() -} - - -private fun onlyHasASingleDslAnnotation(property: KSPropertyDeclaration): ValidatedNel { - val annotations = property.annotations.filter { it.shortName.asString() in annotationDslMarkers }.toList() - if (annotations.size != 1) return HookValidationError.MustOnlyHaveSingleDslAnnotation(annotations, property).invalidNel() - return annotations.single().let(::HookAnnotation).valid() -} \ No newline at end of file + .map { hookInfo } diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt index ee133ea..8c7b565 100644 --- a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt +++ b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt @@ -9,20 +9,24 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test - class HooksProcessorTest { - fun compile(vararg sources: SourceFile, block: KotlinCompilation.() -> Unit = { - symbolProcessorProviders = listOf(HooksProcessor.Provider()) - inheritClassPath = true - }): KotlinCompilation.Result = KotlinCompilation().apply { + fun compile( + vararg sources: SourceFile, + block: KotlinCompilation.() -> Unit = { + symbolProcessorProviders = listOf(HooksProcessor.Provider()) + inheritClassPath = true + } + ): KotlinCompilation.Result = KotlinCompilation().apply { this.sources = sources.toList() block() }.compile() @Test fun testSyncHookCalled() { - val testHookClass = SourceFile.kotlin("TestHooks.kt", """ + val testHookClass = SourceFile.kotlin( + "TestHooks.kt", + """ import com.intuit.hooks.SyncHook import com.intuit.hooks.dsl.Hooks @@ -30,7 +34,8 @@ class HooksProcessorTest { @Sync<(String) -> Unit>() abstract val testSyncHook: SyncHook<*> } - """.trimIndent()) + """.trimIndent() + ) val result = KotlinCompilation().apply { symbolProcessorProviders = listOf(HooksProcessor.Provider()) @@ -41,7 +46,9 @@ class HooksProcessorTest { assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) // assertTrue(result.generatedFiles.map { it.name }.contains("TestHooksImpl.kt")) - val testHookCall = SourceFile.kotlin("Assertions.kt", """ + val testHookCall = SourceFile.kotlin( + "Assertions.kt", + """ import com.intuit.hooks.* internal class TestHooksImpl : TestHooks() { @@ -62,7 +69,8 @@ class HooksProcessorTest { hooks.testSyncHook.call("hello") return tapCalled } - """) + """ + ) val assertionResult = KotlinCompilation().apply { sources = listOf(testHookClass, testHookCall) @@ -79,7 +87,9 @@ class HooksProcessorTest { } @Test fun `hook params are generic`() { - val testHookClass = SourceFile.kotlin("TestHooks.kt", """ + val testHookClass = SourceFile.kotlin( + "TestHooks.kt", + """ import com.intuit.hooks.SyncHook import com.intuit.hooks.dsl.Hooks @@ -87,7 +97,8 @@ class HooksProcessorTest { @Sync<(Map, List>) -> Unit>() abstract val testSyncHook: SyncHook<*> } - """) + """ + ) val processorResult = compile(testHookClass) assertEquals(KotlinCompilation.ExitCode.OK, processorResult.exitCode) @@ -99,25 +110,25 @@ class HooksProcessorTest { // fun testAsyncSeriesWaterfallHookCalled() { // val testHookClass = // """ -//|package com.intuit.hooks.plugin.test -//|import com.intuit.hooks.dsl.Hooks -//|import kotlinx.coroutines.runBlocking -//| -//|abstract class TestHooks : Hooks() { -//| open val testAsyncSeriesWaterfallHook = asyncSeriesWaterfallHook String>() -//|} -//""" +// |package com.intuit.hooks.plugin.test +// |import com.intuit.hooks.dsl.Hooks +// |import kotlinx.coroutines.runBlocking +// | +// |abstract class TestHooks : Hooks() { +// | open val testAsyncSeriesWaterfallHook = asyncSeriesWaterfallHook String>() +// |} +// """ // val testHookCall = // """ -//|fun testHook() : Boolean { -//| var tapCalled = false -//| val hooks = TestHooksImpl() -//| hooks.testAsyncSeriesWaterfallHook.tap("test") { x -> tapCalled = true; "asdf" } -//| runBlocking { -//| hooks.testAsyncSeriesWaterfallHook.call("someVal") -//| } -//| return tapCalled -//|}""" +// |fun testHook() : Boolean { +// | var tapCalled = false +// | val hooks = TestHooksImpl() +// | hooks.testAsyncSeriesWaterfallHook.tap("test") { x -> tapCalled = true; "asdf" } +// | runBlocking { +// | hooks.testAsyncSeriesWaterfallHook.call("someVal") +// | } +// | return tapCalled +// |}""" // // assertThis( // CompilerTest( @@ -173,22 +184,22 @@ class HooksProcessorTest { // fun testHookWithTypeParameter() { // val testHookClass = // """ -//|package com.intuit.hooks.plugin.test -//|import com.intuit.hooks.dsl.Hooks -//| -//|abstract class TestHooks : Hooks() { -//| open val testSyncHook = syncHook<(T) -> Unit>() -//|} -//""" +// |package com.intuit.hooks.plugin.test +// |import com.intuit.hooks.dsl.Hooks +// | +// |abstract class TestHooks : Hooks() { +// | open val testSyncHook = syncHook<(T) -> Unit>() +// |} +// """ // val testHookCall = // """ -//|fun testHook() : Boolean { -//| var tapCalled = false -//| val hooks = TestHooksImpl() -//| hooks.testSyncHook.tap("test") { _, x -> tapCalled = true } -//| hooks.testSyncHook.call("hello") -//| return tapCalled -//|}""" +// |fun testHook() : Boolean { +// | var tapCalled = false +// | val hooks = TestHooksImpl() +// | hooks.testSyncHook.tap("test") { _, x -> tapCalled = true } +// | hooks.testSyncHook.call("hello") +// | return tapCalled +// |}""" // assertThis( // CompilerTest( // config = { hookDependencies() }, @@ -207,27 +218,27 @@ class HooksProcessorTest { // fun `test nested hook class`() { // val controllerClass = // """ -//|package com.intuit.hooks.plugin.test -//|import com.intuit.hooks.dsl.Hooks -//| -//|class Controller { -//| abstract class TestHooks : Hooks() { -//| open val testSyncHook = syncHook<() -> Unit>() -//| } -//| -//| val hooks = ControllerTestHooksImpl() -//|} -//""" +// |package com.intuit.hooks.plugin.test +// |import com.intuit.hooks.dsl.Hooks +// | +// |class Controller { +// | abstract class TestHooks : Hooks() { +// | open val testSyncHook = syncHook<() -> Unit>() +// | } +// | +// | val hooks = ControllerTestHooksImpl() +// |} +// """ // // val testScript = // """ -//|fun testHook() : Boolean { -//| var tapCalled = false -//| val controller = Controller() -//| controller.hooks.testSyncHook.tap("test") { _ -> tapCalled = true } -//| controller.hooks.testSyncHook.call() -//| return tapCalled -//|}""" +// |fun testHook() : Boolean { +// | var tapCalled = false +// | val controller = Controller() +// | controller.hooks.testSyncHook.tap("test") { _ -> tapCalled = true } +// | controller.hooks.testSyncHook.call() +// | return tapCalled +// |}""" // // assertThis( // CompilerTest( diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt index 7cf62cf..25583fe 100644 --- a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt +++ b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt @@ -1,10 +1,10 @@ -//package com.intuit.hooks.plugin +// package com.intuit.hooks.plugin // -//import arrow.meta.plugin.testing.CompilerTest -//import arrow.meta.plugin.testing.assertThis -//import org.junit.jupiter.api.Test +// import arrow.meta.plugin.testing.CompilerTest +// import arrow.meta.plugin.testing.assertThis +// import org.junit.jupiter.api.Test // -//class HookValidationErrors { +// class HookValidationErrors { // @Test // fun testNonInitializedHook() { // assertThis( @@ -168,4 +168,4 @@ // ) // ) // } -//} +// } diff --git a/example-library/api/example-library.api b/example-library/api/example-library.api new file mode 100644 index 0000000..da10009 --- /dev/null +++ b/example-library/api/example-library.api @@ -0,0 +1,52 @@ +public final class com/intuit/hooks/example/library/car/Car { + public fun ()V + public final fun getHooks ()Lcom/intuit/hooks/example/library/car/CarHooksImpl; + public final fun getSpeed ()I + public final fun setSpeed (I)V + public final fun useNavigationSystem (Lcom/intuit/hooks/example/library/car/Location;Lcom/intuit/hooks/example/library/car/Location;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract class com/intuit/hooks/example/library/car/Car$Hooks : com/intuit/hooks/dsl/Hooks { + public fun ()V + public abstract fun getAccelerate ()Lcom/intuit/hooks/SyncHook; + public abstract fun getBrake ()Lcom/intuit/hooks/SyncHook; + public abstract fun getCalculateRoutes ()Lcom/intuit/hooks/AsyncSeriesWaterfallHook; +} + +public final class com/intuit/hooks/example/library/car/CarHooksImpl : com/intuit/hooks/example/library/car/Car$Hooks { + public fun ()V + public synthetic fun getAccelerate ()Lcom/intuit/hooks/SyncHook; + public fun getAccelerate ()Lcom/intuit/hooks/example/library/car/CarHooksImpl$AccelerateSyncHook; + public synthetic fun getBrake ()Lcom/intuit/hooks/SyncHook; + public fun getBrake ()Lcom/intuit/hooks/example/library/car/CarHooksImpl$BrakeSyncHook; + public synthetic fun getCalculateRoutes ()Lcom/intuit/hooks/AsyncSeriesWaterfallHook; + public fun getCalculateRoutes ()Lcom/intuit/hooks/example/library/car/CarHooksImpl$CalculateRoutesAsyncSeriesWaterfallHook; +} + +public final class com/intuit/hooks/example/library/car/CarHooksImpl$AccelerateSyncHook : com/intuit/hooks/SyncHook { + public fun (Lcom/intuit/hooks/example/library/car/CarHooksImpl;)V + public final fun call (I)V + public final fun tap (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/String; + public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/String; +} + +public final class com/intuit/hooks/example/library/car/CarHooksImpl$BrakeSyncHook : com/intuit/hooks/SyncHook { + public fun (Lcom/intuit/hooks/example/library/car/CarHooksImpl;)V + public final fun call ()V +} + +public final class com/intuit/hooks/example/library/car/CarHooksImpl$CalculateRoutesAsyncSeriesWaterfallHook : com/intuit/hooks/AsyncSeriesWaterfallHook { + public fun (Lcom/intuit/hooks/example/library/car/CarHooksImpl;)V + public final fun call (Ljava/util/List;Lcom/intuit/hooks/example/library/car/Location;Lcom/intuit/hooks/example/library/car/Location;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun tap (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function4;)Ljava/lang/String; + public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function4;)Ljava/lang/String; +} + +public abstract class com/intuit/hooks/example/library/car/Location { + public fun ()V +} + +public final class com/intuit/hooks/example/library/car/Route { + public fun ()V +} + diff --git a/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt b/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt index f3795df..1424bdc 100644 --- a/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt +++ b/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt @@ -1,18 +1,15 @@ package com.intuit.hooks.example.library.car import com.intuit.hooks.AsyncSeriesWaterfallHook -import com.intuit.hooks.HookContext import com.intuit.hooks.SyncHook import com.intuit.hooks.dsl.Hooks.AsyncSeriesWaterfall import com.intuit.hooks.dsl.Hooks.Sync import com.intuit.hooks.dsl.HooksDsl - public abstract class Location public class Route - public class Car { public abstract class Hooks : HooksDsl() { diff --git a/example-library/src/test/kotlin/CarHooksTest.kt b/example-library/src/test/kotlin/CarHooksTest.kt index fd42b12..92882a0 100644 --- a/example-library/src/test/kotlin/CarHooksTest.kt +++ b/example-library/src/test/kotlin/CarHooksTest.kt @@ -1,8 +1,6 @@ package com.intuit.hooks.example.library import com.intuit.hooks.example.library.car.Car -import com.intuit.hooks.example.library.car.Location -import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test diff --git a/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt b/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt index 3fa1a66..5c0169a 100644 --- a/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt +++ b/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt @@ -96,7 +96,6 @@ public abstract class Hooks { DeprecationLevel.ERROR, ) protected fun > asyncSeriesLoopHook(): AsyncSeriesLoopHook<*, *> = stub() - } public typealias HooksDsl = Hooks From 149aab4a6ab7f62fe9512ae918daed17a539c8b6 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Thu, 12 May 2022 17:13:50 -0400 Subject: [PATCH 06/23] fix all tests for compiler plugin and enhance validations --- .../{CodeGeneration.kt => HookType.kt} | 0 .../intuit/hooks/plugin/ksp/HooksProcessor.kt | 14 +- .../validation/AnnotationValidations.kt | 34 +- .../validation/HookPropertyValidations.kt | 2 +- .../plugin/ksp/validation/HookValidations.kt | 59 +++ .../plugin/validation/HookValidations.kt | 26 -- .../com/intuit/hooks/plugin/HookTest.kt | 256 ------------- .../hooks/plugin/HookValidationErrors.kt | 339 +++++++++--------- .../intuit/hooks/plugin/HooksProcessorTest.kt | 245 +++++++++++++ .../intuit/hooks/plugin/KotlinCompilation.kt | 62 ++++ .../main/kotlin/com/intuit/hooks/dsl/Hooks.kt | 1 + 11 files changed, 558 insertions(+), 480 deletions(-) rename compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/{CodeGeneration.kt => HookType.kt} (100%) rename compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/{ => ksp}/validation/AnnotationValidations.kt (72%) rename compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/{ => ksp}/validation/HookPropertyValidations.kt (97%) create mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookValidations.kt delete mode 100644 compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt delete mode 100644 compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt create mode 100644 compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt create mode 100644 compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/KotlinCompilation.kt diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/CodeGeneration.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookType.kt similarity index 100% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/CodeGeneration.kt rename to compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookType.kt diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt index fbcd9a7..b107371 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt @@ -8,11 +8,10 @@ import com.google.devtools.ksp.processing.* import com.google.devtools.ksp.symbol.* import com.google.devtools.ksp.validate import com.intuit.hooks.plugin.codegen.HookInfo -import com.intuit.hooks.plugin.codegen.HookType.Companion.annotationDslMarkers import com.intuit.hooks.plugin.codegen.generateClass import com.intuit.hooks.plugin.codegen.generateImports import com.intuit.hooks.plugin.codegen.generateProperty -import com.intuit.hooks.plugin.validation.validateHook +import com.intuit.hooks.plugin.ksp.validation.validateProperty import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.KtLint.format import com.pinterest.ktlint.core.api.FeatureInAlphaState @@ -121,14 +120,11 @@ public class HooksProcessor( private fun KSClassDeclaration.findHooks() = getAllProperties() .filter { - // Only generate hooks for properties that use our annotation - it.annotations.map(KSAnnotation::shortName) - .map(KSName::asString) - .filter(annotationDslMarkers::contains) - .toList() - .isNotEmpty() + // Only process properties that are abstract b/c that's what we need for a concrete class + + it.modifiers.contains(Modifier.ABSTRACT) } - .map(::validateHook) + .map(::validateProperty) .sequenceValidated(Semigroup.nonEmptyList()) private fun generateHookClass(hookInfo: HookInfo): Pair { diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/AnnotationValidations.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/AnnotationValidations.kt similarity index 72% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/AnnotationValidations.kt rename to compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/AnnotationValidations.kt index 51a6a06..4c1a852 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/AnnotationValidations.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/AnnotationValidations.kt @@ -1,4 +1,4 @@ -package com.intuit.hooks.plugin.validation +package com.intuit.hooks.plugin.ksp.validation import arrow.core.* import com.google.devtools.ksp.getVisibility @@ -26,24 +26,24 @@ import com.intuit.hooks.plugin.ksp.text } /** Build [HookInfo] from the validated [HookAnnotation] found on the [property] */ -internal fun validateHookAnnotation( - property: KSPropertyDeclaration, -): ValidatedNel = onlyHasASingleDslAnnotation(property).withEither { - it.flatMap { annotation -> - hasCodeGenerator(annotation).zip( - mustBeHookType(annotation), - validateParameters(annotation), - HookMember( - property.simpleName.asString(), - property.getVisibility().name.lowercase(), - ).let(::HookInfo::partially1), - ).toEither() +internal fun KSPropertyDeclaration.validateHookAnnotation(): ValidatedNel = + onlyHasASingleDslAnnotation().withEither { + it.flatMap { annotation -> + hasCodeGenerator(annotation).zip( + mustBeHookType(annotation), + validateParameters(annotation), + HookMember( + simpleName.asString(), + getVisibility().name.lowercase(), + ).let(::HookInfo::partially1), + ).toEither() + } } -} -private fun onlyHasASingleDslAnnotation(property: KSPropertyDeclaration): ValidatedNel { - val annotations = property.annotations.filter { it.shortName.asString() in annotationDslMarkers }.toList() - if (annotations.size != 1) return HookValidationError.MustOnlyHaveSingleDslAnnotation(annotations, property).invalidNel() +private fun KSPropertyDeclaration.onlyHasASingleDslAnnotation(): ValidatedNel { + val annotations = annotations.filter { it.shortName.asString() in annotationDslMarkers }.toList() + if (annotations.isEmpty()) return HookValidationError.NoHookDslAnnotations(this).invalidNel() + else if (annotations.size > 1) return HookValidationError.TooManyHookDslAnnotations(annotations, this).invalidNel() return annotations.single().let(::HookAnnotation).valid() } diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookPropertyValidations.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookPropertyValidations.kt similarity index 97% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookPropertyValidations.kt rename to compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookPropertyValidations.kt index 179fee7..77b07b1 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookPropertyValidations.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookPropertyValidations.kt @@ -1,4 +1,4 @@ -package com.intuit.hooks.plugin.validation +package com.intuit.hooks.plugin.ksp.validation import arrow.core.ValidatedNel import arrow.core.invalidNel diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookValidations.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookValidations.kt new file mode 100644 index 0000000..9af7415 --- /dev/null +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookValidations.kt @@ -0,0 +1,59 @@ +package com.intuit.hooks.plugin.ksp.validation + +import arrow.core.* +import com.google.devtools.ksp.symbol.* +import com.intuit.hooks.plugin.codegen.HookInfo +import com.intuit.hooks.plugin.codegen.HookType +import com.intuit.hooks.plugin.ksp.text + +// TODO: It'd be nice if the validations were compiler plugin framework agnostic +internal sealed class HookValidationError(val message: String, val symbol: KSNode) { + class AsyncHookWithoutSuspend(symbol: KSNode) : HookValidationError("Async hooks must be defined with a suspend function signature", symbol) + class WaterfallMustHaveParameters(symbol: KSNode) : HookValidationError("Waterfall hooks must take at least one parameter", symbol) + class WaterfallParameterTypeMustMatch(symbol: KSNode) : HookValidationError("Waterfall hooks must specify the same types for the first parameter and the return type", symbol) + class MustBeHookTypeSignature(annotation: HookAnnotation) : HookValidationError("$annotation property requires a hook type signature", annotation.symbol) + class NoCodeGenerator(annotation: HookAnnotation) : HookValidationError("This hook plugin has no code generator for $annotation", annotation.symbol) + class NoHookDslAnnotations(property: KSPropertyDeclaration) : HookValidationError("Hook property must be annotated with respective DSL annotation for ${property.type.text}", property) + class TooManyHookDslAnnotations(annotations: List, property: KSPropertyDeclaration) : HookValidationError("This hook has more than a single hook DSL annotation: $annotations", property) + class HookPropertyTypeMismatch(property: KSPropertyDeclaration, annotationType: String) : HookValidationError("Hook property type (${property.type.text}) does not match annotation hook type (@${annotationType.dropLast(4)})", property) + class UnsupportedAbstractPropertyType(property: KSPropertyDeclaration) : HookValidationError("Abstract property type (${property.type.text}) not supported", property) +} + +/** main entrypoint for validating [KSPropertyDeclaration]s as valid annotated hook members */ +internal fun validateProperty(property: KSPropertyDeclaration): ValidatedNel = with(property) { + // validate property has the correct type + validateHookType().withEither { either -> + either.flatMap { type -> + // aggregate property hook type with annotation hook info + type.valid().zip( + validateHookAnnotation(), + ::Pair + ).toEither() + } + }.withEither { + it.flatMap { (type, info) -> + // validate property against hook info with specific hook type validations + validatePropertyTypeAgainstHookInfo(type, info).zip( + validateHookProperties(info) + ) { _, _ -> info }.toEither() + } + } +} + +private fun KSPropertyDeclaration.validateHookType(): ValidatedNel = try { + HookType.valueOf(type.element.toString()).valid() +} catch (e: Exception) { + HookValidationError.UnsupportedAbstractPropertyType(this).invalidNel() +} + +private fun KSPropertyDeclaration.validatePropertyTypeAgainstHookInfo( + type: HookType, + info: HookInfo +): ValidatedNel = + if (info.hookType == type) info.valid() + else HookValidationError.HookPropertyTypeMismatch(this, info.hookType.name).invalidNel() + +private fun KSPropertyDeclaration.validateHookProperties(hookInfo: HookInfo) = + hookInfo.hookType.properties.map { it.validate(hookInfo, this) } + .sequenceValidated() + .map { hookInfo } diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt deleted file mode 100644 index 0910f0f..0000000 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/validation/HookValidations.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.intuit.hooks.plugin.validation - -import arrow.core.* -import com.google.devtools.ksp.symbol.* -import com.intuit.hooks.plugin.codegen.HookInfo - -// TODO: It'd be nice if the validations were compiler plugin framework agnostic -internal sealed class HookValidationError(val message: String, val symbol: KSNode) { - class AsyncHookWithoutSuspend(symbol: KSNode) : HookValidationError("Async hooks must be defined with a suspend function signature", symbol) - class WaterfallMustHaveParameters(symbol: KSNode) : HookValidationError("Waterfall hooks must take at least one parameter", symbol) - class WaterfallParameterTypeMustMatch(symbol: KSNode) : HookValidationError("Waterfall hooks must specify the same types for the first parameter and the return type", symbol) - class MustBeHookTypeSignature(annotation: HookAnnotation) : HookValidationError("$annotation property requires a hook type signature", annotation.symbol) - class NoCodeGenerator(annotation: HookAnnotation) : HookValidationError("This hook plugin has no code generator for $annotation", annotation.symbol) - class MustOnlyHaveSingleDslAnnotation(annotations: List, property: KSPropertyDeclaration) : HookValidationError("This hook has more than a single hook DSL annotation: $annotations", property) -} - -/** main entrypoint for validating [KSPropertyDeclaration]s as valid annotated hook members */ -internal fun validateHook(property: KSPropertyDeclaration): Validated, HookInfo> = - validateHookAnnotation(property).withEither { - it.flatMap { hookInfo -> validateHookProperties(property, hookInfo).toEither() } - } - -private fun validateHookProperties(property: KSPropertyDeclaration, hookInfo: HookInfo) = - hookInfo.hookType.properties.map { it.validate(hookInfo, property) } - .sequenceValidated() - .map { hookInfo } diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt deleted file mode 100644 index 8c7b565..0000000 --- a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookTest.kt +++ /dev/null @@ -1,256 +0,0 @@ -package com.intuit.hooks.plugin - -import com.intuit.hooks.plugin.ksp.HooksProcessor -import com.tschuchort.compiletesting.KotlinCompilation -import com.tschuchort.compiletesting.SourceFile -import com.tschuchort.compiletesting.addPreviousResultToClasspath -import com.tschuchort.compiletesting.symbolProcessorProviders -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test - -class HooksProcessorTest { - - fun compile( - vararg sources: SourceFile, - block: KotlinCompilation.() -> Unit = { - symbolProcessorProviders = listOf(HooksProcessor.Provider()) - inheritClassPath = true - } - ): KotlinCompilation.Result = KotlinCompilation().apply { - this.sources = sources.toList() - block() - }.compile() - - @Test - fun testSyncHookCalled() { - val testHookClass = SourceFile.kotlin( - "TestHooks.kt", - """ - import com.intuit.hooks.SyncHook - import com.intuit.hooks.dsl.Hooks - - internal abstract class TestHooks : Hooks() { - @Sync<(String) -> Unit>() - abstract val testSyncHook: SyncHook<*> - } - """.trimIndent() - ) - - val result = KotlinCompilation().apply { - symbolProcessorProviders = listOf(HooksProcessor.Provider()) - sources = listOf(testHookClass) - inheritClassPath = true - }.compile() - - assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) -// assertTrue(result.generatedFiles.map { it.name }.contains("TestHooksImpl.kt")) - - val testHookCall = SourceFile.kotlin( - "Assertions.kt", - """ - import com.intuit.hooks.* - - internal class TestHooksImpl : TestHooks() { - - override val testSyncHook: TestSyncHookSyncHook = TestSyncHookSyncHook() - - public inner class TestSyncHookSyncHook : SyncHook<((HookContext, p0: String) -> Unit)>() { - public fun call(p0: String): Unit = super.call { f, context -> f(context, p0) } - public fun tap(name: String, f: ((String) -> Unit)): String? = tap(name, generateRandomId(), f) - public fun tap(name: String, id: String, f: ((String) -> Unit)): String? = super.tap(name, id) { _: HookContext, p0: String -> f(p0) } - } - } - - fun testHook(): Boolean { - var tapCalled = false - val hooks = TestHooksImpl() - hooks.testSyncHook.tap("test") { _, x -> tapCalled = true } - hooks.testSyncHook.call("hello") - return tapCalled - } - """ - ) - - val assertionResult = KotlinCompilation().apply { - sources = listOf(testHookClass, testHookCall) - inheritClassPath = true - addPreviousResultToClasspath(result) - }.compile() - - assertEquals(KotlinCompilation.ExitCode.OK, assertionResult.exitCode) - val assertions = assertionResult.classLoader.loadClass("AssertionsKt") - assertions.declaredMethods.forEach { - it.isAccessible = true - assertTrue(it.invoke(null) as Boolean) - } - } - - @Test fun `hook params are generic`() { - val testHookClass = SourceFile.kotlin( - "TestHooks.kt", - """ - import com.intuit.hooks.SyncHook - import com.intuit.hooks.dsl.Hooks - - internal abstract class TestHooks : Hooks() { - @Sync<(Map, List>) -> Unit>() - abstract val testSyncHook: SyncHook<*> - } - """ - ) - - val processorResult = compile(testHookClass) - assertEquals(KotlinCompilation.ExitCode.OK, processorResult.exitCode) - - TODO() - } - -// @Test -// fun testAsyncSeriesWaterfallHookCalled() { -// val testHookClass = -// """ -// |package com.intuit.hooks.plugin.test -// |import com.intuit.hooks.dsl.Hooks -// |import kotlinx.coroutines.runBlocking -// | -// |abstract class TestHooks : Hooks() { -// | open val testAsyncSeriesWaterfallHook = asyncSeriesWaterfallHook String>() -// |} -// """ -// val testHookCall = -// """ -// |fun testHook() : Boolean { -// | var tapCalled = false -// | val hooks = TestHooksImpl() -// | hooks.testAsyncSeriesWaterfallHook.tap("test") { x -> tapCalled = true; "asdf" } -// | runBlocking { -// | hooks.testAsyncSeriesWaterfallHook.call("someVal") -// | } -// | return tapCalled -// |}""" -// -// assertThis( -// CompilerTest( -// config = { hookDependencies() }, -// code = { -// (testHookClass + testHookCall).source -// }, -// -// assert = { -// "testHook()".source.evalsTo(true) -// } -// ) -// ) -// } -// -// @Test -// fun smokeTest() { -// val allHooks = -// """ -// |package com.intuit.hooks.plugin.test -// |import com.intuit.hooks.BailResult -// |import com.intuit.hooks.LoopResult -// |import com.intuit.hooks.dsl.Hooks -// |import kotlinx.coroutines.ExperimentalCoroutinesApi -// | -// |abstract class GenericHooks : Hooks() { -// | open val sync = syncHook<(newSpeed: Int) -> Unit>() -// | open val syncBail = syncBailHook<(Boolean) -> BailResult>() -// | open val syncLoop = syncLoopHook<(foo: Boolean) -> LoopResult>() -// | open val syncWaterfall = syncWaterfallHook<(name: String) -> String>() -// | @ExperimentalCoroutinesApi -// | open val asyncParallelBail = asyncParallelBailHook BailResult>() -// | open val asyncParallel = asyncParallelHook Int>() -// | open val asyncSeries = asyncSeriesHook Int>() -// | open val asyncSeriesBail = asyncSeriesBailHook BailResult>() -// | open val asyncSeriesLoop = asyncSeriesLoopHook LoopResult>() -// | open val asyncSeriesWaterfall = asyncSeriesWaterfallHook String>() -// |} -// """.trimIndent() -// -// assertThis( -// CompilerTest( -// config = { hookDependencies() }, -// code = { (allHooks).source }, -// assert = { -// compiles -// } -// ) -// ) -// } -// -// @Test -// fun testHookWithTypeParameter() { -// val testHookClass = -// """ -// |package com.intuit.hooks.plugin.test -// |import com.intuit.hooks.dsl.Hooks -// | -// |abstract class TestHooks : Hooks() { -// | open val testSyncHook = syncHook<(T) -> Unit>() -// |} -// """ -// val testHookCall = -// """ -// |fun testHook() : Boolean { -// | var tapCalled = false -// | val hooks = TestHooksImpl() -// | hooks.testSyncHook.tap("test") { _, x -> tapCalled = true } -// | hooks.testSyncHook.call("hello") -// | return tapCalled -// |}""" -// assertThis( -// CompilerTest( -// config = { hookDependencies() }, -// code = { -// (testHookClass + testHookCall).source -// }, -// -// assert = { -// "testHook()".source.evalsTo(true) -// } -// ) -// ) -// } -// -// @Test -// fun `test nested hook class`() { -// val controllerClass = -// """ -// |package com.intuit.hooks.plugin.test -// |import com.intuit.hooks.dsl.Hooks -// | -// |class Controller { -// | abstract class TestHooks : Hooks() { -// | open val testSyncHook = syncHook<() -> Unit>() -// | } -// | -// | val hooks = ControllerTestHooksImpl() -// |} -// """ -// -// val testScript = -// """ -// |fun testHook() : Boolean { -// | var tapCalled = false -// | val controller = Controller() -// | controller.hooks.testSyncHook.tap("test") { _ -> tapCalled = true } -// | controller.hooks.testSyncHook.call() -// | return tapCalled -// |}""" -// -// assertThis( -// CompilerTest( -// config = { hookDependencies() }, -// code = { -// (controllerClass + testScript).source -// }, -// -// assert = { -// "testHook()".source.evalsTo(true) -// } -// ) -// ) -// } -} diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt index 25583fe..f00e21e 100644 --- a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt +++ b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt @@ -1,171 +1,168 @@ -// package com.intuit.hooks.plugin -// -// import arrow.meta.plugin.testing.CompilerTest -// import arrow.meta.plugin.testing.assertThis -// import org.junit.jupiter.api.Test -// -// class HookValidationErrors { -// @Test -// fun testNonInitializedHook() { -// assertThis( -// CompilerTest( -// config = { hookDependencies() }, -// code = { -// """ -// |package com.intuit.hooks.plugin.test -// |import com.intuit.hooks.dsl.Hooks -// |import com.intuit.hooks.SyncHook -// | -// |abstract class TestHooks : Hooks() { -// | abstract var testAbstractHook: SyncHook<() -> Int> -// |} -// """.source -// }, -// assert = { -// failsWith { it.contains("testAbstractHook property needs to be initialized") } -// } -// ) -// ) -// } -// -// @Test -// fun testNonHookSignature() { -// assertThis( -// CompilerTest( -// config = { hookDependencies() }, -// code = { -// """ -// |package com.intuit.hooks.plugin.test -// |import com.intuit.hooks.dsl.Hooks -// |import com.intuit.hooks.SyncHook -// | -// |abstract class TestHooks : Hooks() { -// | open val notAHook = false -// |} -// """.source -// }, -// assert = { -// failsWith { it.contains("property needs to be initialized with a DSL method") } -// } -// ) -// ) -// } -// -// @Test -// fun testMissingCodeGen() { -// assertThis( -// CompilerTest( -// config = { hookDependencies() }, -// code = { -// """ -// |package com.intuit.hooks.plugin.test -// |import com.intuit.hooks.dsl.Hooks -// |import com.intuit.hooks.SyncHook -// | -// |class NotActuallyAHook> : SyncHook() -// |fun > Hooks.notActuallyAHook() : NotActuallyAHook = TODO() -// | -// |abstract class TestHooks : Hooks() { -// | open val foo = notActuallyAHook<() -> Unit>() -// |} -// """.source -// }, -// assert = { -// failsWith { it.contains("This hook plugin has no code generator for NotActuallyAHook") } -// } -// ) -// ) -// } -// -// @Test -// fun testNonSuspendAsync() { -// assertThis( -// CompilerTest( -// config = { hookDependencies() }, -// code = { -// """ -// |package com.intuit.hooks.plugin.test -// |import com.intuit.hooks.dsl.Hooks -// | -// |abstract class TestHooks : Hooks() { -// | open val asyncSeries = asyncSeriesHook<(String) -> Int>() -// |} -// """.source -// }, -// assert = { -// failsWith { it.contains("Async hooks must be defined with a suspend function signature") } -// } -// ) -// ) -// } -// -// @Test -// fun testWaterfallZeroArity() { -// assertThis( -// CompilerTest( -// config = { hookDependencies() }, -// code = { -// """ -// |package com.intuit.hooks.plugin.test -// |import com.intuit.hooks.dsl.Hooks -// | -// |abstract class TestHooks : Hooks() { -// | open val syncWaterfall = syncWaterfallHook<() -> String>() -// |} -// """.source -// }, -// assert = { -// failsWith { it.contains("Waterfall hooks must take at least one parameter") } -// } -// ) -// ) -// } -// -// @Test -// fun multipleCompilerErrors() { -// assertThis( -// CompilerTest( -// config = { hookDependencies() }, -// code = { -// """ -// |package com.intuit.hooks.plugin.test -// |import com.intuit.hooks.dsl.Hooks -// | -// |abstract class TestHooks : Hooks() { -// | open val realBad = asyncSeriesWaterfallHook<() -> String>() -// |} -// """.source -// }, -// assert = { -// allOf( -// failsWith { it.contains("Async hooks must be defined with a suspend function signature") }, -// failsWith { it.contains("Waterfall hooks must take at least one parameter") }, -// failsWith { it.contains("Waterfall hooks must specify the same types for the first parameter and the return type") } -// ) -// } -// ) -// ) -// } -// -// @Test -// fun testWaterfallParameterReturnEquality() { -// assertThis( -// CompilerTest( -// config = { hookDependencies() }, -// code = { -// """ -// |package com.intuit.hooks.plugin.test -// |import com.intuit.hooks.dsl.Hooks -// | -// |abstract class TestHooks : Hooks() { -// | open val syncWaterfall = syncWaterfallHook<(String) -> Boolean>() -// |} -// """.source -// }, -// assert = { -// failsWith { it.contains("Waterfall hooks must specify the same types for the first parameter and the return type") } -// } -// ) -// ) -// } -// } + +package com.intuit.hooks.plugin + +import com.tschuchort.compiletesting.SourceFile +import org.junit.jupiter.api.Test + +class HookValidationErrors { + + @Test fun `abstract property type not supported`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + abstract val nonHookProperty: Int + } + """ + ) + + val (_, result) = compile(testHooks) + result.assertOk() + result.assertContainsMessages("Abstract property type (Int) not supported") + } + + @Test fun `hook property does not have any hook annotation`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + abstract val syncHook: SyncHook<*> + } + """ + ) + + val (_, result) = compile(testHooks) + result.assertOk() + result.assertContainsMessages("Hook property must be annotated with respective DSL annotation for SyncHook<*>") + } + + @Test fun `hook property has too many hook annotations`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.BailResult + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @Sync<() -> Unit>() + @SyncBail<() -> BailResult>() + abstract val syncHook: SyncHook<*> + } + """ + ) + + val (_, result) = compile(testHooks) + result.assertOk() + result.assertContainsMessages("This hook has more than a single hook DSL annotation: [@Sync, @SyncBail]") + } + + @Test fun `hook property type does not match annotation`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.BailResult + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @SyncBail<() -> BailResult>() + abstract val syncHook: SyncHook<*> + } + """ + ) + + val (_, result) = compile(testHooks) + result.assertOk() + result.assertContainsMessages("Hook property type (SyncHook<*>) does not match annotation hook type (@SyncBail") + } + + @Test fun `async hooks must has suspend modifier`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.AsyncSeriesHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @AsyncSeries<() -> Unit>() + abstract val syncHook: AsyncSeriesHook<*> + } + """ + ) + + val (_, result) = compile(testHooks) + result.assertOk() + result.assertContainsMessages("Async hooks must be defined with a suspend function signature") + } + + @Test fun `waterfall hook must have at least one parameter`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.SyncWaterfallHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @SyncWaterfall<() -> String>() + abstract val syncHook: SyncWaterfallHook<*, *> + } + """ + ) + + val (_, result) = compile(testHooks) + result.assertOk() + result.assertContainsMessages("Waterfall hooks must take at least one parameter") + } + + @Test fun `waterfall hook must return the same type of the first parameter`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.SyncWaterfallHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @SyncWaterfall<(Int, Int) -> Unit>() + abstract val syncHook: SyncWaterfallHook<*, *> + } + """ + ) + + val (_, result) = compile(testHooks) + result.assertOk() + result.assertContainsMessages("Waterfall hooks must specify the same types for the first parameter and the return type") + } + + @Test fun `multiple validation errors report at the same time`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.AsyncSeriesBailHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @AsyncSeriesWaterfall<() -> String>() + abstract val realBad: AsyncSeriesBailHook<*, *> + abstract val state: Int + } + """ + ) + + val (_, result) = compile(testHooks) + result.assertOk() + result.assertContainsMessages( + "Hook property type (AsyncSeriesBailHook<*, *>) does not match annotation hook type (@AsyncSeriesWaterfall)", + "Async hooks must be defined with a suspend function signature", + "Waterfall hooks must take at least one parameter", + "Waterfall hooks must specify the same types for the first parameter and the return type", + "Abstract property type (Int) not supported", + ) + } +} diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt new file mode 100644 index 0000000..455a2b5 --- /dev/null +++ b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt @@ -0,0 +1,245 @@ +package com.intuit.hooks.plugin + +import com.tschuchort.compiletesting.SourceFile +import org.junit.jupiter.api.Test + +class HooksProcessorTest { + + @Test fun `generates simple sync hook`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @Sync<(String) -> Unit>() + abstract val testSyncHook: SyncHook<*> + } + """ + ) + + val assertions = SourceFile.kotlin( + "Assertions.kt", + """ + import org.junit.jupiter.api.Assertions.* + + fun testHook() { + var tapCalled = false + val hooks = TestHooksImpl() + hooks.testSyncHook.tap("test") { _, x -> tapCalled = true } + hooks.testSyncHook.call("hello") + assertTrue(tapCalled) + } + """ + ) + + val (compilation, result) = compile(testHooks, assertions) + result.assertOk() + compilation.assertKspGeneratedSources("TestHooksImpl.kt") + result.runCompiledAssertions() + } + + @Test fun `generates hook class with the same package`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + package com.intuit.hooks.test + + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @Sync<(String) -> Unit>() + abstract val testSyncHook: SyncHook<*> + } + """ + ) + + val (compilation, result) = compile(testHooks) + result.assertOk() + compilation.assertKspGeneratedSources("com.intuit.hooks.test.TestHooksImpl.kt") + } + + @Test fun `generates hook with nested generic type params`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @Sync<(Map, List>) -> Unit>() + abstract val testSyncHook: SyncHook<*> + } + """ + ) + + val assertions = SourceFile.kotlin( + "Assertions.kt", + """ + import org.junit.jupiter.api.Assertions.* + + fun testHook() { + val item = mapOf(listOf(1, 2, 3) to listOf("one", "two", "three")) + var tappedItem: Map, List>? = null + val hooks = TestHooksImpl() + hooks.testSyncHook.tap("test") { _, x -> tappedItem = x } + hooks.testSyncHook.call(item) + assertEquals(item, tappedItem) + } + """ + ) + + val (compilation, result) = compile(testHooks, assertions) + result.assertOk() + compilation.assertKspGeneratedSources("TestHooksImpl.kt") + result.runCompiledAssertions() + } + + @Test fun `generates AsyncSeriesWaterfallHook`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.AsyncSeriesWaterfallHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @AsyncSeriesWaterfall String>() + abstract val testAsyncSeriesWaterfallHook: AsyncSeriesWaterfallHook<*, *> + } + """ + ) + + val assertions = SourceFile.kotlin( + "Assertions.kt", + """ + import kotlinx.coroutines.runBlocking + import org.junit.jupiter.api.Assertions.* + + fun testHook() { + val initialValue = "hello" + var tappedItem: String? = null + val hooks = TestHooksImpl() + hooks.testAsyncSeriesWaterfallHook.tap("test") { _, x -> + tappedItem = x + x + " world!" + } + val result = runBlocking { hooks.testAsyncSeriesWaterfallHook.call(initialValue) } + assertEquals(initialValue, tappedItem) + assertEquals("hello world!", result) + } + """ + ) + + val (compilation, result) = compile(testHooks, assertions) + result.assertOk() + compilation.assertKspGeneratedSources("TestHooksImpl.kt") + result.runCompiledAssertions() + } + + @Test fun `smoke test`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.* + import com.intuit.hooks.dsl.Hooks + import kotlinx.coroutines.ExperimentalCoroutinesApi + + internal abstract class TestHooks : Hooks() { + @Sync<(newSpeed: Int) -> Unit>() abstract val sync: SyncHook<*> + @SyncBail<(Boolean) -> BailResult>() abstract val syncBail: SyncBailHook<*, *> + @SyncLoop<(foo: Boolean) -> LoopResult>() abstract val syncLoop: SyncLoopHook<*, *> + @SyncWaterfall<(name: String) -> String>() abstract val syncWaterfall: SyncWaterfallHook<*, *> + @ExperimentalCoroutinesApi + @AsyncParallelBail BailResult>() abstract val asyncParallelBail: AsyncParallelBailHook<*, *> + @AsyncParallel Int>() abstract val asyncParallel: AsyncParallelHook<*> + @AsyncSeries Int>() abstract val asyncSeries: AsyncSeriesHook<*> + @AsyncSeriesBail BailResult>() abstract val asyncSeriesBail: AsyncSeriesBailHook<*, *> + @AsyncSeriesLoop LoopResult>() abstract val asyncSeriesLoop: AsyncSeriesLoopHook<*, *> + @AsyncSeriesWaterfall String>() abstract val asyncSeriesWaterfall: AsyncSeriesWaterfallHook<*, *> + } + """ + ) + + val (compilation, result) = compile(testHooks) + result.assertOk() + compilation.assertKspGeneratedSources("TestHooksImpl.kt") + } + + @Test fun `generates generic hook class`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @Sync<(T) -> Unit>() + abstract val testSyncHook: SyncHook<*> + } + """ + ) + + val assertions = SourceFile.kotlin( + "Assertions.kt", + """ + import org.junit.jupiter.api.Assertions.* + + fun testHook() { + val item = "hello" + var tappedValue: String? = null + val hooks = TestHooksImpl() + hooks.testSyncHook.tap("test") { _, x -> tappedValue = x } + hooks.testSyncHook.call(item) + assertEquals(item, tappedValue) + } + """ + ) + + val (compilation, result) = compile(testHooks, assertions) + result.assertOk() + compilation.assertKspGeneratedSources("TestHooksImpl.kt") + result.runCompiledAssertions() + } + + @Test fun `generates nested hook class`() { + val testHooks = SourceFile.kotlin( + "TestHooks.kt", + """ + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.HooksDsl + + class Controller { + abstract class Hooks : HooksDsl() { + @Sync<(String) -> Unit>() + abstract val testSyncHook: SyncHook<*> + } + + val hooks = ControllerHooksImpl() + } + """ + ) + + val assertions = SourceFile.kotlin( + "Assertions.kt", + """ + import org.junit.jupiter.api.Assertions.* + + fun testHook() { + val item = "hello" + var tappedValue: String? = null + val controller = Controller() + controller.hooks.testSyncHook.tap("test") { _, x -> tappedValue = x } + controller.hooks.testSyncHook.call(item) + assertEquals(item, tappedValue) + } + """ + ) + + val (compilation, result) = compile(testHooks, assertions) + result.assertOk() + compilation.assertKspGeneratedSources("ControllerHooksImpl.kt") + result.runCompiledAssertions() + } +} diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/KotlinCompilation.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/KotlinCompilation.kt new file mode 100644 index 0000000..4ad044c --- /dev/null +++ b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/KotlinCompilation.kt @@ -0,0 +1,62 @@ +package com.intuit.hooks.plugin + +import com.intuit.hooks.plugin.ksp.HooksProcessor +import com.tschuchort.compiletesting.* +import org.jetbrains.kotlin.konan.properties.suffix +import org.jetbrains.kotlin.util.removeSuffixIfPresent +import org.junit.jupiter.api.Assertions +import java.io.File +import java.lang.reflect.InvocationTargetException + +val KotlinCompilation.kspGeneratedSources get() = + kspSourcesDir.walkTopDown().filter(File::isFile).toList() + +/** Assert that all [sources] were generated by the KSP processor */ +fun KotlinCompilation.assertKspGeneratedSources(vararg sources: String) { + sources.map { + kspSourcesDir.resolve("kotlin").resolve( + it.removeSuffixIfPresent(".kt").replace(".", "/").suffix("kt") + ) + }.forEach { + Assertions.assertTrue(kspGeneratedSources.contains(it)) { "KSP processing did not generate file: $it" } + } +} + +/** Run patternized assertions from compiled classpath */ +fun KotlinCompilation.Result.runCompiledAssertions(className: String = "AssertionsKt") { + classLoader.loadClass(className).declaredMethods.forEach { + it.isAccessible = true + try { + it.invoke(null) + } catch (exception: InvocationTargetException) { + Assertions.fail("Compiled assertion failed: ${it.name}", exception.targetException) + } + } +} + +fun KotlinCompilation.Result.assertOk() = assertExitCode(KotlinCompilation.ExitCode.OK) +fun KotlinCompilation.Result.assertCompilationError() = assertExitCode(KotlinCompilation.ExitCode.COMPILATION_ERROR) +fun KotlinCompilation.Result.assertExitCode(code: KotlinCompilation.ExitCode) { + Assertions.assertEquals(code, exitCode) +} + +fun KotlinCompilation.Result.assertContainsMessages(vararg messages: String) { + messages.forEach { + Assertions.assertTrue(this.messages.contains(it)) { + "Compilation result messages did not include: $it" + } + } +} + +/** Perform compilation on the [sources], utilizing the default configuration for the [HooksProcessor], if not explicitly configured */ +fun compile( + vararg sources: SourceFile, + block: KotlinCompilation.() -> Unit = { + symbolProcessorProviders = listOf(HooksProcessor.Provider()) + inheritClassPath = true + kspWithCompilation = true + } +): Pair = KotlinCompilation().apply { + this.sources = sources.toList() + block() +}.let { it to it.compile() } diff --git a/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt b/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt index 5c0169a..c4538f0 100644 --- a/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt +++ b/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt @@ -7,6 +7,7 @@ private const val DEPRECATION_MESSAGE = "The migration to KSP requires DSL marke private inline fun stub(): Nothing = throw NotImplementedError("Compiler stub called!") public abstract class Hooks { + // TODO: Make protected? public annotation class Sync> @Deprecated( From 011554868fd2fb6fd63eb3460c7a4beefd0753e6 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Thu, 12 May 2022 21:08:02 -0400 Subject: [PATCH 07/23] re-enable docs + example-application --- build.gradle.kts | 3 +- buildSrc/src/main/kotlin/Jar.kt | 11 ++- compiler-plugin/README.md | 22 +++--- .../hooks/plugin/HookValidationErrors.kt | 14 ++-- .../intuit/hooks/plugin/HooksProcessorTest.kt | 32 ++++---- docs/build.gradle.kts | 76 ++++--------------- .../src/orchid/resources/wiki/key-concepts.md | 3 +- .../resources/wiki/plugin-architecture.md | 17 ++++- .../src/test/kotlin/example/example-car-01.kt | 8 +- .../src/test/kotlin/example/example-car-02.kt | 8 +- .../test/kotlin/example/example-context-01.kt | 3 +- .../src/test/kotlin/example/example-dsl-01.kt | 22 +++--- docs/src/test/kotlin/example/snippets.kt | 3 +- example-application/build.gradle.kts | 8 +- .../intuit/hooks/example/library/car/Car.kt | 12 +-- .../example/library/generic/GenericHooks.kt | 20 ++--- .../main/kotlin/com/intuit/hooks/dsl/Hooks.kt | 40 +++++----- settings.gradle.kts | 37 ++++++++- 18 files changed, 168 insertions(+), 171 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ceaea88..3f03f38 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile allprojects { repositories { - jcenter() mavenLocal() mavenCentral() maven("https://plugins.gradle.org/m2/") @@ -28,7 +27,7 @@ plugins { alias(libs.plugins.dokka) } -val shouldntPublish = listOf("hooks") // listOf("docs", "example-library", "example-application") +val shouldntPublish = listOf("docs", "example-library", "example-application") val publishModules = subprojects.map { it.name }.subtract(shouldntPublish) val isSnapshot = (version as? String)?.contains("-SNAPSHOT") ?: true diff --git a/buildSrc/src/main/kotlin/Jar.kt b/buildSrc/src/main/kotlin/Jar.kt index 555dbaa..53e05af 100644 --- a/buildSrc/src/main/kotlin/Jar.kt +++ b/buildSrc/src/main/kotlin/Jar.kt @@ -2,9 +2,12 @@ import org.gradle.api.tasks.bundling.Jar import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.artifacts.Configuration import org.gradle.api.file.CopySpec +import org.gradle.api.file.DuplicatesStrategy -fun Jar.fromConfiguration(configuration: NamedDomainObjectProvider, block: CopySpec.() -> Unit = {}) = - fromConfiguration(configuration.get(), block) +fun Jar.fromConfiguration(configuration: NamedDomainObjectProvider, block: CopySpec.() -> Unit = { + this.duplicatesStrategy = DuplicatesStrategy.INCLUDE +}) = fromConfiguration(configuration.get(), block) -fun Jar.fromConfiguration(configuration: Configuration, block: CopySpec.() -> Unit = {}) = - from(configuration.map { if (it.isDirectory) it else getProject().zipTree(it) }, block) +fun Jar.fromConfiguration(configuration: Configuration, block: CopySpec.() -> Unit = { + this.duplicatesStrategy = DuplicatesStrategy.INCLUDE +}) = from(configuration.map { if (it.isDirectory) it else project.zipTree(it) }, block) diff --git a/compiler-plugin/README.md b/compiler-plugin/README.md index f10c0d7..b8479af 100644 --- a/compiler-plugin/README.md +++ b/compiler-plugin/README.md @@ -14,17 +14,17 @@ import com.intuit.hooks.dsl.Hooks --> ```kotlin -abstract class GenericHooks : Hooks() { - open val sync = syncHook<(newSpeed: Int) -> Unit>() - open val syncBail = syncBailHook<(Boolean) -> BailResult>() - open val syncLoop = syncLoopHook<(foo: Boolean) -> LoopResult>() - open val syncWaterfall = syncWaterfallHook<(name: String) -> String>() - open val asyncParallelBail = asyncParallelBailHook BailResult>() - open val asyncParallel = asyncParallelHook Int>() - open val asyncSeries = asyncSeriesHook Int>() - open val asyncSeriesBail = asyncSeriesBailHook BailResult>() - open val asyncSeriesLoop = asyncSeriesLoopHook LoopResult>() - open val asyncSeriesWaterfall = asyncSeriesWaterfallHook String>() +internal abstract class GenericHooks : Hooks() { + @Sync<(newSpeed: Int) -> Unit> abstract val sync: SyncHook<*> + @SyncBail<(Boolean) -> BailResult> abstract val syncBail: SyncBailHook<*, *> + @SyncLoop<(foo: Boolean) -> LoopResult> abstract val syncLoop: SyncLoopHook<*, *> + @SyncWaterfall<(name: String) -> String> abstract val syncWaterfall: SyncWaterfallHook<*, *> + @AsyncParallelBail BailResult> abstract val asyncParallelBail: AsyncParallelBailHook<*, *> + @AsyncParallel Int> abstract val asyncParallel: AsyncParallelHook<*> + @AsyncSeries Int> abstract val asyncSeries: AsyncSeriesHook<*> + @AsyncSeriesBail BailResult> abstract val asyncSeriesBail: AsyncSeriesBailHook<*, *> + @AsyncSeriesLoop LoopResult> abstract val asyncSeriesLoop: AsyncSeriesLoopHook<*, *> + @AsyncSeriesWaterfall String> abstract val asyncSeriesWaterfall: AsyncSeriesWaterfallHook<*, *> } ``` diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt index f00e21e..b41c5be 100644 --- a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt +++ b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt @@ -51,8 +51,8 @@ class HookValidationErrors { import com.intuit.hooks.dsl.Hooks internal abstract class TestHooks : Hooks() { - @Sync<() -> Unit>() - @SyncBail<() -> BailResult>() + @Sync<() -> Unit> + @SyncBail<() -> BailResult> abstract val syncHook: SyncHook<*> } """ @@ -72,7 +72,7 @@ class HookValidationErrors { import com.intuit.hooks.dsl.Hooks internal abstract class TestHooks : Hooks() { - @SyncBail<() -> BailResult>() + @SyncBail<() -> BailResult> abstract val syncHook: SyncHook<*> } """ @@ -91,7 +91,7 @@ class HookValidationErrors { import com.intuit.hooks.dsl.Hooks internal abstract class TestHooks : Hooks() { - @AsyncSeries<() -> Unit>() + @AsyncSeries<() -> Unit> abstract val syncHook: AsyncSeriesHook<*> } """ @@ -110,7 +110,7 @@ class HookValidationErrors { import com.intuit.hooks.dsl.Hooks internal abstract class TestHooks : Hooks() { - @SyncWaterfall<() -> String>() + @SyncWaterfall<() -> String> abstract val syncHook: SyncWaterfallHook<*, *> } """ @@ -129,7 +129,7 @@ class HookValidationErrors { import com.intuit.hooks.dsl.Hooks internal abstract class TestHooks : Hooks() { - @SyncWaterfall<(Int, Int) -> Unit>() + @SyncWaterfall<(Int, Int) -> Unit> abstract val syncHook: SyncWaterfallHook<*, *> } """ @@ -148,7 +148,7 @@ class HookValidationErrors { import com.intuit.hooks.dsl.Hooks internal abstract class TestHooks : Hooks() { - @AsyncSeriesWaterfall<() -> String>() + @AsyncSeriesWaterfall<() -> String> abstract val realBad: AsyncSeriesBailHook<*, *> abstract val state: Int } diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt index 455a2b5..dcffb52 100644 --- a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt +++ b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt @@ -13,7 +13,7 @@ class HooksProcessorTest { import com.intuit.hooks.dsl.Hooks internal abstract class TestHooks : Hooks() { - @Sync<(String) -> Unit>() + @Sync<(String) -> Unit> abstract val testSyncHook: SyncHook<*> } """ @@ -50,7 +50,7 @@ class HooksProcessorTest { import com.intuit.hooks.dsl.Hooks internal abstract class TestHooks : Hooks() { - @Sync<(String) -> Unit>() + @Sync<(String) -> Unit> abstract val testSyncHook: SyncHook<*> } """ @@ -69,7 +69,7 @@ class HooksProcessorTest { import com.intuit.hooks.dsl.Hooks internal abstract class TestHooks : Hooks() { - @Sync<(Map, List>) -> Unit>() + @Sync<(Map, List>) -> Unit> abstract val testSyncHook: SyncHook<*> } """ @@ -105,7 +105,7 @@ class HooksProcessorTest { import com.intuit.hooks.dsl.Hooks internal abstract class TestHooks : Hooks() { - @AsyncSeriesWaterfall String>() + @AsyncSeriesWaterfall String> abstract val testAsyncSeriesWaterfallHook: AsyncSeriesWaterfallHook<*, *> } """ @@ -147,17 +147,17 @@ class HooksProcessorTest { import kotlinx.coroutines.ExperimentalCoroutinesApi internal abstract class TestHooks : Hooks() { - @Sync<(newSpeed: Int) -> Unit>() abstract val sync: SyncHook<*> - @SyncBail<(Boolean) -> BailResult>() abstract val syncBail: SyncBailHook<*, *> - @SyncLoop<(foo: Boolean) -> LoopResult>() abstract val syncLoop: SyncLoopHook<*, *> - @SyncWaterfall<(name: String) -> String>() abstract val syncWaterfall: SyncWaterfallHook<*, *> + @Sync<(newSpeed: Int) -> Unit> abstract val sync: SyncHook<*> + @SyncBail<(Boolean) -> BailResult> abstract val syncBail: SyncBailHook<*, *> + @SyncLoop<(foo: Boolean) -> LoopResult> abstract val syncLoop: SyncLoopHook<*, *> + @SyncWaterfall<(name: String) -> String> abstract val syncWaterfall: SyncWaterfallHook<*, *> @ExperimentalCoroutinesApi - @AsyncParallelBail BailResult>() abstract val asyncParallelBail: AsyncParallelBailHook<*, *> - @AsyncParallel Int>() abstract val asyncParallel: AsyncParallelHook<*> - @AsyncSeries Int>() abstract val asyncSeries: AsyncSeriesHook<*> - @AsyncSeriesBail BailResult>() abstract val asyncSeriesBail: AsyncSeriesBailHook<*, *> - @AsyncSeriesLoop LoopResult>() abstract val asyncSeriesLoop: AsyncSeriesLoopHook<*, *> - @AsyncSeriesWaterfall String>() abstract val asyncSeriesWaterfall: AsyncSeriesWaterfallHook<*, *> + @AsyncParallelBail BailResult> abstract val asyncParallelBail: AsyncParallelBailHook<*, *> + @AsyncParallel Int> abstract val asyncParallel: AsyncParallelHook<*> + @AsyncSeries Int> abstract val asyncSeries: AsyncSeriesHook<*> + @AsyncSeriesBail BailResult> abstract val asyncSeriesBail: AsyncSeriesBailHook<*, *> + @AsyncSeriesLoop LoopResult> abstract val asyncSeriesLoop: AsyncSeriesLoopHook<*, *> + @AsyncSeriesWaterfall String> abstract val asyncSeriesWaterfall: AsyncSeriesWaterfallHook<*, *> } """ ) @@ -175,7 +175,7 @@ class HooksProcessorTest { import com.intuit.hooks.dsl.Hooks internal abstract class TestHooks : Hooks() { - @Sync<(T) -> Unit>() + @Sync<(T) -> Unit> abstract val testSyncHook: SyncHook<*> } """ @@ -212,7 +212,7 @@ class HooksProcessorTest { class Controller { abstract class Hooks : HooksDsl() { - @Sync<(String) -> Unit>() + @Sync<(String) -> Unit> abstract val testSyncHook: SyncHook<*> } diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts index c656e87..7372388 100644 --- a/docs/build.gradle.kts +++ b/docs/build.gradle.kts @@ -1,48 +1,26 @@ -val ORCHID_VERSION: String by project - // 1. Apply Orchid plugin plugins { alias(libs.plugins.orchid) alias(libs.plugins.knit) + alias(libs.plugins.ksp) } -fun DependencyHandlerScope.orchidImplementation(name: String, version: String = ORCHID_VERSION) = - orchidImplementation("io.github.javaeden.orchid", name, version) - -fun DependencyHandlerScope.orchidRuntimeOnly(name: String, version: String = ORCHID_VERSION) = - orchidRuntimeOnly("io.github.javaeden.orchid", name, version) - -val compilerPlugin by configurations.creating - // 2. Include Orchid dependencies dependencies { - orchidImplementation("OrchidCore") - orchidImplementation("OrchidCopper") + orchidImplementation(libs.orchid.core) + orchidImplementation(libs.orchid.copper) + orchidRuntimeOnly(libs.bundles.orchid.plugins) - orchidRuntimeOnly("OrchidDocs") - orchidRuntimeOnly("OrchidKotlindoc") - orchidRuntimeOnly("OrchidPluginDocs") - orchidRuntimeOnly("OrchidGithub") - orchidRuntimeOnly("OrchidChangelog") - orchidRuntimeOnly("OrchidSyntaxHighlighter") - orchidRuntimeOnly("OrchidSnippets") - orchidRuntimeOnly("OrchidCopper") - orchidRuntimeOnly("OrchidWiki") - - testImplementation(platform("org.junit:junit-bom:5.7.0")) - testImplementation("org.junit.jupiter", "junit-jupiter") - testImplementation("io.mockk", "mockk", "1.10.2") + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.bundles.testing) // generated test dependencies - val KNIT_VERSION: String by project - val ARROW_VERSION: String by project - val COROUTINES_VERSION: String by project - testImplementation("org.jetbrains.kotlinx:kotlinx-knit-test:$KNIT_VERSION") - testImplementation(kotlin("stdlib")) - testImplementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", COROUTINES_VERSION) - testCompileOnly("io.arrow-kt", "arrow-annotations", ARROW_VERSION) + testImplementation(libs.kotlin.stdlib) + testImplementation(libs.kotlin.coroutines.core) + testImplementation(libs.knit.testing) testImplementation(project(":hooks")) - compilerPlugin(project(":compiler-plugin")) + testImplementation(project(":compiler-plugin")) + ksp(project(":compiler-plugin")) } // 4. Use the 'Editorial' theme, and set the URL it will have on Github Pages @@ -50,36 +28,14 @@ orchid { githubToken = System.getenv("GH_TOKEN") } -val compileOrchidKotlin by tasks.getting(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class) { - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } -} - -val generatedSourcesRoot: String = buildDir.absolutePath - -sourceSets { - test { - java { - srcDir("$generatedSourcesRoot/generated/source/kapt/main") - } +kotlin { + sourceSets.test { + kotlin.srcDir("build/generated/ksp/test/kotlin") } } tasks { - val cleanGenerated by registering { - group = "build" - delete(generatedSourcesRoot) - } - - compileTestKotlin { - dependsOn(cleanGenerated) - dependsOn(compilerPlugin) - kotlinOptions { - verbose = true - freeCompilerArgs = listOf( - "-Xplugin=${compilerPlugin.resolve().first()}" - ) - } + test { + dependsOn(knit) } } diff --git a/docs/src/orchid/resources/wiki/key-concepts.md b/docs/src/orchid/resources/wiki/key-concepts.md index 7e7485c..23a3014 100644 --- a/docs/src/orchid/resources/wiki/key-concepts.md +++ b/docs/src/orchid/resources/wiki/key-concepts.md @@ -82,9 +82,10 @@ Every plugin and some interceptors have access to a `HookContext`, which can be ```kotlin abstract class CarHooks : Hooks() { - open val brake = syncHook<() -> Unit>() - open val accelerate = syncHook<(newSpeed: Int) -> Unit>() + @Sync<() -> Unit> + abstract val brake: SyncHook<*> + + @Sync<(newSpeed: Int) -> Unit> + abstract val accelerate: SyncHook<*> } + ``` For simplicity's sake, say the car API exposes a `speed` API to change the speed: @@ -60,10 +65,14 @@ In the snippet above, loggers were tapped to each hook from the `car` reference. diff --git a/docs/src/test/kotlin/example/example-car-01.kt b/docs/src/test/kotlin/example/example-car-01.kt index 4473d60..8cf6870 100644 --- a/docs/src/test/kotlin/example/example-car-01.kt +++ b/docs/src/test/kotlin/example/example-car-01.kt @@ -2,10 +2,14 @@ package com.intuit.hooks.example.exampleCar01 import com.intuit.hooks.dsl.Hooks +import com.intuit.hooks.SyncHook abstract class CarHooks : Hooks() { - open val brake = syncHook<() -> Unit>() - open val accelerate = syncHook<(newSpeed: Int) -> Unit>() + @Sync<() -> Unit> + abstract val brake: SyncHook<*> + + @Sync<(newSpeed: Int) -> Unit> + abstract val accelerate: SyncHook<*> } class Car { diff --git a/docs/src/test/kotlin/example/example-car-02.kt b/docs/src/test/kotlin/example/example-car-02.kt index 50501db..a94bcb4 100644 --- a/docs/src/test/kotlin/example/example-car-02.kt +++ b/docs/src/test/kotlin/example/example-car-02.kt @@ -2,10 +2,14 @@ package com.intuit.hooks.example.exampleCar02 import com.intuit.hooks.dsl.Hooks +import com.intuit.hooks.SyncHook abstract class CarHooks : Hooks() { - open val brake = syncHook<() -> Unit>() - open val accelerate = syncHook<(newSpeed: Int) -> Unit>() + @Sync<() -> Unit> + abstract val brake: SyncHook<*> + + @Sync<(newSpeed: Int) -> Unit> + abstract val accelerate: SyncHook<*> } class Car(vararg plugins: Plugin) { diff --git a/docs/src/test/kotlin/example/example-context-01.kt b/docs/src/test/kotlin/example/example-context-01.kt index 42b0b5a..410d1c6 100644 --- a/docs/src/test/kotlin/example/example-context-01.kt +++ b/docs/src/test/kotlin/example/example-context-01.kt @@ -2,9 +2,10 @@ package com.intuit.hooks.example.exampleContext01 import com.intuit.hooks.dsl.Hooks +import com.intuit.hooks.SyncHook abstract class CarHooks : Hooks() { - open val accelerate = syncHook<(newSpeed: Int) -> Unit>() + @Sync<(newSpeed: Int) -> Unit> abstract val accelerate: SyncHook<*> } class Car { diff --git a/docs/src/test/kotlin/example/example-dsl-01.kt b/docs/src/test/kotlin/example/example-dsl-01.kt index 11d0cc6..72259af 100644 --- a/docs/src/test/kotlin/example/example-dsl-01.kt +++ b/docs/src/test/kotlin/example/example-dsl-01.kt @@ -4,17 +4,17 @@ package com.intuit.hooks.example.exampleDsl01 import com.intuit.hooks.* import com.intuit.hooks.dsl.Hooks -abstract class GenericHooks : Hooks() { - open val sync = syncHook<(newSpeed: Int) -> Unit>() - open val syncBail = syncBailHook<(Boolean) -> BailResult>() - open val syncLoop = syncLoopHook<(foo: Boolean) -> LoopResult>() - open val syncWaterfall = syncWaterfallHook<(name: String) -> String>() - open val asyncParallelBail = asyncParallelBailHook BailResult>() - open val asyncParallel = asyncParallelHook Int>() - open val asyncSeries = asyncSeriesHook Int>() - open val asyncSeriesBail = asyncSeriesBailHook BailResult>() - open val asyncSeriesLoop = asyncSeriesLoopHook LoopResult>() - open val asyncSeriesWaterfall = asyncSeriesWaterfallHook String>() +internal abstract class GenericHooks : Hooks() { + @Sync<(newSpeed: Int) -> Unit> abstract val sync: SyncHook<*> + @SyncBail<(Boolean) -> BailResult> abstract val syncBail: SyncBailHook<*, *> + @SyncLoop<(foo: Boolean) -> LoopResult> abstract val syncLoop: SyncLoopHook<*, *> + @SyncWaterfall<(name: String) -> String> abstract val syncWaterfall: SyncWaterfallHook<*, *> + @AsyncParallelBail BailResult> abstract val asyncParallelBail: AsyncParallelBailHook<*, *> + @AsyncParallel Int> abstract val asyncParallel: AsyncParallelHook<*> + @AsyncSeries Int> abstract val asyncSeries: AsyncSeriesHook<*> + @AsyncSeriesBail BailResult> abstract val asyncSeriesBail: AsyncSeriesBailHook<*, *> + @AsyncSeriesLoop LoopResult> abstract val asyncSeriesLoop: AsyncSeriesLoopHook<*, *> + @AsyncSeriesWaterfall String> abstract val asyncSeriesWaterfall: AsyncSeriesWaterfallHook<*, *> } fun main() { diff --git a/docs/src/test/kotlin/example/snippets.kt b/docs/src/test/kotlin/example/snippets.kt index 6ff13c9..4a4e750 100644 --- a/docs/src/test/kotlin/example/snippets.kt +++ b/docs/src/test/kotlin/example/snippets.kt @@ -54,7 +54,8 @@ fun typed() { // START concise_dsl abstract class SomeHooks : Hooks() { - open val syncHook = syncHook<() -> Unit>() + @Sync<() -> Unit> + abstract val syncHook: SyncHook<*> } // END concise_dsl diff --git a/example-application/build.gradle.kts b/example-application/build.gradle.kts index 7764da8..ec4e7ae 100644 --- a/example-application/build.gradle.kts +++ b/example-application/build.gradle.kts @@ -2,18 +2,14 @@ plugins { application } -val COROUTINES_VERSION: String by project - // Resolvable configuration to help configure dependency resolution for the jar task val projectImplementation: Configuration by configurations.creating { configurations.implementation.get().extendsFrom(this) } dependencies { - implementation(kotlin("stdlib")) - implementation(kotlin("reflect")) - implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", COROUTINES_VERSION) - + implementation(libs.kotlin.stdlib) + implementation(libs.kotlin.coroutines.core) projectImplementation(project(":example-library")) } diff --git a/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt b/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt index 1424bdc..959d43c 100644 --- a/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt +++ b/example-library/src/main/kotlin/com/intuit/hooks/example/library/car/Car.kt @@ -2,8 +2,6 @@ package com.intuit.hooks.example.library.car import com.intuit.hooks.AsyncSeriesWaterfallHook import com.intuit.hooks.SyncHook -import com.intuit.hooks.dsl.Hooks.AsyncSeriesWaterfall -import com.intuit.hooks.dsl.Hooks.Sync import com.intuit.hooks.dsl.HooksDsl public abstract class Location @@ -14,17 +12,13 @@ public class Car { public abstract class Hooks : HooksDsl() { - @Sync<(newSpeed: Int) -> Unit>() + @Sync<(newSpeed: Int) -> Unit> public abstract val accelerate: SyncHook<*> - @Sync<() -> Unit>() + @Sync<() -> Unit> public abstract val brake: SyncHook<*> - @AsyncSeriesWaterfall, - source: Location, - target: Location - ) -> List> + @AsyncSeriesWaterfall, source: Location, target: Location) -> List> public abstract val calculateRoutes: AsyncSeriesWaterfallHook<*, *> } diff --git a/example-library/src/main/kotlin/com/intuit/hooks/example/library/generic/GenericHooks.kt b/example-library/src/main/kotlin/com/intuit/hooks/example/library/generic/GenericHooks.kt index 90cc774..9814e46 100644 --- a/example-library/src/main/kotlin/com/intuit/hooks/example/library/generic/GenericHooks.kt +++ b/example-library/src/main/kotlin/com/intuit/hooks/example/library/generic/GenericHooks.kt @@ -5,15 +5,15 @@ import com.intuit.hooks.dsl.Hooks import kotlinx.coroutines.ExperimentalCoroutinesApi internal abstract class GenericHooks : Hooks() { - @Sync<(newSpeed: Int) -> Unit>() abstract val sync: SyncHook<*> - @SyncBail<(Boolean) -> BailResult>() abstract val syncBail: SyncBailHook<*, *> - @SyncLoop<(foo: Boolean) -> LoopResult>() abstract val syncLoop: SyncLoopHook<*, *> - @SyncWaterfall<(name: String) -> String>() abstract val syncWaterfall: SyncWaterfallHook<*, *> + @Sync<(newSpeed: Int) -> Unit> abstract val sync: SyncHook<*> + @SyncBail<(Boolean) -> BailResult> abstract val syncBail: SyncBailHook<*, *> + @SyncLoop<(foo: Boolean) -> LoopResult> abstract val syncLoop: SyncLoopHook<*, *> + @SyncWaterfall<(name: String) -> String> abstract val syncWaterfall: SyncWaterfallHook<*, *> @ExperimentalCoroutinesApi - @AsyncParallelBail BailResult>() abstract val asyncParallelBail: AsyncParallelBailHook<*, *> - @AsyncParallel Int>() abstract val asyncParallel: AsyncParallelHook<*> - @AsyncSeries Int>() abstract val asyncSeries: AsyncSeriesHook<*> - @AsyncSeriesBail BailResult>() abstract val asyncSeriesBail: AsyncSeriesBailHook<*, *> - @AsyncSeriesLoop LoopResult>() abstract val asyncSeriesLoop: AsyncSeriesLoopHook<*, *> - @AsyncSeriesWaterfall String>() abstract val asyncSeriesWaterfall: AsyncSeriesWaterfallHook<*, *> + @AsyncParallelBail BailResult> abstract val asyncParallelBail: AsyncParallelBailHook<*, *> + @AsyncParallel Int> abstract val asyncParallel: AsyncParallelHook<*> + @AsyncSeries Int> abstract val asyncSeries: AsyncSeriesHook<*> + @AsyncSeriesBail BailResult> abstract val asyncSeriesBail: AsyncSeriesBailHook<*, *> + @AsyncSeriesLoop LoopResult> abstract val asyncSeriesLoop: AsyncSeriesLoopHook<*, *> + @AsyncSeriesWaterfall String> abstract val asyncSeriesWaterfall: AsyncSeriesWaterfallHook<*, *> } diff --git a/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt b/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt index c4538f0..9981c92 100644 --- a/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt +++ b/hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt @@ -8,92 +8,92 @@ private inline fun stub(): Nothing = throw NotImplementedError("Compiler stub ca public abstract class Hooks { // TODO: Make protected? - public annotation class Sync> + protected annotation class Sync> @Deprecated( DEPRECATION_MESSAGE, - ReplaceWith("@Hooks.Sync()"), + ReplaceWith("@Hooks.Sync"), DeprecationLevel.ERROR, ) protected fun > syncHook(): SyncHook<*> = stub() - public annotation class SyncBail> + protected annotation class SyncBail> @Deprecated( DEPRECATION_MESSAGE, - ReplaceWith("@Hooks.SyncBail()"), + ReplaceWith("@Hooks.SyncBail"), DeprecationLevel.ERROR, ) protected fun >> syncBailHook(): SyncBailHook<*, *> = stub() - public annotation class SyncWaterfall> + protected annotation class SyncWaterfall> @Deprecated( DEPRECATION_MESSAGE, - ReplaceWith("@Hooks.SyncWaterfall()"), + ReplaceWith("@Hooks.SyncWaterfall"), DeprecationLevel.ERROR, ) protected fun > syncWaterfallHook(): SyncWaterfallHook<*, *> = stub() - public annotation class SyncLoop> + protected annotation class SyncLoop> @Deprecated( DEPRECATION_MESSAGE, - ReplaceWith("@Hooks.SyncLoop()"), + ReplaceWith("@Hooks.SyncLoop"), DeprecationLevel.ERROR, ) protected fun > syncLoopHook(): SyncLoopHook<*, *> = stub() - public annotation class AsyncParallel> + protected annotation class AsyncParallel> @Deprecated( DEPRECATION_MESSAGE, - ReplaceWith("@Hooks.AsyncParallel()"), + ReplaceWith("@Hooks.AsyncParallel"), DeprecationLevel.ERROR, ) protected fun > asyncParallelHook(): AsyncParallelHook<*> = stub() - public annotation class AsyncParallelBail> + protected annotation class AsyncParallelBail> @Deprecated( DEPRECATION_MESSAGE, - ReplaceWith("@Hooks.AsyncParallelBail()"), + ReplaceWith("@Hooks.AsyncParallelBail"), DeprecationLevel.ERROR, ) @ExperimentalCoroutinesApi protected fun >> asyncParallelBailHook(): AsyncParallelBailHook<*, *> = stub() - public annotation class AsyncSeries> + protected annotation class AsyncSeries> @Deprecated( DEPRECATION_MESSAGE, - ReplaceWith("@Hooks.AsyncSeries()"), + ReplaceWith("@Hooks.AsyncSeries"), DeprecationLevel.ERROR, ) protected fun > asyncSeriesHook(): AsyncSeriesHook<*> = stub() - public annotation class AsyncSeriesBail> + protected annotation class AsyncSeriesBail> @Deprecated( DEPRECATION_MESSAGE, - ReplaceWith("@Hooks.AsyncSeriesBail()"), + ReplaceWith("@Hooks.AsyncSeriesBail"), DeprecationLevel.ERROR, ) protected fun >> asyncSeriesBailHook(): AsyncSeriesBailHook<*, *> = stub() - public annotation class AsyncSeriesWaterfall> + protected annotation class AsyncSeriesWaterfall> @Deprecated( DEPRECATION_MESSAGE, - ReplaceWith("@Hooks.AsyncSeriesWaterfall()"), + ReplaceWith("@Hooks.AsyncSeriesWaterfall"), DeprecationLevel.ERROR, ) protected fun > asyncSeriesWaterfallHook(): AsyncSeriesWaterfallHook<*, *> = stub() - public annotation class AsyncSeriesLoop> + protected annotation class AsyncSeriesLoop> @Deprecated( DEPRECATION_MESSAGE, - ReplaceWith("@Hooks.AsyncSeriesLoop()"), + ReplaceWith("@Hooks.AsyncSeriesLoop"), DeprecationLevel.ERROR, ) protected fun > asyncSeriesLoopHook(): AsyncSeriesLoopHook<*, *> = stub() diff --git a/settings.gradle.kts b/settings.gradle.kts index b3d65fc..97a5a52 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,9 +4,9 @@ include( ":compiler-plugin", // ":gradle-plugin", // ":maven-plugin", -// ":docs", + ":docs", ":example-library", -// ":example-application" + ":example-application", ) enableFeaturePreview("ONE_LOCKFILE_PER_PROJECT") @@ -19,6 +19,8 @@ dependencyResolutionManagement { version("ksp", "1.6.21-1.0.5") version("poet", "1.11.0") version("junit", "5.7.0") + version("knit", "0.2.3") // TODO: Upgrade + version("orchid", "0.21.1") plugin("kotlin.jvm", "org.jetbrains.kotlin.jvm").versionRef("kotlin") plugin("ksp", "com.google.devtools.ksp").versionRef("ksp") @@ -29,9 +31,9 @@ dependencyResolutionManagement { plugin("ktlint", "org.jlleitschuh.gradle.ktlint").version("10.3.0") plugin("api", "org.jetbrains.kotlinx.binary-compatibility-validator").version("0.9.0") - plugin("knit", "kotlinx-knit").version("0.2.3") + plugin("knit", "kotlinx-knit").versionRef("knit") plugin("dokka", "org.jetbrains.dokka").versionRef("kotlin") - plugin("orchid", "com.eden.orchidPlugin").version("0.21.1") + plugin("orchid", "com.eden.orchidPlugin").versionRef("orchid") // Kotlin library("kotlin.stdlib", "org.jetbrains.kotlin", "kotlin-stdlib").withoutVersion() @@ -46,12 +48,39 @@ dependencyResolutionManagement { // Arrow library("arrow.core", "io.arrow-kt", "arrow-core").versionRef("arrow") + // Docs + library("orchid.core", "io.github.javaeden.orchid", "OrchidCore").versionRef("orchid") + library("orchid.copper", "io.github.javaeden.orchid", "OrchidCopper").versionRef("orchid") + + library("orchid.plugins.docs", "io.github.javaeden.orchid", "OrchidDocs").versionRef("orchid") + library("orchid.plugins.kotlindoc", "io.github.javaeden.orchid", "OrchidKotlindoc").versionRef("orchid") + library("orchid.plugins.plugindocs", "io.github.javaeden.orchid", "OrchidPluginDocs").versionRef("orchid") + library("orchid.plugins.github", "io.github.javaeden.orchid", "OrchidGithub").versionRef("orchid") + library("orchid.plugins.changelog", "io.github.javaeden.orchid", "OrchidChangelog").versionRef("orchid") + library("orchid.plugins.syntaxHighlighter", "io.github.javaeden.orchid", "OrchidSyntaxHighlighter").versionRef("orchid") + library("orchid.plugins.snippets", "io.github.javaeden.orchid", "OrchidSnippets").versionRef("orchid") + library("orchid.plugins.copper", "io.github.javaeden.orchid", "OrchidCopper").versionRef("orchid") + library("orchid.plugins.wiki", "io.github.javaeden.orchid", "OrchidWiki").versionRef("orchid") + + bundle("orchid.plugins", listOf( + "orchid.plugins.docs", + "orchid.plugins.kotlindoc", + "orchid.plugins.plugindocs", + "orchid.plugins.github", + "orchid.plugins.changelog", + "orchid.plugins.syntaxHighlighter", + "orchid.plugins.snippets", + "orchid.plugins.copper", + "orchid.plugins.wiki", + )) + // Testing // TODO: Swap to Kotlin testing library library("junit.bom", "org.junit", "junit-bom").version("5.7.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").withoutVersion() library("mockk", "io.mockk", "mockk").version("1.10.2") library("ksp.testing", "com.github.tschuchortdev", "kotlin-compile-testing-ksp").version("1.4.8") + library("knit.testing", "org.jetbrains.kotlinx" , "kotlinx-knit-test").versionRef("knit") bundle("testing", listOf("junit.jupiter", "mockk")) } From eb94c1f5239c945e44a294d2043c1e3911dde8eb Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Fri, 13 May 2022 21:45:41 -0400 Subject: [PATCH 08/23] gradle plugin --- build.gradle.kts | 8 ++ .../intuit/hooks/plugin/ksp/HooksProcessor.kt | 3 +- gradle-plugin/api/gradle-plugin.api | 7 +- gradle-plugin/build.gradle.kts | 24 +++--- .../plugin/gradle/HooksGradleExtension.kt | 6 +- .../hooks/plugin/gradle/HooksGradlePlugin.kt | 75 ++++------------- .../src/test/kotlin/HooksGradlePluginTest.kt | 84 +++++++++++++++++++ hooks/api/hooks.api | 30 +++++++ settings.gradle.kts | 30 ++++--- 9 files changed, 170 insertions(+), 97 deletions(-) create mode 100644 gradle-plugin/src/test/kotlin/HooksGradlePluginTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 3f03f38..66384bd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -141,6 +141,8 @@ subprojects { val signingPassword by auth useInMemoryPgpKeys(signingKey, signingPassword) sign(extensions.findByType(PublishingExtension::class.java)!!.publications) + } ?: run { + isRequired = false } } @@ -159,6 +161,12 @@ subprojects { } } + ktlint { + filter { + exclude("**/example/**/*.kt") + } + } + configure { sourceCompatibility = JavaVersion.VERSION_1_8 withSourcesJar() diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt index b107371..fe3b7ac 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt @@ -48,7 +48,7 @@ public class HooksProcessor( .filterIsInstance() .map(KSClassifierReference::referencedName) - // TODO: Account for import aliases :P + how to avoid false positives? + // TODO: Account for import aliases :P + how to avoid false positives? probably through resolve if (!superTypeNames.contains("Hooks") && !superTypeNames.contains("HooksDsl")) { classDeclaration.declarations.filter { it is KSClassDeclaration && it.validate() @@ -121,7 +121,6 @@ public class HooksProcessor( private fun KSClassDeclaration.findHooks() = getAllProperties() .filter { // Only process properties that are abstract b/c that's what we need for a concrete class - it.modifiers.contains(Modifier.ABSTRACT) } .map(::validateProperty) diff --git a/gradle-plugin/api/gradle-plugin.api b/gradle-plugin/api/gradle-plugin.api index 14f1cfc..579016c 100644 --- a/gradle-plugin/api/gradle-plugin.api +++ b/gradle-plugin/api/gradle-plugin.api @@ -4,14 +4,9 @@ public class com/intuit/hooks/plugin/gradle/HooksGradleExtension { public final fun setGeneratedSrcOutputDir (Ljava/lang/String;)V } -public final class com/intuit/hooks/plugin/gradle/HooksGradlePlugin : org/jetbrains/kotlin/gradle/plugin/KotlinCompilerPluginSupportPlugin { +public final class com/intuit/hooks/plugin/gradle/HooksGradlePlugin : org/gradle/api/Plugin { public fun ()V public synthetic fun apply (Ljava/lang/Object;)V public fun apply (Lorg/gradle/api/Project;)V - public fun applyToCompilation (Lorg/jetbrains/kotlin/gradle/plugin/KotlinCompilation;)Lorg/gradle/api/provider/Provider; - public fun getCompilerPluginId ()Ljava/lang/String; - public fun getPluginArtifact ()Lorg/jetbrains/kotlin/gradle/plugin/SubpluginArtifact; - public fun getPluginArtifactForNative ()Lorg/jetbrains/kotlin/gradle/plugin/SubpluginArtifact; - public fun isApplicable (Lorg/jetbrains/kotlin/gradle/plugin/KotlinCompilation;)Z } diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index f92ca44..60aabe4 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -3,8 +3,7 @@ import org.jetbrains.kotlin.konan.properties.Properties import org.jetbrains.kotlin.konan.properties.saveToFile plugins { - `java-gradle-plugin` - id("com.gradle.plugin-publish") version "0.13.0" + id("com.gradle.plugin-publish") version "1.0.0-rc-2" } gradlePlugin { @@ -12,26 +11,26 @@ gradlePlugin { create("HooksGradlePlugin") { id = "com.intuit.hooks" implementationClass = "com.intuit.hooks.plugin.gradle.HooksGradlePlugin" + displayName = "Gradle Hooks plugin" } } + + testSourceSets(sourceSets.test.get()) } pluginBundle { website = "https://intuit.github.io/hooks/" vcsUrl = "https://github.com/intuit/hooks" description = "Gradle wrapper of the Kotlin compiler companion to the Intuit hooks module" - tags = listOf("plugins", "hooks") - - plugins { - named("HooksGradlePlugin") { - displayName = "Gradle Hooks plugin" - } - } + tags = listOf("plugins", "hooks", "ksp", "codegen") } dependencies { - implementation(kotlin("stdlib")) - implementation(kotlin("gradle-plugin-api")) + implementation(libs.kotlin.stdlib) + implementation(libs.ksp.gradle) + + testImplementation(platform(libs.junit.bom)) + testImplementation(libs.bundles.testing) } tasks { @@ -39,11 +38,8 @@ tasks { dependsOn(processResources) doLast { - val ARROW_VERSION: String by project - Properties().apply { set("version", project.version.toString()) - set("arrowVersion", ARROW_VERSION) }.saveToFile(File("$buildDir/resources/main/version.properties")) } } diff --git a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradleExtension.kt b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradleExtension.kt index be11a8d..27e55f7 100644 --- a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradleExtension.kt +++ b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradleExtension.kt @@ -1,10 +1,6 @@ package com.intuit.hooks.plugin.gradle -/** - * Any options to be used to configure the hooks-plugin. - * - * TODO: Still need to create a custom [CommandLineProcessor] to handle this - */ +/** Any options to be used to configure the hooks-plugin */ public open class HooksGradleExtension { public var generatedSrcOutputDir: String? = null } diff --git a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt index e64e9c5..d1ebdef 100644 --- a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt +++ b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt @@ -1,21 +1,14 @@ package com.intuit.hooks.plugin.gradle +import com.google.devtools.ksp.gradle.KspExtension +import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.artifacts.DependencyResolutionListener -import org.gradle.api.artifacts.ResolvableDependencies import org.gradle.api.plugins.JavaPlugin -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.SourceSetContainer -import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation -import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin -import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact -import org.jetbrains.kotlin.gradle.plugin.SubpluginOption -import java.nio.file.Paths import java.util.* -/** Bridge between compiler-plugin and gradle plugin */ -public class HooksGradlePlugin : KotlinCompilerPluginSupportPlugin { +/** Wrap KSP plugin and provide Gradle extension for Hooks processor options */ +public class HooksGradlePlugin : Plugin { private val properties by lazy { val properties = Properties() @@ -32,62 +25,30 @@ public class HooksGradlePlugin : KotlinCompilerPluginSupportPlugin { dependencies.create(dependencyNotation) ) - override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = kotlinCompilation - .target.project.plugins.hasPlugin(HooksGradlePlugin::class.java) - - override fun getCompilerPluginId(): String = "arrow.meta.plugin.compiler" - - override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact( - "com.intuit.hooks", - "compiler-plugin", - version - ) - override fun apply(target: Project) { - target.extensions.create( + val hooksExtension = target.extensions.create( "hooks", HooksGradleExtension::class.java ) - target.gradle.addListener( - object : DependencyResolutionListener { - override fun beforeResolve(dependencies: ResolvableDependencies) { - target.addDependency("api", "com.intuit.hooks:hooks:$version") - target.gradle.removeListener(this) - } + if (!target.pluginManager.hasPlugin("com.google.devtools.ksp")) + target.pluginManager.apply("com.google.devtools.ksp") - override fun afterResolve(dependencies: ResolvableDependencies) = Unit + target.extensions.configure("ksp") { ksp -> + hooksExtension.generatedSrcOutputDir?.let { generatedSrcOutputDir -> + ksp.arg("generatedSrcOutputDir", generatedSrcOutputDir) } - ) - } - - override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider> = kotlinCompilation.target.project.run { - val extension = extensions.findByType(HooksGradleExtension::class.java) - ?: HooksGradleExtension() - - // do validations here - val generatedSrcOutputDir = extension.generatedSrcOutputDir - ?: buildDir.absolutePath - - // aggregate subplugin options - val generated = SubpluginOption("generatedSrcOutputDir", generatedSrcOutputDir) - - // add generatedSrcOutputDir to default source set - plugins.withType(JavaPlugin::class.java) { javaPlugin -> - val sourceSets = extensions.getByType(SourceSetContainer::class.java) - val main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME) - main.java.srcDir(Paths.get(generatedSrcOutputDir, "generated", "source", "kapt", "main")) } - val cleanGenerated = tasks.findByPath("cleanGenerated") ?: tasks.register("cleanGenerated") { - it.group = "build" - delete(generatedSrcOutputDir) - } - - kotlinCompilation.compileKotlinTask.dependsOn(cleanGenerated) + target.addDependency("api", "com.intuit.hooks:hooks:$version") + target.addDependency("ksp", "com.intuit.hooks:compiler-plugin:$version") - provider { - listOf(generated) + // TODO: Maybe apply to Kotlin plugin to be compatible with MPP + target.plugins.withType(JavaPlugin::class.java) { javaPlugin -> + val sourceSets = target.extensions.getByType(SourceSetContainer::class.java) + sourceSets.forEach { + it.java.srcDir(target.buildDir.resolve(hooksExtension.generatedSrcOutputDir ?: "generated/ksp/${it.name}/kotlin")) + } } } } diff --git a/gradle-plugin/src/test/kotlin/HooksGradlePluginTest.kt b/gradle-plugin/src/test/kotlin/HooksGradlePluginTest.kt new file mode 100644 index 0000000..bf7192a --- /dev/null +++ b/gradle-plugin/src/test/kotlin/HooksGradlePluginTest.kt @@ -0,0 +1,84 @@ +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.io.TempDir +import java.io.File + +private fun File.appendKotlin(@Language("kotlin") content: String, trimIndent: Boolean = true) { + require(extension.matches(Regex("kt(s)?"))) + appendText("\n${if (trimIndent) content.trimIndent() else content}\n") +} + +class HooksGradlePluginTest { + + @TempDir lateinit var workingDir: File + + lateinit var buildFile: File + + @BeforeEach fun setup() { + buildFile = workingDir.resolve("build.gradle.kts").apply(File::createNewFile) + buildFile.appendKotlin( + """ + repositories { + mavenLocal() + mavenCentral() + } + + plugins { + kotlin("jvm") + id("com.intuit.hooks") + } + """ + ) + } + + @Test fun `can apply plugin`() { + buildFile.appendKotlin( + """ + hooks { + generatedSrcOutputDir = "asdf" + } + """ + ) + + assertDoesNotThrow { + GradleRunner.create() + .withProjectDir(workingDir) + .withPluginClasspath() + .build() + } + } + + @Test fun `can code gen`() { + val testHooks = workingDir.resolve("src/main/kotlin/TestHooks.kt").apply { + parentFile.mkdirs() + createNewFile() + } + testHooks.appendKotlin( + """ + import com.intuit.hooks.SyncHook + import com.intuit.hooks.dsl.Hooks + + internal abstract class TestHooks : Hooks() { + @Sync<(String) -> Unit> + abstract val testSyncHook: SyncHook<*> + } + """ + ) + + val runner = GradleRunner.create() + .withProjectDir(workingDir) + .withArguments("build") + .withPluginClasspath() + .forwardOutput() + .build() + + assertEquals(TaskOutcome.SUCCESS, runner.task(":kspKotlin")?.outcome) + assertTrue(workingDir.resolve("build/generated/ksp/main/kotlin/TestHooksImpl.kt").exists()) + } +} diff --git a/hooks/api/hooks.api b/hooks/api/hooks.api index ce85c3e..cfdcce1 100644 --- a/hooks/api/hooks.api +++ b/hooks/api/hooks.api @@ -142,3 +142,33 @@ public abstract class com/intuit/hooks/dsl/Hooks { protected final fun syncWaterfallHook ()Lcom/intuit/hooks/SyncWaterfallHook; } +protected abstract interface annotation class com/intuit/hooks/dsl/Hooks$AsyncParallel : java/lang/annotation/Annotation { +} + +protected abstract interface annotation class com/intuit/hooks/dsl/Hooks$AsyncParallelBail : java/lang/annotation/Annotation { +} + +protected abstract interface annotation class com/intuit/hooks/dsl/Hooks$AsyncSeries : java/lang/annotation/Annotation { +} + +protected abstract interface annotation class com/intuit/hooks/dsl/Hooks$AsyncSeriesBail : java/lang/annotation/Annotation { +} + +protected abstract interface annotation class com/intuit/hooks/dsl/Hooks$AsyncSeriesLoop : java/lang/annotation/Annotation { +} + +protected abstract interface annotation class com/intuit/hooks/dsl/Hooks$AsyncSeriesWaterfall : java/lang/annotation/Annotation { +} + +protected abstract interface annotation class com/intuit/hooks/dsl/Hooks$Sync : java/lang/annotation/Annotation { +} + +protected abstract interface annotation class com/intuit/hooks/dsl/Hooks$SyncBail : java/lang/annotation/Annotation { +} + +protected abstract interface annotation class com/intuit/hooks/dsl/Hooks$SyncLoop : java/lang/annotation/Annotation { +} + +protected abstract interface annotation class com/intuit/hooks/dsl/Hooks$SyncWaterfall : java/lang/annotation/Annotation { +} + diff --git a/settings.gradle.kts b/settings.gradle.kts index 97a5a52..95adf2e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ rootProject.name = "hooks-project" include( ":hooks", ":compiler-plugin", -// ":gradle-plugin", + ":gradle-plugin", // ":maven-plugin", ":docs", ":example-library", @@ -42,6 +42,7 @@ dependencyResolutionManagement { // KSP library("ksp.spa", "com.google.devtools.ksp", "symbol-processing-api").versionRef("ksp") library("ksp.poet", "com.squareup", "kotlinpoet-ksp").versionRef("poet") + library("ksp.gradle", "com.google.devtools.ksp", "com.google.devtools.ksp.gradle.plugin").versionRef("ksp") library("ktlint.core", "com.pinterest.ktlint", "ktlint-core").versionRef("ktlint") library("ktlint.ruleset.standard", "com.pinterest.ktlint", "ktlint-ruleset-standard").versionRef("ktlint") @@ -62,17 +63,20 @@ dependencyResolutionManagement { library("orchid.plugins.copper", "io.github.javaeden.orchid", "OrchidCopper").versionRef("orchid") library("orchid.plugins.wiki", "io.github.javaeden.orchid", "OrchidWiki").versionRef("orchid") - bundle("orchid.plugins", listOf( - "orchid.plugins.docs", - "orchid.plugins.kotlindoc", - "orchid.plugins.plugindocs", - "orchid.plugins.github", - "orchid.plugins.changelog", - "orchid.plugins.syntaxHighlighter", - "orchid.plugins.snippets", - "orchid.plugins.copper", - "orchid.plugins.wiki", - )) + bundle( + "orchid.plugins", + listOf( + "orchid.plugins.docs", + "orchid.plugins.kotlindoc", + "orchid.plugins.plugindocs", + "orchid.plugins.github", + "orchid.plugins.changelog", + "orchid.plugins.syntaxHighlighter", + "orchid.plugins.snippets", + "orchid.plugins.copper", + "orchid.plugins.wiki", + ) + ) // Testing // TODO: Swap to Kotlin testing library @@ -80,7 +84,7 @@ dependencyResolutionManagement { library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").withoutVersion() library("mockk", "io.mockk", "mockk").version("1.10.2") library("ksp.testing", "com.github.tschuchortdev", "kotlin-compile-testing-ksp").version("1.4.8") - library("knit.testing", "org.jetbrains.kotlinx" , "kotlinx-knit-test").versionRef("knit") + library("knit.testing", "org.jetbrains.kotlinx", "kotlinx-knit-test").versionRef("knit") bundle("testing", listOf("junit.jupiter", "mockk")) } From 42fc7bef253fbd0400ad00fb7af43cba0e5b2be9 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Mon, 16 May 2022 20:08:56 -0400 Subject: [PATCH 09/23] maven plugin --- compiler-plugin/build.gradle.kts | 6 +-- .../intuit/hooks/plugin/ksp/HooksProcessor.kt | 35 ++++++++--------- example-library/build.gradle.kts | 1 - maven-plugin/build.gradle.kts | 21 +++++++--- .../hooks/plugin/maven/HooksMavenPlugin.kt | 39 +++++++------------ .../resources/META-INF/plexus/components.xml | 5 +++ settings.gradle.kts | 3 +- 7 files changed, 55 insertions(+), 55 deletions(-) diff --git a/compiler-plugin/build.gradle.kts b/compiler-plugin/build.gradle.kts index 7d79372..31157ca 100644 --- a/compiler-plugin/build.gradle.kts +++ b/compiler-plugin/build.gradle.kts @@ -1,8 +1,8 @@ dependencies { implementation(libs.ksp.spa) - implementation(libs.ksp.poet) - implementation(libs.ktlint.core) - implementation(libs.ktlint.ruleset.standard) +// implementation(libs.ksp.poet) +// implementation(libs.ktlint.core) +// implementation(libs.ktlint.ruleset.standard) implementation(libs.arrow.core) testImplementation(project(":hooks")) diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt index fe3b7ac..b526024 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt @@ -12,10 +12,6 @@ import com.intuit.hooks.plugin.codegen.generateClass import com.intuit.hooks.plugin.codegen.generateImports import com.intuit.hooks.plugin.codegen.generateProperty import com.intuit.hooks.plugin.ksp.validation.validateProperty -import com.pinterest.ktlint.core.KtLint -import com.pinterest.ktlint.core.KtLint.format -import com.pinterest.ktlint.core.api.FeatureInAlphaState -import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider public class HooksProcessor( private val codeGenerator: CodeGenerator, @@ -40,7 +36,7 @@ public class HooksProcessor( } } - @OptIn(FeatureInAlphaState::class) +// @OptIn(FeatureInAlphaState::class) override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { // TODO: This should really be restructured to follow KSP visitor pattern for members val superTypeNames = classDeclaration.superTypes @@ -94,20 +90,21 @@ public class HooksProcessor( classDeclaration ) - KtLint.ExperimentalParams( - text = newSource, - ruleSets = listOf(StandardRuleSetProvider().get()), - cb = { _, _ -> } - ) - .let(::format) - .also { - logger.logging( - """formatted generated source: - |$it - """.trimMargin(), - classDeclaration - ) - } +// KtLint.ExperimentalParams( +// text = newSource, +// ruleSets = listOf(StandardRuleSetProvider().get()), +// cb = { _, _ -> } +// ) +// .let(::format) +// .also { +// logger.logging( +// """formatted generated source: +// |$it +// """.trimMargin(), +// classDeclaration +// ) +// } + newSource .let(String::toByteArray) .let(file::write) diff --git a/example-library/build.gradle.kts b/example-library/build.gradle.kts index 9a7c7e4..98acf55 100644 --- a/example-library/build.gradle.kts +++ b/example-library/build.gradle.kts @@ -7,7 +7,6 @@ dependencies { implementation(libs.kotlin.coroutines.core) api(project(":hooks")) - implementation(project(":compiler-plugin")) ksp(project(":compiler-plugin")) testImplementation(platform(libs.junit.bom)) diff --git a/maven-plugin/build.gradle.kts b/maven-plugin/build.gradle.kts index daab864..58f6279 100644 --- a/maven-plugin/build.gradle.kts +++ b/maven-plugin/build.gradle.kts @@ -1,14 +1,23 @@ -val pluginDependency: Configuration by configurations.creating { - configurations.compileClasspath.get().extendsFrom(this) -} +val compilerPlugin: Configuration by configurations.creating dependencies { - implementation(kotlin("maven-plugin")) - pluginDependency(project(":compiler-plugin")) + implementation(libs.kotlin.maven) + implementation("com.dyescape", "kotlin-maven-symbol-processing", "1.3") + compilerPlugin(project(":compiler-plugin")) } tasks { jar { - fromConfiguration(pluginDependency) + fromConfiguration(compilerPlugin) { + this.duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } + + from( + configurations.compileClasspath.get().filter { dependency -> + dependency.absolutePath.contains("kotlin-maven-symbol-processing") + }.map(::zipTree) + ) { + this.duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } } } diff --git a/maven-plugin/src/main/kotlin/com/intuit/hooks/plugin/maven/HooksMavenPlugin.kt b/maven-plugin/src/main/kotlin/com/intuit/hooks/plugin/maven/HooksMavenPlugin.kt index c3a07f3..93a4e55 100644 --- a/maven-plugin/src/main/kotlin/com/intuit/hooks/plugin/maven/HooksMavenPlugin.kt +++ b/maven-plugin/src/main/kotlin/com/intuit/hooks/plugin/maven/HooksMavenPlugin.kt @@ -1,42 +1,31 @@ package com.intuit.hooks.plugin.maven +import com.dyescape.ksp.maven.KotlinSymbolProcessingMavenPluginExtension import org.apache.maven.plugin.MojoExecution import org.apache.maven.project.MavenProject +import org.apache.maven.repository.RepositorySystem import org.codehaus.plexus.component.annotations.Component import org.codehaus.plexus.component.annotations.Requirement import org.codehaus.plexus.logging.Logger import org.jetbrains.kotlin.maven.KotlinMavenPluginExtension import org.jetbrains.kotlin.maven.PluginOption -/** Bridge between hooks-plugin and Kotlin maven plugin */ +/** Slim wrapper of [KotlinSymbolProcessingMavenPluginExtension] to apply additional plugin params */ @Component(role = KotlinMavenPluginExtension::class, hint = "hooks") -public class HooksMavenPlugin : KotlinMavenPluginExtension { +public class HooksMavenPlugin( + private val delegate: KotlinSymbolProcessingMavenPluginExtension = KotlinSymbolProcessingMavenPluginExtension() +) : KotlinMavenPluginExtension by delegate { @Requirement - public lateinit var logger: Logger - - override fun isApplicable(project: MavenProject, mojo: MojoExecution): Boolean = true - - override fun getCompilerPluginId(): String = "arrow.meta.plugin.compiler" - - override fun getPluginOptions(project: MavenProject, mojo: MojoExecution): List { - logger.debug("Loaded Maven plugin ${javaClass.name}") + public lateinit var system: RepositorySystem -// TODO: Add CLI processor for hook specific plugin option - val generatedSrcOutputDir = null // extension.generatedSrcOutputDir - ?: project.build.directory - -// TODO: This doesn't work, but it'd be real nice if it did -// project.addCompileSourceRoot("$generatedSrcOutputDir/generated/source/kapt/main") -// project.dependencies.add(Dependency().apply { -// groupId = "io.arrow-kt" -// artifactId = "arrow-annotations" -// version = "0.11.0" -// scope = "provided" -// }) + @Requirement + public lateinit var logger: Logger - return listOf( - PluginOption("plugin", "arrow.meta.plugin.compiler", "generatedSrcOutputDir", generatedSrcOutputDir) - ) + override fun getPluginOptions(project: MavenProject, execution: MojoExecution): List { + delegate.system = system + val options = delegate.getPluginOptions(project, execution) + // TODO: call into delegate to get gen params to combine with hooks specific params + return options } } diff --git a/maven-plugin/src/main/resources/META-INF/plexus/components.xml b/maven-plugin/src/main/resources/META-INF/plexus/components.xml index df81c9e..62cb111 100644 --- a/maven-plugin/src/main/resources/META-INF/plexus/components.xml +++ b/maven-plugin/src/main/resources/META-INF/plexus/components.xml @@ -1,3 +1,4 @@ + @@ -12,6 +13,10 @@ org.codehaus.plexus.logging.Logger logger + + org.apache.maven.repository.RepositorySystem + system + diff --git a/settings.gradle.kts b/settings.gradle.kts index 95adf2e..ab01c49 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,7 +3,7 @@ include( ":hooks", ":compiler-plugin", ":gradle-plugin", -// ":maven-plugin", + ":maven-plugin", ":docs", ":example-library", ":example-application", @@ -37,6 +37,7 @@ dependencyResolutionManagement { // Kotlin library("kotlin.stdlib", "org.jetbrains.kotlin", "kotlin-stdlib").withoutVersion() + library("kotlin.maven", "org.jetbrains.kotlin", "kotlin-maven-plugin").withoutVersion() library("kotlin.coroutines.core", "org.jetbrains.kotlinx", "kotlinx-coroutines-core").version("1.6.1") // KSP From 0d8a02f78513503bd4bc235ff53c59db9decc3ab Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Tue, 14 Jun 2022 11:26:50 -0700 Subject: [PATCH 10/23] fix some links --- README.md | 12 ++++++------ buildSrc/build.gradle.kts | 2 +- .../src/orchid/resources/wiki/plugin-architecture.md | 2 +- hooks/README.md | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d20ee70..8b7e156 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,12 @@ library for the JVM plus an [Arrow Meta](https://meta.arrow-kt.io/) Compiler Plu ## Structure -- [hooks](https://github.com/intuit/hooks/tree/master/hooks) - The actual implementation of the hooks -- [compiler-plugin](https://github.com/intuit/hooks/tree/master/compiler-plugin) - An Arrow Meta compiler plugin that generates hook subclasses for you -- [gradle-plugin](https://github.com/intuit/hooks/tree/master/gradle-plugin) - A gradle plugin to make using the compiler plugin easier -- [maven-plugin](https://github.com/intuit/hooks/tree/master/maven-plugin) - A maven Kotlin plugin extension to make using the compiler plugin easier -- [example-library](https://github.com/intuit/hooks/tree/master/example-library) - A library that exposes extension points for consumers using the hooks' `call` function -- [example-application](https://github.com/intuit/hooks/tree/master/example-application) - The Application that demonstrates extending a library by calling the hooks' `tap` function +- [hooks](https://github.com/intuit/hooks/tree/main/hooks) - The actual implementation of the hooks +- [compiler-plugin](https://github.com/intuit/hooks/tree/main/compiler-plugin) - An Arrow Meta compiler plugin that generates hook subclasses for you +- [gradle-plugin](https://github.com/intuit/hooks/tree/main/gradle-plugin) - A gradle plugin to make using the compiler plugin easier +- [maven-plugin](https://github.com/intuit/hooks/tree/main/maven-plugin) - A maven Kotlin plugin extension to make using the compiler plugin easier +- [example-library](https://github.com/intuit/hooks/tree/main/example-library) - A library that exposes extension points for consumers using the hooks' `call` function +- [example-application](https://github.com/intuit/hooks/tree/main/example-application) - The Application that demonstrates extending a library by calling the hooks' `tap` function ## :beers: Contributing :beers: diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index cc48205..aebe261 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -2,5 +2,5 @@ plugins { `kotlin-dsl` } repositories { - jcenter() + mavenCentral() } diff --git a/docs/src/orchid/resources/wiki/plugin-architecture.md b/docs/src/orchid/resources/wiki/plugin-architecture.md index 05fc55f..b31e561 100644 --- a/docs/src/orchid/resources/wiki/plugin-architecture.md +++ b/docs/src/orchid/resources/wiki/plugin-architecture.md @@ -130,4 +130,4 @@ fun main() { -> You can get the full code [here](https://github.com/intuit/hooks/tree/master/docs/src/test/kotlin/example/example-car-02.kt). +> You can get the full code [here](https://github.com/intuit/hooks/tree/main/docs/src/test/kotlin/example/example-car-02.kt). diff --git a/hooks/README.md b/hooks/README.md index dba6503..f6847af 100644 --- a/hooks/README.md +++ b/hooks/README.md @@ -53,7 +53,7 @@ fun main() { -> You can get the full code [here](https://github.com/intuit/hooks/tree/master/docs/src/test/kotlin/example/example-synchook-01.kt). +> You can get the full code [here](https://github.com/intuit/hooks/tree/main/docs/src/test/kotlin/example/example-synchook-01.kt). We should expect the `tapped` function to be executed once the hook is `called`, which would print the following: From 06ef099d7a83ac3bd7a3ef6885d8ce66bb70b6b2 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Tue, 14 Jun 2022 15:50:11 -0700 Subject: [PATCH 11/23] fix gradle and maven readmes and lint --- compiler-plugin/build.gradle.kts | 2 -- .../intuit/hooks/plugin/ksp/HooksProcessor.kt | 35 ++++--------------- .../hooks/plugin/HookValidationErrors.kt | 2 +- gradle-plugin/README.md | 6 +++- .../hooks/plugin/gradle/HooksGradlePlugin.kt | 2 +- .../com/intuit/hooks/utils/Parallelism.kt | 1 - maven-plugin/README.md | 16 ++++----- 7 files changed, 19 insertions(+), 45 deletions(-) diff --git a/compiler-plugin/build.gradle.kts b/compiler-plugin/build.gradle.kts index 31157ca..524a33a 100644 --- a/compiler-plugin/build.gradle.kts +++ b/compiler-plugin/build.gradle.kts @@ -1,8 +1,6 @@ dependencies { implementation(libs.ksp.spa) // implementation(libs.ksp.poet) -// implementation(libs.ktlint.core) -// implementation(libs.ktlint.ruleset.standard) implementation(libs.arrow.core) testImplementation(project(":hooks")) diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt index b526024..3b3caf9 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt @@ -77,38 +77,15 @@ public class HooksProcessor( | ${classes.joinToString("\n", "\n", "\n") { it }} |}""".trimMargin() - val file = codeGenerator.createNewFile( + codeGenerator.createNewFile( Dependencies(true, classDeclaration.containingFile!!), packageName.asString(), name, - ) - - logger.logging( - """raw generated source: - |$newSource - """.trimMargin(), - classDeclaration - ) - -// KtLint.ExperimentalParams( -// text = newSource, -// ruleSets = listOf(StandardRuleSetProvider().get()), -// cb = { _, _ -> } -// ) -// .let(::format) -// .also { -// logger.logging( -// """formatted generated source: -// |$it -// """.trimMargin(), -// classDeclaration -// ) -// } - newSource - .let(String::toByteArray) - .let(file::write) - - file.close() + ).use { + newSource + .let(String::toByteArray) + .let(it::write) + } }.valueOr { errors -> errors.forEach { logger.error(it.message, it.symbol) } } diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt index b41c5be..45e56d6 100644 --- a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt +++ b/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt @@ -1,4 +1,4 @@ - + package com.intuit.hooks.plugin import com.tschuchort.compiletesting.SourceFile diff --git a/gradle-plugin/README.md b/gradle-plugin/README.md index fb59873..5043778 100644 --- a/gradle-plugin/README.md +++ b/gradle-plugin/README.md @@ -1,5 +1,9 @@ # Gradle Plugin +> **Warning** +> +> The Gradle plugin automatically bundles a specific version of the KSP plugin, which is tied to a specific version of Kotlin (can be found [here](./settings.gradle.kts#19)). This means the Gradle plugin is only compatible with projects that use that specific Kotlin version. At some point, this module will be upgraded to publish in accordance to the KSP/Kotlin version it bundles. + Applying the hooks Gradle plugin automatically adds the appropriate dependencies to your project, configures the generated source directory, and registers the Kotlin compiler plugin. ### Installation @@ -18,7 +22,7 @@ plugins { // Optional - Hooks extension configuration block hooks { // Optional - configure output directory for generated code - // Default - "${buildDir.absolutePath}/generated/source/kapt/main" + // Default - "${buildDir.absolutePath}/generated/ksp/main/kotlin" generatedSrcOutputDir = "$buildDir/custom/generated/code" } ``` diff --git a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt index d1ebdef..177a925 100644 --- a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt +++ b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt @@ -44,7 +44,7 @@ public class HooksGradlePlugin : Plugin { target.addDependency("ksp", "com.intuit.hooks:compiler-plugin:$version") // TODO: Maybe apply to Kotlin plugin to be compatible with MPP - target.plugins.withType(JavaPlugin::class.java) { javaPlugin -> + target.plugins.withType(JavaPlugin::class.java) { _ -> val sourceSets = target.extensions.getByType(SourceSetContainer::class.java) sourceSets.forEach { it.java.srcDir(target.buildDir.resolve(hooksExtension.generatedSrcOutputDir ?: "generated/ksp/${it.name}/kotlin")) diff --git a/hooks/src/main/kotlin/com/intuit/hooks/utils/Parallelism.kt b/hooks/src/main/kotlin/com/intuit/hooks/utils/Parallelism.kt index 6cb78a5..4fd7892 100644 --- a/hooks/src/main/kotlin/com/intuit/hooks/utils/Parallelism.kt +++ b/hooks/src/main/kotlin/com/intuit/hooks/utils/Parallelism.kt @@ -6,7 +6,6 @@ import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.channels.produce import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch diff --git a/maven-plugin/README.md b/maven-plugin/README.md index d51646d..c4eaca7 100644 --- a/maven-plugin/README.md +++ b/maven-plugin/README.md @@ -1,6 +1,10 @@ # Maven Kotlin Plugin Extension -At the moment, the Maven extension is not complete and only helps to register the Kotlin compiler plugin and partially configure the generated source directory. You will still be required to add the appropriate dependencies and add the generated source directory to your source sets. +> **Warning** +> +> The Maven Kotlin plugin automatically bundles a specific version of the KSP plugin, which is tied to a specific version of Kotlin (can be found [here](./settings.gradle.kts#19)). This means the Gradle plugin is only compatible with projects that use that specific Kotlin version. At some point, this module will be upgraded to publish in accordance to the KSP/Kotlin version it bundles. + +At the moment, the Maven extension is not complete and only helps to register the KSP plugin and partially configure the generated source directory. You will still be required to add the appropriate dependencies and add the generated source directory to your source sets. ### Installation @@ -17,13 +21,6 @@ At the moment, the Maven extension is not complete and only helps to register th hooks ${hooks.version} - - - io.arrow-kt - arrow-annotations - 0.11.0 - provided - @@ -48,10 +45,9 @@ At the moment, the Maven extension is not complete and only helps to register th hooks - + ${project.basedir}/src/main/kotlin - ${build.directory}/generated/source/kapt/main From dd055b117e77bbb2c74f284239571d8cdd2d59ce Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Tue, 14 Jun 2022 16:29:19 -0700 Subject: [PATCH 12/23] try to fix signing --- build.gradle.kts | 28 ++++++++++++++-------------- gradle-plugin/build.gradle.kts | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 66384bd..9b84b43 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -87,7 +87,7 @@ subprojects { toolVersion = "0.8.7" } - if (publishModules.contains(name)) { + if (publishModules.contains(name) && name != "gradle-plugin") { apply { plugin("maven-publish") plugin("signing") @@ -133,19 +133,6 @@ subprojects { } } - configure { - val signingKey by auth { - it?.replace("\\n", "\n") - } - signingKey?.let { - val signingPassword by auth - useInMemoryPgpKeys(signingKey, signingPassword) - sign(extensions.findByType(PublishingExtension::class.java)!!.publications) - } ?: run { - isRequired = false - } - } - tasks { register("javadocJar") { dependsOn("dokkaJavadoc") @@ -161,6 +148,19 @@ subprojects { } } + extensions.findByType()?.apply { + val signingKey by auth { + it?.replace("\\n", "\n") + } + signingKey?.let { + val signingPassword by auth + useInMemoryPgpKeys(signingKey, signingPassword) + sign(extensions.findByType(PublishingExtension::class.java)!!.publications) + } ?: run { + isRequired = false + } + } + ktlint { filter { exclude("**/example/**/*.kt") diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index 60aabe4..3d06ba0 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -3,7 +3,7 @@ import org.jetbrains.kotlin.konan.properties.Properties import org.jetbrains.kotlin.konan.properties.saveToFile plugins { - id("com.gradle.plugin-publish") version "1.0.0-rc-2" + id("com.gradle.plugin-publish") version "1.0.0-rc-3" } gradlePlugin { From e84009685c66c198f0ed6bafe128625a39eb9c80 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Tue, 14 Jun 2022 16:54:07 -0700 Subject: [PATCH 13/23] fix dependency --- maven-plugin/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/maven-plugin/build.gradle.kts b/maven-plugin/build.gradle.kts index 58f6279..b44fd5b 100644 --- a/maven-plugin/build.gradle.kts +++ b/maven-plugin/build.gradle.kts @@ -8,6 +8,7 @@ dependencies { tasks { jar { + dependsOn(project(":compiler-plugin").getTasksByName("jar", false)) fromConfiguration(compilerPlugin) { this.duplicatesStrategy = DuplicatesStrategy.EXCLUDE } From a093a2a3c50809fd3428a6bce1e2e77579226dae Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Tue, 14 Jun 2022 17:07:17 -0700 Subject: [PATCH 14/23] hacky way to fix test dependency --- gradle-plugin/build.gradle.kts | 4 ++++ maven-plugin/build.gradle.kts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index 3d06ba0..a4f55aa 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -47,4 +47,8 @@ tasks { classes { dependsOn(createProperties) } + + test { + dependsOn(":hooks:publishToMavenLocal", ":compiler-plugin:publishToMavenLocal") + } } diff --git a/maven-plugin/build.gradle.kts b/maven-plugin/build.gradle.kts index b44fd5b..83bebc4 100644 --- a/maven-plugin/build.gradle.kts +++ b/maven-plugin/build.gradle.kts @@ -8,7 +8,7 @@ dependencies { tasks { jar { - dependsOn(project(":compiler-plugin").getTasksByName("jar", false)) + dependsOn(":compiler-plugin:jar") fromConfiguration(compilerPlugin) { this.duplicatesStrategy = DuplicatesStrategy.EXCLUDE } From c90927135f01e95fb06fe578fa1e0f1f4f146798 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Tue, 14 Jun 2022 17:29:59 -0700 Subject: [PATCH 15/23] api dump --- maven-plugin/api/maven-plugin.api | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/maven-plugin/api/maven-plugin.api b/maven-plugin/api/maven-plugin.api index 0e38951..20e0d31 100644 --- a/maven-plugin/api/maven-plugin.api +++ b/maven-plugin/api/maven-plugin.api @@ -1,10 +1,15 @@ public final class com/intuit/hooks/plugin/maven/HooksMavenPlugin : org/jetbrains/kotlin/maven/KotlinMavenPluginExtension { public field logger Lorg/codehaus/plexus/logging/Logger; + public field system Lorg/apache/maven/repository/RepositorySystem; public fun ()V + public fun (Lcom/dyescape/ksp/maven/KotlinSymbolProcessingMavenPluginExtension;)V + public synthetic fun (Lcom/dyescape/ksp/maven/KotlinSymbolProcessingMavenPluginExtension;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCompilerPluginId ()Ljava/lang/String; public final fun getLogger ()Lorg/codehaus/plexus/logging/Logger; public fun getPluginOptions (Lorg/apache/maven/project/MavenProject;Lorg/apache/maven/plugin/MojoExecution;)Ljava/util/List; + public final fun getSystem ()Lorg/apache/maven/repository/RepositorySystem; public fun isApplicable (Lorg/apache/maven/project/MavenProject;Lorg/apache/maven/plugin/MojoExecution;)Z public final fun setLogger (Lorg/codehaus/plexus/logging/Logger;)V + public final fun setSystem (Lorg/apache/maven/repository/RepositorySystem;)V } From e22a41b06e8626afc89234a95af024527a44d2b1 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Wed, 15 Jun 2022 17:23:58 -0700 Subject: [PATCH 16/23] remove unnecessary option --- compiler-plugin/build.gradle.kts | 1 + .../intuit/hooks/plugin/ksp/HooksProcessor.kt | 1 - gradle-plugin/api/gradle-plugin.api | 4 +-- gradle-plugin/build.gradle.kts | 4 +++ .../plugin/gradle/HooksGradleExtension.kt | 4 +-- .../hooks/plugin/gradle/HooksGradlePlugin.kt | 35 ++++++++----------- 6 files changed, 21 insertions(+), 28 deletions(-) diff --git a/compiler-plugin/build.gradle.kts b/compiler-plugin/build.gradle.kts index 524a33a..2969218 100644 --- a/compiler-plugin/build.gradle.kts +++ b/compiler-plugin/build.gradle.kts @@ -1,4 +1,5 @@ dependencies { + implementation(libs.kotlin.stdlib) implementation(libs.ksp.spa) // implementation(libs.ksp.poet) implementation(libs.arrow.core) diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt index 3b3caf9..b23fc14 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt +++ b/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt @@ -36,7 +36,6 @@ public class HooksProcessor( } } -// @OptIn(FeatureInAlphaState::class) override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { // TODO: This should really be restructured to follow KSP visitor pattern for members val superTypeNames = classDeclaration.superTypes diff --git a/gradle-plugin/api/gradle-plugin.api b/gradle-plugin/api/gradle-plugin.api index 579016c..0df8ed3 100644 --- a/gradle-plugin/api/gradle-plugin.api +++ b/gradle-plugin/api/gradle-plugin.api @@ -1,7 +1,5 @@ -public class com/intuit/hooks/plugin/gradle/HooksGradleExtension { +public abstract class com/intuit/hooks/plugin/gradle/HooksGradleExtension { public fun ()V - public final fun getGeneratedSrcOutputDir ()Ljava/lang/String; - public final fun setGeneratedSrcOutputDir (Ljava/lang/String;)V } public final class com/intuit/hooks/plugin/gradle/HooksGradlePlugin : org/gradle/api/Plugin { diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index a4f55aa..d484e37 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -33,6 +33,10 @@ dependencies { testImplementation(libs.bundles.testing) } +kotlin { + explicitApi() +} + tasks { val createProperties by creating { dependsOn(processResources) diff --git a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradleExtension.kt b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradleExtension.kt index 27e55f7..3710aa7 100644 --- a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradleExtension.kt +++ b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradleExtension.kt @@ -1,6 +1,4 @@ package com.intuit.hooks.plugin.gradle /** Any options to be used to configure the hooks-plugin */ -public open class HooksGradleExtension { - public var generatedSrcOutputDir: String? = null -} +public abstract class HooksGradleExtension diff --git a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt index 177a925..49d35c8 100644 --- a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt +++ b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt @@ -1,22 +1,21 @@ package com.intuit.hooks.plugin.gradle -import com.google.devtools.ksp.gradle.KspExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPlugin import org.gradle.api.tasks.SourceSetContainer -import java.util.* +import java.util.Properties /** Wrap KSP plugin and provide Gradle extension for Hooks processor options */ public class HooksGradlePlugin : Plugin { private val properties by lazy { - val properties = Properties() - HooksGradlePlugin::class.java.classLoader.getResourceAsStream("version.properties").let(properties::load) - properties + Properties().apply { + HooksGradlePlugin::class.java.classLoader.getResourceAsStream("version.properties").let(::load) + } } - private val version by lazy { + private val hooksVersion by lazy { properties["version"] as String } @@ -25,29 +24,23 @@ public class HooksGradlePlugin : Plugin { dependencies.create(dependencyNotation) ) - override fun apply(target: Project) { - val hooksExtension = target.extensions.create( + override fun apply(project: Project): Unit = with(project) { + extensions.create( "hooks", HooksGradleExtension::class.java ) - if (!target.pluginManager.hasPlugin("com.google.devtools.ksp")) - target.pluginManager.apply("com.google.devtools.ksp") - - target.extensions.configure("ksp") { ksp -> - hooksExtension.generatedSrcOutputDir?.let { generatedSrcOutputDir -> - ksp.arg("generatedSrcOutputDir", generatedSrcOutputDir) - } - } + if (!pluginManager.hasPlugin("com.google.devtools.ksp")) + pluginManager.apply("com.google.devtools.ksp") - target.addDependency("api", "com.intuit.hooks:hooks:$version") - target.addDependency("ksp", "com.intuit.hooks:compiler-plugin:$version") + addDependency("api", "com.intuit.hooks:hooks:$hooksVersion") + addDependency("ksp", "com.intuit.hooks:compiler-plugin:$hooksVersion") // TODO: Maybe apply to Kotlin plugin to be compatible with MPP - target.plugins.withType(JavaPlugin::class.java) { _ -> - val sourceSets = target.extensions.getByType(SourceSetContainer::class.java) + plugins.withType(JavaPlugin::class.java) { _ -> + val sourceSets = extensions.getByType(SourceSetContainer::class.java) sourceSets.forEach { - it.java.srcDir(target.buildDir.resolve(hooksExtension.generatedSrcOutputDir ?: "generated/ksp/${it.name}/kotlin")) + it.java.srcDir(buildDir.resolve("generated/ksp/${it.name}/kotlin")) } } } From 51fa2eb004585a6098c9a81c567353385a24f175 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Wed, 15 Jun 2022 17:32:17 -0700 Subject: [PATCH 17/23] add missing deps to version catalog --- gradle-plugin/build.gradle.kts | 2 +- maven-plugin/build.gradle.kts | 2 +- settings.gradle.kts | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index d484e37..e573bb6 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -3,7 +3,7 @@ import org.jetbrains.kotlin.konan.properties.Properties import org.jetbrains.kotlin.konan.properties.saveToFile plugins { - id("com.gradle.plugin-publish") version "1.0.0-rc-3" + alias(libs.plugins.gradle.publish) } gradlePlugin { diff --git a/maven-plugin/build.gradle.kts b/maven-plugin/build.gradle.kts index 83bebc4..097e25d 100644 --- a/maven-plugin/build.gradle.kts +++ b/maven-plugin/build.gradle.kts @@ -2,7 +2,7 @@ val compilerPlugin: Configuration by configurations.creating dependencies { implementation(libs.kotlin.maven) - implementation("com.dyescape", "kotlin-maven-symbol-processing", "1.3") + implementation(libs.ksp.maven) compilerPlugin(project(":compiler-plugin")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index ab01c49..ae08aad 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,6 +27,7 @@ dependencyResolutionManagement { plugin("release", "net.researchgate.release").version("2.6.0") plugin("nexus", "io.github.gradle-nexus.publish-plugin").version("1.0.0") + plugin("gradle.publish", "com.gradle.plugin-publish").version("1.0.0-rc-3") plugin("ktlint", "org.jlleitschuh.gradle.ktlint").version("10.3.0") plugin("api", "org.jetbrains.kotlinx.binary-compatibility-validator").version("0.9.0") @@ -44,6 +45,7 @@ dependencyResolutionManagement { library("ksp.spa", "com.google.devtools.ksp", "symbol-processing-api").versionRef("ksp") library("ksp.poet", "com.squareup", "kotlinpoet-ksp").versionRef("poet") library("ksp.gradle", "com.google.devtools.ksp", "com.google.devtools.ksp.gradle.plugin").versionRef("ksp") + library("ksp.maven", "com.dyescape", "kotlin-maven-symbol-processing").version("1.3") library("ktlint.core", "com.pinterest.ktlint", "ktlint-core").versionRef("ktlint") library("ktlint.ruleset.standard", "com.pinterest.ktlint", "ktlint-ruleset-standard").versionRef("ktlint") From 826dc8d14a83e5538a5f31e684f7e11d8a7c7e3e Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Wed, 22 Jun 2022 11:27:35 -0700 Subject: [PATCH 18/23] compiler-plugin -> processor --- .fossa.yml | 8 +-- README.md | 8 +-- compiler-plugin/README.md | 58 --------------- docs/build.gradle.kts | 4 +- docs/src/orchid/resources/config.yml | 9 +-- docs/src/orchid/resources/data.yml | 4 +- docs/src/orchid/resources/wiki/using-hooks.md | 4 +- docs/src/test/kotlin/example/snippets.kt | 8 +-- example-library/build.gradle.kts | 2 +- gradle-plugin/README.md | 9 +-- gradle-plugin/build.gradle.kts | 6 +- .../hooks/plugin/gradle/HooksGradlePlugin.kt | 2 +- .../src/test/kotlin/HooksGradlePluginTest.kt | 4 +- hooks/README.md | 4 +- maven-plugin/build.gradle.kts | 8 +-- processor/README.md | 72 +++++++++++++++++++ .../api/compiler-plugin.api | 0 .../build.gradle.kts | 0 .../intuit/hooks/plugin/codegen/HookInfo.kt | 0 .../intuit/hooks/plugin/codegen/HookType.kt | 0 .../intuit/hooks/plugin/ksp/HooksProcessor.kt | 0 .../com/intuit/hooks/plugin/ksp/Text.kt | 0 .../ksp/validation/AnnotationValidations.kt | 0 .../ksp/validation/HookPropertyValidations.kt | 0 .../plugin/ksp/validation/HookValidations.kt | 2 +- ...ols.ksp.processing.SymbolProcessorProvider | 0 .../hooks/plugin/HookValidationErrors.kt | 0 .../intuit/hooks/plugin/HooksProcessorTest.kt | 0 .../intuit/hooks/plugin/KotlinCompilation.kt | 0 settings.gradle.kts | 2 +- 30 files changed, 112 insertions(+), 102 deletions(-) delete mode 100644 compiler-plugin/README.md create mode 100644 processor/README.md rename {compiler-plugin => processor}/api/compiler-plugin.api (100%) rename {compiler-plugin => processor}/build.gradle.kts (100%) rename {compiler-plugin => processor}/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookInfo.kt (100%) rename {compiler-plugin => processor}/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookType.kt (100%) rename {compiler-plugin => processor}/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt (100%) rename {compiler-plugin => processor}/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt (100%) rename {compiler-plugin => processor}/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/AnnotationValidations.kt (100%) rename {compiler-plugin => processor}/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookPropertyValidations.kt (100%) rename {compiler-plugin => processor}/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookValidations.kt (97%) rename {compiler-plugin => processor}/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider (100%) rename {compiler-plugin => processor}/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt (100%) rename {compiler-plugin => processor}/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt (100%) rename {compiler-plugin => processor}/src/test/kotlin/com/intuit/hooks/plugin/KotlinCompilation.kt (100%) diff --git a/.fossa.yml b/.fossa.yml index 4abe775..cb85e73 100755 --- a/.fossa.yml +++ b/.fossa.yml @@ -8,10 +8,6 @@ cli: project: git@github.com:intuit/hooks analyze: modules: - - name: compiler-plugin - type: gradle - target: 'compiler-plugin:' - path: . - name: docs type: gradle target: 'docs:' @@ -36,3 +32,7 @@ analyze: type: gradle target: 'maven-plugin:' path: . + - name: processor + type: gradle + target: 'processor:' + path: . diff --git a/README.md b/README.md index 8b7e156..5e19a24 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Hooks represent "pluggable" points in a software model. They provide a mechanism - Asynchronous support built on Kotlin [coroutines](https://kotlinlang.org/docs/coroutines-guide.html) - Support for additional [hook context](https://intuit.github.io/hooks/wiki/key-concepts/#hook-context) and [interceptors](https://intuit.github.io/hooks/wiki/key-concepts/#interceptors) -Along with the base library, we created a Kotlin compiler plugin to enable hooks to be created with a simple typed-based DSL, limiting the redundancy and overhead required to subclass a hook. +Along with the base library, we created a Kotlin symbol processor to enable hooks to be created with a simple typed-based DSL, limiting the redundancy and overhead required to subclass a hook. Visit our [site](https://intuit.github.io/hooks/) for information about how to use hooks. @@ -45,9 +45,9 @@ library for the JVM plus an [Arrow Meta](https://meta.arrow-kt.io/) Compiler Plu ## Structure - [hooks](https://github.com/intuit/hooks/tree/main/hooks) - The actual implementation of the hooks -- [compiler-plugin](https://github.com/intuit/hooks/tree/main/compiler-plugin) - An Arrow Meta compiler plugin that generates hook subclasses for you -- [gradle-plugin](https://github.com/intuit/hooks/tree/main/gradle-plugin) - A gradle plugin to make using the compiler plugin easier -- [maven-plugin](https://github.com/intuit/hooks/tree/main/maven-plugin) - A maven Kotlin plugin extension to make using the compiler plugin easier +- [processor](https://github.com/intuit/hooks/tree/main/processor) - A Kotlin Symbol Processor that generates hook subclasses for you +- [gradle-plugin](https://github.com/intuit/hooks/tree/main/gradle-plugin) - A Gradle plugin to make using the processor easier +- [maven-plugin](https://github.com/intuit/hooks/tree/main/maven-plugin) - A Maven Kotlin plugin extension to make using the processor easier - [example-library](https://github.com/intuit/hooks/tree/main/example-library) - A library that exposes extension points for consumers using the hooks' `call` function - [example-application](https://github.com/intuit/hooks/tree/main/example-application) - The Application that demonstrates extending a library by calling the hooks' `tap` function diff --git a/compiler-plugin/README.md b/compiler-plugin/README.md deleted file mode 100644 index b8479af..0000000 --- a/compiler-plugin/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Kotlin Compiler Plugin - -Built on [Arrow Meta](https://meta.arrow-kt.io/), the compiler plugin enables consumers to create hooks using the type-driven DSL provided in the hooks library. For the most part, Kotlin compiler plugins are easier to use when wrapped with a build system plugin. We currently have Gradle and Maven wrappers, but if you'd like to use outside those build systems, the compiler plugin is published as `com.intuit.hooks:compiler-plugin:$version`. If you do find yourself using the compiler plugin directly, we would love to hear about your use case and would appreciate any contributions or to support other build systems. - -### Compiler Plugin DSL - -With the compiler plugin registered to your project, you can now create hooks by defining a `Hooks` subclass. This gives you access to a collection of methods to create hook implementations based on the type signature passed into the method. - - - - - -```kotlin -internal abstract class GenericHooks : Hooks() { - @Sync<(newSpeed: Int) -> Unit> abstract val sync: SyncHook<*> - @SyncBail<(Boolean) -> BailResult> abstract val syncBail: SyncBailHook<*, *> - @SyncLoop<(foo: Boolean) -> LoopResult> abstract val syncLoop: SyncLoopHook<*, *> - @SyncWaterfall<(name: String) -> String> abstract val syncWaterfall: SyncWaterfallHook<*, *> - @AsyncParallelBail BailResult> abstract val asyncParallelBail: AsyncParallelBailHook<*, *> - @AsyncParallel Int> abstract val asyncParallel: AsyncParallelHook<*> - @AsyncSeries Int> abstract val asyncSeries: AsyncSeriesHook<*> - @AsyncSeriesBail BailResult> abstract val asyncSeriesBail: AsyncSeriesBailHook<*, *> - @AsyncSeriesLoop LoopResult> abstract val asyncSeriesLoop: AsyncSeriesLoopHook<*, *> - @AsyncSeriesWaterfall String> abstract val asyncSeriesWaterfall: AsyncSeriesWaterfallHook<*, *> -} -``` - -The compiler plugin uses this class to create new hook implementations and instances. Currently, the compiler plugin generates a new class that subclasses `GenericHooks` and overrides the member properties, which is why they are `open`. This is important to note, as otherwise, the code will not compile because the member property is final. When using the hooks DSL, you must follow these constraints: - -1. `Hooks` subclass _must_ be abstract -2. All member properties that use the hooks DSL methods _must_ be open -3. Any `async` hook must take a `suspend` typed method -4. Bail hooks must return a `BailResult` -5. Loop hooks must return a `LoopResult` - -Some of these constraints will give you an error when the compiler plugin runs and others will result in a generic compiler error, like stated above. - -The generated class name will be `${name}Impl`, thus the snippet above could be used in the following manner: - -```kotlin -fun main() { - val hooks = GenericHooksImpl() - hooks.sync.tap("LoggerPlugin") { newSpeed: Int -> - println("newSpeed: $newSpeed") - } - hooks.sync.call(30) - // newSpeed: 30 -} -``` - - - - diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts index 7372388..8c31883 100644 --- a/docs/build.gradle.kts +++ b/docs/build.gradle.kts @@ -19,8 +19,8 @@ dependencies { testImplementation(libs.kotlin.coroutines.core) testImplementation(libs.knit.testing) testImplementation(project(":hooks")) - testImplementation(project(":compiler-plugin")) - ksp(project(":compiler-plugin")) + testImplementation(project(":processor")) + ksp(project(":processor")) } // 4. Use the 'Editorial' theme, and set the URL it will have on Github Pages diff --git a/docs/src/orchid/resources/config.yml b/docs/src/orchid/resources/config.yml index 94e541c..fcff75c 100644 --- a/docs/src/orchid/resources/config.yml +++ b/docs/src/orchid/resources/config.yml @@ -69,12 +69,13 @@ kotlindoc: sourceDirs: - './../../../../hooks/src/main/kotlin' showRunnerLogs: true - relatedModules: [ 'compiler-plugin' ] + relatedModules: [ 'processor' ] - - name: 'Kotlin Compiler Plugin' + - name: 'Hooks Processor' + slug: 'processor' moduleGroup: 'plugins' sourceDirs: - - './../../../../compiler-plugin/src/main/kotlin' + - './../../../../processor/src/main/kotlin' homePageOnly: true - name: 'Gradle Plugin' @@ -117,7 +118,7 @@ snippets: type: 'embedded' baseDirs: - './../../../../hooks/src/' - - './../../../../compiler-plugin/src' + - './../../../../processor/src' - './../../../../gradle-plugin/src' - './../../../../maven-plugin/src' - './../../../../example-library/src' diff --git a/docs/src/orchid/resources/data.yml b/docs/src/orchid/resources/data.yml index 426c1e3..f579a8d 100644 --- a/docs/src/orchid/resources/data.yml +++ b/docs/src/orchid/resources/data.yml @@ -13,8 +13,8 @@ homepageSections: snippets: ['asynchronous'] lang: 'kotlin' - - title: 'Kotlin compiler plugin' - snippets: ['compiler_plugin'] + - title: 'Kotlin symbol processor' + snippets: ['processor'] lang: 'text' tabs: - title: 'Paired with a concise DSL that generates type-safe APIs' diff --git a/docs/src/orchid/resources/wiki/using-hooks.md b/docs/src/orchid/resources/wiki/using-hooks.md index 3ba18de..f1807ff 100644 --- a/docs/src/orchid/resources/wiki/using-hooks.md +++ b/docs/src/orchid/resources/wiki/using-hooks.md @@ -1,10 +1,10 @@ # Usage -At its core, this project exposes a base hooks library, which can be used by itself, but requires a somewhat verbose, redundant API to use. To limit the overhead of using hooks, we also expose a Kotlin compiler plugin built with [Arrow Meta](https://meta.arrow-kt.io/), which provides a simple, type-driven DSL to enable consumers to create hooks. Since Kotlin compiler plugins aren't necessarily easy to configure, we've built a Gradle plugin and a Maven Kotlin plugin extension to configure a project to use hooks. See the module documentation for more information on how to use hooks in your project: +At its core, this project exposes a base hooks library, which can be used by itself, but requires a somewhat verbose, redundant API to use. To limit the overhead of using hooks, we also expose a Kotlin symbol processor built with the [KSP API](https://kotlinlang.org/docs/ksp-overview.html), which provides a simple, type-driven DSL to enable consumers to create hooks. Kotlin symbol processors are relatively easy to integrate into Gradle projects, but to limit the configuration burden, we've built a Gradle plugin and a Maven Kotlin plugin extension to configure a project to use hooks. See the module documentation for more information on how to use hooks in your project: ##### Modules * [Hooks](/hooks/modules/hooks) -* [Kotlin Compiler Plugin](/hooks/modules/kotlin-compiler-plugin) +* [Processor](/hooks/modules/processor) * [Gradle Plugin](/hooks/modules/gradle-plugin) * [Maven Kotlin Plugin Extension](/hooks/modules/maven-plugin) diff --git a/docs/src/test/kotlin/example/snippets.kt b/docs/src/test/kotlin/example/snippets.kt index 4a4e750..0a55e2f 100644 --- a/docs/src/test/kotlin/example/snippets.kt +++ b/docs/src/test/kotlin/example/snippets.kt @@ -59,12 +59,12 @@ abstract class SomeHooks : Hooks() { } // END concise_dsl -val compiler_plugin = +val processor = """ -// START compiler_plugin +// START processor To make hooks easier to use -// END compiler_plugin - """.trimIndent() +// END processor +""" val gradle_plugin = """ diff --git a/example-library/build.gradle.kts b/example-library/build.gradle.kts index 98acf55..917e406 100644 --- a/example-library/build.gradle.kts +++ b/example-library/build.gradle.kts @@ -7,7 +7,7 @@ dependencies { implementation(libs.kotlin.coroutines.core) api(project(":hooks")) - ksp(project(":compiler-plugin")) + ksp(project(":processor")) testImplementation(platform(libs.junit.bom)) testImplementation(libs.bundles.testing) diff --git a/gradle-plugin/README.md b/gradle-plugin/README.md index 5043778..11b59f2 100644 --- a/gradle-plugin/README.md +++ b/gradle-plugin/README.md @@ -4,7 +4,7 @@ > > The Gradle plugin automatically bundles a specific version of the KSP plugin, which is tied to a specific version of Kotlin (can be found [here](./settings.gradle.kts#19)). This means the Gradle plugin is only compatible with projects that use that specific Kotlin version. At some point, this module will be upgraded to publish in accordance to the KSP/Kotlin version it bundles. -Applying the hooks Gradle plugin automatically adds the appropriate dependencies to your project, configures the generated source directory, and registers the Kotlin compiler plugin. +Applying the hooks Gradle plugin automatically adds the appropriate dependencies to your project, configures the generated source directory, and registers the KSP plugin. ### Installation @@ -18,11 +18,4 @@ plugins { // apply hooks gradle plugin id("com.intuit.hooks") version "$HOOKS_VERSION" } - -// Optional - Hooks extension configuration block -hooks { - // Optional - configure output directory for generated code - // Default - "${buildDir.absolutePath}/generated/ksp/main/kotlin" - generatedSrcOutputDir = "$buildDir/custom/generated/code" -} ``` diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index e573bb6..9d29ffb 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -21,7 +21,7 @@ gradlePlugin { pluginBundle { website = "https://intuit.github.io/hooks/" vcsUrl = "https://github.com/intuit/hooks" - description = "Gradle wrapper of the Kotlin compiler companion to the Intuit hooks module" + description = "Gradle wrapper of the Kotlin symbol processor companion to the Intuit hooks module" tags = listOf("plugins", "hooks", "ksp", "codegen") } @@ -53,6 +53,8 @@ tasks { } test { - dependsOn(":hooks:publishToMavenLocal", ":compiler-plugin:publishToMavenLocal") + // TODO: Testing migration required the deps to be pulled from somewhere + // Would be nice if they could just use the local built JARs + dependsOn(":hooks:publishToMavenLocal", ":processor:publishToMavenLocal") } } diff --git a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt index 49d35c8..6b33794 100644 --- a/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt +++ b/gradle-plugin/src/main/kotlin/com/intuit/hooks/plugin/gradle/HooksGradlePlugin.kt @@ -34,7 +34,7 @@ public class HooksGradlePlugin : Plugin { pluginManager.apply("com.google.devtools.ksp") addDependency("api", "com.intuit.hooks:hooks:$hooksVersion") - addDependency("ksp", "com.intuit.hooks:compiler-plugin:$hooksVersion") + addDependency("ksp", "com.intuit.hooks:processor:$hooksVersion") // TODO: Maybe apply to Kotlin plugin to be compatible with MPP plugins.withType(JavaPlugin::class.java) { _ -> diff --git a/gradle-plugin/src/test/kotlin/HooksGradlePluginTest.kt b/gradle-plugin/src/test/kotlin/HooksGradlePluginTest.kt index bf7192a..d4dc42a 100644 --- a/gradle-plugin/src/test/kotlin/HooksGradlePluginTest.kt +++ b/gradle-plugin/src/test/kotlin/HooksGradlePluginTest.kt @@ -40,9 +40,7 @@ class HooksGradlePluginTest { @Test fun `can apply plugin`() { buildFile.appendKotlin( """ - hooks { - generatedSrcOutputDir = "asdf" - } + hooks {} """ ) diff --git a/hooks/README.md b/hooks/README.md index f6847af..3be7f01 100644 --- a/hooks/README.md +++ b/hooks/README.md @@ -1,6 +1,8 @@ # Hooks -> These instructions are for using the base hooks library by itself. Under most circumstances, it is advised to use the Kotlin compiler plugin with the DSL to limit the code overhead. However, if your project is Java-only or doesn't easily support Kotlin compiler plugins, it is still possible to utilize the hooks library. +> **Note** +> +> These instructions are for using the base hooks library by itself. Under most circumstances, it is advised to use the hooks processor with the DSL to limit the code overhead. However, it is still possible to utilize the hooks library directly if necessary. ### Installation diff --git a/maven-plugin/build.gradle.kts b/maven-plugin/build.gradle.kts index 097e25d..8d9ad26 100644 --- a/maven-plugin/build.gradle.kts +++ b/maven-plugin/build.gradle.kts @@ -1,15 +1,15 @@ -val compilerPlugin: Configuration by configurations.creating +val ksp: Configuration by configurations.creating dependencies { implementation(libs.kotlin.maven) implementation(libs.ksp.maven) - compilerPlugin(project(":compiler-plugin")) + ksp(project(":processor")) } tasks { jar { - dependsOn(":compiler-plugin:jar") - fromConfiguration(compilerPlugin) { + dependsOn(":processor:jar") + fromConfiguration(ksp) { this.duplicatesStrategy = DuplicatesStrategy.EXCLUDE } diff --git a/processor/README.md b/processor/README.md new file mode 100644 index 0000000..7f991ec --- /dev/null +++ b/processor/README.md @@ -0,0 +1,72 @@ +# Hooks Processor + +Built on the [Kotlin Symbol Processing API](https://kotlinlang.org/docs/ksp-overview.html#0), the Hooks processor enables consumers to create hooks using the type-driven DSL provided in the hooks library. KSP based processors are generally easy to apply to Gradle projects using the provided [plugin](https://kotlinlang.org/docs/ksp-quickstart.html#use-your-own-processor-in-a-project). However, we also have Gradle and Maven wrappers to ease the burden of KSP configuration. If you'd like to use outside those build systems, the processor is published as `com.intuit.hooks:processor:$version`. If you do find yourself using the processor directly, we would love to hear about your use case and would appreciate any contributions to support other build systems. + +### Manual Gradle KSP configuration + +```kotlin +// build.gradle(.kts) +plugins { + id("com.google.devtools.ksp") version KSP_VERSION // >= 1.0.5 +} + +dependencies { + ksp("com.intuit.hooks", "processor", HOOKS_VERSION) +} +``` + +### Processor DSL + +With the processor configured in your project, you can now create hooks by defining a `Hooks` subclass. This gives you access to a collection of methods to create hook implementations based on the type signature passed into the method. + + + + + +```kotlin +internal abstract class GenericHooks : Hooks() { + @Sync<(newSpeed: Int) -> Unit> abstract val sync: SyncHook<*> + @SyncBail<(Boolean) -> BailResult> abstract val syncBail: SyncBailHook<*, *> + @SyncLoop<(foo: Boolean) -> LoopResult> abstract val syncLoop: SyncLoopHook<*, *> + @SyncWaterfall<(name: String) -> String> abstract val syncWaterfall: SyncWaterfallHook<*, *> + @AsyncParallelBail BailResult> abstract val asyncParallelBail: AsyncParallelBailHook<*, *> + @AsyncParallel Int> abstract val asyncParallel: AsyncParallelHook<*> + @AsyncSeries Int> abstract val asyncSeries: AsyncSeriesHook<*> + @AsyncSeriesBail BailResult> abstract val asyncSeriesBail: AsyncSeriesBailHook<*, *> + @AsyncSeriesLoop LoopResult> abstract val asyncSeriesLoop: AsyncSeriesLoopHook<*, *> + @AsyncSeriesWaterfall String> abstract val asyncSeriesWaterfall: AsyncSeriesWaterfallHook<*, *> +} +``` + +The processor uses this class to create new hook implementations and instances. Currently, the processor generates a new class that subclasses `GenericHooks` and overrides the member properties, which is why they need to be `abstract`. This is important to note, as otherwise, the code will not compile because the member property is final. When using the hooks DSL, you must follow these constraints: + +1. `Hooks` subclass _must_ be abstract +2. All member properties that use the hooks DSL methods _must_ be abstract +3. Hook property types can include star projection, but should be the same hook type +4. Any `async` hook must take a `suspend` typed method +5. Bail hooks must return a `BailResult` +6. Loop hooks must return a `LoopResult` + +Most of these constraints should give you an error when the processor runs, however others might result in a generic compiler error, like stated above. + +The generated class name will be `${name}Impl`, thus the snippet above could be used in the following manner: + +```kotlin +fun main() { + val hooks = GenericHooksImpl() + hooks.sync.tap("LoggerPlugin") { newSpeed: Int -> + println("newSpeed: $newSpeed") + } + hooks.sync.call(30) + // newSpeed: 30 +} +``` + + + + diff --git a/compiler-plugin/api/compiler-plugin.api b/processor/api/compiler-plugin.api similarity index 100% rename from compiler-plugin/api/compiler-plugin.api rename to processor/api/compiler-plugin.api diff --git a/compiler-plugin/build.gradle.kts b/processor/build.gradle.kts similarity index 100% rename from compiler-plugin/build.gradle.kts rename to processor/build.gradle.kts diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookInfo.kt b/processor/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookInfo.kt similarity index 100% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookInfo.kt rename to processor/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookInfo.kt diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookType.kt b/processor/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookType.kt similarity index 100% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookType.kt rename to processor/src/main/kotlin/com/intuit/hooks/plugin/codegen/HookType.kt diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt b/processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt similarity index 100% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt rename to processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/HooksProcessor.kt diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt b/processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt similarity index 100% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt rename to processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/Text.kt diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/AnnotationValidations.kt b/processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/AnnotationValidations.kt similarity index 100% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/AnnotationValidations.kt rename to processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/AnnotationValidations.kt diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookPropertyValidations.kt b/processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookPropertyValidations.kt similarity index 100% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookPropertyValidations.kt rename to processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookPropertyValidations.kt diff --git a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookValidations.kt b/processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookValidations.kt similarity index 97% rename from compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookValidations.kt rename to processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookValidations.kt index 9af7415..66cf5c6 100644 --- a/compiler-plugin/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookValidations.kt +++ b/processor/src/main/kotlin/com/intuit/hooks/plugin/ksp/validation/HookValidations.kt @@ -6,7 +6,7 @@ import com.intuit.hooks.plugin.codegen.HookInfo import com.intuit.hooks.plugin.codegen.HookType import com.intuit.hooks.plugin.ksp.text -// TODO: It'd be nice if the validations were compiler plugin framework agnostic +// TODO: It'd be nice if the validations were codegen framework agnostic internal sealed class HookValidationError(val message: String, val symbol: KSNode) { class AsyncHookWithoutSuspend(symbol: KSNode) : HookValidationError("Async hooks must be defined with a suspend function signature", symbol) class WaterfallMustHaveParameters(symbol: KSNode) : HookValidationError("Waterfall hooks must take at least one parameter", symbol) diff --git a/compiler-plugin/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider similarity index 100% rename from compiler-plugin/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider rename to processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt b/processor/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt similarity index 100% rename from compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt rename to processor/src/test/kotlin/com/intuit/hooks/plugin/HookValidationErrors.kt diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt b/processor/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt similarity index 100% rename from compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt rename to processor/src/test/kotlin/com/intuit/hooks/plugin/HooksProcessorTest.kt diff --git a/compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/KotlinCompilation.kt b/processor/src/test/kotlin/com/intuit/hooks/plugin/KotlinCompilation.kt similarity index 100% rename from compiler-plugin/src/test/kotlin/com/intuit/hooks/plugin/KotlinCompilation.kt rename to processor/src/test/kotlin/com/intuit/hooks/plugin/KotlinCompilation.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index ae08aad..02b728b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,7 @@ rootProject.name = "hooks-project" include( ":hooks", - ":compiler-plugin", + ":processor", ":gradle-plugin", ":maven-plugin", ":docs", From 768a26854e5572d11d0af032dba9f10595c649c7 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Wed, 22 Jun 2022 11:36:16 -0700 Subject: [PATCH 19/23] lint,dump,knit --- docs/src/test/kotlin/example/example-dsl-01.kt | 8 ++++++++ processor/api/{compiler-plugin.api => processor.api} | 0 2 files changed, 8 insertions(+) rename processor/api/{compiler-plugin.api => processor.api} (100%) diff --git a/docs/src/test/kotlin/example/example-dsl-01.kt b/docs/src/test/kotlin/example/example-dsl-01.kt index 72259af..feaa755 100644 --- a/docs/src/test/kotlin/example/example-dsl-01.kt +++ b/docs/src/test/kotlin/example/example-dsl-01.kt @@ -1,6 +1,14 @@ // This file was automatically generated from README.md by Knit tool. Do not edit. package com.intuit.hooks.example.exampleDsl01 +// build.gradle(.kts) +plugins { + id("com.google.devtools.ksp") version KSP_VERSION // >= 1.0.5 +} + +dependencies { + ksp("com.intuit.hooks", "processor", HOOKS_VERSION) +} import com.intuit.hooks.* import com.intuit.hooks.dsl.Hooks diff --git a/processor/api/compiler-plugin.api b/processor/api/processor.api similarity index 100% rename from processor/api/compiler-plugin.api rename to processor/api/processor.api From c789985cbe02f5fa6961bf235c3ffe8a0c026621 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Wed, 22 Jun 2022 11:55:37 -0700 Subject: [PATCH 20/23] make knit happy (and me sad :() --- docs/src/test/kotlin/example/example-dsl-01.kt | 8 -------- .../test/kotlin/example/example-throwaway-01.kt | 14 ++++++++++++++ processor/README.md | 10 ++++++++++ 3 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 docs/src/test/kotlin/example/example-throwaway-01.kt diff --git a/docs/src/test/kotlin/example/example-dsl-01.kt b/docs/src/test/kotlin/example/example-dsl-01.kt index feaa755..72259af 100644 --- a/docs/src/test/kotlin/example/example-dsl-01.kt +++ b/docs/src/test/kotlin/example/example-dsl-01.kt @@ -1,14 +1,6 @@ // This file was automatically generated from README.md by Knit tool. Do not edit. package com.intuit.hooks.example.exampleDsl01 -// build.gradle(.kts) -plugins { - id("com.google.devtools.ksp") version KSP_VERSION // >= 1.0.5 -} - -dependencies { - ksp("com.intuit.hooks", "processor", HOOKS_VERSION) -} import com.intuit.hooks.* import com.intuit.hooks.dsl.Hooks diff --git a/docs/src/test/kotlin/example/example-throwaway-01.kt b/docs/src/test/kotlin/example/example-throwaway-01.kt new file mode 100644 index 0000000..2ea8e88 --- /dev/null +++ b/docs/src/test/kotlin/example/example-throwaway-01.kt @@ -0,0 +1,14 @@ +// This file was automatically generated from README.md by Knit tool. Do not edit. +package com.intuit.hooks.example.exampleThrowaway01 + +/** Throwaway code for knit (would be really nice if I could just specify a start for knit or exclude for knit) + +// build.gradle(.kts) +plugins { + id("com.google.devtools.ksp") version KSP_VERSION // >= 1.0.5 +} + +dependencies { + ksp("com.intuit.hooks", "processor", HOOKS_VERSION) +} +*/ diff --git a/processor/README.md b/processor/README.md index 7f991ec..fba06ec 100644 --- a/processor/README.md +++ b/processor/README.md @@ -4,6 +4,10 @@ Built on the [Kotlin Symbol Processing API](https://kotlinlang.org/docs/ksp-over ### Manual Gradle KSP configuration + + ```kotlin // build.gradle(.kts) plugins { @@ -15,6 +19,12 @@ dependencies { } ``` + + + + ### Processor DSL With the processor configured in your project, you can now create hooks by defining a `Hooks` subclass. This gives you access to a collection of methods to create hook implementations based on the type signature passed into the method. From 9895f28f96c4117ef73219abb26c5e0722093060 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Wed, 22 Jun 2022 12:19:45 -0700 Subject: [PATCH 21/23] upgrade knit --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 02b728b..2bcdb40 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,7 +19,7 @@ dependencyResolutionManagement { version("ksp", "1.6.21-1.0.5") version("poet", "1.11.0") version("junit", "5.7.0") - version("knit", "0.2.3") // TODO: Upgrade + version("knit", "0.4.0") version("orchid", "0.21.1") plugin("kotlin.jvm", "org.jetbrains.kotlin.jvm").versionRef("kotlin") From 3036757b6dbd48ab99c454d94e979a18cbc61f81 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Wed, 22 Jun 2022 12:24:09 -0700 Subject: [PATCH 22/23] upgrade arrow meta and handle no requested version a bit more gracefully --- settings.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 2bcdb40..05d3f38 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ dependencyResolutionManagement { create("libs") { version("kotlin", "1.6.21") version("ktlint", "0.45.2") - version("arrow", "1.0.1") // 1.1.2 + version("arrow", "1.1.2") version("ksp", "1.6.21-1.0.5") version("poet", "1.11.0") version("junit", "5.7.0") @@ -98,7 +98,7 @@ pluginManagement { resolutionStrategy { eachPlugin { when (val id = requested.id.id) { - "kotlinx-knit" -> useModule("org.jetbrains.kotlinx:$id:${requested.version!!}") + "kotlinx-knit" -> useModule("org.jetbrains.kotlinx:$id:${requireNotNull(requested.version)}") } } } From 102e8124ff30d62337a0d41592c756f3984c01b2 Mon Sep 17 00:00:00 2001 From: Jeremiah Zucker Date: Wed, 22 Jun 2022 12:43:22 -0700 Subject: [PATCH 23/23] remove example-library api folder --- example-library/api/example-library.api | 52 ------------------------- 1 file changed, 52 deletions(-) delete mode 100644 example-library/api/example-library.api diff --git a/example-library/api/example-library.api b/example-library/api/example-library.api deleted file mode 100644 index da10009..0000000 --- a/example-library/api/example-library.api +++ /dev/null @@ -1,52 +0,0 @@ -public final class com/intuit/hooks/example/library/car/Car { - public fun ()V - public final fun getHooks ()Lcom/intuit/hooks/example/library/car/CarHooksImpl; - public final fun getSpeed ()I - public final fun setSpeed (I)V - public final fun useNavigationSystem (Lcom/intuit/hooks/example/library/car/Location;Lcom/intuit/hooks/example/library/car/Location;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract class com/intuit/hooks/example/library/car/Car$Hooks : com/intuit/hooks/dsl/Hooks { - public fun ()V - public abstract fun getAccelerate ()Lcom/intuit/hooks/SyncHook; - public abstract fun getBrake ()Lcom/intuit/hooks/SyncHook; - public abstract fun getCalculateRoutes ()Lcom/intuit/hooks/AsyncSeriesWaterfallHook; -} - -public final class com/intuit/hooks/example/library/car/CarHooksImpl : com/intuit/hooks/example/library/car/Car$Hooks { - public fun ()V - public synthetic fun getAccelerate ()Lcom/intuit/hooks/SyncHook; - public fun getAccelerate ()Lcom/intuit/hooks/example/library/car/CarHooksImpl$AccelerateSyncHook; - public synthetic fun getBrake ()Lcom/intuit/hooks/SyncHook; - public fun getBrake ()Lcom/intuit/hooks/example/library/car/CarHooksImpl$BrakeSyncHook; - public synthetic fun getCalculateRoutes ()Lcom/intuit/hooks/AsyncSeriesWaterfallHook; - public fun getCalculateRoutes ()Lcom/intuit/hooks/example/library/car/CarHooksImpl$CalculateRoutesAsyncSeriesWaterfallHook; -} - -public final class com/intuit/hooks/example/library/car/CarHooksImpl$AccelerateSyncHook : com/intuit/hooks/SyncHook { - public fun (Lcom/intuit/hooks/example/library/car/CarHooksImpl;)V - public final fun call (I)V - public final fun tap (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/String; - public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/String; -} - -public final class com/intuit/hooks/example/library/car/CarHooksImpl$BrakeSyncHook : com/intuit/hooks/SyncHook { - public fun (Lcom/intuit/hooks/example/library/car/CarHooksImpl;)V - public final fun call ()V -} - -public final class com/intuit/hooks/example/library/car/CarHooksImpl$CalculateRoutesAsyncSeriesWaterfallHook : com/intuit/hooks/AsyncSeriesWaterfallHook { - public fun (Lcom/intuit/hooks/example/library/car/CarHooksImpl;)V - public final fun call (Ljava/util/List;Lcom/intuit/hooks/example/library/car/Location;Lcom/intuit/hooks/example/library/car/Location;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun tap (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function4;)Ljava/lang/String; - public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function4;)Ljava/lang/String; -} - -public abstract class com/intuit/hooks/example/library/car/Location { - public fun ()V -} - -public final class com/intuit/hooks/example/library/car/Route { - public fun ()V -} -