mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-13 21:55:01 +07:00
%-based formatting for plain string literals, concat-based formatting for concat strings (PY-3654)
This commit is contained in:
@@ -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
-1
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user