Repository: detekt/detekt (kotlin)

Issue:
Behaviour of MultilineLambdaItParameter inconsistent with name
## Expected Behavior
A lambda that uses the implicit it parameter with a single statement spread over multiple lines should be reported by the `MultilineLambdaItParameter` rule

## Observed Behavior
Nothing is reported

## Steps to Reproduce
```
fun detektTest(strings: List<String>): List<String> {
  return strings
    .flatMap {
      it
        .reader()
        .readLines()
    }
}
```

## Context
Based on the name of the rule, its reasonable to expect that any lambda with code spanning multiple lines should be flagged. I raised this in the kotlinlang discord here: https://kotlinlang.slack.com/archives/C88E12QH4/p1712167796864189

As mentioned there, this caused confusion in one of our PRs where code that we expected to fail the rule was passed and then had to be caught by a code reviewer. This behaviour was introduced in this [PR](https://github.com/detekt/detekt/pull/5505) and there was a comment [thread](https://github.com/detekt/detekt/pull/5505/files/993cb9c255e5f60e41b075476e670fd4e0e4c63b#r1014169233) in that PR asking similar questions about the behaviour not matching the rule name that doesn't have a conclusion.

Would appreciate some clarity around whether this rule intends to ban any multi-line lambda using an it parameter, or only multi statement lambdas

## Your Environment
<!-- Include as many relevant details about the environment you experienced the bug in -->
* Version of detekt used: 1.23.4
* Version of Gradle used (if applicable): 8.7
* Gradle scan link (add `--scan` option when running the gradle task):
* Operating System and version: MacOS 14.4.1
* Link to your project (if it's a public repository):

Pull request:
Fixes #7148

Patch:
diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineLambdaItParameter.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineLambdaItParameter.kt
index 34c44d31b487..9daa8c5648fd 100644
--- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineLambdaItParameter.kt
+++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineLambdaItParameter.kt
@@ -8,11 +8,7 @@ import io.gitlab.arturbosch.detekt.api.Rule
 import io.gitlab.arturbosch.detekt.rules.IT_LITERAL
 import io.gitlab.arturbosch.detekt.rules.hasImplicitParameterReference
 import io.gitlab.arturbosch.detekt.rules.implicitParameter
-import org.jetbrains.kotlin.psi.KtBlockExpression
-import org.jetbrains.kotlin.psi.KtExpression
 import org.jetbrains.kotlin.psi.KtLambdaExpression
-import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
-import org.jetbrains.kotlin.utils.ifEmpty
 
 /**
  * Lambda expressions are very useful in a lot of cases, and they often include very small chunks of
@@ -73,13 +69,11 @@ class MultilineLambdaItParameter(config: Config) : Rule(
     override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
         super.visitLambdaExpression(lambdaExpression)
 
-        val statements = lambdaExpression.bodyExpression?.statements.orEmpty().ifEmpty { return }
-        val single = statements.singleOrNull()
-        if (single != null && (single.hasNoLineBreak() || single.hasNoStatements())) return
+        if (!lambdaExpression.isMultiline()) return
 
         val parameterNames = lambdaExpression.valueParameters.map { it.name }
-        // Explicit `it`
         if (IT_LITERAL in parameterNames) {
+            // Explicit `it`
             report(
                 CodeSmell(
                     Entity.from(lambdaExpression),
@@ -87,24 +81,29 @@ class MultilineLambdaItParameter(config: Config) : Rule(
                         "Consider giving your parameter a readable and descriptive name."
                 )
             )
-        } else if (parameterNames.isEmpty()) { // Implicit `it`
-            val implicitParameter = lambdaExpression.implicitParameter(bindingContext)
-            if (implicitParameter != null &&
-                lambdaExpression.hasImplicitParameterReference(implicitParameter, bindingContext)
-            ) {
-                report(
-                    CodeSmell(
-                        Entity.from(lambdaExpression),
-                        "The implicit `it` should not be used in a multiline lambda. " +
-                            "Consider giving your parameter a readable and descriptive name."
-                    )
+        } else if (parameterNames.isEmpty() && lambdaExpression.isUsingImplicitParameter()) {
+            // Implicit `it`
+            report(
+                CodeSmell(
+                    Entity.from(lambdaExpression),
+                    "The implicit `it` should not be used in a multiline lambda. " +
+                        "Consider giving your parameter a readable and descriptive name."
                 )
-            }
+            )
         }
     }
 
-    private fun KtExpression.hasNoLineBreak() = !textContains('\n')
+    private fun KtLambdaExpression.isMultiline(): Boolean {
+        val statements = bodyExpression?.statements ?: return false
+        return when (statements.size) {
+            0 -> false
+            1 -> statements.single().textContains('\n')
+            else -> true
+        }
+    }
 
-    private fun KtExpression.hasNoStatements() =
-        !anyDescendantOfType<KtBlockExpression> { it.statements.isNotEmpty() }
+    private fun KtLambdaExpression.isUsingImplicitParameter(): Boolean {
+        val implicitParameter = implicitParameter(bindingContext) ?: return false
+        return hasImplicitParameterReference(implicitParameter, bindingContext)
+    }
 }


Test patch:
diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineLambdaItParameterSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineLambdaItParameterSpec.kt
index b9196b43f4ab..60e700b497ee 100644
--- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineLambdaItParameterSpec.kt
+++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineLambdaItParameterSpec.kt
@@ -131,7 +131,7 @@ class MultilineLambdaItParameterSpec(val env: KotlinCoreEnvironment) {
         }
 
         @Test
-        fun `does not report when statement has no nested statements`() {
+        fun `reports when statement has multiple line arguments`() {
             val code = """
                 data class Foo(val x: Int, val y: Int)
                 
@@ -145,7 +145,23 @@ class MultilineLambdaItParameterSpec(val env: KotlinCoreEnvironment) {
                 }
             """.trimIndent()
             val findings = subject.compileAndLintWithContext(env, code)
-            assertThat(findings).isEmpty()
+            assertThat(findings).hasSize(1)
+        }
+
+        @Test
+        fun `reports when statement has multiple line calls`() {
+            val code = """
+                fun test(strings: List<String>): List<String> {
+                  return strings
+                    .flatMap {
+                      it
+                        .reader()
+                        .readLines()
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.compileAndLintWithContext(env, code)
+            assertThat(findings).hasSize(1)
         }
     }