Coverage for patterns / python.py: 86%
42 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
1"""Pattern detection for Python source files."""
3from __future__ import annotations
5from models import PatternViolation
6from patterns.common import create_query, run_captures
7from patterns.shared import check_patterns as check_patterns_shared
9_PY_NESTED_IMPORT_QUERY = """
10(block (import_statement) @nested_import)
11(block (import_from_statement) @nested_import)
12"""
14_PY_WILDCARD_IMPORT_QUERY = "(import_from_statement (wildcard_import) @wildcard)"
17def _is_type_checking_import(node) -> bool:
18 """Check if import is inside a 'if TYPE_CHECKING:' block."""
19 parent = node.parent
20 while parent:
21 if parent.type == "if_statement":
22 for child in parent.children:
23 if child.type in ("identifier", "expression_statement"):
24 text = (
25 child.text
26 if child.type == "identifier"
27 else child.text.decode()
28 if child.text
29 else ""
30 )
31 if text == b"TYPE_CHECKING" or "TYPE_CHECKING" in str(text):
32 return True
33 if parent.type == "module":
34 break
35 parent = parent.parent
36 return False
39def check_nested_imports(root) -> list[PatternViolation]:
40 """Detect import statements not at module level (inside functions/classes)."""
41 from parsers.python import PY_LANGUAGE
43 query = create_query(PY_LANGUAGE, _PY_NESTED_IMPORT_QUERY)
44 captures = run_captures(query, root)
46 violations = []
47 for node, capture_name in captures:
48 if _is_type_checking_import(node):
49 continue
50 line = node.start_point[0] + 1
51 violations.append(
52 PatternViolation(
53 type="nested_import",
54 line=line,
55 description="Import statement inside function/class body instead of module level",
56 severity="warning",
57 )
58 )
60 return violations
63def check_wildcard_imports(root) -> list[PatternViolation]:
64 """Detect wildcard imports like 'from module import *'."""
65 from parsers.python import PY_LANGUAGE
67 query = create_query(PY_LANGUAGE, _PY_WILDCARD_IMPORT_QUERY)
68 captures = run_captures(query, root)
70 violations = []
71 for node, capture_name in captures:
72 if capture_name == "wildcard":
73 line = node.start_point[0] + 1
74 violations.append(
75 PatternViolation(
76 type="wildcard_import",
77 line=line,
78 description="Wildcard import 'from module import *' - explicit imports preferred",
79 severity="warning",
80 )
81 )
83 return violations
86def check_patterns(root, patterns: list[str] | None = None) -> list[PatternViolation]:
87 """Run pattern checks for Python files."""
88 return check_patterns_shared("python", root, patterns)