From fe388bf495d76eb9a720db4faf73c2a025f8e160 Mon Sep 17 00:00:00 2001 From: Nacho Lopez Date: Fri, 15 Dec 2023 16:02:54 +0100 Subject: [PATCH] Add treatAsLambda to ParamOrder rule configuration (#170) --- docs/detekt.md | 2 ++ docs/ktlint.md | 9 ++++++++ .../io/nlopez/compose/rules/ParameterOrder.kt | 21 ++++++++++++------- .../rules/detekt/ParameterOrderCheckTest.kt | 13 ++++++++++-- .../rules/ktlint/EditorConfigProperties.kt | 19 +++++++++++++++++ .../rules/ktlint/ParameterOrderCheck.kt | 5 ++++- .../rules/ktlint/ParameterOrderCheckTest.kt | 10 ++++++++- 7 files changed, 68 insertions(+), 11 deletions(-) diff --git a/docs/detekt.md b/docs/detekt.md index 124c1f33..3116e1be 100644 --- a/docs/detekt.md +++ b/docs/detekt.md @@ -28,6 +28,8 @@ Compose: # 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 CompositionLocalAllowlist: active: true # -- You can optionally define a list of CompositionLocals that are allowed here diff --git a/docs/ktlint.md b/docs/ktlint.md index 4297ed60..dc082f55 100644 --- a/docs/ktlint.md +++ b/docs/ktlint.md @@ -126,6 +126,15 @@ Most of the modifier-related rules will look for modifiers based their type: eit compose_custom_modifiers = BananaModifier,PotatoModifier ``` +### Configure types to treat as lambdas in ParamOrder check + +The `param-order-check` rule will do its best to identify trailing lambdas. However, in cases where a typedef / functional interface is being used, we might want to have this rule to treat them as if they were lambdas: not reporting them if they are the last in a method signature and they don't have a default value. To give ktlint some hints, you can configure this in your `.editorconfig` file: + +```editorconfig +[*.{kt,kts}] +compose_treat_as_lambda = MyLambdaType,MyOtherLambdaType +``` + ## Disabling a specific rule To disable a rule you have to follow the [instructions from the ktlint documentation](https://github.com/pinterest/ktlint#how-do-i-suppress-an-errors-for-a-lineblockfile), and use the id of the rule you want to disable with the `compose` tag. diff --git a/rules/common/src/main/kotlin/io/nlopez/compose/rules/ParameterOrder.kt b/rules/common/src/main/kotlin/io/nlopez/compose/rules/ParameterOrder.kt index 319e8ad1..3801df4a 100644 --- a/rules/common/src/main/kotlin/io/nlopez/compose/rules/ParameterOrder.kt +++ b/rules/common/src/main/kotlin/io/nlopez/compose/rules/ParameterOrder.kt @@ -12,6 +12,8 @@ import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtFunctionType import org.jetbrains.kotlin.psi.KtNullableType import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtTypeElement +import org.jetbrains.kotlin.psi.KtUserType class ParameterOrder : ComposeKtVisitor { @@ -31,7 +33,7 @@ class ParameterOrder : ComposeKtVisitor { val currentOrder = function.valueParameters // We look in the original params without defaults and see if the last one is a function. - val hasTrailingFunction = function.hasTrailingFunction + val hasTrailingFunction = with(config) { function.hasTrailingFunction } val trailingLambda = if (hasTrailingFunction) { listOf(function.valueParameters.last()) } else { @@ -59,13 +61,18 @@ class ParameterOrder : ComposeKtVisitor { } } + context(ComposeKtConfig) private val KtFunction.hasTrailingFunction: Boolean - get() = - when (val outerType = valueParameters.lastOrNull()?.typeReference?.typeElement) { - is KtFunctionType -> true - is KtNullableType -> outerType.innerType is KtFunctionType - else -> false - } + get() = valueParameters.lastOrNull()?.typeReference?.typeElement?.isLambda() == true + + context(ComposeKtConfig) + private fun KtTypeElement.isLambda(treatAsLambda: Set = getSet("treatAsLambda", emptySet())): Boolean = + when (this) { + is KtFunctionType -> true + is KtNullableType -> innerType?.isLambda(treatAsLambda) == true + is KtUserType -> getReferencedName() in getSet("treatAsLambda", emptySet()) + else -> false + } companion object { fun createErrorMessage(currentOrder: List, properOrder: List): String = diff --git a/rules/detekt/src/test/kotlin/io/nlopez/compose/rules/detekt/ParameterOrderCheckTest.kt b/rules/detekt/src/test/kotlin/io/nlopez/compose/rules/detekt/ParameterOrderCheckTest.kt index 54df5dd3..5445f7be 100644 --- a/rules/detekt/src/test/kotlin/io/nlopez/compose/rules/detekt/ParameterOrderCheckTest.kt +++ b/rules/detekt/src/test/kotlin/io/nlopez/compose/rules/detekt/ParameterOrderCheckTest.kt @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 package io.nlopez.compose.rules.detekt -import io.gitlab.arturbosch.detekt.api.Config import io.gitlab.arturbosch.detekt.api.SourceLocation +import io.gitlab.arturbosch.detekt.test.TestConfig import io.gitlab.arturbosch.detekt.test.assertThat import io.gitlab.arturbosch.detekt.test.lint import org.intellij.lang.annotations.Language @@ -11,7 +11,10 @@ import org.junit.jupiter.api.Test class ParameterOrderCheckTest { - private val rule = ParameterOrderCheck(Config.empty) + private val testConfig = TestConfig( + "treatAsLambda" to listOf("LambdaType"), + ) + private val rule = ParameterOrderCheck(testConfig) @Test fun `no errors when ordering is correct`() { @@ -28,6 +31,12 @@ class ParameterOrderCheckTest { @Composable fun MyComposable(text1: String, modifier: Modifier = Modifier, m2: Modifier = Modifier, trailing: () -> Unit) { } + @Composable + fun MyComposable(text1: String, modifier: Modifier = Modifier, m2: Modifier = Modifier, trailing: LambdaType) { } + + @Composable + fun MyComposable(text1: String, modifier: Modifier = Modifier, m2: Modifier = Modifier, trailing: LambdaType?) { } + @Composable fun MyComposable(text1: String, modifier: Modifier = Modifier, m2: Modifier = Modifier, trailing: (() -> Unit)?) { } diff --git a/rules/ktlint/src/main/kotlin/io/nlopez/compose/rules/ktlint/EditorConfigProperties.kt b/rules/ktlint/src/main/kotlin/io/nlopez/compose/rules/ktlint/EditorConfigProperties.kt index b4c44a3f..3ac3724a 100644 --- a/rules/ktlint/src/main/kotlin/io/nlopez/compose/rules/ktlint/EditorConfigProperties.kt +++ b/rules/ktlint/src/main/kotlin/io/nlopez/compose/rules/ktlint/EditorConfigProperties.kt @@ -169,3 +169,22 @@ val customModifiers: EditorConfigProperty = } }, ) + +val treatAsLambda: EditorConfigProperty = + EditorConfigProperty( + type = PropertyType.LowerCasingPropertyType( + "compose_treat_as_lambda", + "A comma separated list of types that should be treated as lambdas " + + "(e.g. typedefs of lambdas, fun interfaces)", + PropertyValueParser.IDENTITY_VALUE_PARSER, + emptySet(), + ), + defaultValue = "", + propertyMapper = { property, _ -> + when { + property?.isUnset == true -> "" + property?.getValueAs() != null -> property.getValueAs() + else -> property?.getValueAs() + } + }, + ) diff --git a/rules/ktlint/src/main/kotlin/io/nlopez/compose/rules/ktlint/ParameterOrderCheck.kt b/rules/ktlint/src/main/kotlin/io/nlopez/compose/rules/ktlint/ParameterOrderCheck.kt index b0489a1e..fc1095ac 100644 --- a/rules/ktlint/src/main/kotlin/io/nlopez/compose/rules/ktlint/ParameterOrderCheck.kt +++ b/rules/ktlint/src/main/kotlin/io/nlopez/compose/rules/ktlint/ParameterOrderCheck.kt @@ -7,5 +7,8 @@ import io.nlopez.rules.core.ComposeKtVisitor import io.nlopez.rules.core.ktlint.KtlintRule class ParameterOrderCheck : - KtlintRule("compose:param-order-check"), + KtlintRule( + id = "compose:param-order-check", + editorConfigProperties = setOf(treatAsLambda), + ), ComposeKtVisitor by ParameterOrder() diff --git a/rules/ktlint/src/test/kotlin/io/nlopez/compose/rules/ktlint/ParameterOrderCheckTest.kt b/rules/ktlint/src/test/kotlin/io/nlopez/compose/rules/ktlint/ParameterOrderCheckTest.kt index 32330d45..7fa8abce 100644 --- a/rules/ktlint/src/test/kotlin/io/nlopez/compose/rules/ktlint/ParameterOrderCheckTest.kt +++ b/rules/ktlint/src/test/kotlin/io/nlopez/compose/rules/ktlint/ParameterOrderCheckTest.kt @@ -27,13 +27,21 @@ class ParameterOrderCheckTest { @Composable fun MyComposable(text1: String, modifier: Modifier = Modifier, m2: Modifier = Modifier, trailing: () -> Unit) { } + @Composable + fun MyComposable(text1: String, modifier: Modifier = Modifier, m2: Modifier = Modifier, trailing: LambdaType) { } + + @Composable + fun MyComposable(text1: String, modifier: Modifier = Modifier, m2: Modifier = Modifier, trailing: LambdaType?) { } + @Composable fun MyComposable(text1: String, modifier: Modifier = Modifier, m2: Modifier = Modifier, trailing: (() -> Unit)?) { } @Composable fun MyComposable(modifier: Modifier, text1: String, m2: Modifier = Modifier, trailing: (() -> Unit)?) { } """.trimIndent() - orderingRuleAssertThat(code).hasNoLintViolations() + orderingRuleAssertThat(code) + .withEditorConfigOverride(treatAsLambda to "LambdaType") + .hasNoLintViolations() } @Test