From afd544cbabf2ab46d0001dd2eeee38bcd2d01d91 Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Tue, 14 Jan 2020 17:54:21 +0900 Subject: [PATCH] Introduce "Redundant 'asSequence' call" inspections #KT-35893 Fixed --- .../messages/KotlinBundle.properties | 2 + idea/resources/META-INF/plugin-common.xml | 8 +++ .../RedundantAsSequence.html | 5 ++ .../ConvertCallChainIntoSequenceInspection.kt | 12 ++-- .../RedundantAsSequenceInspection.kt | 67 +++++++++++++++++++ .../redundantAsSequence/.inspection | 1 + .../redundantAsSequence/hasComment.kt | 4 ++ .../redundantAsSequence/hasComment.kt.after | 4 ++ .../redundantAsSequence/hasLineBreak.kt | 7 ++ .../redundantAsSequence/hasLineBreak.kt.after | 6 ++ .../redundantAsSequence/notTermination.kt | 5 ++ .../redundantAsSequence/nullable.kt | 4 ++ .../redundantAsSequence/nullable.kt.after | 4 ++ .../collections/redundantAsSequence/simple.kt | 4 ++ .../redundantAsSequence/simple.kt.after | 4 ++ .../redundantAsSequence/simple2.kt | 4 ++ .../redundantAsSequence/simple2.kt.after | 4 ++ .../LocalInspectionTestGenerated.java | 43 ++++++++++++ 18 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 idea/resources/inspectionDescriptions/RedundantAsSequence.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/collections/RedundantAsSequenceInspection.kt create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/.inspection create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/notTermination.kt create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt create mode 100644 idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt.after diff --git a/idea/resources-en/messages/KotlinBundle.properties b/idea/resources-en/messages/KotlinBundle.properties index c8dbcebd0cf..e1505b05311 100644 --- a/idea/resources-en/messages/KotlinBundle.properties +++ b/idea/resources-en/messages/KotlinBundle.properties @@ -2217,6 +2217,8 @@ logger.initialized.with.foreign.class=Logger initialized with foreign class ''{0 logger.factory.method.name=Logger factory method name logger.factory.class.name=Logger factory class name choose.logger.factory.class=Choose logger factory class +inspection.redundant.assequence.call=Redundant 'asSequence' call +remove.assequence.call.fix.text=Remove 'asSequence' call codestyle.layout.import.aliases.separately=Import aliases separately button.add.package=Add Package listbox.import.package=Package diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index 8f210092dcb..6d6a85e76b7 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -3324,6 +3324,14 @@ language="kotlin" key="inspection.logger.initialized.with.foreign.class.display.name" bundle="messages.KotlinBundle"/> + + diff --git a/idea/resources/inspectionDescriptions/RedundantAsSequence.html b/idea/resources/inspectionDescriptions/RedundantAsSequence.html new file mode 100644 index 00000000000..9b8026dea25 --- /dev/null +++ b/idea/resources/inspectionDescriptions/RedundantAsSequence.html @@ -0,0 +1,5 @@ + + +This inspection reports redundant 'asSequence()' call that can never have a positive performance effect. + + \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt index 755a87db331..9ac3299aa8e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt @@ -236,10 +236,9 @@ private val transformations = listOf( "windowed", "withIndex", "zipWithNext" -).associate { it to FqName("kotlin.collections.$it") } +).associateWith { FqName("kotlin.collections.$it") } -@NonNls -private val terminations = listOf( +internal val collectionTerminationFunctionNames = listOf( "all", "any", "asIterable", @@ -303,9 +302,12 @@ private val terminations = listOf( "toSet", "toSortedSet", "unzip" -).associate { +) + +@NonNls +private val terminations = collectionTerminationFunctionNames.associateWith { val pkg = if (it in listOf("contains", "indexOf", "lastIndexOf")) "kotlin.collections.List" else "kotlin.collections" - it to FqName("$pkg.$it") + FqName("$pkg.$it") } private val lazyTerminations = terminations.filter { (key, _) -> key == "groupingBy" } diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RedundantAsSequenceInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RedundantAsSequenceInspection.kt new file mode 100644 index 00000000000..8ded0684c11 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RedundantAsSequenceInspection.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.collections + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection +import org.jetbrains.kotlin.idea.intentions.callExpression +import org.jetbrains.kotlin.idea.util.CommentSaver +import org.jetbrains.kotlin.idea.util.textRangeIn +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +class RedundantAsSequenceInspection : AbstractKotlinInspection() { + companion object { + private val asSequenceFqName = FqName("kotlin.collections.asSequence") + private val terminations = collectionTerminationFunctionNames.associateWith { FqName("kotlin.sequences.$it") } + } + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = qualifiedExpressionVisitor(fun(qualified) { + val call = qualified.callExpression ?: return + val callee = call.calleeExpression ?: return + if (callee.text != "asSequence") return + val parentCall = qualified.getQualifiedExpressionForReceiver()?.callExpression ?: return + + val context = qualified.analyze(BodyResolveMode.PARTIAL) + if (call.getResolvedCall(context)?.isCalling(asSequenceFqName) != true) return + if (!parentCall.isTermination(context)) return + + holder.registerProblem( + qualified, + callee.textRangeIn(qualified), + KotlinBundle.message("inspection.redundant.assequence.call"), + RemoveAsSequenceFix() + ) + }) + + private fun KtCallExpression.isTermination(context: BindingContext): Boolean { + val fqName = terminations[calleeExpression?.text] ?: return false + return isCalling(fqName, context) + } + + private class RemoveAsSequenceFix : LocalQuickFix { + override fun getName() = KotlinBundle.message("remove.assequence.call.fix.text") + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val qualified = descriptor.psiElement as? KtQualifiedExpression ?: return + val commentSaver = CommentSaver(qualified) + val replaced = qualified.replaced(qualified.receiverExpression) + commentSaver.restore(replaced) + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/.inspection b/idea/testData/inspectionsLocal/collections/redundantAsSequence/.inspection new file mode 100644 index 00000000000..75e1018fa2e --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.collections.RedundantAsSequenceInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt new file mode 100644 index 00000000000..3bdc30c670a --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(list: List) { + list/*comment*/.asSequence().last() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt.after b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt.after new file mode 100644 index 00000000000..a96f6ca0a73 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(list: List) { + list/*comment*/.last() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt new file mode 100644 index 00000000000..01fd7693326 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun test(list: List) { + list + // comment + .asSequence() + .max() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt.after b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt.after new file mode 100644 index 00000000000..2a3acec2fa8 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun test(list: List) { + list + // comment + .max() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/notTermination.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/notTermination.kt new file mode 100644 index 00000000000..665a212bed4 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/notTermination.kt @@ -0,0 +1,5 @@ +// PROBLEM: none +// WITH_RUNTIME +fun test(list: List) { + list.sorted().first() +} diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt new file mode 100644 index 00000000000..f529d04440b --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(list: List?) { + list?.asSequence()?.first() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt.after b/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt.after new file mode 100644 index 00000000000..58313ab07ea --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(list: List?) { + list?.first() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt new file mode 100644 index 00000000000..c024f767611 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(list: List) { + list.asSequence().all { it.isBlank() } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt.after b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt.after new file mode 100644 index 00000000000..c7bbb898d70 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(list: List) { + list.all { it.isBlank() } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt new file mode 100644 index 00000000000..2b5fa744c0c --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(set: Set) { + set.asSequence().any { it.isBlank() } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt.after b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt.after new file mode 100644 index 00000000000..7997d81689f --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(set: Set) { + set.any { it.isBlank() } +} \ 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 687f52f1ba9..272f5fc6678 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -1479,6 +1479,49 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/collections/redundantAsSequence") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RedundantAsSequence extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInRedundantAsSequence() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/inspectionsLocal/collections/redundantAsSequence"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true); + } + + @TestMetadata("hasComment.kt") + public void testHasComment() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/hasComment.kt"); + } + + @TestMetadata("hasLineBreak.kt") + public void testHasLineBreak() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/hasLineBreak.kt"); + } + + @TestMetadata("notTermination.kt") + public void testNotTermination() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/notTermination.kt"); + } + + @TestMetadata("nullable.kt") + public void testNullable() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/nullable.kt"); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/simple.kt"); + } + + @TestMetadata("simple2.kt") + public void testSimple2() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/redundantAsSequence/simple2.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/collections/simplifiableCall") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)