代码质量预检Hook

自动化代码质量检查与门禁

核心概念:Hooks 是 Claude Code 提供的一种扩展机制,允许在特定工具调用(如 Edit、Write、Bash)之前或之后自动执行自定义脚本。代码质量预检Hook利用这一机制,在代码修改的关键节点插入质量检查流程,形成自动化的质量保障闭环。

一、代码质量预检Hook的设计

在代码修改前后自动运行质量检查、确保代码质量和一致性

代码质量预检Hook的核心理念是在开发流程的关键节点自动触发质量检查,将问题消灭在萌芽阶段。其设计遵循"尽早发现、尽早修复"的原则,在代码修改的不同阶段设置不同的检查关卡。

实时检测
在编辑操作执行前拦截语法错误,避免无效修改写入文件
自动修正
编辑完成后自动格式化代码,保持团队风格一致
编译预检
在写入文件前运行编译检查,防止编译错误代码入库
测试验证
修改后自动运行关联测试,确保不引入回归缺陷

整体架构采用分层设计,每层对应一个 Hooks 触发器。Hooks 的执行顺序为:before:tool/Edit(语法检查)→ Edit 执行 → after:tool/Edit(格式化、测试)→ before:tool/Write(编译检查)→ Write 执行。这种链式结构确保了每项修改在写入文件系统之前都经过完整验证。

二、语法错误实时检测Hook(before:tool/Edit)

在Edit工具执行前检查修改后的代码语法、检测常见语法错误(括号不匹配/缺少分号等)、阻止包含语法错误的修改写入、提供错误位置和修复建议

语法检测是代码质量的第一道防线。通过在 before:tool/Edit 阶段注册 Hook,可以在编辑器将修改写入文件之前捕获明显的语法问题。

Hook 配置示例

{ "before:tool/Edit": { "match": "*.{js,ts,jsx,tsx}", "run": "node scripts/syntax-check.js", "timeout": 5000, "onError": "reject" } }

语法检测脚本核心逻辑

// scripts/syntax-check.js const fs = require('fs'); const path = require('path'); function checkSyntax(filePath) { const content = fs.readFileSync(filePath, 'utf-8'); const errors = []; // 括号匹配检查 const pairs = { '(': ')', '[': ']', '{': '}' }; const stack = []; for (let i = 0; i < content.length; i++) { const ch = content[i]; if (pairs[ch]) { stack.push({ char: ch, line: content.slice(0, i).split('\n').length }); } else if (Object.values(pairs).includes(ch)) { const last = stack.pop(); if (!last || pairs[last.char] !== ch) { errors.push(`第 ${content.slice(0, i).split('\n').length} 行:括号不匹配`); } } } if (stack.length > 0) { errors.push(`存在未闭合的括号,起始位置在第 ${stack[0].line} 行`); } return errors; } const fileArg = process.argv[2]; const result = checkSyntax(fileArg); if (result.length > 0) { console.error('语法错误检测失败:'); result.forEach(e => console.error(' - ' + e)); process.exit(1); }
设计要点:onError 设置为 "reject" 可以阻止本次 Edit 操作执行,这是质量门禁的基石。timeout 参数需合理设置(建议 3-5 秒),避免检查脚本执行过久影响开发体验。

除了基础的括号匹配检查,语法检测还可以扩展至:字符串引号匹配检查、模版字面量闭合检查、正则表达式语法有效性检查等。对于 TypeScript 项目,可以调用 tsc --noEmit 做类型级检查。

三、代码风格自动修正Hook(after:tool/Edit)

编辑后自动运行formatter(Prettier/Black/rustfmt)、自动排序import(isort)、代码风格一致性检查、格式修改的差异预览

代码风格一致性是团队协作的重要基础。通过在 after:tool/Edit 阶段注册 Hook,每次修改后自动对文件进行格式化,确保代码库始终保持统一的编码风格。

多语言Formatter配置

{ "after:tool/Edit": { "match": "*.{js,ts,jsx,tsx,css,json,md}", "run": "npx prettier --write {{file}} && npx eslint --fix {{file}}", "timeout": 10000 }, "after:tool/Edit": { "match": "*.py", "run": "black {{file}} && isort {{file}}", "timeout": 10000 }, "after:tool/Edit": { "match": "*.rs", "run": "rustfmt {{file}}", "timeout": 5000 } }

格式化前后的差异预览

为了让开发者清楚了解格式化所做的修改,可以附加一个 diff 预览步骤,在格式化完成后显示变更摘要:

#!/bin/bash # scripts/format-and-diff.sh FILE="$1" case "$FILE" in *.js|*.ts|*.jsx|*.tsx|*.css|*.json|*.md) npx prettier --write "$FILE" npx eslint --fix "$FILE" ;; *.py) black "$FILE" isort "$FILE" ;; *.rs) rustfmt "$FILE" ;; esac # 显示格式化差异 git diff --stat "$FILE" # 统计格式化变更 CHANGES=$(git diff "$FILE" | wc -l) if [ "$CHANGES" -gt 0 ]; then echo "格式化完成,共修改 $CHANGES 行" fi
注意事项:Formatter 的运行需要确保项目已安装对应的工具依赖。对于大型项目,建议只对修改的文件运行格式化,避免全量格式化导致大范围 diff。同时,Formatter 配置(如 .prettierrc、.eslintrc)应纳入版本控制,确保团队所有成员使用一致的标准。

四、编译检查预检Hook(before:tool/Write)

修改代码后自动触发编译检查(TypeScript/Java/Rust)、编译错误检测和报告中、阻止包含编译错误的代码写入、编译警告信息提示

编译检查是比语法检查更严格的代码验证。它不仅在语法层面检查,还进行类型检查、模块解析、引用完整性等深度验证。通过在 before:tool/Write 阶段设置 Hook,可以确保只有通过编译的代码才能被写入文件系统。

TypeScript 编译检查配置

{ "before:tool/Write": { "match": "*.ts", "run": "npx tsc --noEmit --pretty", "timeout": 30000, "onError": "reject" } }

Java 编译检查配置

{ "before:tool/Write": { "match": "*.java", "run": "mvn compile -q 2>&1 || gradle compileJava 2>&1", "timeout": 60000, "onError": "reject" } }

Rust 编译检查配置

{ "before:tool/Write": { "match": "*.rs", "run": "cargo check 2>&1", "timeout": 60000, "onError": "reject" } }

编译检查的输出结果需要解析和分级处理。通常分为三类:

#!/bin/bash # scripts/compile-check.sh FILE="$1" EXT="${FILE##*.}" case "$EXT" in ts|tsx) npx tsc --noEmit --pretty 2>&1 | tee /tmp/compile-result.txt ;; java) mvn compile -q 2>&1 | tee /tmp/compile-result.txt ;; rs) cargo check 2>&1 | tee /tmp/compile-result.txt ;; *) exit 0 ;; esac # 检查是否有错误 if grep -q "^Error:" /tmp/compile-result.txt; then echo "" echo "==============================" echo "编译错误检测:代码存在编译错误" echo "以下为错误摘要:" grep "^Error:" /tmp/compile-result.txt echo "==============================" echo "请修复上述错误后再尝试写入" exit 1 fi # 警告信息提示 WARN_COUNT=$(grep -c "^Warning:" /tmp/compile-result.txt 2>/dev/null || echo 0) if [ "$WARN_COUNT" -gt 0 ]; then echo "编译成功,但存在 $WARN_COUNT 个警告,建议查看并修复" fi exit 0
优化建议:对于大型项目,全量编译可能耗时较长(超过 30 秒),此时可以考虑增量编译策略。TypeScript 的 --incremental 标志、Gradle 的 build cache、Rust 的 sccache 都是有效的加速方案。timeout 值应根据项目规模和编译工具链进行调优。

五、测试自动运行Hook(after:tool/Edit)

修改代码后自动运行相关测试(增量测试)、测试结果验证和报告、测试失败时通知用户、测试覆盖增量统计

测试自动运行是质量保障体系的最后一道防线。通过 after:tool/Edit Hook,每次代码修改后自动触发关联的测试用例,确保新代码不破坏现有功能。

增量测试执行策略

全量测试在大型项目中可能耗时过长。更实用的策略是基于文件变更自动推断受影响的测试范围:

{ "after:tool/Edit": { "match": "src/**/*.{js,ts,jsx,tsx}", "run": "node scripts/run-affected-tests.js {{file}}", "timeout": 60000 } }
// scripts/run-affected-tests.js const { execSync } = require('child_process'); const path = require('path'); function findAffectedTests(changedFile) { // 分析文件变更影响到的测试范围 const fileName = path.basename(changedFile, path.extname(changedFile)); // 策略1:查找同名的 .test 文件 const directTest = changedFile.replace(/\.(js|ts|jsx|tsx)$/, '.test.$1'); // 策略2:查找 __tests__ 目录下相关文件 const dir = path.dirname(changedFile); const dirTest = path.join(dir, '__tests__', `${fileName}.test.*`); // 策略3:使用依赖分析工具推断影响范围 const affected = execSync( `npx jest --listTests --findRelatedTests ${changedFile}`, { encoding: 'utf-8' } ).trim().split('\n'); return [...new Set([directTest, ...affected])]; } function runTests(testFiles) { if (testFiles.length === 0) { console.log('未找到关联的测试文件'); return { pass: true, output: '' }; } try { const output = execSync( `npx jest ${testFiles.join(' ')} --verbose --ci`, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 } ); console.log(output); return { pass: true, output }; } catch (error) { console.error(error.stdout); console.error(error.stderr); return { pass: false, output: error.stdout }; } } // 主流程 const changedFile = process.argv[2]; console.log(`变更文件:${changedFile}`); console.log('正在查找关联测试...'); const testFiles = findAffectedTests(changedFile); console.log(`发现 ${testFiles.length} 个关联测试文件`); const result = runTests(testFiles); if (result.pass) { console.log('所有关联测试通过'); process.exit(0); } else { console.error('测试失败,请检查上述失败用例'); process.exit(1); }

测试结果报告示例

变更文件:src/services/userService.ts 正在查找关联测试... 发现 5 个关联测试文件 PASS src/services/__tests__/userService.test.ts 用户服务 ✓ 创建用户 (12ms) ✓ 查询用户 (8ms) ✓ 更新用户 (10ms) ✓ 删除用户 (6ms) PASS src/api/__tests__/userApi.test.ts API接口 ✓ GET /api/users (15ms) ✓ POST /api/users (18ms) PASS src/models/__tests__/userModel.test.ts 数据模型 ✓ 用户模型验证 (5ms) ✓ 密码加密 (20ms) 测试结果: Test Suites: 3 passed, 3 total Tests: 8 passed, 8 total Time: 1.523s

最佳实践:

1. 第一次跑测试时建议用 --no-cache 确保环境干净

2. 测试失败时不应阻断 Write 操作(不设置 onError:reject),而是通过通知机制告知用户

3. 建议将测试覆盖率增量写入日志,便于追踪质量趋势

4. 对于大型项目,结合 CI/CD 的全量测试与本地 Hook 的增量测试形成互补

六、质量门禁集成

将上述所有 Hook 组合成一个完整的质量门禁流水线,实现从编辑到写入的全程质量守护。

完整 settings.json 配置

{ "hooks": { "before:tool/Edit": [ { "match": "*.{js,ts,jsx,tsx}", "run": "node scripts/syntax-check.js", "timeout": 5000, "description": "语法错误实时检测", "onError": "reject" } ], "after:tool/Edit": [ { "match": "*.{js,ts,jsx,tsx,css,json,md}", "run": "bash scripts/format-and-diff.sh {{file}}", "timeout": 10000, "description": "代码风格自动修正" }, { "match": "src/**/*.{js,ts,jsx,tsx}", "run": "node scripts/run-affected-tests.js {{file}}", "timeout": 60000, "description": "关联测试自动运行" } ], "before:tool/Write": [ { "match": "*.ts", "run": "bash scripts/compile-check.sh {{file}}", "timeout": 30000, "description": "TypeScript编译检查", "onError": "reject" } ] } }
质量门禁规则:

必须通过:语法检查、编译检查 — 这两项设置 onError:reject,未通过则拒绝执行

建议通过:代码格式化、测试运行 — 这两项仅做通知和记录,不阻断流程

通知提醒:所有 Hook 执行完毕后汇总输出质量报告,格式如下:

=== 代码质量预检报告 === 语法检查: 通过 (0 errors) 代码风格自动修正: 完成 (formatted 3 files) 编译检查: 通过 (0 errors, 2 warnings) 关联测试: 通过 (8 passed, 0 failed) =========================== === 代码质量预检报告 === 语法检查: 失败 (2 errors) - 第 15 行: 缺少闭合括号 - 第 42 行: 未闭合的字符串字面量 编译检查: 未执行 (被语法检查阻断) ==========================

七、核心要点总结

1. 分层设防:从语法 → 风格 → 编译 → 测试,四层检查层层递进,形成完整质量保障体系

2. 适时阻断:语法错误和编译错误应在写入前阻断(onError:reject),格式化和测试失败仅做通知

3. 性能考量:Hook 执行时间直接影响开发体验,建议语法检查 < 3s、格式化 < 5s、编译检查 < 30s

4. 渐进增强:先从语法检查入手,逐步叠加风格修正、编译检查、测试运行,避免一次引入过多规则

5. 团队同步:所有 Hook 配置和检查脚本应纳入版本控制,确保团队所有成员使用一致的质量标准