@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,481 @@
1
+ /**
2
+ * C012 Heuristic Analyzer - Command Query Separation
3
+ *
4
+ * Uses regex and pattern matching to detect violations of CQS principle:
5
+ * - Functions that both modify state and return meaningful values
6
+ * - Methods that mix command and query operations
7
+ * - Fallback for when AST parsing fails
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ class C012Analyzer {
14
+ constructor() {
15
+ this.ruleId = 'C012';
16
+ this.ruleName = 'Command Query Separation';
17
+ this.description = 'Separate commands (modify state) from queries (return data)';
18
+ this.severity = 'warning';
19
+ }
20
+
21
+ async analyze(files, language, config = {}) {
22
+ const violations = [];
23
+
24
+ for (const filePath of files) {
25
+ try {
26
+ const content = fs.readFileSync(filePath, 'utf8');
27
+ const fileViolations = await this.analyzeFile(filePath, content, language, config);
28
+ violations.push(...fileViolations);
29
+ } catch (error) {
30
+ console.warn(`C012 analysis failed for ${filePath}:`, error.message);
31
+ }
32
+ }
33
+
34
+ return violations;
35
+ }
36
+
37
+ async analyzeFile(filePath, content, language, config) {
38
+ const violations = [];
39
+ const lines = content.split('\n');
40
+
41
+ // Skip non-supported files
42
+ if (!this.isSupportedFile(filePath)) {
43
+ return violations;
44
+ }
45
+
46
+ for (let i = 0; i < lines.length; i++) {
47
+ const line = lines[i];
48
+ const violation = this.analyzeLine(line, i + 1, lines, filePath);
49
+ if (violation) {
50
+ violations.push(violation);
51
+ }
52
+ }
53
+
54
+ return violations;
55
+ }
56
+
57
+ isSupportedFile(filePath) {
58
+ const supportedExtensions = ['.js', '.jsx', '.ts', '.tsx', '.java', '.kt', '.dart', '.cs'];
59
+ return supportedExtensions.some(ext => filePath.endsWith(ext));
60
+ }
61
+
62
+ analyzeLine(line, lineNumber, allLines, filePath) {
63
+ const trimmedLine = line.trim();
64
+
65
+ // Skip comments and empty lines
66
+ if (!trimmedLine || this.isComment(trimmedLine)) {
67
+ return null;
68
+ }
69
+
70
+ // Check for function declarations
71
+ const functionInfo = this.extractFunctionInfo(trimmedLine, lineNumber, allLines);
72
+ if (!functionInfo) {
73
+ return null;
74
+ }
75
+
76
+ // Skip allowed functions
77
+ if (this.isAllowedFunction(functionInfo.name)) {
78
+ return null;
79
+ }
80
+
81
+ // Extract function body
82
+ const functionBody = this.extractFunctionBody(allLines, lineNumber - 1);
83
+ if (!functionBody) {
84
+ return null;
85
+ }
86
+
87
+ // Check for CQS violation
88
+ const violation = this.checkCQSViolation(functionInfo, functionBody, lineNumber, filePath);
89
+ return violation;
90
+ }
91
+
92
+ isComment(line) {
93
+ return line.startsWith('//') || line.startsWith('/*') || line.startsWith('*');
94
+ }
95
+
96
+ extractFunctionInfo(line, lineNumber, allLines) {
97
+ // Function declaration patterns
98
+ const patterns = [
99
+ // JavaScript/TypeScript functions
100
+ /(?:async\s+)?function\s+(\w+)\s*\(/,
101
+ /(\w+)\s*:\s*(?:async\s+)?\([^)]*\)\s*=>/,
102
+ /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
103
+ /(\w+)\s*\([^)]*\)\s*\{/,
104
+
105
+ // Method definitions
106
+ /(?:public|private|protected)?\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*[:{]/,
107
+ /(\w+)\s*\([^)]*\)\s*:\s*\w+\s*\{/, // TypeScript with return type
108
+
109
+ // Java/Kotlin/C# methods
110
+ /(?:public|private|protected)\s+(?:static\s+)?(?:\w+\s+)?(\w+)\s*\([^)]*\)\s*\{/,
111
+
112
+ // Dart methods
113
+ /(?:\w+\s+)?(\w+)\s*\([^)]*\)\s*(?:async\s*)?\{/
114
+ ];
115
+
116
+ for (const pattern of patterns) {
117
+ const match = line.match(pattern);
118
+ if (match) {
119
+ return {
120
+ name: match[1],
121
+ fullLine: line,
122
+ lineNumber
123
+ };
124
+ }
125
+ }
126
+
127
+ return null;
128
+ }
129
+
130
+ extractFunctionBody(lines, startIndex) {
131
+ let braceCount = 0;
132
+ let body = '';
133
+ let inFunction = false;
134
+
135
+ for (let i = startIndex; i < lines.length; i++) {
136
+ const line = lines[i];
137
+
138
+ // Count braces
139
+ for (const char of line) {
140
+ if (char === '{') {
141
+ braceCount++;
142
+ inFunction = true;
143
+ } else if (char === '}') {
144
+ braceCount--;
145
+ }
146
+ }
147
+
148
+ if (inFunction) {
149
+ body += line + '\n';
150
+ }
151
+
152
+ // Function complete
153
+ if (inFunction && braceCount === 0) {
154
+ break;
155
+ }
156
+
157
+ // Safety limit
158
+ if (i - startIndex > 100) {
159
+ break;
160
+ }
161
+ }
162
+
163
+ return body;
164
+ }
165
+
166
+ checkCQSViolation(functionInfo, functionBody, lineNumber, filePath) {
167
+ const hasStateModification = this.hasStateModification(functionBody);
168
+ const hasReturnValue = this.hasReturnValue(functionBody);
169
+
170
+ // CQS violation: both command and query behavior
171
+ if (hasStateModification && hasReturnValue) {
172
+ // NEW: Check if this is an acceptable pattern
173
+ if (this.isAcceptablePattern(functionInfo.name, functionBody)) {
174
+ return null; // Allow acceptable patterns
175
+ }
176
+
177
+ return {
178
+ ruleId: this.ruleId,
179
+ file: filePath,
180
+ line: lineNumber,
181
+ column: functionInfo.fullLine.indexOf(functionInfo.name) + 1,
182
+ message: `Function '${functionInfo.name}' violates Command Query Separation: both modifies state and returns value`,
183
+ severity: this.severity,
184
+ code: functionInfo.fullLine.trim(),
185
+ type: 'cqs_violation',
186
+ confidence: this.calculateConfidence(hasStateModification, hasReturnValue, functionBody),
187
+ suggestion: this.getSuggestion(functionInfo.name)
188
+ };
189
+ }
190
+
191
+ return null;
192
+ }
193
+
194
+ hasStateModification(functionBody) {
195
+ const modificationPatterns = [
196
+ // Assignment operations
197
+ /\w+\s*=\s*[^=]/,
198
+ /this\.\w+\s*=/,
199
+ /\w+\.\w+\s*=/,
200
+
201
+ // Update operations
202
+ /\+\+\w+/,
203
+ /\w+\+\+/,
204
+ /--\w+/,
205
+ /\w+--/,
206
+ /\w+\s*\+=\s*/,
207
+ /\w+\s*-=\s*/,
208
+ /\w+\s*\*=\s*/,
209
+ /\w+\s*\/=\s*/,
210
+
211
+ // Method calls that modify state
212
+ /\.(?:push|pop|shift|unshift|splice|sort|reverse|fill)\s*\(/,
213
+ /\.(?:set|delete|clear|add|remove|update)\s*\(/,
214
+ /\.(?:save|store|persist|insert|append|prepend)\s*\(/,
215
+ /\.(?:setState|dispatch|emit|trigger)\s*\(/,
216
+
217
+ // Array/object mutations
218
+ /\[\w+\]\s*=/,
219
+ /\{\s*\w+\s*:\s*\w+\s*\}/,
220
+
221
+ // Property modifications
222
+ /\w+\[\w+\]\s*=/,
223
+ /Object\.assign\s*\(/,
224
+
225
+ // State modification keywords
226
+ /\b(?:increment|decrement|modify|change|alter|mutate)\s*\(/,
227
+ /\bset[A-Z]\w*\s*\(/,
228
+ /\bupdate[A-Z]\w*\s*\(/
229
+ ];
230
+
231
+ return modificationPatterns.some(pattern => pattern.test(functionBody));
232
+ }
233
+
234
+ hasReturnValue(functionBody) {
235
+ const returnPatterns = [
236
+ // Return statements with values (excluding simple booleans)
237
+ /return\s+(?!true|false|null|undefined|;|\}|$)\w+/,
238
+ /return\s+(?!true|false)[^;\}]+[^;\s\}]/,
239
+ /return\s+\w+\s*\([^)]*\)/,
240
+ /return\s+\w+\.\w+/,
241
+ /return\s+new\s+\w+/,
242
+ /return\s+\{[^}]+\}/,
243
+ /return\s+\[[^\]]+\]/,
244
+ /return\s+`[^`]+`/,
245
+ /return\s+["'][^"']+["']/,
246
+ /return\s+\d+/,
247
+
248
+ // Arrow function expressions
249
+ /=>\s*(?!true|false|null|undefined)\w+/,
250
+ /=>\s*\w+\s*\([^)]*\)/,
251
+ /=>\s*\w+\.\w+/,
252
+ /=>\s*\{[^}]+\}/,
253
+ /=>\s*\[[^\]]+\]/,
254
+
255
+ // Function expressions that return values
256
+ /function[^}]*return\s+(?!true|false|null|undefined)\w+/
257
+ ];
258
+
259
+ // Exclude simple success/failure indicators
260
+ const simpleReturnPatterns = [
261
+ /return\s+true\s*;?$/,
262
+ /return\s+false\s*;?$/,
263
+ /return\s+null\s*;?$/,
264
+ /return\s+undefined\s*;?$/,
265
+ /return\s*;?$/
266
+ ];
267
+
268
+ const hasComplexReturn = returnPatterns.some(pattern => pattern.test(functionBody));
269
+ const hasOnlySimpleReturn = simpleReturnPatterns.some(pattern => pattern.test(functionBody));
270
+
271
+ return hasComplexReturn && !hasOnlySimpleReturn;
272
+ }
273
+
274
+ isAllowedFunction(functionName) {
275
+ const allowedPatterns = [
276
+ // Constructor and lifecycle
277
+ /^constructor$/,
278
+ /^componentDidMount$/,
279
+ /^componentWillUnmount$/,
280
+ /^useEffect$/,
281
+ /^init$/,
282
+ /^setup$/,
283
+
284
+ // Test functions
285
+ /^test/,
286
+ /^it$/,
287
+ /^describe$/,
288
+ /^before/,
289
+ /^after/,
290
+
291
+ // Getters/setters
292
+ /^get\w+$/,
293
+ /^set\w+$/,
294
+
295
+ // Factory/builder patterns (allowed to create and return)
296
+ /^create\w+$/,
297
+ /^build\w+$/,
298
+ /^make\w+$/,
299
+ /^new\w+$/,
300
+ /Factory$/,
301
+ /Builder$/,
302
+
303
+ // Configuration and initialization
304
+ /^configure\w+$/,
305
+ /^initialize\w+$/,
306
+ /^setup\w+$/,
307
+
308
+ // Toggle operations (expected to modify and return new state)
309
+ /^toggle\w+$/,
310
+ /^switch\w+$/,
311
+
312
+ // Array operations (modify and return)
313
+ /^push$/,
314
+ /^pop$/,
315
+ /^shift$/,
316
+ /^unshift$/,
317
+ /^splice$/,
318
+
319
+ // Standard methods
320
+ /^toString$/,
321
+ /^valueOf$/,
322
+ /^render$/,
323
+ /^main$/,
324
+
325
+ // Event handlers
326
+ /^on[A-Z]/,
327
+ /^handle[A-Z]/,
328
+ /Handler$/,
329
+
330
+ // Utility and helper methods
331
+ /Helper$/,
332
+ /Util$/,
333
+ /Utils$/
334
+ ];
335
+
336
+ return allowedPatterns.some(pattern => pattern.test(functionName));
337
+ }
338
+
339
+ calculateConfidence(hasStateModification, hasReturnValue, functionBody) {
340
+ let confidence = 0.6;
341
+
342
+ // Higher confidence for clear violations
343
+ if (hasStateModification && hasReturnValue) {
344
+ confidence = 0.8;
345
+
346
+ // Even higher if multiple modification patterns
347
+ const modificationCount = this.countModificationPatterns(functionBody);
348
+ if (modificationCount > 2) {
349
+ confidence = 0.9;
350
+ }
351
+
352
+ // Higher if complex return values
353
+ if (this.hasComplexReturnValue(functionBody)) {
354
+ confidence = Math.min(0.95, confidence + 0.1);
355
+ }
356
+ }
357
+
358
+ return confidence;
359
+ }
360
+
361
+ countModificationPatterns(functionBody) {
362
+ const patterns = [
363
+ /\w+\s*=/,
364
+ /\+\+|\--/,
365
+ /\+=|\-=|\*=|\/=/,
366
+ /\.(?:push|pop|set|delete|save|update)\s*\(/
367
+ ];
368
+
369
+ let count = 0;
370
+ patterns.forEach(pattern => {
371
+ const matches = functionBody.match(new RegExp(pattern.source, 'g'));
372
+ if (matches) {
373
+ count += matches.length;
374
+ }
375
+ });
376
+
377
+ return count;
378
+ }
379
+
380
+ isAcceptablePattern(functionName, functionBody) {
381
+ // NEW: Practical CQS - Allow acceptable patterns per strategy
382
+
383
+ // 1. CRUD Operations (single operation + return)
384
+ const crudPatterns = [
385
+ /^(create|insert|add|save|store)\w*$/i,
386
+ /^(update|modify|edit|change)\w*$/i,
387
+ /^(upsert|merge)\w*$/i,
388
+ /^(delete|remove|destroy)\w*$/i
389
+ ];
390
+
391
+ if (crudPatterns.some(pattern => pattern.test(functionName))) {
392
+ // Check if it's a simple CRUD - single operation
393
+ const queryCount = this.countDatabaseOperations(functionBody);
394
+ if (queryCount <= 1) {
395
+ return true; // Single query + return is acceptable
396
+ }
397
+ }
398
+
399
+ // 2. Transaction-based Operations
400
+ if (this.isTransactionBased(functionBody)) {
401
+ return true; // Multiple operations in transaction are atomic
402
+ }
403
+
404
+ // 3. ORM Standard Patterns
405
+ const ormPatterns = [
406
+ /^findOrCreate\w*$/i,
407
+ /^findAndUpdate\w*$/i,
408
+ /^findAndModify\w*$/i,
409
+ /^saveAndReturn\w*$/i,
410
+ /^selectForUpdate\w*$/i
411
+ ];
412
+
413
+ if (ormPatterns.some(pattern => pattern.test(functionName))) {
414
+ return true; // Standard ORM patterns including selectForUpdate
415
+ }
416
+
417
+ // 4. Factory patterns (create and return by design)
418
+ const factoryPatterns = [
419
+ /^(build|construct|generate|produce)\w*$/i,
420
+ /^(transform|convert|map)\w*$/i
421
+ ];
422
+
423
+ if (factoryPatterns.some(pattern => pattern.test(functionName))) {
424
+ return true; // Factory patterns expected to create and return
425
+ }
426
+
427
+ return false; // Not an acceptable pattern - flag as violation
428
+ }
429
+
430
+ countDatabaseOperations(functionBody) {
431
+ const dbOperationPatterns = [
432
+ /\.(save|insert|create|update|delete|remove)\s*\(/gi,
433
+ /\.(find|findOne|findBy|query|execute)\s*\(/gi,
434
+ /\.(upsert|merge|replace)\s*\(/gi,
435
+ /repository\.\w+\s*\(/gi,
436
+ /manager\.\w+\s*\(/gi
437
+ ];
438
+
439
+ let count = 0;
440
+ dbOperationPatterns.forEach(pattern => {
441
+ const matches = functionBody.match(pattern);
442
+ if (matches) {
443
+ count += matches.length;
444
+ }
445
+ });
446
+
447
+ return count;
448
+ }
449
+
450
+ isTransactionBased(functionBody) {
451
+ const transactionPatterns = [
452
+ /\.transaction\s*\(/gi,
453
+ /withTransaction/gi,
454
+ /runInTransaction/gi,
455
+ /beginTransaction/gi,
456
+ /startTransaction/gi,
457
+ /manager\.transaction/gi,
458
+ /queryRunner\.startTransaction/gi
459
+ ];
460
+
461
+ return transactionPatterns.some(pattern => pattern.test(functionBody));
462
+ }
463
+
464
+ hasComplexReturnValue(functionBody) {
465
+ const complexReturnPatterns = [
466
+ /return\s+\w+\s*\([^)]*\)/, // Function calls
467
+ /return\s+new\s+\w+/, // Object creation
468
+ /return\s+\{[^}]+\}/, // Object literals
469
+ /return\s+\[[^\]]+\]/, // Array literals
470
+ /return\s+\w+\.\w+/ // Property access
471
+ ];
472
+
473
+ return complexReturnPatterns.some(pattern => pattern.test(functionBody));
474
+ }
475
+
476
+ getSuggestion(functionName) {
477
+ return `Split '${functionName}' into separate command (modify state) and query (return data) functions`;
478
+ }
479
+ }
480
+
481
+ module.exports = C012Analyzer;