Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Arrow 1.2.0 #32

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
[*.{kt,kts}]
disabled_rules=no-wildcard-imports
ktlint_code_style = ktlint_official
ktlint_disabled_rules = no-wildcard-imports
ij_kotlin_allow_trailing_comma_on_call_site = true
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ tasks {

val publish by creating {
group = "publishing"
if (!isSnapshot)
if (!isSnapshot) {
finalizedBy("closeAndReleaseSonatypeStagingRepository", ":docs:orchidDeploy", ":gradle-plugin:publishPlugins")
}
}

val version by creating {
Expand Down Expand Up @@ -176,6 +177,7 @@ subprojects {
val configure: KotlinCompile.() -> Unit = {
kotlinOptions {
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
freeCompilerArgs += "-Xcontext-receivers"
}
}

Expand Down
2 changes: 1 addition & 1 deletion docs/src/orchid/kotlin/com/intuit/hooks/docs/HooksTheme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ class HooksTheme @Inject constructor(context: OrchidContext) : Theme(context, "H
listOfNotNull(super.getResourceSource(), delegateTheme.resourceSource),
emptyList(),
priority,
ThemeResourceSource
ThemeResourceSource,
)
}
2 changes: 1 addition & 1 deletion example-library/src/test/kotlin/CarHooksTest.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.intuit.hooks.example.library

import com.intuit.hooks.example.library.car.Car
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

internal class CarHooksTest {
Expand Down
4 changes: 3 additions & 1 deletion example-library/src/test/kotlin/GenericHookTests.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.intuit.hooks.example.library

import com.intuit.hooks.BailResult.*
import com.intuit.hooks.BailResult.Bail
import com.intuit.hooks.BailResult.Continue
import com.intuit.hooks.HookContext
import com.intuit.hooks.LoopResult
import com.intuit.hooks.example.library.generic.GenericHooksImpl
Expand Down Expand Up @@ -140,6 +141,7 @@ class GenericHookTests {
val result = h.call("Kian")
Assertions.assertEquals("bail now", result)
}

@Test
fun `async series loop`() = runBlocking {
var incrementedA = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@ public class HooksGradlePlugin : Plugin<Project> {

private fun Project.addDependency(configuration: String, dependencyNotation: String) = configurations
.getByName(configuration).dependencies.add(
dependencies.create(dependencyNotation)
dependencies.create(dependencyNotation),
)

override fun apply(project: Project): Unit = with(project) {
extensions.create(
"hooks",
HooksGradleExtension::class.java
HooksGradleExtension::class.java,
)

if (!pluginManager.hasPlugin("com.google.devtools.ksp"))
if (!pluginManager.hasPlugin("com.google.devtools.ksp")) {
pluginManager.apply("com.google.devtools.ksp")
}

addDependency("api", "com.intuit.hooks:hooks:$hooksVersion")
addDependency("ksp", "com.intuit.hooks:processor:$hooksVersion")
Expand Down
6 changes: 3 additions & 3 deletions gradle-plugin/src/test/kotlin/HooksGradlePluginTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ class HooksGradlePluginTest {
kotlin("jvm")
id("com.intuit.hooks")
}
"""
""",
)
}

@Test fun `can apply plugin`() {
buildFile.appendKotlin(
"""
hooks {}
"""
""",
)

assertDoesNotThrow {
Expand All @@ -66,7 +66,7 @@ class HooksGradlePluginTest {
@Sync<(String) -> Unit>
abstract val testSyncHook: Hook
}
"""
""",
)

val runner = GradleRunner.create()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public abstract class AsyncSeriesBailHook<F : Function<BailResult<R>>, R> : Asyn
taps.forEach { tapInfo ->
when (val result = invokeWithContext(tapInfo.f, context)) {
is BailResult.Bail<R> -> return@call result.value
is BailResult.Continue<*> -> Unit
}
}

Expand Down
2 changes: 1 addition & 1 deletion hooks/src/main/kotlin/com/intuit/hooks/BaseHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public data class TapInfo<FWithContext : Function<*>> internal constructor(
public val name: String,
public val id: String,
public val type: String,
public val f: FWithContext,
public val f: FWithContext
// val stage: Int, // todo: maybe this should be forEachIndexed?
// before?: string | Array // todo: do we even really need this?
)
Expand Down
1 change: 1 addition & 0 deletions hooks/src/main/kotlin/com/intuit/hooks/SyncBailHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public abstract class SyncBailHook<F : Function<BailResult<R>>, R> : SyncBaseHoo
taps.forEach { tapInfo ->
when (val result = invokeWithContext(tapInfo.f, context)) {
is BailResult.Bail<R> -> return@call result.value
is BailResult.Continue<*> -> Unit
}
}

Expand Down
3 changes: 2 additions & 1 deletion hooks/src/main/kotlin/com/intuit/hooks/dsl/Hooks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public abstract class Hooks {
ReplaceWith("@Hooks.AsyncParallelBail<F>"),
DeprecationLevel.ERROR,
)
@ExperimentalCoroutinesApi protected fun <F : Function<BailResult<*>>> asyncParallelBailHook(): AsyncParallelBailHook<*, *> = stub()
@ExperimentalCoroutinesApi
protected fun <F : Function<BailResult<*>>> asyncParallelBailHook(): AsyncParallelBailHook<*, *> = stub()

protected annotation class AsyncSeries<F : Function<*>>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.intuit.hooks

import io.mockk.*
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions
Expand All @@ -10,7 +12,7 @@ class AsyncSeriesLoopHookTests {
class LoopHook1<T1> : AsyncSeriesLoopHook<suspend (HookContext, T1) -> LoopResult, suspend (HookContext, T1) -> Unit>() {
suspend fun call(p1: T1) = super.call(
invokeTap = { f, context -> f(context, p1) },
invokeInterceptor = { f, context -> f(context, p1) }
invokeInterceptor = { f, context -> f(context, p1) },
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class AsyncSeriesWaterfallHookTests {
suspend fun call(p1: R) = super.call(
p1,
invokeTap = { f, r, context -> f(context, r) },
invokeInterceptor = { f, context -> f(context, p1) }
invokeInterceptor = { f, context -> f(context, p1) },
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class SyncLoopHookTests {
class LoopHook1<T1> : SyncLoopHook<(HookContext, T1) -> LoopResult, (HookContext, T1) -> Unit>() {
fun call(p1: T1) = super.call(
invokeTap = { f, context -> f(context, p1) },
invokeInterceptor = { f, context -> f(context, p1) }
invokeInterceptor = { f, context -> f(context, p1) },
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ class SyncWaterfallHookTests {
fun call(p1: T1) = super.call(
p1,
invokeTap = { f, acc, context -> f(context, acc) },
invokeInterceptor = { f, context -> f(context, p1) }
invokeInterceptor = { f, context -> f(context, p1) },
)
}

class Hook2<T1, T2> : SyncWaterfallHook<(HookContext, T1, T2) -> T1, T1>() {
fun call(p1: T1, p2: T2) = super.call(
p1,
invokeTap = { f, acc, context -> f(context, acc, p2) },
invokeInterceptor = { f, context -> f(context, p1, p2) }
invokeInterceptor = { f, context -> f(context, p1, p2) },
)
}

Expand Down
2 changes: 1 addition & 1 deletion maven-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ tasks {
from(
configurations.compileClasspath.get().filter { dependency ->
dependency.absolutePath.contains("kotlin-maven-symbol-processing")
}.map(::zipTree)
}.map(::zipTree),
) {
this.duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
Expand Down
11 changes: 11 additions & 0 deletions processor/api/processor.api
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
public final class com/intuit/hooks/plugin/RaiseKt {
public static final fun accumulate (Larrow/core/raise/Raise;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)V
public static final fun ensure (Larrow/core/raise/Raise;ZLkotlin/jvm/functions/Function0;)V
public static final fun mapOrAccumulate (Larrow/core/raise/Raise;Larrow/core/NonEmptyList;Lkotlin/jvm/functions/Function2;)Larrow/core/NonEmptyList;
public static final fun mapOrAccumulate (Larrow/core/raise/Raise;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)Ljava/util/List;
public static final fun mapOrAccumulate (Larrow/core/raise/Raise;Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function2;)Ljava/util/List;
public static final fun mapOrAccumulate-jkbboic (Larrow/core/raise/Raise;Ljava/util/Set;Lkotlin/jvm/functions/Function2;)Ljava/util/Set;
public static final fun raise (Larrow/core/raise/Raise;Ljava/lang/Object;)Ljava/lang/Void;
public static final fun raiseAll (Larrow/core/raise/Raise;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)Ljava/util/List;
}

public final class com/intuit/hooks/plugin/ksp/HooksProcessor : com/google/devtools/ksp/processing/SymbolProcessor {
public fun <init> (Lcom/google/devtools/ksp/processing/CodeGenerator;Lcom/google/devtools/ksp/processing/KSPLogger;)V
public fun process (Lcom/google/devtools/ksp/processing/Resolver;)Ljava/util/List;
Expand Down
83 changes: 83 additions & 0 deletions processor/src/main/kotlin/com/intuit/hooks/plugin/Raise.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
@file:OptIn(ExperimentalTypeInference::class)

package com.intuit.hooks.plugin

import arrow.core.*
import arrow.core.raise.*
import kotlin.experimental.ExperimentalTypeInference

// Collection of [Raise] helpers for accumulating errors from a single error context

/** Helper for accumulating errors from single-error validators */
@RaiseDSL
internal fun <Error, A> Raise<Nel<Error>>.ensure(@BuilderInference block: Raise<Error>.() -> A): A =
recover(block) { e: Error -> raise(e.nel()) }

/** Helper for accumulating errors from single-error validators */
@RaiseDSL
public inline fun <Error> Raise<Nel<Error>>.ensure(condition: Boolean, raise: () -> Error) {
recover({ ensure(condition, raise) }) { e: Error -> raise(e.nel()) }
}

/** Raise a _logical failure_ of type [Error] in a multi-[Error] accumulator */
@RaiseDSL
public inline fun <Error> Raise<Nel<Error>>.raise(r: Error): Nothing {
raise(r.nel())
}

@RaiseDSL
public inline fun <Error, A> Raise<NonEmptyList<Error>>.raiseAll(
iterable: Iterable<A>,
@BuilderInference transform: Raise<NonEmptyList<Error>>.(A) -> Unit
): List<Unit> = mapOrAccumulate(iterable) { arg ->
recover<NonEmptyList<Error>, Unit>({ transform(arg) }) { errors ->
[email protected](errors)
}
}

/** Explicitly accumulate errors that may have been raised while processing each element */
context(Raise<NonEmptyList<Error>>)
@RaiseDSL
public inline fun <Error, A> Iterable<A>.accumulate(
@BuilderInference operation: Raise<Nel<Error>>.(A) -> Unit
) {
flatMap {
recover({
operation(it); emptyList()
},) { it }
}.toNonEmptyListOrNull()?.let { raise(it) }
}

/** [mapOrAccumulate] variant that accumulates errors from a validator that may raise multiple errors */
context(Raise<NonEmptyList<Error>>)
@RaiseDSL
public inline fun <Error, A, B> Sequence<A>.mapOrAccumulate( // TODO: Consider renaming
@BuilderInference operation: Raise<Nel<Error>>.(A) -> B
): List<B> = toList().mapOrAccumulate(operation)

/** [mapOrAccumulate] variant that accumulates errors from a validator that may raise multiple errors */
context(Raise<NonEmptyList<Error>>)
@RaiseDSL
public inline fun <Error, A, B> Iterable<A>.mapOrAccumulate( // TODO: Consider renaming
@BuilderInference operation: Raise<Nel<Error>>.(A) -> B
): List<B> = recover({
mapOrAccumulate(this@mapOrAccumulate) { operation(it) }
},) { errors -> raise(errors.flatMap { it }) }

/** [mapOrAccumulate] variant that accumulates errors from a validator that may raise multiple errors */
context(Raise<NonEmptyList<Error>>)
@RaiseDSL
public inline fun <Error, A, B> NonEmptyList<A>.mapOrAccumulate( // TODO: Consider renaming
@BuilderInference operation: Raise<Nel<Error>>.(A) -> B
): NonEmptyList<B> = recover({
mapOrAccumulate(this@mapOrAccumulate) { operation(it) }
},) { errors -> raise(errors.flatMap { it }) }

/** [mapOrAccumulate] variant that accumulates errors from a validator that may raise multiple errors */
context(Raise<NonEmptyList<Error>>)
@RaiseDSL
public inline fun <Error, A, B> NonEmptySet<A>.mapOrAccumulate( // TODO: Consider renaming
@BuilderInference operation: Raise<Nel<Error>>.(A) -> B
): NonEmptySet<B> = recover({
mapOrAccumulate(this@mapOrAccumulate) { operation(it) }
},) { errors -> raise(errors.flatMap { it }) }
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ internal data class HooksContainer(
val superclass get() = originalClassName.let {
if (typeArguments.isNotEmpty()) {
it.parameterizedBy(typeArguments)
} else
} else {
it
}
}
}

Expand All @@ -24,7 +25,7 @@ internal data class HookSignature(
val isSuspend: Boolean,
val returnType: TypeName,
val returnTypeType: TypeName?,
val hookFunctionSignatureType: TypeName,
val hookFunctionSignatureType: TypeName
) {
val nullableReturnTypeType: TypeName get() {
requireNotNull(returnTypeType)
Expand All @@ -36,7 +37,7 @@ internal data class HookSignature(
internal class HookParameter(
val name: String?,
val type: TypeName,
val position: Int,
val position: Int
) {
val withType get() = "$withoutType: $type"
val withoutType get() = name ?: "p$position"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal sealed class HookProperty {
object Waterfall : HookProperty()
}

internal enum class HookType(vararg val properties: HookProperty) {
internal enum class HookType(val properties: Set<HookProperty>) {
SyncHook,
SyncBailHook(HookProperty.Bail),
SyncWaterfallHook(HookProperty.Waterfall),
Expand All @@ -19,9 +19,13 @@ internal enum class HookType(vararg val properties: HookProperty) {
AsyncSeriesWaterfallHook(HookProperty.Async, HookProperty.Waterfall),
AsyncSeriesLoopHook(HookProperty.Async, HookProperty.Loop);

constructor(vararg properties: HookProperty) : this(properties.toSet())

companion object {
val annotationDslMarkers = values().map {
it.name.dropLast(4)
val supportedHookTypes = values().map(HookType::name)

val annotationDslMarkers = supportedHookTypes.map {
it.dropLast(4)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ internal fun HookInfo.generateClass(): TypeSpec {
val callBuilder = FunSpec.builder("call")
.addParameters(parameterSpecs)
.apply {
if ([email protected])
if ([email protected]) {
addModifiers(KModifier.SUSPEND)
}
}

val (superclass, call) = when (hookType) {
Expand All @@ -66,7 +67,7 @@ internal fun HookInfo.generateClass(): TypeSpec {
.addCode(
"return super.call(invokeTap = %L, invokeInterceptor = %L)",
CodeBlock.of("{ f, context -> f(context, $paramsWithoutTypes) }"),
CodeBlock.of("{ f, context -> f(context, $paramsWithoutTypes) }")
CodeBlock.of("{ f, context -> f(context, $paramsWithoutTypes) }"),
)

Pair(superclass, call)
Expand All @@ -81,7 +82,7 @@ internal fun HookInfo.generateClass(): TypeSpec {
"return super.call(%N, invokeTap = %L, invokeInterceptor = %L)",
accumulatorName,
CodeBlock.of("{ f, %N, context -> f(context, $paramsWithoutTypes) }", accumulatorName),
CodeBlock.of("{ f, context -> f(context, $paramsWithoutTypes) }")
CodeBlock.of("{ f, context -> f(context, $paramsWithoutTypes) }"),
)

Pair(superclass, call)
Expand Down Expand Up @@ -127,7 +128,7 @@ private val HookInfo.lambdaTypeName get() = createHookContextLambda(hookSignatur
private fun HookInfo.createHookContextLambda(returnType: TypeName): LambdaTypeName {
val get = LambdaTypeName.get(
parameters = listOf(ParameterSpec.unnamed(hookContext)) + parameterSpecs,
returnType = returnType
returnType = returnType,
)

return if (this.isAsync) get.copy(suspending = true) else get
Expand Down