from pathlib import Path import ast import json import os import sys if __package__ in {None, ""}: sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from scripts.run_secret_static_checks import run_secret_static_checks ROOT = Path(__file__).resolve().parents[1] SCRIPT_DIR = ROOT / "scripts" FORBIDDEN_PATTERNS = ( "env " + "= env or " + "os.environ", "env or " + "os.environ", ) def run_script_static_checks(env=None, script_dir: Path = SCRIPT_DIR) -> dict: env = os.environ if env is None else env findings = [] for path in sorted(script_dir.glob("*.py")): findings.extend(check_script_file(path)) findings.extend(run_secret_static_checks(env=env)["findings"]) return { "passed": len(findings) == 0, "script_count": len(list(script_dir.glob("*.py"))), "findings": findings, } def check_script_file(path: Path) -> list[dict]: findings = [] source = path.read_text(encoding="utf-8") tree = None try: tree = ast.parse(source, filename=str(path)) except SyntaxError as exc: findings.append( { "file": display_path(path), "line": exc.lineno or 1, "pattern": "python_syntax", "reason": "script_python_syntax_error", } ) if tree is not None: findings.extend(find_python39_incompatible_annotations(tree, path)) for line_number, line in enumerate(source.splitlines(), start=1): for pattern in FORBIDDEN_PATTERNS: if pattern in line: findings.append( { "file": display_path(path), "line": line_number, "pattern": pattern, "reason": forbidden_pattern_reason(pattern), } ) if tree is not None and has_main_function(tree) and "raise SystemExit(main(" not in source: findings.append( { "file": display_path(path), "line": 1, "pattern": "raise SystemExit(main())", "reason": "automation_entrypoint_must_preserve_exit_code", } ) return findings def forbidden_pattern_reason(pattern: str) -> str: return "empty_env_must_not_fall_back_to_ambient_environment" def has_main_function(tree: ast.AST) -> bool: return any(isinstance(node, ast.FunctionDef) and node.name == "main" for node in ast.walk(tree)) def find_python39_incompatible_annotations(tree: ast.AST, path: Path) -> list[dict]: findings = [] def inspect_annotation(annotation) -> None: if annotation is not None and has_pep604_optional_annotation(annotation): findings.append( { "file": display_path(path), "line": getattr(annotation, "lineno", 1), "pattern": "PEP604 Optional", "reason": "python39_incompatible_pep604_optional_annotation", } ) for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): inspect_annotation(node.returns) for arg in list(node.args.args) + list(node.args.kwonlyargs) + list(node.args.posonlyargs): inspect_annotation(arg.annotation) if node.args.vararg: inspect_annotation(node.args.vararg.annotation) if node.args.kwarg: inspect_annotation(node.args.kwarg.annotation) elif isinstance(node, ast.AnnAssign): inspect_annotation(node.annotation) return findings def has_pep604_optional_annotation(annotation) -> bool: if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr): return is_none_annotation(annotation.left) or is_none_annotation(annotation.right) return any(has_pep604_optional_annotation(child) for child in ast.iter_child_nodes(annotation)) def is_none_annotation(node) -> bool: return isinstance(node, ast.Constant) and node.value is None def display_path(path: Path) -> str: try: return str(path.relative_to(ROOT)) except ValueError: return path.name def main(env=None) -> int: env = os.environ if env is None else env result = run_script_static_checks(env=env) if _get_bool(env, "SCRIPT_CHECKS_OUTPUT_JSON", False): print(json.dumps(result, ensure_ascii=False, sort_keys=True)) else: print(f"Script static checks passed: {result['passed']}") print(f"Script files checked: {result['script_count']}") for finding in result["findings"]: print( f"{finding['file']}:{finding['line']} " f"{finding['reason']} pattern={finding['pattern']}" ) return 0 if result["passed"] else 2 def _get_bool(env, key: str, default: bool) -> bool: value = env.get(key) if value is None or value == "": return default return str(value).strip().lower() in {"1", "true", "yes", "on"} if __name__ == "__main__": raise SystemExit(main())