From 11909a86a892eb0cea15af060957e022f16a1770 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Thu, 6 Dec 2018 17:23:05 +0300 Subject: [PATCH] For each parameter not used: introduce "replace with repeat" An enhancement for KT-27209 --- .../ForEachParameterNotUsedInspection.kt | 35 ++++++++++++++++++- .../forEachParameterNotUsed/normal.kt | 1 + .../forEachParameterNotUsed/normal.kt.after | 1 + .../forEachParameterNotUsed/repeat.kt | 6 ++++ .../forEachParameterNotUsed/repeat.kt.after | 6 ++++ .../forEachParameterNotUsed/safe.kt | 5 +++ .../forEachParameterNotUsed/safe.kt.after | 5 +++ .../forEachParameterNotUsed/sequence.kt | 6 ++++ .../forEachParameterNotUsed/sequence.kt.after | 6 ++++ .../LocalInspectionTestGenerated.java | 15 ++++++++ 10 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 idea/testData/inspectionsLocal/forEachParameterNotUsed/repeat.kt create mode 100644 idea/testData/inspectionsLocal/forEachParameterNotUsed/repeat.kt.after create mode 100644 idea/testData/inspectionsLocal/forEachParameterNotUsed/safe.kt create mode 100644 idea/testData/inspectionsLocal/forEachParameterNotUsed/safe.kt.after create mode 100644 idea/testData/inspectionsLocal/forEachParameterNotUsed/sequence.kt create mode 100644 idea/testData/inspectionsLocal/forEachParameterNotUsed/sequence.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ForEachParameterNotUsedInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ForEachParameterNotUsedInspection.kt index 8030b3c9d18..d42fa265bf6 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/ForEachParameterNotUsedInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ForEachParameterNotUsedInspection.kt @@ -11,14 +11,20 @@ import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.project.Project import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl.WithDestructuringDeclaration import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall +import org.jetbrains.kotlin.idea.core.moveFunctionLiteralOutsideParentheses +import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.intentions.SpecifyExplicitLambdaSignatureIntention +import org.jetbrains.kotlin.idea.project.builtIns 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.DescriptorUtils import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull @@ -45,11 +51,16 @@ class ForEachParameterNotUsedInspection : AbstractKotlinInspection() { if (iterableParameter !is WithDestructuringDeclaration && !lambda.bodyExpression.usesDescriptor(iterableParameter, context) ) { + val fixes = mutableListOf() + if (it.parent is KtDotQualifiedExpression) { + fixes += ReplaceWithRepeatFix() + } + fixes += IntroduceAnonymousParameterFix() holder.registerProblem( calleeExpression, "Loop parameter '${iterableParameter.getThisLabelName()}' is unused", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - IntroduceAnonymousParameterFix() + *fixes.toTypedArray() ) } } @@ -71,6 +82,28 @@ class ForEachParameterNotUsedInspection : AbstractKotlinInspection() { } } + private class ReplaceWithRepeatFix : LocalQuickFix { + override fun getFamilyName() = "Replace with 'repeat(size)'" + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val callExpression = descriptor.psiElement.parent as? KtCallExpression ?: return + val qualifiedExpression = callExpression.parent as? KtDotQualifiedExpression ?: return + val receiverExpression = qualifiedExpression.receiverExpression + val receiverClass = + receiverExpression.resolveToCall()?.resultingDescriptor?.returnType?.constructor?.declarationDescriptor as? ClassDescriptor + val collection = callExpression.builtIns.collection + val sizeText = when { + receiverClass != null && DescriptorUtils.isSubclass(receiverClass, collection) -> "size" + else -> "count()" + } + val lambdaExpression = callExpression.lambdaArguments.singleOrNull()?.getArgumentExpression() ?: return + val replacement = + KtPsiFactory(project).createExpressionByPattern("repeat($0.$sizeText, $1)", receiverExpression, lambdaExpression) + val result = qualifiedExpression.replaced(replacement) as KtCallExpression + result.moveFunctionLiteralOutsideParentheses() + } + } + private fun KtBlockExpression?.usesDescriptor(descriptor: VariableDescriptor, context: BindingContext): Boolean { if (this == null) return false var used = false diff --git a/idea/testData/inspectionsLocal/forEachParameterNotUsed/normal.kt b/idea/testData/inspectionsLocal/forEachParameterNotUsed/normal.kt index c4da8fc0000..dd9a1e5eac0 100644 --- a/idea/testData/inspectionsLocal/forEachParameterNotUsed/normal.kt +++ b/idea/testData/inspectionsLocal/forEachParameterNotUsed/normal.kt @@ -1,4 +1,5 @@ // WITH_RUNTIME +// FIX: Introduce anonymous parameter fun test(list: List) { list.forEach {} diff --git a/idea/testData/inspectionsLocal/forEachParameterNotUsed/normal.kt.after b/idea/testData/inspectionsLocal/forEachParameterNotUsed/normal.kt.after index f4716c40756..a390c37c24a 100644 --- a/idea/testData/inspectionsLocal/forEachParameterNotUsed/normal.kt.after +++ b/idea/testData/inspectionsLocal/forEachParameterNotUsed/normal.kt.after @@ -1,4 +1,5 @@ // WITH_RUNTIME +// FIX: Introduce anonymous parameter fun test(list: List) { list.forEach { _ -> } diff --git a/idea/testData/inspectionsLocal/forEachParameterNotUsed/repeat.kt b/idea/testData/inspectionsLocal/forEachParameterNotUsed/repeat.kt new file mode 100644 index 00000000000..12d9cd1d434 --- /dev/null +++ b/idea/testData/inspectionsLocal/forEachParameterNotUsed/repeat.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// FIX: Replace with 'repeat(size)' + +fun test(list: List) { + list.forEach {} +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/forEachParameterNotUsed/repeat.kt.after b/idea/testData/inspectionsLocal/forEachParameterNotUsed/repeat.kt.after new file mode 100644 index 00000000000..c46460b8cc2 --- /dev/null +++ b/idea/testData/inspectionsLocal/forEachParameterNotUsed/repeat.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// FIX: Replace with 'repeat(size)' + +fun test(list: List) { + repeat(list.size) {} +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/forEachParameterNotUsed/safe.kt b/idea/testData/inspectionsLocal/forEachParameterNotUsed/safe.kt new file mode 100644 index 00000000000..11acec11cc2 --- /dev/null +++ b/idea/testData/inspectionsLocal/forEachParameterNotUsed/safe.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun test(list: List?) { + list?.forEach {} +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/forEachParameterNotUsed/safe.kt.after b/idea/testData/inspectionsLocal/forEachParameterNotUsed/safe.kt.after new file mode 100644 index 00000000000..633869bb639 --- /dev/null +++ b/idea/testData/inspectionsLocal/forEachParameterNotUsed/safe.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun test(list: List?) { + list?.forEach { _ -> } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/forEachParameterNotUsed/sequence.kt b/idea/testData/inspectionsLocal/forEachParameterNotUsed/sequence.kt new file mode 100644 index 00000000000..135dda859a5 --- /dev/null +++ b/idea/testData/inspectionsLocal/forEachParameterNotUsed/sequence.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// FIX: Replace with 'repeat(size)' + +fun test(sequence: Sequence) { + sequence.forEach {} +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/forEachParameterNotUsed/sequence.kt.after b/idea/testData/inspectionsLocal/forEachParameterNotUsed/sequence.kt.after new file mode 100644 index 00000000000..87f0580075f --- /dev/null +++ b/idea/testData/inspectionsLocal/forEachParameterNotUsed/sequence.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +// FIX: Replace with 'repeat(size)' + +fun test(sequence: Sequence) { + repeat(sequence.count()) {} +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 1f77a160ae8..62ab1dd17a4 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -2542,6 +2542,21 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { public void testNormal() throws Exception { runTest("idea/testData/inspectionsLocal/forEachParameterNotUsed/normal.kt"); } + + @TestMetadata("repeat.kt") + public void testRepeat() throws Exception { + runTest("idea/testData/inspectionsLocal/forEachParameterNotUsed/repeat.kt"); + } + + @TestMetadata("safe.kt") + public void testSafe() throws Exception { + runTest("idea/testData/inspectionsLocal/forEachParameterNotUsed/safe.kt"); + } + + @TestMetadata("sequence.kt") + public void testSequence() throws Exception { + runTest("idea/testData/inspectionsLocal/forEachParameterNotUsed/sequence.kt"); + } } @TestMetadata("idea/testData/inspectionsLocal/ImplicitNullableNothingType")