@sun-asterisk/sunlint 1.2.1 → 1.2.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.
Files changed (77) hide show
  1. package/config/rule-analysis-strategies.js +18 -2
  2. package/engines/eslint-engine.js +9 -11
  3. package/engines/heuristic-engine.js +55 -31
  4. package/package.json +2 -1
  5. package/rules/README.md +252 -0
  6. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  7. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  8. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  9. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  10. package/rules/common/C006_function_naming/analyzer.js +504 -0
  11. package/rules/common/C006_function_naming/config.json +86 -0
  12. package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
  13. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  14. package/rules/common/C012_command_query_separation/analyzer.js +481 -0
  15. package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
  16. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  17. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  18. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  19. package/rules/common/C019_log_level_usage/analyzer.js +362 -0
  20. package/rules/common/C019_log_level_usage/config.json +121 -0
  21. package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
  22. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
  23. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
  24. package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
  25. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
  26. package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
  27. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
  28. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
  29. package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
  30. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
  31. package/rules/common/C029_catch_block_logging/config.json +59 -0
  32. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
  33. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
  34. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
  35. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
  36. package/rules/common/C031_validation_separation/analyzer.js +186 -0
  37. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  38. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
  39. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  40. package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
  41. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
  42. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  43. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  44. package/rules/docs/C002_no_duplicate_code.md +57 -0
  45. package/rules/docs/C031_validation_separation.md +72 -0
  46. package/rules/index.js +155 -0
  47. package/rules/migration/converter.js +385 -0
  48. package/rules/migration/mapping.json +164 -0
  49. package/rules/parser/constants.js +31 -0
  50. package/rules/parser/file-config.js +80 -0
  51. package/rules/parser/rule-parser-simple.js +305 -0
  52. package/rules/parser/rule-parser.js +527 -0
  53. package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
  54. package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
  55. package/rules/security/S023_no_json_injection/analyzer.js +278 -0
  56. package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
  57. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  58. package/rules/security/S026_json_schema_validation/config.json +27 -0
  59. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
  60. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  61. package/rules/security/S029_csrf_protection/analyzer.js +330 -0
  62. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  63. package/rules/universal/C010/generic.js +0 -0
  64. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  65. package/rules/utils/ast-utils.js +191 -0
  66. package/rules/utils/base-analyzer.js +98 -0
  67. package/rules/utils/pattern-matchers.js +239 -0
  68. package/rules/utils/rule-helpers.js +264 -0
  69. package/rules/utils/severity-constants.js +93 -0
  70. package/scripts/generate_insights.js +188 -0
  71. package/scripts/merge-reports.js +0 -424
  72. package/scripts/test-scripts/README.md +0 -22
  73. package/scripts/test-scripts/test-c041-comparison.js +0 -114
  74. package/scripts/test-scripts/test-c041-eslint.js +0 -67
  75. package/scripts/test-scripts/test-eslint-rules.js +0 -146
  76. package/scripts/test-scripts/test-real-world.js +0 -44
  77. package/scripts/test-scripts/test-rules-on-real-projects.js +0 -86
@@ -0,0 +1,418 @@
1
+ /**
2
+ * C003 Heuristic Analyzer: No vague abbreviations
3
+ * Detects unclear variable names and abbreviations
4
+ */
5
+
6
+ class C003NoVagueAbbreviations {
7
+ constructor(options = {}) {
8
+ this.options = {
9
+ allowedSingleChar: new Set(options.allowedSingleChar || [
10
+ 'i', 'j', 'k', 'x', 'y', 'z', 'n', 'm', 't', 'v', 'r', 'e', 'p', 'w', 'h'
11
+ ]),
12
+ allowedAbbreviations: new Set(options.allowedAbbreviations || [
13
+ // Technical abbreviations from user feedback
14
+ 'id', 'url', 'uri', 'api', 'ui', 'db', 'fs', 'os', 'io', 'ai', 'ml', 'qa', 'ci', 'cd', 'pr',
15
+ 'jwt', 'uuid', 'json', 'xml', 'html', 'css', 'sql', 'http', 'https', 'ftp', 'smtp', 'tcp', 'udp',
16
+ 'pdf', 'csv', 'tsv', 'png', 'jpg', 'gif', 'svg', 'mp4', 'mp3', 'zip', 'tar',
17
+ 'js', 'ts', 'py', 'rb', 'go', 'rs', 'kt', 'cs', 'vb', 'sh',
18
+ 'dom', 'xhr', 'spa', 'pwa', 'seo', 'cdn', 'ssl', 'tls',
19
+ 'orm', 'ddl', 'dml', 'etl', 'olap', 'oltp',
20
+ 'kpi', 'roi', 'sla', 'poc', 'mvp', 'b2b', 'b2c', 'crm', 'erp',
21
+ 'jsx', 'tsx', 'vue', 'scss', 'less',
22
+ 'it', 'ut', 'e2e',
23
+ // Common development terms
24
+ 'config', 'env', 'app', 'btn', 'img', 'src', 'dest', 'req', 'res', 'ctx',
25
+ 'min', 'max', 'len', 'num', 'str', 'auth', 'log', 'err', 'msg', 'key',
26
+ // Add the variants from user feedback cases
27
+ 'qa1', 'ci1', 'tsx2', 'it2', 'qa2', 'ci2',
28
+ // Common test/function context terms
29
+ 'value', 'result', 'response', 'request', 'data', 'item', 'element', 'object',
30
+ // Common programming terms
31
+ 'async', 'length', 'ms'
32
+ ]),
33
+ minLength: options.minLength || 2,
34
+ strictMode: options.strictMode || false
35
+ };
36
+
37
+ // Patterns for suspicious abbreviations
38
+ this.suspiciousPatterns = [
39
+ /^[a-z]{1,2}[0-9]*$/, // e.g., 'u', 'usr', 'n1', 'v2'
40
+ /^[a-z]*[aeiou]*[bcdfghjklmnpqrstvwxyz]{4,}$/, // too many consonants
41
+ /^(tmp|temp|val|var|data|info|item|elem|el|obj|arr)([A-Z0-9].*)?$/, // generic names
42
+ ];
43
+
44
+ // Unclear names that should be avoided in most contexts (but not in specific contexts)
45
+ this.unclearNames = new Set([
46
+ 'stuff', 'thing', 'something', 'anything', 'everything',
47
+ 'flag', 'check', 'test', 'validate', 'process', 'handle',
48
+ 'obj', 'arg', 'val', 'fn'
49
+ // Removed: 'data', 'info', 'element', 'object', 'value', 'result', 'response', 'request', 'temp', 'tmp', 'variable'
50
+ // These are often acceptable in specific contexts
51
+ ]);
52
+
53
+ // Context patterns where single letters are acceptable
54
+ this.loopPatterns = [
55
+ /for\s*\(\s*(?:let|const|var)\s+([a-z])\s*[=;]/i,
56
+ /\.forEach\s*\(\s*(?:\([^)]*\)|\w+)\s*=>/,
57
+ /\.map\s*\(\s*(?:\([^)]*\)|\w+)\s*=>/,
58
+ /\.filter\s*\(\s*(?:\([^)]*\)|\w+)\s*=>/
59
+ ];
60
+ }
61
+
62
+ /**
63
+ * Check if variable is in loop context
64
+ */
65
+ isLoopContext(content, variableName, line) {
66
+ const lines = content.split('\n');
67
+ const currentLine = lines[line - 1] || '';
68
+ const prevLine = lines[line - 2] || '';
69
+ const nextLine = lines[line] || '';
70
+
71
+ const contextLines = [prevLine, currentLine, nextLine].join(' ');
72
+
73
+ // Check for for loops
74
+ if (/for\s*\(\s*(?:let|const|var)\s+\w+/i.test(contextLines)) {
75
+ return true;
76
+ }
77
+
78
+ // Check for array methods
79
+ if (/\.(forEach|map|filter|reduce|some|every|find|findIndex)\s*\(/i.test(contextLines)) {
80
+ return true;
81
+ }
82
+
83
+ return false;
84
+ }
85
+
86
+ /**
87
+ * Check if variable is in math/algorithm context
88
+ */
89
+ isMathContext(content, variableName, line) {
90
+ const lines = content.split('\n');
91
+ const currentLine = lines[line - 1] || '';
92
+
93
+ // Math constants like a, b, c in equations
94
+ if (/const\s+[a-z]\s*=\s*\d+.*[a-z]\s*=\s*\d+/.test(currentLine)) {
95
+ return true;
96
+ }
97
+
98
+ // Math operations
99
+ if (/[+\-*/=]\s*\w+|\w+\s*[+\-*/=]/.test(currentLine)) {
100
+ return true;
101
+ }
102
+
103
+ return false;
104
+ }
105
+
106
+ /**
107
+ * Check if variable is in function parameter context
108
+ */
109
+ isFunctionParameter(content, variableName, line) {
110
+ const lines = content.split('\n');
111
+ const currentLine = lines[line - 1] || '';
112
+
113
+ // Check if we're in a function parameter list
114
+ return /function.*\(.*\w+.*\)/.test(currentLine) ||
115
+ /\(.*\w+.*\)\s*=>/.test(currentLine) ||
116
+ /\w+\s*=>\s*/.test(currentLine);
117
+ }
118
+
119
+ /**
120
+ * Check if variable is coordinate-like
121
+ */
122
+ isCoordinate(variableName) {
123
+ return /^[xyz](\d+)?$/i.test(variableName) ||
124
+ /^(width|height|top|left|right|bottom)$/i.test(variableName) ||
125
+ /^[wh](\d+)?$/i.test(variableName);
126
+ }
127
+
128
+ /**
129
+ * Check if variable is in math/algorithm context
130
+ */
131
+ isMathContext(content, variableName, line) {
132
+ const lines = content.split('\n');
133
+ const currentLine = lines[line - 1] || '';
134
+
135
+ // Math constants like a, b, c in equations
136
+ if (/const\s+[a-z]\s*=\s*\d+.*[a-z]\s*=\s*\d+/.test(currentLine)) {
137
+ return true;
138
+ }
139
+
140
+ // Math operations
141
+ if (/[+\-*/=]\s*\w+|\w+\s*[+\-*/=]/.test(currentLine)) {
142
+ return true;
143
+ }
144
+
145
+ return false;
146
+ }
147
+
148
+ /**
149
+ * Check if variable is in function parameter context
150
+ */
151
+ isFunctionParameter(content, variableName, line) {
152
+ const lines = content.split('\n');
153
+ const currentLine = lines[line - 1] || '';
154
+
155
+ // Check if we're in a function parameter list
156
+ return /function.*\(.*\w+.*\)/.test(currentLine) ||
157
+ /\(.*\w+.*\)\s*=>/.test(currentLine) ||
158
+ /\w+\s*=>\s*/.test(currentLine);
159
+ }
160
+
161
+ /**
162
+ * Check if variable is in callback/iteration context
163
+ */
164
+ isCallbackContext(content, variableName, line) {
165
+ const lines = content.split('\n');
166
+ const currentLine = lines[line - 1] || '';
167
+
168
+ // Array methods with callback
169
+ return /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(currentLine);
170
+ }
171
+
172
+ /**
173
+ * Check if variable is in comparison/sorting context
174
+ */
175
+ isComparisonContext(content, variableName, line) {
176
+ const lines = content.split('\n');
177
+ const currentLine = lines[line - 1] || '';
178
+ const prevLine = lines[line - 2] || '';
179
+ const nextLine = lines[line] || '';
180
+
181
+ const contextLines = [prevLine, currentLine, nextLine].join(' ');
182
+
183
+ // Comparison/sorting patterns
184
+ return /\.sort\s*\(/.test(contextLines) ||
185
+ /compare\s*\(/.test(contextLines) ||
186
+ /\w+\s*[<>=]+\s*\w+/.test(contextLines);
187
+ }
188
+
189
+ /**
190
+ * Check if variable is in event context
191
+ */
192
+ isEventContext(content, variableName, line) {
193
+ const lines = content.split('\n');
194
+ const contextLines = lines.slice(Math.max(0, line - 2), line + 1).join(' ');
195
+
196
+ return /(?:event|evt|e)\s*[:=]|addEventListener|onClick|onSubmit|handler/i.test(contextLines);
197
+ }
198
+
199
+ /**
200
+ * Check if variable is in destructuring context
201
+ */
202
+ isDestructuring(content, variableName, line) {
203
+ const lines = content.split('\n');
204
+ const currentLine = lines[line - 1] || '';
205
+
206
+ // Check for object destructuring
207
+ if (/const\s*{[^}]*}\s*=/.test(currentLine) || /{\s*\w+[^}]*}/.test(currentLine)) {
208
+ return true;
209
+ }
210
+
211
+ // Check for array destructuring
212
+ if (/const\s*\[[^\]]*\]\s*=/.test(currentLine) || /\[\s*\w+[^\]]*\]/.test(currentLine)) {
213
+ return true;
214
+ }
215
+
216
+ return false;
217
+ }
218
+
219
+ /**
220
+ * Analyze variable names in files
221
+ * @param {string[]} files - Array of file paths to analyze
222
+ * @param {string} language - Programming language
223
+ * @param {Object} options - Analysis options
224
+ * @returns {Object[]} Array of violations
225
+ */
226
+ async analyze(files, language, options) {
227
+ const violations = [];
228
+
229
+ for (const filePath of files) {
230
+ try {
231
+ // Read file content
232
+ const fs = require('fs');
233
+ const content = fs.readFileSync(filePath, 'utf8');
234
+
235
+ // Analyze file content
236
+ const fileViolations = this.analyzeContent(content, filePath);
237
+ violations.push(...fileViolations);
238
+
239
+ } catch (error) {
240
+ console.error(`Error analyzing file ${filePath}:`, error.message);
241
+ }
242
+ }
243
+
244
+ return violations;
245
+ }
246
+
247
+ /**
248
+ * Analyze variable names in content
249
+ */
250
+ analyzeContent(content, filePath) {
251
+ // Ensure content is a string
252
+ if (typeof content !== 'string') {
253
+ console.error('Content is not a string:', typeof content);
254
+ return [];
255
+ }
256
+
257
+ const violations = [];
258
+ const lines = content.split('\n');
259
+
260
+ lines.forEach((line, index) => {
261
+ const lineNumber = index + 1;
262
+
263
+ // Match variable declarations
264
+ const patterns = [
265
+ // const/let/var declarations
266
+ /(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[=:]/g,
267
+ // Function parameters in function declarations
268
+ /function\s+\w*\s*\(\s*([^)]+)\s*\)/g,
269
+ // Arrow function parameters
270
+ /\(\s*([^)]+)\s*\)\s*=>/g,
271
+ // Single parameter arrow functions
272
+ /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/g,
273
+ ];
274
+
275
+ patterns.forEach(pattern => {
276
+ let match;
277
+ while ((match = pattern.exec(line)) !== null) {
278
+ let variableNames = [];
279
+
280
+ if (match[1]) {
281
+ // Handle parameter lists
282
+ if (match[1].includes(',')) {
283
+ variableNames = match[1].split(',')
284
+ .map(param => param.trim().split(/[:\s=]/)[0].trim())
285
+ .filter(name => name && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name));
286
+ } else {
287
+ const cleanName = match[1].trim().split(/[:\s=]/)[0].trim();
288
+ if (cleanName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(cleanName)) {
289
+ variableNames = [cleanName];
290
+ }
291
+ }
292
+ }
293
+
294
+ variableNames.forEach(variableName => {
295
+ const violation = this.checkVariableName(variableName, content, lineNumber, match.index);
296
+ if (violation) {
297
+ violations.push({
298
+ rule: 'C003',
299
+ severity: 'warning',
300
+ message: violation.message,
301
+ line: lineNumber,
302
+ column: match.index + 1,
303
+ filePath: filePath
304
+ });
305
+ }
306
+ });
307
+ }
308
+ });
309
+ });
310
+
311
+ return violations;
312
+ }
313
+
314
+ /**
315
+ * Check individual variable name
316
+ */
317
+ checkVariableName(variableName, content, line, column) {
318
+ // Skip if empty or invalid
319
+ if (!variableName || typeof variableName !== 'string') {
320
+ return null;
321
+ }
322
+
323
+ // Skip TypeScript/React specific names
324
+ if (variableName.startsWith('_') || variableName.includes('$')) {
325
+ return null;
326
+ }
327
+
328
+ // Skip common React patterns
329
+ if (/^(useState|useEffect|useCallback|useMemo|useRef|useContext)$/.test(variableName)) {
330
+ return null;
331
+ }
332
+
333
+ // Check single character variables
334
+ if (variableName.length === 1) {
335
+ const lowerName = variableName.toLowerCase();
336
+
337
+ // Allow if in allowed single char list
338
+ if (this.options.allowedSingleChar.has(lowerName)) {
339
+ return null;
340
+ }
341
+
342
+ // Allow if in loop context
343
+ if (this.isLoopContext(content, variableName, line)) {
344
+ return null;
345
+ }
346
+
347
+ // Allow coordinates
348
+ if (this.isCoordinate(variableName)) {
349
+ return null;
350
+ }
351
+
352
+ // Allow in event context
353
+ if (lowerName === 'e' && this.isEventContext(content, variableName, line)) {
354
+ return null;
355
+ }
356
+
357
+ // Allow in math context (a, b, c coefficients)
358
+ if (['a', 'b', 'c'].includes(lowerName) && this.isMathContext(content, variableName, line)) {
359
+ return null;
360
+ }
361
+
362
+ // Allow a, b in comparison/sorting context
363
+ if (['a', 'b'].includes(lowerName) && this.isComparisonContext(content, variableName, line)) {
364
+ return null;
365
+ }
366
+
367
+ return {
368
+ message: `Variable '${variableName}' is only 1 character long. Use descriptive names (except for counters like i, j, k).`
369
+ };
370
+ }
371
+
372
+ const lowerName = variableName.toLowerCase();
373
+
374
+ // Check if it's an allowed abbreviation
375
+ if (this.options.allowedAbbreviations.has(lowerName)) {
376
+ return null;
377
+ }
378
+
379
+ // Allow in destructuring context
380
+ if (this.isDestructuring(content, variableName, line)) {
381
+ return null;
382
+ }
383
+
384
+ // Allow in callback/function parameter context for common terms
385
+ if (['item', 'data', 'element'].includes(lowerName) &&
386
+ (this.isCallbackContext(content, variableName, line) ||
387
+ this.isFunctionParameter(content, variableName, line))) {
388
+ return null;
389
+ }
390
+
391
+ // Check for minimum length
392
+ if (variableName.length < this.options.minLength) {
393
+ return {
394
+ message: `Variable '${variableName}' is too short (${variableName.length} characters). Use descriptive names with at least ${this.options.minLength} characters.`
395
+ };
396
+ }
397
+
398
+ // Check for suspicious patterns
399
+ for (const pattern of this.suspiciousPatterns) {
400
+ if (pattern.test(lowerName)) {
401
+ return {
402
+ message: `Variable '${variableName}' appears to be an unclear abbreviation. Use full descriptive names.`
403
+ };
404
+ }
405
+ }
406
+
407
+ // Check for unclear names
408
+ if (this.unclearNames.has(lowerName)) {
409
+ return {
410
+ message: `Variable '${variableName}' is unclear or ambiguous. Use more specific descriptive names.`
411
+ };
412
+ }
413
+
414
+ return null;
415
+ }
416
+ }
417
+
418
+ module.exports = C003NoVagueAbbreviations;
@@ -0,0 +1,35 @@
1
+ {
2
+ "id": "C003",
3
+ "name": "no-vague-abbreviations",
4
+ "description": "Clear variable names, avoid arbitrary abbreviations",
5
+ "category": "naming",
6
+ "severity": "warning",
7
+ "language": ["typescript", "javascript"],
8
+ "engine": "heuristic",
9
+ "meta": {
10
+ "docs": {
11
+ "description": "Ensure clear, understandable variable names without arbitrary abbreviations",
12
+ "url": "https://github.com/sun-asterisk/engineer-excellence/blob/main/docs/rules/C003.md"
13
+ },
14
+ "schema": {
15
+ "type": "object",
16
+ "properties": {
17
+ "allowedSingleChar": {
18
+ "type": "array",
19
+ "items": { "type": "string" },
20
+ "description": "Single character variables that are allowed"
21
+ },
22
+ "allowedAbbreviations": {
23
+ "type": "array",
24
+ "items": { "type": "string" },
25
+ "description": "Common abbreviations that are allowed"
26
+ },
27
+ "minLength": {
28
+ "type": "integer",
29
+ "minimum": 1,
30
+ "description": "Minimum variable name length"
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }