核心概念: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"
}
}
编译检查的输出结果需要解析和分级处理。通常分为三类:
- 错误(Error):必须修复的问题,阻止代码写入
- 警告(Warning):建议修复的问题,记录但允许写入
- 提示(Info):可选的优化建议,仅作参考
#!/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 配置和检查脚本应纳入版本控制,确保团队所有成员使用一致的质量标准