
Enforces structured concurrency for coroutines via compiler checks, static analyzers, IDE inspections, lint rules and annotations — compile-time errors, quick fixes, tool window, zero runtime overhead.
A comprehensive toolkit for enforcing structured concurrency in Kotlin Coroutines, inspired by Swift Concurrency. It provides multiple layers of protection through compile-time checks and static analysis.
| Module | Status | Documentation |
|---|---|---|
| Compiler Plugin | ✅ Complete (15 FIR checkers) | gradle-plugin/README.md |
| Gradle Plugin | ✅ Complete (profiles incl. Spring/Ktor) | gradle-plugin/README.md |
| Detekt Rules | ✅ Complete (41 rules) | detekt-rules/README.md |
| Android Lint | ✅ Complete (33 detectors) | lint-rules/README.md |
| IntelliJ Plugin | ✅ Complete (36 inspections, tool window, Flow Analyzer) | intellij-plugin/README.md |
| Annotations | ✅ Complete | annotations/README.md |
| Runtime family | 🧪 Experimental (0.x, alpha) | coroutines-runtime/README.md |
| Sample | ✅ Compilation examples per rule | compilation/README |
| Sample (Detekt) | ✅ Detekt rule validation (30+ examples) | sample-detekt/README.md |
| Sample (Severity) | ✅ Severity DSL demo (standalone build) | sample-severity/README.md |
| Kotlin Coroutines Agent Skill | ✅ AI/agent guidance | docs/KOTLIN_COROUTINES_SKILL.md |
Kotlin Coroutines are powerful but can be misused, leading to:
GlobalScope
runBlocking in suspend functionsCancellationException
This toolkit enforces structured concurrency best practices through:
| Module | Purpose | When |
|---|---|---|
compiler |
K2/FIR Compiler Plugin | Compile-time errors |
detekt-rules |
Detekt custom rules | Static analysis |
lint-rules |
Android Lint rules | Android projects |
intellij-plugin |
IntelliJ/Android Studio Plugin | Real-time IDE analysis |
annotations |
@StructuredScope annotation |
Runtime/Compile |
gradle-plugin |
Gradle integration | Build configuration |
Optional libraries you depend on at runtime. They live under
coroutines-runtime/ and are released independently of the
toolkit above (see Release trains).
| Module | Purpose | When |
|---|---|---|
runtime |
Compliant helpers (experimental, 0.x) | Runtime |
runtime-test |
Test doubles for runtime
|
Tests |
runtime-tracing |
Perfetto trace sink for JVM/desktop | Profiling |
runtime-benchmark |
Tracing overhead benchmark (not published) | Internal |
@StructuredScope annotationviewModelScope, lifecycleScope, rememberCoroutineScope()
1.2.x targets Kotlin 2.4.0+; 1.1.x works with Kotlin 2.3+sample-detekt module with one example per Detekt rule (
./gradlew :sample-detekt:detekt)51 documented patterns (v1.0.0) — single source of truth: docs/rule-codes.yml.
Full descriptions, examples, and suppression IDs: docs/BEST_PRACTICES_COROUTINES.md, docs/SUPPRESSING_RULES.md.
| Layer | Count | Coverage |
|---|---|---|
| Compiler (FIR) | 15 checkers | 9 errors + 6 warnings (default severities); INTEROP_001/002 in K2 |
| Detekt | 41 rules | KMP source-set aware; backend + Compose profiles |
| Android Lint | 33 detectors | Android/Compose + shared compiler rules |
| IntelliJ | 36 inspections | Real-time analysis, quick fixes, Flow Chain Analyzer |
v1.0 highlights: COMPOSE_002/003, TEST_005/006, FLOW_009/011, INTEROP_003/004, DEBUG_001 (opt-in).
Platform guides: Compose · KMP · Ktor · Spring.
Per-module rule lists and configuration examples: detekt-rules · lint-rules · intellij-plugin.
Release:
1.2.1fixes Detekt false positives inCANCEL_001,CANCEL_003,RUNBLOCK_001andSCOPE_002/SCOPE_003(#155).1.2.0addedEXCEPT_004(MisplacedTryCatchInScope) and real"disabled"/"error"/"warning"severity handling. See the CHANGELOG.
The toolkit artifacts use PROJECT_VERSION and are released from a v<PROJECT_VERSION>
tag. The runtime family is independent: structured-coroutines-runtime,
structured-coroutines-runtime-test, and structured-coroutines-runtime-tracing
always share RUNTIME_VERSION and are released only from a
runtime-v<RUNTIME_VERSION> tag. A release workflow validates the tag against the
version in gradle.properties before it runs publication, so published Maven Central
coordinates cannot be selected by a mismatched tag.
Release the runtime family with gh release create runtime-v<RUNTIME_VERSION> --prerelease
(alphas use a -ALPHAnn suffix). Maven Central versions are immutable, so verify with
./gradlew publishRuntimeToMavenCentral --dry-run first.
// settings.gradle.kts
pluginManagement {
repositories {
mavenLocal()
mavenCentral()
gradlePluginPortal()
}
}
// build.gradle.kts
plugins {
kotlin("jvm") version "2.3.0"
id("io.github.santimattius.structured-coroutines") version "1.2.1"
}
dependencies {
implementation("io.github.santimattius:structured-coroutines-annotations:1.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}// build.gradle.kts
plugins {
id("io.gitlab.arturbosch.detekt") version "1.23.7"
}
dependencies {
detektPlugins("io.github.santimattius:structured-coroutines-detekt-rules:1.2.1")
}// build.gradle.kts (Android project)
dependencies {
lintChecks("io.github.santimattius:structured-coroutines-lint-rules:1.2.1")
}Note: Android Lint Rules are only available for Android projects. For multiplatform projects, use the Compiler Plugin or Detekt Rules.
Versioned independently from the toolkit (RUNTIME_VERSION). Check the
latest release for the current
runtime-v* version.
// build.gradle.kts
dependencies {
implementation("io.github.santimattius:structured-coroutines-runtime:<runtime-version>")
testImplementation("io.github.santimattius:structured-coroutines-runtime-test:<runtime-version>")
// JVM/desktop only: Perfetto trace sink
implementation("io.github.santimattius:structured-coroutines-runtime-tracing:<runtime-version>")
}See coroutines-runtime/README.md for the module map and usage.
Install from JetBrains Marketplace or build from source:
# Build the plugin
./gradlew :intellij-plugin:build
# Run IDE sandbox for testing
./gradlew :intellij-plugin:runIdeOr install manually:
intellij-plugin/build/distributions/intellij-plugin-*.zip
plugins {
kotlin("multiplatform") version "2.3.0"
id("io.github.santimattius.structured-coroutines") version "1.2.1"
}
kotlin {
jvm()
iosArm64()
iosSimulatorArm64()
js(IR) { browser(); nodejs() }
sourceSets {
commonMain {
dependencies {
implementation("io.github.santimattius:structured-coroutines-annotations:1.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}
}
}
}import io.github.santimattius.structured.annotations.StructuredScope
// Function parameter
fun loadData(@StructuredScope scope: CoroutineScope) {
scope.launch { fetchData() }
}
// Constructor injection
class UserService(
@property:StructuredScope
private val scope: CoroutineScope
) {
fun fetchUser(id: String) {
scope.launch { /* ... */ }
}
}
// Class property
class Repository {
@StructuredScope
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
fun fetchData() {
scope.launch { /* ... */ }
}
}The plugin automatically recognizes lifecycle-aware framework scopes:
// ✅ Android ViewModel - No annotation needed
class MyViewModel : ViewModel() {
fun load() {
viewModelScope.launch { fetchData() }
}
}
// ✅ Android Activity/Fragment - No annotation needed
class MyActivity : AppCompatActivity() {
fun load() {
lifecycleScope.launch { fetchData() }
}
}
// ✅ Jetpack Compose - No annotation needed
@Composable
fun MyScreen() {
val scope = rememberCoroutineScope()
Button(onClick = { scope.launch { doWork() } }) {
Text("Click")
}
}Recognized Framework Scopes:
| Scope | Framework | Package |
|---|---|---|
viewModelScope |
Android ViewModel | androidx.lifecycle |
lifecycleScope |
Android Lifecycle | androidx.lifecycle |
rememberCoroutineScope() |
Jetpack Compose | androidx.compose.runtime |
// ❌ ERROR
GlobalScope.launch { work() }
// ✅ Use framework scopes or @StructuredScope
viewModelScope.launch { work() }// ❌ ERROR
CoroutineScope(Dispatchers.IO).launch { work() }
// ✅ Use a managed scope
class MyClass(@StructuredScope private val scope: CoroutineScope) {
fun doWork() = scope.launch { work() }
}// ❌ ERROR
suspend fun bad() {
runBlocking { delay(1000) }
}
// ✅ Just suspend
suspend fun good() {
delay(1000)
}// ❌ ERROR
scope.launch(Job()) { work() }
scope.launch(SupervisorJob()) { work() }
// ✅ Use supervisorScope
suspend fun process() = supervisorScope {
launch { task1() }
launch { task2() }
}// ❌ ERROR
class MyError : CancellationException()
// ✅ Use regular Exception
class MyError : Exception()// ⚠️ WARNING - May swallow cancellation
suspend fun bad() {
try {
work()
} catch (e: Exception) {
log(e)
}
}
// ✅ Handle separately
suspend fun good() {
try {
work()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
log(e)
}
}// ⚠️ WARNING - May not execute
try {
work()
} finally {
saveToDb() // Suspend call
}
// ✅ Protected
try {
work()
} finally {
withContext(NonCancellable) {
saveToDb()
}
}// ⚠️ Detekt WARNING
scope.launch {
Thread.sleep(1000) // Blocking!
inputStream.read() // Blocking I/O!
jdbcStatement.execute() // Blocking JDBC!
}
// ✅ Use non-blocking alternatives
scope.launch {
delay(1000)
withContext(Dispatchers.IO) {
inputStream.read()
}
}// ⚠️ Detekt WARNING - Slow test
@Test
fun test() = runBlocking {
delay(1000) // Real delay!
}
// ✅ Fast test with virtual time
@Test
fun test() = runTest {
delay(1000) // Instant!
}// ⚠️ Detekt WARNING - Can't cancel
suspend fun process(items: List<Item>) {
for (item in items) {
heavyWork(item) // No cooperation point
}
}
// ✅ Can be cancelled
suspend fun process(items: List<Item>) {
for (item in items) {
ensureActive()
heavyWork(item)
}
}structured-coroutines:
# Compiler Plugin Rules
GlobalScopeUsage:
active: true
severity: error
InlineCoroutineScope:
active: true
severity: error
RunBlockingInSuspend:
active: true
severity: warning
DispatchersUnconfined:
active: true
severity: warning
CancellationExceptionSubclass:
active: true
severity: error
# Detekt-Only Rules
BlockingCallInCoroutine:
active: true
excludes: [ 'commonMain', 'iosMain' ] # JVM-only
RunBlockingWithDelayInTest:
active: true
ExternalScopeLaunch:
active: true
LoopWithoutYield:
active: true📖 Full Documentation: Detekt Rules Documentation
structured-coroutines/
├── annotations/ # @StructuredScope (Multiplatform)
├── compiler/ # K2/FIR Compiler Plugin
│ ├── UnstructuredLaunchChecker
│ ├── RunBlockingInSuspendChecker
│ ├── JobInBuilderContextChecker
│ ├── DispatchersUnconfinedChecker
│ ├── CancellationExceptionSubclassChecker
│ ├── SuspendInFinallyChecker
│ ├── CancellationExceptionSwallowedChecker
│ ├── UnusedDeferredChecker
│ ├── RedundantLaunchInCoroutineScopeChecker
│ ├── LoopWithoutYieldChecker
│ ├── MisplacedTryCatchInScopeChecker
│ └── ... (13 checker classes; 15 configurable rules)
├── detekt-rules/ # Detekt Custom Rules
│ ├── GlobalScopeUsageRule
│ ├── InlineCoroutineScopeRule
│ ├── RunBlockingInSuspendRule
│ ├── DispatchersUnconfinedRule
│ ├── CancellationExceptionSubclassRule
│ ├── BlockingCallInCoroutineRule
│ ├── RunBlockingWithDelayInTestRule
│ ├── ExternalScopeLaunchRule
│ ├── LoopWithoutYieldRule
│ ├── ScopeReuseAfterCancelRule
│ ├── ChannelNotClosedRule
│ ├── ConsumeEachMultipleConsumersRule
│ ├── MisplacedTryCatchInScopeRule
│ └── ... (41 rules total)
├── lint-rules/ # Android Lint Rules
│ ├── GlobalScopeUsageDetector
│ ├── MainDispatcherMisuseDetector
│ ├── ViewModelScopeLeakDetector
│ └── ... (33 detectors total)
├── intellij-plugin/ # IntelliJ/Android Studio Plugin
│ ├── inspections/ # 36 real-time inspections (incl. LoopWithoutYield, LifecycleAwareFlowCollection)
│ ├── quickfixes/ # 12 automatic quick fixes
│ ├── intentions/ # 6 refactoring intentions (incl. Convert to runTest)
│ ├── guttericons/ # Scope & dispatcher visualization
│ └── view/ # Tool window (findings list, runner, tree visitor)
├── gradle-plugin/ # Gradle Integration
├── coroutines-runtime/ # Runtime family (see coroutines-runtime/README.md)
│ ├── runtime/ # Compliant helpers (Multiplatform, experimental, 0.x)
│ ├── runtime-test/ # Test doubles for :runtime (TestDispatcherProvider)
│ ├── runtime-tracing/ # Perfetto trace sink for JVM/desktop
│ └── runtime-benchmark/ # Opt-in overhead benchmark (not published)
├── sample/ # Examples
│ └── compilation/ # One example per compiler rule (errors & warnings)
└── kotlin-coroutines-skill/ # AI/agent skill for coroutine best practices
| Platform | Compiler Plugin | Detekt Rules | Android Lint | IDE Plugin |
|---|---|---|---|---|
| JVM | ✅ | ✅ | ❌ | ✅ |
| Android | ✅ | ✅ | ✅ | ✅ |
| iOS | ✅ | ✅ | ❌ | ✅ |
| macOS | ✅ | ✅ | ❌ | ✅ |
| watchOS | ✅ | ✅ | ❌ | ✅ |
| tvOS | ✅ | ✅ | ❌ | ✅ |
| Linux | ✅ | ✅ | ❌ | ✅ |
| Windows | ✅ | ✅ | ❌ | ✅ |
| JS | ✅ | ✅ | ❌ | ✅ |
| WASM | ✅ | ✅ | ❌ | ✅ |
# Publish locally
./gradlew publishToMavenLocal
# Run compiler plugin tests
./gradlew :compiler:test
# Run detekt rules tests
./gradlew :detekt-rules:test
# Run all tests
./gradlew test| Approach | When | Rules (approx.) | CI | Real-time | Platform |
|---|---|---|---|---|---|
| Compiler Plugin | Compile | 15 checkers | ✅ | ❌ | All (KMP) |
| Detekt Rules | Analysis | 41 rules | ✅ | ❌ | All (KMP) |
| Android Lint Rules | Analysis | 33 detectors | ✅ | ❌ | Android only |
| IDE Plugin | Editing | 36 inspections | ❌ | ✅ | All |
| Combined (All) | All | 51 patterns | ✅ | ✅ | - |
| Code Review | Manual | — | ❌ | ❌ | - |
| Runtime | Late | — | ❌ | ❌ | - |
Notes:
docs/rule-codes.yml
1.2.x; Kotlin 2.3.0+ for 1.1.x
Copyright 2026 Santiago Mattiauda
Licensed under the Apache License, Version 2.0
Contributions welcome! See CONTRIBUTING.md for setup, module layout, and how to add rules.
This project follows the Code of Conduct. Security issues should be reported per SECURITY.md, not via public issues.
git clone https://github.com/santimattius/structured-coroutines.git
cd structured-coroutines
./gradlew publishToMavenLocal
./gradlew testEach module contains its own detailed documentation:
| Module | Documentation | Description |
|---|---|---|
| Gradle Plugin | gradle-plugin/README.md | Installation, configuration, severity settings |
| Detekt Rules | detekt-rules/README.md | 41 rules with examples and configuration |
| Android Lint | lint-rules/README.md | 33 detectors, Android/Compose-specific detection |
| IntelliJ Plugin | intellij-plugin/README.md | 36 inspections, quick fixes, Flow Analyzer, K2 support |
| Annotations | annotations/README.md | @StructuredScope usage and multiplatform support |
| Runtime family | coroutines-runtime/README.md | Overview, module map, build and publishing for the four runtime modules |
| Runtime | runtime/README.md | Experimental helpers that replace flagged patterns (0.x, opt-in) |
| Runtime Test | runtime-test/README.md |
TestDispatcherProvider for :runtime
|
| Runtime Tracing (JVM) | runtime-tracing/README.md | Perfetto trace sink for JVM/desktop |
| Runtime Benchmark | runtime-benchmark/README.md | Opt-in TracingDispatcher overhead benchmark (not published) |
| Compiler | compiler/README.md | K2/FIR checker implementation details |
| Sample (compilation) | compilation/README.md | One example per compiler rule (errors and warnings) |
| Sample (Detekt) | sample-detekt/README.md | One example per Detekt rule; run :sample-detekt:detekt to validate |
| Sample (Severity) | sample-severity/README.md | Severity DSL demo (standalone build); run ./gradlew -p sample-severity compileKotlin
|
| Kotlin Coroutines Agent Skill | docs/KOTLIN_COROUTINES_SKILL.md | AI/agent skill and decision resource: 32 practices, strict rules, triage playbook (v2.0.0) |
| Best Practices | docs/BEST_PRACTICES_COROUTINES.md | Canonical guide to coroutine good/bad practices |
| Decision Guide | docs/DECISION_GUIDE.md | Quick-reference: launch vs async, scope, dispatcher, timeout, Flow |
| Suppressing Rules | docs/SUPPRESSING_RULES.md | Unified suppression IDs (Compiler, Detekt, Lint, IntelliJ) by rule code |
All user-facing text is externalized for localization:
compiler/src/main/resources/messages/CompilerBundle*.properties. Default language is English
so builds and CI are predictable. To use Spanish (or the JVM default locale), set the system
property:
-Dstructured.coroutines.compiler.locale=es (e.g. in gradle.properties:
org.gradle.jvmargs=... -Dstructured.coroutines.compiler.locale=es)-Dstructured.coroutines.compiler.locale=default
StructuredCoroutinesBundle.properties; the IDE uses the platform
language. Spanish: StructuredCoroutinesBundle_es.properties.To add a new language, add a _<locale> properties file (e.g. CompilerBundle_de.properties) with
the same keys.
A comprehensive toolkit for enforcing structured concurrency in Kotlin Coroutines, inspired by Swift Concurrency. It provides multiple layers of protection through compile-time checks and static analysis.
| Module | Status | Documentation |
|---|---|---|
| Compiler Plugin | ✅ Complete (15 FIR checkers) | gradle-plugin/README.md |
| Gradle Plugin | ✅ Complete (profiles incl. Spring/Ktor) | gradle-plugin/README.md |
| Detekt Rules | ✅ Complete (41 rules) | detekt-rules/README.md |
| Android Lint | ✅ Complete (33 detectors) | lint-rules/README.md |
| IntelliJ Plugin | ✅ Complete (36 inspections, tool window, Flow Analyzer) | intellij-plugin/README.md |
| Annotations | ✅ Complete | annotations/README.md |
| Runtime family | 🧪 Experimental (0.x, alpha) | coroutines-runtime/README.md |
| Sample | ✅ Compilation examples per rule | compilation/README |
| Sample (Detekt) | ✅ Detekt rule validation (30+ examples) | sample-detekt/README.md |
| Sample (Severity) | ✅ Severity DSL demo (standalone build) | sample-severity/README.md |
| Kotlin Coroutines Agent Skill | ✅ AI/agent guidance | docs/KOTLIN_COROUTINES_SKILL.md |
Kotlin Coroutines are powerful but can be misused, leading to:
GlobalScope
runBlocking in suspend functionsCancellationException
This toolkit enforces structured concurrency best practices through:
| Module | Purpose | When |
|---|---|---|
compiler |
K2/FIR Compiler Plugin | Compile-time errors |
detekt-rules |
Detekt custom rules | Static analysis |
lint-rules |
Android Lint rules | Android projects |
intellij-plugin |
IntelliJ/Android Studio Plugin | Real-time IDE analysis |
annotations |
@StructuredScope annotation |
Runtime/Compile |
gradle-plugin |
Gradle integration | Build configuration |
Optional libraries you depend on at runtime. They live under
coroutines-runtime/ and are released independently of the
toolkit above (see Release trains).
| Module | Purpose | When |
|---|---|---|
runtime |
Compliant helpers (experimental, 0.x) | Runtime |
runtime-test |
Test doubles for runtime
|
Tests |
runtime-tracing |
Perfetto trace sink for JVM/desktop | Profiling |
runtime-benchmark |
Tracing overhead benchmark (not published) | Internal |
@StructuredScope annotationviewModelScope, lifecycleScope, rememberCoroutineScope()
1.2.x targets Kotlin 2.4.0+; 1.1.x works with Kotlin 2.3+sample-detekt module with one example per Detekt rule (
./gradlew :sample-detekt:detekt)51 documented patterns (v1.0.0) — single source of truth: docs/rule-codes.yml.
Full descriptions, examples, and suppression IDs: docs/BEST_PRACTICES_COROUTINES.md, docs/SUPPRESSING_RULES.md.
| Layer | Count | Coverage |
|---|---|---|
| Compiler (FIR) | 15 checkers | 9 errors + 6 warnings (default severities); INTEROP_001/002 in K2 |
| Detekt | 41 rules | KMP source-set aware; backend + Compose profiles |
| Android Lint | 33 detectors | Android/Compose + shared compiler rules |
| IntelliJ | 36 inspections | Real-time analysis, quick fixes, Flow Chain Analyzer |
v1.0 highlights: COMPOSE_002/003, TEST_005/006, FLOW_009/011, INTEROP_003/004, DEBUG_001 (opt-in).
Platform guides: Compose · KMP · Ktor · Spring.
Per-module rule lists and configuration examples: detekt-rules · lint-rules · intellij-plugin.
Release:
1.2.1fixes Detekt false positives inCANCEL_001,CANCEL_003,RUNBLOCK_001andSCOPE_002/SCOPE_003(#155).1.2.0addedEXCEPT_004(MisplacedTryCatchInScope) and real"disabled"/"error"/"warning"severity handling. See the CHANGELOG.
The toolkit artifacts use PROJECT_VERSION and are released from a v<PROJECT_VERSION>
tag. The runtime family is independent: structured-coroutines-runtime,
structured-coroutines-runtime-test, and structured-coroutines-runtime-tracing
always share RUNTIME_VERSION and are released only from a
runtime-v<RUNTIME_VERSION> tag. A release workflow validates the tag against the
version in gradle.properties before it runs publication, so published Maven Central
coordinates cannot be selected by a mismatched tag.
Release the runtime family with gh release create runtime-v<RUNTIME_VERSION> --prerelease
(alphas use a -ALPHAnn suffix). Maven Central versions are immutable, so verify with
./gradlew publishRuntimeToMavenCentral --dry-run first.
// settings.gradle.kts
pluginManagement {
repositories {
mavenLocal()
mavenCentral()
gradlePluginPortal()
}
}
// build.gradle.kts
plugins {
kotlin("jvm") version "2.3.0"
id("io.github.santimattius.structured-coroutines") version "1.2.1"
}
dependencies {
implementation("io.github.santimattius:structured-coroutines-annotations:1.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}// build.gradle.kts
plugins {
id("io.gitlab.arturbosch.detekt") version "1.23.7"
}
dependencies {
detektPlugins("io.github.santimattius:structured-coroutines-detekt-rules:1.2.1")
}// build.gradle.kts (Android project)
dependencies {
lintChecks("io.github.santimattius:structured-coroutines-lint-rules:1.2.1")
}Note: Android Lint Rules are only available for Android projects. For multiplatform projects, use the Compiler Plugin or Detekt Rules.
Versioned independently from the toolkit (RUNTIME_VERSION). Check the
latest release for the current
runtime-v* version.
// build.gradle.kts
dependencies {
implementation("io.github.santimattius:structured-coroutines-runtime:<runtime-version>")
testImplementation("io.github.santimattius:structured-coroutines-runtime-test:<runtime-version>")
// JVM/desktop only: Perfetto trace sink
implementation("io.github.santimattius:structured-coroutines-runtime-tracing:<runtime-version>")
}See coroutines-runtime/README.md for the module map and usage.
Install from JetBrains Marketplace or build from source:
# Build the plugin
./gradlew :intellij-plugin:build
# Run IDE sandbox for testing
./gradlew :intellij-plugin:runIdeOr install manually:
intellij-plugin/build/distributions/intellij-plugin-*.zip
plugins {
kotlin("multiplatform") version "2.3.0"
id("io.github.santimattius.structured-coroutines") version "1.2.1"
}
kotlin {
jvm()
iosArm64()
iosSimulatorArm64()
js(IR) { browser(); nodejs() }
sourceSets {
commonMain {
dependencies {
implementation("io.github.santimattius:structured-coroutines-annotations:1.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}
}
}
}import io.github.santimattius.structured.annotations.StructuredScope
// Function parameter
fun loadData(@StructuredScope scope: CoroutineScope) {
scope.launch { fetchData() }
}
// Constructor injection
class UserService(
@property:StructuredScope
private val scope: CoroutineScope
) {
fun fetchUser(id: String) {
scope.launch { /* ... */ }
}
}
// Class property
class Repository {
@StructuredScope
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
fun fetchData() {
scope.launch { /* ... */ }
}
}The plugin automatically recognizes lifecycle-aware framework scopes:
// ✅ Android ViewModel - No annotation needed
class MyViewModel : ViewModel() {
fun load() {
viewModelScope.launch { fetchData() }
}
}
// ✅ Android Activity/Fragment - No annotation needed
class MyActivity : AppCompatActivity() {
fun load() {
lifecycleScope.launch { fetchData() }
}
}
// ✅ Jetpack Compose - No annotation needed
@Composable
fun MyScreen() {
val scope = rememberCoroutineScope()
Button(onClick = { scope.launch { doWork() } }) {
Text("Click")
}
}Recognized Framework Scopes:
| Scope | Framework | Package |
|---|---|---|
viewModelScope |
Android ViewModel | androidx.lifecycle |
lifecycleScope |
Android Lifecycle | androidx.lifecycle |
rememberCoroutineScope() |
Jetpack Compose | androidx.compose.runtime |
// ❌ ERROR
GlobalScope.launch { work() }
// ✅ Use framework scopes or @StructuredScope
viewModelScope.launch { work() }// ❌ ERROR
CoroutineScope(Dispatchers.IO).launch { work() }
// ✅ Use a managed scope
class MyClass(@StructuredScope private val scope: CoroutineScope) {
fun doWork() = scope.launch { work() }
}// ❌ ERROR
suspend fun bad() {
runBlocking { delay(1000) }
}
// ✅ Just suspend
suspend fun good() {
delay(1000)
}// ❌ ERROR
scope.launch(Job()) { work() }
scope.launch(SupervisorJob()) { work() }
// ✅ Use supervisorScope
suspend fun process() = supervisorScope {
launch { task1() }
launch { task2() }
}// ❌ ERROR
class MyError : CancellationException()
// ✅ Use regular Exception
class MyError : Exception()// ⚠️ WARNING - May swallow cancellation
suspend fun bad() {
try {
work()
} catch (e: Exception) {
log(e)
}
}
// ✅ Handle separately
suspend fun good() {
try {
work()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
log(e)
}
}// ⚠️ WARNING - May not execute
try {
work()
} finally {
saveToDb() // Suspend call
}
// ✅ Protected
try {
work()
} finally {
withContext(NonCancellable) {
saveToDb()
}
}// ⚠️ Detekt WARNING
scope.launch {
Thread.sleep(1000) // Blocking!
inputStream.read() // Blocking I/O!
jdbcStatement.execute() // Blocking JDBC!
}
// ✅ Use non-blocking alternatives
scope.launch {
delay(1000)
withContext(Dispatchers.IO) {
inputStream.read()
}
}// ⚠️ Detekt WARNING - Slow test
@Test
fun test() = runBlocking {
delay(1000) // Real delay!
}
// ✅ Fast test with virtual time
@Test
fun test() = runTest {
delay(1000) // Instant!
}// ⚠️ Detekt WARNING - Can't cancel
suspend fun process(items: List<Item>) {
for (item in items) {
heavyWork(item) // No cooperation point
}
}
// ✅ Can be cancelled
suspend fun process(items: List<Item>) {
for (item in items) {
ensureActive()
heavyWork(item)
}
}structured-coroutines:
# Compiler Plugin Rules
GlobalScopeUsage:
active: true
severity: error
InlineCoroutineScope:
active: true
severity: error
RunBlockingInSuspend:
active: true
severity: warning
DispatchersUnconfined:
active: true
severity: warning
CancellationExceptionSubclass:
active: true
severity: error
# Detekt-Only Rules
BlockingCallInCoroutine:
active: true
excludes: [ 'commonMain', 'iosMain' ] # JVM-only
RunBlockingWithDelayInTest:
active: true
ExternalScopeLaunch:
active: true
LoopWithoutYield:
active: true📖 Full Documentation: Detekt Rules Documentation
structured-coroutines/
├── annotations/ # @StructuredScope (Multiplatform)
├── compiler/ # K2/FIR Compiler Plugin
│ ├── UnstructuredLaunchChecker
│ ├── RunBlockingInSuspendChecker
│ ├── JobInBuilderContextChecker
│ ├── DispatchersUnconfinedChecker
│ ├── CancellationExceptionSubclassChecker
│ ├── SuspendInFinallyChecker
│ ├── CancellationExceptionSwallowedChecker
│ ├── UnusedDeferredChecker
│ ├── RedundantLaunchInCoroutineScopeChecker
│ ├── LoopWithoutYieldChecker
│ ├── MisplacedTryCatchInScopeChecker
│ └── ... (13 checker classes; 15 configurable rules)
├── detekt-rules/ # Detekt Custom Rules
│ ├── GlobalScopeUsageRule
│ ├── InlineCoroutineScopeRule
│ ├── RunBlockingInSuspendRule
│ ├── DispatchersUnconfinedRule
│ ├── CancellationExceptionSubclassRule
│ ├── BlockingCallInCoroutineRule
│ ├── RunBlockingWithDelayInTestRule
│ ├── ExternalScopeLaunchRule
│ ├── LoopWithoutYieldRule
│ ├── ScopeReuseAfterCancelRule
│ ├── ChannelNotClosedRule
│ ├── ConsumeEachMultipleConsumersRule
│ ├── MisplacedTryCatchInScopeRule
│ └── ... (41 rules total)
├── lint-rules/ # Android Lint Rules
│ ├── GlobalScopeUsageDetector
│ ├── MainDispatcherMisuseDetector
│ ├── ViewModelScopeLeakDetector
│ └── ... (33 detectors total)
├── intellij-plugin/ # IntelliJ/Android Studio Plugin
│ ├── inspections/ # 36 real-time inspections (incl. LoopWithoutYield, LifecycleAwareFlowCollection)
│ ├── quickfixes/ # 12 automatic quick fixes
│ ├── intentions/ # 6 refactoring intentions (incl. Convert to runTest)
│ ├── guttericons/ # Scope & dispatcher visualization
│ └── view/ # Tool window (findings list, runner, tree visitor)
├── gradle-plugin/ # Gradle Integration
├── coroutines-runtime/ # Runtime family (see coroutines-runtime/README.md)
│ ├── runtime/ # Compliant helpers (Multiplatform, experimental, 0.x)
│ ├── runtime-test/ # Test doubles for :runtime (TestDispatcherProvider)
│ ├── runtime-tracing/ # Perfetto trace sink for JVM/desktop
│ └── runtime-benchmark/ # Opt-in overhead benchmark (not published)
├── sample/ # Examples
│ └── compilation/ # One example per compiler rule (errors & warnings)
└── kotlin-coroutines-skill/ # AI/agent skill for coroutine best practices
| Platform | Compiler Plugin | Detekt Rules | Android Lint | IDE Plugin |
|---|---|---|---|---|
| JVM | ✅ | ✅ | ❌ | ✅ |
| Android | ✅ | ✅ | ✅ | ✅ |
| iOS | ✅ | ✅ | ❌ | ✅ |
| macOS | ✅ | ✅ | ❌ | ✅ |
| watchOS | ✅ | ✅ | ❌ | ✅ |
| tvOS | ✅ | ✅ | ❌ | ✅ |
| Linux | ✅ | ✅ | ❌ | ✅ |
| Windows | ✅ | ✅ | ❌ | ✅ |
| JS | ✅ | ✅ | ❌ | ✅ |
| WASM | ✅ | ✅ | ❌ | ✅ |
# Publish locally
./gradlew publishToMavenLocal
# Run compiler plugin tests
./gradlew :compiler:test
# Run detekt rules tests
./gradlew :detekt-rules:test
# Run all tests
./gradlew test| Approach | When | Rules (approx.) | CI | Real-time | Platform |
|---|---|---|---|---|---|
| Compiler Plugin | Compile | 15 checkers | ✅ | ❌ | All (KMP) |
| Detekt Rules | Analysis | 41 rules | ✅ | ❌ | All (KMP) |
| Android Lint Rules | Analysis | 33 detectors | ✅ | ❌ | Android only |
| IDE Plugin | Editing | 36 inspections | ❌ | ✅ | All |
| Combined (All) | All | 51 patterns | ✅ | ✅ | - |
| Code Review | Manual | — | ❌ | ❌ | - |
| Runtime | Late | — | ❌ | ❌ | - |
Notes:
docs/rule-codes.yml
1.2.x; Kotlin 2.3.0+ for 1.1.x
Copyright 2026 Santiago Mattiauda
Licensed under the Apache License, Version 2.0
Contributions welcome! See CONTRIBUTING.md for setup, module layout, and how to add rules.
This project follows the Code of Conduct. Security issues should be reported per SECURITY.md, not via public issues.
git clone https://github.com/santimattius/structured-coroutines.git
cd structured-coroutines
./gradlew publishToMavenLocal
./gradlew testEach module contains its own detailed documentation:
| Module | Documentation | Description |
|---|---|---|
| Gradle Plugin | gradle-plugin/README.md | Installation, configuration, severity settings |
| Detekt Rules | detekt-rules/README.md | 41 rules with examples and configuration |
| Android Lint | lint-rules/README.md | 33 detectors, Android/Compose-specific detection |
| IntelliJ Plugin | intellij-plugin/README.md | 36 inspections, quick fixes, Flow Analyzer, K2 support |
| Annotations | annotations/README.md | @StructuredScope usage and multiplatform support |
| Runtime family | coroutines-runtime/README.md | Overview, module map, build and publishing for the four runtime modules |
| Runtime | runtime/README.md | Experimental helpers that replace flagged patterns (0.x, opt-in) |
| Runtime Test | runtime-test/README.md |
TestDispatcherProvider for :runtime
|
| Runtime Tracing (JVM) | runtime-tracing/README.md | Perfetto trace sink for JVM/desktop |
| Runtime Benchmark | runtime-benchmark/README.md | Opt-in TracingDispatcher overhead benchmark (not published) |
| Compiler | compiler/README.md | K2/FIR checker implementation details |
| Sample (compilation) | compilation/README.md | One example per compiler rule (errors and warnings) |
| Sample (Detekt) | sample-detekt/README.md | One example per Detekt rule; run :sample-detekt:detekt to validate |
| Sample (Severity) | sample-severity/README.md | Severity DSL demo (standalone build); run ./gradlew -p sample-severity compileKotlin
|
| Kotlin Coroutines Agent Skill | docs/KOTLIN_COROUTINES_SKILL.md | AI/agent skill and decision resource: 32 practices, strict rules, triage playbook (v2.0.0) |
| Best Practices | docs/BEST_PRACTICES_COROUTINES.md | Canonical guide to coroutine good/bad practices |
| Decision Guide | docs/DECISION_GUIDE.md | Quick-reference: launch vs async, scope, dispatcher, timeout, Flow |
| Suppressing Rules | docs/SUPPRESSING_RULES.md | Unified suppression IDs (Compiler, Detekt, Lint, IntelliJ) by rule code |
All user-facing text is externalized for localization:
compiler/src/main/resources/messages/CompilerBundle*.properties. Default language is English
so builds and CI are predictable. To use Spanish (or the JVM default locale), set the system
property:
-Dstructured.coroutines.compiler.locale=es (e.g. in gradle.properties:
org.gradle.jvmargs=... -Dstructured.coroutines.compiler.locale=es)-Dstructured.coroutines.compiler.locale=default
StructuredCoroutinesBundle.properties; the IDE uses the platform
language. Spanish: StructuredCoroutinesBundle_es.properties.To add a new language, add a _<locale> properties file (e.g. CompilerBundle_de.properties) with
the same keys.