diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 21fb73f48178..4259ea2165b9 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -152,6 +152,7 @@ INTN.negate.$0.to.$1=Negate ''{0}'' to ''{1}'' INTN.string.concatenation.to.format=Replace string concatenation with format operator INTN.replace.plus.with.format.operator=Replace + with string formatting operator +INTN.replace.plus.with.str.format=Replace + with str.format method call INTN.format.operator.to.method=Convert format operator usage to str.format method call INTN.replace.with.method=Replace with str.format method call diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyStringConcatenationToFormatIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyStringConcatenationToFormatIntention.java index 1e1be0521d9d..e6e2e268067f 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/PyStringConcatenationToFormatIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyStringConcatenationToFormatIntention.java @@ -67,8 +67,10 @@ public class PyStringConcatenationToFormatIntention extends BaseIntentionAction return false; } } - - setText(PyBundle.message("INTN.replace.plus.with.format.operator")); + if (LanguageLevel.forElement(element).isPy3K()) + setText(PyBundle.message("INTN.replace.plus.with.str.format")); + else + setText(PyBundle.message("INTN.replace.plus.with.format.operator")); return true; } @@ -104,13 +106,14 @@ public class PyStringConcatenationToFormatIntention extends BaseIntentionAction while (element.getParent() instanceof PyBinaryExpression) { element = element.getParent(); } - StringBuilder stringLiteral = new StringBuilder(); - StringBuilder parameters = new StringBuilder(); - NotNullFunction escaper = StringUtil.escaper(false, null); + final LanguageLevel languageLevel = LanguageLevel.forElement(element); - int addParens = 0; + NotNullFunction escaper = StringUtil.escaper(false, null); + StringBuilder stringLiteral = new StringBuilder(); + List parameters = new ArrayList(); Pair quotes = new Pair("\"", "\""); boolean quotesDetected = false; + int paramCount = 0; for (PyExpression expression : getSimpleExpressions((PyBinaryExpression) element)) { if (expression instanceof PyStringLiteralExpression) { if (!quotesDetected) { @@ -119,27 +122,43 @@ public class PyStringConcatenationToFormatIntention extends BaseIntentionAction } stringLiteral.append(escaper.fun(((PyStringLiteralExpression)expression).getStringValue())); } else { - ++addParens; - stringLiteral.append("%s"); - parameters.append(expression.getText()).append(", "); + addParamToString(stringLiteral, paramCount, languageLevel); + parameters.add(expression.getText()); + ++paramCount; } } if (quotes == null) quotes = new Pair("\"", "\""); + stringLiteral.insert(0, quotes.getFirst()); + stringLiteral.append(quotes.getSecond()); PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - PyStringLiteralExpression stringLiteralExpression = - elementGenerator.createStringLiteralAlreadyEscaped(quotes.getFirst() + stringLiteral.toString() + quotes.getSecond()); - if (addParens > 0) { - final String paramString = addParens > 1? "(" + parameters.substring(0, parameters.length() - 2) +")" - : parameters.substring(0, parameters.length() - 2); + if (!parameters.isEmpty()) { + if (languageLevel.isPy3K()) { + stringLiteral.append(".format(").append(StringUtil.join(parameters, ",")).append(")"); + + } + else { + final String paramString = parameters.size() > 1? "(" + StringUtil.join(parameters, ",") +")" + : StringUtil.join(parameters, ","); + stringLiteral.append(" % ").append(paramString); + } final PyExpression expression = elementGenerator.createFromText(LanguageLevel.getDefault(), - PyExpressionStatement.class, paramString).getExpression(); - element.replace(elementGenerator.createBinaryExpression("%", stringLiteralExpression, expression)); + PyExpressionStatement.class, stringLiteral.toString()).getExpression(); + element.replace(expression); } else { + PyStringLiteralExpression stringLiteralExpression = + elementGenerator.createStringLiteralAlreadyEscaped(stringLiteral.toString()); element.replace(stringLiteralExpression); } } + + private static void addParamToString(StringBuilder stringLiteral, int i, LanguageLevel level) { + if (level.isPy3K()) + stringLiteral.append("{").append(i).append("}"); + else + stringLiteral.append("%s"); + } } diff --git a/python/testData/intentions/afterStringConcatToFormatPy3.py b/python/testData/intentions/afterStringConcatToFormatPy3.py new file mode 100644 index 000000000000..6d713a0a68b5 --- /dev/null +++ b/python/testData/intentions/afterStringConcatToFormatPy3.py @@ -0,0 +1,3 @@ + +string = "string" +some_string = "some {0}".format(string) diff --git a/python/testData/intentions/beforeStringConcatToFormatPy3.py b/python/testData/intentions/beforeStringConcatToFormatPy3.py new file mode 100644 index 000000000000..42bff183899d --- /dev/null +++ b/python/testData/intentions/beforeStringConcatToFormatPy3.py @@ -0,0 +1,3 @@ + +string = "string" +some_string = "some " + string diff --git a/python/testSrc/com/jetbrains/python/PyIntentionTest.java b/python/testSrc/com/jetbrains/python/PyIntentionTest.java index 7a415f7241da..c638cf936583 100644 --- a/python/testSrc/com/jetbrains/python/PyIntentionTest.java +++ b/python/testSrc/com/jetbrains/python/PyIntentionTest.java @@ -133,6 +133,10 @@ public class PyIntentionTest extends PyTestCase { doNegativeTest(PyBundle.message("INTN.replace.plus.with.format.operator")); } + public void testStringConcatToFormatPy3() { //PY-4706 + doTest(PyBundle.message("INTN.replace.plus.with.str.format"), LanguageLevel.PYTHON33); + } + public void testConvertFormatOperatorToMethod() { doTest(PyBundle.message("INTN.replace.with.method"), LanguageLevel.PYTHON26); }