#!/usr/bin/env python3 """ Heuristic static checks aligned with common 阿里巴巴手册 themes (not full P3C). Outputs markdown to stdout or --out file. """ from __future__ import annotations import argparse import re import sys from pathlib import Path TAB = re.compile(r"^\t+") TRAIL = re.compile(r"[ \t]+$") LONG_LINE = re.compile(r".{121,}") SYSTEM_OUT = re.compile(r"\bSystem\.(out|err)\.(print|println)\s*\(") EMPTY_CATCH = re.compile(r"catch\s*\([^)]+\)\s*\{\s*\}", re.MULTILINE) CATCH_EXCEPTION_PASS = re.compile( r"catch\s*\(\s*Exception\s+\w+\s*\)\s*\{\s*\}", re.MULTILINE ) TODO_FXXX = re.compile(r"\b(FIXME|XXX)\b", re.IGNORECASE) def scan_file(path: Path, rel: str) -> list[tuple[str, int, str]]: issues: list[tuple[str, int, str]] = [] try: text = path.read_text(encoding="utf-8", errors="replace") except OSError as e: return [("ERROR", 0, f"{rel}: {e}")] lines = text.splitlines() for i, line in enumerate(lines, 1): if TAB.search(line): issues.append(("FORMAT", i, "行首含 TAB,建议仅用空格缩进(手册排版/可读)")) if TRAIL.search(line): issues.append(("FORMAT", i, "行尾多余空白")) if LONG_LINE.search(line): issues.append(("FORMAT", i, "行宽超过约 120 字符,建议换行(团队规约常见上限)")) if SYSTEM_OUT.search(line) and "/test/" not in str(path).replace("\\", "/"): issues.append(("STYLE", i, "使用 System.out/err,生产代码建议改用日志框架")) if TODO_FXXX.search(line): issues.append(("STYLE", i, "含 FIXME/XXX 标记,发版前应清理或跟踪")) body = "\n".join(lines) if EMPTY_CATCH.search(body): issues.append(("RISK", 0, "存在空 catch 块 {},手册建议至少记录或处理异常")) if CATCH_EXCEPTION_PASS.search(body): issues.append( ("RISK", 0, "存在 catch (Exception) { } 空实现,建议细化异常类型或记录日志") ) return issues def main() -> int: ap = argparse.ArgumentParser() ap.add_argument( "--repo", type=Path, default=Path(__file__).resolve().parent.parent, help="Repo root containing maven-*", ) ap.add_argument("--out", type=Path, default=None, help="Write markdown report") args = ap.parse_args() repo: Path = args.repo roots = sorted(p for p in repo.glob("maven-*") if p.is_dir()) lines_out: list[str] = [] lines_out.append("# 阿里巴巴手册相关:启发式静态扫描\n") lines_out.append( "> 非 P3C 全量规则;仅覆盖 **TAB/行尾空白/超长行/System.out/空 catch/FIXME** 等易自动化项。\n" ) grand = 0 for root in roots: name = root.name lines_out.append(f"\n## `{name}`\n") mod_issues = 0 for java in sorted(root.rglob("*.java")): sp = str(java).replace("\\", "/") if "/target/" in sp: continue rel = java.relative_to(repo) found = scan_file(java, str(rel)) if not found: continue mod_issues += len(found) lines_out.append(f"\n### `{rel}`\n") lines_out.append("| 级别 | 行 | 说明 |\n|------|----|------|\n") for sev, ln, msg in found[:30]: lines_out.append(f"| {sev} | {ln or '-'} | {msg} |\n") if len(found) > 30: lines_out.append(f"\n… 另有 {len(found) - 30} 条(同文件省略)\n") if mod_issues == 0: lines_out.append("\n(本模块未发现上述启发式命中项)\n") grand += mod_issues lines_out.append(f"\n---\n\n**启发式命中条数(含重复行规则)**:{grand}\n") text = "".join(lines_out) if args.out: args.out.parent.mkdir(parents=True, exist_ok=True) args.out.write_text(text, encoding="utf-8") print(args.out, file=sys.stderr) else: sys.stdout.write(text) return 0 if __name__ == "__main__": sys.exit(main())