Introduce inspection for determining if a forEach parameter is unused
#KT-22068 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
c3b2d1829f
commit
e81eee4cc1
@@ -0,0 +1,5 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
This inspection guards against useless forEach loops by identifying when the iterable's values are not used.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -2924,6 +2924,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ForEachParameterNotUsedInspection"
|
||||||
|
displayName="Iterated elements are not used in forEach"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Probable bugs"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
||||||
displayName="Nested lambda has shadowed implicit parameter"
|
displayName="Nested lambda has shadowed implicit parameter"
|
||||||
groupPath="Kotlin"
|
groupPath="Kotlin"
|
||||||
|
|||||||
@@ -2923,6 +2923,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ForEachParameterNotUsedInspection"
|
||||||
|
displayName="Iterated elements are not used in forEach"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Probable bugs"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
||||||
displayName="Nested lambda has shadowed implicit parameter"
|
displayName="Nested lambda has shadowed implicit parameter"
|
||||||
groupPath="Kotlin"
|
groupPath="Kotlin"
|
||||||
|
|||||||
@@ -2923,6 +2923,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ForEachParameterNotUsedInspection"
|
||||||
|
displayName="Iterated elements are not used in forEach"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Probable bugs"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
||||||
displayName="Nested lambda has shadowed implicit parameter"
|
displayName="Nested lambda has shadowed implicit parameter"
|
||||||
groupPath="Kotlin"
|
groupPath="Kotlin"
|
||||||
|
|||||||
@@ -2924,6 +2924,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ForEachParameterNotUsedInspection"
|
||||||
|
displayName="Iterated elements are not used in forEach"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Probable bugs"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
||||||
displayName="Nested lambda has shadowed implicit parameter"
|
displayName="Nested lambda has shadowed implicit parameter"
|
||||||
groupPath="Kotlin"
|
groupPath="Kotlin"
|
||||||
|
|||||||
@@ -2923,6 +2923,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ForEachParameterNotUsedInspection"
|
||||||
|
displayName="Iterated elements are not used in forEach"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Probable bugs"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
||||||
displayName="Nested lambda has shadowed implicit parameter"
|
displayName="Nested lambda has shadowed implicit parameter"
|
||||||
groupPath="Kotlin"
|
groupPath="Kotlin"
|
||||||
|
|||||||
@@ -2923,6 +2923,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ForEachParameterNotUsedInspection"
|
||||||
|
displayName="Iterated elements are not used in forEach"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Probable bugs"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
||||||
displayName="Nested lambda has shadowed implicit parameter"
|
displayName="Nested lambda has shadowed implicit parameter"
|
||||||
groupPath="Kotlin"
|
groupPath="Kotlin"
|
||||||
|
|||||||
@@ -2924,6 +2924,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ForEachParameterNotUsedInspection"
|
||||||
|
displayName="Iterated elements are not used in forEach"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Probable bugs"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NestedLambdaShadowedImplicitParameterInspection"
|
||||||
displayName="Nested lambda has shadowed implicit parameter"
|
displayName="Nested lambda has shadowed implicit parameter"
|
||||||
groupPath="Kotlin"
|
groupPath="Kotlin"
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.inspections
|
||||||
|
|
||||||
|
import com.intellij.codeInspection.ProblemHighlightType
|
||||||
|
import com.intellij.codeInspection.ProblemsHolder
|
||||||
|
import com.intellij.psi.PsiElementVisitor
|
||||||
|
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.getCallableDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.getThisLabelName
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||||
|
|
||||||
|
class ForEachParameterNotUsedInspection : AbstractKotlinInspection() {
|
||||||
|
companion object {
|
||||||
|
private val COLLECTIONS_FOREACH_FQNAME = FqName("kotlin.collections.forEach")
|
||||||
|
private val SEQUENCES_FOREACH_FQNAME = FqName("kotlin.sequences.forEach")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||||
|
return callExpressionVisitor {
|
||||||
|
val calleeExpression = it.calleeExpression as? KtNameReferenceExpression ?: return@callExpressionVisitor
|
||||||
|
if (calleeExpression.getReferencedName() != "forEach") return@callExpressionVisitor
|
||||||
|
when (it.getCallableDescriptor()?.fqNameOrNull()) {
|
||||||
|
COLLECTIONS_FOREACH_FQNAME, SEQUENCES_FOREACH_FQNAME -> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
fun test() {
|
||||||
|
val items = sequenceOf<Any>()
|
||||||
|
items.forEach {
|
||||||
|
ForEachable().forEach {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ForEachable {
|
||||||
|
fun forEach(action: (ForEachable) -> Unit) {}
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
<problems>
|
||||||
|
<problem>
|
||||||
|
<file>test.kt</file>
|
||||||
|
<line>3</line>
|
||||||
|
<module>light_idea_test_case</module>
|
||||||
|
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||||
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Iterated elements are not used in forEach</problem_class>
|
||||||
|
<description>Loop parameter 'it' is unused</description>
|
||||||
|
</problem>
|
||||||
|
<problem>
|
||||||
|
<file>test.kt</file>
|
||||||
|
<line>4</line>
|
||||||
|
<module>light_idea_test_case</module>
|
||||||
|
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||||
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Iterated elements are not used in forEach</problem_class>
|
||||||
|
<description>Loop parameter 'item' is unused</description>
|
||||||
|
</problem>
|
||||||
|
<problem>
|
||||||
|
<file>test.kt</file>
|
||||||
|
<line>7</line>
|
||||||
|
<module>light_idea_test_case</module>
|
||||||
|
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||||
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Iterated elements are not used in forEach</problem_class>
|
||||||
|
<description>Loop parameter 'it' is unused</description>
|
||||||
|
</problem>
|
||||||
|
<problem>
|
||||||
|
<file>correctReference.kt</file>
|
||||||
|
<line>3</line>
|
||||||
|
<module>light_idea_test_case</module>
|
||||||
|
<entry_point TYPE="file" FQNAME="temp:///src/correctReference.kt" />
|
||||||
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Iterated elements are not used in forEach</problem_class>
|
||||||
|
<description>Loop parameter 'it' is unused</description>
|
||||||
|
</problem>
|
||||||
|
</problems>
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.ForEachParameterNotUsedInspection
|
||||||
|
// WITH_RUNTIME
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fun test() {
|
||||||
|
val items = listOf<Any>()
|
||||||
|
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) {}
|
||||||
+5
@@ -169,6 +169,11 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
|
|||||||
runTest("idea/testData/inspections/equalsAndHashCode/inspectionData/inspections.test");
|
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")
|
@TestMetadata("gradleWrongPluginVersion/inspectionData/inspections.test")
|
||||||
public void testGradleWrongPluginVersion_inspectionData_Inspections_test() throws Exception {
|
public void testGradleWrongPluginVersion_inspectionData_Inspections_test() throws Exception {
|
||||||
runTest("idea/testData/inspections/gradleWrongPluginVersion/inspectionData/inspections.test");
|
runTest("idea/testData/inspections/gradleWrongPluginVersion/inspectionData/inspections.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user