code-simplifier 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -1
- package/bin/code-simplifier.js +73 -1
- package/cypress.config.js +45 -0
- package/jest.config.js +46 -0
- package/lib/ai-auto-fix.js +829 -0
- package/lib/ai-code-review.js +705 -0
- package/lib/ai-knowledge-base.js +757 -0
- package/lib/ai-quality-analyzer.js +1095 -0
- package/lib/ai-refactor-advisor.js +853 -0
- package/lib/ai-trend-analyzer.js +674 -0
- package/lib/auto-fix.js +382 -0
- package/lib/eslint-integration.js +328 -0
- package/lib/git-hooks.js +353 -0
- package/lib/master.js +117 -0
- package/lib/multi-language-analyzer.js +397 -0
- package/lib/ralph-integration.js +541 -0
- package/package.json +87 -61
package/lib/auto-fix.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Code-Simplifier 自动修复
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra')
|
|
8
|
+
const path = require('path')
|
|
9
|
+
const chalk = require('chalk')
|
|
10
|
+
const ora = require('ora')
|
|
11
|
+
|
|
12
|
+
class AutoFix {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.fixes = []
|
|
15
|
+
this.applied = []
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 运行自动修复
|
|
20
|
+
*/
|
|
21
|
+
async run(options = {}) {
|
|
22
|
+
console.log(chalk.blue.bold('\n🔧 Code-Simplifier 自动修复'))
|
|
23
|
+
console.log(chalk.gray('='.repeat(70)))
|
|
24
|
+
|
|
25
|
+
const spinner = ora(chalk.blue('正在分析代码问题...')).start()
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const {
|
|
29
|
+
dir = 'src',
|
|
30
|
+
pattern = '**/*.{js,ts,jsx,tsx,py,java}',
|
|
31
|
+
dryRun = false
|
|
32
|
+
} = options
|
|
33
|
+
|
|
34
|
+
// 1. 扫描文件
|
|
35
|
+
const files = await this.scanFiles(dir, pattern)
|
|
36
|
+
spinner.info(chalk.gray(`扫描文件: ${files.length} 个`))
|
|
37
|
+
|
|
38
|
+
// 2. 检测问题
|
|
39
|
+
const issues = await this.detectIssues(files)
|
|
40
|
+
spinner.info(chalk.gray(`检测到问题: ${issues.length} 个`))
|
|
41
|
+
|
|
42
|
+
// 3. 应用修复
|
|
43
|
+
if (!dryRun && issues.length > 0) {
|
|
44
|
+
const result = await this.applyFixes(issues, files)
|
|
45
|
+
spinner.succeed(chalk.green('自动修复完成'))
|
|
46
|
+
return result
|
|
47
|
+
} else if (issues.length > 0) {
|
|
48
|
+
spinner.info(chalk.yellow('这是预览模式,未实际修改文件'))
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
dryRun: true,
|
|
52
|
+
totalIssues: issues.length,
|
|
53
|
+
fixes: issues
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
spinner.succeed(chalk.green('没有发现需要修复的问题'))
|
|
57
|
+
return {
|
|
58
|
+
success: true,
|
|
59
|
+
dryRun: false,
|
|
60
|
+
totalIssues: 0,
|
|
61
|
+
fixes: []
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
spinner.fail(chalk.red('自动修复失败'))
|
|
66
|
+
throw error
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 扫描文件
|
|
72
|
+
*/
|
|
73
|
+
async scanFiles(dir, pattern) {
|
|
74
|
+
const glob = require('glob')
|
|
75
|
+
return await new Promise((resolve) => {
|
|
76
|
+
try {
|
|
77
|
+
glob(pattern, {
|
|
78
|
+
cwd: dir,
|
|
79
|
+
ignore: ['node_modules/**', 'dist/**', 'build/**', 'vendor/**', '.git/**'],
|
|
80
|
+
nodir: true
|
|
81
|
+
}, (err, matches) => {
|
|
82
|
+
resolve(err ? [] : matches)
|
|
83
|
+
})
|
|
84
|
+
} catch (e) {
|
|
85
|
+
resolve([])
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 检测问题
|
|
92
|
+
*/
|
|
93
|
+
async detectIssues(files) {
|
|
94
|
+
const issues = []
|
|
95
|
+
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
try {
|
|
98
|
+
const content = await fs.readFile(file, 'utf8')
|
|
99
|
+
const lines = content.split('\n')
|
|
100
|
+
|
|
101
|
+
// 检测各种问题
|
|
102
|
+
issues.push(...this.checkTrailingSpaces(file, lines))
|
|
103
|
+
issues.push(...this.checkConsoleLog(file, lines))
|
|
104
|
+
issues.push(...this.checkDebugger(file, lines))
|
|
105
|
+
issues.push(...this.checkMissingSemicolon(file, lines))
|
|
106
|
+
issues.push(...this.checkVarDeclaration(file, lines))
|
|
107
|
+
issues.push(...this.checkLongLine(file, lines))
|
|
108
|
+
issues.push(...this.checkTODO(file, lines))
|
|
109
|
+
} catch (error) {
|
|
110
|
+
// 忽略无法读取的文件
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return issues
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 检查尾随空格
|
|
119
|
+
*/
|
|
120
|
+
checkTrailingSpaces(file, lines) {
|
|
121
|
+
const issues = []
|
|
122
|
+
lines.forEach((line, index) => {
|
|
123
|
+
if (/\s+$/.test(line) && line.trim().length > 0) {
|
|
124
|
+
issues.push({
|
|
125
|
+
file,
|
|
126
|
+
line: index + 1,
|
|
127
|
+
type: 'trailing-space',
|
|
128
|
+
message: 'Remove trailing whitespace',
|
|
129
|
+
fix: (content) => content.replace(/[ \t]+$/gm, '')
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
return issues
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 检查 console.log
|
|
138
|
+
*/
|
|
139
|
+
checkConsoleLog(file, lines) {
|
|
140
|
+
const issues = []
|
|
141
|
+
lines.forEach((line, index) => {
|
|
142
|
+
if (line.includes('console.log') || line.includes('console.error')) {
|
|
143
|
+
issues.push({
|
|
144
|
+
file,
|
|
145
|
+
line: index + 1,
|
|
146
|
+
type: 'console-log',
|
|
147
|
+
message: 'Remove console statement',
|
|
148
|
+
fix: (content) => {
|
|
149
|
+
const lines = content.split('\n')
|
|
150
|
+
if (lines[index].includes('console.log')) {
|
|
151
|
+
lines[index] = lines[index].replace(/console\.log\([^)]*\);?/, '// TODO: Remove console.log')
|
|
152
|
+
}
|
|
153
|
+
if (lines[index].includes('console.error')) {
|
|
154
|
+
lines[index] = lines[index].replace(/console\.error\([^)]*\);?/, '// TODO: Remove console.error')
|
|
155
|
+
}
|
|
156
|
+
return lines.join('\n')
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
return issues
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 检查 debugger
|
|
166
|
+
*/
|
|
167
|
+
checkDebugger(file, lines) {
|
|
168
|
+
const issues = []
|
|
169
|
+
lines.forEach((line, index) => {
|
|
170
|
+
if (line.trim() === 'debugger;') {
|
|
171
|
+
issues.push({
|
|
172
|
+
file,
|
|
173
|
+
line: index + 1,
|
|
174
|
+
type: 'debugger',
|
|
175
|
+
message: 'Remove debugger statement',
|
|
176
|
+
fix: (content) => {
|
|
177
|
+
const lines = content.split('\n')
|
|
178
|
+
lines[index] = lines[index].replace(/^\s*debugger;\s*$/, '// TODO: Remove debugger')
|
|
179
|
+
return lines.join('\n')
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
return issues
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 检查缺失分号
|
|
189
|
+
*/
|
|
190
|
+
checkMissingSemicolon(file, lines) {
|
|
191
|
+
const issues = []
|
|
192
|
+
lines.forEach((line, index) => {
|
|
193
|
+
const trimmed = line.trim()
|
|
194
|
+
|
|
195
|
+
// 跳过注释、空行、函数定义等
|
|
196
|
+
if (trimmed.startsWith('//') ||
|
|
197
|
+
trimmed.startsWith('/*') ||
|
|
198
|
+
trimmed === '' ||
|
|
199
|
+
trimmed.endsWith(';') ||
|
|
200
|
+
trimmed.endsWith('{') ||
|
|
201
|
+
trimmed.endsWith('}') ||
|
|
202
|
+
trimmed.includes('function') ||
|
|
203
|
+
trimmed.includes('if') ||
|
|
204
|
+
trimmed.includes('for') ||
|
|
205
|
+
trimmed.includes('while')) {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 检查可能需要分号的语句
|
|
210
|
+
if (trimmed.includes('var ') ||
|
|
211
|
+
trimmed.includes('let ') ||
|
|
212
|
+
trimmed.includes('const ') ||
|
|
213
|
+
/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*[^;]+$/.test(trimmed)) {
|
|
214
|
+
issues.push({
|
|
215
|
+
file,
|
|
216
|
+
line: index + 1,
|
|
217
|
+
type: 'missing-semicolon',
|
|
218
|
+
message: 'Add missing semicolon',
|
|
219
|
+
fix: (content) => {
|
|
220
|
+
const lines = content.split('\n')
|
|
221
|
+
lines[index] = lines[index].trimEnd() + ';'
|
|
222
|
+
return lines.join('\n')
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
return issues
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 检查 var 声明(建议使用 let/const)
|
|
232
|
+
*/
|
|
233
|
+
checkVarDeclaration(file, lines) {
|
|
234
|
+
const issues = []
|
|
235
|
+
lines.forEach((line, index) => {
|
|
236
|
+
if (line.trim().startsWith('var ')) {
|
|
237
|
+
issues.push({
|
|
238
|
+
file,
|
|
239
|
+
line: index + 1,
|
|
240
|
+
type: 'var-declaration',
|
|
241
|
+
message: 'Consider using let or const instead of var',
|
|
242
|
+
fix: (content) => {
|
|
243
|
+
const lines = content.split('\n')
|
|
244
|
+
lines[index] = lines[index].replace(/^(\s*)var\s+/, '$1let ')
|
|
245
|
+
return lines.join('\n')
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
return issues
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 检查长行
|
|
255
|
+
*/
|
|
256
|
+
checkLongLine(file, lines) {
|
|
257
|
+
const issues = []
|
|
258
|
+
const maxLength = 120
|
|
259
|
+
|
|
260
|
+
lines.forEach((line, index) => {
|
|
261
|
+
if (line.length > maxLength) {
|
|
262
|
+
issues.push({
|
|
263
|
+
file,
|
|
264
|
+
line: index + 1,
|
|
265
|
+
type: 'long-line',
|
|
266
|
+
message: `Line exceeds ${maxLength} characters`,
|
|
267
|
+
fix: null // 需要手动处理
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
return issues
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 检查 TODO 注释
|
|
276
|
+
*/
|
|
277
|
+
checkTODO(file, lines) {
|
|
278
|
+
const issues = []
|
|
279
|
+
lines.forEach((line, index) => {
|
|
280
|
+
if (line.includes('TODO') || line.includes('FIXME')) {
|
|
281
|
+
issues.push({
|
|
282
|
+
file,
|
|
283
|
+
line: index + 1,
|
|
284
|
+
type: 'todo-comment',
|
|
285
|
+
message: 'TODO/FIXME comment found',
|
|
286
|
+
fix: null // 不自动修复
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
return issues
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* 应用修复
|
|
295
|
+
*/
|
|
296
|
+
async applyFixes(issues, files) {
|
|
297
|
+
const appliedFixes = []
|
|
298
|
+
const fileFixes = {}
|
|
299
|
+
|
|
300
|
+
// 按文件分组
|
|
301
|
+
for (const issue of issues) {
|
|
302
|
+
if (!issue.fix) continue // 无法自动修复的问题
|
|
303
|
+
|
|
304
|
+
if (!fileFixes[issue.file]) {
|
|
305
|
+
fileFixes[issue.file] = []
|
|
306
|
+
}
|
|
307
|
+
fileFixes[issue.file].push(issue)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 应用修复
|
|
311
|
+
for (const [file, fileIssues] of Object.entries(fileFixes)) {
|
|
312
|
+
try {
|
|
313
|
+
let content = await fs.readFile(file, 'utf8')
|
|
314
|
+
|
|
315
|
+
// 按行号倒序排列,避免行号偏移
|
|
316
|
+
fileIssues.sort((a, b) => b.line - a.line)
|
|
317
|
+
|
|
318
|
+
for (const issue of fileIssues) {
|
|
319
|
+
try {
|
|
320
|
+
const originalContent = content
|
|
321
|
+
content = issue.fix(content)
|
|
322
|
+
|
|
323
|
+
if (content !== originalContent) {
|
|
324
|
+
appliedFixes.push(issue)
|
|
325
|
+
}
|
|
326
|
+
} catch (e) {
|
|
327
|
+
// 修复失败,跳过
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 写入文件
|
|
332
|
+
await fs.writeFile(file, content, 'utf8')
|
|
333
|
+
} catch (error) {
|
|
334
|
+
// 写入失败,跳过
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
success: true,
|
|
340
|
+
dryRun: false,
|
|
341
|
+
totalIssues: issues.length,
|
|
342
|
+
applied: appliedFixes.length,
|
|
343
|
+
fixes: appliedFixes,
|
|
344
|
+
skipped: issues.length - appliedFixes.length
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 生成修复报告
|
|
350
|
+
*/
|
|
351
|
+
generateReport(result) {
|
|
352
|
+
const timestamp = new Date().toLocaleString()
|
|
353
|
+
|
|
354
|
+
let report = `# 自动修复报告\n\n`
|
|
355
|
+
report += `**生成时间**: ${timestamp}\n`
|
|
356
|
+
report += `**模式**: ${result.dryRun ? '预览' : '实际修复'}\n\n`
|
|
357
|
+
|
|
358
|
+
report += `## 统计\n\n`
|
|
359
|
+
report += `- 检测到问题: ${result.totalIssues}\n`
|
|
360
|
+
report += `- 已修复: ${result.applied}\n`
|
|
361
|
+
report += `- 跳过: ${result.skipped}\n\n`
|
|
362
|
+
|
|
363
|
+
if (result.fixes.length > 0) {
|
|
364
|
+
report += `## 修复详情\n\n`
|
|
365
|
+
result.fixes.forEach(fix => {
|
|
366
|
+
report += `- **${fix.file}:${fix.line}**\n`
|
|
367
|
+
report += ` - 类型: ${fix.type}\n`
|
|
368
|
+
report += ` - 问题: ${fix.message}\n`
|
|
369
|
+
report += ` - 状态: ✅ 已修复\n\n`
|
|
370
|
+
})
|
|
371
|
+
} else {
|
|
372
|
+
report += `## 修复详情\n\n`
|
|
373
|
+
report += `没有可自动修复的问题。\n\n`
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
report += `---\n*由 Code-Simplifier 自动修复生成*\n`
|
|
377
|
+
|
|
378
|
+
return report
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
module.exports = new AutoFix()
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Code-Simplifier ESLint 集成
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { exec } = require('child_process')
|
|
8
|
+
const util = require('util')
|
|
9
|
+
const execPromise = util.promisify(exec)
|
|
10
|
+
const fs = require('fs-extra')
|
|
11
|
+
const path = require('path')
|
|
12
|
+
const chalk = require('chalk')
|
|
13
|
+
const ora = require('ora')
|
|
14
|
+
|
|
15
|
+
class ESLintIntegration {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.eslintConfig = null
|
|
18
|
+
this.eslintResults = []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 检查是否支持 ESLint
|
|
23
|
+
*/
|
|
24
|
+
async isSupported(projectDir) {
|
|
25
|
+
try {
|
|
26
|
+
// 检查是否有 eslint 配置文件
|
|
27
|
+
const configFiles = [
|
|
28
|
+
'.eslintrc.js',
|
|
29
|
+
'.eslintrc.yaml',
|
|
30
|
+
'.eslintrc.yml',
|
|
31
|
+
'.eslintrc.json',
|
|
32
|
+
'.eslintrc',
|
|
33
|
+
'package.json'
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
for (const configFile of configFiles) {
|
|
37
|
+
const configPath = path.join(projectDir, configFile)
|
|
38
|
+
if (await fs.pathExists(configPath)) {
|
|
39
|
+
if (configFile === 'package.json') {
|
|
40
|
+
const pkg = await fs.readJson(configPath)
|
|
41
|
+
if (pkg.eslintConfig) {
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 检查是否安装了 eslint
|
|
51
|
+
try {
|
|
52
|
+
await execPromise('npx eslint --version', { cwd: projectDir })
|
|
53
|
+
return true
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 运行 ESLint 分析
|
|
64
|
+
*/
|
|
65
|
+
async runAnalysis(projectDir, options = {}) {
|
|
66
|
+
console.log(chalk.blue.bold('\n🔍 ESLint 代码分析'))
|
|
67
|
+
console.log(chalk.gray('='.repeat(70)))
|
|
68
|
+
|
|
69
|
+
const spinner = ora(chalk.blue('正在运行 ESLint...')).start()
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const {
|
|
73
|
+
format = 'json',
|
|
74
|
+
fix = false,
|
|
75
|
+
files = '**/*.{js,jsx,ts,tsx,vue}',
|
|
76
|
+
ignorePath = '.gitignore'
|
|
77
|
+
} = options
|
|
78
|
+
|
|
79
|
+
// 构建 eslint 命令
|
|
80
|
+
let cmd = `npx eslint --format ${format} `
|
|
81
|
+
if (fix) cmd += '--fix '
|
|
82
|
+
cmd += `--ignore-pattern "${ignorePath}" `
|
|
83
|
+
cmd += `"${files}"`
|
|
84
|
+
|
|
85
|
+
const { stdout } = await execPromise(cmd, { cwd: projectDir })
|
|
86
|
+
|
|
87
|
+
spinner.succeed(chalk.green('ESLint 分析完成'))
|
|
88
|
+
|
|
89
|
+
// 解析结果
|
|
90
|
+
let results = []
|
|
91
|
+
if (format === 'json') {
|
|
92
|
+
results = JSON.parse(stdout)
|
|
93
|
+
} else {
|
|
94
|
+
results = this.parseTextOutput(stdout)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.eslintResults = results
|
|
98
|
+
|
|
99
|
+
// 生成分析报告
|
|
100
|
+
const report = this.generateReport(results)
|
|
101
|
+
this.printSummary(results)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
results,
|
|
106
|
+
report,
|
|
107
|
+
summary: this.getSummary(results)
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error.stdout) {
|
|
111
|
+
// ESLint 返回非零退出码但有输出
|
|
112
|
+
try {
|
|
113
|
+
const results = JSON.parse(error.stdout)
|
|
114
|
+
spinner.succeed(chalk.green('ESLint 分析完成'))
|
|
115
|
+
this.eslintResults = results
|
|
116
|
+
this.printSummary(results)
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
results,
|
|
120
|
+
report: this.generateReport(results),
|
|
121
|
+
summary: this.getSummary(results)
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
// 解析失败
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
spinner.fail(chalk.red('ESLint 分析失败'))
|
|
129
|
+
console.error(chalk.gray(error.message))
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
error: error.message,
|
|
133
|
+
results: []
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 自动修复问题
|
|
140
|
+
*/
|
|
141
|
+
async autoFix(projectDir, options = {}) {
|
|
142
|
+
console.log(chalk.blue.bold('\n🔧 自动修复 ESLint 问题'))
|
|
143
|
+
console.log(chalk.gray('='.repeat(70)))
|
|
144
|
+
|
|
145
|
+
const spinner = ora(chalk.blue('正在自动修复...')).start()
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const { files = '**/*.{js,jsx,ts,tsx,vue}' } = options
|
|
149
|
+
|
|
150
|
+
const cmd = `npx eslint --fix "${files}"`
|
|
151
|
+
const { stdout } = await execPromise(cmd, { cwd: projectDir })
|
|
152
|
+
|
|
153
|
+
spinner.succeed(chalk.green('自动修复完成'))
|
|
154
|
+
|
|
155
|
+
console.log(chalk.gray(stdout))
|
|
156
|
+
console.log(chalk.green('\n✅ 修复完成,请检查修改'))
|
|
157
|
+
|
|
158
|
+
return { success: true, output: stdout }
|
|
159
|
+
} catch (error) {
|
|
160
|
+
spinner.fail(chalk.red('自动修复失败'))
|
|
161
|
+
console.error(chalk.red(error.message))
|
|
162
|
+
return { success: false, error: error.message }
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 生成报告
|
|
168
|
+
*/
|
|
169
|
+
generateReport(results) {
|
|
170
|
+
const timestamp = new Date().toLocaleString()
|
|
171
|
+
|
|
172
|
+
let totalErrors = 0
|
|
173
|
+
let totalWarnings = 0
|
|
174
|
+
let fixedCount = 0
|
|
175
|
+
|
|
176
|
+
results.forEach(file => {
|
|
177
|
+
totalErrors += file.errorCount || 0
|
|
178
|
+
totalWarnings += file.warningCount || 0
|
|
179
|
+
fixedCount += file.fixedCount || 0
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
return `# ESLint 分析报告
|
|
183
|
+
|
|
184
|
+
**生成时间**: ${timestamp}
|
|
185
|
+
|
|
186
|
+
## 概览
|
|
187
|
+
|
|
188
|
+
- **文件总数**: ${results.length}
|
|
189
|
+
- **错误数**: ${totalErrors}
|
|
190
|
+
- **警告数**: ${totalWarnings}
|
|
191
|
+
- **已修复**: ${fixedCount}
|
|
192
|
+
|
|
193
|
+
## 文件详情
|
|
194
|
+
|
|
195
|
+
${results.map(file => {
|
|
196
|
+
return `### ${file.filePath}
|
|
197
|
+
|
|
198
|
+
- 错误: ${file.errorCount}
|
|
199
|
+
- 警告: ${file.warningCount}
|
|
200
|
+
- 修复: ${file.fixedCount}
|
|
201
|
+
|
|
202
|
+
${file.messages.map(msg => {
|
|
203
|
+
const severity = msg.severity === 2 ? '❌ 错误' : '⚠️ 警告'
|
|
204
|
+
return `- ${severity}: ${msg.message} (${msg.ruleId}) at line ${msg.line}`
|
|
205
|
+
}).join('\n')}
|
|
206
|
+
`
|
|
207
|
+
}).join('\n')}
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
*由 Code-Simplifier ESLint 集成生成*
|
|
211
|
+
`
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 打印摘要
|
|
216
|
+
*/
|
|
217
|
+
printSummary(results) {
|
|
218
|
+
const summary = this.getSummary(results)
|
|
219
|
+
|
|
220
|
+
console.log(chalk.cyan('\n📊 ESLint 分析结果:'))
|
|
221
|
+
console.log(chalk.cyan(` 文件总数: ${summary.totalFiles}`))
|
|
222
|
+
console.log(chalk.red(` 错误: ${summary.totalErrors}`))
|
|
223
|
+
console.log(chalk.yellow(` 警告: ${summary.totalWarnings}`))
|
|
224
|
+
console.log(chalk.green(` 已修复: ${summary.fixedCount}`))
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 获取摘要
|
|
229
|
+
*/
|
|
230
|
+
getSummary(results) {
|
|
231
|
+
return {
|
|
232
|
+
totalFiles: results.length,
|
|
233
|
+
totalErrors: results.reduce((sum, file) => sum + (file.errorCount || 0), 0),
|
|
234
|
+
totalWarnings: results.reduce((sum, file) => sum + (file.warningCount || 0), 0),
|
|
235
|
+
fixedCount: results.reduce((sum, file) => sum + (file.fixedCount || 0), 0)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 解析文本输出
|
|
241
|
+
*/
|
|
242
|
+
parseTextOutput(stdout) {
|
|
243
|
+
// 简单的文本解析逻辑
|
|
244
|
+
const lines = stdout.split('\n')
|
|
245
|
+
const results = []
|
|
246
|
+
|
|
247
|
+
let currentFile = null
|
|
248
|
+
let currentMessages = []
|
|
249
|
+
|
|
250
|
+
for (const line of lines) {
|
|
251
|
+
const match = line.match(/^(.+?):\s+(\d+):(\d+)\s+(error|warning)\s+(.+?)(?:\s+\((.+?)\))?$/)
|
|
252
|
+
if (match) {
|
|
253
|
+
const [_, filePath, line, col, severity, message, ruleId] = match
|
|
254
|
+
|
|
255
|
+
if (currentFile !== filePath) {
|
|
256
|
+
if (currentFile) {
|
|
257
|
+
results.push({
|
|
258
|
+
filePath: currentFile,
|
|
259
|
+
messages: currentMessages,
|
|
260
|
+
errorCount: currentMessages.filter(m => m.severity === 2).length,
|
|
261
|
+
warningCount: currentMessages.filter(m => m.severity === 1).length
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
currentFile = filePath
|
|
265
|
+
currentMessages = []
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
currentMessages.push({
|
|
269
|
+
line: parseInt(line),
|
|
270
|
+
column: parseInt(col),
|
|
271
|
+
severity: severity === 'error' ? 2 : 1,
|
|
272
|
+
message,
|
|
273
|
+
ruleId
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (currentFile) {
|
|
279
|
+
results.push({
|
|
280
|
+
filePath: currentFile,
|
|
281
|
+
messages: currentMessages,
|
|
282
|
+
errorCount: currentMessages.filter(m => m.severity === 2).length,
|
|
283
|
+
warningCount: currentMessages.filter(m => m.severity === 1).length
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return results
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* 集成到质量分析
|
|
292
|
+
*/
|
|
293
|
+
integrateWithQualityAnalysis(eslintResult, qualityResult) {
|
|
294
|
+
if (!eslintResult || !eslintResult.success) {
|
|
295
|
+
return qualityResult
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const eslintSummary = eslintResult.summary
|
|
299
|
+
|
|
300
|
+
// 根据 ESLint 结果调整质量评分
|
|
301
|
+
let adjustedScore = qualityResult.score
|
|
302
|
+
|
|
303
|
+
// 错误扣分(每个错误扣 2 分)
|
|
304
|
+
adjustedScore -= eslintSummary.totalErrors * 2
|
|
305
|
+
|
|
306
|
+
// 警告扣分(每个警告扣 0.5 分)
|
|
307
|
+
adjustedScore -= eslintSummary.totalWarnings * 0.5
|
|
308
|
+
|
|
309
|
+
// 限制分数范围
|
|
310
|
+
adjustedScore = Math.max(0, Math.min(100, adjustedScore))
|
|
311
|
+
|
|
312
|
+
// 更新结果
|
|
313
|
+
return {
|
|
314
|
+
...qualityResult,
|
|
315
|
+
score: Math.round(adjustedScore),
|
|
316
|
+
eslint: {
|
|
317
|
+
totalFiles: eslintSummary.totalFiles,
|
|
318
|
+
totalErrors: eslintSummary.totalErrors,
|
|
319
|
+
totalWarnings: eslintSummary.totalWarnings,
|
|
320
|
+
fixedCount: eslintSummary.fixedCount,
|
|
321
|
+
results: eslintResult.results
|
|
322
|
+
},
|
|
323
|
+
totalIssues: qualityResult.totalIssues + eslintSummary.totalErrors + eslintSummary.totalWarnings
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = new ESLintIntegration()
|