diff --git a/idea/resources/intentionDescriptions/ReplaceUnderscoreWithParameterNameIntention/after.kt.template b/idea/resources/intentionDescriptions/ReplaceUnderscoreWithParameterNameIntention/after.kt.template
new file mode 100644
index 00000000000..122ee7186c8
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ReplaceUnderscoreWithParameterNameIntention/after.kt.template
@@ -0,0 +1 @@
+val (first, second) = Pair(1, 2)
\ No newline at end of file
diff --git a/idea/resources/intentionDescriptions/ReplaceUnderscoreWithParameterNameIntention/before.kt.template b/idea/resources/intentionDescriptions/ReplaceUnderscoreWithParameterNameIntention/before.kt.template
new file mode 100644
index 00000000000..38dda8ddbc0
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ReplaceUnderscoreWithParameterNameIntention/before.kt.template
@@ -0,0 +1 @@
+val (first, _) = Pair(1, 2)
\ No newline at end of file
diff --git a/idea/resources/intentionDescriptions/ReplaceUnderscoreWithParameterNameIntention/description.html b/idea/resources/intentionDescriptions/ReplaceUnderscoreWithParameterNameIntention/description.html
new file mode 100644
index 00000000000..ab211677b10
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ReplaceUnderscoreWithParameterNameIntention/description.html
@@ -0,0 +1,5 @@
+
+
+Gives name to an anonymous parameter
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 0e5e9e50902..95c91aa5d53 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1265,6 +1265,11 @@
Kotlin
+
+ org.jetbrains.kotlin.idea.intentions.ReplaceUnderscoreWithParameterNameIntention
+ Kotlin
+
+
org.jetbrains.kotlin.idea.intentions.AddJvmOverloadsIntention
Kotlin
diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceUnderscoreWithParameterNameIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceUnderscoreWithParameterNameIntention.kt
new file mode 100644
index 00000000000..a02877f6a26
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceUnderscoreWithParameterNameIntention.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.intentions
+
+import com.intellij.openapi.editor.Editor
+import org.jetbrains.kotlin.builtins.extractParameterNameFromFunctionTypeArgument
+import org.jetbrains.kotlin.descriptors.CallableDescriptor
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.idea.caches.resolve.analyze
+import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
+import org.jetbrains.kotlin.idea.core.CollectingNameValidator
+import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
+import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
+import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.DescriptorUtils
+import org.jetbrains.kotlin.resolve.calls.callUtil.getParameterForArgument
+import org.jetbrains.kotlin.resolve.calls.callUtil.getParentResolvedCall
+import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
+import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
+
+class ReplaceUnderscoreWithParameterNameIntention : SelfTargetingOffsetIndependentIntention(
+ KtCallableDeclaration::class.java,
+ "Replace '_' with parameter name"
+) {
+ override fun isApplicableTo(element: KtCallableDeclaration) =
+ element.name == "_" && (element is KtDestructuringDeclarationEntry || element is KtParameter)
+
+ override fun applyTo(element: KtCallableDeclaration, editor: Editor?) {
+ val suggestedParameterName = suggestedParameterName(element)
+ val validator = CollectingNameValidator(
+ filter = NewDeclarationNameValidator(element.parent.parent, null, NewDeclarationNameValidator.Target.VARIABLES)
+ )
+ val name = suggestedParameterName?.let {
+ KotlinNameSuggester.suggestNameByName(it, validator)
+ } ?: run {
+ val elementDescriptor = element.resolveToDescriptorIfAny() as? CallableDescriptor
+ elementDescriptor?.returnType?.let { KotlinNameSuggester.suggestNamesByType(it, validator).firstOrNull() }
+ } ?: return
+ element.setName(name)
+ }
+
+ private fun suggestedParameterName(element: KtCallableDeclaration) =
+ when (element) {
+ is KtDestructuringDeclarationEntry -> dataClassParameterName(element)
+ is KtParameter -> lambdaParameterName(element)
+ else -> null
+ }
+
+ private fun lambdaParameterName(element: KtParameter): String? {
+ val functionLiteral = element.getParentOfType(strict = true) ?: return null
+ val idx = functionLiteral.valueParameters.indexOf(element)
+ if (idx == -1) return null
+ val context = functionLiteral.analyze(BodyResolveMode.PARTIAL)
+ val resolvedCall = element.getParentResolvedCall(context)
+ val lambdaArgument = functionLiteral.getParentOfType(strict = true) ?: return null
+ val lambdaParam = resolvedCall?.getParameterForArgument(lambdaArgument) ?: return null
+ return lambdaParam.type.arguments.getOrNull(idx)?.type?.extractParameterNameFromFunctionTypeArgument()?.asString()
+ }
+
+ private fun dataClassParameterName(declarationEntry: KtDestructuringDeclarationEntry): String? {
+ val context = declarationEntry.analyze()
+ val componentResolvedCall = context[BindingContext.COMPONENT_RESOLVED_CALL, declarationEntry] ?: return null
+ val receiver = componentResolvedCall.dispatchReceiver ?: componentResolvedCall.extensionReceiver ?: return null
+ val classDescriptor = receiver.type.constructor.declarationDescriptor as? ClassDescriptor ?: return null
+ return when {
+ classDescriptor.isData -> {
+ val primaryParameters = classDescriptor.unsubstitutedPrimaryConstructor?.valueParameters
+ primaryParameters?.getOrNull(declarationEntry.entryIndex())?.name?.asString()
+ }
+ DescriptorUtils.isSubclass(classDescriptor, classDescriptor.builtIns.mapEntry) -> {
+ listOf("key", "value").getOrNull(declarationEntry.entryIndex())
+ }
+ else -> null
+ }
+ }
+
+ private fun KtDestructuringDeclarationEntry.entryIndex() =
+ parent.getChildrenOfType().indexOf(this)
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/.intention b/idea/testData/intentions/replaceUnderscoreWithParameterName/.intention
new file mode 100644
index 00000000000..0ee411edc81
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/.intention
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.intentions.ReplaceUnderscoreWithParameterNameIntention
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/anonymous.kt b/idea/testData/intentions/replaceUnderscoreWithParameterName/anonymous.kt
new file mode 100644
index 00000000000..d9f3d2f89e3
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/anonymous.kt
@@ -0,0 +1 @@
+val f = fun(i: Int, _: String) = i
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/anonymous.kt.after b/idea/testData/intentions/replaceUnderscoreWithParameterName/anonymous.kt.after
new file mode 100644
index 00000000000..53666d124b6
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/anonymous.kt.after
@@ -0,0 +1 @@
+val f = fun(i: Int, s: String) = i
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/conflict.kt b/idea/testData/intentions/replaceUnderscoreWithParameterName/conflict.kt
new file mode 100644
index 00000000000..dabe9d9c5de
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/conflict.kt
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+
+fun foo(pair: Pair) {
+ val (_, _) = pair
+ val first = 42
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/conflict.kt.after b/idea/testData/intentions/replaceUnderscoreWithParameterName/conflict.kt.after
new file mode 100644
index 00000000000..9c5a4d6dfa3
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/conflict.kt.after
@@ -0,0 +1,6 @@
+// WITH_RUNTIME
+
+fun foo(pair: Pair) {
+ val (first1, _) = pair
+ val first = 42
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/destructuringDeclaration.kt b/idea/testData/intentions/replaceUnderscoreWithParameterName/destructuringDeclaration.kt
new file mode 100644
index 00000000000..6ea8369d23b
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/destructuringDeclaration.kt
@@ -0,0 +1,5 @@
+data class Data(val first: Int, val second: Int)
+
+fun foo() {
+ val (first, _) = Data(1, 2)
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/destructuringDeclaration.kt.after b/idea/testData/intentions/replaceUnderscoreWithParameterName/destructuringDeclaration.kt.after
new file mode 100644
index 00000000000..7f8bd53e92d
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/destructuringDeclaration.kt.after
@@ -0,0 +1,5 @@
+data class Data(val first: Int, val second: Int)
+
+fun foo() {
+ val (first, second) = Data(1, 2)
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/for.kt b/idea/testData/intentions/replaceUnderscoreWithParameterName/for.kt
new file mode 100644
index 00000000000..8b9b920eb6b
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/for.kt
@@ -0,0 +1,7 @@
+data class Data(val first: Int, val second: Int)
+
+fun foo(list: List) {
+ for ((_, _) in list) {
+
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/for.kt.after b/idea/testData/intentions/replaceUnderscoreWithParameterName/for.kt.after
new file mode 100644
index 00000000000..01555447068
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/for.kt.after
@@ -0,0 +1,7 @@
+data class Data(val first: Int, val second: Int)
+
+fun foo(list: List) {
+ for ((first, _) in list) {
+
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/lambda.kt b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambda.kt
new file mode 100644
index 00000000000..873d1a67fc0
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambda.kt
@@ -0,0 +1,7 @@
+fun foo(f: (a: Int, b: Int, c: Int) -> Unit) {
+ f(1, 2, 3)
+}
+
+fun bar() {
+ foo { _, _, _ -> }
+}
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/lambda.kt.after b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambda.kt.after
new file mode 100644
index 00000000000..cc001f96d37
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambda.kt.after
@@ -0,0 +1,7 @@
+fun foo(f: (a: Int, b: Int, c: Int) -> Unit) {
+ f(1, 2, 3)
+}
+
+fun bar() {
+ foo { _, b, _ -> }
+}
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaConflict.kt b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaConflict.kt
new file mode 100644
index 00000000000..2fca0249661
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaConflict.kt
@@ -0,0 +1,7 @@
+fun foo(f: (a: Int, b: Int, c: Int) -> Int) {
+ f(1, 2, 3)
+}
+
+fun bar(c: Int) {
+ foo { _, _, _ -> c }
+}
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaConflict.kt.after b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaConflict.kt.after
new file mode 100644
index 00000000000..e64b67a8f96
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaConflict.kt.after
@@ -0,0 +1,7 @@
+fun foo(f: (a: Int, b: Int, c: Int) -> Int) {
+ f(1, 2, 3)
+}
+
+fun bar(c: Int) {
+ foo { _, _, c1 -> c }
+}
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaNoNames.kt b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaNoNames.kt
new file mode 100644
index 00000000000..f1a05a33b67
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaNoNames.kt
@@ -0,0 +1,7 @@
+fun foo(f: (Int, Int, Int) -> Unit) {
+ f(1, 2, 3)
+}
+
+fun bar() {
+ foo { _, _, _ -> }
+}
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaNoNames.kt.after b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaNoNames.kt.after
new file mode 100644
index 00000000000..0b915ddf0e3
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaNoNames.kt.after
@@ -0,0 +1,7 @@
+fun foo(f: (Int, Int, Int) -> Unit) {
+ f(1, 2, 3)
+}
+
+fun bar() {
+ foo { _, i, _ -> }
+}
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/map.kt b/idea/testData/intentions/replaceUnderscoreWithParameterName/map.kt
new file mode 100644
index 00000000000..9ea6dc9a056
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/map.kt
@@ -0,0 +1,7 @@
+// WITH_RUNTIME
+
+fun foo(map: Map) {
+ for ((_, _) in map) {
+
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/map.kt.after b/idea/testData/intentions/replaceUnderscoreWithParameterName/map.kt.after
new file mode 100644
index 00000000000..ca430947824
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/map.kt.after
@@ -0,0 +1,7 @@
+// WITH_RUNTIME
+
+fun foo(map: Map) {
+ for ((key, _) in map) {
+
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/triple.kt b/idea/testData/intentions/replaceUnderscoreWithParameterName/triple.kt
new file mode 100644
index 00000000000..307c54feca7
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/triple.kt
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+fun foo(t: Triple) {
+ val (_, _, _) = t
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/replaceUnderscoreWithParameterName/triple.kt.after b/idea/testData/intentions/replaceUnderscoreWithParameterName/triple.kt.after
new file mode 100644
index 00000000000..73e4220b787
--- /dev/null
+++ b/idea/testData/intentions/replaceUnderscoreWithParameterName/triple.kt.after
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+fun foo(t: Triple) {
+ val (_, _, third) = t
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
index d0e53f6b17e..52ea8167278 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
@@ -13951,6 +13951,69 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
+ @TestMetadata("idea/testData/intentions/replaceUnderscoreWithParameterName")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class ReplaceUnderscoreWithParameterName extends AbstractIntentionTest {
+ public void testAllFilesPresentInReplaceUnderscoreWithParameterName() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceUnderscoreWithParameterName"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("anonymous.kt")
+ public void testAnonymous() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceUnderscoreWithParameterName/anonymous.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("conflict.kt")
+ public void testConflict() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceUnderscoreWithParameterName/conflict.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("destructuringDeclaration.kt")
+ public void testDestructuringDeclaration() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceUnderscoreWithParameterName/destructuringDeclaration.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("for.kt")
+ public void testFor() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceUnderscoreWithParameterName/for.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("lambda.kt")
+ public void testLambda() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceUnderscoreWithParameterName/lambda.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("lambdaConflict.kt")
+ public void testLambdaConflict() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaConflict.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("lambdaNoNames.kt")
+ public void testLambdaNoNames() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceUnderscoreWithParameterName/lambdaNoNames.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("map.kt")
+ public void testMap() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceUnderscoreWithParameterName/map.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("triple.kt")
+ public void testTriple() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceUnderscoreWithParameterName/triple.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/intentions/replaceUntilWithRangeTo")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)