KT-7144 Intention action on an argument in a call to use named arguments form

#KT-7144 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-06-23 21:05:24 +03:00
parent c689af142a
commit 9d83510af5
33 changed files with 309 additions and 15 deletions
@@ -354,12 +354,12 @@ public class JetPsiFactory(private val project: Project) {
createExpressionByPattern("if ($0) $1", condition, thenExpr)) as JetIfExpression
}
public fun createArgument(expression: JetExpression, name: String? = null, isSpread: Boolean = false): JetValueArgument {
public fun createArgument(expression: JetExpression, name: Name? = null, isSpread: Boolean = false): JetValueArgument {
val argumentList = buildByPattern({ pattern, args -> createByPattern(pattern, *args) { createCallArguments(it) } }) {
appendFixedText("(")
if (name != null) {
appendName(Name.identifier(name))
appendName(name)
appendFixedText("=")
}
@@ -20,8 +20,8 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses
@@ -43,10 +43,10 @@ public fun JetFunctionLiteralArgument.moveInsideParentheses(bindingContext: Bind
return moveInsideParenthesesAndReplaceWith(this.getArgumentExpression(), bindingContext)
}
public fun JetFunctionLiteralArgument.getFunctionLiteralArgumentName(bindingContext: BindingContext): String? {
public fun JetFunctionLiteralArgument.getFunctionLiteralArgumentName(bindingContext: BindingContext): Name? {
val callExpression = getParent() as JetCallExpression
val resolvedCall = callExpression.getResolvedCall(bindingContext)
return (resolvedCall?.getArgumentMapping(this) as? ArgumentMatch)?.valueParameter?.getName()?.toString()
return (resolvedCall?.getArgumentMapping(this) as? ArgumentMatch)?.valueParameter?.getName()
}
public fun JetFunctionLiteralArgument.moveInsideParenthesesAndReplaceWith(
@@ -56,7 +56,7 @@ public fun JetFunctionLiteralArgument.moveInsideParenthesesAndReplaceWith(
public fun JetFunctionLiteralArgument.moveInsideParenthesesAndReplaceWith(
replacement: JetExpression,
functionLiteralArgumentName: String?
functionLiteralArgumentName: Name?
): JetCallExpression {
val oldCallExpression = getParent() as JetCallExpression
val newCallExpression = oldCallExpression.copy() as JetCallExpression
@@ -0,0 +1 @@
list.joinToString(<spot>separator = </spot>"\n")
@@ -0,0 +1 @@
list.joinToString("\n")
@@ -0,0 +1,5 @@
<html>
<body>
This intention adds name to an argument of a function call to use named argument syntax.
</body>
</html>
+5
View File
@@ -923,6 +923,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.AddNameToArgumentIntention</className>
<category>Kotlin</category>
</intentionAction>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.DeprecatedCallableAddReplaceWithInspection"
displayName="Add 'replaceWith' argument to 'deprecated' annotation"
groupName="Kotlin"
@@ -0,0 +1,74 @@
/*
* Copyright 2010-2015 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.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.conversion.copy.end
import org.jetbrains.kotlin.idea.conversion.copy.start
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatchStatus
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
public class AddNameToArgumentIntention
: JetSelfTargetingIntention<JetValueArgument>(javaClass(), "Use named argument"), LowPriorityAction {
override fun isApplicableTo(element: JetValueArgument, caretOffset: Int): Boolean {
val expression = element.getArgumentExpression() ?: return false
if (detectNameToAdd(element) == null) return false
if (expression is JetFunctionLiteralExpression) {
val range = expression.getTextRange()
return caretOffset == range.start || caretOffset == range.end
}
return true
}
override fun applyTo(element: JetValueArgument, editor: Editor) {
val name = detectNameToAdd(element)!!
val newArgument = JetPsiFactory(element).createArgument(element.getArgumentExpression()!!, name, element.getSpreadElement() != null)
element.replace(newArgument)
}
private fun detectNameToAdd(argument: JetValueArgument): Name? {
if (argument.isNamed()) return null
if (argument is JetFunctionLiteralArgument) return null
val argumentList = argument.getParent() as? JetValueArgumentList ?: return null
if (argument != argumentList.getArguments().last()) return null
val callExpr = argumentList.getParent() as? JetExpression ?: return null
val resolvedCall = callExpr.getResolvedCall(callExpr.analyze(BodyResolveMode.PARTIAL)) ?: return null
val argumentMatch = resolvedCall.getArgumentMapping(argument) as? ArgumentMatch ?: return null
if (argumentMatch.status != ArgumentMatchStatus.SUCCESS) return null
if (!resolvedCall.getResultingDescriptor().hasStableParameterNames()) return null
if (argumentMatch.valueParameter.getVarargElementType() != null) {
val varargArgument = resolvedCall.getValueArguments()[argumentMatch.valueParameter] as? VarargValueArgument ?: return null
if (varargArgument.getArguments().size() != 1) return null
}
return argumentMatch.valueParameter.getName()
}
}
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.idea.JetIcons;
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage;
import org.jetbrains.kotlin.idea.core.CorePackage;
import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.JetCallElement;
import org.jetbrains.kotlin.psi.JetExpression;
import org.jetbrains.kotlin.psi.JetFile;
@@ -147,7 +148,7 @@ public class AddNameToArgumentFix extends JetIntentionAction<JetValueArgument> {
private static JetValueArgument getParsedArgumentWithName(@NotNull String name, @NotNull JetValueArgument argument) {
JetExpression argumentExpression = argument.getArgumentExpression();
assert argumentExpression != null : "Argument should be already parsed.";
return JetPsiFactory(argument).createArgument(argumentExpression, name, false);
return JetPsiFactory(argument).createArgument(argumentExpression, Name.identifier(name), argument.getSpreadElement() != null);
}
@NotNull
@@ -23,17 +23,15 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.getFunctionLiteralArgumentName
import org.jetbrains.kotlin.idea.core.moveInsideParenthesesAndReplaceWith
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionFactory
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.idea.core.getFunctionLiteralArgumentName
import org.jetbrains.kotlin.idea.core.moveInsideParenthesesAndReplaceWith
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.utils.sure
import java.util.ArrayList
public class DeprecatedLambdaSyntaxFix(element: JetFunctionLiteralExpression) : JetIntentionAction<JetFunctionLiteralExpression>(element), CleanupFix {
override fun getText() = JetBundle.message("migrate.lambda.syntax")
@@ -102,7 +100,7 @@ private class DeparenthesizeParameterList(
private class LambdaToFunctionExpression(
val functionLiteralExpression: JetFunctionLiteralExpression
): DeprecatedSyntaxFix {
val functionLiteralArgumentName: String?
val functionLiteralArgumentName: Name?
val receiverType: String?
val returnType: String?
@@ -485,7 +485,7 @@ public abstract class DeprecatedSymbolUsageFixBase(
if (argument.isNamed()) continue
if (argument is JetFunctionLiteralArgument) continue
val argumentMatch = resolvedCall.getArgumentMapping(argument) as ArgumentMatch
val name = argumentMatch.valueParameter.getName().asString()
val name = argumentMatch.valueParameter.getName()
//TODO: not always correct for vararg's
val newArgument = psiFactory.createArgument(argument.getArgumentExpression()!!, name, argument.getSpreadElement() != null)
@@ -43,7 +43,7 @@ public class ReplaceJavaAnnotationPositionedArgumentsFix(element: JetAnnotationE
val valueArgument = (argument.value as? ExpressionValueArgument)?.getValueArgument() ?: return@argumentProcessor
val expression = valueArgument.getArgumentExpression() ?: return@argumentProcessor
valueArgument.asElement().replace(psiFactory.createArgument(expression, argument.getKey().getName().asString()))
valueArgument.asElement().replace(psiFactory.createArgument(expression, argument.getKey().getName()))
}
}
+1
View File
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.AddNameToArgumentIntention
@@ -0,0 +1,6 @@
// IS_APPLICABLE: false
fun foo(s: String, b: Boolean){}
fun bar() {
foo("", b = true<caret>)
}
@@ -0,0 +1,8 @@
// IS_APPLICABLE: false
// ERROR: <html>None of the following functions can be called with the arguments supplied. <ul><li>foo(String, Boolean, <font color=red><b>Char</b></font>) <i>defined in</i> root package</li><li>foo(String, Boolean, <font color=red><b>Int</b></font>) <i>defined in</i> root package</li></ul></html>
fun foo(s: String, b: Boolean, p: Int){}
fun foo(s: String, b: Boolean, c: Char){}
fun bar() {
foo("", <caret>true)
}
@@ -0,0 +1,6 @@
// IS_APPLICABLE: false
fun foo(s: String, handler: () -> Unit){}
fun bar() {
foo("") <caret>{ }
}
@@ -0,0 +1,7 @@
// ERROR: No value passed for parameter p
fun foo(s: String, b: Boolean, p: Int){}
fun bar() {
foo("", <caret>true)
}
@@ -0,0 +1,7 @@
// ERROR: No value passed for parameter p
fun foo(s: String, b: Boolean, p: Int){}
fun bar() {
foo("", <caret>b = true)
}
@@ -0,0 +1,6 @@
// IS_APPLICABLE: false
// WITH_RUNTIME
fun f() {
java.io.File(<caret>"file")
}
+6
View File
@@ -0,0 +1,6 @@
// IS_APPLICABLE: false
fun foo(s: String, b: Boolean){}
fun bar() {
foo(<caret>"", true)
}
@@ -0,0 +1,5 @@
// IS_APPLICABLE: false
// ERROR: Unresolved reference: foo
fun bar() {
foo("", <caret>true)
}
@@ -0,0 +1,5 @@
fun foo(handler: () -> Unit){}
fun bar() {
foo(<caret>{ })
}
@@ -0,0 +1,5 @@
fun foo(handler: () -> Unit){}
fun bar() {
foo(<caret>handler = { })
}
@@ -0,0 +1,5 @@
fun foo(handler: () -> Unit){}
fun bar() {
foo({ }<caret>)
}
@@ -0,0 +1,5 @@
fun foo(handler: () -> Unit){}
fun bar() {
foo(handler = { }<caret>)
}
@@ -0,0 +1,7 @@
// IS_APPLICABLE: false
fun foo(handler: () -> Unit){}
fun bar() {
foo({<caret>})
}
+5
View File
@@ -0,0 +1,5 @@
fun foo(s: String, b: Boolean){}
fun bar() {
foo("", true<caret>)
}
@@ -0,0 +1,5 @@
fun foo(s: String, b: Boolean){}
fun bar() {
foo("", b = true<caret>)
}
+5
View File
@@ -0,0 +1,5 @@
fun foo(vararg s: String){}
fun bar() {
foo(""<caret>)
}
@@ -0,0 +1,5 @@
fun foo(vararg s: String){}
fun bar() {
foo(s = ""<caret>)
}
+5
View File
@@ -0,0 +1,5 @@
fun foo(vararg s: String){}
fun bar(array: Array<String>) {
foo(<caret>*array)
}
@@ -0,0 +1,5 @@
fun foo(vararg s: String){}
fun bar(array: Array<String>) {
foo(<caret>s = *array)
}
+7
View File
@@ -0,0 +1,7 @@
// IS_APPLICABLE: false
fun foo(vararg s: String){}
fun bar() {
foo("a", "b"<caret>)
}
@@ -92,6 +92,99 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/addNameToArgument")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class AddNameToArgument extends AbstractIntentionTest {
public void testAllFilesPresentInAddNameToArgument() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/addNameToArgument"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("alreadyNamed.kt")
public void testAlreadyNamed() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/alreadyNamed.kt");
doTest(fileName);
}
@TestMetadata("ambiguousCall.kt")
public void testAmbiguousCall() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/ambiguousCall.kt");
doTest(fileName);
}
@TestMetadata("functionLiteralArgument.kt")
public void testFunctionLiteralArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/functionLiteralArgument.kt");
doTest(fileName);
}
@TestMetadata("incompleteCall.kt")
public void testIncompleteCall() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/incompleteCall.kt");
doTest(fileName);
}
@TestMetadata("javaMethod.kt")
public void testJavaMethod() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/javaMethod.kt");
doTest(fileName);
}
@TestMetadata("notLast.kt")
public void testNotLast() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/notLast.kt");
doTest(fileName);
}
@TestMetadata("notResolved.kt")
public void testNotResolved() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/notResolved.kt");
doTest(fileName);
}
@TestMetadata("rangeForLambda1.kt")
public void testRangeForLambda1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/rangeForLambda1.kt");
doTest(fileName);
}
@TestMetadata("rangeForLambda2.kt")
public void testRangeForLambda2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/rangeForLambda2.kt");
doTest(fileName);
}
@TestMetadata("rangeForLambda3.kt")
public void testRangeForLambda3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/rangeForLambda3.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/simple.kt");
doTest(fileName);
}
@TestMetadata("vararg1.kt")
public void testVararg1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/vararg1.kt");
doTest(fileName);
}
@TestMetadata("vararg2.kt")
public void testVararg2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/vararg2.kt");
doTest(fileName);
}
@TestMetadata("vararg3.kt")
public void testVararg3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/addNameToArgument/vararg3.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/branched")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)