Introduce "Redundant 'asSequence' call" inspections
#KT-35893 Fixed
This commit is contained in:
committed by
igoriakovlev
parent
e2c3455445
commit
afd544cbab
@@ -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.method.name=Logger factory method name
|
||||||
logger.factory.class.name=Logger factory class name
|
logger.factory.class.name=Logger factory class name
|
||||||
choose.logger.factory.class=Choose logger factory class
|
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
|
codestyle.layout.import.aliases.separately=Import aliases separately
|
||||||
button.add.package=Add Package
|
button.add.package=Add Package
|
||||||
listbox.import.package=Package
|
listbox.import.package=Package
|
||||||
|
|||||||
@@ -3324,6 +3324,14 @@
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
key="inspection.logger.initialized.with.foreign.class.display.name" bundle="messages.KotlinBundle"/>
|
key="inspection.logger.initialized.with.foreign.class.display.name" bundle="messages.KotlinBundle"/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.collections.RedundantAsSequenceInspection"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Style issues"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="WEAK WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
key="inspection.redundant.assequence.call" bundle="messages.KotlinBundle"/>
|
||||||
|
|
||||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightWorkspaceSettings"/>
|
<projectService serviceImplementation="org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightWorkspaceSettings"/>
|
||||||
|
|
||||||
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightSettings"/>
|
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.codeInsight.KotlinCodeInsightSettings"/>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
This inspection reports redundant 'asSequence()' call that can never have a positive performance effect.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+7
-5
@@ -236,10 +236,9 @@ private val transformations = listOf(
|
|||||||
"windowed",
|
"windowed",
|
||||||
"withIndex",
|
"withIndex",
|
||||||
"zipWithNext"
|
"zipWithNext"
|
||||||
).associate { it to FqName("kotlin.collections.$it") }
|
).associateWith { FqName("kotlin.collections.$it") }
|
||||||
|
|
||||||
@NonNls
|
internal val collectionTerminationFunctionNames = listOf(
|
||||||
private val terminations = listOf(
|
|
||||||
"all",
|
"all",
|
||||||
"any",
|
"any",
|
||||||
"asIterable",
|
"asIterable",
|
||||||
@@ -303,9 +302,12 @@ private val terminations = listOf(
|
|||||||
"toSet",
|
"toSet",
|
||||||
"toSortedSet",
|
"toSortedSet",
|
||||||
"unzip"
|
"unzip"
|
||||||
).associate {
|
)
|
||||||
|
|
||||||
|
@NonNls
|
||||||
|
private val terminations = collectionTerminationFunctionNames.associateWith {
|
||||||
val pkg = if (it in listOf("contains", "indexOf", "lastIndexOf")) "kotlin.collections.List" else "kotlin.collections"
|
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" }
|
private val lazyTerminations = terminations.filter { (key, _) -> key == "groupingBy" }
|
||||||
|
|||||||
+67
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.jetbrains.kotlin.idea.inspections.collections.RedundantAsSequenceInspection
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(list: List<String>) {
|
||||||
|
list/*comment*/.<caret>asSequence().last()
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(list: List<String>) {
|
||||||
|
list/*comment*/.last()
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(list: List<String>) {
|
||||||
|
list
|
||||||
|
// comment
|
||||||
|
.<caret>asSequence()
|
||||||
|
.max()
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(list: List<String>) {
|
||||||
|
list
|
||||||
|
// comment
|
||||||
|
.max()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(list: List<String>) {
|
||||||
|
list.<caret>sorted().first()
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(list: List<String>?) {
|
||||||
|
list?.<caret>asSequence()?.first()
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(list: List<String>?) {
|
||||||
|
list?.first()
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(list: List<String>) {
|
||||||
|
list.<caret>asSequence().all { it.isBlank() }
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(list: List<String>) {
|
||||||
|
list.all { it.isBlank() }
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(set: Set<String>) {
|
||||||
|
set.<caret>asSequence().any { it.isBlank() }
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(set: Set<String>) {
|
||||||
|
set.any { it.isBlank() }
|
||||||
|
}
|
||||||
+43
@@ -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")
|
@TestMetadata("idea/testData/inspectionsLocal/collections/simplifiableCall")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user