diff --git a/idea/resources/inspectionDescriptions/SimplifiableCallChain.html b/idea/resources/inspectionDescriptions/SimplifiableCallChain.html
new file mode 100644
index 00000000000..09e019f2619
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/SimplifiableCallChain.html
@@ -0,0 +1,5 @@
+
+
+This inspection reports two-call chains replaceable by single call, e.g. map {}.filterNotNull() to mapNotNull {}
+
+
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 427cf1179df..189a5a6b038 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -2266,6 +2266,14 @@
language="kotlin"
/>
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/FunctionUtils.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/FunctionUtils.kt
new file mode 100644
index 00000000000..885617cbd53
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/FunctionUtils.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.kotlin.idea.inspections.collections
+
+import org.jetbrains.kotlin.builtins.getFunctionalClassKind
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.calls.callUtil.getType
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+import org.jetbrains.kotlin.types.KotlinType
+
+fun KotlinType.isFunctionOfAnyKind() = constructor.declarationDescriptor?.getFunctionalClassKind() != null
+
+fun ResolvedCall<*>.hasLastFunctionalParameterWithResult(context: BindingContext, predicate: (KotlinType) -> Boolean): Boolean {
+ val lastParameter = resultingDescriptor.valueParameters.lastOrNull() ?: return false
+ val lastArgument = valueArguments[lastParameter]?.arguments?.singleOrNull() ?: return false
+ val functionalType = lastArgument.getArgumentExpression()?.getType(context) ?: return false
+ // Both Function & KFunction must pass here
+ if (!functionalType.isFunctionOfAnyKind()) return false
+ val resultType = functionalType.arguments.lastOrNull()?.type ?: return false
+ return predicate(resultType)
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifiableCallChainInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifiableCallChainInspection.kt
new file mode 100644
index 00000000000..2a6c0137249
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifiableCallChainInspection.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2010-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.kotlin.idea.inspections.collections
+
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.openapi.util.TextRange
+import org.jetbrains.kotlin.builtins.KotlinBuiltIns
+import org.jetbrains.kotlin.idea.caches.resolve.analyze
+import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
+import org.jetbrains.kotlin.psi.psiUtil.endOffset
+import org.jetbrains.kotlin.psi.psiUtil.startOffset
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
+import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
+
+class SimplifiableCallChainInspection : AbstractKotlinInspection() {
+
+
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) =
+ object : KtVisitorVoid() {
+ override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
+ super.visitQualifiedExpression(expression)
+
+ val firstQualifiedExpression = expression.receiverExpression as? KtQualifiedExpression ?: return
+ val firstCallExpression = firstQualifiedExpression.selectorExpression as? KtCallExpression ?: return
+ val secondCallExpression = expression.selectorExpression as? KtCallExpression ?: return
+
+ val actualConversions = conversionGroups[
+ firstCallExpression.calleeExpression?.text to secondCallExpression.calleeExpression?.text
+ ] ?: return
+
+ val context = expression.analyze(BodyResolveMode.PARTIAL)
+ val firstResolvedCall = firstQualifiedExpression.getResolvedCall(context) ?: return
+ val conversion = actualConversions.firstOrNull {
+ firstResolvedCall.resultingDescriptor.fqNameOrNull()?.asString() == it.firstFqName
+ } ?: return
+
+ val secondResolvedCall = expression.getResolvedCall(context) ?: return
+ val secondResultingDescriptor = secondResolvedCall.resultingDescriptor
+ if (secondResultingDescriptor.fqNameOrNull()?.asString() != conversion.secondFqName) return
+ if (secondResolvedCall.valueArguments.any { (parameter, resolvedArgument) ->
+ parameter.type.isFunctionOfAnyKind() &&
+ resolvedArgument !is DefaultValueArgument
+ }) return
+
+ if (conversion.replacement.startsWith("joinTo")) {
+ // Function parameter in map must have String result type
+ if (!firstResolvedCall.hasLastFunctionalParameterWithResult(context) { KotlinBuiltIns.isString(it) }) return
+ }
+
+ val descriptor = holder.manager.createProblemDescriptor(
+ expression,
+ TextRange(firstCallExpression.startOffset, firstCallExpression.endOffset).shiftRight(-expression.startOffset),
+ "Call chain on collection type may be simplified",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ isOnTheFly,
+ SimplifyCallChainFix(conversion.replacement)
+ )
+ holder.registerProblem(descriptor)
+ }
+ }
+
+ companion object {
+
+ private val conversions = listOf(
+ Conversion("kotlin.collections.filter", "kotlin.collections.first", "first"),
+ Conversion("kotlin.collections.filter", "kotlin.collections.firstOrNull", "firstOrNull"),
+ Conversion("kotlin.collections.filter", "kotlin.collections.last", "last"),
+ Conversion("kotlin.collections.filter", "kotlin.collections.lastOrNull", "lastOrNull"),
+ Conversion("kotlin.collections.filter", "kotlin.collections.single", "single"),
+ Conversion("kotlin.collections.filter", "kotlin.collections.singleOrNull", "singleOrNull"),
+ Conversion("kotlin.collections.filter", "kotlin.collections.isNotEmpty", "any"),
+ Conversion("kotlin.collections.filter", "kotlin.collections.List.isEmpty", "none"),
+
+ Conversion("kotlin.text.filter", "kotlin.text.first", "first"),
+ Conversion("kotlin.text.filter", "kotlin.text.firstOrNull", "firstOrNull"),
+ Conversion("kotlin.text.filter", "kotlin.text.last", "last"),
+ Conversion("kotlin.text.filter", "kotlin.text.lastOrNull", "lastOrNull"),
+ Conversion("kotlin.text.filter", "kotlin.text.single", "single"),
+ Conversion("kotlin.text.filter", "kotlin.text.singleOrNull", "singleOrNull"),
+ Conversion("kotlin.text.filter", "kotlin.text.isNotEmpty", "any"),
+ Conversion("kotlin.text.filter", "kotlin.text.isEmpty", "none"),
+
+ Conversion("kotlin.collections.map", "kotlin.collections.joinTo", "joinTo"),
+ Conversion("kotlin.collections.map", "kotlin.collections.joinToString", "joinToString"),
+ Conversion("kotlin.collections.map", "kotlin.collections.filterNotNull", "mapNotNull")
+ )
+
+ private val conversionGroups = conversions.groupBy { it.firstName to it.secondName }
+
+ data class Conversion(val firstFqName: String, val secondFqName: String, val replacement: String) {
+ private fun String.convertToShort() = takeLastWhile { it != '.' }
+
+ val firstName = firstFqName.convertToShort()
+
+ val secondName = secondFqName.convertToShort()
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifyCallChainFix.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifyCallChainFix.kt
new file mode 100644
index 00000000000..db29d0ec09f
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/SimplifyCallChainFix.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.kotlin.idea.inspections.collections
+
+import com.intellij.codeInspection.LocalQuickFix
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.openapi.project.Project
+import org.jetbrains.kotlin.idea.core.replaced
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
+
+class SimplifyCallChainFix(val newName: String) : LocalQuickFix {
+ override fun getName() = "Merge call chain to '$newName'"
+
+ override fun getFamilyName() = name
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ (descriptor.psiElement as? KtQualifiedExpression)?.let { secondQualifiedExpression ->
+ val factory = KtPsiFactory(secondQualifiedExpression)
+ val firstQualifiedExpression = secondQualifiedExpression.receiverExpression as? KtQualifiedExpression ?: return
+ val operationSign = if (firstQualifiedExpression is KtSafeQualifiedExpression) "?." else "."
+ val firstCallExpression = firstQualifiedExpression.selectorExpression as? KtCallExpression ?: return
+ val secondCallExpression = secondQualifiedExpression.selectorExpression as? KtCallExpression ?: return
+
+ val lastArgumentPrefix = if (newName.startsWith("joinTo")) "transform = " else ""
+ val arguments = secondCallExpression.valueArgumentList?.arguments.orEmpty().map { it.text } +
+ firstCallExpression.valueArgumentList?.arguments.orEmpty().map { "$lastArgumentPrefix${it.text}"}
+ val lambdaArgument = firstCallExpression.lambdaArguments.singleOrNull()
+
+ val argumentsText = arguments.ifNotEmpty { joinToString(prefix = "(", postfix = ")") } ?: ""
+ val newQualifiedExpression = if (lambdaArgument != null) factory.createExpressionByPattern(
+ "$0$1$2 $3 $4",
+ firstQualifiedExpression.receiverExpression,
+ operationSign,
+ newName,
+ argumentsText,
+ lambdaArgument.getLambdaExpression().text
+ )
+ else factory.createExpressionByPattern(
+ "$0$1$2 $3",
+ firstQualifiedExpression.receiverExpression,
+ operationSign,
+ newName,
+ argumentsText
+ )
+
+ secondQualifiedExpression.replaced(newQualifiedExpression)
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/UselessCallOnCollectionInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/UselessCallOnCollectionInspection.kt
index e0f9e1d761c..6155955dd3b 100644
--- a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/UselessCallOnCollectionInspection.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/UselessCallOnCollectionInspection.kt
@@ -18,9 +18,6 @@ package org.jetbrains.kotlin.idea.inspections.collections
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.openapi.util.TextRange
-import org.jetbrains.kotlin.builtins.KotlinBuiltIns
-import org.jetbrains.kotlin.builtins.getFunctionalClassKind
-import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtQualifiedExpression
import org.jetbrains.kotlin.psi.psiUtil.endOffset
@@ -61,13 +58,7 @@ class UselessCallOnCollectionInspection : AbstractUselessCallInspection() {
if (TypeUtils.isNullableType(receiverTypeArgument)) return
if (calleeExpression.text != "filterNotNull") {
// Also check last argument functional type to have not-null result
- val lastParameter = resolvedCall.resultingDescriptor.valueParameters.lastOrNull() ?: return
- val lastArgument = resolvedCall.valueArguments[lastParameter]?.arguments?.singleOrNull() ?: return
- val functionalType = lastArgument.getArgumentExpression()?.getType(context) ?: return
- // Both Function & KFunction must pass here
- if (functionalType.constructor.declarationDescriptor?.getFunctionalClassKind() == null) return
- val resultType = functionalType.arguments.lastOrNull()?.type ?: return
- if (TypeUtils.isNullableType(resultType)) return
+ if (!resolvedCall.hasLastFunctionalParameterWithResult(context) { !TypeUtils.isNullableType(it) }) return
}
}
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/.inspection b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/.inspection
new file mode 100644
index 00000000000..b980466f40c
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.collections.SimplifiableCallChainInspection
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirst.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirst.kt
new file mode 100644
index 00000000000..ced1c738267
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirst.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val nullableList: List? = listOf("1", "")
+val x = nullableList?.filter { it.isNotEmpty() }?.first()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirst.kt.after b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirst.kt.after
new file mode 100644
index 00000000000..662755aff63
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirst.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val nullableList: List? = listOf("1", "")
+val x = nullableList?.first { it.isNotEmpty() }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstFake.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstFake.kt
new file mode 100644
index 00000000000..ce02728a413
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstFake.kt
@@ -0,0 +1,4 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+val x = listOf("1", "").filter { it.isNotEmpty() }.first { it[0] == 'A' }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstFakeReference.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstFakeReference.kt
new file mode 100644
index 00000000000..7d85716ebd8
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstFakeReference.kt
@@ -0,0 +1,4 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+val x = listOf('1', 'a', 0.toChar()).filter { it.toInt() != 0 }.first(Char::isLetter)
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstOrNullReference.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstOrNullReference.kt
new file mode 100644
index 00000000000..fda1c2161a2
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstOrNullReference.kt
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf("1", "").filter(String::isNotEmpty).firstOrNull()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstOrNullReference.kt.after b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstOrNullReference.kt.after
new file mode 100644
index 00000000000..5a8a9e6d250
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstOrNullReference.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf("1", "").firstOrNull(String::isNotEmpty)
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsEmpty.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsEmpty.kt
new file mode 100644
index 00000000000..4c2a249d736
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsEmpty.kt
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf(1, 2, 3).filter { it % 2 != 0 }.isEmpty()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsEmpty.kt.after b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsEmpty.kt.after
new file mode 100644
index 00000000000..62034587209
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsEmpty.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf(1, 2, 3).none { it % 2 != 0 }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsNotEmpty.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsNotEmpty.kt
new file mode 100644
index 00000000000..8d096627a7d
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsNotEmpty.kt
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf(1, 2, 3).filter { it % 2 != 0 }.isNotEmpty()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsNotEmpty.kt.after b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsNotEmpty.kt.after
new file mode 100644
index 00000000000..3b8f7ac7f6f
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsNotEmpty.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf(1, 2, 3).any { it % 2 != 0 }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterLastExplicit.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterLastExplicit.kt
new file mode 100644
index 00000000000..43ad7e5fb7d
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterLastExplicit.kt
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf("1", "").filter { element -> element.isNotEmpty() }.last()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterLastExplicit.kt.after b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterLastExplicit.kt.after
new file mode 100644
index 00000000000..81d3e4481e8
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterLastExplicit.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf("1", "").last { element -> element.isNotEmpty() }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextIsEmpty.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextIsEmpty.kt
new file mode 100644
index 00000000000..9c54460eb45
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextIsEmpty.kt
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = "abcdef".filter { it.isDigit() }.isEmpty()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextIsEmpty.kt.after b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextIsEmpty.kt.after
new file mode 100644
index 00000000000..b5b5890d4bd
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextIsEmpty.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = "abcdef".none { it.isDigit() }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextSingleOrNull.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextSingleOrNull.kt
new file mode 100644
index 00000000000..caea5be3bba
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextSingleOrNull.kt
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = "5abc".filter { it.isDigit() }.singleOrNull()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextSingleOrNull.kt.after b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextSingleOrNull.kt.after
new file mode 100644
index 00000000000..16da3c09766
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextSingleOrNull.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = "5abc".singleOrNull { it.isDigit() }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinTo.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinTo.kt
new file mode 100644
index 00000000000..9939c2b031d
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinTo.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val sb = StringBuilder()
+val x = listOf(1, 2, 3).map { "$it*$it" }.joinTo(buffer = sb, prefix = "= ", separator = " + ")
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinTo.kt.after b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinTo.kt.after
new file mode 100644
index 00000000000..2c698b4bf4d
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinTo.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val sb = StringBuilder()
+val x = listOf(1, 2, 3).joinTo(buffer = sb, prefix = "= ", separator = " + ") { "$it*$it" }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToFake.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToFake.kt
new file mode 100644
index 00000000000..2c57550a3eb
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToFake.kt
@@ -0,0 +1,5 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+val sb = StringBuilder()
+val x = listOf(1, 2, 3).map { "$it*$it" }.joinTo(buffer = sb, prefix = "= ", separator = " + ", transform = String::capitalize)
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToFakeWithLambda.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToFakeWithLambda.kt
new file mode 100644
index 00000000000..72e22d3a41e
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToFakeWithLambda.kt
@@ -0,0 +1,5 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+val sb = StringBuilder()
+val x = listOf(1, 2, 3).map { "$it*$it" }.joinTo(buffer = sb, prefix = "= ", separator = " + ") { "${it.substring(1)}"}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToString.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToString.kt
new file mode 100644
index 00000000000..e97fabddcd3
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToString.kt
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf(1, 2, 3).map { "$it*$it" }.joinToString(prefix = "= ", separator = " + ")
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToString.kt.after b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToString.kt.after
new file mode 100644
index 00000000000..2f97dd7ba5b
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToString.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf(1, 2, 3).joinToString(prefix = "= ", separator = " + ") { "$it*$it" }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReference.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReference.kt
new file mode 100644
index 00000000000..84a8b81b215
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReference.kt
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf(1, 2, 3).map(Int::toString).joinToString(prefix = "= ", separator = " + ")
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReference.kt.after b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReference.kt.after
new file mode 100644
index 00000000000..cdeb86c64f2
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReference.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf(1, 2, 3).joinToString(prefix = "= ", separator = " + ", transform = Int::toString)
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReferenceFake.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReferenceFake.kt
new file mode 100644
index 00000000000..4972dcc3cbc
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReferenceFake.kt
@@ -0,0 +1,4 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+val x = listOf(1, 2, 3).map(Int::toDouble).joinToString(prefix = "= ", separator = " + ")
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapNotNull.kt b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapNotNull.kt
new file mode 100644
index 00000000000..4e06e43850a
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapNotNull.kt
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf(1, 0, 2).map { if (it != 0) it else null }.filterNotNull()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapNotNull.kt.after b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapNotNull.kt.after
new file mode 100644
index 00000000000..ef63f8229b0
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapNotNull.kt.after
@@ -0,0 +1,3 @@
+// WITH_RUNTIME
+
+val x = listOf(1, 0, 2).mapNotNull { if (it != 0) it else null }
\ 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 502022c3d0a..ba59914e14d 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
@@ -59,6 +59,111 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/collections"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
+ @TestMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class SimplifiableCallChain extends AbstractLocalInspectionTest {
+ public void testAllFilesPresentInSimplifiableCallChain() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/collections/simplifiableCallChain"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("filterFirst.kt")
+ public void testFilterFirst() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirst.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("filterFirstFake.kt")
+ public void testFilterFirstFake() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstFake.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("filterFirstFakeReference.kt")
+ public void testFilterFirstFakeReference() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstFakeReference.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("filterFirstOrNullReference.kt")
+ public void testFilterFirstOrNullReference() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterFirstOrNullReference.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("filterIsEmpty.kt")
+ public void testFilterIsEmpty() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsEmpty.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("filterIsNotEmpty.kt")
+ public void testFilterIsNotEmpty() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterIsNotEmpty.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("filterLastExplicit.kt")
+ public void testFilterLastExplicit() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterLastExplicit.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("filterTextIsEmpty.kt")
+ public void testFilterTextIsEmpty() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextIsEmpty.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("filterTextSingleOrNull.kt")
+ public void testFilterTextSingleOrNull() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/filterTextSingleOrNull.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("joinTo.kt")
+ public void testJoinTo() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinTo.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("joinToFake.kt")
+ public void testJoinToFake() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToFake.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("joinToFakeWithLambda.kt")
+ public void testJoinToFakeWithLambda() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToFakeWithLambda.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("joinToString.kt")
+ public void testJoinToString() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToString.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("joinToStringWithReference.kt")
+ public void testJoinToStringWithReference() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReference.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("joinToStringWithReferenceFake.kt")
+ public void testJoinToStringWithReferenceFake() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/joinToStringWithReferenceFake.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("mapNotNull.kt")
+ public void testMapNotNull() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/simplifiableCallChain/mapNotNull.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/inspectionsLocal/collections/uselessCallOnCollection")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)