
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 (14 FIR checkers) | gradle-plugin/README.md |
| Gradle Plugin | ✅ Complete (profiles incl. Spring/Ktor) | gradle-plugin/README.md |
| Detekt Rules | ✅ Complete (40 rules) | detekt-rules/README.md |
| Android Lint | ✅ Complete (35 detectors) | lint-rules/README.md |
| IntelliJ Plugin | ✅ Complete (35 inspections, tool window, Flow Analyzer) | intellij-plugin/README.md |
| Annotations | ✅ Complete | annotations/README.md |
| Sample | ✅ Compilation examples per rule | compilation/README |
| Sample (Detekt) | ✅ Detekt rule validation (30+ examples) | sample-detekt/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 |
@StructuredScope annotationviewModelScope, lifecycleScope, rememberCoroutineScope()
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) | 14 checkers | 7 errors + 7 warnings; INTEROP_001/002 in K2 |
| Detekt | 40 rules | KMP source-set aware; backend + Compose profiles |
| Android Lint | 35 detectors | Android/Compose + shared compiler rules |
| IntelliJ | 35 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.
// 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 "0.1.0"
}
dependencies {
implementation("io.github.santimattius:structured-coroutines-annotations:0.1.0")
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:0.1.0")
}// build.gradle.kts (Android project)
dependencies {
lintChecks("io.github.santimattius:structured-coroutines-lint-rules:0.1.0")
}Note: Android Lint Rules are only available for Android projects. For multiplatform projects, use the Compiler Plugin or Detekt Rules.
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 "0.1.0"
}
kotlin {
jvm()
iosArm64()
iosSimulatorArm64()
js(IR) { browser(); nodejs() }
sourceSets {
commonMain {
dependencies {
implementation("io.github.santimattius:structured-coroutines-annotations:0.1.0")
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
├── detekt-rules/ # Detekt Custom Rules
│ ├── GlobalScopeUsageRule
│ ├── InlineCoroutineScopeRule
│ ├── RunBlockingInSuspendRule
│ ├── DispatchersUnconfinedRule
│ ├── CancellationExceptionSubclassRule
│ ├── BlockingCallInCoroutineRule
│ ├── RunBlockingWithDelayInTestRule
│ ├── ExternalScopeLaunchRule
│ ├── LoopWithoutYieldRule
│ ├── ScopeReuseAfterCancelRule
│ ├── ChannelNotClosedRule
│ ├── ConsumeEachMultipleConsumersRule
│ └── FlowBlockingCallRule
├── lint-rules/ # Android Lint Rules
│ ├── GlobalScopeUsageDetector
│ ├── MainDispatcherMisuseDetector
│ ├── ViewModelScopeLeakDetector
│ └── ... (21 rules total)
├── intellij-plugin/ # IntelliJ/Android Studio Plugin
│ ├── inspections/ # 13 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
├── 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 | 14 checkers | ✅ | ❌ | All (KMP) |
| Detekt Rules | Analysis | 40 rules | ✅ | ❌ | All (KMP) |
| Android Lint Rules | Analysis | 35 detectors | ✅ | ❌ | Android only |
| IDE Plugin | Editing | 35 inspections | ❌ | ✅ | All |
| Combined (All) | All | 51 patterns | ✅ | ✅ | - |
| Code Review | Manual | — | ❌ | ❌ | - |
| Runtime | Late | — | ❌ | ❌ | - |
Notes:
docs/rule-codes.yml
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 | 40 rules with examples and configuration |
| Android Lint | lint-rules/README.md | 35 detectors, Android/Compose-specific detection |
| IntelliJ Plugin | intellij-plugin/README.md | 35 inspections, quick fixes, Flow Analyzer, K2 support |
| Annotations | annotations/README.md | @StructuredScope usage and multiplatform support |
| 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 |
| 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.
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 (14 FIR checkers) | gradle-plugin/README.md |
| Gradle Plugin | ✅ Complete (profiles incl. Spring/Ktor) | gradle-plugin/README.md |
| Detekt Rules | ✅ Complete (40 rules) | detekt-rules/README.md |
| Android Lint | ✅ Complete (35 detectors) | lint-rules/README.md |
| IntelliJ Plugin | ✅ Complete (35 inspections, tool window, Flow Analyzer) | intellij-plugin/README.md |
| Annotations | ✅ Complete | annotations/README.md |
| Sample | ✅ Compilation examples per rule | compilation/README |
| Sample (Detekt) | ✅ Detekt rule validation (30+ examples) | sample-detekt/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 |
@StructuredScope annotationviewModelScope, lifecycleScope, rememberCoroutineScope()
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) | 14 checkers | 7 errors + 7 warnings; INTEROP_001/002 in K2 |
| Detekt | 40 rules | KMP source-set aware; backend + Compose profiles |
| Android Lint | 35 detectors | Android/Compose + shared compiler rules |
| IntelliJ | 35 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.
// 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 "0.1.0"
}
dependencies {
implementation("io.github.santimattius:structured-coroutines-annotations:0.1.0")
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:0.1.0")
}// build.gradle.kts (Android project)
dependencies {
lintChecks("io.github.santimattius:structured-coroutines-lint-rules:0.1.0")
}Note: Android Lint Rules are only available for Android projects. For multiplatform projects, use the Compiler Plugin or Detekt Rules.
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 "0.1.0"
}
kotlin {
jvm()
iosArm64()
iosSimulatorArm64()
js(IR) { browser(); nodejs() }
sourceSets {
commonMain {
dependencies {
implementation("io.github.santimattius:structured-coroutines-annotations:0.1.0")
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
├── detekt-rules/ # Detekt Custom Rules
│ ├── GlobalScopeUsageRule
│ ├── InlineCoroutineScopeRule
│ ├── RunBlockingInSuspendRule
│ ├── DispatchersUnconfinedRule
│ ├── CancellationExceptionSubclassRule
│ ├── BlockingCallInCoroutineRule
│ ├── RunBlockingWithDelayInTestRule
│ ├── ExternalScopeLaunchRule
│ ├── LoopWithoutYieldRule
│ ├── ScopeReuseAfterCancelRule
│ ├── ChannelNotClosedRule
│ ├── ConsumeEachMultipleConsumersRule
│ └── FlowBlockingCallRule
├── lint-rules/ # Android Lint Rules
│ ├── GlobalScopeUsageDetector
│ ├── MainDispatcherMisuseDetector
│ ├── ViewModelScopeLeakDetector
│ └── ... (21 rules total)
├── intellij-plugin/ # IntelliJ/Android Studio Plugin
│ ├── inspections/ # 13 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
├── 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 | 14 checkers | ✅ | ❌ | All (KMP) |
| Detekt Rules | Analysis | 40 rules | ✅ | ❌ | All (KMP) |
| Android Lint Rules | Analysis | 35 detectors | ✅ | ❌ | Android only |
| IDE Plugin | Editing | 35 inspections | ❌ | ✅ | All |
| Combined (All) | All | 51 patterns | ✅ | ✅ | - |
| Code Review | Manual | — | ❌ | ❌ | - |
| Runtime | Late | — | ❌ | ❌ | - |
Notes:
docs/rule-codes.yml
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 | 40 rules with examples and configuration |
| Android Lint | lint-rules/README.md | 35 detectors, Android/Compose-specific detection |
| IntelliJ Plugin | intellij-plugin/README.md | 35 inspections, quick fixes, Flow Analyzer, K2 support |
| Annotations | annotations/README.md | @StructuredScope usage and multiplatform support |
| 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 |
| 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.
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.