From feaad68b7eebb50bae42cbb316c5547073dd151e Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Mon, 26 Sep 2016 17:46:01 +0300 Subject: [PATCH] PY-20917 'Replace with str.format method call' messes up with set call In call expression we should try to add arguments only for dict calls, "collection" and other calls we should get "as is" --- ...onvertFormatOperatorToMethodIntention.java | 41 +++++++++++-------- .../callWithNoneReturnType.py | 4 ++ .../callWithNoneReturnType_after.py | 5 +++ .../setCall.py | 1 + .../setCall_after.py | 1 + ...rtFormatOperatorToMethodIntentionTest.java | 10 +++++ 6 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/callWithNoneReturnType.py create mode 100644 python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/callWithNoneReturnType_after.py create mode 100644 python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/setCall.py create mode 100644 python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/setCall_after.py diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ConvertFormatOperatorToMethodIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ConvertFormatOperatorToMethodIntention.java index 16998e06b103..3231917f5009 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/ConvertFormatOperatorToMethodIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/ConvertFormatOperatorToMethodIntention.java @@ -37,6 +37,7 @@ import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl; import com.jetbrains.python.psi.types.*; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -286,22 +287,15 @@ public class ConvertFormatOperatorToMethodIntention extends BaseIntentionAction else if (rhs instanceof PyCallExpression) { // potential dict(foo=1) -> format(foo=1) final PyCallExpression callExpression = (PyCallExpression)rhs; final PyExpression callee = callExpression.getCallee(); - if (callee instanceof PyReferenceExpression) { - PsiElement maybeDict = ((PyReferenceExpression)callee).getReference().resolve(); - if (maybeDict instanceof PyFunction) { - PyFunction dictInit = (PyFunction)maybeDict; - if (PyNames.INIT.equals(dictInit.getName())) { - final PyClassType dictType = PyBuiltinCache.getInstance(file).getDictType(); - if (dictType != null && dictType.getPyClass() == dictInit.getContainingClass()) { - target.append(sure(sure(callExpression.getArgumentList()).getNode()).getChars()); - } - } - else { // just a call, reuse - target.append("("); - if (converted.getSecond()) target.append("**"); // map-by-name formatting was detected - target.append(paramText).append(")"); - } - } + final PyClassType dictType = PyBuiltinCache.getInstance(file).getDictType(); + final PyClassType classType = PyUtil.as(rhsType, PyClassType.class); + if (classType != null && callee != null && isDictCall(callee, classType, dictType)) { + target.append(sure(sure(callExpression.getArgumentList()).getNode()).getChars()); + } + else { // just a call, reuse + target.append("("); + if (converted.getSecond()) target.append("**"); // map-by-name formatting was detected + target.append(paramText).append(")"); } } else if (rhsType instanceof PyCollectionType && "dict".equals(rhsType.getName())) { @@ -313,6 +307,21 @@ public class ConvertFormatOperatorToMethodIntention extends BaseIntentionAction final PyExpression parenthesized = elementGenerator.createExpressionFromText(LanguageLevel.forElement(element), target.toString()); element.replace(sure(((PyParenthesizedExpression)parenthesized).getContainedExpression())); } + + private static boolean isDictCall(@NotNull PyExpression callee, @NotNull PyClassType classType, @Nullable PyClassType dictType) { + if (dictType != null && classType.getPyClass() == dictType.getPyClass()) { + if (callee instanceof PyReferenceExpression) { + PsiElement maybeDict = ((PyReferenceExpression)callee).getReference().resolve(); + final PyFunction dictInit = PyUtil.as(maybeDict, PyFunction.class); + if (dictInit != null) { + if (PyNames.INIT.equals(dictInit.getName())) { + return true; + } + } + } + } + return false; + } private static String getSeparator(PyStringLiteralExpression leftExpression) { String separator = ""; // detect nontrivial whitespace around the "%" diff --git a/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/callWithNoneReturnType.py b/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/callWithNoneReturnType.py new file mode 100644 index 000000000000..0709294cbe66 --- /dev/null +++ b/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/callWithNoneReturnType.py @@ -0,0 +1,4 @@ +def f(): + pass + +"%s" % f() \ No newline at end of file diff --git a/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/callWithNoneReturnType_after.py b/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/callWithNoneReturnType_after.py new file mode 100644 index 000000000000..fb5c30c96b98 --- /dev/null +++ b/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/callWithNoneReturnType_after.py @@ -0,0 +1,5 @@ +def f(): + pass + + +"{}".format(f()) \ No newline at end of file diff --git a/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/setCall.py b/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/setCall.py new file mode 100644 index 000000000000..4cb95e6abf9a --- /dev/null +++ b/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/setCall.py @@ -0,0 +1 @@ +"%s" % set({1, 2}) \ No newline at end of file diff --git a/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/setCall_after.py b/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/setCall_after.py new file mode 100644 index 000000000000..c40e3cc33e6f --- /dev/null +++ b/python/testData/intentions/PyConvertFormatOperatorToMethodIntentionTest/setCall_after.py @@ -0,0 +1 @@ +"{}".format(set({1, 2})) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/intentions/PyConvertFormatOperatorToMethodIntentionTest.java b/python/testSrc/com/jetbrains/python/intentions/PyConvertFormatOperatorToMethodIntentionTest.java index e31ec3ad703e..fcc47e383d96 100644 --- a/python/testSrc/com/jetbrains/python/intentions/PyConvertFormatOperatorToMethodIntentionTest.java +++ b/python/testSrc/com/jetbrains/python/intentions/PyConvertFormatOperatorToMethodIntentionTest.java @@ -88,4 +88,14 @@ public class PyConvertFormatOperatorToMethodIntentionTest extends PyIntentionTes public void testSet() { doTest(PyBundle.message("INTN.replace.with.method"), LanguageLevel.PYTHON26); } + + // PY-20917 + public void testSetCall() { + doTest(PyBundle.message("INTN.replace.with.method"), LanguageLevel.PYTHON26); + } + + // PY-20917 + public void testCallWithNoneReturnType() { + doTest(PyBundle.message("INTN.replace.with.method"), LanguageLevel.PYTHON26); + } } \ No newline at end of file