Introduce intention to name anonymous parameter #KT-17191 Fixed

This commit is contained in:
Dmitry Neverov
2017-04-24 23:01:35 +03:00
committed by Mikhail Glukhikh
parent bc071bf543
commit 9dd217eee3
25 changed files with 276 additions and 0 deletions
@@ -0,0 +1 @@
val (first, <spot>second</spot>) = Pair(1, 2)
@@ -0,0 +1 @@
val (first, <spot>_</spot>) = Pair(1, 2)
@@ -0,0 +1,5 @@
<html>
<body>
Gives name to an anonymous parameter
</body>
</html>
+5
View File
@@ -1265,6 +1265,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ReplaceUnderscoreWithParameterNameIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.AddJvmOverloadsIntention</className>
<category>Kotlin</category>
@@ -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>(
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<KtFunctionLiteral>(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<KtLambdaArgument>(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<KtDestructuringDeclarationEntry>().indexOf(this)
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.ReplaceUnderscoreWithParameterNameIntention
@@ -0,0 +1 @@
val f = fun(i: Int, <caret>_: String) = i
@@ -0,0 +1 @@
val f = fun(i: Int, s: String) = i
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(pair: Pair<Int, Int>) {
val (<caret>_, _) = pair
val first = 42
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo(pair: Pair<Int, Int>) {
val (first1, _) = pair
val first = 42
}
@@ -0,0 +1,5 @@
data class Data(val first: Int, val second: Int)
fun foo() {
val (first, <caret>_) = Data(1, 2)
}
@@ -0,0 +1,5 @@
data class Data(val first: Int, val second: Int)
fun foo() {
val (first, second) = Data(1, 2)
}
@@ -0,0 +1,7 @@
data class Data(val first: Int, val second: Int)
fun foo(list: List<Data>) {
for ((<caret>_, _) in list) {
}
}
@@ -0,0 +1,7 @@
data class Data(val first: Int, val second: Int)
fun foo(list: List<Data>) {
for ((first, _) in list) {
}
}
@@ -0,0 +1,7 @@
fun foo(f: (a: Int, b: Int, c: Int) -> Unit) {
f(1, 2, 3)
}
fun bar() {
foo { _, <caret>_, _ -> }
}
@@ -0,0 +1,7 @@
fun foo(f: (a: Int, b: Int, c: Int) -> Unit) {
f(1, 2, 3)
}
fun bar() {
foo { _, b, _ -> }
}
@@ -0,0 +1,7 @@
fun foo(f: (a: Int, b: Int, c: Int) -> Int) {
f(1, 2, 3)
}
fun bar(c: Int) {
foo { _, _, <caret>_ -> c }
}
@@ -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 }
}
@@ -0,0 +1,7 @@
fun foo(f: (Int, Int, Int) -> Unit) {
f(1, 2, 3)
}
fun bar() {
foo { _, <caret>_, _ -> }
}
@@ -0,0 +1,7 @@
fun foo(f: (Int, Int, Int) -> Unit) {
f(1, 2, 3)
}
fun bar() {
foo { _, i, _ -> }
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun foo(map: Map<String, Int>) {
for ((<caret>_, _) in map) {
}
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun foo(map: Map<String, Int>) {
for ((key, _) in map) {
}
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun foo(t: Triple<String, Int, Boolean>) {
val (_, _, <caret>_) = t
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun foo(t: Triple<String, Int, Boolean>) {
val (_, _, third) = t
}
@@ -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)