check_syntax.py 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. """Check one edited repository file without executing its contents."""
  2. import json
  3. import subprocess
  4. import sys
  5. from pathlib import Path
  6. PROJECT_ROOT = Path(__file__).resolve().parents[2]
  7. CHECK_TIMEOUT = 10
  8. def check_file(file_path: str, project_dir: Path) -> None:
  9. project_dir = project_dir.resolve()
  10. path = Path(file_path)
  11. if not path.is_absolute():
  12. path = project_dir / path
  13. path = path.resolve()
  14. try:
  15. relative = path.relative_to(project_dir)
  16. except ValueError:
  17. return
  18. if path.suffix == ".py":
  19. compile(path.read_bytes(), str(path), "exec")
  20. return
  21. if path.suffix == ".sh" and relative.parts[:2] == ("scripts", "server"):
  22. command = ["bash", "-n", str(path)]
  23. elif path.suffix == ".sh" and (
  24. relative.parts[:2] == ("scripts", "router")
  25. or relative == Path("build_router_package.sh")
  26. ):
  27. command = ["sh", "-n", str(path)]
  28. elif path.suffix == ".js" and relative.parts[:1] == ("substore",):
  29. command = ["node", "--check", str(path)]
  30. else:
  31. return
  32. result = subprocess.run(
  33. command,
  34. cwd=str(project_dir),
  35. capture_output=True,
  36. text=True,
  37. timeout=CHECK_TIMEOUT,
  38. )
  39. if result.returncode:
  40. detail = result.stderr.strip() or result.stdout.strip()
  41. raise RuntimeError(detail or f"{command[0]} exited with status {result.returncode}")
  42. def main() -> int:
  43. try:
  44. payload = json.load(sys.stdin)
  45. if not isinstance(payload, dict):
  46. raise ValueError("Hook payload must be a JSON object")
  47. if payload.get("tool_name") not in ("Write", "Edit"):
  48. return 0
  49. tool_input = payload.get("tool_input")
  50. if not isinstance(tool_input, dict):
  51. raise ValueError("Write/Edit payload must contain tool_input")
  52. file_path = tool_input.get("file_path")
  53. if not isinstance(file_path, str) or not file_path:
  54. raise ValueError("Write/Edit payload must contain a nonempty file_path")
  55. check_file(file_path, PROJECT_ROOT)
  56. except (OSError, SyntaxError, ValueError, RuntimeError, subprocess.TimeoutExpired) as exc:
  57. print(f"[syntax-check] {exc}", file=sys.stderr)
  58. return 2
  59. return 0
  60. if __name__ == "__main__":
  61. sys.exit(main())