项目代码提交检测机制实现

概述

为提高了代码质量和团队协作效率。通过 Git hooks 和工具链的配合,实现了代码提交检测机制:

  1. 代码质量保证:通过 ESLint 检测代码规范和质量问题
  2. 提交信息规范:通过自定义检查确保提交信息格式统一
  3. 自动化流程:在提交时自动执行检查和格式化
  4. 错误拦截:检测不通过时阻止提交,确保代码库质量

1. 检测机制架构

采用两层检测机制来确保代码质量和提交规范:

sql 复制代码
git commit
    ↓
┌─────────────────────────────────────┐
│  Pre-commit Hook                    │
│  ├── Prettier 代码格式化            │
│  ├── ESLint 代码质量检测            │
│  └── 重新暂存文件                   │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│  Prepare-commit-msg Hook            │
│  └── 提交信息格式检查               │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│  提交完成                           │
└─────────────────────────────────────┘

2. Git Hooks 实现原理

2.1 Yorkie 工具

项目使用 yorkie 包来管理 Git hooks,配置在 package.json 中:

json 复制代码
{
  "devDependencies": {
    "yorkie": "^2.0.0"
  },
  "gitHooks": {
    "pre-commit": "lint-staged",
    "prepare-commit-msg": "node ./scripts/git-hooks/prepare-commit-msg.mjs"
  }
  "lint-staged": {
    "*.{js,vue,json}": [
      "prettier --write",
      "eslint",
      "git add ."
    ]
  }
}

2.2 Hook 文件位置

实际的 Git hook 文件位于:

  • .git/hooks/pre-commit
  • .git/hooks/prepare-commit-msg

2.3 Yorkie Runner

yorkie 通过 node_modules/yorkie/src/runner.js 执行配置的命令:

javascript 复制代码
const fs = require('fs')
const path = require('path')
const execa = require('execa')

const cwd = process.cwd()
const pkg = fs.readFileSync(path.join(cwd, 'package.json'))
const hooks = JSON.parse(pkg).gitHooks

const hook = process.argv[2]
const command = hooks[hook]

console.log(` > running ${hook} hook: ${command}`)
try {
  execa.shellSync(command, { stdio: 'inherit' })
} catch (e) {
  process.exit(1)
}

2.4 eslint配置示例

ESLint 配置位于 .eslintrc.js 文件中:

javascript 复制代码
module.exports = {
  // ... 其他配置
  rules: {
    // 不允许出现未使用变量
    'no-unused-vars': 'error',
    // 其他规则...
  }
};

会被检测到的错误:

javascript 复制代码
// ❌ 错误:声明了但未使用
const unusedVariable = 'hello';
const anotherVar = 123;

// ❌ 错误:函数参数未使用
function unusedParam(param1, param2) {
  console.log('hello');
  // param1 和 param2 都没有使用
}

2.5 提交信息标准格式

项目要求提交信息必须符合以下格式:

bash 复制代码
type(可选 scope): 本次代码做了哪些事情,前面要有一个空格

https://project.feishu.cn/xxx

支持的提交类型

  • feature - 新功能
  • bugfix - 修复bug
  • task - 任务
  • docs - 文档
  • style - 样式
  • refactor - 重构
  • performance - 性能优化
  • test - 测试
  • chore - 杂务
  • revert - 回滚
  • config - 配置
  • fix - 修复
  • feat - 功能

3. 提交信息格式检查

3.1 提交代码检查

通过"pre-commit": "lint-staged"自动检测提交的代码

3.2 提交信息检查

3.2.1 检查文件

提交信息检查由以下文件实现:

  • scripts/git-hooks/prepare-commit-msg.mjs - 主检查文件
  • scripts/git-hooks/messge-checker.mjs - 检查逻辑实现

3.2.2 检查过程

messge-checker.mjs

javascript 复制代码
import Readline from 'n-readlines';

const rules = [
  {
    example: 'type(可选 scope): 本次代码做了哪些事情,前面要有一个空格',
    checker(line) {
      const commitRE =
        /^(feature|bugfix|task|docs|style|refactor|performance|test|chore|revert|config|fix|feat)(\(.+\))?(:) .*$/;
      return commitRE.test(line);
    },
    special(line) {
      const commitRE = /^([A-Za-z]+)(\(utils\)) .*$/;
      return commitRE.test(line);
    }
  },
  {
    example: '必须是空行',
    checker: function (line) {
      return line.trim().length === 0;
    }
  },
  {
    example: 'https://project.feishu.cn/brm/story/detail/15260602',
    checker: function (line) {
      const commitRE = /^https:\/\/project.feishu.cn\//;
      return commitRE.test(line) || line.includes('.feishu.cn/base/');
    }
  }
];

function commitMessageChecker(fileName, debug) {
  const liner = new Readline(fileName);

  let i = 0;
  let isSpecial = false;
  let line;
  const ret = {
    code: 0,
    msg: []
  };
  debug && console.log('--------------------------------------------------------------');
  while ((line = liner.next())) {
    // docs 类型的提交,只需要校验第一行
    if (
      `${line}`.startsWith('docs') ||
      `${line}`.startsWith('fix') ||
      `${line}`.startsWith('feat') ||
      `${line}`.startsWith('config')
    ) {
      if (rules[i] && !rules[i].checker(`${line}`)) {
        ret.code = -1;
        ret.msg.push(`第${i + 1}行格式不对,规范为:${rules[i].example}`);
      }
      return ret;
    }

    // 忽略注释行
    if (`${line}`.startsWith('#')) {
      continue;
    }

    debug && console.log(`${line}`);

    if (rules[i] && !rules[i].checker(`${line}`)) {
      ret.code = -1;
      ret.msg.push(`第${i + 1}行格式不对,规范为:${rules[i].example}`);
    }

    // 特殊的提交,只需要校验第一行
    if (rules[i] && rules[i].special && rules[i].special(`${line}`)) {
      isSpecial = true;
      break;
    }

    i++;
  }
  debug && console.log('--------------------------------------------------------------');

  // console.log(`isSpecial = ${isSpecial}, i = ${i}`)
  // 不是utils域,就必须要3行
  if (!isSpecial && i !== rules.length) {
    ret.code = -1;
    ret.msg.push(`commit message必须为${rules.length}行,而本次有${i}行`);
  }

  return ret;
}

export default commitMessageChecker;

// 另外一个实现切分文件多行的方法,这里没有采用主要是担心兼容性不够
/*
const data = fs.readFileSync('file.txt', 'UTF-8');
const lines = data.split(/\r?\n/);
lines.forEach((line) => {
    console.log(line);
});
*/

prepare-commit-msg

javascript 复制代码
import chalk from 'chalk';
import checker from './messge-checker.mjs';

const msgPath = './.git/COMMIT_EDITMSG';

const ret = checker(msgPath, true);

if (ret.code !== 0) {
  console.error(
    `  ${chalk.bgRed.white(' ERROR ')} ${chalk.red('commit message 格式错误,信息如下:')}\n\n${ret.msg
      .map((m) => {
        return `  ${chalk.green(m)}`;
      })
      .join('\n\n')}\n\n${chalk.red('  详细规范 README.md #commit message 规范')}`
  );

  process.exit(1);
}

4. 检测触发流程

4.1 完整执行流程

bash 复制代码
git commit
    ↓
┌─────────────────────────────────────┐
│  .git/hooks/pre-commit              │
│  ↓                                  │
│  yorkie runner                      │
│  ↓                                  │
│  lint-staged                        │
│  ↓                                  │
│  prettier --write [files]           │
│  ↓                                  │
│  eslint [files] ← 这里检测未使用变量 │
│  ↓                                  │
│  git add .                          │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│  .git/hooks/prepare-commit-msg      │
│  ↓                                  │
│  yorkie runner                      │
│  ↓                                  │
│  prepare-commit-msg.mjs             │
│  ↓                                  │
│  messge-checker.mjs ← 检查提交信息   │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│  提交完成                           │
└─────────────────────────────────────┘

5. 错误处理机制

5.1 ESLint 错误处理

当 ESLint 检测到错误时:

bash 复制代码
src/pages/header/guideDialogs/audioTranscriptionModal.vue
  231:8  error  'SearchSelect' is defined but never used  no-unused-vars
  373:7  error  'searchCompanyByKeyword' is assigned a value but never used  no-unused-vars

✖ 2 problems (2 errors, 0 warnings)
  • 显示具体的错误位置和类型
  • 返回非零退出码
  • 阻止提交继续执行

5.2 提交信息错误处理

当提交信息格式错误时:

bash 复制代码
 ERROR  commit message 格式错误,信息如下:

  第1行格式不对,规范为:type(可选 scope): 本次代码做了哪些事情,前面要有一个空格

  详细规范 README.md #commit message 规范
  • 使用 chalk 库美化错误信息
  • 显示具体的错误位置和规范要求
  • 调用 process.exit(1) 强制终止
相关推荐
井柏然5 小时前
为什么打 npm 包时要将 Vue/React 进行 external 处理?
javascript·vite·前端工程化
小Lu的开源日常1 天前
踩坑日记:为什么 .gitignore 不起作用了
git·代码规范·trae
井柏然1 天前
前端工程化—实战npm包深入理解 external 及实例唯一性
前端·javascript·前端工程化
井柏然1 天前
从 npm 包实战深入理解 external 及实例唯一性
前端·javascript·前端工程化
RJiazhen7 天前
从迁移至 Rsbuild 说起,前端为什么要工程化
前端·架构·前端工程化
huangql5207 天前
UniApp + Vite + Vue3 + TypeScript 项目中 ESLint 与 Prettier 的完整配置指南
vue.js·typescript·团队开发·代码规范
tangzzzfan9 天前
Git 提交规范与 Git Flow 最佳实践分享
代码规范
jason_yang10 天前
JavaScript 风格指南 精选版
前端·javascript·代码规范
fatfishccc10 天前
(五)数据重构的艺术:优化你的代码结构与可读性
代码规范