@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,590 @@
1
+ /**
2
+ * Heuristic analyzer for: C047 – Logic retry không được viết lặp lại nhiều nơi
3
+ * Purpose: Detect duplicate retry logic patterns and suggest centralized retry utilities
4
+ */
5
+
6
+ class C047Analyzer {
7
+ constructor() {
8
+ this.ruleId = 'C047';
9
+ this.ruleName = 'No Duplicate Retry Logic';
10
+ this.description = 'Logic retry không được viết lặp lại nhiều nơi - use centralized retry utility instead';
11
+
12
+ // Enhanced patterns that indicate retry logic
13
+ this.retryIndicators = [
14
+ 'maxretries', 'maxattempts', 'maxtries',
15
+ 'attempt', 'retry', 'tries', 'retries',
16
+ 'backoff', 'delay', 'timeout',
17
+ 'exponential', 'linear'
18
+ ];
19
+
20
+ // Architectural layer detection patterns
21
+ this.layerPatterns = {
22
+ ui: ['component', 'view', 'page', 'modal', 'form', 'screen', 'widget', 'button'],
23
+ logic: ['service', 'usecase', 'viewmodel', 'controller', 'handler', 'manager', 'business'],
24
+ repository: ['repository', 'dao', 'store', 'cache', 'persistence', 'data'],
25
+ infrastructure: ['client', 'adapter', 'gateway', 'connector', 'network', 'http', 'api']
26
+ };
27
+
28
+ // Retry purpose classification
29
+ this.purposeIndicators = {
30
+ network: ['fetch', 'axios', 'request', 'http', 'api', 'ajax', 'xhr'],
31
+ database: ['query', 'transaction', 'connection', 'db', 'sql', 'insert', 'update'],
32
+ validation: ['validate', 'check', 'verify', 'confirm', 'assert'],
33
+ ui: ['click', 'submit', 'load', 'render', 'update', 'refresh'],
34
+ auth: ['login', 'authenticate', 'authorize', 'token', 'session']
35
+ };
36
+
37
+ // Allowed centralized retry utilities
38
+ this.allowedRetryUtils = [
39
+ 'RetryUtil', 'retryWithBackoff', 'withRetry', 'retry',
40
+ 'retryAsync', 'retryPromise', 'retryOperation',
41
+ 'exponentialBackoff', 'linearBackoff'
42
+ ];
43
+
44
+ // Detected retry patterns for duplicate checking with context
45
+ this.retryPatterns = [];
46
+ }
47
+
48
+ async analyze(files, language, options = {}) {
49
+ const violations = [];
50
+
51
+ for (const filePath of files) {
52
+ if (options.verbose) {
53
+ console.log(`🔍 Running C047 analysis on ${require('path').basename(filePath)}`);
54
+ }
55
+
56
+ try {
57
+ const content = require('fs').readFileSync(filePath, 'utf8');
58
+ this.retryPatterns = []; // Reset for each file
59
+ const fileViolations = this.analyzeFile(content, filePath);
60
+ violations.push(...fileViolations);
61
+ } catch (error) {
62
+ console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
63
+ }
64
+ }
65
+
66
+ return violations;
67
+ }
68
+
69
+ analyzeFile(content, filePath) {
70
+ const violations = [];
71
+ const lines = content.split('\n');
72
+
73
+ // Find all retry patterns in the file
74
+ this.findRetryPatterns(lines, filePath);
75
+
76
+ // Add architectural context to patterns
77
+ this.retryPatterns.forEach(pattern => {
78
+ if (!pattern.context) {
79
+ const contextLines = lines.slice(Math.max(0, pattern.line - 10), pattern.line + 10);
80
+ const contextContent = contextLines.join('\n');
81
+ pattern.context = this.analyzeArchitecturalContext(filePath, contextContent);
82
+ }
83
+ });
84
+
85
+ // Enhanced duplicate detection with architectural intelligence
86
+ const duplicateGroups = this.enhancedDuplicateDetection();
87
+
88
+ // Generate violations with architectural context
89
+ const enhancedViolations = this.generateEnhancedViolations(duplicateGroups, filePath);
90
+ violations.push(...enhancedViolations);
91
+
92
+ return violations;
93
+ }
94
+
95
+ findRetryPatterns(lines, filePath) {
96
+ for (let i = 0; i < lines.length; i++) {
97
+ const line = lines[i];
98
+ const trimmedLine = line.trim().toLowerCase();
99
+
100
+ // Skip comments and empty lines
101
+ if (!trimmedLine || trimmedLine.startsWith('//') || trimmedLine.startsWith('/*')) {
102
+ continue;
103
+ }
104
+
105
+ // Skip if using allowed retry utilities
106
+ if (this.usesAllowedRetryUtil(line)) {
107
+ continue;
108
+ }
109
+
110
+ // Pattern 1: For/while loop with retry indicators
111
+ if (this.isRetryLoopPattern(line, lines, i)) {
112
+ const pattern = this.extractRetryPattern(lines, i, 'loop');
113
+ if (pattern) {
114
+ this.retryPatterns.push({
115
+ ...pattern,
116
+ line: i + 1,
117
+ column: line.indexOf(line.trim()) + 1
118
+ });
119
+ }
120
+ }
121
+
122
+ // Pattern 2: Variable declarations with retry indicators
123
+ else if (this.isRetryVariableDeclaration(trimmedLine)) {
124
+ // Additional context check for false positives
125
+ const contextLines = lines.slice(Math.max(0, i - 5), Math.min(lines.length, i + 10));
126
+ const contextText = contextLines.join('\n').toLowerCase();
127
+
128
+ // Skip if it's clearly data processing context
129
+ if (contextText.includes('filter(') && contextText.includes('map(') &&
130
+ !contextText.includes('try') && !contextText.includes('catch') &&
131
+ !contextText.includes('timeout') && !contextText.includes('delay')) {
132
+ continue;
133
+ }
134
+
135
+ const pattern = this.extractRetryPattern(lines, i, 'variable');
136
+ if (pattern) {
137
+ this.retryPatterns.push({
138
+ ...pattern,
139
+ line: i + 1,
140
+ column: line.indexOf(line.trim()) + 1
141
+ });
142
+ }
143
+ }
144
+
145
+ // Pattern 3: Recursive function with retry logic
146
+ else if (this.isRetryFunctionPattern(lines, i)) {
147
+ // Additional check for actual retry vs. recursive data processing
148
+ const contextLines = lines.slice(i, Math.min(lines.length, i + 20));
149
+ const contextText = contextLines.join('\n').toLowerCase();
150
+
151
+ // Skip recursive data processing functions
152
+ if (contextText.includes('flatmap') || contextText.includes('children') ||
153
+ (contextText.includes('recursive') && !contextText.includes('retry'))) {
154
+ continue;
155
+ }
156
+
157
+ const pattern = this.extractRetryPattern(lines, i, 'recursive');
158
+ if (pattern) {
159
+ this.retryPatterns.push({
160
+ ...pattern,
161
+ line: i + 1,
162
+ column: line.indexOf(line.trim()) + 1
163
+ });
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ isRetryLoopPattern(line, lines, index) {
170
+ const trimmedLine = line.trim().toLowerCase();
171
+
172
+ // Check for for/while loops with retry indicators
173
+ if ((trimmedLine.includes('for') || trimmedLine.includes('while')) &&
174
+ (trimmedLine.includes('(') && trimmedLine.includes(')'))) {
175
+
176
+ // Look in the loop condition and surrounding context for retry indicators
177
+ const contextLines = lines.slice(Math.max(0, index - 2), Math.min(lines.length, index + 10));
178
+ const contextText = contextLines.join('\n').toLowerCase();
179
+
180
+ return this.retryIndicators.some(indicator => contextText.includes(indicator)) &&
181
+ (contextText.includes('try') || contextText.includes('catch') || contextText.includes('error'));
182
+ }
183
+
184
+ return false;
185
+ }
186
+
187
+ isRetryVariableDeclaration(line) {
188
+ // Skip simple constant definitions (export const X = number)
189
+ if (/^export\s+const\s+\w+\s*=\s*\d+/.test(line.trim())) {
190
+ return false;
191
+ }
192
+
193
+ // Skip test-related patterns (jest mocks, test descriptions)
194
+ if (/it\(|describe\(|test\(|mock\w*\(|\.mock/.test(line)) {
195
+ return false;
196
+ }
197
+
198
+ // Skip HTTP response patterns
199
+ if (/response\.status\(|\.json\(|return.*errors/.test(line)) {
200
+ return false;
201
+ }
202
+
203
+ // Skip simple array declarations
204
+ if (/(?:const|let|var)\s+\w+Array\s*=\s*\[\s*\]/.test(line.trim())) {
205
+ return false;
206
+ }
207
+
208
+ // Check for variable declarations with retry-related names that involve logic
209
+ const declarationPatterns = [
210
+ /(?:const|let|var)\s+.*(?:retry|attempt|tries|maxretries|maxattempts)/,
211
+ /(?:retry|attempt|tries|maxretries|maxattempts)\s*[:=]/
212
+ ];
213
+
214
+ // Only consider it a retry pattern if it has logical complexity
215
+ if (declarationPatterns.some(pattern => pattern.test(line))) {
216
+ // Exclude simple constant assignments to numbers
217
+ if (/=\s*\d+\s*[;,]?\s*$/.test(line.trim())) {
218
+ return false;
219
+ }
220
+ // Exclude simple array initializations
221
+ if (/=\s*\[\s*\]\s*[;,]?\s*$/.test(line.trim())) {
222
+ return false;
223
+ }
224
+ return true;
225
+ }
226
+
227
+ return false;
228
+ }
229
+
230
+ isRetryFunctionPattern(lines, index) {
231
+ const line = lines[index].trim().toLowerCase();
232
+
233
+ // Skip test functions
234
+ if (line.includes('it(') || line.includes('describe(') || line.includes('test(')) {
235
+ return false;
236
+ }
237
+
238
+ // Check for function declarations with retry indicators
239
+ if ((line.includes('function') || line.includes('=>')) &&
240
+ this.retryIndicators.some(indicator => line.includes(indicator))) {
241
+
242
+ // Skip obvious non-retry functions (formatters, validators, mappers)
243
+ if (line.includes('format') || line.includes('validate') || line.includes('map') ||
244
+ line.includes('filter') || line.includes('transform')) {
245
+ return false;
246
+ }
247
+
248
+ // Look for retry logic in function body
249
+ const functionBody = this.getFunctionBody(lines, index);
250
+ if (!functionBody) return false;
251
+
252
+ // Check if it's a recursive function (calls itself) without actual retry patterns
253
+ const functionName = this.extractFunctionName(line);
254
+ if (functionName && functionBody.includes(functionName) &&
255
+ !functionBody.includes('try') && !functionBody.includes('catch') &&
256
+ !functionBody.includes('timeout') && !functionBody.includes('delay')) {
257
+ return false;
258
+ }
259
+
260
+ return functionBody &&
261
+ this.retryIndicators.some(indicator => functionBody.includes(indicator)) &&
262
+ (functionBody.includes('try') || functionBody.includes('catch') ||
263
+ functionBody.includes('throw') || functionBody.includes('error'));
264
+ }
265
+
266
+ return false;
267
+ }
268
+
269
+ usesAllowedRetryUtil(line) {
270
+ return this.allowedRetryUtils.some(util =>
271
+ line.includes(util) && (line.includes('.') || line.includes('('))
272
+ );
273
+ }
274
+
275
+ extractRetryPattern(lines, startIndex, type) {
276
+ // Extract the pattern characteristics for similarity comparison
277
+ const contextLines = lines.slice(startIndex, Math.min(lines.length, startIndex + 20));
278
+ const contextText = contextLines.join('\n').toLowerCase();
279
+
280
+ // Extract key characteristics
281
+ const characteristics = {
282
+ type: type,
283
+ hasForLoop: contextText.includes('for'),
284
+ hasWhileLoop: contextText.includes('while'),
285
+ hasDoWhile: contextText.includes('do') && contextText.includes('while'),
286
+ hasTryCatch: contextText.includes('try') && contextText.includes('catch'),
287
+ hasMaxRetries: /max.*(?:retry|attempt|tries)/.test(contextText),
288
+ hasBackoff: contextText.includes('backoff') || contextText.includes('delay') || contextText.includes('timeout'),
289
+ hasExponential: contextText.includes('exponential') || /math\.pow|2\s*\*\*|\*\s*2/.test(contextText),
290
+ hasLinear: contextText.includes('linear') || /\*\s*(?:attempt|retry)/.test(contextText),
291
+ hasSetTimeout: contextText.includes('settimeout') || contextText.includes('promise') && contextText.includes('resolve'),
292
+ signature: this.generatePatternSignature(contextText)
293
+ };
294
+
295
+ return characteristics;
296
+ }
297
+
298
+ generatePatternSignature(contextText) {
299
+ // Create a normalized signature for pattern matching
300
+ let signature = '';
301
+
302
+ if (contextText.includes('for')) signature += 'FOR_';
303
+ if (contextText.includes('while')) signature += 'WHILE_';
304
+ if (/max.*(?:retry|attempt)/.test(contextText)) signature += 'MAX_';
305
+ if (contextText.includes('try') && contextText.includes('catch')) signature += 'TRYCATCH_';
306
+ if (contextText.includes('settimeout') || contextText.includes('delay')) signature += 'DELAY_';
307
+ if (contextText.includes('exponential') || /math\.pow/.test(contextText)) signature += 'EXPONENTIAL_';
308
+ if (contextText.includes('throw')) signature += 'THROW_';
309
+
310
+ return signature || 'GENERIC_RETRY';
311
+ }
312
+
313
+ extractFunctionName(line) {
314
+ // Extract function name from function declaration
315
+ const functionMatch = line.match(/(?:function\s+(\w+)|(\w+)\s*(?:\([^)]*\))?\s*=>|(\w+)\s*:\s*(?:async\s+)?function)/);
316
+ if (functionMatch) {
317
+ return functionMatch[1] || functionMatch[2] || functionMatch[3];
318
+ }
319
+
320
+ // Try to extract from method declaration
321
+ const methodMatch = line.match(/(\w+)\s*\(/);
322
+ if (methodMatch) {
323
+ return methodMatch[1];
324
+ }
325
+
326
+ return null;
327
+ }
328
+
329
+ getFunctionBody(lines, startIndex) {
330
+ // Extract function body for analysis
331
+ let braceDepth = 0;
332
+ let foundStartBrace = false;
333
+ const bodyLines = [];
334
+
335
+ for (let i = startIndex; i < lines.length; i++) {
336
+ const line = lines[i];
337
+
338
+ for (const char of line) {
339
+ if (char === '{') {
340
+ braceDepth++;
341
+ foundStartBrace = true;
342
+ } else if (char === '}') {
343
+ braceDepth--;
344
+ }
345
+ }
346
+
347
+ if (foundStartBrace) {
348
+ bodyLines.push(line);
349
+
350
+ if (braceDepth === 0) {
351
+ break;
352
+ }
353
+ }
354
+ }
355
+
356
+ return bodyLines.join('\n').toLowerCase();
357
+ }
358
+
359
+ findDuplicateRetryLogic() {
360
+ const groups = [];
361
+ const processed = new Set();
362
+
363
+ this.retryPatterns.forEach((pattern, index) => {
364
+ if (processed.has(index)) return;
365
+
366
+ const similarPatterns = [pattern];
367
+ processed.add(index);
368
+
369
+ // Find similar patterns
370
+ this.retryPatterns.forEach((otherPattern, otherIndex) => {
371
+ if (otherIndex !== index && !processed.has(otherIndex)) {
372
+ if (this.areSimilarPatterns(pattern, otherPattern)) {
373
+ similarPatterns.push(otherPattern);
374
+ processed.add(otherIndex);
375
+ }
376
+ }
377
+ });
378
+
379
+ if (similarPatterns.length > 0) {
380
+ groups.push({
381
+ signature: pattern.signature,
382
+ patterns: similarPatterns
383
+ });
384
+ }
385
+ });
386
+
387
+ return groups;
388
+ }
389
+
390
+ areSimilarPatterns(pattern1, pattern2) {
391
+ // Check if two patterns are similar enough to be considered duplicates
392
+
393
+ // Same signature indicates very similar patterns
394
+ if (pattern1.signature === pattern2.signature) {
395
+ return true;
396
+ }
397
+
398
+ // Compare characteristics
399
+ const similarities = [
400
+ pattern1.hasForLoop === pattern2.hasForLoop,
401
+ pattern1.hasWhileLoop === pattern2.hasWhileLoop,
402
+ pattern1.hasTryCatch === pattern2.hasTryCatch,
403
+ pattern1.hasMaxRetries === pattern2.hasMaxRetries,
404
+ pattern1.hasBackoff === pattern2.hasBackoff,
405
+ pattern1.hasSetTimeout === pattern2.hasSetTimeout
406
+ ].filter(Boolean).length;
407
+
408
+ // Consider patterns similar if they share at least 4 out of 6 characteristics
409
+ return similarities >= 4;
410
+ }
411
+
412
+ getFunctionNameForLine(lines, lineIndex) {
413
+ // Look backwards to find the function declaration for this line
414
+ for (let i = lineIndex; i >= 0; i--) {
415
+ const line = lines[i].trim();
416
+
417
+ // Match function declarations
418
+ const functionMatch = line.match(/(?:function|async\s+function)\s+(\w+)/);
419
+ if (functionMatch) {
420
+ return functionMatch[1];
421
+ }
422
+
423
+ // Match arrow functions assigned to variables
424
+ const arrowMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=.*=>/);
425
+ if (arrowMatch) {
426
+ return arrowMatch[1];
427
+ }
428
+
429
+ // Stop at class/module boundaries
430
+ if (line.includes('class ') || line.includes('module.exports') || line.includes('export')) {
431
+ break;
432
+ }
433
+ }
434
+
435
+ return 'anonymous';
436
+ }
437
+
438
+ // 🧠 ARCHITECTURAL INTELLIGENCE METHODS
439
+
440
+ analyzeArchitecturalContext(filePath, content) {
441
+ const fileName = require('path').basename(filePath).toLowerCase();
442
+ const dirPath = require('path').dirname(filePath).toLowerCase();
443
+
444
+ // Determine architectural layer
445
+ let layer = 'unknown';
446
+ for (const [layerName, patterns] of Object.entries(this.layerPatterns)) {
447
+ if (patterns.some(pattern => fileName.includes(pattern) || dirPath.includes(pattern))) {
448
+ layer = layerName;
449
+ break;
450
+ }
451
+ }
452
+
453
+ // Extract retry purpose/scope
454
+ const purpose = this.extractRetryPurpose(content);
455
+
456
+ return { layer, purpose, filePath: fileName };
457
+ }
458
+
459
+ extractRetryPurpose(content) {
460
+ const contentLower = content.toLowerCase();
461
+
462
+ for (const [purpose, indicators] of Object.entries(this.purposeIndicators)) {
463
+ if (indicators.some(indicator => contentLower.includes(indicator))) {
464
+ return purpose;
465
+ }
466
+ }
467
+
468
+ return 'general';
469
+ }
470
+
471
+ enhancedDuplicateDetection() {
472
+ const groups = [];
473
+ const processed = new Set();
474
+
475
+ // Add architectural context to each pattern
476
+ this.retryPatterns.forEach((pattern, index) => {
477
+ if (!pattern.context) {
478
+ // This would be called during pattern extraction in a real implementation
479
+ pattern.context = { layer: 'unknown', purpose: 'general' };
480
+ }
481
+ });
482
+
483
+ this.retryPatterns.forEach((pattern, index) => {
484
+ if (processed.has(index)) return;
485
+
486
+ const similarPatterns = [pattern];
487
+ processed.add(index);
488
+
489
+ this.retryPatterns.forEach((otherPattern, otherIndex) => {
490
+ if (otherIndex !== index && !processed.has(otherIndex)) {
491
+ if (this.areSimilarPatternsWithContext(pattern, otherPattern)) {
492
+ similarPatterns.push(otherPattern);
493
+ processed.add(otherIndex);
494
+ }
495
+ }
496
+ });
497
+
498
+ if (similarPatterns.length > 1) {
499
+ const legitimacy = this.assessDuplicateLegitimacy(similarPatterns);
500
+
501
+ if (!legitimacy.isLegitimate) {
502
+ groups.push({
503
+ signature: pattern.signature,
504
+ patterns: similarPatterns,
505
+ legitimacy: legitimacy
506
+ });
507
+ }
508
+ }
509
+ });
510
+
511
+ return groups;
512
+ }
513
+
514
+ areSimilarPatternsWithContext(pattern1, pattern2) {
515
+ // First check basic similarity
516
+ if (!this.areSimilarPatterns(pattern1, pattern2)) {
517
+ return false;
518
+ }
519
+
520
+ // Enhanced context-aware similarity
521
+ const context1 = pattern1.context || { layer: 'unknown', purpose: 'general' };
522
+ const context2 = pattern2.context || { layer: 'unknown', purpose: 'general' };
523
+
524
+ // Same layer AND same purpose = likely duplicate
525
+ // Different layer OR different purpose = likely legitimate
526
+ return context1.layer === context2.layer && context1.purpose === context2.purpose;
527
+ }
528
+
529
+ assessDuplicateLegitimacy(patterns) {
530
+ const layers = new Set(patterns.map(p => p.context?.layer || 'unknown'));
531
+ const purposes = new Set(patterns.map(p => p.context?.purpose || 'general'));
532
+
533
+ // Cross-layer retries are often legitimate
534
+ if (layers.size > 1) {
535
+ return {
536
+ isLegitimate: true,
537
+ reason: 'Cross-layer retry patterns are architecturally valid',
538
+ confidence: 'high'
539
+ };
540
+ }
541
+
542
+ // Different purposes in same layer can be legitimate
543
+ if (purposes.size > 1) {
544
+ return {
545
+ isLegitimate: true,
546
+ reason: 'Different retry purposes in same layer',
547
+ confidence: 'medium'
548
+ };
549
+ }
550
+
551
+ // Same layer, same purpose - likely duplicate
552
+ const layer = [...layers][0];
553
+ const purpose = [...purposes][0];
554
+
555
+ return {
556
+ isLegitimate: false,
557
+ reason: `Duplicate ${purpose} retry logic in ${layer} layer`,
558
+ confidence: 'high',
559
+ severity: 'warning'
560
+ };
561
+ }
562
+
563
+ generateEnhancedViolations(duplicateGroups, filePath) {
564
+ const violations = [];
565
+
566
+ duplicateGroups.forEach(group => {
567
+ const firstPattern = group.patterns[0];
568
+
569
+ violations.push({
570
+ file: filePath,
571
+ line: firstPattern.line,
572
+ column: firstPattern.column || 1,
573
+ message: `${group.legitimacy.reason} (${group.patterns.length} similar patterns found). Consider using a centralized retry utility.`,
574
+ severity: group.legitimacy.severity || 'warning',
575
+ ruleId: this.ruleId,
576
+ type: 'duplicate_retry_logic',
577
+ duplicateCount: group.patterns.length,
578
+ architecturalContext: {
579
+ layers: [...new Set(group.patterns.map(p => p.context?.layer))],
580
+ purposes: [...new Set(group.patterns.map(p => p.context?.purpose))],
581
+ confidence: group.legitimacy.confidence
582
+ }
583
+ });
584
+ });
585
+
586
+ return violations;
587
+ }
588
+ }
589
+
590
+ module.exports = C047Analyzer;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * C075 Rule: Functions must have explicit return type declarations
3
+ * Ensures type safety by requiring explicit return type annotations
4
+ * Severity: warning
5
+ * Category: Quality
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ class C075ExplicitReturnTypesAnalyzer {
12
+ constructor() {
13
+ this.ruleId = 'C075';
14
+ this.ruleName = 'Explicit Function Return Types';
15
+ this.description = 'Functions must have explicit return type declarations';
16
+ this.severity = 'warning';
17
+ }
18
+
19
+ async analyze(files, language, config) {
20
+ const violations = [];
21
+
22
+ for (const filePath of files) {
23
+ try {
24
+ const fileContent = fs.readFileSync(filePath, 'utf8');
25
+ const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
26
+ violations.push(...fileViolations);
27
+ } catch (error) {
28
+ console.warn(`C075 analysis error for ${filePath}:`, error.message);
29
+ }
30
+ }
31
+
32
+ return violations;
33
+ }
34
+
35
+ async analyzeFile(filePath, fileContent, language, config) {
36
+ const violations = [];
37
+
38
+ try {
39
+ // Skip non-TypeScript files
40
+ if (!this.isTypeScriptFile(filePath)) {
41
+ return violations;
42
+ }
43
+
44
+ // Simple regex-based analysis for now
45
+ const lines = fileContent.split('\n');
46
+
47
+ for (let i = 0; i < lines.length; i++) {
48
+ const line = lines[i];
49
+ const lineNumber = i + 1;
50
+
51
+ // Look for function declarations without return types
52
+ const functionPatterns = [
53
+ /^(\s*)(function\s+\w+\s*\([^)]*\))\s*\{/, // function name() {
54
+ /^(\s*)(export\s+function\s+\w+\s*\([^)]*\))\s*\{/, // export function name() {
55
+ /^(\s*)(\w+\s*=\s*function\s*\([^)]*\))\s*\{/, // name = function() {
56
+ /^(\s*)(\w+\s*=\s*\([^)]*\)\s*=>\s*)\{/, // name = () => {
57
+ /^(\s*)(\w+\([^)]*\))\s*\{/, // method() {
58
+ ];
59
+
60
+ for (const pattern of functionPatterns) {
61
+ const match = line.match(pattern);
62
+ if (match) {
63
+ const fullMatch = match[2];
64
+
65
+ // Skip if already has return type annotation
66
+ if (fullMatch.includes('):') || line.includes('):')) {
67
+ continue;
68
+ }
69
+
70
+ // Skip constructors
71
+ if (fullMatch.includes('constructor')) {
72
+ continue;
73
+ }
74
+
75
+ violations.push({
76
+ ruleId: this.ruleId,
77
+ severity: this.severity,
78
+ message: `Function is missing explicit return type annotation`,
79
+ filePath: filePath,
80
+ line: lineNumber,
81
+ column: match[1].length + 1,
82
+ source: line.trim(),
83
+ suggestion: 'Add explicit return type annotation (: ReturnType)'
84
+ });
85
+ }
86
+ }
87
+ }
88
+
89
+ } catch (error) {
90
+ console.warn(`C075 analysis error for ${filePath}:`, error.message);
91
+ }
92
+
93
+ return violations;
94
+ }
95
+
96
+ isTypeScriptFile(filePath) {
97
+ return /\.(ts|tsx)$/.test(filePath);
98
+ }
99
+ }
100
+
101
+ module.exports = C075ExplicitReturnTypesAnalyzer;
102
+
103
+ module.exports = C075ExplicitReturnTypesAnalyzer;