### Install Dependencies and Serve Docs Locally Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/README.md Use this command to install project dependencies and serve the documentation locally for quick previews. Visit http://localhost:8000 to see changes live. ```bash uv sync uv run mkdocs serve ``` -------------------------------- ### Run Gradle Commands in Temporary Directory Source: https://github.com/mrmans0n/compose-rules/blob/main/rules/functional-tests/README.md When tests fail, you can navigate to the temporary project directory shown in the test output and run Gradle commands manually to debug. This example shows how to change directory and run a specific Gradle task with a stacktrace. ```bash cd /tmp/compose-rules-functional-test-xyz123 ./gradlew spotlessKotlinCheck --stacktrace ``` -------------------------------- ### Use Detekt Compose Rules Uber JAR with Detekt CLI Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Use the provided uber JAR file with the Detekt CLI for static analysis. Ensure the JAR version matches your project's setup. ```shell detekt -p detekt-compose--all.jar -c your/config/detekt.yml ``` -------------------------------- ### ComposableNaming Rule Examples Source: https://context7.com/mrmans0n/compose-rules/llms.txt Enforces naming conventions for composables: UpperCamelCase for unit-returning and lowerCamelCase for value-returning. It also shows how to allow specific naming patterns. ```kotlin // ❌ Unit-returning composable starts with lowercase @Composable fun userProfile(userId: String) { /* emits UI */ } ``` ```kotlin // ✅ Unit-returning composable starts with uppercase @Composable fun UserProfile(userId: String) { /* emits UI */ } ``` ```kotlin // ❌ Value-returning composable starts with uppercase @Composable fun RememberScrollState(): ScrollState = remember { ScrollState(0) } ``` ```kotlin // ✅ Value-returning composable starts with lowercase @Composable fun rememberScrollState(): ScrollState = remember { ScrollState(0) } ``` ```kotlin // Allow Molecule-style Presenters that intentionally return a value with uppercase name: // detekt.yml: // ComposableNaming: // allowedComposableFunctionNames: .*Presenter // .editorconfig: // compose_allowed_composable_function_names = .*Presenter ``` -------------------------------- ### Example Functional Test for Ktlint Source: https://github.com/mrmans0n/compose-rules/blob/main/rules/functional-tests/README.md This example demonstrates a functional test that verifies the detection of the 'ModifierMissing' rule via ktlint. It sets up a project, writes a violating Kotlin file, runs the spotless check, and asserts that the specific rule violation is present in the output. ```kotlin @Test fun `ModifierMissing is detected via ktlint`() { setupKtlintProject() projectDir.writeFile( "src/main/kotlin/Violations.kt", """ @Composable fun MissingModifier() { Row { } // Missing modifier parameter } """ ) val result = createGradleRunner( projectDir = projectDir, arguments = listOf("spotlessKotlinCheck") ).buildAndFail() result.assertOutputContains("compose:modifier-missing-check") } ``` -------------------------------- ### ComposableParamOrder Rule Examples Source: https://context7.com/mrmans0n/compose-rules/llms.txt Enforces the standard Compose parameter order: required parameters, then modifier, then optional parameters, and finally optional trailing composable lambda. ```kotlin // ❌ modifier appears before required params; optional params precede required ones @Composable fun Avatar( modifier: Modifier = Modifier, imageUrl: String, enabled: Boolean = true, onClick: () -> Unit, contentDescription: String, ) { /* ... */ } ``` ```kotlin // ✅ Correct order: required first, modifier as first optional, other optionals, trailing lambda @Composable fun Avatar( imageUrl: String, // required contentDescription: String, // required onClick: () -> Unit, // required (non-composable lambda, not trailing) modifier: Modifier = Modifier, // modifier — first optional enabled: Boolean = true, // other optional params content: @Composable () -> Unit, // trailing composable lambda — last ) { /* ... */ } ``` -------------------------------- ### Composable Parameter Ordering Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/rules.md Order composable parameters with required parameters first, followed by optional parameters starting with 'modifier', and finally a trailing lambda for content slots. ```kotlin // ✅ @Composable fun Avatar( imageUrl: String, // Required parameters go first contentDescription: String, onClick: () -> Unit, modifier: Modifier = Modifier, // Optional parameters, start with modifier enabled: Boolean = true, // Other optional parameters loadingContent: @Composable (() -> Unit)? = null, errorContent: @Composable (() -> Unit)? = null, content: @Composable () -> Unit, // A trailing lambda _can_ be last. Recommended for `content` slots. ) { ... } ``` -------------------------------- ### RememberMissing Rule Examples Source: https://context7.com/mrmans0n/compose-rules/llms.txt Detects state usages not wrapped in `remember`, which prevents state re-creation on recomposition. Shows correct usage with `remember` and primitive-specific state builders. ```kotlin // ❌ New state instance created on every recomposition @Composable fun Counter() { var count = mutableStateOf(0) // missing remember! Button(onClick = { count.value++ }) { Text("Count: ${count.value}") } } ``` ```kotlin // ✅ State is remembered across recompositions @Composable fun Counter() { var count by remember { mutableStateOf(0) } Button(onClick = { count++ }) { Text("Count: $count") } } ``` ```kotlin // ✅ Use primitive-specific variants to avoid JVM autoboxing @Composable fun PrimitiveCounter() { var count by remember { mutableIntStateOf(0) } // instead of mutableStateOf Text("Count: $count") } ``` -------------------------------- ### Deploy and Serve Versioned Docs Locally Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/README.md To preview the documentation with versioning enabled, first deploy a test version locally using 'mike deploy dev --no-push', then serve the site with 'mike serve'. ```bash uv run mike deploy dev --no-push uv run mike serve ``` -------------------------------- ### Build the Project with Gradle Source: https://github.com/mrmans0n/compose-rules/blob/main/CONTRIBUTING.md Run this command to compile the code, execute all tests, and ensure linters pass. This is a necessary step before submitting changes. ```shell ./gradlew build ``` -------------------------------- ### ModifierMissing Rule Example Source: https://context7.com/mrmans0n/compose-rules/llms.txt Ensures content-emitting @Composable functions expose a modifier parameter. The correct version includes a modifier parameter with a default value. ```kotlin // ❌ Will trigger ModifierMissing — emits content but has no modifier param @Composable fun UserCard(name: String) { Column { Text(text = name) } } ``` ```kotlin // ✅ Correct — modifier parameter is exposed with a default value @Composable fun UserCard(name: String, modifier: Modifier = Modifier) { Column(modifier) { Text(text = name) } } ``` ```kotlin // detekt.yml to also check internal composables: // ModifierMissing: // active: true // checkModifiersForVisibility: public_and_internal // .editorconfig equivalent: // compose_check_modifiers_for_visibility = public_and_internal ``` -------------------------------- ### Useful Documentation Commands Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/README.md A collection of commands for managing the documentation, including serving locally, building the site, listing deployed versions, and previewing versioned documentation. ```bash uv run mkdocs serve ``` ```bash uv run mkdocs build ``` ```bash uv run mike list ``` ```bash uv run mike serve ``` -------------------------------- ### Manually test ktlint sample Source: https://github.com/mrmans0n/compose-rules/blob/main/samples/README.md Navigate to the ktlint sample directory and run the Gradle spotlessCheck task. This command should fail with compose-rules violations. ```bash cd samples/ktlint-sample ../../gradlew spotlessCheck ``` -------------------------------- ### Ignore Composable Functions for Function Naming Rule Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Configure the standard:function-naming rule to ignore Composable functions, which typically start with an uppercase letter. ```editorconfig [*.{kt,kts}] ktlint_function_naming_ignore_when_annotated_with = Composable ``` -------------------------------- ### Manually test detekt sample Source: https://github.com/mrmans0n/compose-rules/blob/main/samples/README.md Navigate to the detekt sample directory and run the Gradle check task. This command should fail, indicating compose-rules violations. ```bash cd samples/detekt-sample ../../gradlew check ``` -------------------------------- ### Enable and Configure Preview Naming Strategy Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Enforce a naming convention for preview composables by enabling the rule and setting the desired strategy. ```editorconfig [*.{kt,kts}] compose_preview_naming_enabled = true compose_preview_naming_strategy = suffix ``` -------------------------------- ### Integrate ktlint sample test into CI Source: https://github.com/mrmans0n/compose-rules/blob/main/samples/README.md Use this YAML configuration to run the ktlint sample test in a CI environment. The script verifies expected violations and returns an appropriate exit code. ```yaml - name: Test ktlint sample run: ./scripts/test-ktlint-sample.sh ``` -------------------------------- ### Configure PreviewPublic Rule Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Enable the PreviewPublic rule to enforce public visibility for preview functions. Set 'active' to true to activate. ```yaml PreviewPublic: active: true ``` -------------------------------- ### Integrate detekt sample test into CI Source: https://github.com/mrmans0n/compose-rules/blob/main/samples/README.md Use this YAML configuration to run the detekt sample test in a CI environment. The script verifies expected violations and returns an appropriate exit code. ```yaml - name: Test detekt sample run: ./scripts/test-detekt-sample.sh ``` -------------------------------- ### Configure PreviewNaming Rule Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Enable the PreviewNaming rule for preview naming strategies. It is opt-in and disabled by default. Customize the 'previewNamingStrategy' to 'suffix', 'prefix', or 'anywhere'. ```yaml PreviewNaming: active: false # Opt-in, disabled by default. # -- You can optionally configure the naming strategy for previews. # -- Possible values are: `suffix`, `prefix`, `anywhere`. By default, it will be `suffix`. # previewNamingStrategy: suffix ``` -------------------------------- ### Test detekt sample with validation script Source: https://github.com/mrmans0n/compose-rules/blob/main/samples/README.md Run this script to test the detekt sample project. It returns exit code 0 if all expected violations are found. ```bash # Test detekt sample ./scripts/test-detekt-sample.sh ``` -------------------------------- ### Test ktlint sample with validation script Source: https://github.com/mrmans0n/compose-rules/blob/main/samples/README.md Run this script to test the ktlint sample project. It returns exit code 0 if all expected violations are found. ```bash # Test ktlint sample ./scripts/test-ktlint-sample.sh ``` -------------------------------- ### Configure PreviewAnnotationNaming Rule Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Enable the PreviewAnnotationNaming rule to enforce naming conventions for preview annotations. Set 'active' to true to activate. ```yaml PreviewAnnotationNaming: active: true ``` -------------------------------- ### Correct LaunchedEffect Key Usage Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/rules.md Demonstrates how to correctly use keys with LaunchedEffect to manage effect restarts. Use `rememberUpdatedState` to ensure the effect points to the latest callback without unnecessary restarts, or use the parameter directly as a key if rebuilding the effect is acceptable. ```kotlin ```kotlin // ❌ onClick changes, but the effect won't be pointing to the right one! @Composable fun MyComposable(onClick: () -> Unit) { LaunchedEffect(Unit) { delay(10.seconds) // something that takes time, a flow collection, etc onClick() } // ... } ``` ``` ```kotlin ```kotlin // ✅ onClick changes and the LaunchedEffect won't be rebuilt -- but will point at the correct onClick! @Composable fun MyComposable(onClick: () -> Unit) { val latestOnClick by rememberUpdatedState(onClick) LaunchedEffect(Unit) { delay(10.seconds) // something that takes time, a flow collection, etc latestOnClick() } // ... } ``` ``` ```kotlin ```kotlin // ✅ _If we don't care about rebuilding the effect_, we can also use the parameter as key @Composable fun MyComposable(onClick: () -> Unit) { // This effect will be rebuilt every time onClick changes, so it will always point to the latest one. LaunchedEffect(onClick) { delay(10.seconds) // something that takes time, a flow collection, etc onClick() } } ``` ``` -------------------------------- ### Implement Custom Rule with ComposeKtVisitor Source: https://context7.com/mrmans0n/compose-rules/llms.txt Extend ComposeKtVisitor to create custom rules. Override visit methods relevant to your analysis, such as visitComposable to inspect @Composable functions. ```kotlin class MyCustomRule : ComposeKtVisitor { // Called for every @Composable function override fun visitComposable(function: KtFunction, emitter: Emitter, config: ComposeKtConfig) { val functionName = function.name ?: return // Example: flag composables containing "TODO" in their name if (functionName.contains("TODO", ignoreCase = true)) { emitter.report(function, "Composable contains TODO in its name.") } } // Called for every function (composable or not) override fun visitFunction(function: KtFunction, emitter: Emitter, config: ComposeKtConfig) { } // Called for every class override fun visitClass(clazz: KtClass, emitter: Emitter, config: ComposeKtConfig) { } // Called once per file — useful for file-level analysis override fun visitFile(file: KtFile, emitter: Emitter, config: ComposeKtConfig) { } } ``` -------------------------------- ### Enable Compose Rules in detekt.yml Source: https://context7.com/mrmans0n/compose-rules/llms.txt Configure detekt to enable specific Compose rules. Most rules are active by default, but some like Material2, UnstableCollections, and PreviewNaming are opt-in and require explicit activation. ```yaml Compose: active: true Material2: active: true UnstableCollections: active: true PreviewNaming: active: true ``` -------------------------------- ### Run Functional Tests with Verbose Output Source: https://github.com/mrmans0n/compose-rules/blob/main/rules/functional-tests/README.md Execute functional tests and enable verbose logging to see detailed output during the test run. ```bash ./gradlew :rules:functional-tests:functionalTest --info ``` -------------------------------- ### Use Compose Rules with Ktlint CLI Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Run Ktlint with the Compose Rules uber jar using the -R flag. ```shell ktlint -R ktlint-compose--all.jar ``` -------------------------------- ### Configure Allowed CompositionLocals in .editorconfig Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Define a list of allowed CompositionLocals for the compositionlocal-allowlist rule in .editorconfig. ```editorconfig [*.{kt,kts}] compose_allowed_composition_locals = LocalSomething,LocalSomethingElse ``` -------------------------------- ### Enable Compose Rules in detekt.yml Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Configure Detekt Compose rules by setting 'active: true' for desired rules in your `detekt.yml` file. Some rules offer optional parameters for further customization. ```yaml Compose: ComposableAnnotationNaming: active: true ComposableNaming: active: true # -- You can optionally disable the checks in this rule for regex matches against the composable name (e.g. molecule presenters) # allowedComposableFunctionNames: .*Presenter,.*MoleculePresenter ComposableParamOrder: active: true # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically) # treatAsLambda: MyLambdaType # -- You can optionally have a list of types to be treated as composable lambdas (e.g. typedefs or fun interfaces not picked up automatically). # -- The difference with treatAsLambda is that those need `@Composable` MyLambdaType in the definition, while these won't. # treatAsComposableLambda: MyComposableLambdaType CompositionLocalAllowlist: active: true # -- You can optionally define a list of CompositionLocals that are allowed here # allowedCompositionLocals: LocalSomething,LocalSomethingElse CompositionLocalNaming: active: true ContentEmitterReturningValues: active: true # -- You can optionally add your own composables here # contentEmitters: MyComposable,MyOtherComposable ContentTrailingLambda: active: true # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically) # treatAsLambda: MyLambdaType # -- You can optionally have a list of types to be treated as composable lambdas (e.g. typedefs or fun interfaces not picked up automatically). # -- The difference with treatAsLambda is that those need `@Composable` MyLambdaType in the definition, while these won't. # treatAsComposableLambda: MyComposableLambdaType ContentSlotReused: active: true # -- You can optionally have a list of types to be treated as composable lambdas (e.g. typedefs or fun interfaces not picked up automatically). # -- The difference with treatAsLambda is that those need `@Composable` MyLambdaType in the definition, while these won't. # treatAsComposableLambda: MyComposableLambdaType DefaultsVisibility: active: true LambdaParameterEventTrailing: active: true # -- You can optionally add your own composables here # contentEmitters: MyComposable,MyOtherComposable # -- You can add composables here that you don't want to count as content emitters (e.g. custom dialogs or modals) # contentEmittersDenylist: MyNonEmitterComposable LambdaParameterInRestartableEffect: active: true # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically) # treatAsLambda: MyLambdaType Material2: active: false # Opt-in, disabled by default. Turn on if you want to disallow Material 2 usages. # -- You can optionally allow parts of it, if you are in the middle of a migration. # allowedFromM2: icons.Icons,TopAppBar ModifierClickableOrder: active: true # -- You can optionally add your own Modifier types # customModifiers: BananaModifier,PotatoModifier ModifierComposed: active: true # -- You can optionally add your own Modifier types # customModifiers: BananaModifier,PotatoModifier ModifierMissing: active: true # -- You can optionally control the visibility of which composables to check for here # -- Possible values are: `only_public`, `public_and_internal` and `all` (default is `only_public`) # checkModifiersForVisibility: only_public # -- You can optionally add your own Modifier types # customModifiers: BananaModifier,PotatoModifier # -- You can suppress this check in functions annotated with these annotations # ignoreAnnotated: ['Potato', 'Banana'] ModifierNaming: active: true # -- You can optionally add your own Modifier types # customModifiers: BananaModifier,PotatoModifier ModifierNotUsedAtRoot: active: true # -- You can optionally add your own composables here # contentEmitters: MyComposable,MyOtherComposable # -- You can optionally add your own Modifier types # customModifiers: BananaModifier,PotatoModifier ModifierReused: active: true # -- You can optionally add your own Modifier types # customModifiers: BananaModifier,PotatoModifier ModifierWithoutDefault: active: true MultipleEmitters: active: true # -- You can optionally add your own composables here that will count as content emitters # contentEmitters: MyComposable,MyOtherComposable # -- You can add composables here that you don't want to count as content emitters (e.g. custom dialogs or modals) # contentEmittersDenylist: MyNonEmitterComposable MutableParams: active: true MutableStateAutoboxing: active: true MutableStateParam: active: true ParameterNaming: active: true ``` -------------------------------- ### Configure RememberContentMissing Rule Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Enable the RememberContentMissing rule to ensure necessary remember calls are present. Set 'active' to true to activate. ```yaml RememberContentMissing: active: true ``` -------------------------------- ### Add detekt Plugin via Gradle (Kotlin DSL) Source: https://context7.com/mrmans0n/compose-rules/llms.txt Add the detekt plugin dependency to your module's build.gradle.kts file using Kotlin DSL. Replace with the latest release version. ```kotlin dependencies { detektPlugins("io.nlopez.compose.rules:detekt:") } ``` -------------------------------- ### Run All Functional Tests Source: https://github.com/mrmans0n/compose-rules/blob/main/rules/functional-tests/README.md Execute all functional tests for the compose-rules module. ```bash ./gradlew :rules:functional-tests:functionalTest ``` -------------------------------- ### Add Detekt Compose Rules Dependency to Gradle Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Add this dependency to your `detektPlugins` block in your Gradle build file to enable the rules. ```groovy dependencies { detektPlugins "io.nlopez.compose.rules:detekt:" } ``` -------------------------------- ### Configure Custom ViewModel Factories in .editorconfig Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Add custom ViewModel factory names to be recognized by the vm-injection-check rule in .editorconfig. ```editorconfig [*.{kt,kts}] compose_view_model_factories = myViewModel,potatoViewModel ``` -------------------------------- ### Configure Allowed State Holder Names in .editorconfig Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Add custom regex patterns for state holder class names to be recognized by the vm-forwarding-check rule in .editorconfig. ```editorconfig [*.{kt,kts}] compose_allowed_state_holder_names = .*ViewModel,.*Presenter,.*Component,.*SomethingElse ``` -------------------------------- ### Migrate from Material 2 to Material 3 Source: https://context7.com/mrmans0n/compose-rules/llms.txt Flags imports or usage from `androidx.compose.material` (Material 2). Encourage migration to Material 3 equivalents. Enable the rule and optionally specify allowed Material 2 APIs. ```kotlin // ❌ Importing Material 2 components import androidx.compose.material.Button import androidx.compose.material.Text ``` ```kotlin // ✅ Use Material 3 equivalents import androidx.compose.material3.Button import androidx.compose.material3.Text ``` -------------------------------- ### Read Rule Configuration with ComposeKtConfig Source: https://context7.com/mrmans0n/compose-rules/llms.txt Access per-rule configuration values using ComposeKtConfig. Supports reading boolean flags, comma-separated sets, strings, lists, and integers. ```kotlin override fun visitComposable(function: KtFunction, emitter: Emitter, config: ComposeKtConfig) { // Read a boolean flag (default: false) val strictMode: Boolean = config.getBoolean("strictMode", false) // Read a comma-separated allow-list as a Set val allowedNames: Set = config.getSet("allowedNames", emptySet()) // Read a single String value val strategy: String? = config.getString("namingStrategy", "suffix") // Read a list of strings val emitters: List = config.getList("contentEmitters", emptyList()) // Read an integer threshold val maxParams: Int = config.getInt("maxParameters", 10) } ``` -------------------------------- ### Configure Lambda Types and Parameter Names Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Optionally specify types to be treated as lambdas using 'treatAsLambda' and list allowed lambda parameter names with 'allowedLambdaParameterNames'. ```yaml # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically) # treatAsLambda: MyLambdaType # -- You can optionally add your allowed lambda names (e.g., usages from the past found in the official Compose code) # allowedLambdaParameterNames: onSizeChanged, onGloballyPositioned ``` -------------------------------- ### Publish Compose Rules to Maven Local Source: https://github.com/mrmans0n/compose-rules/blob/main/rules/functional-tests/README.md Before running functional tests, publish the compose-rules artifacts to Maven Local to make them available for the tests. ```bash ./gradlew publishToMavenLocal ``` -------------------------------- ### Configure StateParam Rule Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Enable the StateParam rule to enforce the use of state parameters in composables. Set 'active' to true to activate. ```yaml StateParam: active: true ``` -------------------------------- ### Configure Custom Content Emitters in .editorconfig Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Add custom composables that emit content to the .editorconfig file for specific rules. ```editorconfig [*.{kt,kts}] compose_content_emitters = MyComposable,MyOtherComposable ``` -------------------------------- ### Configure ViewModelForwarding Rule Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Enable the ViewModelForwarding rule to manage ViewModel forwarding. Customize 'allowedStateHolderNames', 'allowedForwarding', and 'allowedForwardingOfTypes' using regex or specific names. ```yaml ViewModelForwarding: active: true # -- You can optionally use this rule on things other than types ending in "ViewModel" or "Presenter" (which are the defaults). You can add your own via a regex here: # allowedStateHolderNames: .*ViewModel,.*Presenter # -- You can optionally add an allowlist for Composable names that won't be affected by this rule # allowedForwarding: .*Content,.*FancyStuff # -- You can optionally add an allowlist for ViewModel/StateHolder names that won't be affected by this rule # allowedForwardingOfTypes: PotatoViewModel,(Apple|Banana)ViewModel,.*FancyViewModel ``` -------------------------------- ### Configure Denylist for Content Emitters in .editorconfig Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Exclude specific composables from content emitter checks by adding them to the denylist in .editorconfig. ```editorconfig [*.{kt,kts}] compose_content_emitters_denylist = MyModalComposable,MyDialogComposable ``` -------------------------------- ### Configure RememberMissing Rule Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Enable the RememberMissing rule to ensure that composables use remember appropriately. Set 'active' to true to activate. ```yaml RememberMissing: active: true ``` -------------------------------- ### Commit and Push for Snapshot Version Source: https://github.com/mrmans0n/compose-rules/blob/main/RELEASING.md Commit staged changes and push to the main branch to update to the next snapshot version. This is done after a release is published. ```bash $ git commit -am "Bump version to X.Y.Z-SNAPSHOT" $ git push ``` -------------------------------- ### Configure Types to Treat as Lambdas Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Define types that should be treated as lambdas for rules like param-order-check. ```editorconfig [*.{kt,kts}] compose_treat_as_lambda = MyLambdaType,MyOtherLambdaType ``` -------------------------------- ### Require Private Preview Composables Source: https://context7.com/mrmans0n/compose-rules/llms.txt Requires `@Preview`-only composables to be `private` to prevent accidental usage in production UI trees. This ensures preview functions are only called by the preview tooling. ```kotlin // ❌ Preview composable is public — may be called outside of preview tooling @Preview @Composable fun UserCardPreview() { UserCard(name = "Ada Lovelace") } ``` ```kotlin // ✅ Preview composable is private @Preview @Composable private fun UserCardPreview() { UserCard(name = "Ada Lovelace") } ``` -------------------------------- ### View Functional Test Report Source: https://github.com/mrmans0n/compose-rules/blob/main/rules/functional-tests/README.md After running the functional tests, you can view a detailed HTML report of the test results. This command opens the report in your default web browser. ```bash open rules/functional-tests/build/reports/tests/functionalTest/index.html ``` -------------------------------- ### Enable Material 2 Detector Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Enable the material-two rule to flag Material 2 API usage. Disabled by default. ```editorconfig [*.{kt,kts}] compose_disallow_material2 = true ``` -------------------------------- ### Ensure ViewModel Acquisition as Default Parameter Source: https://context7.com/mrmans0n/compose-rules/llms.txt Acquire ViewModels using default parameter values (e.g., `viewModel()`, `hiltViewModel()`) to make dependencies explicit and testable. Avoid calling ViewModel acquisition functions directly within the composable body. ```kotlin // ❌ Implicit dependency — hard to provide a fake in tests @Composable fun ProfileScreen() { val vm = viewModel() Text(vm.userName) } // ✅ Explicit dependency via default parameter value @Composable fun ProfileScreen( viewModel: ProfileViewModel = viewModel(), modifier: Modifier = Modifier, ) { Text(viewModel.userName, modifier = modifier) } // Register additional factory functions (e.g., custom DI integration): // detekt.yml: // ViewModelInjection: // viewModelFactories: koinViewModel,potatoViewModel // .editorconfig: // compose_view_model_factories = koinViewModel,potatoViewModel ``` -------------------------------- ### Configure UnstableCollections Rule Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Enable the UnstableCollections rule to enforce the use of stable collections. This rule is opt-in and disabled by default. Activate it if strong skipping is disabled. ```yaml UnstableCollections: active: false # Opt-in, disabled by default. Turn on if you want to enforce this (e.g. you have strong skipping disabled) ``` -------------------------------- ### Configure ViewModelInjection Rule Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/detekt.md Enable the ViewModelInjection rule for ViewModel injection. Optionally, add custom ViewModel factories using 'viewModelFactories'. ```yaml ViewModelInjection: active: true # -- You can optionally add your own ViewModel factories here # viewModelFactories: hiltViewModel,potatoViewModel ``` -------------------------------- ### Stop Gradle Daemon Source: https://github.com/mrmans0n/compose-rules/blob/main/rules/functional-tests/README.md If you encounter Gradle daemon issues, you can manually stop all running Gradle daemons. This is a troubleshooting step for TestKit environments. ```bash ./gradlew --stop ``` -------------------------------- ### Allowlist Custom CompositionLocals Source: https://context7.com/mrmans0n/compose-rules/llms.txt Flags usage of `CompositionLocal` not in an explicit allowlist. Add custom `CompositionLocal`s to the `allowedCompositionLocals` in `detekt.yml` or `compose_allowed_composition_locals` in `.editorconfig` to suppress findings. ```kotlin // ❌ Using a custom CompositionLocal not on the allowlist triggers the rule val LocalMyService = compositionLocalOf { error("No MyService") } ``` -------------------------------- ### Commit and Push for Release Source: https://github.com/mrmans0n/compose-rules/blob/main/RELEASING.md Commit staged changes and push to the main branch to trigger the release workflow. This action publishes artifacts to Maven Central. ```bash $ git commit -am "Bump version to X.Y.Z" $ git push ``` -------------------------------- ### Run Specific Test Method Source: https://github.com/mrmans0n/compose-rules/blob/main/rules/functional-tests/README.md Execute a single, specific test method within a functional test class. ```bash ./gradlew :rules:functional-tests:functionalTest --tests "KtlintFunctionalTest.clean code passes ktlint checks" ``` -------------------------------- ### Run Specific Test Class Source: https://github.com/mrmans0n/compose-rules/blob/main/rules/functional-tests/README.md Execute functional tests for a specific test class, such as KtlintFunctionalTest or DetektFunctionalTest. ```bash ./gradlew :rules:functional-tests:functionalTest --tests KtlintFunctionalTest ``` ```bash ./gradlew :rules:functional-tests:functionalTest --tests DetektFunctionalTest ``` -------------------------------- ### Composable Emits Content XOR Returns Value Source: https://context7.com/mrmans0n/compose-rules/llms.txt Adheres to the Compose API guideline: a composable should either emit layout content or return a value, but not both. If a value needs to be exposed, use a callback parameter. ```kotlin // ❌ Emits content AND returns a value @Composable fun LoadingButton(onClick: () -> Unit): Boolean { var isLoading by remember { mutableStateOf(false) } Button(onClick = { isLoading = true; onClick() }) { if (isLoading) CircularProgressIndicator() else Text("Submit") } return isLoading // should be provided as a callback/parameter instead } ``` ```kotlin // ✅ Expose state via a callback parameter @Composable fun LoadingButton( onClick: () -> Unit, onLoadingChange: (Boolean) -> Unit, modifier: Modifier = Modifier, ) { var isLoading by remember { mutableStateOf(false) } Button( onClick = { isLoading = true; onLoadingChange(true); onClick() }, modifier = modifier, ) { if (isLoading) CircularProgressIndicator() else Text("Submit") } } ``` -------------------------------- ### Configure Types to Treat as Composable Lambdas Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Specify types that should be recognized as composable lambdas for the content-trailing-lambda rule. ```editorconfig [*.{kt,kts}] compose_treat_as_composable_lambda = MyLambdaComposableType,MyOtherComposableLambdaType ``` -------------------------------- ### Run Functional Tests as Part of Check Task Source: https://github.com/mrmans0n/compose-rules/blob/main/rules/functional-tests/README.md Functional tests are integrated into the CI pipeline and can be run as part of the standard 'check' Gradle task. ```bash ./gradlew check # Includes functionalTest ``` -------------------------------- ### Allow Specific Material 2 APIs Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Configure specific Material 2 APIs to be allowed during migration, even when the material-two rule is enabled. ```editorconfig [*.{kt,kts}] compose_disallow_material2 = true compose_allowed_from_m2 = icons.filled,Button ``` -------------------------------- ### Ensure Defaults Object Visibility Matches Composable Source: https://context7.com/mrmans0n/compose-rules/llms.txt Requires that a companion `Defaults` object has the same visibility as the composable it accompanies. This ensures consumers can build upon defaults without copy-pasting. ```kotlin // ❌ Composable is public but its Defaults object is internal @Composable fun FancyButton( colors: FancyButtonColors = FancyButtonDefaults.colors(), modifier: Modifier = Modifier, ) { /* ... */ } internal object FancyButtonDefaults { // ← should be public fun colors() = FancyButtonColors(...) } ``` ```kotlin // ✅ Defaults object has matching visibility @Composable fun FancyButton( colors: FancyButtonColors = FancyButtonDefaults.colors(), modifier: Modifier = Modifier, ) { /* ... */ } object FancyButtonDefaults { fun colors() = FancyButtonColors(...) } ``` -------------------------------- ### Composable Event Parameter Naming Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/rules.md Event parameters in composable functions should use the 'on' prefix followed by a present tense verb, like 'onClick' or 'onTextChange'. ```kotlin // ❌ @Composable fun Avatar(onShown: () -> Unit, onChanged: () -> Unit) { /* ... */ } // ✅ @Composable fun Avatar(onShow: () -> Unit, onChange: () -> Unit) { /* ... */ } ``` -------------------------------- ### Allowlist ViewModel/State Holder Types for Forwarding Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Specify ViewModel or state holder types that are permitted to be forwarded without triggering the ViewModelForwarding rule. ```editorconfig [*.{kt,kts}] compose_allowed_forwarding_of_types = .*MyViewModel,PotatoViewModel ``` -------------------------------- ### Configure Allowed Composable Lambda Names in .editorconfig Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Specify allowed parameter names for function types in composables using .editorconfig for the parameter-naming rule. ```editorconfig [*.{kt,kts}] compose_allowed_lambda_parameter_names = onSizeChanged,onGloballyPositioned ``` -------------------------------- ### Capture Lambda Parameters in Restartable Effects Source: https://context7.com/mrmans0n/compose-rules/llms.txt Flags lambda parameters used directly inside restartable effects. Use `rememberUpdatedState` to capture the latest lambda or use the lambda as a key to restart the effect. ```kotlin // ❌ onClick captured at launch time — may call a stale reference @Composable fun AutoSubmit(onClick: () -> Unit) { LaunchedEffect(Unit) { delay(3.seconds) onClick() // ← stale reference if onClick changes } } ``` ```kotlin // ✅ Use rememberUpdatedState so the effect always calls the latest lambda @Composable fun AutoSubmit(onClick: () -> Unit) { val latestOnClick by rememberUpdatedState(onClick) LaunchedEffect(Unit) { delay(3.seconds) latestOnClick() } } ``` ```kotlin // ✅ Alternatively, use onClick as the key to restart the effect on change @Composable fun AutoSubmit(onClick: () -> Unit) { LaunchedEffect(onClick) { delay(3.seconds) onClick() } } ``` -------------------------------- ### Configure Allowed Composable Function Names in .editorconfig Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Allow specific regex patterns for composable function names that return values in .editorconfig for the naming-check rule. ```editorconfig [*.{kt,kts}] compose_allowed_composable_function_names = .*Presenter,.*SomethingElse ``` -------------------------------- ### Add Compose Rules to Kotlinter Buildscript Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Specify the Compose Rules dependency in the buildscript classpath when using Kotlinter. ```groovy buildscript { dependencies { classpath "io.nlopez.compose.rules:ktlint:" } } ``` -------------------------------- ### Use Primitive-Optimized State for Primitives Source: https://context7.com/mrmans0n/compose-rules/llms.txt Avoid autoboxing for primitive types like Int and Long by using type-specific state holders such as `mutableIntStateOf` and `mutableLongStateOf`. Prefer Compose's primitive collection state over `List` for primitive collections. ```kotlin // ❌ Uses generic mutableStateOf with a primitive type — causes autoboxing var score by remember { mutableStateOf(0) } var elapsed by remember { mutableStateOf(0L) } // ✅ Use the type-specific variants var score by remember { mutableIntStateOf(0) } var elapsed by remember { mutableLongStateOf(0L) } // Primitive collections — prefer Compose collection state over List // ❌ var items by remember { mutableStateOf(listOf()) } // ✅ var items by remember { mutableIntListOf() } ``` -------------------------------- ### Add Compose Rules to ktlint-gradle Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/ktlint.md Declare the Compose Rules dependency using ktlintRuleset for ktlint-gradle. ```groovy dependencies { ktlintRuleset "io.nlopez.compose.rules:ktlint:" } ``` -------------------------------- ### Report Violations with Emitter Source: https://context7.com/mrmans0n/compose-rules/llms.txt Use the Emitter interface to report findings. The report function can optionally indicate if a violation is auto-correctable. ```kotlin // Emitter is a fun interface: // fun interface Emitter { // fun report(element: PsiElement, errorMessage: String, canBeAutoCorrected: Boolean): Decision // } override fun visitComposable(function: KtFunction, emitter: Emitter, config: ComposeKtConfig) { // Report without auto-correct support emitter.report(function, "Describe the violation here.") // Report with optional auto-correct (Decision.Fix triggers the fix block) val decision = emitter.report(function, "Auto-correctable violation.", canBeAutoCorrected = true) decision.ifFix { // Perform PSI mutation to auto-correct the source } } ``` -------------------------------- ### Preserve Content Slot Lifecycle in Compose Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/rules.md Avoid reusing content slots in branching code to prevent disposal and recomposition. Use `remember { movableContentOf { ... } }` to ensure content is preserved correctly across recompositions. ```kotlin // ❌ @Composable fun Avatar(user: User, content: @Composable () -> Unit) { if (user.isFollower) { content() } else { content() } } ``` ```kotlin // ✅ @Composable fun Avatar(user: User, content: @Composable () -> Unit) { val content = remember { movableContentOf { content() } } if (user.isFollower) { content() } else { content() } } ``` -------------------------------- ### Emit Single Layout Node in Compose Source: https://github.com/mrmans0n/compose-rules/blob/main/docs/rules.md Composable functions should emit zero or one layout node. Avoid emitting multiple distinct UI elements directly. Instead, wrap them in a single parent layout within the composable. ```kotlin // This will render: // // //