Repository: detekt/detekt (kotlin)

Issue:
[NoNameShadowing] False negative inside lambdas
## Expected Behavior
```kotlin
fun asdf(rotation: Float, onClick: (Float) -> Unit) {
    foo {
        asdf(rotation) { rotation -> onClick(rotation) }
    }
}

fun foo(block: () -> Unit) {
}
```

on that code I expect to have a violation of this rule because `rotation ->` shadows `rotation: Float` from the main function.

But if the code is like this:

```kotlin
fun asdf(rotation: Float, onClick: (Float) -> Unit) {
    asdf(rotation) { rotation -> onClick(rotation) }
}
```

The rule catches the problem.

## Observed Behavior
Nothing is marked as shadowed.

## Context
I know that this rule only relies on the kotlin compiler warnings but they seem a bit too lax.

Also, this type of pattern happens a lot on Compose because nearly always you are inside a `Box` or a `Column` and that "disables" this rule.

This issue is related to this other: https://github.com/appKODE/detekt-rules-compose/issues/41 but for sure they are two different issues.

## Your Environment
* Version of detekt used: 1.23.7

Pull request description:
Part of https://github.com/detekt/detekt/issues/8044

Also fixes: https://github.com/detekt/detekt/issues/8069
and fixes https://github.com/detekt/detekt/issues/7741

Patch:
diff --git a/detekt-core/src/main/kotlin/dev/detekt/core/reporting/Reporting.kt b/detekt-core/src/main/kotlin/dev/detekt/core/reporting/Reporting.kt
index 9ed2cb2ebb75..0eca5cf61f6f 100644
--- a/detekt-core/src/main/kotlin/dev/detekt/core/reporting/Reporting.kt
+++ b/detekt-core/src/main/kotlin/dev/detekt/core/reporting/Reporting.kt
@@ -10,10 +10,10 @@ internal fun defaultReportMapping(report: OutputReport) =
 
 internal fun printIssues(issues: Map<String, List<Issue>>, basePath: Path): String =
     buildString {
-        issues.forEach { (key, issues) ->
+        issues.forEach { (key, nestedIssues) ->
             append(key)
             append("\n")
-            issues.forEach {
+            nestedIssues.forEach {
                 append("\t")
                 append(it.detailed(basePath).yellow())
                 append("\n")
diff --git a/detekt-psi-utils/api/detekt-psi-utils.api b/detekt-psi-utils/api/detekt-psi-utils.api
index 6fbe872ec7c0..ff3ebf58f3b4 100644
--- a/detekt-psi-utils/api/detekt-psi-utils.api
+++ b/detekt-psi-utils/api/detekt-psi-utils.api
@@ -57,6 +57,7 @@ public final class dev/detekt/psi/KtLambdaExpressionKt {
 	public static final fun hasImplicitParameterReference (Lorg/jetbrains/kotlin/psi/KtLambdaExpression;)Z
 	public static final fun hasImplicitParameterReference (Lorg/jetbrains/kotlin/psi/KtLambdaExpression;Lorg/jetbrains/kotlin/descriptors/ValueParameterDescriptor;Lorg/jetbrains/kotlin/resolve/BindingContext;)Z
 	public static final fun implicitParameter (Lorg/jetbrains/kotlin/psi/KtLambdaExpression;Lorg/jetbrains/kotlin/resolve/BindingContext;)Lorg/jetbrains/kotlin/descriptors/ValueParameterDescriptor;
+	public static final fun implicitParameterOrNull (Lorg/jetbrains/kotlin/psi/KtLambdaExpression;)Lorg/jetbrains/kotlin/analysis/api/symbols/KaValueParameterSymbol;
 }
 
 public final class dev/detekt/psi/KtModifierListKt {
diff --git a/detekt-psi-utils/src/main/kotlin/dev/detekt/psi/KtLambdaExpression.kt b/detekt-psi-utils/src/main/kotlin/dev/detekt/psi/KtLambdaExpression.kt
index 60b54e14451e..3ab4a6b329d8 100644
--- a/detekt-psi-utils/src/main/kotlin/dev/detekt/psi/KtLambdaExpression.kt
+++ b/detekt-psi-utils/src/main/kotlin/dev/detekt/psi/KtLambdaExpression.kt
@@ -25,6 +25,13 @@ fun KtLambdaExpression.implicitParameter(bindingContext: BindingContext): ValueP
         firstParameter(bindingContext)
     }
 
+fun KtLambdaExpression.implicitParameterOrNull(): KaValueParameterSymbol? =
+    if (valueParameters.isNotEmpty()) {
+        null
+    } else {
+        firstParameterOrNull()
+    }
+
 fun KtLambdaExpression.hasImplicitParameterReference(
     implicitParameter: ValueParameterDescriptor,
     bindingContext: BindingContext,
diff --git a/detekt-rules-documentation/src/main/kotlin/dev/detekt/rules/documentation/OutdatedDocumentation.kt b/detekt-rules-documentation/src/main/kotlin/dev/detekt/rules/documentation/OutdatedDocumentation.kt
index 9e725564e899..9a409400ff87 100644
--- a/detekt-rules-documentation/src/main/kotlin/dev/detekt/rules/documentation/OutdatedDocumentation.kt
+++ b/detekt-rules-documentation/src/main/kotlin/dev/detekt/rules/documentation/OutdatedDocumentation.kt
@@ -208,7 +208,7 @@ class OutdatedDocumentation(config: Config) : Rule(
             doc.sortedBy { it.name }.zip(element.sortedBy { it.name })
         }
 
-        return zippedElements.all { (doc, element) -> declarationMatches(doc, element) }
+        return zippedElements.all { (docItr, elementItr) -> declarationMatches(docItr, elementItr) }
     }
 
     private fun declarationMatches(doc: Declaration, element: Declaration): Boolean =
diff --git a/detekt-rules-naming/src/main/kotlin/dev/detekt/rules/naming/NoNameShadowing.kt b/detekt-rules-naming/src/main/kotlin/dev/detekt/rules/naming/NoNameShadowing.kt
index 2f6bbcc456b2..f22744f4c03d 100644
--- a/detekt-rules-naming/src/main/kotlin/dev/detekt/rules/naming/NoNameShadowing.kt
+++ b/detekt-rules-naming/src/main/kotlin/dev/detekt/rules/naming/NoNameShadowing.kt
@@ -1,20 +1,35 @@
 package dev.detekt.rules.naming
 
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.parentOfType
+import com.intellij.psi.util.parents
+import com.intellij.psi.util.parentsOfType
 import dev.detekt.api.ActiveByDefault
 import dev.detekt.api.Config
 import dev.detekt.api.Entity
 import dev.detekt.api.Finding
-import dev.detekt.api.RequiresFullAnalysis
+import dev.detekt.api.RequiresAnalysisApi
 import dev.detekt.api.Rule
 import dev.detekt.psi.hasImplicitParameterReference
-import dev.detekt.psi.implicitParameter
-import org.jetbrains.kotlin.diagnostics.Errors
+import dev.detekt.psi.implicitParameterOrNull
+import org.jetbrains.kotlin.builtins.StandardNames
+import org.jetbrains.kotlin.psi.KtClass
+import org.jetbrains.kotlin.psi.KtClassInitializer
+import org.jetbrains.kotlin.psi.KtClassOrObject
+import org.jetbrains.kotlin.psi.KtDeclaration
 import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.psi.KtFunction
 import org.jetbrains.kotlin.psi.KtLambdaExpression
 import org.jetbrains.kotlin.psi.KtNamedDeclaration
+import org.jetbrains.kotlin.psi.KtObjectDeclaration
 import org.jetbrains.kotlin.psi.KtParameter
+import org.jetbrains.kotlin.psi.KtPrimaryConstructor
 import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
+import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
 import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
+import org.jetbrains.kotlin.psi.psiUtil.hasInnerModifier
 
 /**
  * Disallows shadowing variable declarations.
@@ -46,50 +61,115 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
  *
  */
 @ActiveByDefault(since = "1.21.0")
-class NoNameShadowing(config: Config) :
-    Rule(
-        config,
-        "Disallow shadowing variable declarations."
-    ),
-    RequiresFullAnalysis {
+class NoNameShadowing(
+    config: Config,
+) : Rule(config, "Disallow shadowing variable declarations."), RequiresAnalysisApi {
 
     override fun visitProperty(property: KtProperty) {
         super.visitProperty(property)
-        checkNameShadowing(property)
+        checkNameShadowing(property, property)
     }
 
     override fun visitDestructuringDeclarationEntry(multiDeclarationEntry: KtDestructuringDeclarationEntry) {
         super.visitDestructuringDeclarationEntry(multiDeclarationEntry)
-        checkNameShadowing(multiDeclarationEntry)
+        checkNameShadowing(multiDeclarationEntry, multiDeclarationEntry.parent)
     }
 
     override fun visitParameter(parameter: KtParameter) {
         super.visitParameter(parameter)
-        checkNameShadowing(parameter)
+        checkNameShadowing(
+            parameter,
+            // if this param is from lambda or we fallback to function(ctor)
+            (parameter.parent.parent.parent as? KtLambdaExpression)
+                ?: parameter.parentOfType<KtFunction>(false).let {
+                    if (it is KtPrimaryConstructor) {
+                        // if this param is from primary constructor then pass the class to
+                        // start(excluding) the search from
+                        parameter.parentOfType<KtClass>(false)
+                    } else {
+                        it
+                    }
+                }
+                ?: return,
+        )
     }
 
-    private fun checkNameShadowing(declaration: KtNamedDeclaration) {
-        val nameIdentifier = declaration.nameIdentifier ?: return
-        if (bindingContext.diagnostics.forElement(declaration).any { it.factory == Errors.NAME_SHADOWING }) {
-            report(Finding(Entity.from(nameIdentifier), "Name shadowed: ${nameIdentifier.text}"))
+    private fun checkNameShadowing(
+        declaration: KtNamedDeclaration,
+        parentToSkipSearchFrom: PsiElement,
+    ) {
+        val declarationNameIdentifier = declaration.nameIdentifier ?: return
+        val declarationName = declarationNameIdentifier.text
+        val matched = parentToSkipSearchFrom.parents(false)
+            .filterIsInstance<KtElement>()
+            .any { parent ->
+                parent.isParentElementAccessible(declaration.accessibleClasses) && when (parent) {
+                    is KtClass -> {
+                        parent.primaryConstructor
+                            ?.valueParameters
+                            .orEmpty()
+                            .filter {
+                                declaration.parentOfType<KtClassInitializer>(false) != null &&
+                                    // only accessible when declaration is in the same class
+                                    declaration.getParentOfType<KtClass>(true) == parent
+                            }
+                            .any { it.name == declarationName }
+                    }
+
+                    is KtFunction -> {
+                        parent.valueParameters.any { it.name == declarationName }
+                    }
+
+                    is KtLambdaExpression -> {
+                        if (parent.valueParameters.isNotEmpty()) {
+                            parent.valueParameters.any { it.name == declarationName }
+                        } else {
+                            parent.implicitParameterOrNull() != null &&
+                                declarationName == StandardNames.IMPLICIT_LAMBDA_PARAMETER_NAME.asString()
+                        }
+                    }
+
+                    else -> false
+                }
+            }
+        if (matched) {
+            report(Finding(Entity.from(declarationNameIdentifier), "Name shadowed: $declarationName"))
         }
     }
 
+    private fun KtElement.isParentElementAccessible(accessibleClasses: List<KtClassOrObject>) =
+        (this.containingClassOrObject() == null && accessibleClasses.isEmpty()) ||
+            (this.containingClassOrObject() in accessibleClasses)
+
     override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
         super.visitLambdaExpression(lambdaExpression)
-        val implicitParameter = lambdaExpression.implicitParameter(bindingContext) ?: return
-        if (lambdaExpression.hasImplicitParameterReference(implicitParameter, bindingContext) &&
+        if (lambdaExpression.hasImplicitParameterReference() &&
             lambdaExpression.hasParentImplicitParameterLambda()
         ) {
-            report(
-                Finding(
-                    Entity.from(lambdaExpression),
-                    "Name shadowed: implicit lambda parameter 'it'"
-                )
-            )
+            report(Finding(Entity.from(lambdaExpression), "Name shadowed: implicit lambda parameter 'it'"))
         }
     }
 
     private fun KtLambdaExpression.hasParentImplicitParameterLambda(): Boolean =
-        getStrictParentOfType<KtLambdaExpression>()?.implicitParameter(bindingContext) != null
+        getStrictParentOfType<KtLambdaExpression>()?.implicitParameterOrNull() != null
+
+    @Suppress("ClassOrdering")
+    private val KtDeclaration.accessibleClasses: List<KtClassOrObject>
+        get() = buildList {
+            var currentClass: KtClassOrObject? = containingClassOrObject()
+            if (currentClass is KtClass) {
+                while (currentClass != null) {
+                    add(currentClass)
+                    currentClass = if (currentClass.hasInnerModifier()) {
+                        currentClass.containingClassOrObject
+                    } else {
+                        null
+                    }
+                }
+            } else {
+                addAll(this@accessibleClasses.parentsOfType<KtObjectDeclaration>().toList())
+            }
+        }
+
+    private fun KtElement.containingClassOrObject(): KtClassOrObject? = getParentOfType(false)
 }


Test patch:
diff --git a/detekt-rules-naming/src/test/kotlin/dev/detekt/rules/naming/NoNameShadowingSpec.kt b/detekt-rules-naming/src/test/kotlin/dev/detekt/rules/naming/NoNameShadowingSpec.kt
index a0925b033a8e..a10f2757adb0 100644
--- a/detekt-rules-naming/src/test/kotlin/dev/detekt/rules/naming/NoNameShadowingSpec.kt
+++ b/detekt-rules-naming/src/test/kotlin/dev/detekt/rules/naming/NoNameShadowingSpec.kt
@@ -5,6 +5,7 @@ import dev.detekt.test.assertj.assertThat
 import dev.detekt.test.lintWithContext
 import dev.detekt.test.utils.KotlinCoreEnvironmentTest
 import dev.detekt.test.utils.KotlinEnvironmentContainer
+import org.junit.jupiter.api.Nested
 import org.junit.jupiter.api.Test
 
 @KotlinCoreEnvironmentTest
@@ -89,6 +90,17 @@ class NoNameShadowingSpec(val env: KotlinEnvironmentContainer) {
         assertThat(findings).isEmpty()
     }
 
+    @Test
+    fun `does not report different name with file level prop`() {
+        val code = """
+            val a: Int = 0
+            fun test(b: Int) {
+            }
+        """.trimIndent()
+        val findings = subject.lintWithContext(env, code)
+        assertThat(findings).isEmpty()
+    }
+
     @Test
     fun `does not report not shadowing nested lambda implicit 'it' parameter`() {
         val code = """
@@ -111,4 +123,486 @@ class NoNameShadowingSpec(val env: KotlinEnvironmentContainer) {
         val findings = subject.lintWithContext(env, code)
         assertThat(findings).isEmpty()
     }
+
+    @Test
+    fun `reports shadowing of variable in local function when param name is same`() {
+        val code = """
+            fun test(a: Int) {
+                fun localTest(a: Int) {
+                }
+            }
+        """.trimIndent()
+        val findings = subject.lintWithContext(env, code)
+        assertThat(findings).hasSize(1)
+    }
+
+    @Test
+    fun `does not report shadowing of variable in local function when param name is different`() {
+        val code = """
+            fun test(a: Int) {
+                fun localTest(b: Int) {
+                }
+            }
+        """.trimIndent()
+        val findings = subject.lintWithContext(env, code)
+        assertThat(findings).isEmpty()
+    }
+
+    @Test
+    fun `reports when use is inside a call of function - #7741`() {
+        val code = """
+            fun asdf(rotation: Float, onClick: (Float) -> Unit) {
+                foo {
+                    asdf(rotation) { rotation -> onClick(rotation) }
+                }
+            }
+
+            fun foo(block: () -> Unit) {
+            }
+        """.trimIndent()
+        val findings = subject.lintWithContext(env, code)
+        assertThat(findings).hasSize(1)
+    }
+
+    @Test
+    fun `does not report when outer lambda doesn't have it ref - #8069`() {
+        val code = """
+            class MySettingsPlugin {
+                fun apply(settings: Any) {
+                    settings.beforeProject {
+                        val f = listOf<String>()
+                        f.map { java.io.File(it) }
+                    }
+                }
+        
+                fun Any.beforeProject(block: Any.() -> Unit) {
+                    this.block()
+                }
+            }
+        """.trimIndent()
+        val findings = subject.lintWithContext(env, code)
+        assertThat(findings).isEmpty()
+    }
+
+    @Test
+    fun `does report when outer lambda does have it ref and it is used`() {
+        val code = """
+            class MySettingsPlugin {
+                fun apply(settings: Any) {
+                    settings.beforeProject {
+                        val f = listOf<String>()
+                        f.map { java.io.File(it) }
+                    }
+                }
+        
+                fun Any.beforeProject(block: (Any) -> Unit) {
+                    block(this)
+                }
+            }
+        """.trimIndent()
+        val findings = subject.lintWithContext(env, code)
+        assertThat(findings).hasSize(1)
+    }
+
+    @Test
+    fun `does not report when outer lambda does have it ref and it is not used`() {
+        val code = """
+            class MySettingsPlugin {
+                fun apply(settings: Any) {
+                    settings.beforeProject {
+                        val f = listOf<String>()
+                        f.map { java.io.File("soem path") }
+                    }
+                }
+        
+                fun Any.beforeProject(block: (Any) -> Unit) {
+                    block(this)
+                }
+            }
+        """.trimIndent()
+        val findings = subject.lintWithContext(env, code)
+        assertThat(findings).isEmpty()
+    }
+
+    @Test
+    fun `does report when unnamed function is used`() {
+        val code = """
+            fun test() {
+                fun (a: Int) {
+                    fun foo(a: Int) {
+                        
+                    }
+                }
+
+                fun foo(b: Int) {
+                     fun (b: Int) {}   
+                }
+            }
+        """.trimIndent()
+        val findings = subject.lintWithContext(env, code)
+        assertThat(findings).satisfiesExactlyInAnyOrder(
+            { assertThat(it).hasMessage("Name shadowed: a") },
+            { assertThat(it).hasMessage("Name shadowed: b") },
+        )
+    }
+
+    @Test
+    fun `does report when unnamed function is used present inside a class`() {
+        val code = """
+            class Test {
+                fun test() {
+                fun (a: Int) {
+                    fun foo(a: Int) {
+                        
+                    }
+                }
+
+                fun foo(b: Int) {
+                     fun (b: Int) {}   
+                }
+            }
+            }
+        """.trimIndent()
+        val findings = subject.lintWithContext(env, code)
+        assertThat(findings).satisfiesExactlyInAnyOrder(
+            { assertThat(it).hasMessage("Name shadowed: a") },
+            { assertThat(it).hasMessage("Name shadowed: b") },
+        )
+    }
+
+    @Nested
+    inner class `with class` {
+        @Test
+        fun `does not report class with param and property`() {
+            val code = """
+                class Foo(val a: Int, b: Int)
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report class and function has same param name`() {
+            val code = """
+                class Foo(val a: Int) {
+                    fun foo(a: Int) {
+                        // this access
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report class param without val var and function has same param name`() {
+            val code = """
+                class Foo(a: Int) {
+                    fun foo(a: Int) {}
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does report class param without val var and function inside init has same param name`() {
+            val code = """
+                class Foo(a: Int) {
+                    init {
+                        fun foo(a: Int) {}
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).hasSize(1)
+        }
+
+        @Test
+        fun `does not report class and lambda has same param name`() {
+            val code = """
+                class Foo(val a: Int) {
+                    fun foo() {
+                        listOf(1).map { a -> }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report class param without val or var and lambda has same param name`() {
+            val code = """
+                class Foo(a: Int) {
+                    fun foo() {
+                        listOf(1).map { a -> }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does report class param without val or var and lambda inside init has same param name`() {
+            val code = """
+                class Foo(a: Int, b: Int) {
+                    init {
+                        fun foo() {
+                            listOf(1).map { a -> }
+                        }
+                        listOf(1).map { b -> }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).satisfiesExactlyInAnyOrder(
+                { assertThat(it).hasMessage("Name shadowed: a") },
+                { assertThat(it).hasMessage("Name shadowed: b") },
+            )
+        }
+
+        @Test
+        fun `does not report when same name is used in companion class and class`() {
+            val code = """
+                class Foo(val a: Int) {
+                    companion object {
+                        fun foo() {
+                            listOf(1).map { a -> }
+                        }
+                        fun bar(a: Int) {}
+                        init {
+                            listOf(1).map { a -> }
+                        }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report when same name used in object and class`() {
+            val code = """
+                class Foo(val a: Int) {
+                    object A {
+                        fun foo() {
+                            listOf(1).map { a -> }
+                        }
+                        fun bar(a: Int) {}
+                        init {
+                            listOf(1).map { a -> }
+                            fun foo() {
+                                listOf(1).map { a -> }
+                            }
+                        }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report when different name used in object and nested object`() {
+            val code = """
+                object A {
+                    val a: Int = 0
+                    object B {
+                        val b : Int = 0
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report when same name used in object and nested object`() {
+            val code = """
+                object A {
+                    val a: Int = 0
+                    object B {
+                        // A.a access
+                        val a : Int = 0
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report when different name used in object and nested object with class`() {
+            val code = """
+                class Abc(val a: Int, val b: Int) {
+                    object A {
+                        val a: Int = 0
+                        object B {
+                            val b : Int = 0
+                        }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `report shadowing variable with instance level prop`() {
+            val code = """
+                class A {
+                    val i: Int = 0
+                    fun test(i: Int) {
+                        // this access
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report shadowing variable with instance level extension prop`() {
+            val code = """
+                class A {
+                    val Int.i: Int
+                        get() = this
+                    fun test(i: Int) {
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report param from non inner class`() {
+            val code = """
+                class A(val a: Int, val b: Int, c: Int){
+                    class B(val a: Int = 0) {
+                        fun test(b: Int) {
+                            listOf(1).map { c ->  }
+                        }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report param from inner class separated by non inner class`() {
+            val code = """
+                class A(val a: Int, val b: Int, c: Int) {
+                    class B {
+                        inner class C(val a: Int = 0) {
+                            fun test(b: Int) {
+                                listOf(1).map { c -> }
+                            }
+                        }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report param from inner class`() {
+            val code = """
+                class A(val a: Int, val b: Int, val c: Int, val d: Int){
+                    inner class B(val a: Int = 0) {
+                        fun test(b: Int) {
+                            // this access
+                            listOf(1).map { c ->  }
+                        }
+                        init {
+                            listOf(1).map { d ->  }
+                        }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report param from inner class when class doesn't use var or val`() {
+            val code = """
+                class A(a: Int, b: Int, c: Int, d: Int, e: Int) {
+                    inner class B(val a: Int = 0) {
+                        fun test(b: Int) {
+                            listOf(1).map { c ->  }
+                        }
+                        init {
+                            listOf(1).map { d -> print(d) }
+                        }
+                        val e: Int = 0
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report param from nested inner class`() {
+            val code = """
+                class A(val a: Int, val b: Int, val c: Int, val d: Int){
+                    inner class B {
+                        // this access
+                        inner class C(val a: Int = 0) {
+                            fun test(b: Int) {
+                                listOf(1).map { c ->  }
+                            }
+                        }
+                        init {
+                            listOf(1).map { d ->  }
+                        }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report class inside function with different names`() {
+            val code = """
+                fun foo(a: Int, b: Int, c: Int, d: Int) {
+                    class A(val a1: Int) {
+                        fun bar(b1: Int) {
+                            listOf(1).map { c1 -> }
+                        }
+
+                        init {
+                            listOf(1).map { d1 -> }
+                        }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+
+        @Test
+        fun `does not report when anonymous object is used`() {
+            val code = """
+                fun test() {
+                    object {
+                        val a: Int = 0
+                        init {
+                            listOf<Int>().map { a ->
+                                // this access
+                            }
+                        }
+                    }
+                }
+            """.trimIndent()
+            val findings = subject.lintWithContext(env, code)
+            assertThat(findings).isEmpty()
+        }
+    }
 }
diff --git a/detekt-rules/src/test/kotlin/dev/detekt/rules/RuleProviderSpec.kt b/detekt-rules/src/test/kotlin/dev/detekt/rules/RuleProviderSpec.kt
index 05162124bfb0..10e9748c6d93 100644
--- a/detekt-rules/src/test/kotlin/dev/detekt/rules/RuleProviderSpec.kt
+++ b/detekt-rules/src/test/kotlin/dev/detekt/rules/RuleProviderSpec.kt
@@ -65,8 +65,8 @@ private fun getRulesPackageNameForProvider(providerType: Class<out RuleSetProvid
     return packageName!!
 }
 
-private fun getRules(provider: RuleSetProvider): List<Rule> {
-    val ruleSet = provider.instance()
+private fun getRules(ruleSetProvider: RuleSetProvider): List<Rule> {
+    val ruleSet = ruleSetProvider.instance()
     val rules = ruleSet.rules.map { (_, provider) -> provider(Config.empty) }
     assertThat(rules).isNotEmpty
     return rules
diff --git a/detekt-test/src/main/kotlin/dev/detekt/test/TestConfig.kt b/detekt-test/src/main/kotlin/dev/detekt/test/TestConfig.kt
index 56a82a669494..2739c27588c0 100644
--- a/detekt-test/src/main/kotlin/dev/detekt/test/TestConfig.kt
+++ b/detekt-test/src/main/kotlin/dev/detekt/test/TestConfig.kt
@@ -13,7 +13,10 @@ class TestConfig(override val parent: Config?, vararg pairs: Pair<String, Any>)
 
     constructor(vararg pairs: Pair<String, Any>) : this(Config.empty, *pairs)
 
-    override fun subConfig(key: String) = TestConfig(this, *values.map { (key, value) -> key to value }.toTypedArray())
+    override fun subConfig(key: String) = TestConfig(
+        this,
+        *values.map { (keyInValues, value) -> keyInValues to value }.toTypedArray()
+    )
 
     override fun subConfigKeys(): Set<String> = values.keys