diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java index 647e7d61d3cc..338bb42ce1ff 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertTripleQuotedStringIntention.java @@ -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 subStrings = StringUtil.split(stringText, "\n", false, true); + List 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; } } diff --git a/python/testData/intentions/ConvertTripleQuotedStringIsNotAvailableForRawStrings.py b/python/testData/intentions/ConvertTripleQuotedStringIsNotAvailableForRawStrings.py new file mode 100644 index 000000000000..153e86c943a0 --- /dev/null +++ b/python/testData/intentions/ConvertTripleQuotedStringIsNotAvailableForRawStrings.py @@ -0,0 +1,2 @@ +S = r"""foo +bar""" \ No newline at end of file diff --git a/python/testData/intentions/afterConvertTripleQuotedString.py b/python/testData/intentions/afterConvertTripleQuotedString.py index d3cc2560d372..d3647058f160 100644 --- a/python/testData/intentions/afterConvertTripleQuotedString.py +++ b/python/testData/intentions/afterConvertTripleQuotedString.py @@ -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.' - ) \ No newline at end of file + ' And they went out.') \ No newline at end of file diff --git a/python/testData/intentions/afterConvertTripleQuotedString1.py b/python/testData/intentions/afterConvertTripleQuotedString1.py index 90c7819ca4e4..ba15158e496f 100644 --- a/python/testData/intentions/afterConvertTripleQuotedString1.py +++ b/python/testData/intentions/afterConvertTripleQuotedString1.py @@ -1,3 +1,2 @@ s = ("\n" - "my quote\" some text after it\n" -) \ No newline at end of file + "my quote\" some text after it\n") \ No newline at end of file diff --git a/python/testData/intentions/afterConvertTripleQuotedStringDoesNotReplacePythonEscapes.py b/python/testData/intentions/afterConvertTripleQuotedStringDoesNotReplacePythonEscapes.py new file mode 100644 index 000000000000..7c064dee4dbf --- /dev/null +++ b/python/testData/intentions/afterConvertTripleQuotedStringDoesNotReplacePythonEscapes.py @@ -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 \" ") \ No newline at end of file diff --git a/python/testData/intentions/beforeConvertTripleQuotedStringDoesNotReplacePythonEscapes.py b/python/testData/intentions/beforeConvertTripleQuotedStringDoesNotReplacePythonEscapes.py new file mode 100644 index 000000000000..fcd7d31e0010 --- /dev/null +++ b/python/testData/intentions/beforeConvertTripleQuotedStringDoesNotReplacePythonEscapes.py @@ -0,0 +1,6 @@ +s = """Hex escape\x01 +Octal escape\015 +Not standard short escape \v +Unicode name escape \N{FF} +Long Unicode escape \U00020002 +Should be escaped " """ \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/intentions/PyIntentionTest.java b/python/testSrc/com/jetbrains/python/intentions/PyIntentionTest.java index 058da63e4d01..b045db073103 100644 --- a/python/testSrc/com/jetbrains/python/intentions/PyIntentionTest.java +++ b/python/testSrc/com/jetbrains/python/intentions/PyIntentionTest.java @@ -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")); }