mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-71549 Convert some syntax-related annotators to visitors and add them to PySyntaxAnnotator
GitOrigin-RevId: 46f4a0ccd8c2e7ef45ce5fa37ec5c48376924587
This commit is contained in:
committed by
intellij-monorepo-bot
parent
09cc73185a
commit
2e6c983cb2
@@ -170,14 +170,7 @@
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PySyntaxAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyDocStringAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyDefinitionsAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyGeneratorInArgumentListAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyStarAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyStringLiteralQuotesAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyFStringsAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyPatternAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyTryExceptAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyLocalVariableAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyTypeParameterListAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyCompositeAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyFunctionHighlightingAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyBuiltinAnnotator"/>
|
||||
|
||||
+49
-57
@@ -29,71 +29,63 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public final class PyFStringsAnnotator extends PyAnnotatorBase {
|
||||
final class PyFStringsAnnotator extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
|
||||
PyFStringsAnnotator(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
|
||||
@Override
|
||||
public void annotate(@NotNull PsiElement element, @NotNull PyAnnotationHolder holder) {
|
||||
element.accept(new MyVisitor(holder));
|
||||
}
|
||||
|
||||
private static class MyVisitor extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
|
||||
private MyVisitor(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
|
||||
@Override
|
||||
public void visitPyFStringFragment(@NotNull PyFStringFragment node) {
|
||||
final PsiElement typeConversion = node.getTypeConversion();
|
||||
if (typeConversion != null) {
|
||||
final String conversionChar = typeConversion.getText().substring(1);
|
||||
if (conversionChar.isEmpty()) {
|
||||
report(typeConversion, PyPsiBundle.message("ANN.fstrings.missing.conversion.character"));
|
||||
}
|
||||
else if (conversionChar.length() > 1 || "sra".indexOf(conversionChar.charAt(0)) < 0) {
|
||||
report(typeConversion, PyPsiBundle.message("ANN.fstrings.illegal.conversion.character", conversionChar));
|
||||
}
|
||||
public void visitPyFStringFragment(@NotNull PyFStringFragment node) {
|
||||
final PsiElement typeConversion = node.getTypeConversion();
|
||||
if (typeConversion != null) {
|
||||
final String conversionChar = typeConversion.getText().substring(1);
|
||||
if (conversionChar.isEmpty()) {
|
||||
report(typeConversion, PyPsiBundle.message("ANN.fstrings.missing.conversion.character"));
|
||||
}
|
||||
else if (conversionChar.length() > 1 || "sra".indexOf(conversionChar.charAt(0)) < 0) {
|
||||
report(typeConversion, PyPsiBundle.message("ANN.fstrings.illegal.conversion.character", conversionChar));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyFormattedStringElement(@NotNull PyFormattedStringElement node) {
|
||||
final String wholeNodeText = node.getText();
|
||||
for (TextRange textRange : node.getLiteralPartRanges()) {
|
||||
int i = textRange.getStartOffset();
|
||||
while (i < textRange.getEndOffset()) {
|
||||
final int nextOffset = skipNamedUnicodeEscape(wholeNodeText, i, textRange.getEndOffset());
|
||||
if (i != nextOffset) {
|
||||
i = nextOffset;
|
||||
@Override
|
||||
public void visitPyFormattedStringElement(@NotNull PyFormattedStringElement node) {
|
||||
final String wholeNodeText = node.getText();
|
||||
for (TextRange textRange : node.getLiteralPartRanges()) {
|
||||
int i = textRange.getStartOffset();
|
||||
while (i < textRange.getEndOffset()) {
|
||||
final int nextOffset = skipNamedUnicodeEscape(wholeNodeText, i, textRange.getEndOffset());
|
||||
if (i != nextOffset) {
|
||||
i = nextOffset;
|
||||
continue;
|
||||
}
|
||||
final char c = wholeNodeText.charAt(i);
|
||||
if (c == '}') {
|
||||
if (i + 1 < textRange.getEndOffset() && wholeNodeText.charAt(i + 1) == '}') {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
final char c = wholeNodeText.charAt(i);
|
||||
if (c == '}') {
|
||||
if (i + 1 < textRange.getEndOffset() && wholeNodeText.charAt(i + 1) == '}') {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
reportCharacter(node, i, PyPsiBundle.message("ANN.fstrings.single.right.brace.not.allowed.inside.fstrings"));
|
||||
}
|
||||
i++;
|
||||
reportCharacter(node, i, PyPsiBundle.message("ANN.fstrings.single.right.brace.not.allowed.inside.fstrings"));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private static int skipNamedUnicodeEscape(@NotNull String nodeText, int offset, int endOffset) {
|
||||
if (StringUtil.startsWith(nodeText, offset, "\\N{")) {
|
||||
final int rightBraceOffset = nodeText.indexOf('}', offset + 3);
|
||||
return rightBraceOffset < 0 ? endOffset : rightBraceOffset + 1;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void reportCharacter(@NotNull PsiElement element, int offset, @NotNull @InspectionMessage String message) {
|
||||
final int nodeStartOffset = element.getTextRange().getStartOffset();
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, message).range(TextRange.from(offset, 1).shiftRight(nodeStartOffset)).create();
|
||||
}
|
||||
|
||||
public void report(@NotNull PsiElement element, @NotNull @InspectionMessage String error) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, error).range(element).create();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int skipNamedUnicodeEscape(@NotNull String nodeText, int offset, int endOffset) {
|
||||
if (StringUtil.startsWith(nodeText, offset, "\\N{")) {
|
||||
final int rightBraceOffset = nodeText.indexOf('}', offset + 3);
|
||||
return rightBraceOffset < 0 ? endOffset : rightBraceOffset + 1;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void reportCharacter(@NotNull PsiElement element, int offset, @NotNull @InspectionMessage String message) {
|
||||
final int nodeStartOffset = element.getTextRange().getStartOffset();
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, message).range(TextRange.from(offset, 1).shiftRight(nodeStartOffset)).create();
|
||||
}
|
||||
|
||||
public void report(@NotNull PsiElement element, @NotNull @InspectionMessage String error) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, error).range(element).create();
|
||||
}
|
||||
}
|
||||
+12
-20
@@ -16,7 +16,6 @@
|
||||
package com.jetbrains.python.validation;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.PyPsiBundle;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.psi.PyArgumentList;
|
||||
@@ -26,26 +25,19 @@ import com.jetbrains.python.psi.PyGeneratorExpression;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
public final class PyGeneratorInArgumentListAnnotator extends PyAnnotatorBase {
|
||||
final class PyGeneratorInArgumentListAnnotator extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
|
||||
PyGeneratorInArgumentListAnnotator(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
|
||||
@Override
|
||||
public void annotate(@NotNull PsiElement element, @NotNull PyAnnotationHolder holder) {
|
||||
element.accept(new MyVisitor(holder));
|
||||
}
|
||||
|
||||
private static class MyVisitor extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
|
||||
private MyVisitor(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
|
||||
@Override
|
||||
public void visitPyArgumentList(@NotNull PyArgumentList node) {
|
||||
if (node.getArguments().length > 1) {
|
||||
for (PyExpression expression : node.getArguments()) {
|
||||
if (expression instanceof PyGeneratorExpression) {
|
||||
ASTNode firstChildNode = expression.getNode().getFirstChildNode();
|
||||
if (firstChildNode.getElementType() != PyTokenTypes.LPAR) {
|
||||
myHolder.markError(expression, PyPsiBundle.message("ANN.generator.expression.must.be.parenthesized.if.not.sole.argument"));
|
||||
}
|
||||
public void visitPyArgumentList(@NotNull PyArgumentList node) {
|
||||
if (node.getArguments().length > 1) {
|
||||
for (PyExpression expression : node.getArguments()) {
|
||||
if (expression instanceof PyGeneratorExpression) {
|
||||
ASTNode firstChildNode = expression.getNode().getFirstChildNode();
|
||||
if (firstChildNode.getElementType() != PyTokenTypes.LPAR) {
|
||||
myHolder.markError(expression, PyPsiBundle.message("ANN.generator.expression.must.be.parenthesized.if.not.sole.argument"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+137
-144
@@ -15,180 +15,173 @@ import java.util.*;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
public final class PyPatternAnnotator extends PyAnnotatorBase {
|
||||
final class PyPatternAnnotator extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
|
||||
PyPatternAnnotator(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
|
||||
@Override
|
||||
public void annotate(@NotNull PsiElement element, @NotNull PyAnnotationHolder holder) {
|
||||
element.accept(new MyVisitor(holder));
|
||||
public void visitPySingleStarPattern(@NotNull PySingleStarPattern starPattern) {
|
||||
PsiElement parent = starPattern.getParent();
|
||||
if (!(parent instanceof PySequencePattern)) {
|
||||
myHolder.markError(starPattern, PyPsiBundle.message("ANN.patterns.single.star.pattern.cannot.be.used.outside.sequence.patterns"));
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyVisitor extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
@Override
|
||||
public void visitPyDoubleStarPattern(@NotNull PyDoubleStarPattern starPattern) {
|
||||
PsiElement parent = starPattern.getParent();
|
||||
if (!(parent instanceof PyMappingPattern)) {
|
||||
myHolder.markError(starPattern, PyPsiBundle.message("ANN.patterns.double.star.pattern.cannot.be.used.outside.mapping.patterns"));
|
||||
}
|
||||
}
|
||||
|
||||
private MyVisitor(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
@Override
|
||||
public void visitPyLiteralPattern(@NotNull PyLiteralPattern literalPattern) {
|
||||
PyBinaryExpression expression = as(literalPattern.getExpression(), PyBinaryExpression.class);
|
||||
if (expression != null) {
|
||||
PyNumericLiteralExpression rightOperand = as(expression.getRightExpression(), PyNumericLiteralExpression.class);
|
||||
if (rightOperand != null && rightOperand.getFirstChild().getNode().getElementType() != PyTokenTypes.IMAGINARY_LITERAL) {
|
||||
myHolder.markError(expression, PyPsiBundle.message("ANN.patterns.invalid.complex.number.literal"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPySingleStarPattern(@NotNull PySingleStarPattern starPattern) {
|
||||
PsiElement parent = starPattern.getParent();
|
||||
if (!(parent instanceof PySequencePattern)) {
|
||||
myHolder.markError(starPattern, PyPsiBundle.message("ANN.patterns.single.star.pattern.cannot.be.used.outside.sequence.patterns"));
|
||||
@Override
|
||||
public void visitPyKeyValuePattern(@NotNull PyKeyValuePattern keyValuePattern) {
|
||||
PyPattern keyPattern = keyValuePattern.getKeyPattern();
|
||||
if (!(keyPattern instanceof PyValuePattern || keyPattern instanceof PyLiteralPattern)) {
|
||||
myHolder.markError(keyPattern, PyPsiBundle.message("ANN.patterns.key.pattern.can.only.be.value.or.literal.pattern"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyOrPattern(@NotNull PyOrPattern orPattern) {
|
||||
List<PyPattern> alternatives = orPattern.getAlternatives();
|
||||
PyPattern lastAlternative = alternatives.get(alternatives.size() - 1);
|
||||
Map<PyPattern, Set<String>> patternToBoundNames = new HashMap<>();
|
||||
Set<String> allBoundNames = new HashSet<>();
|
||||
|
||||
for (PyPattern alternative : alternatives) {
|
||||
Set<String> boundNames = SyntaxTraverser.psiTraverser(alternative)
|
||||
.filter(PyTargetExpression.class)
|
||||
.map(PyTargetExpression::getName)
|
||||
.toSet();
|
||||
patternToBoundNames.put(alternative, boundNames);
|
||||
allBoundNames.addAll(boundNames);
|
||||
|
||||
if (alternative != lastAlternative && alternative.isIrrefutable()) {
|
||||
myHolder.markError(alternative, PyPsiBundle.message("ANN.patterns.pattern.makes.remaining.alternatives.unreachable"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyDoubleStarPattern(@NotNull PyDoubleStarPattern starPattern) {
|
||||
PsiElement parent = starPattern.getParent();
|
||||
if (!(parent instanceof PyMappingPattern)) {
|
||||
myHolder.markError(starPattern, PyPsiBundle.message("ANN.patterns.double.star.pattern.cannot.be.used.outside.mapping.patterns"));
|
||||
for (Map.Entry<PyPattern, Set<String>> entry : patternToBoundNames.entrySet()) {
|
||||
Set<String> boundNames = entry.getValue();
|
||||
if (!boundNames.equals(allBoundNames)) {
|
||||
Collection<String> missingNames = ContainerUtil.subtract(allBoundNames, boundNames);
|
||||
String nameList = StringUtil.join(ContainerUtil.sorted(missingNames), ", ");
|
||||
myHolder.markError(entry.getKey(), PyPsiBundle.message("ANN.patterns.pattern.does.not.bind.names", missingNames.size(), nameList));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyLiteralPattern(@NotNull PyLiteralPattern literalPattern) {
|
||||
PyBinaryExpression expression = as(literalPattern.getExpression(), PyBinaryExpression.class);
|
||||
if (expression != null) {
|
||||
PyNumericLiteralExpression rightOperand = as(expression.getRightExpression(), PyNumericLiteralExpression.class);
|
||||
if (rightOperand != null && rightOperand.getFirstChild().getNode().getElementType() != PyTokenTypes.IMAGINARY_LITERAL) {
|
||||
myHolder.markError(expression, PyPsiBundle.message("ANN.patterns.invalid.complex.number.literal"));
|
||||
}
|
||||
@Override
|
||||
public void visitPyMatchStatement(@NotNull PyMatchStatement matchStatement) {
|
||||
List<PyCaseClause> clauses = matchStatement.getCaseClauses();
|
||||
if (clauses.isEmpty()) return;
|
||||
|
||||
for (PyCaseClause clause : clauses.subList(0, clauses.size() - 1)) {
|
||||
PyPattern pattern = clause.getPattern();
|
||||
if (pattern == null) {
|
||||
continue;
|
||||
}
|
||||
PyPattern unwrappedPattern = unwrapGroupAndAsPatterns(pattern);
|
||||
// There will be another warning for top-level star patterns
|
||||
if (unwrappedPattern instanceof PySingleStarPattern || unwrappedPattern instanceof PyDoubleStarPattern) {
|
||||
continue;
|
||||
}
|
||||
if (clause.getGuardCondition() == null && pattern.isIrrefutable()) {
|
||||
myHolder.markError(pattern, PyPsiBundle.message("ANN.patterns.pattern.makes.remaining.case.clauses.unreachable"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyKeyValuePattern(@NotNull PyKeyValuePattern keyValuePattern) {
|
||||
PyPattern keyPattern = keyValuePattern.getKeyPattern();
|
||||
if (!(keyPattern instanceof PyValuePattern || keyPattern instanceof PyLiteralPattern)) {
|
||||
myHolder.markError(keyPattern, PyPsiBundle.message("ANN.patterns.key.pattern.can.only.be.value.or.literal.pattern"));
|
||||
@Override
|
||||
public void visitPyPatternArgumentList(@NotNull PyPatternArgumentList argumentList) {
|
||||
Set<String> usedAttrNames = new HashSet<>();
|
||||
for (PyPattern attrPattern : argumentList.getPatterns()) {
|
||||
PyKeywordPattern keywordPattern = as(attrPattern, PyKeywordPattern.class);
|
||||
if (keywordPattern == null) {
|
||||
continue;
|
||||
}
|
||||
if (!usedAttrNames.add(keywordPattern.getKeyword())) {
|
||||
myHolder.markError(keywordPattern.getKeywordElement(),
|
||||
PyPsiBundle.message("ANN.patterns.attribute.name.is.repeated", keywordPattern.getKeyword()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyOrPattern(@NotNull PyOrPattern orPattern) {
|
||||
List<PyPattern> alternatives = orPattern.getAlternatives();
|
||||
PyPattern lastAlternative = alternatives.get(alternatives.size() - 1);
|
||||
Map<PyPattern, Set<String>> patternToBoundNames = new HashMap<>();
|
||||
Set<String> allBoundNames = new HashSet<>();
|
||||
@Override
|
||||
public void visitPyCaseClause(@NotNull PyCaseClause caseClause) {
|
||||
PyPattern pattern = caseClause.getPattern();
|
||||
if (pattern == null) return;
|
||||
|
||||
for (PyPattern alternative : alternatives) {
|
||||
Set<String> boundNames = SyntaxTraverser.psiTraverser(alternative)
|
||||
.filter(PyTargetExpression.class)
|
||||
.map(PyTargetExpression::getName)
|
||||
.toSet();
|
||||
patternToBoundNames.put(alternative, boundNames);
|
||||
allBoundNames.addAll(boundNames);
|
||||
Stack<Set<String>> boundNamesPerOrBranch = new Stack<>();
|
||||
// Assume that the top-level pattern is a single branch of a synthetic OR pattern
|
||||
boundNamesPerOrBranch.push(new HashSet<>());
|
||||
|
||||
if (alternative != lastAlternative && alternative.isIrrefutable()) {
|
||||
myHolder.markError(alternative, PyPsiBundle.message("ANN.patterns.pattern.makes.remaining.alternatives.unreachable"));
|
||||
pattern.accept(new PyRecursiveElementVisitor() {
|
||||
@Override
|
||||
public void visitPyOrPattern(@NotNull PyOrPattern orPattern) {
|
||||
Set<String> allBoundNamesInOrPattern = new HashSet<>();
|
||||
for (PyPattern alternative : orPattern.getAlternatives()) {
|
||||
boundNamesPerOrBranch.push(new HashSet<>());
|
||||
alternative.accept(this);
|
||||
allBoundNamesInOrPattern.addAll(boundNamesPerOrBranch.peek());
|
||||
boundNamesPerOrBranch.pop();
|
||||
}
|
||||
boundNamesPerOrBranch.peek().addAll(allBoundNamesInOrPattern);
|
||||
}
|
||||
|
||||
for (Map.Entry<PyPattern, Set<String>> entry : patternToBoundNames.entrySet()) {
|
||||
Set<String> boundNames = entry.getValue();
|
||||
if (!boundNames.equals(allBoundNames)) {
|
||||
Collection<String> missingNames = ContainerUtil.subtract(allBoundNames, boundNames);
|
||||
String nameList = StringUtil.join(ContainerUtil.sorted(missingNames), ", ");
|
||||
myHolder.markError(entry.getKey(), PyPsiBundle.message("ANN.patterns.pattern.does.not.bind.names", missingNames.size(), nameList));
|
||||
@Override
|
||||
public void visitPyTargetExpression(@NotNull PyTargetExpression target) {
|
||||
String name = target.getName();
|
||||
boolean alreadyBound = ContainerUtil.exists(boundNamesPerOrBranch, names -> names.contains(name));
|
||||
if (alreadyBound) {
|
||||
myHolder.markError(target, PyPsiBundle.message("ANN.patterns.name.already.bound", name));
|
||||
}
|
||||
boundNamesPerOrBranch.peek().add(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPySequencePattern(@NotNull PySequencePattern sequencePattern) {
|
||||
List<PySingleStarPattern> starPatterns = PsiTreeUtil.getChildrenOfTypeAsList(sequencePattern, PySingleStarPattern.class);
|
||||
if (starPatterns.size() > 1) {
|
||||
for (PySingleStarPattern pattern : starPatterns.subList(1, starPatterns.size())) {
|
||||
myHolder.markError(pattern, PyPsiBundle.message("ANN.patterns.repeated.star.pattern"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyMatchStatement(@NotNull PyMatchStatement matchStatement) {
|
||||
List<PyCaseClause> clauses = matchStatement.getCaseClauses();
|
||||
if (clauses.isEmpty()) return;
|
||||
|
||||
for (PyCaseClause clause : clauses.subList(0, clauses.size() - 1)) {
|
||||
PyPattern pattern = clause.getPattern();
|
||||
if (pattern == null) {
|
||||
continue;
|
||||
}
|
||||
PyPattern unwrappedPattern = unwrapGroupAndAsPatterns(pattern);
|
||||
// There will be another warning for top-level star patterns
|
||||
if (unwrappedPattern instanceof PySingleStarPattern || unwrappedPattern instanceof PyDoubleStarPattern) {
|
||||
continue;
|
||||
}
|
||||
if (clause.getGuardCondition() == null && pattern.isIrrefutable()) {
|
||||
myHolder.markError(pattern, PyPsiBundle.message("ANN.patterns.pattern.makes.remaining.case.clauses.unreachable"));
|
||||
}
|
||||
@Override
|
||||
public void visitPyMappingPattern(@NotNull PyMappingPattern mappingPattern) {
|
||||
List<PyDoubleStarPattern> starPatterns = PsiTreeUtil.getChildrenOfTypeAsList(mappingPattern, PyDoubleStarPattern.class);
|
||||
if (starPatterns.size() > 1) {
|
||||
for (PyDoubleStarPattern pattern : starPatterns.subList(1, starPatterns.size())) {
|
||||
myHolder.markError(pattern, PyPsiBundle.message("ANN.patterns.repeated.star.pattern"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyPatternArgumentList(@NotNull PyPatternArgumentList argumentList) {
|
||||
Set<String> usedAttrNames = new HashSet<>();
|
||||
for (PyPattern attrPattern : argumentList.getPatterns()) {
|
||||
PyKeywordPattern keywordPattern = as(attrPattern, PyKeywordPattern.class);
|
||||
if (keywordPattern == null) {
|
||||
continue;
|
||||
}
|
||||
if (!usedAttrNames.add(keywordPattern.getKeyword())) {
|
||||
myHolder.markError(keywordPattern.getKeywordElement(),
|
||||
PyPsiBundle.message("ANN.patterns.attribute.name.is.repeated", keywordPattern.getKeyword()));
|
||||
}
|
||||
}
|
||||
private static @NotNull PyPattern unwrapGroupAndAsPatterns(@NotNull PyPattern pattern) {
|
||||
if (pattern instanceof PyGroupPattern) {
|
||||
return unwrapGroupAndAsPatterns(((PyGroupPattern)pattern).getPattern());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyCaseClause(@NotNull PyCaseClause caseClause) {
|
||||
PyPattern pattern = caseClause.getPattern();
|
||||
if (pattern == null) return;
|
||||
|
||||
Stack<Set<String>> boundNamesPerOrBranch = new Stack<>();
|
||||
// Assume that the top-level pattern is a single branch of a synthetic OR pattern
|
||||
boundNamesPerOrBranch.push(new HashSet<>());
|
||||
|
||||
pattern.accept(new PyRecursiveElementVisitor() {
|
||||
@Override
|
||||
public void visitPyOrPattern(@NotNull PyOrPattern orPattern) {
|
||||
Set<String> allBoundNamesInOrPattern = new HashSet<>();
|
||||
for (PyPattern alternative : orPattern.getAlternatives()) {
|
||||
boundNamesPerOrBranch.push(new HashSet<>());
|
||||
alternative.accept(this);
|
||||
allBoundNamesInOrPattern.addAll(boundNamesPerOrBranch.peek());
|
||||
boundNamesPerOrBranch.pop();
|
||||
}
|
||||
boundNamesPerOrBranch.peek().addAll(allBoundNamesInOrPattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyTargetExpression(@NotNull PyTargetExpression target) {
|
||||
String name = target.getName();
|
||||
boolean alreadyBound = ContainerUtil.exists(boundNamesPerOrBranch, names -> names.contains(name));
|
||||
if (alreadyBound) {
|
||||
myHolder.markError(target, PyPsiBundle.message("ANN.patterns.name.already.bound", name));
|
||||
}
|
||||
boundNamesPerOrBranch.peek().add(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPySequencePattern(@NotNull PySequencePattern sequencePattern) {
|
||||
List<PySingleStarPattern> starPatterns = PsiTreeUtil.getChildrenOfTypeAsList(sequencePattern, PySingleStarPattern.class);
|
||||
if (starPatterns.size() > 1) {
|
||||
for (PySingleStarPattern pattern : starPatterns.subList(1, starPatterns.size())) {
|
||||
myHolder.markError(pattern, PyPsiBundle.message("ANN.patterns.repeated.star.pattern"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyMappingPattern(@NotNull PyMappingPattern mappingPattern) {
|
||||
List<PyDoubleStarPattern> starPatterns = PsiTreeUtil.getChildrenOfTypeAsList(mappingPattern, PyDoubleStarPattern.class);
|
||||
if (starPatterns.size() > 1) {
|
||||
for (PyDoubleStarPattern pattern : starPatterns.subList(1, starPatterns.size())) {
|
||||
myHolder.markError(pattern, PyPsiBundle.message("ANN.patterns.repeated.star.pattern"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull PyPattern unwrapGroupAndAsPatterns(@NotNull PyPattern pattern) {
|
||||
if (pattern instanceof PyGroupPattern) {
|
||||
return unwrapGroupAndAsPatterns(((PyGroupPattern)pattern).getPattern());
|
||||
}
|
||||
if (pattern instanceof PyAsPattern) {
|
||||
return unwrapGroupAndAsPatterns(((PyAsPattern)pattern).getPattern());
|
||||
}
|
||||
return pattern;
|
||||
if (pattern instanceof PyAsPattern) {
|
||||
return unwrapGroupAndAsPatterns(((PyAsPattern)pattern).getPattern());
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,45 +25,38 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
public final class PyStarAnnotator extends PyAnnotatorBase {
|
||||
final class PyStarAnnotator extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
|
||||
PyStarAnnotator(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
|
||||
@Override
|
||||
public void annotate(@NotNull PsiElement element, @NotNull PyAnnotationHolder holder) {
|
||||
element.accept(new MyVisitor(holder));
|
||||
public void visitPyStarExpression(@NotNull PyStarExpression node) {
|
||||
super.visitPyStarExpression(node);
|
||||
PsiElement parent = node.getParent();
|
||||
if (!node.isAssignmentTarget() &&
|
||||
!allowedUnpacking(node) &&
|
||||
!(parent instanceof PyParameterTypeList) &&
|
||||
!(parent instanceof PyTypeParameter) &&
|
||||
!(parent instanceof PyAnnotation && isVariadicArg(parent.getParent()))) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.can.t.use.starred.expression.here")).create();
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyVisitor extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
|
||||
private MyVisitor(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
|
||||
@Override
|
||||
public void visitPyStarExpression(@NotNull PyStarExpression node) {
|
||||
super.visitPyStarExpression(node);
|
||||
PsiElement parent = node.getParent();
|
||||
if (!node.isAssignmentTarget() &&
|
||||
!allowedUnpacking(node) &&
|
||||
!(parent instanceof PyParameterTypeList) &&
|
||||
!(parent instanceof PyTypeParameter) &&
|
||||
!(parent instanceof PyAnnotation && isVariadicArg(parent.getParent()))) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.can.t.use.starred.expression.here")).create();
|
||||
}
|
||||
private static boolean allowedUnpacking(@NotNull PyStarExpression starExpression) {
|
||||
if (!starExpression.isUnpacking()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean allowedUnpacking(@NotNull PyStarExpression starExpression) {
|
||||
if (!starExpression.isUnpacking()) {
|
||||
// Additional contexts where unpacking is prohibited depending on the language version are covered in CompatibilityVisitor.
|
||||
final PsiElement parent = PsiTreeUtil.skipParentsOfType(starExpression, PyParenthesizedExpression.class);
|
||||
if (parent instanceof PyTupleExpression) {
|
||||
final PsiElement tupleParent = parent.getParent();
|
||||
if (tupleParent instanceof PyYieldExpression && ((PyYieldExpression)tupleParent).isDelegating()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Additional contexts where unpacking is prohibited depending on the language version are covered in CompatibilityVisitor.
|
||||
final PsiElement parent = PsiTreeUtil.skipParentsOfType(starExpression, PyParenthesizedExpression.class);
|
||||
if (parent instanceof PyTupleExpression) {
|
||||
final PsiElement tupleParent = parent.getParent();
|
||||
if (tupleParent instanceof PyYieldExpression && ((PyYieldExpression)tupleParent).isDelegating()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isVariadicArg(@Nullable PsiElement parameter) {
|
||||
|
||||
+61
-69
@@ -5,7 +5,6 @@ import com.intellij.lang.ASTNode;
|
||||
import com.intellij.lang.annotation.HighlightSeverity;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.PyPsiBundle;
|
||||
import com.jetbrains.python.psi.PyElementVisitor;
|
||||
@@ -19,82 +18,75 @@ import java.util.List;
|
||||
/**
|
||||
* Looks for well-formedness of string constants.
|
||||
*/
|
||||
public final class PyStringLiteralQuotesAnnotator extends PyAnnotatorBase {
|
||||
final class PyStringLiteralQuotesAnnotator extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
|
||||
PyStringLiteralQuotesAnnotator(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
|
||||
private static final String TRIPLE_QUOTES = "\"\"\"";
|
||||
private static final String TRIPLE_APOS = "'''";
|
||||
|
||||
@Override
|
||||
public void annotate(@NotNull PsiElement element, @NotNull PyAnnotationHolder holder) {
|
||||
element.accept(new MyVisitor(holder));
|
||||
public void visitPyStringLiteralExpression(final @NotNull PyStringLiteralExpression node) {
|
||||
final List<ASTNode> stringNodes = node.getStringNodes();
|
||||
for (ASTNode stringNode : stringNodes) {
|
||||
// TODO Migrate to newer PyStringElement 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);
|
||||
final boolean foundError;
|
||||
if (StringUtil.startsWith(unprefixed, TRIPLE_QUOTES)) {
|
||||
foundError = checkTripleQuotedString(stringNode, unprefixed, TRIPLE_QUOTES);
|
||||
}
|
||||
else if (StringUtil.startsWith(unprefixed, TRIPLE_APOS)) {
|
||||
foundError = checkTripleQuotedString(stringNode, unprefixed, TRIPLE_APOS);
|
||||
}
|
||||
else {
|
||||
foundError = checkQuotedString(stringNode, unprefixed);
|
||||
}
|
||||
if (foundError) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyVisitor extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
|
||||
private MyVisitor(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
|
||||
private static final String TRIPLE_QUOTES = "\"\"\"";
|
||||
private static final String TRIPLE_APOS = "'''";
|
||||
|
||||
@Override
|
||||
public void visitPyStringLiteralExpression(final @NotNull PyStringLiteralExpression node) {
|
||||
final List<ASTNode> stringNodes = node.getStringNodes();
|
||||
for (ASTNode stringNode : stringNodes) {
|
||||
// TODO Migrate to newer PyStringElement 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);
|
||||
final boolean foundError;
|
||||
if (StringUtil.startsWith(unprefixed, TRIPLE_QUOTES)) {
|
||||
foundError = checkTripleQuotedString(stringNode, unprefixed, TRIPLE_QUOTES);
|
||||
}
|
||||
else if (StringUtil.startsWith(unprefixed, TRIPLE_APOS)) {
|
||||
foundError = checkTripleQuotedString(stringNode, unprefixed, TRIPLE_APOS);
|
||||
}
|
||||
else {
|
||||
foundError = checkQuotedString(stringNode, unprefixed);
|
||||
}
|
||||
if (foundError) {
|
||||
break;
|
||||
}
|
||||
private boolean checkQuotedString(@NotNull ASTNode stringNode, @NotNull String nodeText) {
|
||||
final char firstQuote = nodeText.charAt(0);
|
||||
final char lastChar = nodeText.charAt(nodeText.length() - 1);
|
||||
int precedingBackslashCount = 0;
|
||||
for (int i = nodeText.length() - 2; i >= 0; i--) {
|
||||
if (nodeText.charAt(i) == '\\') {
|
||||
precedingBackslashCount++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkQuotedString(@NotNull ASTNode stringNode, @NotNull String nodeText) {
|
||||
final char firstQuote = nodeText.charAt(0);
|
||||
final char lastChar = nodeText.charAt(nodeText.length() - 1);
|
||||
int precedingBackslashCount = 0;
|
||||
for (int i = nodeText.length() - 2; i >= 0; i--) {
|
||||
if (nodeText.charAt(i) == '\\') {
|
||||
precedingBackslashCount++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nodeText.length() == 1 || lastChar != firstQuote || precedingBackslashCount % 2 != 0) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.missing.closing.quote", firstQuote)).range(stringNode)
|
||||
.create();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (nodeText.length() == 1 || lastChar != firstQuote || precedingBackslashCount % 2 != 0) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.missing.closing.quote", firstQuote)).range(stringNode)
|
||||
.create();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkTripleQuotedString(@NotNull ASTNode stringNode, @NotNull String text, @NotNull String quotes) {
|
||||
if (text.length() < 6 || !text.endsWith(quotes)) {
|
||||
int startOffset = StringUtil.trimTrailing(stringNode.getText()).lastIndexOf('\n');
|
||||
if (startOffset < 0) {
|
||||
startOffset = stringNode.getTextRange().getStartOffset();
|
||||
}
|
||||
else {
|
||||
startOffset = stringNode.getTextRange().getStartOffset() + startOffset + 1;
|
||||
}
|
||||
final TextRange highlightRange = new TextRange(startOffset, stringNode.getTextRange().getEndOffset());
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.missing.closing.triple.quotes")).range(highlightRange)
|
||||
.create();
|
||||
return true;
|
||||
private boolean checkTripleQuotedString(@NotNull ASTNode stringNode, @NotNull String text, @NotNull String quotes) {
|
||||
if (text.length() < 6 || !text.endsWith(quotes)) {
|
||||
int startOffset = StringUtil.trimTrailing(stringNode.getText()).lastIndexOf('\n');
|
||||
if (startOffset < 0) {
|
||||
startOffset = stringNode.getTextRange().getStartOffset();
|
||||
}
|
||||
return false;
|
||||
else {
|
||||
startOffset = stringNode.getTextRange().getStartOffset() + startOffset + 1;
|
||||
}
|
||||
final TextRange highlightRange = new TextRange(startOffset, stringNode.getTextRange().getEndOffset());
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.missing.closing.triple.quotes")).range(highlightRange)
|
||||
.create();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,14 @@ public final class PySyntaxAnnotator extends PyAnnotatorBase implements DumbAwar
|
||||
new PyGlobalAnnotatorVisitor(holder),
|
||||
new PyImportAnnotatorVisitor(holder),
|
||||
new PyAsyncAwaitAnnotatorVisitor(holder),
|
||||
new PyAstNumericLiteralAnnotatorVisitor(holder)
|
||||
new PyAstNumericLiteralAnnotatorVisitor(holder),
|
||||
new PyGeneratorInArgumentListAnnotator(holder),
|
||||
new PyStarAnnotator(holder),
|
||||
new PyStringLiteralQuotesAnnotator(holder),
|
||||
new PyFStringsAnnotator(holder),
|
||||
new PyPatternAnnotator(holder),
|
||||
new PyTryExceptAnnotator(holder),
|
||||
new PyTypeParameterListAnnotator(holder)
|
||||
);
|
||||
for (PsiElementVisitor visitor : visitors) {
|
||||
psiElement.accept(visitor);
|
||||
|
||||
+89
-96
@@ -11,116 +11,109 @@ import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class PyTryExceptAnnotator extends PyAnnotatorBase {
|
||||
final class PyTryExceptAnnotator extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
|
||||
PyTryExceptAnnotator(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
|
||||
@Override
|
||||
public void annotate(@NotNull PsiElement element, @NotNull PyAnnotationHolder holder) {
|
||||
element.accept(new MyVisitor(holder));
|
||||
public void visitPyTryExceptStatement(@NotNull PyTryExceptStatement node) {
|
||||
boolean haveDefaultExcept = false;
|
||||
for (PyExceptPart part : node.getExceptParts()) {
|
||||
if (haveDefaultExcept) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.default.except.must.be.last")).range(part).create();
|
||||
}
|
||||
if (part.getExceptClass() == null) {
|
||||
haveDefaultExcept = true;
|
||||
}
|
||||
}
|
||||
|
||||
boolean haveStar = false;
|
||||
boolean haveNotStar = false;
|
||||
for (PyExceptPart exceptPart : node.getExceptParts()) {
|
||||
var star = PyPsiUtils.getFirstChildOfType(exceptPart, PyTokenTypes.MULT);
|
||||
if (star != null) {
|
||||
haveStar = true;
|
||||
}
|
||||
else {
|
||||
haveNotStar = true;
|
||||
}
|
||||
if (haveNotStar && haveStar) {
|
||||
var exceptKeyword = exceptPart.getFirstChild();
|
||||
int startOffset = exceptKeyword.getTextOffset();
|
||||
int endOffset = exceptKeyword.getTextRange().getEndOffset();
|
||||
if (star != null) {
|
||||
endOffset = star.getTextRange().getEndOffset();
|
||||
}
|
||||
var textRange = new TextRange(startOffset, endOffset);
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.try.except.can.not.have.except.and.star.except"))
|
||||
.range(textRange).create();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyVisitor extends PyElementVisitor {
|
||||
private final @NotNull PyAnnotationHolder myHolder;
|
||||
@Override
|
||||
public void visitPyRaiseStatement(@NotNull PyRaiseStatement node) {
|
||||
if (node.getExpressions().length == 0 &&
|
||||
PsiTreeUtil.getParentOfType(node, PyExceptPart.class, PyFinallyPart.class, PyFunction.class) == null) {
|
||||
myHolder.markError(node, PyPsiBundle.message("ANN.no.exception.to.reraise"));
|
||||
}
|
||||
}
|
||||
|
||||
private MyVisitor(@NotNull PyAnnotationHolder holder) { myHolder = holder; }
|
||||
|
||||
@Override
|
||||
public void visitPyTryExceptStatement(@NotNull PyTryExceptStatement node) {
|
||||
boolean haveDefaultExcept = false;
|
||||
for (PyExceptPart part : node.getExceptParts()) {
|
||||
if (haveDefaultExcept) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.default.except.must.be.last")).range(part).create();
|
||||
}
|
||||
if (part.getExceptClass() == null) {
|
||||
haveDefaultExcept = true;
|
||||
}
|
||||
}
|
||||
|
||||
boolean haveStar = false;
|
||||
boolean haveNotStar = false;
|
||||
for (PyExceptPart exceptPart : node.getExceptParts()) {
|
||||
var star = PyPsiUtils.getFirstChildOfType(exceptPart, PyTokenTypes.MULT);
|
||||
if (star != null) {
|
||||
haveStar = true;
|
||||
}
|
||||
else {
|
||||
haveNotStar = true;
|
||||
}
|
||||
if (haveNotStar && haveStar) {
|
||||
var exceptKeyword = exceptPart.getFirstChild();
|
||||
int startOffset = exceptKeyword.getTextOffset();
|
||||
int endOffset = exceptKeyword.getTextRange().getEndOffset();
|
||||
if (star != null) {
|
||||
endOffset = star.getTextRange().getEndOffset();
|
||||
}
|
||||
var textRange = new TextRange(startOffset, endOffset);
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.try.except.can.not.have.except.and.star.except"))
|
||||
.range(textRange).create();
|
||||
break;
|
||||
private static @Nullable PyReferenceExpression tryGetExceptionGroupInExpression(@Nullable PsiElement exceptExpression) {
|
||||
if (exceptExpression instanceof PyReferenceExpression &&
|
||||
"ExceptionGroup".equals(((PyReferenceExpression)exceptExpression).getName())) {
|
||||
return (PyReferenceExpression)exceptExpression;
|
||||
}
|
||||
if (exceptExpression instanceof PyParenthesizedExpression) {
|
||||
return tryGetExceptionGroupInExpression(PyPsiUtils.flattenParens((PyParenthesizedExpression)exceptExpression));
|
||||
}
|
||||
if (exceptExpression instanceof PyTupleExpression) {
|
||||
for (PsiElement child : exceptExpression.getChildren()) {
|
||||
var result = tryGetExceptionGroupInExpression(child);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyRaiseStatement(@NotNull PyRaiseStatement node) {
|
||||
if (node.getExpressions().length == 0 &&
|
||||
PsiTreeUtil.getParentOfType(node, PyExceptPart.class, PyFinallyPart.class, PyFunction.class) == null) {
|
||||
myHolder.markError(node, PyPsiBundle.message("ANN.no.exception.to.reraise"));
|
||||
}
|
||||
@Override
|
||||
public void visitPyExceptBlock(@NotNull PyExceptPart node) {
|
||||
if (!node.isStar()) return;
|
||||
|
||||
var exceptClass = node.getExceptClass();
|
||||
var exceptionGroup = tryGetExceptionGroupInExpression(exceptClass);
|
||||
if (exceptionGroup != null) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.exception.group.in.star.except")).range(exceptionGroup)
|
||||
.create();
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable PyReferenceExpression tryGetExceptionGroupInExpression(@Nullable PsiElement exceptExpression) {
|
||||
if (exceptExpression instanceof PyReferenceExpression &&
|
||||
"ExceptionGroup".equals(((PyReferenceExpression)exceptExpression).getName())) {
|
||||
return (PyReferenceExpression)exceptExpression;
|
||||
}
|
||||
if (exceptExpression instanceof PyParenthesizedExpression) {
|
||||
return tryGetExceptionGroupInExpression(PyPsiUtils.flattenParens((PyParenthesizedExpression)exceptExpression));
|
||||
}
|
||||
if (exceptExpression instanceof PyTupleExpression) {
|
||||
for (PsiElement child : exceptExpression.getChildren()) {
|
||||
var result = tryGetExceptionGroupInExpression(child);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@Override
|
||||
public void visitPyReturnStatement(@NotNull PyReturnStatement node) {
|
||||
PyExceptPart exceptPart = PsiTreeUtil.getParentOfType(node, PyExceptPart.class, false, PyFunction.class);
|
||||
if (exceptPart != null && exceptPart.isStar()) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.continue.break.or.return.in.star.except")).create();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyExceptBlock(@NotNull PyExceptPart node) {
|
||||
if (!node.isStar()) return;
|
||||
|
||||
var exceptClass = node.getExceptClass();
|
||||
var exceptionGroup = tryGetExceptionGroupInExpression(exceptClass);
|
||||
if (exceptionGroup != null) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.exception.group.in.star.except")).range(exceptionGroup)
|
||||
.create();
|
||||
}
|
||||
private void checkForContinueAndReturn(@NotNull PsiElement node) {
|
||||
PyExceptPart exceptPart = PsiTreeUtil.getParentOfType(node, PyExceptPart.class, false, PyLoopStatement.class);
|
||||
if (exceptPart != null && exceptPart.isStar()) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.continue.break.or.return.in.star.except")).create();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyReturnStatement(@NotNull PyReturnStatement node) {
|
||||
PyExceptPart exceptPart = PsiTreeUtil.getParentOfType(node, PyExceptPart.class, false, PyFunction.class);
|
||||
if (exceptPart != null && exceptPart.isStar()) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.continue.break.or.return.in.star.except")).create();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void visitPyContinueStatement(@NotNull PyContinueStatement node) {
|
||||
checkForContinueAndReturn(node);
|
||||
}
|
||||
|
||||
private void checkForContinueAndReturn(@NotNull PsiElement node) {
|
||||
PyExceptPart exceptPart = PsiTreeUtil.getParentOfType(node, PyExceptPart.class, false, PyLoopStatement.class);
|
||||
if (exceptPart != null && exceptPart.isStar()) {
|
||||
myHolder.newAnnotation(HighlightSeverity.ERROR, PyPsiBundle.message("ANN.continue.break.or.return.in.star.except")).create();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyContinueStatement(@NotNull PyContinueStatement node) {
|
||||
checkForContinueAndReturn(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyBreakStatement(@NotNull PyBreakStatement node) {
|
||||
checkForContinueAndReturn(node);
|
||||
}
|
||||
@Override
|
||||
public void visitPyBreakStatement(@NotNull PyBreakStatement node) {
|
||||
checkForContinueAndReturn(node);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-8
@@ -1,7 +1,6 @@
|
||||
package com.jetbrains.python.validation
|
||||
|
||||
import com.intellij.lang.annotation.HighlightSeverity
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.jetbrains.python.PyPsiBundle
|
||||
import com.jetbrains.python.ast.PyAstTypeParameter.Kind.TypeVar
|
||||
import com.jetbrains.python.psi.PyElementVisitor
|
||||
@@ -10,13 +9,7 @@ import com.jetbrains.python.psi.PyTypeParameter
|
||||
import com.jetbrains.python.psi.PyTypeParameterList
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils
|
||||
|
||||
class PyTypeParameterListAnnotator : PyAnnotatorBase() {
|
||||
override fun annotate(element: PsiElement, holder: PyAnnotationHolder) {
|
||||
element.accept(PyTypeParameterListAnnotatorVisitor(holder))
|
||||
}
|
||||
}
|
||||
|
||||
private class PyTypeParameterListAnnotatorVisitor(private val holder: PyAnnotationHolder) : PyElementVisitor() {
|
||||
class PyTypeParameterListAnnotator(private val holder: PyAnnotationHolder) : PyElementVisitor() {
|
||||
override fun visitPyTypeParameterList(node: PyTypeParameterList) {
|
||||
if (!node.typeParameters.isEmpty()) {
|
||||
val namesSet = mutableSetOf<String>()
|
||||
|
||||
Reference in New Issue
Block a user