"""静态检查(无第三方 linter 时的替代):未使用导入、裸 except、过长函数。 用法:``python dev/lint_scan.py`` `from __future__ import annotations` 会被误判(它确实没有显式引用),已忽略。 """ import ast import os ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TARGETS = [os.path.join(ROOT, "recorder"), ROOT] IGNORE = {"annotations"} MAX_FUNC_LINES = 130 issues: list[str] = [] for target in TARGETS: files = ([os.path.join(target, f) for f in sorted(os.listdir(target)) if f.endswith(".py")] if os.path.isdir(target) else [target]) for path in files: rel = os.path.relpath(path, ROOT) with open(path, encoding="utf-8") as fh: src = fh.read() lines = src.splitlines() tree = ast.parse(src, path) def has_noqa(lineno: int) -> bool: return 0 < lineno <= len(lines) and "noqa" in lines[lineno - 1] imported: dict[str, int] = {} for node in ast.walk(tree): if isinstance(node, ast.Import): for a in node.names: imported[(a.asname or a.name).split(".")[0]] = node.lineno elif isinstance(node, ast.ImportFrom): for a in node.names: if a.name != "*": imported[a.asname or a.name] = node.lineno used = {n.id for n in ast.walk(tree) if isinstance(n, ast.Name)} for name, line in sorted(imported.items(), key=lambda kv: kv[1]): if name in used or name in IGNORE or has_noqa(line): continue if f'"{name}"' in src or f"'{name}'" in src: continue issues.append(f"{rel}:{line} 未使用的导入 {name}") for node in ast.walk(tree): if isinstance(node, ast.ExceptHandler) and node.type is None \ and not has_noqa(node.lineno): issues.append(f"{rel}:{node.lineno} 裸 except") if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): length = (node.end_lineno or node.lineno) - node.lineno if length > MAX_FUNC_LINES and not has_noqa(node.lineno): issues.append(f"{rel}:{node.lineno} 函数 {node.name} 过长({length} 行)") print("\n".join(issues) if issues else "未发现明显问题") print(f"\n共 {len(issues)} 条提示")