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");