Quickfix for MIXING_NAMED_AND_POSITIONED_ARGUMENTS.
This commit is contained in:
committed by
Nikolay Krasko
parent
d5739bbe7f
commit
b2bb581127
@@ -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;
|
||||
|
||||
@@ -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
|
||||
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
|
||||
|
||||
@@ -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<JetValueArgument> {
|
||||
|
||||
@NotNull
|
||||
private final List<String> possibleNames;
|
||||
|
||||
public AddNameToArgumentFix(@NotNull JetValueArgument argument, @NotNull List<String> possibleNames) {
|
||||
super(argument);
|
||||
this.possibleNames = possibleNames;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> 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<? extends CallableDescriptor> 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<String> usedParameters = getUsedParameters(callElement, callableDescriptor);
|
||||
List<String> 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<String> getUsedParameters(@NotNull JetCallElement callElement, @NotNull CallableDescriptor callableDescriptor) {
|
||||
Set<String> 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<String>(
|
||||
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<String> possibleNames = generatePossibleNames(argument);
|
||||
return possibleNames.isEmpty() ? null : new AddNameToArgumentFix(argument, possibleNames);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add name to argument: 'b = "FOO"'" "true"
|
||||
fun f(a: Int, b: String) {}
|
||||
|
||||
fun g() {
|
||||
f(a = 10, b = "FOO")
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Add name to argument: 'b = "FOO"'" "true"
|
||||
class A(a: Int, b: String) {}
|
||||
|
||||
fun f() {
|
||||
A(a = 1, b = "FOO")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Add name to argument: 'b = "FOO"'" "true"
|
||||
fun f(a: String, b: String) {}
|
||||
|
||||
fun g() {
|
||||
f(a = "BAR", b = "FOO")
|
||||
}
|
||||
+6
@@ -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")
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add name to argument: 'b = "FOO"'" "true"
|
||||
fun f(a: Int, b: String) {}
|
||||
|
||||
fun g() {
|
||||
f(a = 10, <caret>"FOO")
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Add name to argument: 'b = "FOO"'" "true"
|
||||
class A(a: Int, b: String) {}
|
||||
|
||||
fun f() {
|
||||
A(a = 1, <caret>"FOO")
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Add name to argument..." "true"
|
||||
fun f(a: Int, b: String = "b", c: String = "c") {}
|
||||
|
||||
fun g() {
|
||||
f(a = 10, <caret>"FOO")
|
||||
}
|
||||
@@ -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, <caret>B())
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Add name to argument: 'b = "FOO"'" "true"
|
||||
fun f(a: String, b: String) {}
|
||||
|
||||
fun g() {
|
||||
f(a = "BAR", <caret>"FOO")
|
||||
}
|
||||
+6
@@ -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, <caret>"FOO")
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user