diff --git a/idea/resources/inspectionDescriptions/UselessCallOnNotNull.html b/idea/resources/inspectionDescriptions/UselessCallOnNotNull.html
new file mode 100644
index 00000000000..7ec18355771
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/UselessCallOnNotNull.html
@@ -0,0 +1,8 @@
+
+
+This inspection reports calls on not-null receiver that make sense only for nullable receiver, e.g.
+
+listOf(1).orEmpty()
+
+
+
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 0e0e36ff850..e35e97fea4d 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -2242,6 +2242,14 @@
language="kotlin"
/>
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RemoveUselessCallFix.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RemoveUselessCallFix.kt
new file mode 100644
index 00000000000..c4d0882425b
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RemoveUselessCallFix.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.KtQualifiedExpression
+
+class RemoveUselessCallFix : LocalQuickFix {
+
+ override fun getName() = "Remove useless call"
+
+ override fun getFamilyName() = name
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ (descriptor.psiElement as? KtQualifiedExpression)?.let {
+ it.replaced(it.receiverExpression)
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RenameUselessCallFix.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RenameUselessCallFix.kt
new file mode 100644
index 00000000000..0c5e1108051
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/RenameUselessCallFix.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.KtPsiFactory
+import org.jetbrains.kotlin.psi.KtQualifiedExpression
+import org.jetbrains.kotlin.psi.KtSafeQualifiedExpression
+import org.jetbrains.kotlin.psi.createExpressionByPattern
+
+class RenameUselessCallFix(val newName: String) : LocalQuickFix {
+ override fun getName() = "Rename useless call to '$newName'"
+
+ override fun getFamilyName() = name
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ (descriptor.psiElement as? KtQualifiedExpression)?.let {
+ val factory = KtPsiFactory(it)
+ val sign = if (it is KtSafeQualifiedExpression) "?." else "."
+ it.replaced(factory.createExpressionByPattern("$0$sign$newName()", it.receiverExpression))
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/UselessCallOnNotNullInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/UselessCallOnNotNullInspection.kt
new file mode 100644
index 00000000000..d2904887b02
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/UselessCallOnNotNullInspection.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.IntentionWrapper
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.openapi.util.TextRange
+import com.intellij.psi.PsiElementVisitor
+import org.jetbrains.kotlin.idea.caches.resolve.analyze
+import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
+import org.jetbrains.kotlin.idea.quickfix.ReplaceWithDotCallFix
+import org.jetbrains.kotlin.psi.KtCallExpression
+import org.jetbrains.kotlin.psi.KtQualifiedExpression
+import org.jetbrains.kotlin.psi.KtSafeQualifiedExpression
+import org.jetbrains.kotlin.psi.KtVisitorVoid
+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.callUtil.getType
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
+import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
+import org.jetbrains.kotlin.types.TypeUtils
+
+class UselessCallOnNotNullInspection : AbstractKotlinInspection() {
+
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
+ return object : KtVisitorVoid() {
+ override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
+ super.visitQualifiedExpression(expression)
+ val selector = expression.selectorExpression as? KtCallExpression ?: return
+ val calleeExpression = selector.calleeExpression ?: return
+ if (calleeExpression.text !in names) return
+
+ val context = expression.analyze(BodyResolveMode.PARTIAL)
+ val resolvedCall = expression.getResolvedCall(context) ?: return
+ val conversion = fqNames[resolvedCall.resultingDescriptor.fqNameOrNull()?.asString()] ?: return
+ val newName = conversion.replacementName
+
+ val safeExpression = expression as? KtSafeQualifiedExpression
+ val notNullType = expression.receiverExpression.getType(context)?.let { TypeUtils.isNullableType(it) } == false
+ if (newName != null && (notNullType || safeExpression != null)) {
+ val descriptor = holder.manager.createProblemDescriptor(
+ expression,
+ TextRange(expression.operationTokenNode.startOffset - expression.startOffset,
+ calleeExpression.endOffset - expression.startOffset),
+ "Call on not-null type may be reduced",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ isOnTheFly,
+ RenameUselessCallFix(newName)
+ )
+ holder.registerProblem(descriptor)
+ }
+ else if (notNullType) {
+ val descriptor = holder.manager.createProblemDescriptor(
+ expression,
+ TextRange(expression.operationTokenNode.startOffset - expression.startOffset,
+ calleeExpression.endOffset - expression.startOffset),
+ "Useless call on not-null type",
+ ProblemHighlightType.LIKE_UNUSED_SYMBOL,
+ isOnTheFly,
+ RemoveUselessCallFix()
+ )
+ holder.registerProblem(descriptor)
+ }
+ else if (safeExpression != null) {
+ holder.registerProblem(
+ safeExpression.operationTokenNode.psi,
+ "This call is useless with ?.",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ IntentionWrapper(ReplaceWithDotCallFix(safeExpression), safeExpression.containingKtFile)
+ )
+ }
+ }
+ }
+ }
+
+ companion object {
+ private data class Conversion(val replacementName: String? = null)
+
+ private val deleteConversion = Conversion()
+
+ private val fqNames = mapOf("kotlin.collections.orEmpty" to deleteConversion,
+ "kotlin.text.orEmpty" to deleteConversion,
+ "kotlin.text.isNullOrEmpty" to Conversion("isEmpty"),
+ "kotlin.text.isNullOrBlank" to Conversion("isBlank"))
+
+ private val names = fqNames.keys.mapTo(mutableSetOf()) { fqName -> fqName.takeLastWhile { it != '.' } }
+ }
+}
+
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/.inspection b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/.inspection
new file mode 100644
index 00000000000..5cfb7ab7f42
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.collections.UselessCallOnNotNullInspection
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullType.kt b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullType.kt
new file mode 100644
index 00000000000..81605b6c580
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullType.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val list1: List = listOf(1)
+val list = list1.orEmpty()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullType.kt.after b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullType.kt.after
new file mode 100644
index 00000000000..1ead3bb5f09
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullType.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val list1: List = listOf(1)
+val list = list1
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullTypeChain.kt b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullTypeChain.kt
new file mode 100644
index 00000000000..8bb618154d0
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullTypeChain.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val list1: List = listOf(1)
+val list = list1.orEmpty().map { "$it" }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullTypeChain.kt.after b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullTypeChain.kt.after
new file mode 100644
index 00000000000..2abb3dd65cf
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullTypeChain.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val list1: List = listOf(1)
+val list = list1.map { "$it" }
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrBlankSafe.kt b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrBlankSafe.kt
new file mode 100644
index 00000000000..43fee35ad1e
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrBlankSafe.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val s: String? = ""
+val blank = s?.isNullOrBlank()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrBlankSafe.kt.after b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrBlankSafe.kt.after
new file mode 100644
index 00000000000..1e519a5c5b3
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrBlankSafe.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val s: String? = ""
+val blank = s?.isBlank()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrEmpty.kt b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrEmpty.kt
new file mode 100644
index 00000000000..dcb8d7d1100
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrEmpty.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val s = ""
+val empty = s.isNullOrEmpty()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrEmpty.kt.after b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrEmpty.kt.after
new file mode 100644
index 00000000000..3c3a7834fe9
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrEmpty.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val s = ""
+val empty = s.isEmpty()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrEmptyFake.kt b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrEmptyFake.kt
new file mode 100644
index 00000000000..451e3d9a002
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrEmptyFake.kt
@@ -0,0 +1,5 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+val s: String? = null
+val empty = s.isNullOrEmpty()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/OrEmptyFake.kt b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/OrEmptyFake.kt
new file mode 100644
index 00000000000..a5156836900
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/OrEmptyFake.kt
@@ -0,0 +1,5 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+val list: List? = null
+val empty = list.orEmpty()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/SafeCall.kt b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/SafeCall.kt
new file mode 100644
index 00000000000..f5ee86203ff
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/SafeCall.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val list1: List? = listOf(1)
+val list = list1?.orEmpty()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/SafeCall.kt.after b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/SafeCall.kt.after
new file mode 100644
index 00000000000..8e74260c620
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/SafeCall.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val list1: List? = listOf(1)
+val list = list1.orEmpty()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/String.kt b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/String.kt
new file mode 100644
index 00000000000..23604ee541f
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/String.kt
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val s = ""
+val s1 = s.orEmpty()
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/String.kt.after b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/String.kt.after
new file mode 100644
index 00000000000..a8b0c855792
--- /dev/null
+++ b/idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/String.kt.after
@@ -0,0 +1,4 @@
+// WITH_RUNTIME
+
+val s = ""
+val s1 = s
\ 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 7a95a03aae9..f21015a6bbf 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
@@ -51,6 +51,72 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
}
}
+ @TestMetadata("idea/testData/inspectionsLocal/collections")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class Collections extends AbstractLocalInspectionTest {
+ public void testAllFilesPresentInCollections() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/collections"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("idea/testData/inspectionsLocal/collections/uselessCallOnNotNull")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class UselessCallOnNotNull extends AbstractLocalInspectionTest {
+ public void testAllFilesPresentInUselessCallOnNotNull() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/collections/uselessCallOnNotNull"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("NotNullType.kt")
+ public void testNotNullType() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullType.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("NotNullTypeChain.kt")
+ public void testNotNullTypeChain() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NotNullTypeChain.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("NullOrBlankSafe.kt")
+ public void testNullOrBlankSafe() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrBlankSafe.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("NullOrEmpty.kt")
+ public void testNullOrEmpty() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrEmpty.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("NullOrEmptyFake.kt")
+ public void testNullOrEmptyFake() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/NullOrEmptyFake.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("OrEmptyFake.kt")
+ public void testOrEmptyFake() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/OrEmptyFake.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("SafeCall.kt")
+ public void testSafeCall() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/SafeCall.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("String.kt")
+ public void testString() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/collections/uselessCallOnNotNull/String.kt");
+ doTest(fileName);
+ }
+ }
+ }
+
@TestMetadata("idea/testData/inspectionsLocal/copyWithoutNamedArguments")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)