|
|
@@ -0,0 +1,270 @@
|
|
|
+#!/usr/bin/env node
|
|
|
+
|
|
|
+const { execSync } = require("child_process");
|
|
|
+const fs = require("fs");
|
|
|
+const path = require("path");
|
|
|
+
|
|
|
+console.log("🔍 TypeScript 错误分析工具 v2\n");
|
|
|
+console.log("=".repeat(80) + "\n");
|
|
|
+
|
|
|
+/**
|
|
|
+ * 运行 TypeScript 编译检查
|
|
|
+ */
|
|
|
+function getTscErrors() {
|
|
|
+ console.log("📊 正在运行 TypeScript 检查...\n");
|
|
|
+
|
|
|
+ try {
|
|
|
+ const result = execSync("npx tsc --noEmit -p tsconfig.app.json 2>&1", {
|
|
|
+ cwd: path.resolve(__dirname, ".."),
|
|
|
+ encoding: "utf-8",
|
|
|
+ env: { ...process.env, NODE_NO_WARNINGS: "1" },
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!result || result.trim().length === 0) {
|
|
|
+ console.log("✅ 没有类型错误!");
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ } catch (error) {
|
|
|
+ return error.stdout || error.stderr || error.message || "";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 保存原始输出
|
|
|
+ */
|
|
|
+function saveRawOutput(output) {
|
|
|
+ const rawPath = path.join(__dirname, "..", "tsc-raw-output.txt");
|
|
|
+ fs.writeFileSync(rawPath, output, "utf-8");
|
|
|
+ console.log(`📝 原始输出已保存: ${path.basename(rawPath)}\n`);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 解析错误信息
|
|
|
+ * 支持格式: src/file.tsx(1,22): error TS2307: Error message
|
|
|
+ */
|
|
|
+function parseErrors(errorOutput) {
|
|
|
+ const lines = errorOutput.split("\n");
|
|
|
+ const errorsByType = new Map();
|
|
|
+ let parsedCount = 0;
|
|
|
+
|
|
|
+ console.log(`📄 总行数: ${lines.length}\n`);
|
|
|
+ console.log("🔍 开始解析错误...\n");
|
|
|
+
|
|
|
+ for (let i = 0; i < lines.length; i++) {
|
|
|
+ const line = lines[i].trim();
|
|
|
+
|
|
|
+ // 匹配格式: src/file.tsx(1,22): error TS2307: Error message
|
|
|
+ const errorMatch = line.match(
|
|
|
+ /^(.+?)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)$/
|
|
|
+ );
|
|
|
+
|
|
|
+ if (errorMatch) {
|
|
|
+ parsedCount++;
|
|
|
+ const [, filePath, lineNum, colNum, errorCode, errorMessage] = errorMatch;
|
|
|
+
|
|
|
+ const errorObj = {
|
|
|
+ filePath: filePath.trim(),
|
|
|
+ lineNum: parseInt(lineNum),
|
|
|
+ colNum: parseInt(colNum),
|
|
|
+ errorCode,
|
|
|
+ errorMessage: errorMessage.trim(),
|
|
|
+ fullError: line,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 按错误代码分组
|
|
|
+ if (!errorsByType.has(errorCode)) {
|
|
|
+ errorsByType.set(errorCode, {
|
|
|
+ code: errorCode,
|
|
|
+ count: 0,
|
|
|
+ samples: [],
|
|
|
+ description: errorMessage.trim(),
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ const errorType = errorsByType.get(errorCode);
|
|
|
+ errorType.count++;
|
|
|
+
|
|
|
+ // 只保存前 2 个样本
|
|
|
+ if (errorType.samples.length < 2) {
|
|
|
+ errorType.samples.push(errorObj);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 每 100 个输出进度
|
|
|
+ if (parsedCount % 100 === 0) {
|
|
|
+ process.stdout.write(`\r 已解析 ${parsedCount} 个错误...`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (parsedCount > 0) {
|
|
|
+ process.stdout.write(`\r 已解析 ${parsedCount} 个错误...完成\n\n`);
|
|
|
+ }
|
|
|
+
|
|
|
+ return errorsByType;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 生成 Markdown 报告
|
|
|
+ */
|
|
|
+function generateMarkdownReport(errorsByType) {
|
|
|
+ let report = "";
|
|
|
+
|
|
|
+ report += "# TypeScript 错误分析报告\n\n";
|
|
|
+ report += `生成时间: ${new Date().toLocaleString("zh-CN")}\n\n`;
|
|
|
+ report += "=".repeat(80) + "\n\n";
|
|
|
+
|
|
|
+ // 统计
|
|
|
+ const totalErrors = Array.from(errorsByType.values()).reduce(
|
|
|
+ (sum, e) => sum + e.count,
|
|
|
+ 0
|
|
|
+ );
|
|
|
+ report += "## 📊 错误统计\n\n";
|
|
|
+ report += `- **总错误数**: ${totalErrors}\n`;
|
|
|
+ report += `- **错误类型**: ${errorsByType.size} 种\n\n`;
|
|
|
+
|
|
|
+ // 排序
|
|
|
+ const sortedErrors = Array.from(errorsByType.values()).sort(
|
|
|
+ (a, b) => b.count - a.count
|
|
|
+ );
|
|
|
+
|
|
|
+ report += "### 错误分布\n\n";
|
|
|
+ report += "| 排名 | 错误代码 | 数量 | 占比 | 描述 |\n";
|
|
|
+ report += "|------|---------|------|------|------|\n";
|
|
|
+
|
|
|
+ sortedErrors.forEach((error, index) => {
|
|
|
+ const percentage = ((error.count / totalErrors) * 100).toFixed(1);
|
|
|
+ const desc =
|
|
|
+ error.description.substring(0, 60) +
|
|
|
+ (error.description.length > 60 ? "..." : "");
|
|
|
+ report += `| ${index + 1} | ${error.code} | ${error.count} | ${percentage}% | ${desc} |\n`;
|
|
|
+ });
|
|
|
+
|
|
|
+ report += "\n" + "=".repeat(80) + "\n\n";
|
|
|
+
|
|
|
+ // 详细样本
|
|
|
+ report += "## 📋 错误详细样本 (每种类型 2 个)\n\n";
|
|
|
+
|
|
|
+ sortedErrors.forEach((error, index) => {
|
|
|
+ report += `### ${index + 1}. ${error.code} - ${error.count} 个错误\n\n`;
|
|
|
+ report += `**完整描述**: ${error.description}\n\n`;
|
|
|
+
|
|
|
+ if (error.samples.length > 0) {
|
|
|
+ report += "**样本:**\n\n";
|
|
|
+ error.samples.forEach((sample, sampleIndex) => {
|
|
|
+ report += `#### 样本 ${sampleIndex + 1}\n\n`;
|
|
|
+ report += "```\n";
|
|
|
+ report += `${sample.fullError}\n`;
|
|
|
+ report += "```\n\n";
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ report += "---\n\n";
|
|
|
+ });
|
|
|
+
|
|
|
+ return report;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 生成 JSON 报告
|
|
|
+ */
|
|
|
+function generateJsonReport(errorsByType) {
|
|
|
+ const sortedErrors = Array.from(errorsByType.values()).sort(
|
|
|
+ (a, b) => b.count - a.count
|
|
|
+ );
|
|
|
+
|
|
|
+ return JSON.stringify(
|
|
|
+ {
|
|
|
+ generatedAt: new Date().toISOString(),
|
|
|
+ summary: {
|
|
|
+ totalErrors: sortedErrors.reduce((sum, e) => sum + e.count, 0),
|
|
|
+ errorTypes: errorsByType.size,
|
|
|
+ },
|
|
|
+ errors: sortedErrors.map((error) => ({
|
|
|
+ code: error.code,
|
|
|
+ count: error.count,
|
|
|
+ description: error.description,
|
|
|
+ percentage: (
|
|
|
+ (error.count / sortedErrors.reduce((sum, e) => sum + e.count, 0)) *
|
|
|
+ 100
|
|
|
+ ).toFixed(2),
|
|
|
+ samples: error.samples,
|
|
|
+ })),
|
|
|
+ },
|
|
|
+ null,
|
|
|
+ 2
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 主函数
|
|
|
+ */
|
|
|
+function main() {
|
|
|
+ // 获取错误
|
|
|
+ const errorOutput = getTscErrors();
|
|
|
+
|
|
|
+ if (!errorOutput || errorOutput.trim().length === 0) {
|
|
|
+ console.log("✅ 没有 TypeScript 错误!\n");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存原始输出
|
|
|
+ saveRawOutput(errorOutput);
|
|
|
+
|
|
|
+ // 解析错误
|
|
|
+ const errorsByType = parseErrors(errorOutput);
|
|
|
+
|
|
|
+ if (errorsByType.size === 0) {
|
|
|
+ console.log("⚠️ 无法解析错误信息\n");
|
|
|
+ console.log("💡 请检查 tsc-raw-output.txt 查看原始输出\n");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成报告
|
|
|
+ console.log("📝 正在生成报告...\n");
|
|
|
+ const mdReport = generateMarkdownReport(errorsByType);
|
|
|
+ const jsonReport = generateJsonReport(errorsByType);
|
|
|
+
|
|
|
+ // 保存文件
|
|
|
+ const mdPath = path.join(__dirname, "..", "tsc-errors-report.md");
|
|
|
+ const jsonPath = path.join(__dirname, "..", "tsc-errors-report.json");
|
|
|
+
|
|
|
+ fs.writeFileSync(mdPath, mdReport, "utf-8");
|
|
|
+ fs.writeFileSync(jsonPath, jsonReport, "utf-8");
|
|
|
+
|
|
|
+ // 输出摘要
|
|
|
+ const sortedErrors = Array.from(errorsByType.values()).sort(
|
|
|
+ (a, b) => b.count - a.count
|
|
|
+ );
|
|
|
+
|
|
|
+ const totalErrors = sortedErrors.reduce((sum, e) => sum + e.count, 0);
|
|
|
+
|
|
|
+ console.log("=".repeat(80));
|
|
|
+ console.log("✅ 分析完成");
|
|
|
+ console.log("=".repeat(80));
|
|
|
+ console.log(`\n📊 总错误数: ${totalErrors}`);
|
|
|
+ console.log(`📊 错误类型: ${errorsByType.size} 种\n`);
|
|
|
+
|
|
|
+ console.log("Top 10 错误类型:\n");
|
|
|
+ sortedErrors.slice(0, 10).forEach((error, index) => {
|
|
|
+ const percentage = ((error.count / totalErrors) * 100).toFixed(1);
|
|
|
+ const countStr = String(error.count).padStart(4);
|
|
|
+ const pctStr = String(percentage).padStart(5);
|
|
|
+ console.log(
|
|
|
+ ` ${(index + 1).toString().padStart(2)}. ${error.code.padEnd(8)} ${countStr} 个 (${pctStr}%)`
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log("\n" + "=".repeat(80));
|
|
|
+ console.log("📄 报告已生成:\n");
|
|
|
+ console.log(` - tsc-errors-report.md`);
|
|
|
+ console.log(` - tsc-errors-report.json`);
|
|
|
+ console.log("=".repeat(80) + "\n");
|
|
|
+
|
|
|
+ console.log("💡 下一步:\n");
|
|
|
+ console.log(" cat ../tsc-errors-report.md\n");
|
|
|
+ console.log(" 把报告内容发给 AI,获取修复方案\n");
|
|
|
+}
|
|
|
+
|
|
|
+main();
|