133 lines
5.6 KiB
JavaScript
133 lines
5.6 KiB
JavaScript
|
|
import { exec } from "child_process";
|
|||
|
|
import { promisify } from "util";
|
|||
|
|
import { existsSync } from "fs";
|
|||
|
|
import { join } from "path";
|
|||
|
|
const execAsync = promisify(exec);
|
|||
|
|
export const lintCheckTool = {
|
|||
|
|
name: "lint_check",
|
|||
|
|
description: "对项目执行代码检查。自动检测项目类型并运行对应的 lint 工具(ESLint、TypeScript 编译检查、Pylint 等),返回结构化的错误列表。",
|
|||
|
|
inputSchema: {
|
|||
|
|
type: "object",
|
|||
|
|
properties: {
|
|||
|
|
project_path: {
|
|||
|
|
type: "string",
|
|||
|
|
description: "项目根目录(绝对路径)",
|
|||
|
|
},
|
|||
|
|
fix: {
|
|||
|
|
type: "boolean",
|
|||
|
|
description: "是否自动修复可修复的问题(默认 false)",
|
|||
|
|
},
|
|||
|
|
files: {
|
|||
|
|
type: "string",
|
|||
|
|
description: "指定检查的文件或 glob(可选,默认检查整个项目)",
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
required: ["project_path"],
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
async function runCommand(cmd, cwd, timeout = 30000) {
|
|||
|
|
try {
|
|||
|
|
const { stdout, stderr } = await execAsync(cmd, {
|
|||
|
|
cwd,
|
|||
|
|
timeout,
|
|||
|
|
maxBuffer: 1024 * 1024 * 5,
|
|||
|
|
shell: process.platform === "win32" ? "powershell.exe" : "/bin/bash",
|
|||
|
|
});
|
|||
|
|
return { stdout, stderr, code: 0 };
|
|||
|
|
}
|
|||
|
|
catch (error) {
|
|||
|
|
return {
|
|||
|
|
stdout: error.stdout || "",
|
|||
|
|
stderr: error.stderr || "",
|
|||
|
|
code: error.code ?? 1,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
export async function executeLintCheck(args) {
|
|||
|
|
const { project_path, fix = false, files } = args;
|
|||
|
|
const results = [];
|
|||
|
|
const hasFile = (name) => existsSync(join(project_path, name));
|
|||
|
|
const hasNodeModules = hasFile("node_modules");
|
|||
|
|
// TypeScript 编译检查
|
|||
|
|
if (hasFile("tsconfig.json")) {
|
|||
|
|
const result = await runCommand("npx tsc --noEmit --pretty", project_path);
|
|||
|
|
const errorCount = (result.stdout.match(/error TS/g) || []).length;
|
|||
|
|
results.push({
|
|||
|
|
tool: "TypeScript (tsc --noEmit)",
|
|||
|
|
success: result.code === 0,
|
|||
|
|
errorCount,
|
|||
|
|
warningCount: 0,
|
|||
|
|
output: result.stdout || result.stderr || "(无输出)",
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
// ESLint
|
|||
|
|
if (hasFile(".eslintrc.js") || hasFile(".eslintrc.json") || hasFile(".eslintrc.yml") || hasFile("eslint.config.js") || hasFile("eslint.config.mjs")) {
|
|||
|
|
const target = files || "src/";
|
|||
|
|
const fixFlag = fix ? " --fix" : "";
|
|||
|
|
const result = await runCommand(`npx eslint ${target}${fixFlag} --format stylish`, project_path);
|
|||
|
|
const errorMatch = result.stdout.match(/(\d+) errors?/);
|
|||
|
|
const warnMatch = result.stdout.match(/(\d+) warnings?/);
|
|||
|
|
results.push({
|
|||
|
|
tool: `ESLint${fix ? " (--fix)" : ""}`,
|
|||
|
|
success: result.code === 0,
|
|||
|
|
errorCount: errorMatch ? parseInt(errorMatch[1]) : 0,
|
|||
|
|
warningCount: warnMatch ? parseInt(warnMatch[1]) : 0,
|
|||
|
|
output: result.stdout || result.stderr || "✅ 无问题",
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
// Python: pylint / flake8
|
|||
|
|
if (hasFile("requirements.txt") || hasFile("setup.py") || hasFile("pyproject.toml")) {
|
|||
|
|
const target = files || ".";
|
|||
|
|
// 优先 flake8
|
|||
|
|
const result = await runCommand(`python -m flake8 ${target} --max-line-length=120 --count`, project_path);
|
|||
|
|
if (result.code !== 127) { // 127 = command not found
|
|||
|
|
const lines = result.stdout.trim().split("\n").filter(Boolean);
|
|||
|
|
results.push({
|
|||
|
|
tool: "Flake8",
|
|||
|
|
success: result.code === 0,
|
|||
|
|
errorCount: lines.length,
|
|||
|
|
warningCount: 0,
|
|||
|
|
output: result.stdout || result.stderr || "✅ 无问题",
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 如果什么检查工具都没找到
|
|||
|
|
if (results.length === 0) {
|
|||
|
|
// 尝试通用的 package.json lint 脚本
|
|||
|
|
if (hasFile("package.json")) {
|
|||
|
|
const result = await runCommand("npm run lint 2>&1 || echo LINT_SCRIPT_NOT_FOUND", project_path);
|
|||
|
|
if (!result.stdout.includes("LINT_SCRIPT_NOT_FOUND") && !result.stdout.includes("Missing script")) {
|
|||
|
|
results.push({
|
|||
|
|
tool: "npm run lint",
|
|||
|
|
success: result.code === 0,
|
|||
|
|
errorCount: result.code === 0 ? 0 : 1,
|
|||
|
|
warningCount: 0,
|
|||
|
|
output: result.stdout || result.stderr,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (results.length === 0) {
|
|||
|
|
return `# Lint 检查结果\n\n⚠️ 未检测到 lint 工具配置(ESLint、tsconfig、pylint 等)\n项目路径: ${project_path}\n\n建议:\n- TypeScript 项目:添加 tsconfig.json\n- JS 项目:npm init @eslint/config\n- Python 项目:pip install flake8`;
|
|||
|
|
}
|
|||
|
|
// 组装报告
|
|||
|
|
const totalErrors = results.reduce((sum, r) => sum + r.errorCount, 0);
|
|||
|
|
const totalWarnings = results.reduce((sum, r) => sum + r.warningCount, 0);
|
|||
|
|
const allPassed = results.every((r) => r.success);
|
|||
|
|
const output = [
|
|||
|
|
`# Lint 检查报告`,
|
|||
|
|
``,
|
|||
|
|
`📂 项目: ${project_path}`,
|
|||
|
|
`${allPassed ? "✅ 全部通过" : "❌ 发现问题"} | 错误: ${totalErrors} | 警告: ${totalWarnings}`,
|
|||
|
|
``,
|
|||
|
|
];
|
|||
|
|
for (const r of results) {
|
|||
|
|
output.push(`## ${r.success ? "✅" : "❌"} ${r.tool}`, `错误: ${r.errorCount} | 警告: ${r.warningCount}`, "```", r.output.slice(0, 3000), // 限制输出长度
|
|||
|
|
"```", ``);
|
|||
|
|
}
|
|||
|
|
if (!allPassed && !fix) {
|
|||
|
|
output.push(`💡 提示: 可以设置 fix=true 自动修复可修复的问题`);
|
|||
|
|
}
|
|||
|
|
return output.join("\n");
|
|||
|
|
}
|
|||
|
|
//# sourceMappingURL=lintCheck.js.map
|