For each parameter not used: introduce "replace with repeat"

An enhancement for KT-27209
This commit is contained in:
Mikhail Glukhikh
2018-12-06 17:23:05 +03:00
parent e06c995516
commit 11909a86a8
10 changed files with 85 additions and 1 deletions
@@ -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<LocalQuickFix>()
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
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// FIX: Introduce anonymous parameter
fun test(list: List<String>) {
list.for<caret>Each {}
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// FIX: Introduce anonymous parameter
fun test(list: List<String>) {
list.forEach { _ -> }
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// FIX: Replace with 'repeat(size)'
fun test(list: List<String>) {
list.for<caret>Each {}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// FIX: Replace with 'repeat(size)'
fun test(list: List<String>) {
repeat(list.size) {}
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun test(list: List<String>?) {
list?.for<caret>Each {}
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun test(list: List<String>?) {
list?.forEach { _ -> }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// FIX: Replace with 'repeat(size)'
fun test(sequence: Sequence<String>) {
sequence.for<caret>Each {}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// FIX: Replace with 'repeat(size)'
fun test(sequence: Sequence<String>) {
repeat(sequence.count()) {}
}
@@ -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")