PY-20785 Do not inject inside f-string fragments which contain named unicode escapes

We cannot decode them properly yet because JDK doesn't offer any
analog of unicodedata.lookup() in Python.
This commit is contained in:
Mikhail Golubev
2016-09-26 09:09:23 +03:00
parent 7196817a77
commit de6feb7cc5
3 changed files with 26 additions and 3 deletions
@@ -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);
@@ -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();
@@ -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<FragmentOffsets> fragments = FStringParser.parse(fStringText);
assertSize(1, fragments);
final FragmentOffsets offsets = fragments.get(0);
assertEquals(expected, offsets.containsNamedUnicodeEscape());
}
}