mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +07:00
PY-31442 Migrate FStringsAnnotator to the new f-strings AST
Also tweaked interfaces of PyFormattedStringNode and PyFStringFragment.
This commit is contained in:
@@ -213,6 +213,14 @@ public class PyElementVisitor extends PsiElementVisitor {
|
||||
visitPyElement(node);
|
||||
}
|
||||
|
||||
public void visitPyFormattedStringNode(PyFormattedStringNode node) {
|
||||
visitPyElement(node);
|
||||
}
|
||||
|
||||
public void visitPyFStringFragment(PyFStringFragment node) {
|
||||
visitPyElement(node);
|
||||
}
|
||||
|
||||
public void visitPyNumericLiteralExpression(final PyNumericLiteralExpression node) {
|
||||
visitPyElement(node);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.psi;
|
||||
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PyFStringFragment extends PsiElement {
|
||||
public interface PyFStringFragment extends PyElement {
|
||||
@Nullable
|
||||
PyExpression getMainExpression();
|
||||
PyExpression getExpression();
|
||||
|
||||
@NotNull
|
||||
List<PyFStringFragment> getFormatFragments();
|
||||
TextRange getExpressionContentRange();
|
||||
|
||||
@Nullable
|
||||
PsiElement getColon();
|
||||
PsiElement getTypeConversion();
|
||||
|
||||
@Nullable
|
||||
PyFStringFragmentFormatPart getFormatPart();
|
||||
|
||||
@Nullable
|
||||
PsiElement getClosingBrace();
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.psi;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PyFStringFragmentFormatPart extends PyElement {
|
||||
@NotNull
|
||||
List<PyFStringFragment> getFragments();
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.psi;
|
||||
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
@@ -8,4 +9,8 @@ import java.util.List;
|
||||
public interface PyFormattedStringNode extends PyRichStringNode {
|
||||
@NotNull
|
||||
List<PyFStringFragment> getFragments();
|
||||
|
||||
@NotNull
|
||||
List<TextRange> getLiteralPartRanges();
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.jetbrains.python.psi;
|
||||
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
@@ -11,7 +10,7 @@ import java.util.Set;
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public interface PyRichStringNode extends PsiElement {
|
||||
public interface PyRichStringNode extends PyElement {
|
||||
|
||||
enum Modifier {
|
||||
UNICODE,
|
||||
|
||||
@@ -74,7 +74,8 @@ public class PyStringLiteralUtil {
|
||||
public static boolean isStringLiteralToken(@NotNull String text) {
|
||||
final PythonLexer lexer = new PythonLexer();
|
||||
lexer.start(text);
|
||||
return PyTokenTypes.STRING_NODES.contains(lexer.getTokenType()) && lexer.getTokenEnd() == lexer.getBufferEnd();
|
||||
return PyTokenTypes.STRING_NODES.contains(lexer.getTokenType()) && lexer.getTokenEnd() == lexer.getBufferEnd() ||
|
||||
PyTokenTypes.FSTRING_START == lexer.getTokenType();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,12 +43,14 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.wm.WindowManager;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.stubs.StubElement;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.ui.awt.RelativePoint;
|
||||
import com.intellij.util.*;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.NotNullPredicate;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.codeInsight.completion.OverwriteEqualsInsertHandler;
|
||||
@@ -1914,8 +1916,10 @@ public class PyUtil {
|
||||
private final TextRange myContentRange;
|
||||
|
||||
public StringNodeInfo(@NotNull ASTNode node) {
|
||||
if (!PyTokenTypes.STRING_NODES.contains(node.getElementType())) {
|
||||
throw new IllegalArgumentException("Node must be valid Python string literal token, but " + node.getElementType() + " was given");
|
||||
final IElementType nodeType = node.getElementType();
|
||||
// TODO Migrate to newer PyRichStringNode API
|
||||
if (!PyTokenTypes.STRING_NODES.contains(nodeType) && nodeType != PyElementTypes.FSTRING_NODE) {
|
||||
throw new IllegalArgumentException("Node must be valid Python string literal token, but " + nodeType + " was given");
|
||||
}
|
||||
myNode = node;
|
||||
final String nodeText = node.getText();
|
||||
|
||||
@@ -2,9 +2,21 @@
|
||||
package com.jetbrains.python.psi.impl;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.psi.PyFStringFragment;
|
||||
import com.jetbrains.python.psi.PyFStringFragmentFormatPart;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PyFStringFragmentFormatPartImpl extends PyElementImpl {
|
||||
import java.util.List;
|
||||
|
||||
public class PyFStringFragmentFormatPartImpl extends PyElementImpl implements PyFStringFragmentFormatPart {
|
||||
public PyFStringFragmentFormatPartImpl(ASTNode astNode) {
|
||||
super(astNode);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<PyFStringFragment> getFragments() {
|
||||
return findChildrenByType(PyElementTypes.FSTRING_FRAGMENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,37 +2,50 @@
|
||||
package com.jetbrains.python.psi.impl;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.psi.PyElementVisitor;
|
||||
import com.jetbrains.python.psi.PyExpression;
|
||||
import com.jetbrains.python.psi.PyFStringFragment;
|
||||
import com.jetbrains.python.psi.PyFStringFragmentFormatPart;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PyFStringFragmentImpl extends PyElementImpl implements PyFStringFragment {
|
||||
public PyFStringFragmentImpl(ASTNode astNode) {
|
||||
super(astNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
|
||||
pyVisitor.visitPyFStringFragment(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PyExpression getMainExpression() {
|
||||
public PyExpression getExpression() {
|
||||
return findChildByClass(PyExpression.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<PyFStringFragment> getFormatFragments() {
|
||||
return findChildrenByType(PyElementTypes.FSTRING_FRAGMENT);
|
||||
public TextRange getExpressionContentRange() {
|
||||
final PsiElement endAnchor = ObjectUtils.coalesce(getTypeConversion(), getFormatPart(), getClosingBrace());
|
||||
return TextRange.create(1, endAnchor != null ? endAnchor.getStartOffsetInParent(): getTextLength());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement getColon() {
|
||||
return findChildByType(PyTokenTypes.COLON);
|
||||
public PsiElement getTypeConversion() {
|
||||
return findChildByType(PyTokenTypes.FSTRING_FRAGMENT_TYPE_CONVERSION);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PyFStringFragmentFormatPart getFormatPart() {
|
||||
return findChildByClass(PyFStringFragmentFormatPart.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -7,9 +7,11 @@ import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiErrorElement;
|
||||
import com.intellij.psi.SyntaxTraverser;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.psi.PyElementVisitor;
|
||||
import com.jetbrains.python.psi.PyFStringFragment;
|
||||
import com.jetbrains.python.psi.PyFormattedStringNode;
|
||||
import com.jetbrains.python.psi.PyStringLiteralUtil;
|
||||
@@ -26,12 +28,30 @@ public class PyFormattedStringNodeImpl extends PyElementImpl implements PyFormat
|
||||
super(astNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
|
||||
pyVisitor.visitPyFormattedStringNode(this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<PyFStringFragment> getFragments() {
|
||||
return findChildrenByType(PyElementTypes.FSTRING_FRAGMENT);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<TextRange> getLiteralPartRanges() {
|
||||
final int nodeStart = getTextRange().getStartOffset();
|
||||
final TextRange contentRange = getContentRange();
|
||||
return SyntaxTraverser.psiApi()
|
||||
.children(this)
|
||||
.filter(child -> child.getNode().getElementType() == PyTokenTypes.FSTRING_TEXT)
|
||||
.map(part -> part.getTextRange().shiftLeft(nodeStart))
|
||||
.map(range -> range.intersection(contentRange))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getPrefix() {
|
||||
|
||||
@@ -15,87 +15,93 @@
|
||||
*/
|
||||
package com.jetbrains.python.validation;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import com.jetbrains.python.codeInsight.fstrings.FStringParser;
|
||||
import com.jetbrains.python.codeInsight.fstrings.FStringParser.Fragment;
|
||||
import com.intellij.psi.PsiComment;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.psi.PyFStringFragment;
|
||||
import com.jetbrains.python.psi.PyFStringFragmentFormatPart;
|
||||
import com.jetbrains.python.psi.PyFormattedStringNode;
|
||||
import com.jetbrains.python.psi.PyStringLiteralExpression;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.StringNodeInfo;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class FStringsAnnotator extends PyAnnotator {
|
||||
@Override
|
||||
public void visitPyStringLiteralExpression(PyStringLiteralExpression pyString) {
|
||||
for (ASTNode node : pyString.getStringNodes()) {
|
||||
final StringNodeInfo nodeInfo = new StringNodeInfo(node);
|
||||
final String nodeText = node.getText();
|
||||
if (nodeInfo.isFormatted()) {
|
||||
final int nodeContentEnd = nodeInfo.getContentRange().getEndOffset();
|
||||
final FStringParser.ParseResult result = FStringParser.parse(nodeText);
|
||||
TextRange unclosedBraceRange = null;
|
||||
for (Fragment fragment : result.getFragments()) {
|
||||
final int fragLeftBrace = fragment.getLeftBraceOffset();
|
||||
final int fragContentEnd = fragment.getContentEndOffset();
|
||||
final int fragRightBrace = fragment.getRightBraceOffset();
|
||||
|
||||
final TextRange wholeFragmentRange = TextRange.create(fragLeftBrace, fragRightBrace == -1 ? nodeContentEnd : fragRightBrace + 1);
|
||||
if (fragment.getDepth() > 2) {
|
||||
// Do not report anything about expression fragments nested deeper that three times
|
||||
if (fragment.getDepth() == 3) {
|
||||
report("Expression fragment inside f-string is nested too deeply", wholeFragmentRange, node);
|
||||
@Override
|
||||
public void visitPyFStringFragment(PyFStringFragment node) {
|
||||
final List<PyFStringFragment> enclosingFragments = PsiTreeUtil.collectParents(node, PyFStringFragment.class, false,
|
||||
PyStringLiteralExpression.class::isInstance);
|
||||
if (enclosingFragments.size() > 1) {
|
||||
report(node, "Expression fragment inside f-string is nested too deeply");
|
||||
}
|
||||
final PsiElement typeConversion = node.getTypeConversion();
|
||||
if (typeConversion != null) {
|
||||
final String conversionChar = typeConversion.getText().substring(1);
|
||||
if (conversionChar.isEmpty()) {
|
||||
report(typeConversion, "Conversion character is expected: should be one of 's', 'r', 'a'");
|
||||
}
|
||||
else if (conversionChar.length() > 1 || "sra".indexOf(conversionChar.charAt(0)) < 0) {
|
||||
report(typeConversion, "Illegal conversion character '" + conversionChar + "': should be one of 's', 'r', 'a'");
|
||||
}
|
||||
}
|
||||
|
||||
final boolean topLevel = PsiTreeUtil.getParentOfType(node, PyFStringFragment.class, true) == null;
|
||||
if (topLevel) {
|
||||
final List<PyFStringFragment> fragments = Lists.newArrayList(node);
|
||||
final PyFStringFragmentFormatPart formatPart = node.getFormatPart();
|
||||
if (formatPart != null) {
|
||||
fragments.addAll(formatPart.getFragments());
|
||||
}
|
||||
for (PyFStringFragment fragment : fragments) {
|
||||
final String wholeNodeText = fragment.getText();
|
||||
final TextRange range = fragment.getExpressionContentRange();
|
||||
for (int i = range.getStartOffset(); i < range.getEndOffset(); i++) {
|
||||
if (wholeNodeText.charAt(i) == '\\') {
|
||||
reportCharacter(fragment, i, "Expression fragments inside f-strings cannot include backslashes");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyFormattedStringNode(PyFormattedStringNode node) {
|
||||
final String wholeNodeText = node.getText();
|
||||
for (TextRange textRange : node.getLiteralPartRanges()) {
|
||||
int i = textRange.getStartOffset();
|
||||
while (i < textRange.getEndOffset()) {
|
||||
final char c = wholeNodeText.charAt(i);
|
||||
if (c == '}') {
|
||||
if (i + 1 < textRange.getEndOffset() && wholeNodeText.charAt(i + 1) == '}') {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if (CharArrayUtil.isEmptyOrSpaces(nodeText, fragLeftBrace + 1, fragContentEnd) && fragContentEnd < nodeContentEnd) {
|
||||
final TextRange range = TextRange.create(fragLeftBrace, fragContentEnd + 1);
|
||||
report("Empty expression fragments are not allowed inside f-strings", range, node);
|
||||
}
|
||||
if (fragRightBrace == -1 && unclosedBraceRange == null) {
|
||||
unclosedBraceRange = wholeFragmentRange;
|
||||
}
|
||||
if (fragment.getFirstHashOffset() != -1) {
|
||||
final TextRange range = TextRange.create(fragment.getFirstHashOffset(), fragment.getContentEndOffset());
|
||||
report("Expression fragments inside f-strings cannot include line comments", range, node);
|
||||
}
|
||||
for (int i = fragLeftBrace + 1; i < fragment.getContentEndOffset(); i++) {
|
||||
if (nodeText.charAt(i) == '\\') {
|
||||
reportCharacter("Expression fragments inside f-strings cannot include backslashes", i, node);
|
||||
}
|
||||
}
|
||||
// Do not warn about illegal conversion character if '!' is right before closing quotes
|
||||
if (fragContentEnd < nodeContentEnd && nodeText.charAt(fragContentEnd) == '!' && fragContentEnd + 1 < nodeContentEnd) {
|
||||
final char conversionChar = nodeText.charAt(fragContentEnd + 1);
|
||||
// No conversion character -- highlight only "!"
|
||||
if (fragContentEnd + 1 == fragRightBrace || conversionChar == ':') {
|
||||
reportCharacter("Conversion character is expected: should be one of 's', 'r', 'a'", fragContentEnd, node);
|
||||
}
|
||||
// Wrong conversion character -- highlight both "!" and the following symbol
|
||||
else if ("sra".indexOf(conversionChar) < 0) {
|
||||
final TextRange range = TextRange.from(fragContentEnd, 2);
|
||||
report("Illegal conversion character '" + conversionChar + "': should be one of 's', 'r', 'a'", range, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Integer offset : result.getSingleRightBraces()) {
|
||||
reportCharacter("Single '}' is not allowed inside f-strings", offset, node);
|
||||
}
|
||||
if (unclosedBraceRange != null) {
|
||||
report("'}' is expected", unclosedBraceRange, node);
|
||||
reportCharacter(node, i, "Single '}' is not allowed inside f-strings");
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void report(@NotNull String message, @NotNull TextRange range, @NotNull ASTNode node) {
|
||||
getHolder().createErrorAnnotation(range.shiftRight(node.getTextRange().getStartOffset()), message);
|
||||
@Override
|
||||
public void visitComment(PsiComment comment) {
|
||||
final boolean insideFragment = PsiTreeUtil.getParentOfType(comment, PyFStringFragment.class) != null;
|
||||
if (insideFragment) {
|
||||
report(comment, "Expression fragments inside f-strings cannot include line comments");
|
||||
}
|
||||
}
|
||||
|
||||
private void reportCharacter(@NotNull String message, int offset, @NotNull ASTNode node) {
|
||||
report(message, TextRange.from(offset, 1), node);
|
||||
public void reportCharacter(PsiElement element, int offset, String message) {
|
||||
final int nodeStartOffset = element.getTextRange().getStartOffset();
|
||||
getHolder().createErrorAnnotation(TextRange.from(offset, 1).shiftRight(nodeStartOffset), message);
|
||||
}
|
||||
|
||||
public void report(PsiElement element, String error) {
|
||||
getHolder().createErrorAnnotation(element, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.psi.PyStringLiteralExpression;
|
||||
import com.jetbrains.python.psi.PyStringLiteralUtil;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
@@ -25,6 +26,10 @@ public class StringLiteralQuotesAnnotator extends PyAnnotator {
|
||||
public void visitPyStringLiteralExpression(final PyStringLiteralExpression node) {
|
||||
final List<ASTNode> stringNodes = node.getStringNodes();
|
||||
for (ASTNode stringNode : stringNodes) {
|
||||
// TODO Migrate to newer PyRichStringNode API
|
||||
if (stringNode.getElementType() == PyElementTypes.FSTRING_NODE) {
|
||||
continue;
|
||||
}
|
||||
final String nodeText = PyPsiUtils.getElementTextWithoutHostEscaping(stringNode.getPsi());
|
||||
final int index = PyStringLiteralUtil.getPrefixLength(nodeText);
|
||||
final String unprefixed = nodeText.substring(index);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
f'{<error descr="Expression fragments inside f-strings cannot include backslashes">\</error>t}'
|
||||
f'<error descr="'}' is expected">{<error descr="Expression fragments inside f-strings cannot include backslashes">\</error>t</error>'
|
||||
f'{<error descr="Expression fragments inside f-strings cannot include backslashes">\</error>N{GREEK SMALL LETTER ALPHA}}'
|
||||
f'{<error descr="expression expected"><error descr="Expression fragments inside f-strings cannot include backslashes">\</error>t</error>}'
|
||||
f'{<error descr="expression expected"><error descr="Expression fragments inside f-strings cannot include backslashes">\</error>t</error><error descr="type conversion, : or } expected">'</error>
|
||||
f'{<error descr="expression expected"><error descr="Expression fragments inside f-strings cannot include backslashes">\</error>N{GREEK SMALL LETTER ALPHA}</error>}'
|
||||
f'{Formatable():\n\t}'
|
||||
f'{42:{<error descr="Expression fragments inside f-strings cannot include backslashes">\</error>t}}'
|
||||
f'{42:{<error descr="expression expected"><error descr="Expression fragments inside f-strings cannot include backslashes">\</error>t</error>}}'
|
||||
@@ -1,10 +1,10 @@
|
||||
f'<error descr="Empty expression fragments are not allowed inside f-strings">{}</error>'
|
||||
f'<error descr="'}' is expected">{</error>'
|
||||
<error descr="Missing closing quote [']">f'<error descr="'}' is expected">{</error></error>
|
||||
f'<error descr="Empty expression fragments are not allowed inside f-strings">{!</error>r}'
|
||||
f'<error descr="Empty expression fragments are not allowed inside f-strings">{:</error>2.3}'
|
||||
f'{42:2.<error descr="Empty expression fragments are not allowed inside f-strings">{}</error>}'
|
||||
f'<error descr="Empty expression fragments are not allowed inside f-strings">{ }</error>'
|
||||
f'{42:<error descr="Empty expression fragments are not allowed inside f-strings">{ }</error>}'
|
||||
f'<error descr="'}' is expected"><error descr="Empty expression fragments are not allowed inside f-strings">{ :</error>{ </error>'
|
||||
f'<error descr="Empty expression fragments are not allowed inside f-strings">{ !</error>r:<error descr="Empty expression fragments are not allowed inside f-strings">{ :</error>42}}'
|
||||
f'{<error descr="expression expected">}</error>'
|
||||
f'{<error descr="expression expected"><error descr="type conversion, : or } expected">'</error></error>
|
||||
f'{<EOLError descr="type conversion, : or } expected"></EOLError><EOLError descr="expression expected"></EOLError><EOLError descr="' expected"></EOLError>
|
||||
f'{<error descr="expression expected">!</error>r}'
|
||||
f'{<error descr="expression expected">:</error>2.3}'
|
||||
f'{42:2.{<error descr="expression expected">}</error>}'
|
||||
f'{<error descr="expression expected"> </error> }'
|
||||
f'{42:{<error descr="expression expected"> </error>}}'
|
||||
f'{<error descr="expression expected"> </error> :{<error descr="expression expected"><error descr="type conversion, : or } expected"> </error></error> '
|
||||
f'{<error descr="expression expected"> </error> !r:{<error descr="expression expected"> </error> :42}}'
|
||||
@@ -1,7 +1,10 @@
|
||||
f'<error descr="'}' is expected">{<error descr="Expression fragments inside f-strings cannot include line comments">#</error></error>'
|
||||
<error descr="Missing closing quote [']">f'<error descr="'}' is expected">{<error descr="Expression fragments inside f-strings cannot include line comments">#</error></error></error>
|
||||
f'{<error descr="Expression fragments inside f-strings cannot include line comments">#foo#</error>}'
|
||||
f'{42:#}'
|
||||
f'{42:{<error descr="Expression fragments inside f-strings cannot include line comments">#</error>}}'
|
||||
f'{x <error descr="Expression fragments inside f-strings cannot include line comments">### foo</error>}'
|
||||
f'{<error descr="' expected"><error descr="expression expected"><error descr="type conversion, : or } expected">#</error></error></error>'
|
||||
f'{<error descr="' expected"><error descr="expression expected"><error descr="type conversion, : or } expected">#</error></error></error>
|
||||
f'{<error descr="' expected"><error descr="expression expected"><error descr="type conversion, : or } expected">#</error></error></error>foo#}'
|
||||
f'{42:<error descr="' expected"><error descr="} expected">#</error></error>}'
|
||||
f'{42:{<error descr="' expected"><error descr="expression expected"><error descr="type conversion, : or } expected">#</error></error></error>}}'
|
||||
f'{x<error descr="' expected"><error descr="type conversion, : or } expected"> </error></error>### foo}'
|
||||
f'{"###"}'
|
||||
f'''{[
|
||||
42 <error descr="Expression fragments inside f-strings cannot include line comments"># foo</error>
|
||||
]}'''
|
||||
|
||||
@@ -2,8 +2,8 @@ f'{42!r}'
|
||||
f'{42!s}'
|
||||
f'{42!a}'
|
||||
f'{42<error descr="Illegal conversion character 'z': should be one of 's', 'r', 'a'">!z</error>}'
|
||||
f'{42<error descr="Illegal conversion character 'f': should be one of 's', 'r', 'a'">!f</error>oo}'
|
||||
f'{42<error descr="Illegal conversion character 'foo': should be one of 's', 'r', 'a'">!foo</error>}'
|
||||
f'{42<error descr="Conversion character is expected: should be one of 's', 'r', 'a'">!</error>}'
|
||||
f'{42<error descr="Conversion character is expected: should be one of 's', 'r', 'a'">!</error>:2}'
|
||||
f'<error descr="'}' is expected">{42!</error>'
|
||||
<error descr="Missing closing quote [']">f'<error descr="'}' is expected">{42!</error></error>
|
||||
f'{42<error descr="Conversion character is expected: should be one of 's', 'r', 'a'">!</error><error descr=": or } expected">'</error>
|
||||
f'{42<error descr="Conversion character is expected: should be one of 's', 'r', 'a'">!</error><EOLError descr=": or } expected"></EOLError><EOLError descr="' expected"></EOLError>
|
||||
|
||||
@@ -3,8 +3,8 @@ f'{42!r}'
|
||||
f'{42!r:03}'
|
||||
f'{42:03}'
|
||||
f'{42!r:{y}.{z}}'
|
||||
f'<error descr="'}' is expected">{</error>'
|
||||
f'<error descr="'}' is expected">{42:{</error>'
|
||||
f'<error descr="'}' is expected">{42!r:{</error>'
|
||||
f'{<error descr="expression expected"><error descr="type conversion, : or } expected">'</error></error>
|
||||
f'{42:{<error descr="expression expected"><error descr="type conversion, : or } expected">'</error></error>
|
||||
f'{42!r:{<error descr="expression expected"><error descr="type conversion, : or } expected">'</error></error>
|
||||
f'{{'
|
||||
f'{{<error descr="'}' is expected">{</error>'
|
||||
f'{{{<error descr="expression expected"><error descr="type conversion, : or } expected">'</error></error>
|
||||
@@ -1,7 +1,7 @@
|
||||
f'{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{}</error>}}'
|
||||
f'{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{# foo}</error>}}'
|
||||
f'{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{z!z}</error>}}'
|
||||
f'{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{z:{42}}</error>}}'
|
||||
f'<error descr="Empty expression fragments are not allowed inside f-strings">{:</error><error descr="Empty expression fragments are not allowed inside f-strings">{:</error><error descr="Expression fragment inside f-string is nested too deeply">{:{}}</error>}}'
|
||||
f'<error descr="'}' is expected">{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{z</error></error>'
|
||||
<error descr="Missing closing quote [']">f'<error descr="'}' is expected">{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{z</error></error></error>
|
||||
f'{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{<error descr="expression expected">}</error></error>}}'
|
||||
f'{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{</error><error descr="' expected"><error descr="expression expected"><error descr="type conversion, : or } expected">#</error></error></error> foo}}}'
|
||||
f'{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{z<error descr="Illegal conversion character 'z': should be one of 's', 'r', 'a'">!z</error>}</error>}}'
|
||||
f'{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{z:<error descr="Expression fragment inside f-string is nested too deeply">{42}</error>}</error>}}'
|
||||
f'{<error descr="expression expected">:</error>{<error descr="expression expected">:</error><error descr="Expression fragment inside f-string is nested too deeply">{<error descr="expression expected">:</error><error descr="Expression fragment inside f-string is nested too deeply">{<error descr="expression expected">}</error></error>}</error>}}'
|
||||
f'{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{z</error><error descr="type conversion, : or } expected">'</error>
|
||||
f'{x:{y:<error descr="Expression fragment inside f-string is nested too deeply">{z</error><EOLError descr="type conversion, : or } expected"></EOLError><EOLError descr="' expected"></EOLError>
|
||||
1
python/testData/psi/FStringDeeplyNestedEmptyFragments.py
Normal file
1
python/testData/psi/FStringDeeplyNestedEmptyFragments.py
Normal file
@@ -0,0 +1 @@
|
||||
s = f'{:{:{:{}}}}'
|
||||
37
python/testData/psi/FStringDeeplyNestedEmptyFragments.txt
Normal file
37
python/testData/psi/FStringDeeplyNestedEmptyFragments.txt
Normal file
@@ -0,0 +1,37 @@
|
||||
PyFile:FStringDeeplyNestedEmptyFragments.py
|
||||
PyAssignmentStatement
|
||||
PyTargetExpression: s
|
||||
PsiElement(Py:IDENTIFIER)('s')
|
||||
PsiWhiteSpace(' ')
|
||||
PsiElement(Py:EQ)('=')
|
||||
PsiWhiteSpace(' ')
|
||||
PyStringLiteralExpression: {:{:{:{}}}}
|
||||
PyFormattedStringNode
|
||||
PsiElement(Py:FSTRING_START)('f'')
|
||||
PyFStringFragment
|
||||
PsiElement(Py:FSTRING_FRAGMENT_START)('{')
|
||||
PsiErrorElement:expression expected
|
||||
<empty list>
|
||||
PyFStringFragmentFormatPart
|
||||
PsiElement(Py:FSTRING_FRAGMENT_FORMAT_START)(':')
|
||||
PyFStringFragment
|
||||
PsiElement(Py:FSTRING_FRAGMENT_START)('{')
|
||||
PsiErrorElement:expression expected
|
||||
<empty list>
|
||||
PyFStringFragmentFormatPart
|
||||
PsiElement(Py:FSTRING_FRAGMENT_FORMAT_START)(':')
|
||||
PyFStringFragment
|
||||
PsiElement(Py:FSTRING_FRAGMENT_START)('{')
|
||||
PsiErrorElement:expression expected
|
||||
<empty list>
|
||||
PyFStringFragmentFormatPart
|
||||
PsiElement(Py:FSTRING_FRAGMENT_FORMAT_START)(':')
|
||||
PyFStringFragment
|
||||
PsiElement(Py:FSTRING_FRAGMENT_START)('{')
|
||||
PsiErrorElement:expression expected
|
||||
<empty list>
|
||||
PsiElement(Py:FSTRING_FRAGMENT_END)('}')
|
||||
PsiElement(Py:FSTRING_FRAGMENT_END)('}')
|
||||
PsiElement(Py:FSTRING_FRAGMENT_END)('}')
|
||||
PsiElement(Py:FSTRING_FRAGMENT_END)('}')
|
||||
PsiElement(Py:FSTRING_END)(''')
|
||||
@@ -0,0 +1,3 @@
|
||||
pass
|
||||
f'''{1 +
|
||||
2}'''
|
||||
@@ -0,0 +1,21 @@
|
||||
PyFile:MultilineFStringContainingMultilineExpressionAfterStatementBreak.py
|
||||
PyPassStatement
|
||||
PsiElement(Py:PASS_KEYWORD)('pass')
|
||||
PsiWhiteSpace('\n')
|
||||
PyExpressionStatement
|
||||
PyStringLiteralExpression: {1 +
|
||||
2}
|
||||
PyFormattedStringNode
|
||||
PsiElement(Py:FSTRING_START)('f'''')
|
||||
PyFStringFragment
|
||||
PsiElement(Py:FSTRING_FRAGMENT_START)('{')
|
||||
PyBinaryExpression
|
||||
PyNumericLiteralExpression
|
||||
PsiElement(Py:INTEGER_LITERAL)('1')
|
||||
PsiWhiteSpace(' ')
|
||||
PsiElement(Py:PLUS)('+')
|
||||
PsiWhiteSpace(' \n')
|
||||
PyNumericLiteralExpression
|
||||
PsiElement(Py:INTEGER_LITERAL)('2')
|
||||
PsiElement(Py:FSTRING_FRAGMENT_END)('}')
|
||||
PsiElement(Py:FSTRING_END)(''''')
|
||||
@@ -821,6 +821,10 @@ public class PythonParsingTest extends ParsingTestCase {
|
||||
doTest(LanguageLevel.PYTHON36);
|
||||
}
|
||||
|
||||
public void testFStringDeeplyNestedEmptyFragments() {
|
||||
doTest(LanguageLevel.PYTHON36);
|
||||
}
|
||||
|
||||
// PY-19036
|
||||
public void testAwaitInNonAsyncNestedFunction() {
|
||||
doTest(LanguageLevel.PYTHON35);
|
||||
|
||||
Reference in New Issue
Block a user