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.
@@ -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()