From 1d0e37bd58d214ff76a7423c39df87a9e4485ff2 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Fri, 10 Oct 2025 19:06:32 +0800 Subject: [PATCH 1/3] doc: Update doc version.json --- docs/src/version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/version.json b/docs/src/version.json index e375e75..bb9e81c 100644 --- a/docs/src/version.json +++ b/docs/src/version.json @@ -1 +1 @@ -{"version": "2.2.20-0.13.1"} \ No newline at end of file +{"version": "2.2.20-0.13.2"} \ No newline at end of file From 55ca116f1613e2a28cdaaecdd4dca150c8ba0ef3 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Fri, 24 Oct 2025 00:12:05 +0800 Subject: [PATCH 2/3] doc: Update README --- README.md | 933 +-------------------------------------------------- README_CN.md | 611 +-------------------------------- 2 files changed, 10 insertions(+), 1534 deletions(-) diff --git a/README.md b/README.md index 61064be..857bf0a 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ **English** | [简体中文](README_CN.md) -## Summary +## What's this -Kotlin compiler plugin for generating platform-compatible functions for suspend functions. +This is a Kotlin compiler plugin for generating platform-compatible functions for suspend functions. ### JVM @@ -180,934 +180,13 @@ class Foo { } ``` -## Usage +## Documentation -### The version - -Before `0.9.0` (included), the naming convention for versions was `x.y.z`. -But it seems that the contents of the Kotlin compiler may find changes with each version, -and such version numbers do not reflect the corresponding Kotlin version, -and may lead to some confusion as a result. - -Therefore, starting after `0.9.0`, versions will be named in the form `$Kotlin-$plugin`, -e.g. `2.0.20-0.9.1`. -The first half is the version of Kotlin used for the build, while the second half is the version of this plugin. - -If the version is less than or equal to `0.9.0`, you can refer to this comparison table: - -| Kotlin version | plugin version | -|----------------|-------------------------| -| `2.0.0` | `0.8.0-beta1` ~ `0.9.0` | -| `1.9.22` | `0.7.0-beta1` | -| `1.9.21` | `0.6.0` | -| `1.9.10` | `0.5.1` | -| `1.9.0` | `0.5.0` | -| `1.8.21` | `0.3.1` ~ `0.4.0` | - -> [!note] -> I haven't documented in detail the compiler plugin compatibility between each Kotlin version. -> From my memory and guess, Kotlin versions have a higher probability of incompatibility when minor is added (e.g. `1.8.0` -> `1.9.0`), -> and a smaller probability of incompatibility when patch is added (e.g. `1.9.21` -> `1.9.22`). - -### Gradle - -**Using the [plugins DSL](https://docs.gradle.org/current/userguide/plugins.html#sec:plugins_block):** - -_build.gradle.kts_ - -```Kotlin -plugins { - kotlin("jvm") version "$KOTLIN_VERSION" // or multiplatform - id("love.forte.plugin.suspend-transform") version "$PLUGIN_VERSION" - // other... -} - -// other... - -// config it. -suspendTransformPlugin { - // Config the SuspendTransformPluginExtension ... -} -``` - -**Using [legacy plugin application](https://docs.gradle.org/current/userguide/plugins.html#sec:old_plugin_application):** - -_build.gradle.kts_ - -```Kotlin -buildscript { - repositories { - mavenCentral() - gradlePluginPortal() - } - dependencies { - classpath("love.forte.plugin.suspend-transform:suspend-transform-plugin-gradle:$GRADLE_PLUGIN_VERSION") - } -} - -plugins { - id("org.jetbrains.kotlin.jvm") // or multiplatform? - id("love.forte.plugin.suspend-transform") - // other... -} - -// other... - -// config it. -suspendTransformPlugin { - // Config the SuspendTransformPluginExtension ... -} -``` - -## Config the extension - -### Enabled - -Enable the Kotlin compiler plugin. -Default value is `true`. - -```Kotlin -suspendTransformPlugin { - enabled = true -} -``` - -### Include the default annotations and runtime - -If you wish to use the Transformer we provide, then you may need to add the `annotation` and `runtime` dependencies. - -You can add them automatically via configuration. - -```Kotlin -suspendTransformPlugin { - // include the annotation - // Default is `true` - includeAnnotation = true - // The default can be left unconfigured and the default values are used exclusively. - annotationDependency { - // Default is `compileOnly` - configurationName = "compileOnly" - // Default is same as the plugin version - version = "" - } - - // Include the runtime - // Default is `true` - includeRuntime = true - // The default can be left unconfigured and the default values are used exclusively. - runtimeDependency { - // Default is `implementation` - configurationName = "implementation" - // Default is same as the plugin version - version = "" - } -} -``` - -You can also disable them and add dependencies manually. - -```Kotlin -plugin { - kotlin("jvm") version "..." // Take the Kotlin/JVM as an example - id("love.forte.plugin.suspend-transform") version "" -} - -dependencies { - // annotation - compileOnly("love.forte.plugin.suspend-transform:suspend-transform-annotation:") - // runtime - implementation("love.forte.plugin.suspend-transform:suspend-transform-runtime:") -} - -suspendTransformPlugin { - // Disable them - includeAnnotation = false - includeRuntime = false -} -``` - -### Add transformers - -`Transformer` is the type used to describe how the suspend function is transformed. -You need to add some `Transformer`s to make the compiler plugin actually work. - - -```Kotlin -suspendTransformPlugin { - // Config the transformers - transformers { - add(TargetPlatform.JVM) { // this: TransformerSpec - // Config the TransformerSpec... - } - - addJvm { // this: TransformerSpec - // Config the TransformerSpec... - } - - // Use a default transformer we provided from `SuspendTransformConfigurations` - add(TargetPlatform.JVM, SuspendTransformConfigurations.jvmBlockingTransformer) - - addJvm { // this: TransformerSpec - // Modify and adjust from a Transformer - from(SuspendTransformConfigurations.jvmBlockingTransformer) - // Further configurations... - } - } -} -``` - -#### Add the default transformers - -First, we provide some simple and commonly used implementations. -You can use them simply and quickly through configuration. - -> [!note] -> The default `Transformer`s depend on the `annotation` and `runtime` we provide. -> Make sure you include them before using it. - -**JVM blocking** - -```Kotlin -suspendTransformPlugin { - transformers { - // The 1st way: - addJvmBlocking() - - // Or the 2ed way: - addJvm(SuspendTransformConfigurations.jvmBlockingTransformer) - // Or use transformers.add(TargetPlatform.JVM, jvmBlockingTransformer), etc. - } -} -``` - -`JvmBlocking` allows you to mark `@JvmBlocking` on the suspend function, -which generates a `xxxBlocking` function. - -```Kotlin -class Cat { - @JvmBlocking - suspend fun meow() { - // ... - } - - // Generated: - fun meowBlocking() { - `$runInBlocking$` { meow() } - } -} -``` - -The `$runInBlocking$` based on `kotlinx.coroutines.runBlocking` 。 - -**JVM Async** - -```Kotlin -suspendTransformPlugin { - transformers { - // The 1st way: - addJvmAsync() - - // Or the 2ed way: - addJvm(SuspendTransformConfigurations.jvmAsyncTransformer) - // Or use transformers.add(TargetPlatform.JVM, jvmAsyncTransformer), etc. - } -} -``` - -`JvmAsync` allows you to mark `@JvmAsync` on the suspend function, -which generates a `xxxAsync` function. - -```Kotlin -class Cat { - @JvmBlocking - suspend fun meow(): String = "Meow!" - - // Generated: - fun meowAsync(): CompletableFuture { - `$runInAsync$`(block = { meow() }, scope = this as? CoroutineScope) - } -} -``` - -The `block` is the original suspend function that needs to be executed -and the `scope` is the `CoroutineScope` that will be used. - -If the current scope is a `CoroutineScope`, it takes precedence over itself. -Otherwise, `GlobalScope` is used internally. - -Why use `GlobalScope`: When using an internal scope, this scope qualifies: -1. global. -2. is never visible externally, so it is not artificially closed. -3. is not intended for IO and does not require a custom dispatcher. - -We believe `GlobalScope` meets these conditions. - -_Have a different point? Feel free to create issue!_ - -**JS Promise** - -```Kotlin -suspendTransformPlugin { - transformers { - // The 1st way: - addJsPromise() - - // Or the 2ed way: - addJs(SuspendTransformConfigurations.jsPromiseTransformer) - // Or use transformers.add(TargetPlatform.JS, jsPromiseTransformer), etc. - } -} -``` - -```Kotlin -class Cat { - @JsPromise - suspend fun meow(): String = "Meow!" - - // Generated: - fun meowAsync(): Promise { - `$runInAsync$`(block = { meow() }, scope = this as? CoroutineScope) - } -} -``` - -The `block` is the original suspend function that needs to be executed -and the `scope` is the `CoroutineScope` that will be used. - -#### Use the defaults - -The `addJvmBlocking()` and `addJvmAsync()` may be combined as `useJvmDefault()`. - -```Kotlin -suspendTransformPlugin { - transformers { - // Includes addJvmBlocking() and addJvmAsync() - useJvmDefault() - } -} -``` - -The `addJsPromise()` may be combined as `useJsDefault()`. - -```Kotlin -suspendTransformPlugin { - transformers { - // Includes addJsPromise() - useJsDefault() - } -} -``` - -The `useJvmDefault()` and `useJsDefault()` may be combined as `useDefault()`. - -```Kotlin -suspendTransformPlugin { - transformers { - // Includes useJvmDefault() and useJsDefault() - useDefault() - } -} -``` - -#### Use custom transformers - -You can also customize your `Transformer` if the default `Transformer`s don't meet your needs, -e.g. if you want to fully implement blocking logic and don't want to use `kotlinx.coroutines.runBlocking`. - -> A fully customized implementation of JVM Blocking/Async Transformers reference: -> https://github.com/simple-robot/simpler-robot/blob/v4-main/simbot-commons/simbot-common-suspend-runner/src/jvmMain/kotlin/love/forte/simbot/suspendrunner/BlockingRunner.kt - -```Kotlin -suspendTransformPlugin { - // If customized, then you may not use the annotation and runtime we provide. - includeAnnotation = false - includeRuntime = false - - transformer { - // See below for details - } -} -``` - -As an example, you intend to create a custom annotation: `@JBlock`, -which is executed via the function `inBlock` when the suspend function uses this annotation. - -```Kotlin -// Your annotation -annotation class JBlock(...) - -// Your top-level transform function -fun inBlock(block: suspend () -> T): T { - TODO("Your impl") -} -``` - -First, let's agree that the following properties should be included in the annotation: - -- `baseName`: The generated function's **base name**. - When the value of this property is empty, the name of the original function is used by default. - ```Kotlin - @JBlock(baseName = "") - suspend fun meow1() // Generated function name: ${baseName}${suffix} -> meow1Blocking - - @JBlock(baseName = "meow999") - suspend fun meow2() // Generated function name: ${baseName}${suffix} -> meow999Blocking - ``` -- `suffix`: The generated function name's suffix. -- `asProperty`: Make the generated function a property. - Can be used in cases where the original function has no arguments. - ```Kotlin - @JBlock(asProperty = true) - suspend fun value(): Int - - // Generated: - val valueBlocking: Int - get() = inBlock { value() } - ``` - -So your annotation should look like this: - -```Kotlin -annotation class JBlock( - val baseName: String = "", - val suffix: String = "Blocking", - val asProperty: Boolean = false -) -``` - -The configuration: - -```Kotlin -suspendTransformPlugin { - includeAnnotation = false - includeRuntime = false - transformers { - addJvm { - markAnnotation { - // Your annotation class's info. - classInfo { - packageName = "com.example" - className = "JBlock" - } - - // The property names. - baseNameProperty = "baseName" // Default is `baseName` - suffixProperty = "suffix" // Default is `suffix` - asPropertyProperty = "asProperty" // Default is `asProperty` - - // The compiler plugin doesn't seem to be able to get the default values for annotations - // (or I haven't found a way to do it yet). - // So here you need to configure the default value of the annotation, which needs to be consistent with your definition. - defaultSuffix = "Blocking" - defaultAsProperty = false // For the same reasons as above. - } - } - } -} -``` - -However, the property names do not have to be the same as these three, as long as the function and type correspond. So we can adjust it like this: - -```Kotlin -annotation class JBlock( - val myBaseName: String = "", - val mySuffix: String = "Blocking", - val myAsProperty: Boolean = false -) -``` - -The configuration: - -```Kotlin -suspendTransformPlugin { - includeAnnotation = false - includeRuntime = false - transformers { - addJvm { - markAnnotation { - // Your annotation class's info. - classInfo { - packageName = "com.example" - className = "JBlock" - } - - // The property names. - baseNameProperty = "myBaseName" - suffixProperty = "mySuffix" - asPropertyProperty = "myAsProperty" - - // The default values. - defaultSuffix = "Blocking" - defaultAsProperty = false - } - } - } -} -``` - -Then configure the information for your transform function. - -```Kotlin -// Your top-level transform function -fun inBlock(block: suspend () -> T): T { - TODO("Your impl") -} -``` - -The configuration: - -```Kotlin -suspendTransformPlugin { - includeAnnotation = false - includeRuntime = false - transformers { - addJvm { - markAnnotation { - // ... - } - - // The function info - transformFunctionInfo { - packageName = "com.example" - functionName = "inBlock" - } - - // The return type configs - - // The return type. - // If `null` it means the same type as the original function return. - // If you return a specific type (e.g. `CompletableFuture`) you need to configure that type. - // - // Default value is null. - transformReturnType = null - - // Whether the returned type contains a generic type that is of the same type as the original function. - // e.g. CompletableFuture, The `T` represents the value returned by the original function. - // In this case it is set to `true`. - // - // Set to `false` if the return type is of a specific type, - // but without a generic (a rare case, an example: `Job`). - // Valid if `transformReturnType` is not null. - // - // Default value is false. - transformReturnTypeGeneric = false - } - } -} -``` - -Finally, in the process of generating the function, we allow some manipulation of the annotations. -- Copy annotations from original function to generated synthetic function. - - exclude some annotations from copying. -- Include some annotations to original function. -- Include some annotations to generated synthetic function. - -Now let's assume: -- We want to add `@JvmSynthetic` to the original function. -- We want to add `@JApi` to the generated synthetic function. -- Copy the annotations without copying `@JvmSynthetic` (exclude `@JvmSynthetic`). - -The `@JApi`: - -```Kotlin -@RequiresOptIn(message = "Api for Java", level = RequiresOptIn.Level.WARNING) -@Retention(AnnotationRetention.BINARY) -annotation class JApi -``` - -The configuration: - -```Kotlin -suspendTransformPlugin { - includeAnnotation = false - includeRuntime = false - transformers { - addJvm { - markAnnotation { - // ... - } - transformFunctionInfo { - // ... - } - - // Enabling annotated copies - // Default is FALSE - copyAnnotationsToSyntheticFunction = true - // If the generated synthetic function is property (asProperty=true), - // Copy annotations to the property. - // Otherwise, copy to the property's getter function. - // Default is FALSE - copyAnnotationsToSyntheticProperty = true - - // Include `@kotlin.jvm.JvmSynthetic` to original function. - addOriginFunctionIncludeAnnotation { - // Some common types are defined in SuspendTransformConfigurations. See below. - classInfo { - packageName = "kotlin.jvm" - className = "JvmSynthetic" - } - // Default is false - repeatable = false - } - - // Include `@com.example.JApi` to generated synthetic function - addSyntheticFunctionIncludeAnnotation { - classInfo { - packageName = "com.example" - className = "JApi" - } - // Marks whether this annotation supports being added to a property. - // Default is FALSE - includeProperty = true - } - - // Exclude `@kotlin.jvm.JvmSynthetic` when copying. - addCopyAnnotationExclude { - // SuspendTransformConfigurations provides a small number of - // common annotations or type definitions that can be used directly. - from(SuspendTransformConfigurations.jvmSyntheticClassInfo) - } - } - } -} -``` - -The full example: - -Code: - -```Kotlin -annotation class JBlock( - val myBaseName: String = "", - val mySuffix: String = "Blocking", - val myAsProperty: Boolean = false -) - -@RequiresOptIn(message = "Api for Java", level = RequiresOptIn.Level.WARNING) -@Retention(AnnotationRetention.BINARY) -annotation class JApi - -fun inBlock(block: suspend () -> T): T { - TODO("Your impl") -} -``` - -Configuration: - -```Kotlin -suspendTransformPlugin { - includeAnnotation = false - includeRuntime = false - transformers { - addJvm { - markAnnotation { - classInfo { - packageName = "com.example" - className = "JBlock" - } - - baseNameProperty = "myBaseName" - suffixProperty = "mySuffix" - asPropertyProperty = "myAsProperty" - - defaultSuffix = "Blocking" - defaultAsProperty = false - } - - transformFunctionInfo { - packageName = "com.example" - functionName = "inBlock" - } - - copyAnnotationsToSyntheticFunction = true - copyAnnotationsToSyntheticProperty = true - - addOriginFunctionIncludeAnnotation { - classInfo { - from(SuspendTransformConfigurations.jvmSyntheticClassInfo) - } - repeatable = false - } - - addSyntheticFunctionIncludeAnnotation { - classInfo { - packageName = "com.example" - className = "JApi" - } - includeProperty = true - } - - addCopyAnnotationExclude { - from(SuspendTransformConfigurations.jvmSyntheticClassInfo) - } - } - } -} -``` +This is the [documentation](https://kstcp.forte.love/). > [!note] -> Since the property name is configurable, the same annotation can be reused on multiple transformers. -> Annotation: -> ```Kotlin -> annotation class JTrans( -> val blockingBaseName: String = "", -> val blockingSuffix: String = "Blocking", -> val blockingAsProperty: Boolean = false, -> -> val asyncBaseName: String = "", -> val asyncSuffix: String = "Async", -> val asyncAsProperty: Boolean = false -> ) -> ``` -> Configuration: -> ```Kotlin -> suspendTransformPlugin { -> includeAnnotation = false -> includeRuntime = false -> transformers { -> // For blocking -> addJvm { -> markAnnotation { -> classInfo { -> packageName = "com.example" -> className = "JTrans" -> } -> baseNameProperty = "blockingBaseName" -> suffixProperty = "blockingSuffix" -> asPropertyProperty = "blockingAsProperty" -> defaultSuffix = "Blocking" -> defaultAsProperty = false -> } -> -> transformFunctionInfo { -> packageName = "com.example" -> functionName = "inBlock" -> } -> -> // other config... -> } -> -> // For async -> addJvm { -> markAnnotation { -> classInfo { -> packageName = "com.example" -> className = "JTrans" -> } -> baseNameProperty = "asyncBaseName" -> suffixProperty = "asyncSuffix" -> asPropertyProperty = "asyncAsProperty" -> defaultSuffix = "Async" -> defaultAsProperty = false -> } -> -> transformFunctionInfo { -> packageName = "com.example" -> functionName = "inAsync" -> } -> } -> } ->} -> ``` - -## Cautions -### Gradle JVM - -**Gradle JVM** must be JDK11+ - -### K2 - -K2 is supported since `v0.7.0`. - -### JsExport - -If you want to use `@JsExport` with default configuration in JS, -try this: - -_build.gradle.kts_ - -```kotlin -import love.forte.plugin.suspendtrans.configuration.SuspendTransformConfigurations - -plugins { - // ... -} - -suspendTransformPlugin { - transformers { - addJsPromise { - addCopyAnnotationExclude { - // The generated function does not include (copy) `@JsExport.Ignore`. - from(kotlinJsExportIgnoreClassInfo) - } - } - } -} -``` - -```Kotlin -@file:OptIn(ExperimentalJsExport::class) - -@JsExport -class Foo { - @JsPromise - @JsExport.Ignore - suspend fun run(): Int = ... -} -``` - -## Effect - -**source:** - -```kotlin -import love.forte.plugin.suspendtrans.annotation.JvmAsync -import love.forte.plugin.suspendtrans.annotation.JvmBlocking - -@JvmBlocking -@JvmAsync -interface Foo { - - suspend fun name(): String - - suspend fun age(def: Int = 5): Int - - @JvmBlocking(asProperty = true) - suspend fun self(): Foo -} - -@JvmBlocking -@JvmAsync -class FooImpl : Foo { - suspend fun size(): Long = 666 - override suspend fun name(): String = "forte" - override suspend fun age(def: Int): Int = def - @JvmBlocking(asProperty = true) // must be 'asProperty=true' - override suspend fun self(): FooImpl = this -} - -class Bar { - @JvmBlocking - @JvmAsync - suspend fun bar(): String = "" - - suspend fun noTrans(): Int = 1 -} -``` - -**compiled:** - -> _Simplified from decompiled results._ - -```kotlin -import love.forte.plugin.suspendtrans.annotation.JvmAsync -import love.forte.plugin.suspendtrans.annotation.JvmBlocking -import love.forte.plugin.suspendtrans.annotation.Generated -import love.forte.plugin.suspendtrans.annotation.Api4J -import kotlin.jvm.JvmSynthetic - -@JvmBlocking -@JvmAsync -interface Foo { - @Generated - @Api4J - val selfBlocking: Foo /* compiled code */ - - suspend fun age(def: Int /* = compiled code */): Int - - @Generated - @Api4J - fun ageAsync(def: Int /* = compiled code */): java.util.concurrent.CompletableFuture { /* compiled code */ } - - @Generated - @Api4J - fun ageBlocking(def: Int /* = compiled code */): Int { /* compiled code */ } - - suspend fun name(): String - - @Generated - @Api4J - fun nameAsync(): java.util.concurrent.CompletableFuture { /* compiled code */ } - - @Generated - @Api4J - fun nameBlocking(): String { /* compiled code */ } - - @JvmBlocking - suspend fun self(): Foo - - @Generated - @Api4J - fun selfAsync(): java.util.concurrent.CompletableFuture { /* compiled code */ } -} - -@JvmBlocking -@JvmAsync -class FooImpl : Foo { - @Generated - @Api4J - open val selfBlocking: FooImpl /* compiled code */ - - @JvmSynthetic - open suspend fun age(def: Int): Int { /* compiled code */ } - - @Generated - @Api4J - open fun ageAsync(def: Int): java.util.concurrent.CompletableFuture { /* compiled code */ } - - @Generated - @Api4J - open fun ageBlocking(def: Int): Int { /* compiled code */ } - - @JvmSynthetic - open suspend fun name(): String { /* compiled code */ } - - @Generated - @Api4J - open fun nameAsync(): java.util.concurrent.CompletableFuture { /* compiled code */ } - - @Generated - @Api4J - open fun nameBlocking(): String { /* compiled code */ } - - @JvmSynthetic - @JvmBlocking - suspend fun self(): FooImpl { /* compiled code */ } - - @Generated - @Api4J - fun selfAsync(): java.util.concurrent.CompletableFuture { /* compiled code */ } - - @JvmSynthetic - suspend fun size(): Long { /* compiled code */ } - - @Generated - @Api4J - fun sizeAsync(): java.util.concurrent.CompletableFuture { /* compiled code */ } - - @Generated - @Api4J - fun sizeBlocking(): Long { /* compiled code */ } -} - - -class Bar { - @JvmSynthetic - @JvmBlocking - @JvmAsync - suspend fun bar(): String { /* compiled code */ } - - @Generated - @Api4J - fun barAsync(): java.util.concurrent.CompletableFuture { /* compiled code */ } - - @Generated - @Api4J - fun barBlocking(): String { /* compiled code */ } - - fun noTrans(): Int { /* compiled code */ } -} -``` +> If you notice any issues or omissions in the documentation, +> feel free to [provide feedback](https://github.com/ForteScarlet/kotlin-suspend-transform-compiler-plugin/issues) anytime! ## Use Cases diff --git a/README_CN.md b/README_CN.md index 59f341a..3f08e7e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -8,9 +8,9 @@ **English** | [简体中文](README_CN.md) -## 概述 +## 这是什么? -用于为挂起函数生成平台兼容函数的 Kotlin 编译器插件。 +这是一个用于为挂起函数生成平台兼容函数的 Kotlin 编译器插件。 ### JVM @@ -182,614 +182,11 @@ class Foo { ## 使用方式 -### 版本说明 - -`0.9.0` 及之前版本使用 `x.y.z` 的命名规则。但由于 Kotlin 编译器可能随版本变化, -这种命名方式无法反映对应的 Kotlin 版本,可能导致混淆。 - -因此,`0.9.0` 之后的版本将采用 `$Kotlin-$plugin` 的命名形式, -例如 `2.0.20-0.9.1`。前半部分为构建所用的 Kotlin 版本,后半部分为插件版本。 - -若版本小于等于 `0.9.0`,可参考以下对照表: - -| Kotlin 版本 | 插件版本 | -|-----------|-------------------------| -| `2.0.0` | `0.8.0-beta1` ~ `0.9.0` | -| `1.9.22` | `0.7.0-beta1` | -| `1.9.21` | `0.6.0` | -| `1.9.10` | `0.5.1` | -| `1.9.0` | `0.5.0` | -| `1.8.21` | `0.3.1` ~ `0.4.0` | - -> [!note] -> 未详细记录各 Kotlin 版本的编译器插件兼容性。 -> 根据经验,次要版本升级(如 `1.8.0` -> `1.9.0`)更可能不兼容, -> 补丁版本(如 `1.9.21` -> `1.9.22`)不兼容概率较低。 - -### Gradle - -**使用 [plugins DSL](https://docs.gradle.org/current/userguide/plugins.html#sec:plugins_block):** - -_build.gradle.kts_ - -```Kotlin -plugins { - kotlin("jvm") version "$KOTLIN_VERSION" // 或 multiplatform - id("love.forte.plugin.suspend-transform") version "$PLUGIN_VERSION" - // 其他... -} - -// 其他... - -// 配置插件 -suspendTransformPlugin { - // 配置 SuspendTransformPluginExtension ... -} -``` - -**使用 [传统插件应用方式](https://docs.gradle.org/current/userguide/plugins.html#sec:old_plugin_application):** - -_build.gradle.kts_ - -```Kotlin -buildscript { - repositories { - mavenCentral() - gradlePluginPortal() - } - dependencies { - classpath("love.forte.plugin.suspend-transform:suspend-transform-plugin-gradle:$GRADLE_PLUGIN_VERSION") - } -} - -plugins { - id("org.jetbrains.kotlin.jvm") // 或 multiplatform? - id("love.forte.plugin.suspend-transform") - // 其他... -} - -// 其他... - -// 配置插件 -suspendTransformPlugin { - // 配置 SuspendTransformPluginExtension ... -} -``` - -## 配置扩展 - -### 启用插件 - -启用 Kotlin 编译器插件。默认值为 `true`。 - -```Kotlin -suspendTransformPlugin { - enabled = true -} -``` - -### 包含默认注解和运行时 - -若需使用我们提供的转换器,需添加 `annotation` 和 `runtime` 依赖。 -可通过配置自动添加: - -```Kotlin -suspendTransformPlugin { - // 包含注解 - // 默认为 `true` - includeAnnotation = true - // 默认值可留空,使用专属默认值 - annotationDependency { - // 默认为 `compileOnly` - configurationName = "compileOnly" - // 默认与插件版本相同 - version = "" - } - - // 包含运行时 - // 默认为 `true` - includeRuntime = true - // 默认值可留空,使用专属默认值 - runtimeDependency { - // 默认为 `implementation` - configurationName = "implementation" - // 默认与插件版本相同 - version = "" - } -} -``` - -也可手动添加依赖: - -```Kotlin -plugin { - kotlin("jvm") version "..." // 以 Kotlin/JVM 为例 - id("love.forte.plugin.suspend-transform") version "2.1.20-0.12.0" -} - -dependencies { - // 注解 - compileOnly("love.forte.plugin.suspend-transform:suspend-transform-annotation:") - // 运行时 - implementation("love.forte.plugin.suspend-transform:suspend-transform-runtime:") -} - -suspendTransformPlugin { - // 禁用自动包含 - includeAnnotation = false - includeRuntime = false -} -``` - -### 添加转换器 - -`Transformer` 用于描述如何转换挂起函数。需添加 `Transformer` 以使插件生效。 - -```Kotlin -suspendTransformPlugin { - // 配置转换器 - transformers { - add(TargetPlatform.JVM) { // this: TransformerSpec - // 配置 TransformerSpec... - } - - addJvm { // this: TransformerSpec - // 配置 TransformerSpec... - } - - // 使用预置的默认转换器 - add(TargetPlatform.JVM, SuspendTransformConfigurations.jvmBlockingTransformer) - - addJvm { // this: TransformerSpec - // 基于现有转换器调整 - from(SuspendTransformConfigurations.jvmBlockingTransformer) - // 进一步配置... - } - } -} -``` - -#### 添加默认转换器 - -我们提供了一些常用实现,可通过配置快速使用。 +请参见 [文档](https://kstcp.forte.love/)。 > [!note] -> 默认 `Transformer` 依赖我们提供的 `annotation` 和 `runtime`,请确保已包含。 - -**JVM 阻塞式** - -```Kotlin -suspendTransformPlugin { - transformers { - // 方式一: - addJvmBlocking() - - // 方式二: - addJvm(SuspendTransformConfigurations.jvmBlockingTransformer) - } -} -``` - -`JvmBlocking` 允许在挂起函数上标记 `@JvmBlocking`,生成 `xxxBlocking` 函数。 - -```Kotlin -class Cat { - @JvmBlocking - suspend fun meow() { - // ... - } - - // 生成: - fun meowBlocking() { - `$runInBlocking$` { meow() } - } -} -``` - -`$runInBlocking$` 基于 `kotlinx.coroutines.runBlocking`。 - -**JVM 异步式** - -```Kotlin -suspendTransformPlugin { - transformers { - // 方式一: - addJvmAsync() - - // 方式二: - addJvm(SuspendTransformConfigurations.jvmAsyncTransformer) - } -} -``` - -`JvmAsync` 允许在挂起函数上标记 `@JvmAsync`,生成 `xxxAsync` 函数。 +> 如果文档有任何问题或遗漏,欢迎随时[反馈](https://github.com/ForteScarlet/kotlin-suspend-transform-compiler-plugin/issues)! -```Kotlin -class Cat { - @JvmBlocking - suspend fun meow(): String = "Meow!" - - // 生成: - fun meowAsync(): CompletableFuture { - `$runInAsync$`(block = { meow() }, scope = this as? CoroutineScope) - } -} -``` - -`block` 是需要执行的原始挂起函数,`scope` 是使用的协程作用域。 - -若当前作用域是 `CoroutineScope`,则优先使用自身。否则内部使用 `GlobalScope`。 - -使用 `GlobalScope` 的原因: -1. 全局性。 -2. 不可见,不会被手动关闭。 -3. 不涉及 IO,无需自定义调度器。 - -若有异议,欢迎提交 issue! - -**JS Promise** - -```Kotlin -suspendTransformPlugin { - transformers { - // 方式一: - addJsPromise() - - // 方式二: - addJs(SuspendTransformConfigurations.jsPromiseTransformer) - } -} -``` - -```Kotlin -class Cat { - @JsPromise - suspend fun meow(): String = "Meow!" - - // 生成: - fun meowAsync(): Promise { - `$runInAsync$`(block = { meow() }, scope = this as? CoroutineScope) - } -} -``` - -#### 使用默认转换器 - -`addJvmBlocking()` 和 `addJvmAsync()` 可以被合并为 `useJvmDefault()`。 - -```Kotlin -suspendTransformPlugin { - transformers { - // 包括 addJvmBlocking() 和 addJvmAsync() - useJvmDefault() - } -} -``` - -`addJsPromise()` 可以被合并为 `useJsDefault()` 。 - -```Kotlin -suspendTransformPlugin { - transformers { - // 包括 addJsPromise() - useJsDefault() - } -} -``` - -`useJvmDefault()` 和 `useJsDefault` 可以被合并为 `useDefault()` 。 - -```Kotlin -suspendTransformPlugin { - transformers { - // 包括 addJvmDefault() 和 addJsPromise() - useDefault() - } -} -``` - -#### 自定义转换器 - -若默认转换器不满足需求,可自定义 `Transformer`,例如完全自定义阻塞逻辑。 - -> 完整自定义实现参考: -> https://github.com/simple-robot/simpler-robot/blob/v4-main/simbot-commons/simbot-common-suspend-runner/src/jvmMain/kotlin/love/forte/simbot/suspendrunner/BlockingRunner.kt - -```Kotlin -suspendTransformPlugin { - // 自定义时可能无需默认注解和运行时 - includeAnnotation = false - includeRuntime = false - - transformer { - // 具体配置见下文 - } -} -``` - -示例:自定义注解 `@JBlock`,通过函数 `inBlock` 执行挂起函数。 - -```Kotlin -// 自定义注解 -annotation class JBlock(...) - -// 自定义顶层转换函数 -fun inBlock(block: suspend () -> T): T { - TODO("你的实现") -} -``` - -假设注解包含以下属性: -- `baseName`: 生成函数的基础名(默认为原函数名) -- `suffix`: 生成函数名的后缀 -- `asProperty`: 将生成函数转为属性(适用于无参数的函数) - -注解定义: - -```Kotlin -annotation class JBlock( - val baseName: String = "", - val suffix: String = "Blocking", - val asProperty: Boolean = false -) -``` - -配置示例: - -```Kotlin -suspendTransformPlugin { - includeAnnotation = false - includeRuntime = false - transformers { - addJvm { - markAnnotation { - // 注解类信息 - classInfo { - packageName = "com.example" - className = "JBlock" - } - - // 属性名映射 - baseNameProperty = "baseName" // 默认为 `baseName` - suffixProperty = "suffix" // 默认为 `suffix` - asPropertyProperty = "asProperty" // 默认为 `asProperty` - - // 默认值需手动配置(编译器无法获取注解默认值) - defaultSuffix = "Blocking" - defaultAsProperty = false - } - } - } -} -``` - -若属性名不同: - -```Kotlin -annotation class JBlock( - val myBaseName: String = "", - val mySuffix: String = "Blocking", - val myAsProperty: Boolean = false -) -``` - -配置调整: - -```Kotlin -baseNameProperty = "myBaseName" -suffixProperty = "mySuffix" -asPropertyProperty = "myAsProperty" -``` - -转换函数配置: - -```Kotlin -transformFunctionInfo { - packageName = "com.example" - functionName = "inBlock" -} - -// 返回类型配置 -transformReturnType = null // 与原函数返回类型相同 -transformReturnTypeGeneric = false // 无泛型 -``` - -注解复制配置示例: - -```Kotlin -addOriginFunctionIncludeAnnotation { - classInfo { - packageName = "kotlin.jvm" - className = "JvmSynthetic" - } - repeatable = false -} - -addSyntheticFunctionIncludeAnnotation { - classInfo { - packageName = "com.example" - className = "JApi" - } - includeProperty = true -} - -addCopyAnnotationExclude { - from(SuspendTransformConfigurations.jvmSyntheticClassInfo) -} -``` - -完整示例: - -代码: - -```Kotlin -annotation class JBlock( - val myBaseName: String = "", - val mySuffix: String = "Blocking", - val myAsProperty: Boolean = false -) - -@RequiresOptIn(message = "Java 接口", level = RequiresOptIn.Level.WARNING) -@Retention(AnnotationRetention.BINARY) -annotation class JApi - -fun inBlock(block: suspend () -> T): T { - TODO("你的实现") -} -``` - -配置: - -```Kotlin -suspendTransformPlugin { - includeAnnotation = false - includeRuntime = false - transformers { - addJvm { - markAnnotation { - classInfo { - packageName = "com.example" - className = "JBlock" - } - - baseNameProperty = "myBaseName" - suffixProperty = "mySuffix" - asPropertyProperty = "myAsProperty" - - defaultSuffix = "Blocking" - defaultAsProperty = false - } - - transformFunctionInfo { - packageName = "com.example" - functionName = "inBlock" - } - - copyAnnotationsToSyntheticFunction = true - copyAnnotationsToSyntheticProperty = true - - addOriginFunctionIncludeAnnotation { - classInfo.from(SuspendTransformConfigurations.jvmSyntheticClassInfo) - repeatable = false - } - - addSyntheticFunctionIncludeAnnotation { - classInfo { - packageName = "com.example" - className = "JApi" - } - includeProperty = true - } - - addCopyAnnotationExclude { - from(SuspendTransformConfigurations.jvmSyntheticClassInfo) - } - } - } -} -``` - -> [!note] -> 同一注解可通过不同属性名复用于多个转换器。例如: -> ```Kotlin -> annotation class JTrans( -> val blockingBaseName: String = "", -> val blockingSuffix: String = "Blocking", -> val blockingAsProperty: Boolean = false, -> -> val asyncBaseName: String = "", -> val asyncSuffix: String = "Async", -> val asyncAsProperty: Boolean = false -> ) -> ``` - -## 注意事项 -### Gradle JVM - -**Gradle JVM** 必须为 JDK11+ - -### K2 - -自 `v0.7.0` 起支持 K2。 - -### JsExport - -若需在 JS 中使用 `@JsExport` 的默认配置: - -_build.gradle.kts_ - -```kotlin -plugins { - // ... -} - -suspendTransformPlugin { - transformers { - addJsPromise { - addCopyAnnotationExclude { - // 生成函数不包含 `@JsExport.Ignore` - from(kotlinJsExportIgnoreClassInfo) - } - } - } -} -``` - -```Kotlin -@file:OptIn(ExperimentalJsExport::class) - -@JsExport -class Foo { - @JsPromise - @JsExport.Ignore - suspend fun run(): Int = ... -} -``` - -## 效果示例 - -**源码:** - -```kotlin -import love.forte.plugin.suspendtrans.annotation.JvmAsync -import love.forte.plugin.suspendtrans.annotation.JvmBlocking - -@JvmBlocking -@JvmAsync -interface Foo { - - suspend fun name(): String - - suspend fun age(def: Int = 5): Int - - @JvmBlocking(asProperty = true) - suspend fun self(): Foo -} - -@JvmBlocking -@JvmAsync -class FooImpl : Foo { - suspend fun size(): Long = 666 - override suspend fun name(): String = "forte" - override suspend fun age(def: Int): Int = def - @JvmBlocking(asProperty = true) // 必须为 'asProperty=true' - override suspend fun self(): FooImpl = this -} - -class Bar { - @JvmBlocking - @JvmAsync - suspend fun bar(): String = "" - - suspend fun noTrans(): Int = 1 -} -``` - -**编译结果(简化版):** - -```kotlin -// 生成代码的详细实现略,参见原文 -``` ## 应用案例 From 5eb72cad1639d78f67126f26a3cdd561945ba5e7 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Fri, 24 Oct 2025 01:01:45 +0800 Subject: [PATCH 3/3] release: v2.2.21-0.13.2 --- .changelog/v2.2.21-0.13.2.md | 1 + buildSrc/src/main/kotlin/IProject.kt | 2 +- docs/src/version.json | 2 +- gradle/libs.versions.toml | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 .changelog/v2.2.21-0.13.2.md diff --git a/.changelog/v2.2.21-0.13.2.md b/.changelog/v2.2.21-0.13.2.md new file mode 100644 index 0000000..c45ebb5 --- /dev/null +++ b/.changelog/v2.2.21-0.13.2.md @@ -0,0 +1 @@ +Kotlin version: `v2.2.21` \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/IProject.kt b/buildSrc/src/main/kotlin/IProject.kt index 531f338..10cf1e6 100644 --- a/buildSrc/src/main/kotlin/IProject.kt +++ b/buildSrc/src/main/kotlin/IProject.kt @@ -32,7 +32,7 @@ object IProject : ProjectDetail() { const val HOMEPAGE = "https://github.com/ForteScarlet/kotlin-suspend-transform-compiler-plugin" // Remember the libs.versions.toml! - val ktVersion = "2.2.20" + val ktVersion = "2.2.21" val pluginVersion = "0.13.2" override val version: String = "$ktVersion-$pluginVersion" diff --git a/docs/src/version.json b/docs/src/version.json index bb9e81c..45ce4c2 100644 --- a/docs/src/version.json +++ b/docs/src/version.json @@ -1 +1 @@ -{"version": "2.2.20-0.13.2"} \ No newline at end of file +{"version": "2.2.21-0.13.2"} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 91daee2..8ef7a59 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlinx-coroutines = "1.8.0" kotlinx-serialization = "1.7.1" google-auto-service = "1.0.1" # Remember the `IProject.ktVersion`! -kotlin = "2.2.20" +kotlin = "2.2.21" # https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-publish-libraries.html#configure-the-project # https://github.com/vanniktech/gradle-maven-publish-plugin