diff --git a/python/src/com/jetbrains/python/codeInsight/fstrings/FStringParser.java b/python/src/com/jetbrains/python/codeInsight/fstrings/FStringParser.java index ce98395f72bd..5ba2c88db133 100644 --- a/python/src/com/jetbrains/python/codeInsight/fstrings/FStringParser.java +++ b/python/src/com/jetbrains/python/codeInsight/fstrings/FStringParser.java @@ -76,12 +76,14 @@ public class FStringParser { int bracesBalance = 0; char stringLiteralQuote = '\0'; int quotesNum = 0; + boolean containsNamedUnicodeEscape = false; int offset = leftBraceOffset + 1; while (offset < myNodeContentRange.getEndOffset()) { // Actually they aren't allowed inside expression fragments, but we skip them anyway to prevent injection errors final int nextOffset = skipNamedUnicodeEscape(offset); if (offset != nextOffset) { + containsNamedUnicodeEscape = true; offset = nextOffset; continue; } @@ -139,7 +141,7 @@ public class FStringParser { if (contentEndOffset == -1) { contentEndOffset = offset; } - myFragments.add(new FragmentOffsets(leftBraceOffset, contentEndOffset, rightBraceOffset)); + myFragments.add(new FragmentOffsets(leftBraceOffset, contentEndOffset, rightBraceOffset, containsNamedUnicodeEscape)); return offset; } @@ -155,14 +157,16 @@ public class FStringParser { private final int myLeftBraceOffset; private final int myRightBraceOffset; private final int myContentEndOffset; + private final boolean myContainsNamedUnicodeEscape; - public FragmentOffsets(int leftBraceOffset, int contentEndOffset, int rightBraceOffset) { + private FragmentOffsets(int leftBraceOffset, int contentEndOffset, int rightBraceOffset, boolean escape) { assert contentEndOffset > leftBraceOffset; assert rightBraceOffset < 0 || (contentEndOffset <= rightBraceOffset && leftBraceOffset < rightBraceOffset); myLeftBraceOffset = leftBraceOffset; myRightBraceOffset = rightBraceOffset; myContentEndOffset = contentEndOffset; + myContainsNamedUnicodeEscape = escape; } public int getLeftBraceOffset() { @@ -177,6 +181,10 @@ public class FStringParser { return myContentEndOffset; } + public boolean containsNamedUnicodeEscape() { + return myContainsNamedUnicodeEscape; + } + @NotNull public TextRange getContentRange() { return TextRange.create(myLeftBraceOffset + 1, myContentEndOffset); diff --git a/python/src/com/jetbrains/python/codeInsight/fstrings/PyFStringsInjector.java b/python/src/com/jetbrains/python/codeInsight/fstrings/PyFStringsInjector.java index a8518af8e2d0..9851f41b2639 100644 --- a/python/src/com/jetbrains/python/codeInsight/fstrings/PyFStringsInjector.java +++ b/python/src/com/jetbrains/python/codeInsight/fstrings/PyFStringsInjector.java @@ -46,6 +46,7 @@ public class PyFStringsInjector extends PyInjectorBase { for (ASTNode node : pyString.getStringNodes()) { final int relNodeOffset = node.getTextRange().getStartOffset() - pyString.getTextRange().getStartOffset(); for (FragmentOffsets offsets : getInjectionRanges(node)) { + if (offsets.containsNamedUnicodeEscape()) continue; registrar.startInjecting(PyDocstringLanguageDialect.getInstance()); registrar.addPlace(null, null, pyString, offsets.getContentRange().shiftRight(relNodeOffset)); registrar.doneInjecting(); diff --git a/python/testSrc/com/jetbrains/python/PyFStringTest.java b/python/testSrc/com/jetbrains/python/PyFStringTest.java index c9d50b4084e8..604b9a255715 100644 --- a/python/testSrc/com/jetbrains/python/PyFStringTest.java +++ b/python/testSrc/com/jetbrains/python/PyFStringTest.java @@ -24,7 +24,6 @@ import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -97,4 +96,19 @@ public class PyFStringTest extends PyTestCase { doTestRanges("f'\\N{foo}\\N{}\\N{{{{{}{|42 + \\N{DIGIT ONE}|}'"); doTestRanges("f'{|x|:\\N{DIGIT_ONE}}'"); } + + // PY-20785 + public void testUnicodeEscapeInsideExpressionFragment() { + doTestUnicodeEscapeDetection("f'{\\N{FOO}}'", true); + doTestUnicodeEscapeDetection("f'{\"\\N{FOO\"}'", true); + doTestUnicodeEscapeDetection("f'{\"\\N{\"}'", true); + doTestUnicodeEscapeDetection("f'{\"\\\\N{FOO}\"}'", false); + } + + private static void doTestUnicodeEscapeDetection(String fStringText, boolean expected) { + final List fragments = FStringParser.parse(fStringText); + assertSize(1, fragments); + final FragmentOffsets offsets = fragments.get(0); + assertEquals(expected, offsets.containsNamedUnicodeEscape()); + } }