From b2bb581127dea24acbbad36962c70802ccfbd6a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sapalski?= Date: Mon, 6 May 2013 10:16:43 +0200 Subject: [PATCH] Quickfix for MIXING_NAMED_AND_POSITIONED_ARGUMENTS. --- .../jetbrains/jet/lang/psi/JetPsiFactory.java | 9 + .../jetbrains/jet/plugin/JetBundle.properties | 7 +- .../plugin/quickfix/AddNameToArgumentFix.java | 198 ++++++++++++++++++ .../jet/plugin/quickfix/QuickFixes.java | 2 + .../afterMixedNamedAndPositionalArguments.kt | 6 + ...dNamedAndPositionalArgumentsConstructor.kt | 6 + ...ixedNamedAndPositionalArgumentsMultiple.kt | 6 + ...MixedNamedAndPositionalArgumentsSubtype.kt | 8 + ...xedNamedAndPositionalArgumentsUsedNamed.kt | 6 + ...medAndPositionalArgumentsUsedPositional.kt | 6 + .../beforeMixedNamedAndPositionalArguments.kt | 6 + ...dNamedAndPositionalArgumentsConstructor.kt | 6 + ...ixedNamedAndPositionalArgumentsMultiple.kt | 6 + ...MixedNamedAndPositionalArgumentsSubtype.kt | 8 + ...xedNamedAndPositionalArgumentsUsedNamed.kt | 6 + ...medAndPositionalArgumentsUsedPositional.kt | 6 + .../quickfix/QuickFixTestGenerated.java | 30 +++ 17 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 idea/src/org/jetbrains/jet/plugin/quickfix/AddNameToArgumentFix.java create mode 100644 idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArguments.kt create mode 100644 idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsConstructor.kt create mode 100644 idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsMultiple.kt create mode 100644 idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsSubtype.kt create mode 100644 idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsUsedNamed.kt create mode 100644 idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsUsedPositional.kt create mode 100644 idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArguments.kt create mode 100644 idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsConstructor.kt create mode 100644 idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsMultiple.kt create mode 100644 idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsSubtype.kt create mode 100644 idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsUsedNamed.kt create mode 100644 idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsUsedPositional.kt diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java index 23df1f27e61..cfb5dfd6f59 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java @@ -283,6 +283,15 @@ public class JetPsiFactory { return (JetIfExpression) createExpression(project, JetPsiUnparsingUtils.toIf(condition, thenExpr, elseExpr)); } + @NotNull + public static JetValueArgument createArgumentWithName( + @NotNull Project project, + @NotNull String name, + @NotNull JetExpression argumentExpression + ) { + return createCallArguments(project, "(" + name + " = " + argumentExpression.getText() + ")").getArguments().get(0); + } + public static class IfChainBuilder { private final StringBuilder sb = new StringBuilder(); private boolean first = true; diff --git a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties index c56c934fe75..0ac4c6666ce 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties @@ -196,4 +196,9 @@ change.function.signature.family=Change function signature change.function.signature.chooser.title=Choose signature change.function.signature.action=Change function signature remove.unnecessary.parentheses=Remove unnecessary parentheses -remove.unnecessary.parentheses.family=Remove Unnecessary Parentheses \ No newline at end of file +remove.unnecessary.parentheses.family=Remove Unnecessary Parentheses +add.name.to.argument.family=Add Name to Argument +add.name.to.argument.single=Add name to argument\: ''{0}'' +add.name.to.argument.multiple=Add name to argument... +add.name.to.argument.action=Add name to argument... +add.name.to.parameter.name.chooser.title=Choose parameter name diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/AddNameToArgumentFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/AddNameToArgumentFix.java new file mode 100644 index 00000000000..cc0f03947f3 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/AddNameToArgumentFix.java @@ -0,0 +1,198 @@ +/* + * Copyright 2010-2013 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.jet.plugin.quickfix; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.ListPopupStep; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.descriptors.CallableDescriptor; +import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor; +import org.jetbrains.jet.lang.diagnostics.Diagnostic; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.checker.JetTypeChecker; +import org.jetbrains.jet.plugin.JetBundle; +import org.jetbrains.jet.plugin.JetIcons; +import org.jetbrains.jet.plugin.project.WholeProjectAnalyzerFacade; + +import javax.swing.*; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class AddNameToArgumentFix extends JetIntentionAction { + + @NotNull + private final List possibleNames; + + public AddNameToArgumentFix(@NotNull JetValueArgument argument, @NotNull List possibleNames) { + super(argument); + this.possibleNames = possibleNames; + } + + @NotNull + private static List generatePossibleNames(@NotNull JetValueArgument argument) { + JetCallElement callElement = PsiTreeUtil.getParentOfType(argument, JetCallElement.class); + assert callElement != null : "The argument has to be inside a function or constructor call"; + + JetExpression callee = callElement.getCalleeExpression(); + if (!(callee instanceof JetReferenceExpression)) return Collections.emptyList(); + + BindingContext context = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile((JetFile) argument.getContainingFile()).getBindingContext(); + ResolvedCall resolvedCall = context.get(BindingContext.RESOLVED_CALL, (JetReferenceExpression) callee); + if (resolvedCall == null) return Collections.emptyList(); + + CallableDescriptor callableDescriptor = resolvedCall.getResultingDescriptor(); + JetType type = context.get(BindingContext.EXPRESSION_TYPE, argument.getArgumentExpression()); + Set usedParameters = getUsedParameters(callElement, callableDescriptor); + List names = Lists.newArrayList(); + for (ValueParameterDescriptor parameter: callableDescriptor.getValueParameters()) { + String name = parameter.getName().getName(); + if (usedParameters.contains(name)) continue; + if (type == null || JetTypeChecker.INSTANCE.isSubtypeOf(type, parameter.getType())) { + names.add(name); + } + } + return names; + } + + @NotNull + private static Set getUsedParameters(@NotNull JetCallElement callElement, @NotNull CallableDescriptor callableDescriptor) { + Set usedParameters = Sets.newHashSet(); + boolean isPositionalArgument = true; + int idx = 0; + for (ValueArgument argument : callElement.getValueArguments()) { + if (argument.isNamed()) { + JetValueArgumentName name = argument.getArgumentName(); + assert name != null : "Named argument's name cannot be null"; + usedParameters.add(name.getText()); + isPositionalArgument = false; + } + else if (isPositionalArgument) { + ValueParameterDescriptor parameter = callableDescriptor.getValueParameters().get(idx); + usedParameters.add(parameter.getName().getName()); + idx++; + } + } + return usedParameters; + } + + @Override + protected void invoke(@NotNull Project project, Editor editor, JetFile file) { + if (possibleNames.size() == 1 || editor == null || !editor.getComponent().isShowing()) { + addName(project, element, possibleNames.get(0)); + } + else { + chooseNameAndAdd(project, editor); + } + } + + private void chooseNameAndAdd(@NotNull Project project, Editor editor) { + JBPopupFactory.getInstance().createListPopup(getNamePopup(project)).showInBestPositionFor(editor); + } + + private ListPopupStep getNamePopup(final @NotNull Project project) { + return new BaseListPopupStep( + JetBundle.message("add.name.to.parameter.name.chooser.title"), possibleNames) { + @Override + public PopupStep onChosen(String selectedName, boolean finalChoice) { + if (finalChoice) { + addName(project, element, selectedName); + } + return FINAL_CHOICE; + } + + @Override + public Icon getIconFor(String name) { + return JetIcons.PARAMETER; + } + + @NotNull + @Override + public String getTextFor(String name) { + return getParsedArgumentWithName(name, element).getText(); + } + }; + } + + private static void addName(@NotNull Project project, final @NotNull JetValueArgument argument, final @NotNull String name) { + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + JetValueArgument newArgument = getParsedArgumentWithName(name, argument); + argument.replace(newArgument); + } + }); + } + }, JetBundle.message("add.name.to.argument.action"), null); + } + + @NotNull + private static JetValueArgument getParsedArgumentWithName(@NotNull String name, @NotNull JetValueArgument argument) { + JetExpression argumentExpression = argument.getArgumentExpression(); + assert argumentExpression != null : "Argument should be already parsed."; + return JetPsiFactory.createArgumentWithName(argument.getProject(), name, argumentExpression); + } + + @NotNull + @Override + public String getText() { + if (possibleNames.size() == 1) { + return JetBundle.message("add.name.to.argument.single", getParsedArgumentWithName(possibleNames.get(0), element).getText()); + } else { + return JetBundle.message("add.name.to.argument.multiple"); + } + } + + @NotNull + @Override + public String getFamilyName() { + return JetBundle.message("add.name.to.argument.family"); + } + @NotNull + public static JetIntentionActionFactory createFactory() { + return new JetIntentionActionFactory() { + @Nullable + @Override + public IntentionAction createAction(Diagnostic diagnostic) { + JetValueArgument argument = QuickFixUtil.getParentElementOfType(diagnostic, JetValueArgument.class); + if (argument == null) return null; + List possibleNames = generatePossibleNames(argument); + return possibleNames.isEmpty() ? null : new AddNameToArgumentFix(argument, possibleNames); + } + }; + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java index 756b883bfee..9369f5b330b 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java @@ -85,6 +85,8 @@ public class QuickFixes { factories.put(NON_VARARG_SPREAD, RemovePsiElementSimpleFix.createRemoveSpreadFactory()); + factories.put(MIXING_NAMED_AND_POSITIONED_ARGUMENTS, AddNameToArgumentFix.createFactory()); + factories.put(NON_MEMBER_FUNCTION_NO_BODY, addFunctionBodyFactory); factories.put(NOTHING_TO_OVERRIDE, RemoveModifierFix.createRemoveModifierFromListOwnerFactory(OVERRIDE_KEYWORD)); diff --git a/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArguments.kt b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArguments.kt new file mode 100644 index 00000000000..a6c98c01470 --- /dev/null +++ b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArguments.kt @@ -0,0 +1,6 @@ +// "Add name to argument: 'b = "FOO"'" "true" +fun f(a: Int, b: String) {} + +fun g() { + f(a = 10, b = "FOO") +} \ No newline at end of file diff --git a/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsConstructor.kt b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsConstructor.kt new file mode 100644 index 00000000000..fca2a83f77f --- /dev/null +++ b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsConstructor.kt @@ -0,0 +1,6 @@ +// "Add name to argument: 'b = "FOO"'" "true" +class A(a: Int, b: String) {} + +fun f() { + A(a = 1, b = "FOO") +} \ No newline at end of file diff --git a/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsMultiple.kt b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsMultiple.kt new file mode 100644 index 00000000000..9ee83810f46 --- /dev/null +++ b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsMultiple.kt @@ -0,0 +1,6 @@ +// "Add name to argument..." "true" +fun f(a: Int, b: String = "b", c: String = "c") {} + +fun g() { + f(a = 10, b = "FOO") +} \ No newline at end of file diff --git a/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsSubtype.kt b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsSubtype.kt new file mode 100644 index 00000000000..8c19cc0867d --- /dev/null +++ b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsSubtype.kt @@ -0,0 +1,8 @@ +// "Add name to argument: 'b = B()'" "true" +open class A {} +open class B : A() {} + +fun f(a: Int, b: A) {} +fun g() { + f(a=1, b = B()) +} \ No newline at end of file diff --git a/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsUsedNamed.kt b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsUsedNamed.kt new file mode 100644 index 00000000000..993b836aef6 --- /dev/null +++ b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsUsedNamed.kt @@ -0,0 +1,6 @@ +// "Add name to argument: 'b = "FOO"'" "true" +fun f(a: String, b: String) {} + +fun g() { + f(a = "BAR", b = "FOO") +} \ No newline at end of file diff --git a/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsUsedPositional.kt b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsUsedPositional.kt new file mode 100644 index 00000000000..077ee955ac4 --- /dev/null +++ b/idea/testData/quickfix/checkArguments/afterMixedNamedAndPositionalArgumentsUsedPositional.kt @@ -0,0 +1,6 @@ +// "Add name to argument: 'b = "FOO"'" "true" +fun f(a: String, x: Int, b: String) {} + +fun g() { + f("BAR", x = 10, b = "FOO") +} \ No newline at end of file diff --git a/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArguments.kt b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArguments.kt new file mode 100644 index 00000000000..7d8310d29e7 --- /dev/null +++ b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArguments.kt @@ -0,0 +1,6 @@ +// "Add name to argument: 'b = "FOO"'" "true" +fun f(a: Int, b: String) {} + +fun g() { + f(a = 10, "FOO") +} \ No newline at end of file diff --git a/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsConstructor.kt b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsConstructor.kt new file mode 100644 index 00000000000..3e6ccdbe349 --- /dev/null +++ b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsConstructor.kt @@ -0,0 +1,6 @@ +// "Add name to argument: 'b = "FOO"'" "true" +class A(a: Int, b: String) {} + +fun f() { + A(a = 1, "FOO") +} \ No newline at end of file diff --git a/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsMultiple.kt b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsMultiple.kt new file mode 100644 index 00000000000..16e2f905cc8 --- /dev/null +++ b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsMultiple.kt @@ -0,0 +1,6 @@ +// "Add name to argument..." "true" +fun f(a: Int, b: String = "b", c: String = "c") {} + +fun g() { + f(a = 10, "FOO") +} \ No newline at end of file diff --git a/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsSubtype.kt b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsSubtype.kt new file mode 100644 index 00000000000..8ff0c9b38d4 --- /dev/null +++ b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsSubtype.kt @@ -0,0 +1,8 @@ +// "Add name to argument: 'b = B()'" "true" +open class A {} +open class B : A() {} + +fun f(a: Int, b: A) {} +fun g() { + f(a=1, B()) +} \ No newline at end of file diff --git a/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsUsedNamed.kt b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsUsedNamed.kt new file mode 100644 index 00000000000..7460c8eb891 --- /dev/null +++ b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsUsedNamed.kt @@ -0,0 +1,6 @@ +// "Add name to argument: 'b = "FOO"'" "true" +fun f(a: String, b: String) {} + +fun g() { + f(a = "BAR", "FOO") +} \ No newline at end of file diff --git a/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsUsedPositional.kt b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsUsedPositional.kt new file mode 100644 index 00000000000..d984c15088c --- /dev/null +++ b/idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsUsedPositional.kt @@ -0,0 +1,6 @@ +// "Add name to argument: 'b = "FOO"'" "true" +fun f(a: String, x: Int, b: String) {} + +fun g() { + f("BAR", x = 10, "FOO") +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java index 9b32c411512..d35d22f475b 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java @@ -422,6 +422,36 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/quickfix/checkArguments"), Pattern.compile("^before(\\w+)\\.kt$"), true); } + @TestMetadata("beforeMixedNamedAndPositionalArguments.kt") + public void testMixedNamedAndPositionalArguments() throws Exception { + doTest("idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArguments.kt"); + } + + @TestMetadata("beforeMixedNamedAndPositionalArgumentsConstructor.kt") + public void testMixedNamedAndPositionalArgumentsConstructor() throws Exception { + doTest("idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsConstructor.kt"); + } + + @TestMetadata("beforeMixedNamedAndPositionalArgumentsMultiple.kt") + public void testMixedNamedAndPositionalArgumentsMultiple() throws Exception { + doTest("idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsMultiple.kt"); + } + + @TestMetadata("beforeMixedNamedAndPositionalArgumentsSubtype.kt") + public void testMixedNamedAndPositionalArgumentsSubtype() throws Exception { + doTest("idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsSubtype.kt"); + } + + @TestMetadata("beforeMixedNamedAndPositionalArgumentsUsedNamed.kt") + public void testMixedNamedAndPositionalArgumentsUsedNamed() throws Exception { + doTest("idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsUsedNamed.kt"); + } + + @TestMetadata("beforeMixedNamedAndPositionalArgumentsUsedPositional.kt") + public void testMixedNamedAndPositionalArgumentsUsedPositional() throws Exception { + doTest("idea/testData/quickfix/checkArguments/beforeMixedNamedAndPositionalArgumentsUsedPositional.kt"); + } + @TestMetadata("beforeNonVarargSpread.kt") public void testNonVarargSpread() throws Exception { doTest("idea/testData/quickfix/checkArguments/beforeNonVarargSpread.kt");