code-abyss 1.7.0 → 1.7.2

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.
@@ -3,6 +3,7 @@
3
3
 
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
+ const { parseCliArgs, buildReport, hasFatal } = require(path.join(__dirname, '..', '..', 'lib', 'shared.js'));
6
7
 
7
8
  // 质量规则配置
8
9
  const MAX_LINE_LENGTH = 120;
@@ -15,44 +16,79 @@ const MIN_FUNCTION_NAME_LENGTH = 2;
15
16
  const EXCLUDE_DIRS = new Set(['.git', 'node_modules', '__pycache__', '.venv', 'venv', 'dist', 'build', '.tox']);
16
17
  const CODE_EXTENSIONS = new Set(['.py', '.js', '.ts', '.go', '.java', '.rs', '.c', '.cpp']);
17
18
 
18
- const COMMENT_PREFIXES = { '.js': '//', '.ts': '//', '.go': '//', '.java': '//', '.c': '//', '.cpp': '//', '.rs': '//' };
19
+ const COMMENT_PREFIXES = {
20
+ '.js': '//', '.ts': '//', '.go': '//', '.java': '//',
21
+ '.c': '//', '.cpp': '//', '.rs': '//',
22
+ };
19
23
 
20
24
  // --- Analysis ---
21
25
 
22
26
  function analyzeGenericFile(filePath) {
23
- const metrics = { path: filePath, lines: 0, code_lines: 0, comment_lines: 0, blank_lines: 0, functions: 0, classes: 0, max_complexity: 0, avg_function_length: 0 };
27
+ const metrics = {
28
+ path: filePath, lines: 0, code_lines: 0, comment_lines: 0,
29
+ blank_lines: 0, functions: 0, classes: 0,
30
+ max_complexity: 0, avg_function_length: 0,
31
+ };
24
32
  const issues = [];
25
33
  let content;
26
- try { content = fs.readFileSync(filePath, 'utf-8'); } catch { return { metrics, issues }; }
34
+ try {
35
+ content = fs.readFileSync(filePath, 'utf-8');
36
+ } catch { return { metrics, issues }; }
27
37
 
28
38
  const lines = content.split('\n');
29
39
  metrics.lines = lines.length;
30
- const prefix = COMMENT_PREFIXES[path.extname(filePath).toLowerCase()] || '//';
40
+ const prefix = COMMENT_PREFIXES[
41
+ path.extname(filePath).toLowerCase()
42
+ ] || '//';
31
43
 
32
44
  for (let i = 0; i < lines.length; i++) {
33
45
  const stripped = lines[i].trim();
34
46
  if (!stripped) metrics.blank_lines++;
35
- else if (stripped.startsWith(prefix) || stripped.startsWith('/*') || stripped.startsWith('*')) metrics.comment_lines++;
47
+ else if (
48
+ stripped.startsWith(prefix) ||
49
+ stripped.startsWith('/*') ||
50
+ stripped.startsWith('*')
51
+ ) metrics.comment_lines++;
36
52
  else metrics.code_lines++;
37
53
 
38
54
  if (lines[i].length > MAX_LINE_LENGTH) {
39
- issues.push({ severity: 'info', category: '格式', message: `行过长 (${lines[i].length} > ${MAX_LINE_LENGTH})`, file_path: filePath, line_number: i + 1, suggestion: null });
55
+ issues.push({
56
+ severity: 'info', category: '格式',
57
+ message: `行过长 (${lines[i].length} > ${MAX_LINE_LENGTH})`,
58
+ file_path: filePath, line_number: i + 1,
59
+ suggestion: null,
60
+ });
40
61
  }
41
62
  }
42
63
 
43
64
  if (metrics.code_lines > MAX_FILE_LENGTH) {
44
- issues.push({ severity: 'warning', category: '复杂度', message: `文件过长 (${metrics.code_lines} 行代码 > ${MAX_FILE_LENGTH})`, file_path: filePath, suggestion: '考虑拆分为多个模块', line_number: null });
65
+ issues.push({
66
+ severity: 'warning', category: '复杂度',
67
+ message: `文件过长 (${metrics.code_lines} 行代码 > ${MAX_FILE_LENGTH})`,
68
+ file_path: filePath, suggestion: '考虑拆分为多个模块',
69
+ line_number: null,
70
+ });
45
71
  }
46
72
 
47
73
  return { metrics, issues };
48
74
  }
49
75
 
50
76
  function analyzePythonFile(filePath) {
51
- const metrics = { path: filePath, lines: 0, code_lines: 0, comment_lines: 0, blank_lines: 0, functions: 0, classes: 0, max_complexity: 0, avg_function_length: 0 };
77
+ const metrics = {
78
+ path: filePath, lines: 0, code_lines: 0, comment_lines: 0,
79
+ blank_lines: 0, functions: 0, classes: 0,
80
+ max_complexity: 0, avg_function_length: 0,
81
+ };
52
82
  const issues = [];
53
83
  let content;
54
- try { content = fs.readFileSync(filePath, 'utf-8'); } catch (e) {
55
- issues.push({ severity: 'error', category: '文件', message: `无法读取文件: ${e.message}`, file_path: filePath, line_number: null, suggestion: null });
84
+ try {
85
+ content = fs.readFileSync(filePath, 'utf-8');
86
+ } catch (e) {
87
+ issues.push({
88
+ severity: 'error', category: '文件',
89
+ message: `无法读取文件: ${e.message}`,
90
+ file_path: filePath, line_number: null, suggestion: null,
91
+ });
56
92
  return { metrics, issues };
57
93
  }
58
94
 
@@ -73,12 +109,22 @@ function analyzePythonFile(filePath) {
73
109
  else { metrics.code_lines++; }
74
110
 
75
111
  if (lines[i].length > MAX_LINE_LENGTH) {
76
- issues.push({ severity: 'info', category: '格式', message: `行过长 (${lines[i].length} > ${MAX_LINE_LENGTH})`, file_path: filePath, line_number: i + 1, suggestion: null });
112
+ issues.push({
113
+ severity: 'info', category: '格式',
114
+ message: `行过长 (${lines[i].length} > ${MAX_LINE_LENGTH})`,
115
+ file_path: filePath, line_number: i + 1,
116
+ suggestion: null,
117
+ });
77
118
  }
78
119
  }
79
120
 
80
121
  if (metrics.code_lines > MAX_FILE_LENGTH) {
81
- issues.push({ severity: 'warning', category: '复杂度', message: `文件过长 (${metrics.code_lines} 行代码 > ${MAX_FILE_LENGTH})`, file_path: filePath, suggestion: '考虑拆分为多个模块', line_number: null });
122
+ issues.push({
123
+ severity: 'warning', category: '复杂度',
124
+ message: `文件过长 (${metrics.code_lines} 行代码 > ${MAX_FILE_LENGTH})`,
125
+ file_path: filePath, suggestion: '考虑拆分为多个模块',
126
+ line_number: null,
127
+ });
82
128
  }
83
129
 
84
130
  // Regex-based Python analysis (no AST available in Node)
@@ -91,7 +137,10 @@ function analyzePythonFile(filePath) {
91
137
  const lineNum = content.substring(0, match.index).split('\n').length;
92
138
  const name = match[2];
93
139
  const indent = match[1].length;
94
- const params = match[3].trim() ? match[3].split(',').map(p => p.trim()).filter(p => p && p !== 'self' && p !== 'cls') : [];
140
+ const params = match[3].trim()
141
+ ? match[3].split(',').map(p => p.trim())
142
+ .filter(p => p && p !== 'self' && p !== 'cls')
143
+ : [];
95
144
 
96
145
  // Calculate function length by finding next line at same or lesser indent
97
146
  const funcLines = lines.slice(lineNum); // lines after def
@@ -120,25 +169,53 @@ function analyzePythonFile(filePath) {
120
169
 
121
170
  // Check function length
122
171
  if (length > MAX_FUNCTION_LENGTH) {
123
- issues.push({ severity: 'warning', category: '复杂度', message: `函数 '${name}' 过长 (${length} 行 > ${MAX_FUNCTION_LENGTH})`, file_path: filePath, line_number: lineNum, suggestion: '考虑拆分为多个小函数' });
172
+ issues.push({
173
+ severity: 'warning', category: '复杂度',
174
+ message: `函数 '${name}' 过长 (${length} 行 > ${MAX_FUNCTION_LENGTH})`,
175
+ file_path: filePath, line_number: lineNum,
176
+ suggestion: '考虑拆分为多个小函数',
177
+ });
124
178
  }
125
179
  // Check complexity
126
180
  if (complexity > MAX_COMPLEXITY) {
127
- issues.push({ severity: 'warning', category: '复杂度', message: `函数 '${name}' 圈复杂度过高 (${complexity} > ${MAX_COMPLEXITY})`, file_path: filePath, line_number: lineNum, suggestion: '减少嵌套层级,提取子函数' });
181
+ issues.push({
182
+ severity: 'warning', category: '复杂度',
183
+ message: `函数 '${name}' 圈复杂度过高 (${complexity} > ${MAX_COMPLEXITY})`,
184
+ file_path: filePath, line_number: lineNum,
185
+ suggestion: '减少嵌套层级,提取子函数',
186
+ });
128
187
  }
129
188
  // Check parameter count
130
189
  if (params.length > MAX_PARAMETERS) {
131
- issues.push({ severity: 'warning', category: '设计', message: `函数 '${name}' 参数过多 (${params.length} > ${MAX_PARAMETERS})`, file_path: filePath, line_number: lineNum, suggestion: '考虑使用配置对象或数据类封装参数' });
190
+ issues.push({
191
+ severity: 'warning', category: '设计',
192
+ message: `函数 '${name}' 参数过多 (${params.length} > ${MAX_PARAMETERS})`,
193
+ file_path: filePath, line_number: lineNum,
194
+ suggestion: '考虑使用配置对象或数据类封装参数',
195
+ });
132
196
  }
133
197
  // Check naming
134
- const SPECIAL = new Set(['setUp', 'tearDown', 'setUpClass', 'tearDownClass', 'setUpModule', 'tearDownModule']);
198
+ const SPECIAL = new Set([
199
+ 'setUp', 'tearDown', 'setUpClass',
200
+ 'tearDownClass', 'setUpModule', 'tearDownModule',
201
+ ]);
135
202
  if (!name.startsWith('_') && !SPECIAL.has(name) && !name.startsWith('visit_')) {
136
203
  if (!/^[a-z][a-z0-9_]*$/.test(name)) {
137
- issues.push({ severity: 'info', category: '命名', message: `函数名 '${name}' 不符合 snake_case 规范`, file_path: filePath, line_number: lineNum, suggestion: '函数名应使用 snake_case,如 my_function_name' });
204
+ issues.push({
205
+ severity: 'info', category: '命名',
206
+ message: `函数名 '${name}' 不符合 snake_case 规范`,
207
+ file_path: filePath, line_number: lineNum,
208
+ suggestion: '函数名应使用 snake_case',
209
+ });
138
210
  }
139
211
  }
140
212
  if (name.length < MIN_FUNCTION_NAME_LENGTH) {
141
- issues.push({ severity: 'warning', category: '命名', message: `函数名 '${name}' 过短`, file_path: filePath, line_number: lineNum, suggestion: '使用更具描述性的函数名' });
213
+ issues.push({
214
+ severity: 'warning', category: '命名',
215
+ message: `函数名 '${name}' 过短`,
216
+ file_path: filePath, line_number: lineNum,
217
+ suggestion: '使用更具描述性的函数名',
218
+ });
142
219
  }
143
220
  }
144
221
 
@@ -147,7 +224,12 @@ function analyzePythonFile(filePath) {
147
224
  const name = match[2];
148
225
  metrics.classes++;
149
226
  if (!/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
150
- issues.push({ severity: 'warning', category: '命名', message: `类名 '${name}' 不符合 PascalCase 规范`, file_path: filePath, line_number: lineNum, suggestion: '类名应使用 PascalCase,如 MyClassName' });
227
+ issues.push({
228
+ severity: 'warning', category: '命名',
229
+ message: `类名 '${name}' 不符合 PascalCase 规范`,
230
+ file_path: filePath, line_number: lineNum,
231
+ suggestion: '类名应使用 PascalCase,如 MyClassName',
232
+ });
151
233
  }
152
234
  }
153
235
 
@@ -164,7 +246,11 @@ function analyzePythonFile(filePath) {
164
246
  function scanDirectory(scanPath, excludeDirs) {
165
247
  const resolved = path.resolve(scanPath);
166
248
  const exclude = excludeDirs || EXCLUDE_DIRS;
167
- const result = { scan_path: resolved, files_scanned: 0, total_lines: 0, total_code_lines: 0, issues: [], file_metrics: [] };
249
+ const result = {
250
+ scan_path: resolved, files_scanned: 0,
251
+ total_lines: 0, total_code_lines: 0,
252
+ issues: [], file_metrics: [],
253
+ };
168
254
 
169
255
  function walk(dir) {
170
256
  let entries;
@@ -191,83 +277,58 @@ function scanDirectory(scanPath, excludeDirs) {
191
277
 
192
278
  // --- Reporting ---
193
279
 
194
- function errorCount(result) { return result.issues.filter(i => i.severity === 'error').length; }
195
- function warningCount(result) { return result.issues.filter(i => i.severity === 'warning').length; }
196
- function passed(result) { return !result.issues.some(i => i.severity === 'error'); }
280
+ function passed(result) { return !hasFatal(result.issues); }
197
281
 
198
282
  function formatReport(result, verbose) {
199
- const lines = [];
200
- const sep = '='.repeat(60);
201
- const dash = '-'.repeat(40);
202
- lines.push(sep, '代码质量检查报告', sep);
203
- lines.push(`\n扫描路径: ${result.scan_path}`);
204
- lines.push(`扫描文件: ${result.files_scanned}`);
205
- lines.push(`总行数: ${result.total_lines}`);
206
- lines.push(`代码行数: ${result.total_code_lines}`);
207
- lines.push(`检查结果: ${passed(result) ? '✓ 通过' : '✗ 需要关注'}`);
208
- lines.push(`错误: ${errorCount(result)} | 警告: ${warningCount(result)}`);
209
-
210
- if (result.issues.length) {
211
- lines.push('\n' + dash, '问题列表:', dash);
212
- const byCategory = {};
213
- for (const issue of result.issues) {
214
- (byCategory[issue.category] || (byCategory[issue.category] = [])).push(issue);
215
- }
216
- const icons = { error: '✗', warning: '⚠', info: 'ℹ' };
217
- for (const cat of Object.keys(byCategory).sort()) {
218
- const catIssues = byCategory[cat];
219
- lines.push(`\n【${cat}】(${catIssues.length} 个)`);
220
- for (const issue of catIssues.slice(0, 10)) {
221
- const icon = icons[issue.severity];
222
- const loc = issue.line_number ? `:${issue.line_number}` : '';
223
- lines.push(` ${icon} ${issue.file_path}${loc}`);
224
- lines.push(` ${issue.message}`);
225
- if (verbose && issue.suggestion) lines.push(` 💡 ${issue.suggestion}`);
226
- }
227
- if (catIssues.length > 10) lines.push(` ... 及其他 ${catIssues.length - 10} 个问题`);
228
- }
229
- }
283
+ const errs = result.issues.filter(i => i.severity === 'error').length;
284
+ const warns = result.issues.filter(i => i.severity === 'warning').length;
285
+ const fields = {
286
+ '扫描路径': result.scan_path,
287
+ '扫描文件': result.files_scanned,
288
+ '总行数': result.total_lines,
289
+ '代码行数': result.total_code_lines,
290
+ '检查结果': passed(result) ? '✓ 通过' : '✗ 需要关注',
291
+ '统计': `错误: ${errs} | 警告: ${warns}`,
292
+ };
293
+ let report = buildReport(
294
+ '代码质量检查报告', fields, result.issues, verbose, 'category'
295
+ );
230
296
 
231
297
  if (verbose && result.file_metrics.length) {
232
- const complex = result.file_metrics.filter(m => m.max_complexity > 0).sort((a, b) => b.max_complexity - a.max_complexity).slice(0, 5);
298
+ const complex = result.file_metrics
299
+ .filter(m => m.max_complexity > 0)
300
+ .sort((a, b) => b.max_complexity - a.max_complexity)
301
+ .slice(0, 5);
233
302
  if (complex.length) {
234
- lines.push('\n' + dash, '复杂度最高的文件:', dash);
303
+ const lines = ['\n' + '-'.repeat(40), '复杂度最高的文件:', '-'.repeat(40)];
235
304
  for (const m of complex) lines.push(` ${m.path}: 复杂度 ${m.max_complexity}, ${m.functions} 个函数`);
305
+ report += '\n' + lines.join('\n');
236
306
  }
237
307
  }
238
-
239
- lines.push('\n' + sep);
240
- return lines.join('\n');
308
+ return report;
241
309
  }
242
310
 
243
311
  // --- CLI ---
244
312
 
245
313
  function main() {
246
- const args = process.argv.slice(2);
247
- let scanPath = '.', verbose = false, jsonOutput = false;
248
-
249
- for (let i = 0; i < args.length; i++) {
250
- if (args[i] === '-v' || args[i] === '--verbose') verbose = true;
251
- else if (args[i] === '--json') jsonOutput = true;
252
- else if (!args[i].startsWith('-')) scanPath = args[i];
253
- }
314
+ const opts = parseCliArgs(process.argv);
254
315
 
255
- const result = scanDirectory(scanPath);
316
+ const result = scanDirectory(opts.target);
256
317
 
257
- if (jsonOutput) {
318
+ if (opts.json) {
258
319
  const output = {
259
320
  scan_path: result.scan_path,
260
321
  files_scanned: result.files_scanned,
261
322
  total_lines: result.total_lines,
262
323
  total_code_lines: result.total_code_lines,
263
324
  passed: passed(result),
264
- error_count: errorCount(result),
265
- warning_count: warningCount(result),
325
+ error_count: result.issues.filter(i => i.severity === 'error').length,
326
+ warning_count: result.issues.filter(i => i.severity === 'warning').length,
266
327
  issues: result.issues
267
328
  };
268
329
  console.log(JSON.stringify(output, null, 2));
269
330
  } else {
270
- console.log(formatReport(result, verbose));
331
+ console.log(formatReport(result, opts.verbose));
271
332
  }
272
333
 
273
334
  process.exit(passed(result) ? 0 : 1);