%-based formatting for plain string literals, concat-based formatting for concat strings (PY-3654)

This commit is contained in:
Andrey Vlasovskikh
2012-12-27 20:23:41 +04:00
parent e3d2070fdc
commit 04eafc4693
21 changed files with 151 additions and 40 deletions
@@ -1,12 +1,16 @@
package com.jetbrains.python.inspections;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.HashMap;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
/**
@@ -221,6 +225,68 @@ public class PyStringFormatParser {
return result;
}
@NotNull
public static List<SubstitutionChunk> getPositionalSubstitutions(@NotNull List<SubstitutionChunk> substitutions) {
final ArrayList<SubstitutionChunk> result = new ArrayList<SubstitutionChunk>();
for (SubstitutionChunk s : substitutions) {
if (s.getMappingKey() == null) {
result.add(s);
}
}
return result;
}
@NotNull
public static Map<String, SubstitutionChunk> getKeywordSubstitutions(@NotNull List<SubstitutionChunk> substitutions) {
final Map<String, SubstitutionChunk> result = new HashMap<String, SubstitutionChunk>();
for (SubstitutionChunk s : substitutions) {
final String key = s.getMappingKey();
if (key != null) {
result.put(key, s);
}
}
return result;
}
/**
* Return the RHS operand of %-based string literal format expression.
*/
@Nullable
public static PyExpression getFormatValueExpression(@NotNull PyStringLiteralExpression element) {
final PsiElement parent = element.getParent();
if (parent instanceof PyBinaryExpression) {
final PyBinaryExpression binaryExpr = (PyBinaryExpression)parent;
if (binaryExpr.isOperator("%")) {
PyExpression expr = binaryExpr.getRightExpression();
while (expr instanceof PyParenthesizedExpression) {
expr = ((PyParenthesizedExpression)expr).getContainedExpression();
}
return expr;
}
}
return null;
}
/**
* Return the argument list of the str.format() literal format expression.
*/
@Nullable
public static PyArgumentList getNewStyleFormatValueExpression(@NotNull PyStringLiteralExpression element) {
final PsiElement parent = element.getParent();
if (parent instanceof PyQualifiedExpression) {
final PyQualifiedExpression qualifiedExpr = (PyQualifiedExpression)parent;
final String name = qualifiedExpr.getReferencedName();
if ("format".equals(name)) {
final PsiElement parent2 = qualifiedExpr.getParent();
if (parent2 instanceof PyCallExpression) {
final PyCallExpression callExpr = (PyCallExpression)parent2;
return callExpr.getArgumentList();
}
}
}
return null;
}
@NotNull
public static List<TextRange> getEscapeRanges(@NotNull String s) {
final List<TextRange> ranges = new ArrayList<TextRange>();
@@ -7,10 +7,13 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.IElementType;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PythonStringUtil;
import com.jetbrains.python.inspections.PyStringFormatParser;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.jetbrains.python.PyTokenTypes.*;
/**
@@ -72,34 +75,68 @@ public class PyReplaceExpressionUtil implements PyElementTypes {
@NotNull PsiElement newExpression,
@NotNull TextRange textRange) {
final String fullText = oldExpression.getText();
final PyElementGenerator generator = PyElementGenerator.getInstance(oldExpression.getProject());
final LanguageLevel languageLevel = LanguageLevel.forElement(oldExpression);
final String prefix = fullText.substring(0, textRange.getStartOffset());
final String suffix = fullText.substring(textRange.getEndOffset(), oldExpression.getTextLength());
final Pair<String, String> detectedQuotes = PythonStringUtil.getQuotes(fullText);
final Pair<String, String> quotes = detectedQuotes != null ? detectedQuotes : Pair.create("'", "'");
final PsiElement parent = oldExpression.getParent();
final boolean parensNeeded = parent instanceof PyExpression && !(parent instanceof PyParenthesizedExpression);
final String leftQuote = quotes.getFirst();
final String rightQuote = quotes.getSecond();
final StringBuilder builder = new StringBuilder();
if (parensNeeded) {
builder.append("(");
final PyExpression valueExpression = PyStringFormatParser.getFormatValueExpression(oldExpression);
final PyElementGenerator generator = PyElementGenerator.getInstance(oldExpression.getProject());
final LanguageLevel languageLevel = LanguageLevel.forElement(oldExpression);
final List<PyStringFormatParser.SubstitutionChunk> substitutions = new PyStringFormatParser(fullText).parseSubstitutions();
// TODO: Handle %-formatted strings
if (isConcatFormatting(oldExpression) || substitutions.size() > 0) {
// 'foobar' + 'baz' -> s + 'bar' + 'baz'
// 'foobar%s' -> s + 'bar%s'
final Pair<String, String> detectedQuotes = PythonStringUtil.getQuotes(fullText);
final Pair<String, String> quotes = detectedQuotes != null ? detectedQuotes : Pair.create("'", "'");
final String leftQuote = quotes.getFirst();
final String rightQuote = quotes.getSecond();
final StringBuilder builder = new StringBuilder();
if (valueExpression != null) {
builder.append("(");
}
if (!leftQuote.endsWith(prefix)) {
builder.append(prefix + rightQuote + " + ");
}
final int pos = builder.toString().length();
builder.append(newExpression.getText());
if (!rightQuote.startsWith(suffix)) {
builder.append(" + " + leftQuote + suffix);
}
if (valueExpression != null) {
builder.append(")");
}
final PsiElement expression = generator.createExpressionFromText(languageLevel, builder.toString());
final PsiElement newElement = oldExpression.replace(expression);
return newElement.findElementAt(pos);
}
if (!leftQuote.endsWith(prefix)) {
builder.append(prefix + rightQuote + " + ");
else {
// 'foobar' -> '%sbar' % s
final PsiElement parent = oldExpression.getParent();
final boolean parensNeeded = parent instanceof PyExpression && !(parent instanceof PyParenthesizedExpression);
final StringBuilder builder = new StringBuilder();
if (parensNeeded) {
builder.append("(");
}
builder.append(prefix);
builder.append("%s");
builder.append(suffix);
builder.append(" % ");
final int pos = builder.toString().length();
builder.append(newExpression.getText());
if (parensNeeded) {
builder.append(")");
}
final PyExpression expression = generator.createExpressionFromText(languageLevel, builder.toString());
final PsiElement newElement = oldExpression.replace(expression);
return newElement.findElementAt(pos);
}
final int pos = builder.toString().length();
builder.append(newExpression.getText());
if (!rightQuote.startsWith(suffix)) {
builder.append(" + " + leftQuote + suffix);
}
if (parensNeeded) {
builder.append(")");
}
final PsiElement expression = generator.createExpressionFromText(languageLevel, builder.toString());
final PsiElement newElement = oldExpression.replace(expression);
return newElement.findElementAt(pos);
}
private static boolean isConcatFormatting(PyStringLiteralExpression element) {
final PsiElement parent = element.getParent();
return parent instanceof PyBinaryExpression && ((PyBinaryExpression)parent).isOperator("+");
}
private static boolean isNotAssociative(@NotNull final PyBinaryExpression binaryExpression) {
@@ -1,2 +1,2 @@
a = b'bar'
b'foo' + a + b'baz'
b'foo' + a + b'baz' + suffix
@@ -1 +1 @@
b'foo<selection>bar</selection>baz'
b'foo<selection>bar</selection>baz' + suffix
@@ -1,2 +1,2 @@
a = "hello"
print(a + " world")
print(a + " world" + "!")
@@ -1 +1 @@
print(<selection>"hello</selection> world")
print(<selection>"hello</selection> world" + "!")
@@ -1,2 +1,2 @@
a = "hello"
print(a + " world")
print(a + " world" + "!")
@@ -1 +1 @@
print("<selection>hello</selection> world")
print("<selection>hello</selection> world" + "!")
@@ -1,2 +1,2 @@
a = "lo wor"
print("hel" + a + "ld")
print(prefix + "hel" + a + "ld")
@@ -1 +1 @@
print("hel<selection>lo wor</selection>ld")
print(prefix + "hel<selection>lo wor</selection>ld")
@@ -1,2 +1,2 @@
a = "world"
print("hello " + a)
print("hello " + a + suffix)
@@ -1 +1 @@
print("hello <selection>world</selection>")
print("hello <selection>world</selection>" + suffix)
@@ -0,0 +1,2 @@
a = "hello"
print("%s world" % a)
@@ -0,0 +1 @@
print("<selection>hello</selection> world")
@@ -1,2 +1,2 @@
a = u"lo \u00d6sterreich\\!\n"
print(u"Hel" + a + u"\n")
print(u"Hel%s\n" % a)
@@ -1,2 +1,2 @@
a = 'foo'
print((a + 'bar').upper())
print(('%sbar' % a).upper())
@@ -1,2 +1,2 @@
a = "two"
"one " + a + " three"
"one %s three" % a
@@ -1,2 +1,2 @@
a = 'foo'
x = a + 'bar'
x = '%sbar' % a
@@ -3,4 +3,4 @@ print(""""One two
* """ + a + """
* Four
* Five""")
* Five""" + suffix)
@@ -2,4 +2,4 @@ print(""""One two
* <selection>Three</selection>
* Four
* Five""")
* Five""" + suffix)
@@ -88,6 +88,11 @@ public class PyIntroduceVariableTest extends PyIntroduceTestCase {
doTest();
}
// PY-3654
public void testSimpleSubstring() {
doTest();
}
// PY-3654
public void testLeftSubstring() {
doTest();