Git钩子脚本如何检查函数复杂度

wen 实用脚本 1

本文目录导读:

Git钩子脚本如何检查函数复杂度

  1. 使用 ESLint 的复杂度规则
  2. 使用 Plato 工具
  3. 使用 Python 脚本来分析
  4. 使用 JavaScript 自定义检查脚本
  5. 使用 Husky + lint-staged(推荐)
  6. 高级检查 - 多种复杂度指标
  7. 最佳实践

Git钩子脚本可以通过多种方式检查函数复杂度,以下是几种常见的方法:

使用 ESLint 的复杂度规则

安装配置

npm install --save-dev eslint eslint-plugin-complexity

创建 .eslintrc.js

module.exports = {
  rules: {
    'complexity': ['error', { max: 10 }], // 最大圈复杂度
  }
};

pre-commit 钩子脚本

#!/bin/bash
# .git/hooks/pre-commit
echo "检查函数复杂度..."
# 获取暂存区的 .js 文件
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".js$")
if [ -z "$STAGED_FILES" ]; then
  echo "没有需要检查的 JS 文件"
  exit 0
fi
# 运行 ESLint 检查复杂度
npx eslint --rule 'complexity: ["error", 10]' $STAGED_FILES
if [ $? -ne 0 ]; then
  echo "复杂度检查失败!请简化函数复杂度"
  exit 1
fi
echo "复杂度检查通过"

使用 Plato 工具

安装 Plato

npm install -g plato
# 或
yarn global add plato

pre-commit 脚本

#!/bin/bash
# .git/hooks/pre-commit
THRESHOLD=15  # 复杂度阈值
# 检查每个暂存的文件
for file in $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|jsx|tsx)$'); do
  if [ -f "$file" ]; then
    # 使用 plato 检查复杂度
    plato -r -d report_temp -t "Complexity Check" "$file" > /dev/null 2>&1
    # 解析报告中的复杂性信息
    COMPLEXITY=$(grep -oP '(?<=cyclomatic complexity )[0-9]+' report_temp/report.json 2>/dev/null)
    if [ ! -z "$COMPLEXITY" ] && [ "$COMPLEXITY" -gt "$THRESHOLD" ]; then
      echo "⚠️ 文件 $file 中存在函数复杂度超标 ($COMPLEXITY > $THRESHOLD)"
      rm -rf report_temp
      exit 1
    fi
    rm -rf report_temp
  fi
done
echo "复杂度检查通过"

使用 Python 脚本来分析

安装 radon

pip install radon

pre-commit 脚本

#!/usr/bin/env python3
# .git/hooks/pre-commit-check.py
import subprocess
import json
from radon.complexity import cc_visit
from radon.visitors import Function
def check_complexity():
    # 获取暂存的文件
    result = subprocess.run(
        ['git', 'diff', '--cached', '--name-only', '--diff-filter=ACM'],
        capture_output=True, text=True
    )
    files = [f for f in result.stdout.split('\n') if f.endswith('.py')]
    threshold = 10  # 圈复杂度阈值
    failed = False
    for file_path in files:
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                code = f.read()
            # 分析复杂度
            functions = cc_visit(code)
            for func in functions:
                if func.complexity > threshold:
                    print(f"⚠️ 文件 {file_path}")
                    print(f"  函数 {func.name} 复杂度: {func.complexity} (阈值: {threshold})")
                    failed = True
        except Exception as e:
            print(f"❌ 分析 {file_path} 时出错: {e}")
    return failed
if __name__ == "__main__":
    if check_complexity():
        print("\n❌ 复杂度检查失败!请简化高复杂度函数")
        exit(1)
    else:
        print("✅ 复杂度检查通过")
        exit(0)

pre-commit 脚本调用

#!/bin/bash
# .git/hooks/pre-commit
python3 .git/hooks/pre-commit-check.py

使用 JavaScript 自定义检查脚本

创建复杂度检查工具

// complexity-check.js
const fs = require('fs');
const path = require('path');
function calculateCyclomaticComplexity(code) {
  // 匹配会增加复杂度的关键字
  const patterns = [
    /if/g, /else if/g, /for/g, /while/g, /case/g,
    /catch/g, /\&\&/g, /\|\|/g, /\?/g
  ];
  let complexity = 1; // 基础复杂度
  patterns.forEach(pattern => {
    const matches = code.match(pattern);
    if (matches) {
      complexity += matches.length;
    }
  });
  return complexity;
}
// 解析函数
function extractFunctions(code) {
  const functionRegex = /(?:function\s+(\w+)|(\w+)\s*=\s*function|(\w+)\s*=>\s*{)/g;
  const functions = [];
  let match;
  while ((match = functionRegex.exec(code)) !== null) {
    const name = match[1] || match[2] || match[3];
    const startIndex = match.index;
    // 简单找到函数体结束
    let endIndex = code.indexOf('}', startIndex);
    if (endIndex > -1) {
      const functionCode = code.substring(startIndex, endIndex + 1);
      functions.push({
        name,
        code: functionCode,
        start: startIndex,
        end: endIndex
      });
    }
  }
  return functions;
}
function processFile(filePath, threshold = 10) {
  const code = fs.readFileSync(filePath, 'utf8');
  const functions = extractFunctions(code);
  const issues = [];
  functions.forEach(func => {
    const complexity = calculateCyclomaticComplexity(func.code);
    if (complexity > threshold) {
      issues.push({
        file: filePath,
        function: func.name || 'anonymous',
        complexity,
        threshold
      });
    }
  });
  return issues;
}
// 主函数
const threshold = 10;
const stagedFiles = process.argv[2].split('\n');
let hasErrors = false;
stagedFiles.forEach(file => {
  if (file.endsWith('.js') || file.endsWith('.ts')) {
    const issues = processFile(file, threshold);
    issues.forEach(issue => {
      console.log(`⚠️ 函数复杂度超标: ${issue.file} - ${issue.function}`);
      console.log(`  复杂度: ${issue.complexity} (阈值: ${issue.threshold})`);
      hasErrors = true;
    });
  }
});
if (hasErrors) {
  process.exit(1);
}

pre-commit 脚本

#!/bin/bash
# .git/hooks/pre-commit
echo "检查函数复杂度..."
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts)$')
if [ -z "$STAGED_FILES" ]; then
  exit 0
fi
node complexity-check.js "$STAGED_FILES"
if [ $? -ne 0 ]; then
  echo "❌ 复杂度检查失败"
  exit 1
fi
echo "✅ 复杂度检查通过"

使用 Husky + lint-staged(推荐)

安装和配置

npm install --save-dev husky lint-staged eslint

package.json 配置

{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.js": [
      "eslint --rule 'complexity: [\"error\", 10]'",
      "git add"
    ]
  }
}

高级检查 - 多种复杂度指标

综合检查脚本

#!/usr/bin/env python3
"""综合复杂度检查"""
import ast
import subprocess
from collections import defaultdict
class ComplexityChecker(ast.NodeVisitor):
    def __init__(self):
        self.complexities = defaultdict(int)
        self.current_function = None
    def visit_FunctionDef(self, node):
        self.current_function = node.name
        self.complexities[node.name] = 1  # 基础复杂度
        self.generic_visit(node)
    def visit_If(self, node):
        if self.current_function:
            self.complexities[self.current_function] += 1
        self.generic_visit(node)
    def visit_While(self, node):
        if self.current_function:
            self.complexities[self.current_function] += 1
        self.generic_visit(node)
    def visit_For(self, node):
        if self.current_function:
            self.complexities[self.current_function] += 1
        self.generic_visit(node)
    def visit_BoolOp(self, node):
        if self.current_function:
            # 每个布尔运算符增加复杂度
            self.complexities[self.current_function] += len(node.values) - 1
        self.generic_visit(node)
def check_files():
    result = subprocess.run(
        ['git', 'diff', '--cached', '--name-only', '--diff-filter=ACM'],
        capture_output=True, text=True
    )
    files = [f for f in result.stdout.split('\n') if f.endswith('.py')]
    threshold = 10
    failed = False
    for file_path in files:
        try:
            with open(file_path, 'r') as f:
                tree = ast.parse(f.read())
            checker = ComplexityChecker()
            checker.visit(tree)
            for func_name, complexity in checker.complexities.items():
                if complexity > threshold:
                    print(f"⚠️ {file_path}:{func_name} 复杂度 {complexity} (阈值 {threshold})")
                    failed = True
        except SyntaxError:
            print(f"❌ {file_path}: 语法错误")
            failed = True
    return failed
if __name__ == "__main__":
    if check_files():
        print("\n❌ 复杂度检查失败")
        exit(1)
    else:
        print("✅ 复杂度检查通过")
        exit(0)

最佳实践

  1. 阈值设置

    • 圈复杂度:建议 10-15
    • 认知复杂度:建议 15-20
  2. 性能优化

    • 只检查暂存的文件
    • 缓存已检查的结果
  3. 错误处理

    • 提供清晰的错误信息
    • 标记具体的问题函数
  4. 集成到 CI/CD

    # .github/workflows/complexity.yml
    name: Check Complexity
    on: [push, pull_request]
    jobs:
    check:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
       - name: Check complexity
         run: |
           pip install radon
           radon cc . -s -n 10 -e "tests/*"

这些方法可以根据项目的技术栈和需求选择和调整,建议从简单的 ESLint 规则开始,逐步增加更复杂的检查。

抱歉,评论功能暂时关闭!