diff --git a/idea/resources/inspectionDescriptions/ForEachParameterNotUsed.html b/idea/resources/inspectionDescriptions/ForEachParameterNotUsed.html
new file mode 100644
index 00000000000..280af78a53e
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/ForEachParameterNotUsed.html
@@ -0,0 +1,5 @@
+
+
+This inspection guards against useless forEach loops by identifying when the iterable's values are not used.
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 77000e98e68..195f4b81acd 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -2924,6 +2924,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
language="kotlin"
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
+ val lambda = it.lambdaArguments.singleOrNull()?.getLambdaExpression() ?: return@callExpressionVisitor
+ val descriptor = lambda.analyze()[BindingContext.FUNCTION, lambda.functionLiteral] ?: return@callExpressionVisitor
+ val iterableParameter = descriptor.valueParameters.singleOrNull() ?: return@callExpressionVisitor
+
+ if (lambda.bodyExpression?.usesDescriptor(iterableParameter) != true && it.calleeExpression != null) {
+ holder.registerProblem(
+ it.calleeExpression!!,
+ "Loop parameter '${iterableParameter.getThisLabelName()}' is unused",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun KtBlockExpression.usesDescriptor(descriptor: VariableDescriptor): Boolean {
+ var used = false
+ acceptChildren(object : KtVisitorVoid() {
+ override fun visitKtElement(element: KtElement) {
+ if (!used) {
+ if (element.children.isNotEmpty()) {
+ element.acceptChildren(this)
+ } else {
+ val bindingContext = element.analyze()
+ val resolvedCall = element.getResolvedCall(bindingContext) ?: return
+
+ used = resolvedCall.candidateDescriptor == descriptor
+ }
+ }
+ }
+ })
+ return used
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspections/forEachParameterNotUsed/correctReference.kt b/idea/testData/inspections/forEachParameterNotUsed/correctReference.kt
new file mode 100644
index 00000000000..e0f667413d9
--- /dev/null
+++ b/idea/testData/inspections/forEachParameterNotUsed/correctReference.kt
@@ -0,0 +1,10 @@
+fun test() {
+ val items = sequenceOf()
+ items.forEach {
+ ForEachable().forEach {}
+ }
+}
+
+class ForEachable {
+ fun forEach(action: (ForEachable) -> Unit) {}
+}
\ No newline at end of file
diff --git a/idea/testData/inspections/forEachParameterNotUsed/inspectionData/expected.xml b/idea/testData/inspections/forEachParameterNotUsed/inspectionData/expected.xml
new file mode 100644
index 00000000000..4d2af48e66b
--- /dev/null
+++ b/idea/testData/inspections/forEachParameterNotUsed/inspectionData/expected.xml
@@ -0,0 +1,34 @@
+
+
+ test.kt
+ 3
+ light_idea_test_case
+
+ Iterated elements are not used in forEach
+ Loop parameter 'it' is unused
+
+
+ test.kt
+ 4
+ light_idea_test_case
+
+ Iterated elements are not used in forEach
+ Loop parameter 'item' is unused
+
+
+ test.kt
+ 7
+ light_idea_test_case
+
+ Iterated elements are not used in forEach
+ Loop parameter 'it' is unused
+
+
+ correctReference.kt
+ 3
+ light_idea_test_case
+
+ Iterated elements are not used in forEach
+ Loop parameter 'it' is unused
+
+
\ No newline at end of file
diff --git a/idea/testData/inspections/forEachParameterNotUsed/inspectionData/inspections.test b/idea/testData/inspections/forEachParameterNotUsed/inspectionData/inspections.test
new file mode 100644
index 00000000000..ca92ab2769f
--- /dev/null
+++ b/idea/testData/inspections/forEachParameterNotUsed/inspectionData/inspections.test
@@ -0,0 +1,2 @@
+// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.ForEachParameterNotUsedInspection
+// WITH_RUNTIME
diff --git a/idea/testData/inspections/forEachParameterNotUsed/test.kt b/idea/testData/inspections/forEachParameterNotUsed/test.kt
new file mode 100644
index 00000000000..25ec0a23f80
--- /dev/null
+++ b/idea/testData/inspections/forEachParameterNotUsed/test.kt
@@ -0,0 +1,11 @@
+fun test() {
+ val items = listOf()
+ items.forEach { }
+ items.forEach { item -> }
+ items.forEach { doSomething(it) }
+ items.forEach { item -> doSomething(item) }
+ items.forEach { items.forEach { doSomething(it) } }
+ items.forEach { items.forEach { thing -> doSomething(it); doSomething(thing) } }
+}
+
+fun doSomething(item: Any) {}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
index ba3901559e2..669d0ea338f 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java
@@ -169,6 +169,11 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
runTest("idea/testData/inspections/equalsAndHashCode/inspectionData/inspections.test");
}
+ @TestMetadata("forEachParameterNotUsed/inspectionData/inspections.test")
+ public void testForEachParameterNotUsed_inspectionData_Inspections_test() throws Exception {
+ runTest("idea/testData/inspections/forEachParameterNotUsed/inspectionData/inspections.test");
+ }
+
@TestMetadata("gradleWrongPluginVersion/inspectionData/inspections.test")
public void testGradleWrongPluginVersion_inspectionData_Inspections_test() throws Exception {
runTest("idea/testData/inspections/gradleWrongPluginVersion/inspectionData/inspections.test");