From 108dea5b4631104467fa531e3b7e79363c7968a7 Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Fri, 29 Jun 2018 18:52:14 +0300 Subject: [PATCH] Introduce "Call chain on collection should be converted into 'Sequence'" So #KT-15476 Fixed --- .../ConvertCallChainIntoSequence.html | 5 + idea/src/META-INF/plugin.xml | 9 + idea/src/META-INF/plugin.xml.173 | 9 + idea/src/META-INF/plugin.xml.181 | 9 + idea/src/META-INF/plugin.xml.as31 | 9 + idea/src/META-INF/plugin.xml.as32 | 9 + idea/src/META-INF/plugin.xml.as33 | 9 + .../ConvertCallChainIntoSequenceInspection.kt | 171 ++++++++++++++++++ .../convertCallChainIntoSequence/.inspection | 1 + .../convertCallChainIntoSequence/all.kt | 35 ++++ .../convertCallChainIntoSequence/all.kt.after | 37 ++++ .../convertCallChainIntoSequence/comment.kt | 8 + .../comment.kt.after | 9 + .../convertCallChainIntoSequence/comment2.kt | 7 + .../comment2.kt.after | 9 + .../noTargetCallChain.kt | 6 + .../noTargetCallChain2.kt | 6 + .../noTargetCallChain3.kt | 11 ++ .../convertCallChainIntoSequence/nullable.kt | 7 + .../nullable.kt.after | 9 + .../convertCallChainIntoSequence/nullable2.kt | 5 + .../nullable2.kt.after | 5 + .../convertCallChainIntoSequence/return.kt | 11 ++ .../returnAtLabels.kt | 10 + .../returnAtLabels.kt.after | 12 ++ .../convertCallChainIntoSequence/simple.kt | 5 + .../simple.kt.after | 5 + .../convertCallChainIntoSequence/simple2.kt | 7 + .../simple2.kt.after | 9 + .../convertCallChainIntoSequence/simple3.kt | 10 + .../simple3.kt.after | 12 ++ .../convertCallChainIntoSequence/simple4.kt | 11 ++ .../simple4.kt.after | 13 ++ .../termination.kt | 8 + .../termination.kt.after | 9 + .../termination2.kt | 8 + .../termination2.kt.after | 10 + .../termination3.kt | 10 + .../termination3.kt.after | 12 ++ .../termination4.kt | 8 + .../termination4.kt.after | 10 + .../LocalInspectionTestGenerated.java | 103 +++++++++++ 42 files changed, 668 insertions(+) create mode 100644 idea/resources/inspectionDescriptions/ConvertCallChainIntoSequence.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/.inspection create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/all.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/all.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment2.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment2.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain2.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain3.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable2.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable2.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/return.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/returnAtLabels.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/returnAtLabels.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple2.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple2.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple3.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple3.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple4.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple4.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination2.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination2.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination3.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination3.kt.after create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination4.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination4.kt.after diff --git a/idea/resources/inspectionDescriptions/ConvertCallChainIntoSequence.html b/idea/resources/inspectionDescriptions/ConvertCallChainIntoSequence.html new file mode 100644 index 00000000000..ecee8462150 --- /dev/null +++ b/idea/resources/inspectionDescriptions/ConvertCallChainIntoSequence.html @@ -0,0 +1,5 @@ + + +This inspection reports call chain on collection should be converted into Sequence. + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 8a852cfa69b..538118ae80e 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2967,6 +2967,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173 index 694fcda736b..5ed173c780d 100644 --- a/idea/src/META-INF/plugin.xml.173 +++ b/idea/src/META-INF/plugin.xml.173 @@ -2966,6 +2966,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.181 b/idea/src/META-INF/plugin.xml.181 index c44273b2ddc..b4c23f5ce71 100644 --- a/idea/src/META-INF/plugin.xml.181 +++ b/idea/src/META-INF/plugin.xml.181 @@ -2966,6 +2966,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31 index 3f778c6c6f5..ce31b9a9528 100644 --- a/idea/src/META-INF/plugin.xml.as31 +++ b/idea/src/META-INF/plugin.xml.as31 @@ -2966,6 +2966,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32 index e02aae45ddf..811598e069f 100644 --- a/idea/src/META-INF/plugin.xml.as32 +++ b/idea/src/META-INF/plugin.xml.as32 @@ -2966,6 +2966,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as33 b/idea/src/META-INF/plugin.xml.as33 index 10ac958aa35..d709672fd4e 100644 --- a/idea/src/META-INF/plugin.xml.as33 +++ b/idea/src/META-INF/plugin.xml.as33 @@ -2967,6 +2967,15 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt new file mode 100644 index 00000000000..d8ac79de19e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt @@ -0,0 +1,171 @@ +/* + * 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.collections + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiWhiteSpace +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +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.intentions.getCallableDescriptor +import org.jetbrains.kotlin.idea.util.CommentSaver +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.siblings +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe + +class ConvertCallChainIntoSequenceInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = + qualifiedExpressionVisitor(fun(expression) { + val (targetQualified, targetCall) = expression.findTarget() ?: return + val rangeInElement = targetCall.calleeExpression?.textRange?.shiftRight(-targetQualified.startOffset) ?: return + holder.registerProblem( + holder.manager.createProblemDescriptor( + targetQualified, + rangeInElement, + "Call chain on collection should be converted into 'Sequence'", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + isOnTheFly, + ConvertCallChainIntoSequenceFix() + ) + ) + }) +} + +private class ConvertCallChainIntoSequenceFix : LocalQuickFix { + override fun getName() = "Convert call chain into 'Sequence'" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val expression = descriptor.psiElement as? KtQualifiedExpression ?: return + val calls = expression.collectCallExpression().reversed() + val firstCall = calls.firstOrNull() ?: return + val lastCall = calls.lastOrNull() ?: return + val first = firstCall.parent as? KtQualifiedExpression ?: return + val last = lastCall.parent as? KtQualifiedExpression ?: return + val next = last.parent as? KtQualifiedExpression + val nextType = next?.getCallableDescriptor()?.returnType + + val psiFactory = KtPsiFactory(expression) + val dot = buildString { + if (first.receiverExpression.siblings().filterIsInstance().any { it.textContains('\n') }) append("\n") + if (first is KtSafeQualifiedExpression) append("?") + append(".") + } + + val firstCommentSaver = CommentSaver(first) + val firstReplaced = first.replaced( + psiFactory.buildExpression { + appendExpression(first.receiverExpression) + appendFixedText(dot) + appendExpression(psiFactory.createExpression("asSequence()")) + appendFixedText(dot) + appendExpression(firstCall) + } + ) + firstCommentSaver.restore(firstReplaced) + + val nextDescriptor = next?.getCallableDescriptor() + if (nextDescriptor == null + || !nextDescriptor.fqNameSafe.asString().startsWith("kotlin.sequences.") + || nextDescriptor.returnType != nextType + ) { + val lastCommentSaver = CommentSaver(last) + val lastReplaced = last.replace( + psiFactory.buildExpression { + appendExpression(last) + appendFixedText(dot) + appendExpression(psiFactory.createExpression("toList()")) + } + ) + lastCommentSaver.restore(lastReplaced) + } + } +} + +private fun KtQualifiedExpression.findTarget(): Pair? { + if (parent is KtQualifiedExpression) return null + + val calls = collectCallExpression() + if (calls.isEmpty()) return null + + val qualified = calls.first().parent as? KtQualifiedExpression ?: return null + val fqName = qualified.getCallableDescriptor()?.returnType?.constructor?.declarationDescriptor?.fqNameSafe + if (fqName != KotlinBuiltIns.FQ_NAMES.list) return null + return qualified to calls.last() +} + +private fun KtQualifiedExpression.collectCallExpression(): List { + val calls = mutableListOf() + + fun collect(qualified: KtQualifiedExpression) { + val call = qualified.callExpression ?: return + calls.add(call) + val receiver = qualified.receiverExpression + if (receiver is KtQualifiedExpression) collect(receiver) + } + collect(this) + + if (calls.size < 2) return emptyList() + + val transformationCalls = calls + .asSequence() + .dropWhile { !it.isTransformation() } + .takeWhile { it.isTransformation() && !it.hasReturn() } + .toList() + if (transformationCalls.size < 2) return emptyList() + + return transformationCalls +} + +private fun KtCallExpression.hasReturn(): Boolean = valueArguments.any { arg -> + arg.anyDescendantOfType { it.labelQualifier == null } +} + +private fun KtCallExpression.isTransformation(): Boolean { + val fqName = transformations[calleeExpression?.text] ?: return false + return fqName == getCallableDescriptor()?.fqNameSafe +} + +private val transformations = listOf( + "chunked", + "distinct", + "distinctBy", + "drop", + "dropWhile", + "filter", + "filterIndexed", + "filterIsInstance", + "filterNot", + "filterNotNull", + "map", + "mapIndexed", + "mapIndexedNotNull", + "mapNotNull", + "minus", + "minusElement", + "onEach", + "plus", + "plusElement", + "requireNoNulls", + "sorted", + "sortedBy", + "sortedByDescending", + "sortedDescending", + "sortedWith", + "take", + "takeWhile", + "windowed", + "zipWithNext" +).associate { it to FqName("kotlin.collections.$it") } diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/.inspection b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/.inspection new file mode 100644 index 00000000000..4f43651193e --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.collections.ConvertCallChainIntoSequenceInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/all.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/all.kt new file mode 100644 index 00000000000..82621315f0f --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/all.kt @@ -0,0 +1,35 @@ +// WITH_RUNTIME + +fun test(list: List): List, List>> { + return list + .chunked(1) + .distinct() + .distinctBy { it } + .drop(1) + .dropWhile { true } + .filter { true } + .filterIndexed { index, list -> true } + .filterIsInstance() + .filterNot { true } + .filterNotNull() + .map { it } + .mapIndexed { index, i -> i } + .mapIndexedNotNull { index, i -> i } + .mapNotNull { it } + .minus(1) + .minusElement(1) + .onEach {} + .plus(1) + .plusElement(1) + .requireNoNulls() + .sorted() + .sortedBy { it } + .sortedByDescending { it } + .sortedDescending() + .sortedWith(Comparator { o1, o2 -> 0 }) + .take(1) + .takeWhile { true } + .windowed(1, 1, true) + .zipWithNext() + .zipWithNext { a, b -> a } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/all.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/all.kt.after new file mode 100644 index 00000000000..fc6aeb1424e --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/all.kt.after @@ -0,0 +1,37 @@ +// WITH_RUNTIME + +fun test(list: List): List, List>> { + return list + .asSequence() + .chunked(1) + .distinct() + .distinctBy { it } + .drop(1) + .dropWhile { true } + .filter { true } + .filterIndexed { index, list -> true } + .filterIsInstance() + .filterNot { true } + .filterNotNull() + .map { it } + .mapIndexed { index, i -> i } + .mapIndexedNotNull { index, i -> i } + .mapNotNull { it } + .minus(1) + .minusElement(1) + .onEach {} + .plus(1) + .plusElement(1) + .requireNoNulls() + .sorted() + .sortedBy { it } + .sortedByDescending { it } + .sortedDescending() + .sortedWith(Comparator { o1, o2 -> 0 }) + .take(1) + .takeWhile { true } + .windowed(1, 1, true) + .zipWithNext() + .zipWithNext { a, b -> a } + .toList() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment.kt new file mode 100644 index 00000000000..6aaf9d981af --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun test(list: List): Int { + return list // comment1 + .filter { it > 1 } // comment2 + .map { it * 2 } // comment3 + .sum() // comment4 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment.kt.after new file mode 100644 index 00000000000..28cdd42c077 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +fun test(list: List): Int { + return list // comment1 + .asSequence() + .filter { it > 1 } // comment2 + .map { it * 2 } // comment3 + .sum() // comment4 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment2.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment2.kt new file mode 100644 index 00000000000..26a49549cbe --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment2.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun test(list: List): List { + return list // comment1 + .filter { it > 1 } // comment2 + .map { it * 2 } // comment3 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment2.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment2.kt.after new file mode 100644 index 00000000000..039aa386f1c --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment2.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +fun test(list: List): List { + return list // comment1 + .asSequence() + .filter { it > 1 } // comment2 + .map { it * 2 } + .toList() // comment3 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain.kt new file mode 100644 index 00000000000..069532c93e9 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain.kt @@ -0,0 +1,6 @@ +// PROBLEM: none +// WITH_RUNTIME + +fun test(list: List): List { + return list.filter { it > 1 } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain2.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain2.kt new file mode 100644 index 00000000000..8e4f63c0a99 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain2.kt @@ -0,0 +1,6 @@ +// PROBLEM: none +// WITH_RUNTIME + +fun test(list: List): List { + return list.filter { it > 2 }.dropLast(1).filter { it > 2 } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain3.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain3.kt new file mode 100644 index 00000000000..6c85c7e8863 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain3.kt @@ -0,0 +1,11 @@ +// PROBLEM: none +// WITH_RUNTIME + +fun test(list: List): List { + return list + .reversed() + .map { it + 1 } + .map { it + 1 } + .dropLast(1) + .takeLast(2) +} diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable.kt new file mode 100644 index 00000000000..1fc731f55c8 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun test(list: List?): List? { + return list + ?.filter { it > 1 } + ?.map { it * 2 } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable.kt.after new file mode 100644 index 00000000000..9110c8ba000 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +fun test(list: List?): List? { + return list + ?.asSequence() + ?.filter { it > 1 } + ?.map { it * 2 } + ?.toList() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable2.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable2.kt new file mode 100644 index 00000000000..723877c5b30 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable2.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun test(list: List?): List? { + return list?.filter { it > 1 }!!.filter { it > 2 }.filter { it > 3 } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable2.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable2.kt.after new file mode 100644 index 00000000000..79d87da12c6 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable2.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun test(list: List?): List? { + return list?.filter { it > 1 }!!.asSequence().filter { it > 2 }.filter { it > 3 }.toList() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/return.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/return.kt new file mode 100644 index 00000000000..fff6dbbd7df --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/return.kt @@ -0,0 +1,11 @@ +// PROBLEM: none +// WITH_RUNTIME + +fun test(list: List): List { + return list + .filter { it > 1 } + .mapNotNull { + if (it == 2) return emptyList() + it * 2 + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/returnAtLabels.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/returnAtLabels.kt new file mode 100644 index 00000000000..d6141e2b2f4 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/returnAtLabels.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME + +fun test(list: List): List { + return list + .filter { it > 1 } + .mapNotNull { + if (it == 2) return@mapNotNull null + it * 2 + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/returnAtLabels.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/returnAtLabels.kt.after new file mode 100644 index 00000000000..e1462640ce1 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/returnAtLabels.kt.after @@ -0,0 +1,12 @@ +// WITH_RUNTIME + +fun test(list: List): List { + return list + .asSequence() + .filter { it > 1 } + .mapNotNull { + if (it == 2) return@mapNotNull null + it * 2 + } + .toList() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple.kt new file mode 100644 index 00000000000..1c3e5699f50 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun test(): List { + return listOf(1, 2, 3).filter { it > 1 }.map { it * 2 } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple.kt.after new file mode 100644 index 00000000000..5024242557e --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun test(): List { + return listOf(1, 2, 3).asSequence().filter { it > 1 }.map { it * 2 }.toList() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple2.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple2.kt new file mode 100644 index 00000000000..31504495b28 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple2.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun test(list: List): List { + return list + .filter { it > 1 } + .map { it * 2 } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple2.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple2.kt.after new file mode 100644 index 00000000000..912ad5b95ea --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple2.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +fun test(list: List): List { + return list + .asSequence() + .filter { it > 1 } + .map { it * 2 } + .toList() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple3.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple3.kt new file mode 100644 index 00000000000..5c78c12f695 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple3.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME + +fun test(list: List): List { + return list + .reversed() + .map { it + 1 } + .map { it + 1 } + .dropLast(1) + .takeLast(2) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple3.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple3.kt.after new file mode 100644 index 00000000000..11be64e287a --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple3.kt.after @@ -0,0 +1,12 @@ +// WITH_RUNTIME + +fun test(list: List): List { + return list + .reversed() + .asSequence() + .map { it + 1 } + .map { it + 1 } + .toList() + .dropLast(1) + .takeLast(2) +} diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple4.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple4.kt new file mode 100644 index 00000000000..0e17f9cb386 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple4.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME + +fun test(foo: Foo): List { + return foo.getList() + .filter { it > 1 } + .map { it * 2 } +} + +class Foo { + fun getList(): List = listOf(1, 2, 3) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple4.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple4.kt.after new file mode 100644 index 00000000000..aca43ad97b0 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple4.kt.after @@ -0,0 +1,13 @@ +// WITH_RUNTIME + +fun test(foo: Foo): List { + return foo.getList() + .asSequence() + .filter { it > 1 } + .map { it * 2 } + .toList() +} + +class Foo { + fun getList(): List = listOf(1, 2, 3) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination.kt new file mode 100644 index 00000000000..1bfa7cc3b87 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun test(): Int { + return listOf(1, 2, 3) + .filter { it > 1 } + .map { it * 2 } + .sum() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination.kt.after new file mode 100644 index 00000000000..d06c7b7a9fa --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +fun test(): Int { + return listOf(1, 2, 3) + .asSequence() + .filter { it > 1 } + .map { it * 2 } + .sum() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination2.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination2.kt new file mode 100644 index 00000000000..29780cae6b9 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination2.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun test(): Int { + return listOf(1, 2, 3) + .filter { it > 1 } + .map { it * 2 } + .binarySearch(4) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination2.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination2.kt.after new file mode 100644 index 00000000000..3fa8a1a757e --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination2.kt.after @@ -0,0 +1,10 @@ +// WITH_RUNTIME + +fun test(): Int { + return listOf(1, 2, 3) + .asSequence() + .filter { it > 1 } + .map { it * 2 } + .toList() + .binarySearch(4) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination3.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination3.kt new file mode 100644 index 00000000000..db506dac515 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination3.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME + +fun test(): Int { + return listOf(1, 2, 3) + .filter { it > 1 } + .map { it * 2 } + .let { + it.binarySearch(1) + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination3.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination3.kt.after new file mode 100644 index 00000000000..2634c754f10 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination3.kt.after @@ -0,0 +1,12 @@ +// WITH_RUNTIME + +fun test(): Int { + return listOf(1, 2, 3) + .asSequence() + .filter { it > 1 } + .map { it * 2 } + .toList() + .let { + it.binarySearch(1) + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination4.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination4.kt new file mode 100644 index 00000000000..7286d27456e --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination4.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun test(list: List>): List { + return list + .filter { it.count() > 2 } + .map { it + it } + .flatMap { it + it } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination4.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination4.kt.after new file mode 100644 index 00000000000..d722f941e11 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination4.kt.after @@ -0,0 +1,10 @@ +// WITH_RUNTIME + +fun test(list: List>): List { + return list + .asSequence() + .filter { it.count() > 2 } + .map { it + it } + .toList() + .flatMap { it + it } +} \ 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 e428835171f..c0644e8e21e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -492,6 +492,109 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/collections"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); } + @TestMetadata("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConvertCallChainIntoSequence extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + @TestMetadata("all.kt") + public void testAll() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/all.kt"); + } + + public void testAllFilesPresentInConvertCallChainIntoSequence() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("comment.kt") + public void testComment() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment.kt"); + } + + @TestMetadata("comment2.kt") + public void testComment2() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/comment2.kt"); + } + + @TestMetadata("noTargetCallChain.kt") + public void testNoTargetCallChain() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain.kt"); + } + + @TestMetadata("noTargetCallChain2.kt") + public void testNoTargetCallChain2() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain2.kt"); + } + + @TestMetadata("noTargetCallChain3.kt") + public void testNoTargetCallChain3() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/noTargetCallChain3.kt"); + } + + @TestMetadata("nullable.kt") + public void testNullable() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable.kt"); + } + + @TestMetadata("nullable2.kt") + public void testNullable2() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/nullable2.kt"); + } + + @TestMetadata("return.kt") + public void testReturn() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/return.kt"); + } + + @TestMetadata("returnAtLabels.kt") + public void testReturnAtLabels() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/returnAtLabels.kt"); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple.kt"); + } + + @TestMetadata("simple2.kt") + public void testSimple2() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple2.kt"); + } + + @TestMetadata("simple3.kt") + public void testSimple3() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple3.kt"); + } + + @TestMetadata("simple4.kt") + public void testSimple4() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/simple4.kt"); + } + + @TestMetadata("termination.kt") + public void testTermination() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination.kt"); + } + + @TestMetadata("termination2.kt") + public void testTermination2() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination2.kt"); + } + + @TestMetadata("termination3.kt") + public void testTermination3() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination3.kt"); + } + + @TestMetadata("termination4.kt") + public void testTermination4() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/termination4.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)