| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """PostToolUse hook (GLOBAL): 写源码后扫描 SELECT * 裸查询。
- 对齐 Rules §10 性能禁则(禁止 SELECT *,须显式列名)。仅对文本源文件生效。
- 非阻断 exit 1 回显 AI。SELECT 与 * 跨行(\\s+)亦可识别;count(*) 不含 'select *' 不误报。
- """
- import sys
- import os
- import json
- import re
- TEXT_EXT = {".php", ".html", ".htm", ".js", ".css", ".py", ".sql", ".inc"}
- SELECT_STAR = re.compile(r"select\s+\*", re.IGNORECASE)
- def find_select_star_lines(text):
- """逐行扫描,跳过 PHP/JS/CSS 注释(//、#、/* */),返回命中行号。
- 真实 SQL 多位于字符串字面量所在代码行,仍会被捕获;仅排除注释行,
- 以降低说明性注释里出现 select * 的误报。
- """
- hits = []
- in_block = False
- for i, line in enumerate(text.splitlines(), start=1):
- s = line.strip()
- if in_block:
- if "*/" in line:
- in_block = False
- continue
- if "/*" in line:
- if "*/" in line:
- continue # 同行闭合块注释,整行视为注释
- in_block = True
- continue
- if s.startswith("//") or s.startswith("#"):
- continue
- if SELECT_STAR.search(line):
- hits.append(i)
- return hits
- def main():
- try:
- raw = sys.stdin.read()
- if not raw.strip():
- return 0
- data = json.loads(raw)
- except Exception:
- return 0
- tool_input = data.get("tool_input", {}) or {}
- path = (
- tool_input.get("filePath")
- or tool_input.get("file_path")
- or tool_input.get("path")
- or ""
- )
- if not path:
- return 0
- path = os.path.abspath(path)
- if os.path.splitext(path)[1].lower() not in TEXT_EXT:
- return 0
- if not os.path.isfile(path):
- return 0
- try:
- with open(path, "r", encoding="utf-8", errors="ignore") as f:
- text = f.read()
- except Exception:
- return 0
- hits = find_select_star_lines(text)
- if hits:
- print("[SQL-WARN] 发现 SELECT * 裸查询,请改为显式列名(Rules §10 性能禁则):")
- for line in hits:
- print(" 行 %d" % line)
- return 1
- return 0
- if __name__ == "__main__":
- sys.exit(main())
|