PY-14774 Hide annotations for *args/**kwargs parameters in "Change signature" dialog

It was done already for normal (not starred) parameters of function.
Validation of names (parts after leading '*') for such parameters is
performed now as well.

I've also added a few more warnings in "Change signature"
dialog, namely:

* Multiple parameters with single or double star
* Several starred parameters have the same name
This commit is contained in:
Mikhail Golubev
2014-12-23 12:50:44 +03:00
parent 099cd884c9
commit 11ba3e0e2d
9 changed files with 103 additions and 22 deletions
@@ -628,6 +628,7 @@ refactoring.change.signature.dialog.validation.name.defined=Name is already defi
refactoring.change.signature.dialog.validation.function.name=Incorrect function name
refactoring.change.signature.dialog.validation.parameter.name=Incorrect parameter name
refactoring.change.signature.dialog.validation.multiple.star=Multiple * arguments are not allowed
refactoring.change.signature.dialog.validation.multiple.double.star=Multiple ** arguments are not allowed
refactoring.change.signature.dialog.validation.default.missing=Default value is missing
refactoring.change.signature.dialog.validation.parameter.missing=Parameter name is missing
refactoring.change.signature.dialog.default.value.checkbox=Use default value in signature:
@@ -140,10 +140,11 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase<PyParamet
PyParameterTableModelItem info = parameters.get(index);
final PyParameterInfo parameter = info.parameter;
final String name = parameter.getName();
if (parameterNames.contains(name)) {
final String nameWithoutStars = StringUtil.trimLeading(name, '*').trim();
if (parameterNames.contains(nameWithoutStars)) {
return PyBundle.message("ANN.duplicate.param.name");
}
parameterNames.add(name);
parameterNames.add(nameWithoutStars);
if (name.equals("*")) {
hadSingleStar = true;
@@ -155,16 +156,25 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase<PyParamet
if (hadKeywordContainer) {
return PyBundle.message("ANN.starred.param.after.kwparam");
}
if (hadSingleStar) {
if (hadSingleStar || hadPositionalContainer) {
return PyBundle.message("refactoring.change.signature.dialog.validation.multiple.star");
}
if (!isNameValid(name.substring(1), myProject)) {
return PyBundle.message("refactoring.change.signature.dialog.validation.parameter.name");
}
hadPositionalContainer = true;
}
else if (name.startsWith("**")) {
hadKeywordContainer = true;
if (hadSingleStar && !hadParamsAfterSingleStar) {
return PyBundle.message("ANN.named.arguments.after.star");
}
if (hadKeywordContainer) {
return PyBundle.message("refactoring.change.signature.dialog.validation.multiple.double.star");
}
if (!isNameValid(name.substring(2), myProject)) {
return PyBundle.message("refactoring.change.signature.dialog.validation.parameter.name");
}
hadKeywordContainer = true;
}
else {
if (!isNameValid(name, myProject)) {
@@ -310,7 +310,7 @@ public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProc
final PyParameter[] oldParameters = function.getParameterList().getParameters();
for (PyParameterInfo paramInfo: changeInfo.getNewParameters()) {
if (paramInfo.getOldIndex() >= 0 && paramInfo.isRenamed()) {
final String newName = StringUtil.trimLeading(paramInfo.getName(), '*');
final String newName = StringUtil.trimLeading(paramInfo.getName(), '*').trim();
final UsageInfo[] usages = RenameUtil.findUsages(oldParameters[paramInfo.getOldIndex()], newName, true, false, null);
for (UsageInfo info : usages) {
RenameUtil.rename(info, newName);
@@ -355,11 +355,12 @@ public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProc
final PsiElement parameterList = baseMethod.getParameterList();
final PyParameterInfo[] parameters = changeInfo.getNewParameters();
StringBuilder builder = new StringBuilder("def foo(");
final StringBuilder builder = new StringBuilder("def foo(");
final PyStringLiteralExpression docstring = baseMethod.getDocStringExpression();
final PyParameter[] oldParameters = baseMethod.getParameterList().getParameters();
final PyElementGenerator generator = PyElementGenerator.getInstance(baseMethod.getProject());
for (int i = 0; i != parameters.length; ++i) {
PyParameterInfo info = parameters[i];
final PyParameterInfo info = parameters[i];
final int oldIndex = info.getOldIndex();
if (i != 0 && oldIndex < oldParameters.length) {
@@ -367,15 +368,14 @@ public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProc
}
if (docstring != null && oldIndex < 0) {
final String replacement =
new PyDocstringGenerator(baseMethod).withParam("param", info.getName()).docStringAsText();
PyExpression str =
PyElementGenerator.getInstance(baseMethod.getProject()).createDocstring(replacement).getExpression();
docstring.replace(str);
final String replacement = new PyDocstringGenerator(baseMethod).withParam("param", info.getName()).docStringAsText();
final PyExpression newDocstring = generator.createDocstring(replacement).getExpression();
docstring.replace(newDocstring);
}
if (oldIndex < oldParameters.length)
if (oldIndex < oldParameters.length) {
builder.append(info.getName());
}
if (oldIndex >= 0 && oldIndex < oldParameters.length) {
final PyParameter parameter = oldParameters[oldIndex];
if (parameter instanceof PyNamedParameter) {
@@ -386,18 +386,17 @@ public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProc
}
}
final String defaultValue = info.getDefaultValue();
if (defaultValue != null && info.getDefaultInSignature() && !StringUtil.isEmpty(defaultValue)) {
if (defaultValue != null && info.getDefaultInSignature() && StringUtil.isNotEmpty(defaultValue)) {
builder.append(" = ").append(defaultValue);
}
}
builder.append("): pass");
final PyParameterList parameterList1 =
PyElementGenerator.getInstance(baseMethod.getProject())
.createFromText(LanguageLevel.forElement(baseMethod), PyFunction.class,
builder.toString()).getParameterList();
parameterList.replace(parameterList1);
final PyParameterList newParameterList = generator.createFromText(LanguageLevel.forElement(baseMethod),
PyFunction.class,
builder.toString()).getParameterList();
parameterList.replace(newParameterList);
}
@Override
@@ -48,9 +48,22 @@ public class PyMethodDescriptor implements MethodDescriptor<PyParameterInfo, Str
for (int i = 0; i < parameters.length; i++) {
PyParameter parameter = parameters[i];
final PyExpression defaultValue = parameter.getDefaultValue();
final String name = parameter instanceof PyNamedParameter && !((PyNamedParameter)parameter).isPositionalContainer() &&
!((PyNamedParameter)parameter).isKeywordContainer() ? parameter.getName() : parameter.getText();
parameterInfos.add(new PyParameterInfo(i, name, defaultValue == null? null : defaultValue.getText(),
final String name;
if (parameter instanceof PyNamedParameter) {
if (((PyNamedParameter)parameter).isPositionalContainer()) {
name = "*" + parameter.getName();
}
else if (((PyNamedParameter)parameter).isKeywordContainer()) {
name = "**" + parameter.getName();
}
else {
name = parameter.getName();
}
}
else {
name = parameter.getText();
}
parameterInfos.add(new PyParameterInfo(i, name, defaultValue == null ? null : defaultValue.getText(),
defaultValue != null && !StringUtil.isEmptyOrSpaces(defaultValue.getText())));
}
return parameterInfos;
@@ -0,0 +1,2 @@
def func(*foo, **bar):
pass
@@ -0,0 +1,2 @@
def func(foo, **bar):
pass
@@ -0,0 +1,2 @@
def func(foo, *bar):
pass
@@ -0,0 +1,2 @@
def func(**kwargs):
pass
@@ -19,7 +19,10 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.testFramework.TestDataPath;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyFunction;
@@ -198,6 +201,53 @@ public class PyChangeSignatureTest extends PyTestCase {
PyBundle.message("ANN.non.default.param.after.default"));
}
// PY-14774
public void testAnnotationsForStarredParametersAreNotShownInDialog() throws Exception {
runWithLanguageLevel(LanguageLevel.PYTHON30, new Runnable() {
public void run() {
myFixture.configureByText(PythonFileType.INSTANCE, "def func(a, b:int, *args: tuple, c:list, d:str='foo', ** kwargs:dict):\n" +
" pass");
final PyFunction function = (PyFunction)new PyChangeSignatureHandler().findTargetMember(myFixture.getFile(), myFixture.getEditor());
assertNotNull(function);
final List<String> expected = Arrays.asList("a", "b", "*args", "c", "d", "**kwargs");
final List<PyParameterInfo> parameters = new PyMethodDescriptor(function).getParameters();
assertEquals(expected, ContainerUtil.map(parameters, new Function<PyParameterInfo, String>() {
@Override
public String fun(PyParameterInfo info) {
return info.getOldName();
}
}));
}
});
}
public void testDuplicateNamesOfStarredParameters() throws Exception {
final PyParameterInfo firstParam = new PyParameterInfo(0, "*foo", null, false);
firstParam.setName("*bar");
doValidationTest(null, Arrays.asList(firstParam, new PyParameterInfo(1, "**bar", null, false)),
PyBundle.message("ANN.duplicate.param.name"));
}
public void testMultipleSingleStarredParameters() throws Exception {
final PyParameterInfo firstParam = new PyParameterInfo(0, "foo", null, false);
firstParam.setName("*foo");
doValidationTest(null, Arrays.asList(firstParam, new PyParameterInfo(1, "*bar", null, false)),
PyBundle.message("refactoring.change.signature.dialog.validation.multiple.star"));
}
public void testMultipleDoubleStarredParameters() throws Exception {
final PyParameterInfo firstParam = new PyParameterInfo(0, "foo", null, false);
firstParam.setName("**foo");
doValidationTest(null, Arrays.asList(firstParam, new PyParameterInfo(1, "**bar", null, false)),
PyBundle.message("refactoring.change.signature.dialog.validation.multiple.double.star"));
}
public void testParameterNameWithMoreThanTwoStars() throws Exception {
final PyParameterInfo firstParam = new PyParameterInfo(0, "**kwargs", null, false);
firstParam.setName("***kwargs");
doValidationTest(null, Arrays.asList(firstParam), PyBundle.message("refactoring.change.signature.dialog.validation.parameter.name"));
}
public void doChangeSignatureTest(@Nullable String newName, @Nullable List<PyParameterInfo> parameters) {
myFixture.configureByFile("refactoring/changeSignature/" + getTestName(true) + ".before.py");
changeSignature(newName, parameters);