diff --git a/idea/resources/inspectionDescriptions/MoveSuspiciousCallableReferenceIntoParentheses.html b/idea/resources/inspectionDescriptions/MoveSuspiciousCallableReferenceIntoParentheses.html
new file mode 100644
index 00000000000..37729e9f98e
--- /dev/null
+++ b/idea/resources/inspectionDescriptions/MoveSuspiciousCallableReferenceIntoParentheses.html
@@ -0,0 +1,5 @@
+
+
+This inspection reports a lambda expression with one callable reference because it is a common error to replace a lambda with a callable reference without changing curly braces to parentheses.
+
+
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index ccb6cc4f88c..0e5e9e50902 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -2128,6 +2128,13 @@
language="JAVA"
/>
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/MoveSuspiciousCallableReferenceIntoParenthesesInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/MoveSuspiciousCallableReferenceIntoParenthesesInspection.kt
new file mode 100644
index 00000000000..f184077ce36
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/inspections/MoveSuspiciousCallableReferenceIntoParenthesesInspection.kt
@@ -0,0 +1,65 @@
+/*
+ * 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
+
+import com.intellij.codeInspection.IntentionWrapper
+import com.intellij.codeInspection.LocalInspectionToolSession
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.psi.PsiElementVisitor
+import org.jetbrains.kotlin.idea.caches.resolve.analyze
+import org.jetbrains.kotlin.idea.intentions.ConvertLambdaToReferenceIntention
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
+import org.jetbrains.kotlin.psi.KtLambdaExpression
+import org.jetbrains.kotlin.psi.KtVisitorVoid
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
+
+class MoveSuspiciousCallableReferenceIntoParenthesesInspection : AbstractKotlinInspection() {
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
+ return object : KtVisitorVoid() {
+ override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
+ val callableReference = lambdaExpression.bodyExpression?.statements?.singleOrNull() as? KtCallableReferenceExpression
+ if (callableReference != null) {
+ holder.registerProblem(
+ lambdaExpression,
+ "Suspicious callable reference as the only lambda element",
+ ProblemHighlightType.WEAK_WARNING,
+ IntentionWrapper(MoveIntoParenthesesIntention(), lambdaExpression.containingFile)
+ )
+
+ }
+ }
+ }
+ }
+
+ class MoveIntoParenthesesIntention : ConvertLambdaToReferenceIntention(
+ "Move suspicious callable reference into parentheses '()'"
+ ) {
+ override fun buildReferenceText(element: KtLambdaExpression): String? {
+ val callableReferenceExpression =
+ element.bodyExpression?.statements?.singleOrNull() as? KtCallableReferenceExpression ?: return null
+ val callableReference = callableReferenceExpression.callableReference
+ val resolvedCall = callableReference.getResolvedCall(callableReference.analyze())
+ val receiverValue = resolvedCall?.let { it.extensionReceiver ?: it.dispatchReceiver }
+ return (receiverValue?.let { "${it.type}" } ?: "") + "::${callableReference.text}"
+ }
+
+ override fun isApplicableTo(element: KtLambdaExpression) = true
+ }
+}
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt
index 123fe2b5e27..acbef8891b7 100644
--- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt
@@ -47,10 +47,15 @@ class ConvertLambdaToReferenceInspection : IntentionBasedInspection(
- KtLambdaExpression::class.java, "Convert lambda to reference"
-) {
- private fun KtLambdaArgument.outerCalleeDescriptor(): FunctionDescriptor? {
+open class ConvertLambdaToReferenceIntention(text: String) :
+ SelfTargetingOffsetIndependentIntention(KtLambdaExpression::class.java, text) {
+
+ @Suppress("unused")
+ constructor(): this("Convert lambda to reference")
+
+ open fun buildReferenceText(element: KtLambdaExpression) = buildReferenceText(lambdaExpression = element, shortTypes = false)
+
+ protected fun KtLambdaArgument.outerCalleeDescriptor(): FunctionDescriptor? {
val outerCallExpression = parent as? KtCallExpression ?: return null
val context = outerCallExpression.analyze()
val outerCallee = outerCallExpression.calleeExpression as? KtReferenceExpression ?: return null
@@ -163,7 +168,7 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
}
override fun applyTo(element: KtLambdaExpression, editor: Editor?) {
- val referenceName = buildReferenceText(lambdaExpression = element, shortTypes = false) ?: return
+ val referenceName = buildReferenceText(element) ?: return
val factory = KtPsiFactory(element)
val lambdaArgument = element.parent as? KtLambdaArgument
if (lambdaArgument == null) {
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/.inspection b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/.inspection
new file mode 100644
index 00000000000..b181ff92b8a
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/.inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.inspections.MoveSuspiciousCallableReferenceIntoParenthesesInspection
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/defaultParameter.kt b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/defaultParameter.kt
new file mode 100644
index 00000000000..9d206230698
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/defaultParameter.kt
@@ -0,0 +1,6 @@
+// PROBLEM: Suspicious callable reference as the only lambda element
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map { it::toString }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/defaultParameter.kt.after b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/defaultParameter.kt.after
new file mode 100644
index 00000000000..ef0a5d1b191
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/defaultParameter.kt.after
@@ -0,0 +1,6 @@
+// PROBLEM: Suspicious callable reference as the only lambda element
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map(Int::toString)
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/it.kt b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/it.kt
new file mode 100644
index 00000000000..dc26c391163
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/it.kt
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map { it::toString }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/it.kt.after b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/it.kt.after
new file mode 100644
index 00000000000..2de927b9672
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/it.kt.after
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map(Int::toString)
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/lambdaWithArg.kt b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/lambdaWithArg.kt
new file mode 100644
index 00000000000..6b74b602595
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/lambdaWithArg.kt
@@ -0,0 +1,13 @@
+// WITH_RUNTIME
+
+fun foo() {
+ x(1) { ::y }
+}
+
+fun y() {
+
+}
+
+fun x(number: Int, func: () -> Unit) {
+
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/lambdaWithArg.kt.after b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/lambdaWithArg.kt.after
new file mode 100644
index 00000000000..ed4cacef543
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/lambdaWithArg.kt.after
@@ -0,0 +1,13 @@
+// WITH_RUNTIME
+
+fun foo() {
+ x(1, ::y)
+}
+
+fun y() {
+
+}
+
+fun x(number: Int, func: () -> Unit) {
+
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/multipleLines.kt b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/multipleLines.kt
new file mode 100644
index 00000000000..d016489248f
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/multipleLines.kt
@@ -0,0 +1,9 @@
+// WITH_RUNTIME
+// PROBLEM: none
+
+fun foo() {
+ listOf(1,2,3).map {
+ println(it)
+ Int::toString
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/noBody.kt b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/noBody.kt
new file mode 100644
index 00000000000..6cdc29bdeb2
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/noBody.kt
@@ -0,0 +1,6 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map {}
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/noneCallableRef.kt b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/noneCallableRef.kt
new file mode 100644
index 00000000000..ca4a9f0b9a2
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/noneCallableRef.kt
@@ -0,0 +1,6 @@
+// PROBLEM: none
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map { println(it) }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/normal.kt b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/normal.kt
new file mode 100644
index 00000000000..786e69b7d0a
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/normal.kt
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map { Int::toString }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/normal.kt.after b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/normal.kt.after
new file mode 100644
index 00000000000..2de927b9672
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/normal.kt.after
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map(Int::toString)
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter.kt b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter.kt
new file mode 100644
index 00000000000..ced496d0b3c
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter.kt
@@ -0,0 +1,6 @@
+// FIX: Move suspicious callable reference into parentheses '()'
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map { bar -> bar::toString }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter.kt.after b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter.kt.after
new file mode 100644
index 00000000000..53b7fdb7d7f
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter.kt.after
@@ -0,0 +1,6 @@
+// FIX: Move suspicious callable reference into parentheses '()'
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map(Int::toString)
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter2.kt b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter2.kt
new file mode 100644
index 00000000000..f0521ef0e0e
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter2.kt
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map { bar -> bar::toString }
+ listOf(4,5).map { bar -> bar::toString }
+}
\ No newline at end of file
diff --git a/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter2.kt.after b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter2.kt.after
new file mode 100644
index 00000000000..94daca3f165
--- /dev/null
+++ b/idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter2.kt.after
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+
+fun foo() {
+ listOf(1,2,3).map { bar -> bar::toString }
+ listOf(4,5).map(Int::toString)
+}
\ 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
new file mode 100644
index 00000000000..8f2e62c1fa0
--- /dev/null
+++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java
@@ -0,0 +1,101 @@
+/*
+ * 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;
+
+import com.intellij.testFramework.TestDataPath;
+import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
+import org.jetbrains.kotlin.test.KotlinTestUtils;
+import org.jetbrains.kotlin.test.TargetBackend;
+import org.jetbrains.kotlin.test.TestMetadata;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.regex.Pattern;
+
+/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
+@SuppressWarnings("all")
+@TestMetadata("idea/testData/inspectionsLocal")
+@TestDataPath("$PROJECT_ROOT")
+@RunWith(JUnit3RunnerWithInners.class)
+public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
+ public void testAllFilesPresentInInspectionsLocal() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class MoveSuspiciousCallableReferenceIntoParentheses extends AbstractLocalInspectionTest {
+ public void testAllFilesPresentInMoveSuspiciousCallableReferenceIntoParentheses() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("defaultParameter.kt")
+ public void testDefaultParameter() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/defaultParameter.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("it.kt")
+ public void testIt() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/it.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("lambdaWithArg.kt")
+ public void testLambdaWithArg() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/lambdaWithArg.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("multipleLines.kt")
+ public void testMultipleLines() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/multipleLines.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("noBody.kt")
+ public void testNoBody() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/noBody.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("noneCallableRef.kt")
+ public void testNoneCallableRef() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/noneCallableRef.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("normal.kt")
+ public void testNormal() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/normal.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("parameter.kt")
+ public void testParameter() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("parameter2.kt")
+ public void testParameter2() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter2.kt");
+ doTest(fileName);
+ }
+ }
+}