post-edit-format.sh 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. #!/bin/bash
  2. # hooks/post-edit-format.sh
  3. # PostToolUse hook - auto-formats files after Write or Edit operations
  4. # Matcher: Write|Edit
  5. #
  6. # Configuration in .claude/settings.json:
  7. # {
  8. # "hooks": {
  9. # "PostToolUse": [{
  10. # "matcher": "Write|Edit",
  11. # "hooks": ["bash hooks/post-edit-format.sh $FILE_PATH"]
  12. # }]
  13. # }
  14. # }
  15. FILE="$1"
  16. # Skip if no file path provided
  17. if [[ -z "$FILE" || ! -f "$FILE" ]]; then
  18. exit 0
  19. fi
  20. EXT="${FILE##*.}"
  21. FORMATTED=false
  22. format_js_ts() {
  23. if command -v npx &>/dev/null; then
  24. if [[ -f "node_modules/.bin/prettier" ]]; then
  25. npx prettier --write "$FILE" 2>/dev/null && FORMATTED=true
  26. return 0
  27. elif [[ -f "node_modules/.bin/biome" ]]; then
  28. npx biome format --write "$FILE" 2>/dev/null && FORMATTED=true
  29. return 0
  30. fi
  31. fi
  32. # Fallback: dprint if available
  33. if command -v dprint &>/dev/null; then
  34. dprint fmt "$FILE" 2>/dev/null && FORMATTED=true
  35. fi
  36. }
  37. format_python() {
  38. if command -v ruff &>/dev/null; then
  39. ruff format "$FILE" 2>/dev/null && FORMATTED=true
  40. elif command -v black &>/dev/null; then
  41. black --quiet "$FILE" 2>/dev/null && FORMATTED=true
  42. fi
  43. # Also fix import sorting
  44. if command -v ruff &>/dev/null; then
  45. ruff check --fix --select I "$FILE" 2>/dev/null
  46. elif command -v isort &>/dev/null; then
  47. isort --quiet "$FILE" 2>/dev/null
  48. fi
  49. }
  50. format_go() {
  51. if command -v goimports &>/dev/null; then
  52. goimports -w "$FILE" 2>/dev/null && FORMATTED=true
  53. elif command -v gofmt &>/dev/null; then
  54. gofmt -w "$FILE" 2>/dev/null && FORMATTED=true
  55. fi
  56. }
  57. format_rust() {
  58. if command -v rustfmt &>/dev/null; then
  59. rustfmt "$FILE" 2>/dev/null && FORMATTED=true
  60. fi
  61. }
  62. format_php() {
  63. if [[ -f "vendor/bin/pint" ]]; then
  64. ./vendor/bin/pint "$FILE" 2>/dev/null && FORMATTED=true
  65. elif command -v php-cs-fixer &>/dev/null; then
  66. php-cs-fixer fix "$FILE" --quiet 2>/dev/null && FORMATTED=true
  67. fi
  68. }
  69. format_css() {
  70. if command -v npx &>/dev/null && [[ -f "node_modules/.bin/prettier" ]]; then
  71. npx prettier --write "$FILE" 2>/dev/null && FORMATTED=true
  72. fi
  73. }
  74. format_json_yaml() {
  75. if command -v npx &>/dev/null && [[ -f "node_modules/.bin/prettier" ]]; then
  76. npx prettier --write "$FILE" 2>/dev/null && FORMATTED=true
  77. elif command -v dprint &>/dev/null; then
  78. dprint fmt "$FILE" 2>/dev/null && FORMATTED=true
  79. fi
  80. }
  81. case "$EXT" in
  82. ts|tsx|js|jsx|mjs|cjs)
  83. format_js_ts
  84. ;;
  85. py|pyi)
  86. format_python
  87. ;;
  88. go)
  89. format_go
  90. ;;
  91. rs)
  92. format_rust
  93. ;;
  94. php)
  95. format_php
  96. ;;
  97. css|scss|less)
  98. format_css
  99. ;;
  100. json|yaml|yml)
  101. format_json_yaml
  102. ;;
  103. md)
  104. # Markdown formatting is optional and sometimes unwanted
  105. # Uncomment to enable:
  106. # if command -v npx &>/dev/null && [[ -f "node_modules/.bin/prettier" ]]; then
  107. # npx prettier --write --prose-wrap preserve "$FILE" 2>/dev/null && FORMATTED=true
  108. # fi
  109. ;;
  110. esac
  111. # Silent success - only output on format to keep context clean
  112. if [[ "$FORMATTED" == "true" ]]; then
  113. echo "Formatted: $(basename "$FILE")"
  114. fi
  115. exit 0