PY-8989 Several improvements in PyConvertTripleQuotedStringIntention

* Do not replace Python-specific escape sequences with
StringUtil#escapeStringCharacter, take care of quotes only with
StringUtil#escapeChar
* Do not show intention for multiline raw strings, because we can't
insert '\n' inside them.
* Do not include empty line before closing triple quote in result, but
insert '\n' in the line before
* Do not place closing brace on its own line
This commit is contained in:
Mikhail Golubev
2014-12-16 13:11:11 +03:00
parent 2238ade915
commit df06f2600d
7 changed files with 68 additions and 32 deletions
@@ -45,6 +45,9 @@ import java.util.List;
*/
public class PyConvertTripleQuotedStringIntention extends BaseIntentionAction {
public static final String TRIPLE_SINGLE_QUOTE = "'''";
public static final String TRIPLE_DOUBLE_QUOTE = "\"\"\"";
@NotNull
public String getFamilyName() {
return PyBundle.message("INTN.triple.quoted.string");
@@ -69,11 +72,16 @@ public class PyConvertTripleQuotedStringIntention extends BaseIntentionAction {
if (docStringOwner.getDocStringExpression() == string) return false;
}
String stringText = string.getText();
final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText);
final String prefix = stringText.substring(0, prefixLength);
if (StringUtil.containsIgnoreCase(prefix, "r")) {
return false;
}
stringText = stringText.substring(prefixLength);
if (stringText.length() >= 6) {
if (stringText.startsWith("'''") && stringText.endsWith("'''") ||
stringText.startsWith("\"\"\"") && stringText.endsWith("\"\"\"")) {
if (stringText.startsWith(TRIPLE_SINGLE_QUOTE) && stringText.endsWith(TRIPLE_SINGLE_QUOTE) ||
stringText.startsWith(TRIPLE_DOUBLE_QUOTE) && stringText.endsWith(TRIPLE_DOUBLE_QUOTE)) {
return true;
}
}
@@ -89,31 +97,37 @@ public class PyConvertTripleQuotedStringIntention extends BaseIntentionAction {
String stringText = string.getText();
final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText);
final String prefix = stringText.substring(0, prefixLength);
final char firstQuote = stringText.substring(prefixLength).charAt(0);
stringText = stringText.substring(prefixLength);
final char firstQuote = stringText.charAt(0);
stringText = string.getStringValue();
final List<String> subStrings = StringUtil.split(stringText, "\n", false, true);
List<String> lines = StringUtil.split(stringText, "\n", true, false);
final String lastLine = lines.get(lines.size() - 1);
boolean lastLineExcluded = false;
if (lastLine.equals(TRIPLE_SINGLE_QUOTE) || lastLine.equals(TRIPLE_DOUBLE_QUOTE)) {
lastLineExcluded = true;
lines = lines.subList(0, lines.size() - 1);
}
final StringBuilder result = new StringBuilder();
if (subStrings.size() != 1) {
if (lines.size() != 1) {
result.append("(");
}
boolean lastString = false;
for (String s : subStrings) {
for (int i = 0; i < lines.size(); i++) {
final String validSubstring = convertToValidSubString(lines.get(i), firstQuote);
final boolean isLastLine = i == lines.size() - 1;
result.append(prefix);
result.append(firstQuote);
final String validSubstring = convertToValidSubString(s, firstQuote);
if (s.endsWith("'''") || s.endsWith("\"\"\"")) {
lastString = true;
}
result.append(validSubstring);
if (!isLastLine || lastLineExcluded) {
result.append("\\n");
}
result.append(firstQuote);
if (!lastString) {
result.append(" ").append("\n");
if (!isLastLine) {
result.append("\n");
}
}
if (subStrings.size() != 1) {
if (lines.size() != 1) {
result.append(")");
}
final PyExpressionStatement e = elementGenerator.createFromText(LanguageLevel.forElement(string), PyExpressionStatement.class, result.toString());
@@ -131,19 +145,14 @@ public class PyConvertTripleQuotedStringIntention extends BaseIntentionAction {
@NotNull
private static String convertToValidSubString(@NotNull String s, char firstQuote) {
final String subString;
if (s.startsWith("'''") || s.startsWith("\"\"\"")) {
subString = convertToValidSubString(s.substring(3), firstQuote);
if (s.startsWith(TRIPLE_SINGLE_QUOTE) || s.startsWith(TRIPLE_DOUBLE_QUOTE)) {
return convertToValidSubString(s.substring(3), firstQuote);
}
else if (s.endsWith("'''") || s.endsWith("\"\"\"")) {
final String trimmed = s.trim();
subString = convertToValidSubString(trimmed.substring(0, trimmed.length() - 3), firstQuote);
else if (s.endsWith(TRIPLE_SINGLE_QUOTE) || s.endsWith(TRIPLE_DOUBLE_QUOTE)) {
return convertToValidSubString(s.substring(0, s.length() - 3), firstQuote);
}
else {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder = StringUtil.escapeStringCharacters(s.length(), s, String.valueOf(firstQuote), true, stringBuilder);
subString = stringBuilder.toString();
return StringUtil.escapeChar(s, firstQuote);
}
return subString;
}
}
@@ -0,0 +1,2 @@
S = r"""foo<caret>
bar"""
@@ -1,7 +1,5 @@
if True:
if True:
a = ('\'Hello\' - said man.\n'
'\n'
a = ('\'Hello\' - said man.\n\n'
' Let\'s go.\n'
' And they went out.'
)
' And they went out.')
@@ -1,3 +1,2 @@
s = ("\n"
"my quote\" some text after it\n"
)
"my quote\" some text after it\n")
@@ -0,0 +1,6 @@
s = ("Hex escape\x01\n"
"Octal escape\015\n"
"Not standard short escape \v\n"
"Unicode name escape \N{FF}\n"
"Long Unicode escape \U00020002\n"
"Should be escaped \" ")
@@ -0,0 +1,6 @@
s = """<caret>Hex escape\x01
Octal escape\015
Not standard short escape \v
Unicode name escape \N{FF}
Long Unicode escape \U00020002
Should be escaped " """
@@ -251,6 +251,22 @@ public class PyIntentionTest extends PyTestCase {
doTest(PyBundle.message("INTN.triple.quoted.string"));
}
// PY-8989
public void testConvertTripleQuotedStringIsNotAvailableForRawStrings() {
myFixture.configureByFile("intentions/" + getTestName(false) + ".py");
assertEmpty(myFixture.filterAvailableIntentions(PyBundle.message("INTN.triple.quoted.string")));
}
// PY-8989
public void testConvertTripleQuotedStringDoesNotReplacePythonEscapes() {
doTest(PyBundle.message("INTN.triple.quoted.string"), LanguageLevel.PYTHON33);
}
// PY-8989
public void testConvertTripleQuotedStringMultilineGluedString() {
doTest(PyBundle.message("INTN.triple.quoted.string"), LanguageLevel.PYTHON33);
}
public void testTransformConditionalExpression() { //PY-3094
doTest(PyBundle.message("INTN.transform.into.if.else.statement"));
}