diff --git a/python/python-psi-impl/resources/intellij.python.psi.impl.xml b/python/python-psi-impl/resources/intellij.python.psi.impl.xml index 9607e1d50e04..bf210ecaf980 100644 --- a/python/python-psi-impl/resources/intellij.python.psi.impl.xml +++ b/python/python-psi-impl/resources/intellij.python.psi.impl.xml @@ -170,14 +170,7 @@ - - - - - - - diff --git a/python/python-psi-impl/src/com/jetbrains/python/validation/PyFStringsAnnotator.java b/python/python-psi-impl/src/com/jetbrains/python/validation/PyFStringsAnnotator.java index 23ce375ed59e..90ef8b4fad4a 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/validation/PyFStringsAnnotator.java +++ b/python/python-psi-impl/src/com/jetbrains/python/validation/PyFStringsAnnotator.java @@ -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(); + } +} \ No newline at end of file diff --git a/python/python-psi-impl/src/com/jetbrains/python/validation/PyGeneratorInArgumentListAnnotator.java b/python/python-psi-impl/src/com/jetbrains/python/validation/PyGeneratorInArgumentListAnnotator.java index 9e55abd63be3..0e20528d943b 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/validation/PyGeneratorInArgumentListAnnotator.java +++ b/python/python-psi-impl/src/com/jetbrains/python/validation/PyGeneratorInArgumentListAnnotator.java @@ -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")); } } } diff --git a/python/python-psi-impl/src/com/jetbrains/python/validation/PyPatternAnnotator.java b/python/python-psi-impl/src/com/jetbrains/python/validation/PyPatternAnnotator.java index 9389d96c4655..84cc1e6a0e3a 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/validation/PyPatternAnnotator.java +++ b/python/python-psi-impl/src/com/jetbrains/python/validation/PyPatternAnnotator.java @@ -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 alternatives = orPattern.getAlternatives(); + PyPattern lastAlternative = alternatives.get(alternatives.size() - 1); + Map> patternToBoundNames = new HashMap<>(); + Set allBoundNames = new HashSet<>(); + + for (PyPattern alternative : alternatives) { + Set 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> entry : patternToBoundNames.entrySet()) { + Set boundNames = entry.getValue(); + if (!boundNames.equals(allBoundNames)) { + Collection 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 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 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 alternatives = orPattern.getAlternatives(); - PyPattern lastAlternative = alternatives.get(alternatives.size() - 1); - Map> patternToBoundNames = new HashMap<>(); - Set allBoundNames = new HashSet<>(); + @Override + public void visitPyCaseClause(@NotNull PyCaseClause caseClause) { + PyPattern pattern = caseClause.getPattern(); + if (pattern == null) return; - for (PyPattern alternative : alternatives) { - Set boundNames = SyntaxTraverser.psiTraverser(alternative) - .filter(PyTargetExpression.class) - .map(PyTargetExpression::getName) - .toSet(); - patternToBoundNames.put(alternative, boundNames); - allBoundNames.addAll(boundNames); + Stack> 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 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> entry : patternToBoundNames.entrySet()) { - Set boundNames = entry.getValue(); - if (!boundNames.equals(allBoundNames)) { - Collection 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 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 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 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 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> 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 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 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 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; } } diff --git a/python/python-psi-impl/src/com/jetbrains/python/validation/PyStarAnnotator.java b/python/python-psi-impl/src/com/jetbrains/python/validation/PyStarAnnotator.java index 62a1f1620ecc..5fe37e6c6c44 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/validation/PyStarAnnotator.java +++ b/python/python-psi-impl/src/com/jetbrains/python/validation/PyStarAnnotator.java @@ -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) { diff --git a/python/python-psi-impl/src/com/jetbrains/python/validation/PyStringLiteralQuotesAnnotator.java b/python/python-psi-impl/src/com/jetbrains/python/validation/PyStringLiteralQuotesAnnotator.java index 1c0fa848375e..6c7b2eb25894 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/validation/PyStringLiteralQuotesAnnotator.java +++ b/python/python-psi-impl/src/com/jetbrains/python/validation/PyStringLiteralQuotesAnnotator.java @@ -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 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 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; } } \ No newline at end of file diff --git a/python/python-psi-impl/src/com/jetbrains/python/validation/PySyntaxAnnotator.java b/python/python-psi-impl/src/com/jetbrains/python/validation/PySyntaxAnnotator.java index 43bf80116fcf..7eb2957b16a4 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/validation/PySyntaxAnnotator.java +++ b/python/python-psi-impl/src/com/jetbrains/python/validation/PySyntaxAnnotator.java @@ -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); diff --git a/python/python-psi-impl/src/com/jetbrains/python/validation/PyTryExceptAnnotator.java b/python/python-psi-impl/src/com/jetbrains/python/validation/PyTryExceptAnnotator.java index 9f7bcb89a0a6..55bd91ad7e37 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/validation/PyTryExceptAnnotator.java +++ b/python/python-psi-impl/src/com/jetbrains/python/validation/PyTryExceptAnnotator.java @@ -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); } } diff --git a/python/python-psi-impl/src/com/jetbrains/python/validation/PyTypeParameterListAnnotator.kt b/python/python-psi-impl/src/com/jetbrains/python/validation/PyTypeParameterListAnnotator.kt index a150ea27b5fb..be270aad890b 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/validation/PyTypeParameterListAnnotator.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/validation/PyTypeParameterListAnnotator.kt @@ -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()