AST visitor that finds sys.path.insert() and sys.path.append() calls.
| 28 | |
| 29 | |
| 30 | class SysPathVisitor(ast.NodeVisitor): |
| 31 | """AST visitor that finds sys.path.insert() and sys.path.append() calls.""" |
| 32 | |
| 33 | def __init__(self) -> None: |
| 34 | self.violations: list[tuple[int, str]] = [] |
| 35 | |
| 36 | def visit_Call(self, node: ast.Call) -> None: # noqa: N802 |
| 37 | if ( |
| 38 | isinstance(node.func, ast.Attribute) |
| 39 | and node.func.attr in ("insert", "append") |
| 40 | and isinstance(node.func.value, ast.Attribute) |
| 41 | and node.func.value.attr == "path" |
| 42 | and isinstance(node.func.value.value, ast.Name) |
| 43 | and node.func.value.value.id == "sys" |
| 44 | ): |
| 45 | self.violations.append( |
| 46 | ( |
| 47 | node.lineno, |
| 48 | "SPI001 sys.path modification is FORBIDDEN. " |
| 49 | "Use 'uv run' to launch scripts (handles path resolution automatically). " |
| 50 | "Do NOT use bare 'python' — always 'uv run python' or 'uv run <script>'. " |
| 51 | "Remove the sys.path hack and fix imports to use proper package paths.", |
| 52 | ) |
| 53 | ) |
| 54 | self.generic_visit(node) |
| 55 | |
| 56 | |
| 57 | def check_file(path: str, source: str) -> list[tuple[int, str]]: |