From 39a18a8cfd9b96c783c9a408ad36fee10452c418 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 31 Oct 2016 21:07:00 +0300 Subject: [PATCH] PY-21245 Handle quotes in lookup items inside multiline string literals better Namely, allow lookup items that contain quotes in some cases if the containing string literal is multiline, hence there is still a suitable type of quotes to wrap injected string literal into. The orignal error described in the issue has been fixed earlier as part of other changes for PY-21161. --- .../PyConvertToFStringIntention.java | 18 ++++++++++++------ ...xContainsAlternativeQuoteOfMultilineHost.py | 1 + ...insAlternativeQuoteOfMultilineHost_after.py | 1 + ...ainsBothTypesOfQuotesInsideMultilineHost.py | 3 +++ ...thTypesOfQuotesInsideMultilineHost_after.py | 3 +++ ...tMethodIndexContainsQuoteOfMultilineHost.py | 1 + ...dIndexContainsQuoteOfMultilineHost_after.py | 1 + .../PyConvertToFStringIntentionTest.java | 18 +++++++++++++++++- 8 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsAlternativeQuoteOfMultilineHost.py create mode 100644 python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsAlternativeQuoteOfMultilineHost_after.py create mode 100644 python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsBothTypesOfQuotesInsideMultilineHost.py create mode 100644 python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsBothTypesOfQuotesInsideMultilineHost_after.py create mode 100644 python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsQuoteOfMultilineHost.py create mode 100644 python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsQuoteOfMultilineHost_after.py diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertToFStringIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertToFStringIntention.java index 2cb749e7991d..5d6bfa7256c8 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertToFStringIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertToFStringIntention.java @@ -355,7 +355,7 @@ public class PyConvertToFStringIntention extends PyBaseIntentionAction { if (adjusted == null) return false; newStringText.append(adjusted.getText()); - final String quotedAttrsAndItems = quoteItemsInFragments(field, stringInfo.getSingleQuote()); + final String quotedAttrsAndItems = quoteItemsInFragments(field, stringInfo); if (quotedAttrsAndItems == null) return false; newStringText.append(quotedAttrsAndItems); @@ -396,7 +396,7 @@ public class PyConvertToFStringIntention extends PyBaseIntentionAction { } @Nullable - private static String quoteItemsInFragments(@NotNull PyNewStyleStringFormatParser.Field field, char hostStringQuote) { + private static String quoteItemsInFragments(@NotNull PyNewStyleStringFormatParser.Field field, @NotNull StringNodeInfo hostStringInfo) { List escaped = new ArrayList<>(); for (String part : field.getAttributesAndLookups()) { if (part.startsWith(".")) { @@ -411,11 +411,17 @@ public class PyConvertToFStringIntention extends PyBaseIntentionAction { escaped.add(part); continue; } - final char quote = flipQuote(hostStringQuote); - if (indexText.indexOf(hostStringQuote) >= 0 || indexText.indexOf(quote) >= 0) { - return null; + final char originalQuote = hostStringInfo.getSingleQuote(); + char targetQuote = flipQuote(originalQuote); + // there are no escapes inside the fragment, so the lookup key cannot contain + // the host string quote unless it's a multiline string literal + if (indexText.indexOf(targetQuote) >= 0) { + if (!hostStringInfo.isTripleQuoted() || indexText.indexOf(originalQuote) >= 0) { + return null; + } + targetQuote = originalQuote; } - escaped.add("[" + quote + indexText + quote + "]"); + escaped.add("[" + targetQuote + indexText + targetQuote + "]"); } } return StringUtil.join(escaped, ""); diff --git a/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsAlternativeQuoteOfMultilineHost.py b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsAlternativeQuoteOfMultilineHost.py new file mode 100644 index 000000000000..2230678e2739 --- /dev/null +++ b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsAlternativeQuoteOfMultilineHost.py @@ -0,0 +1 @@ +'''{x["]}'''.format(x=d) \ No newline at end of file diff --git a/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsAlternativeQuoteOfMultilineHost_after.py b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsAlternativeQuoteOfMultilineHost_after.py new file mode 100644 index 000000000000..ea7492d0def3 --- /dev/null +++ b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsAlternativeQuoteOfMultilineHost_after.py @@ -0,0 +1 @@ +f'''{d['"']}''' \ No newline at end of file diff --git a/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsBothTypesOfQuotesInsideMultilineHost.py b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsBothTypesOfQuotesInsideMultilineHost.py new file mode 100644 index 000000000000..24d9a96c7e2a --- /dev/null +++ b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsBothTypesOfQuotesInsideMultilineHost.py @@ -0,0 +1,3 @@ +''' +{0["']} +'''.format(d) diff --git a/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsBothTypesOfQuotesInsideMultilineHost_after.py b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsBothTypesOfQuotesInsideMultilineHost_after.py new file mode 100644 index 000000000000..24d9a96c7e2a --- /dev/null +++ b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsBothTypesOfQuotesInsideMultilineHost_after.py @@ -0,0 +1,3 @@ +''' +{0["']} +'''.format(d) diff --git a/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsQuoteOfMultilineHost.py b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsQuoteOfMultilineHost.py new file mode 100644 index 000000000000..cc73e2551017 --- /dev/null +++ b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsQuoteOfMultilineHost.py @@ -0,0 +1 @@ +'''{x[']}'''.format(x=d) \ No newline at end of file diff --git a/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsQuoteOfMultilineHost_after.py b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsQuoteOfMultilineHost_after.py new file mode 100644 index 000000000000..2de3590e84a5 --- /dev/null +++ b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodIndexContainsQuoteOfMultilineHost_after.py @@ -0,0 +1 @@ +f'''{d["'"]}''' \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/intentions/PyConvertToFStringIntentionTest.java b/python/testSrc/com/jetbrains/python/intentions/PyConvertToFStringIntentionTest.java index 5d7e74d144c6..f01e6ba2eb06 100644 --- a/python/testSrc/com/jetbrains/python/intentions/PyConvertToFStringIntentionTest.java +++ b/python/testSrc/com/jetbrains/python/intentions/PyConvertToFStringIntentionTest.java @@ -94,11 +94,27 @@ public class PyConvertToFStringIntentionTest extends PyIntentionTestCase { public void testFormatMethodItemAccess() { doTest(); } - + + // PY-21245 public void testFormatMethodIndexContainsHostAlternativeQuote() { doTest(); } + // PY-21245 + public void testFormatMethodIndexContainsQuoteOfMultilineHost() { + doTest(); + } + + // PY-21245 + public void testFormatMethodIndexContainsAlternativeQuoteOfMultilineHost() { + doTest(); + } + + // PY-21245 + public void testFormatMethodIndexContainsBothTypesOfQuotesInsideMultilineHost() { + doTest(); + } + public void testFormatMethodIndexContainsBackslash() { doTest(); }