Process all reads of particular key's value together (PY-26286)

GitOrigin-RevId: d2b56880b023c2ff87decff20bfac3c4713de32a
This commit is contained in:
Semyon Proshev
2019-10-25 19:11:52 +03:00
committed by intellij-monorepo-bot
parent 92282b0204
commit f8c683fd2c
15 changed files with 151 additions and 42 deletions

View File

@@ -18,6 +18,7 @@ package com.jetbrains.python.codeInsight.intentions;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@@ -26,6 +27,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
@@ -123,8 +125,7 @@ public class ConvertVariadicParamIntention extends PyBaseIntentionAction {
final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class);
if (function != null) {
replaceKeywordContainerSubscriptions(function, project);
replaceKeywordContainerCalls(function, project);
replaceKeywordContainerUsages(function, project);
}
}
@@ -177,53 +178,63 @@ public class ConvertVariadicParamIntention extends PyBaseIntentionAction {
.orElse(null);
}
private static void replaceKeywordContainerSubscriptions(@NotNull PyFunction function, @NotNull Project project) {
private static void replaceKeywordContainerUsages(@NotNull PyFunction function, @NotNull Project project) {
final PyParameter keywordContainer = getKeywordContainer(function.getParameterList());
if (keywordContainer == null) return;
final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
final Set<String> names = new LinkedHashSet<>();
final MultiMap<String, PySubscriptionExpression> subscriptions = MultiMap.create();
final MultiMap<String, PyCallExpression> calls = MultiMap.create();
SyntaxTraverser
.psiTraverser(function.getStatementList())
.filter(e -> isKeywordContainerSubscription(e, keywordContainer))
.filter(PySubscriptionExpression.class)
.forEach(
subscription -> {
final String indexValue = getIndexValueToReplace(subscription);
if (indexValue != null) {
final PyExpression parameter = elementGenerator.createExpressionFromText(LanguageLevel.forElement(function), indexValue);
e -> {
if (isKeywordContainerSubscription(e, keywordContainer)) {
final PySubscriptionExpression subscription = (PySubscriptionExpression)e;
final String indexValue = getIndexValueToReplace(subscription);
insertParameter(function.getParameterList(), parameter, false, elementGenerator);
subscription.replace(parameter);
if (indexValue != null) {
names.add(indexValue);
subscriptions.putValue(indexValue, subscription);
}
}
}
);
}
else if (isKeywordContainerCall(e, keywordContainer)) {
final PyCallExpression call = (PyCallExpression)e;
final String indexValue = getIndexValueToReplace(call);
private static void replaceKeywordContainerCalls(@NotNull PyFunction function, @NotNull Project project) {
final PyParameter keywordContainer = getKeywordContainer(function.getParameterList());
if (keywordContainer == null) return;
final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
SyntaxTraverser
.psiTraverser(function.getStatementList())
.filter(e -> isKeywordContainerCall(e, keywordContainer))
.filter(PyCallExpression.class)
.forEach(
call -> {
final String indexValue = getIndexValueToReplace(call);
if (indexValue != null) {
final PyNamedParameter parameter = createParameter(elementGenerator, call, indexValue);
if (parameter != null) {
final PyExpression parameterUsage = elementGenerator.createExpressionFromText(LanguageLevel.forElement(function), indexValue);
insertParameter(function.getParameterList(), parameter, parameter.hasDefaultValue(), elementGenerator);
call.replace(parameterUsage);
if (indexValue != null) {
names.add(indexValue);
calls.putValue(indexValue, call);
}
}
}
);
final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
for (String name : names) {
final Collection<PySubscriptionExpression> currentSubscriptions = subscriptions.get(name);
final Collection<PyCallExpression> currentCalls = calls.get(name);
if (!currentSubscriptions.isEmpty()) {
final PyExpression parameter = elementGenerator.createExpressionFromText(LanguageLevel.forElement(function), name);
insertParameter(function.getParameterList(), parameter, false, elementGenerator);
currentSubscriptions.forEach(e -> e.replace(parameter));
currentCalls.forEach(e -> e.replace(parameter));
}
else if (!currentCalls.isEmpty()) {
final Ref<String> defaultValue = getCommonDefaultKeyValue(currentCalls);
if (defaultValue == null) return;
final LanguageLevel languageLevel = LanguageLevel.forElement(function);
final PyNamedParameter parameter = elementGenerator.createParameter(name, defaultValue.get(), null, languageLevel);
final PyExpression parameterUsage = elementGenerator.createExpressionFromText(languageLevel, name);
insertParameter(function.getParameterList(), parameter, parameter.hasDefaultValue(), elementGenerator);
currentCalls.forEach(e -> e.replace(parameterUsage));
}
}
}
private static boolean isKeywordContainerSubscription(@Nullable PsiElement element, @NotNull PyParameter keywordContainer) {
@@ -263,22 +274,40 @@ public class ConvertVariadicParamIntention extends PyBaseIntentionAction {
parameterList.addBefore((PsiElement)elementGenerator.createComma(), placeToInsertParameter);
}
/**
* @return null when <code>calls</code> is empty or there are different default values.
*/
@Nullable
private static PyNamedParameter createParameter(@NotNull PyElementGenerator elementGenerator,
@NotNull PyCallExpression call,
@NotNull String parameterName) {
private static Ref<String> getCommonDefaultKeyValue(@NotNull Collection<PyCallExpression> calls) {
Ref<String> defaultValue = null;
for (PyCallExpression call : calls) {
final String currentDefaultValue = getDefaultKeyValue(call);
if (defaultValue == null) {
defaultValue = Ref.create(currentDefaultValue);
}
else if (!Objects.equals(defaultValue.get(), currentDefaultValue)) {
return null;
}
}
return defaultValue;
}
@Nullable
private static String getDefaultKeyValue(@NotNull PyCallExpression call) {
final PyExpression[] arguments = call.getArguments();
if (arguments.length > 1) {
final PyExpression argument = PyUtil.peelArgument(arguments[1]);
return argument == null ? null : elementGenerator.createParameter(parameterName + "=" + argument.getText());
return argument == null ? null : argument.getText();
}
final PyQualifiedExpression callee = PyUtil.as(call.getCallee(), PyQualifiedExpression.class);
if (callee != null && "get".equals(callee.getReferencedName())) {
return elementGenerator.createParameter(parameterName + "=" + PyNames.NONE);
return PyNames.NONE;
}
return elementGenerator.createParameter(parameterName);
return null;
}
@Nullable

View File

@@ -0,0 +1,3 @@
def bar(**kwa<caret>rgs):
b = kwargs.get('foo')
c = kwargs.get('foo')

View File

@@ -0,0 +1,3 @@
def bar(**kwa<caret>rgs):
b = kwargs.get('foo', 0)
c = kwargs.get('foo', 1)

View File

@@ -0,0 +1,3 @@
def bar(**kwa<caret>rgs):
b = kwargs.get('foo', 0)
c = kwargs.get('foo', 0)

View File

@@ -0,0 +1,3 @@
def bar(foo=0, **kwargs):
b = foo
c = foo

View File

@@ -0,0 +1,3 @@
def bar(foo=None, **kwargs):
b = foo
c = foo

View File

@@ -0,0 +1,3 @@
def bar(**kwa<caret>rgs):
a = kwargs['foo']
b = kwargs['foo']

View File

@@ -0,0 +1,4 @@
def bar(**kwa<caret>rgs):
a = kwargs['foo']
b = kwargs.get('foo')
c = kwargs.get('foo')

View File

@@ -0,0 +1,4 @@
def bar(**kwa<caret>rgs):
a = kwargs['foo']
b = kwargs.get('foo', 0)
c = kwargs.get('foo', 1)

View File

@@ -0,0 +1,4 @@
def bar(foo, **kwargs):
a = foo
b = foo
c = foo

View File

@@ -0,0 +1,4 @@
def bar(**kwa<caret>rgs):
a = kwargs['foo']
b = kwargs.get('foo', 1)
c = kwargs.get('foo', 1)

View File

@@ -0,0 +1,4 @@
def bar(foo, **kwargs):
a = foo
b = foo
c = foo

View File

@@ -0,0 +1,4 @@
def bar(foo, **kwargs):
a = foo
b = foo
c = foo

View File

@@ -0,0 +1,3 @@
def bar(foo, **kwargs):
a = foo
b = foo

View File

@@ -239,7 +239,7 @@ public class PyIntentionTest extends PyTestCase {
}
public void testConvertVariadicParamPositionalContainerInPy3() {
runWithLanguageLevel(LanguageLevel.PYTHON34, () -> doTest(PyBundle.message("INTN.convert.variadic.param")));
runWithLanguageLevel(LanguageLevel.getLatest(), () -> doTest(PyBundle.message("INTN.convert.variadic.param")));
}
// PY-26284
@@ -267,6 +267,41 @@ public class PyIntentionTest extends PyTestCase {
doNegativeTest(PyBundle.message("INTN.convert.variadic.param"));
}
// PY-26286
public void testConvertVariadicParamSeveralSubscriptions() {
doTest(PyBundle.message("INTN.convert.variadic.param"));
}
// PY-26286
public void testConvertVariadicParamSeveralCalls() {
doTest(PyBundle.message("INTN.convert.variadic.param"));
}
// PY-26286
public void testConvertVariadicParamSeveralCallsWithSameDefaultValue() {
doTest(PyBundle.message("INTN.convert.variadic.param"));
}
// PY-26286
public void testConvertVariadicParamSeveralCallsWithDifferentDefaultValue() {
doNegativeTest(PyBundle.message("INTN.convert.variadic.param"));
}
// PY-26286
public void testConvertVariadicParamSeveralSubscriptionsAndCalls() {
doTest(PyBundle.message("INTN.convert.variadic.param"));
}
// PY-26286
public void testConvertVariadicParamSeveralSubscriptionsAndCallsWithSameDefaultValue() {
doTest(PyBundle.message("INTN.convert.variadic.param"));
}
// PY-26286
public void testConvertVariadicParamSeveralSubscriptionsAndCallsWithDifferentDefaultValue() {
doTest(PyBundle.message("INTN.convert.variadic.param"));
}
public void testConvertTripleQuotedString() { //PY-2697
doTest(PyBundle.message("INTN.triple.quoted.string"));
}