analyze-tsc-errors.cjs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. #!/usr/bin/env node
  2. const { execSync } = require("child_process");
  3. const fs = require("fs");
  4. const path = require("path");
  5. console.log("🔍 TypeScript 错误分析工具 v2\n");
  6. console.log("=".repeat(80) + "\n");
  7. /**
  8. * 运行 TypeScript 编译检查
  9. */
  10. function getTscErrors() {
  11. console.log("📊 正在运行 TypeScript 检查...\n");
  12. try {
  13. const result = execSync("npx tsc --noEmit -p tsconfig.app.json 2>&1", {
  14. cwd: path.resolve(__dirname, ".."),
  15. encoding: "utf-8",
  16. env: { ...process.env, NODE_NO_WARNINGS: "1" },
  17. });
  18. if (!result || result.trim().length === 0) {
  19. console.log("✅ 没有类型错误!");
  20. return "";
  21. }
  22. return result;
  23. } catch (error) {
  24. return error.stdout || error.stderr || error.message || "";
  25. }
  26. }
  27. /**
  28. * 保存原始输出
  29. */
  30. function saveRawOutput(output) {
  31. const rawPath = path.join(__dirname, "..", "tsc-raw-output.txt");
  32. fs.writeFileSync(rawPath, output, "utf-8");
  33. console.log(`📝 原始输出已保存: ${path.basename(rawPath)}\n`);
  34. }
  35. /**
  36. * 解析错误信息
  37. * 支持格式: src/file.tsx(1,22): error TS2307: Error message
  38. */
  39. function parseErrors(errorOutput) {
  40. const lines = errorOutput.split("\n");
  41. const errorsByType = new Map();
  42. let parsedCount = 0;
  43. console.log(`📄 总行数: ${lines.length}\n`);
  44. console.log("🔍 开始解析错误...\n");
  45. for (let i = 0; i < lines.length; i++) {
  46. const line = lines[i].trim();
  47. // 匹配格式: src/file.tsx(1,22): error TS2307: Error message
  48. const errorMatch = line.match(
  49. /^(.+?)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)$/
  50. );
  51. if (errorMatch) {
  52. parsedCount++;
  53. const [, filePath, lineNum, colNum, errorCode, errorMessage] = errorMatch;
  54. const errorObj = {
  55. filePath: filePath.trim(),
  56. lineNum: parseInt(lineNum),
  57. colNum: parseInt(colNum),
  58. errorCode,
  59. errorMessage: errorMessage.trim(),
  60. fullError: line,
  61. };
  62. // 按错误代码分组
  63. if (!errorsByType.has(errorCode)) {
  64. errorsByType.set(errorCode, {
  65. code: errorCode,
  66. count: 0,
  67. samples: [],
  68. description: errorMessage.trim(),
  69. });
  70. }
  71. const errorType = errorsByType.get(errorCode);
  72. errorType.count++;
  73. // 只保存前 2 个样本
  74. if (errorType.samples.length < 2) {
  75. errorType.samples.push(errorObj);
  76. }
  77. // 每 100 个输出进度
  78. if (parsedCount % 100 === 0) {
  79. process.stdout.write(`\r 已解析 ${parsedCount} 个错误...`);
  80. }
  81. }
  82. }
  83. if (parsedCount > 0) {
  84. process.stdout.write(`\r 已解析 ${parsedCount} 个错误...完成\n\n`);
  85. }
  86. return errorsByType;
  87. }
  88. /**
  89. * 生成 Markdown 报告
  90. */
  91. function generateMarkdownReport(errorsByType) {
  92. let report = "";
  93. report += "# TypeScript 错误分析报告\n\n";
  94. report += `生成时间: ${new Date().toLocaleString("zh-CN")}\n\n`;
  95. report += "=".repeat(80) + "\n\n";
  96. // 统计
  97. const totalErrors = Array.from(errorsByType.values()).reduce(
  98. (sum, e) => sum + e.count,
  99. 0
  100. );
  101. report += "## 📊 错误统计\n\n";
  102. report += `- **总错误数**: ${totalErrors}\n`;
  103. report += `- **错误类型**: ${errorsByType.size} 种\n\n`;
  104. // 排序
  105. const sortedErrors = Array.from(errorsByType.values()).sort(
  106. (a, b) => b.count - a.count
  107. );
  108. report += "### 错误分布\n\n";
  109. report += "| 排名 | 错误代码 | 数量 | 占比 | 描述 |\n";
  110. report += "|------|---------|------|------|------|\n";
  111. sortedErrors.forEach((error, index) => {
  112. const percentage = ((error.count / totalErrors) * 100).toFixed(1);
  113. const desc =
  114. error.description.substring(0, 60) +
  115. (error.description.length > 60 ? "..." : "");
  116. report += `| ${index + 1} | ${error.code} | ${error.count} | ${percentage}% | ${desc} |\n`;
  117. });
  118. report += "\n" + "=".repeat(80) + "\n\n";
  119. // 详细样本
  120. report += "## 📋 错误详细样本 (每种类型 2 个)\n\n";
  121. sortedErrors.forEach((error, index) => {
  122. report += `### ${index + 1}. ${error.code} - ${error.count} 个错误\n\n`;
  123. report += `**完整描述**: ${error.description}\n\n`;
  124. if (error.samples.length > 0) {
  125. report += "**样本:**\n\n";
  126. error.samples.forEach((sample, sampleIndex) => {
  127. report += `#### 样本 ${sampleIndex + 1}\n\n`;
  128. report += "```\n";
  129. report += `${sample.fullError}\n`;
  130. report += "```\n\n";
  131. });
  132. }
  133. report += "---\n\n";
  134. });
  135. return report;
  136. }
  137. /**
  138. * 生成 JSON 报告
  139. */
  140. function generateJsonReport(errorsByType) {
  141. const sortedErrors = Array.from(errorsByType.values()).sort(
  142. (a, b) => b.count - a.count
  143. );
  144. return JSON.stringify(
  145. {
  146. generatedAt: new Date().toISOString(),
  147. summary: {
  148. totalErrors: sortedErrors.reduce((sum, e) => sum + e.count, 0),
  149. errorTypes: errorsByType.size,
  150. },
  151. errors: sortedErrors.map((error) => ({
  152. code: error.code,
  153. count: error.count,
  154. description: error.description,
  155. percentage: (
  156. (error.count / sortedErrors.reduce((sum, e) => sum + e.count, 0)) *
  157. 100
  158. ).toFixed(2),
  159. samples: error.samples,
  160. })),
  161. },
  162. null,
  163. 2
  164. );
  165. }
  166. /**
  167. * 主函数
  168. */
  169. function main() {
  170. // 获取错误
  171. const errorOutput = getTscErrors();
  172. if (!errorOutput || errorOutput.trim().length === 0) {
  173. console.log("✅ 没有 TypeScript 错误!\n");
  174. return;
  175. }
  176. // 保存原始输出
  177. saveRawOutput(errorOutput);
  178. // 解析错误
  179. const errorsByType = parseErrors(errorOutput);
  180. if (errorsByType.size === 0) {
  181. console.log("⚠️ 无法解析错误信息\n");
  182. console.log("💡 请检查 tsc-raw-output.txt 查看原始输出\n");
  183. return;
  184. }
  185. // 生成报告
  186. console.log("📝 正在生成报告...\n");
  187. const mdReport = generateMarkdownReport(errorsByType);
  188. const jsonReport = generateJsonReport(errorsByType);
  189. // 保存文件
  190. const mdPath = path.join(__dirname, "..", "tsc-errors-report.md");
  191. const jsonPath = path.join(__dirname, "..", "tsc-errors-report.json");
  192. fs.writeFileSync(mdPath, mdReport, "utf-8");
  193. fs.writeFileSync(jsonPath, jsonReport, "utf-8");
  194. // 输出摘要
  195. const sortedErrors = Array.from(errorsByType.values()).sort(
  196. (a, b) => b.count - a.count
  197. );
  198. const totalErrors = sortedErrors.reduce((sum, e) => sum + e.count, 0);
  199. console.log("=".repeat(80));
  200. console.log("✅ 分析完成");
  201. console.log("=".repeat(80));
  202. console.log(`\n📊 总错误数: ${totalErrors}`);
  203. console.log(`📊 错误类型: ${errorsByType.size} 种\n`);
  204. console.log("Top 10 错误类型:\n");
  205. sortedErrors.slice(0, 10).forEach((error, index) => {
  206. const percentage = ((error.count / totalErrors) * 100).toFixed(1);
  207. const countStr = String(error.count).padStart(4);
  208. const pctStr = String(percentage).padStart(5);
  209. console.log(
  210. ` ${(index + 1).toString().padStart(2)}. ${error.code.padEnd(8)} ${countStr} 个 (${pctStr}%)`
  211. );
  212. });
  213. console.log("\n" + "=".repeat(80));
  214. console.log("📄 报告已生成:\n");
  215. console.log(` - tsc-errors-report.md`);
  216. console.log(` - tsc-errors-report.json`);
  217. console.log("=".repeat(80) + "\n");
  218. console.log("💡 下一步:\n");
  219. console.log(" cat ../tsc-errors-report.md\n");
  220. console.log(" 把报告内容发给 AI,获取修复方案\n");
  221. }
  222. main();