Source code for rude.rules.pyflakes.annotations

"""
Annotation rules: type annotation validation.

F722: ForwardAnnotationSyntaxError - syntax error in forward annotation
"""

from __future__ import annotations

import ast
from collections.abc import Iterator
from typing import TYPE_CHECKING, ClassVar

from rude.core.node_types import NodeType
from rude.core.rule import Rule
from rude.core.types import Diagnostic, Severity
from rude.utils import extract_string_content

if TYPE_CHECKING:
    from rude.core.node import Node


[docs] class ForwardAnnotationSyntaxError(Rule): """ F722: Syntax error in forward annotation. Rationale: A forward annotation with invalid syntax will raise a ``SyntaxError`` when evaluated at runtime or by type checkers. Example:: # Bad x: "List[int" # F722 - unclosed bracket # Good x: "List[int]" """ code: ClassVar[str] = "F722" message: ClassVar[str] = "syntax error in forward annotation {annotation!r}" severity: ClassVar[Severity] = Severity.ERROR node_types = {NodeType.STRING}
[docs] def check(self, node: Node) -> Iterator[Diagnostic]: # Check if this string is in an annotation context if not self._is_annotation_context(node): return # Extract string content content = extract_string_content(node.text) if content is None: return # Try to parse as Python expression try: ast.parse(content, mode="eval") except SyntaxError: yield self.diagnostic(node, self.message.format(annotation=content))
def _is_annotation_context(self, node: Node) -> bool: """Check if this string is used as a type annotation.""" # Quick-reject: check parent type without inflating pt = node.parent_type if pt is None: return False # Direct annotation: x: "str" if pt == "type": return True # Function return type: def foo() -> "str": # The structure is: function_definition -> type -> string # But tree-sitter might wrap it differently if pt == "function_definition": parent = node.parent if parent is not None: # Check if this is the return type return_type = parent.child_by_field("return_type") if return_type and self._contains(return_type, node): return True # Check for annotation context with intermediate nodes # Sometimes the structure is: type -> concatenated_string -> string if pt == "concatenated_string": parent = node.parent if parent is not None: gp = parent.parent if gp and gp.type == "type": return True return False def _contains(self, container: Node, target: Node) -> bool: """Check if container contains target.""" if container.raw.id == target.raw.id: return True return any(self._contains(child, target) for child in container.children)
ANNOTATION_RULES = [ ForwardAnnotationSyntaxError, ] __all__ = [ "ANNOTATION_RULES", "ForwardAnnotationSyntaxError", ]