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(); }