@sun-asterisk/sunlint 1.1.7 → 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.
Files changed (74) hide show
  1. package/.sunlint.json +1 -1
  2. package/CHANGELOG.md +83 -0
  3. package/README.md +66 -4
  4. package/config/presets/all.json +125 -0
  5. package/config/presets/beginner.json +16 -8
  6. package/config/presets/ci.json +12 -4
  7. package/config/presets/maintainability.json +38 -0
  8. package/config/presets/performance.json +32 -0
  9. package/config/presets/quality.json +103 -0
  10. package/config/presets/recommended.json +36 -12
  11. package/config/presets/security.json +88 -0
  12. package/config/presets/strict.json +15 -5
  13. package/config/rules/rules-registry-generated.json +6312 -0
  14. package/config/rules-summary.json +1941 -0
  15. package/core/adapters/sunlint-rule-adapter.js +452 -0
  16. package/core/analysis-orchestrator.js +4 -4
  17. package/core/config-manager.js +28 -5
  18. package/core/rule-selection-service.js +52 -55
  19. package/docs/CONFIGURATION.md +111 -3
  20. package/docs/LANGUAGE-SPECIFIC-RULES.md +308 -0
  21. package/docs/README.md +3 -0
  22. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +156 -0
  23. package/engines/eslint-engine.js +92 -2
  24. package/engines/heuristic-engine.js +8 -31
  25. package/origin-rules/common-en.md +1320 -0
  26. package/origin-rules/dart-en.md +289 -0
  27. package/origin-rules/java-en.md +60 -0
  28. package/origin-rules/kotlin-mobile-en.md +453 -0
  29. package/origin-rules/reactjs-en.md +102 -0
  30. package/origin-rules/security-en.md +1055 -0
  31. package/origin-rules/swift-en.md +449 -0
  32. package/origin-rules/typescript-en.md +136 -0
  33. package/package.json +6 -5
  34. package/scripts/copy-rules.js +86 -0
  35. package/rules/README.md +0 -252
  36. package/rules/common/C002_no_duplicate_code/analyzer.js +0 -65
  37. package/rules/common/C002_no_duplicate_code/config.json +0 -23
  38. package/rules/common/C003_no_vague_abbreviations/analyzer.js +0 -418
  39. package/rules/common/C003_no_vague_abbreviations/config.json +0 -35
  40. package/rules/common/C006_function_naming/analyzer.js +0 -349
  41. package/rules/common/C006_function_naming/config.json +0 -86
  42. package/rules/common/C010_limit_block_nesting/analyzer.js +0 -389
  43. package/rules/common/C013_no_dead_code/analyzer.js +0 -206
  44. package/rules/common/C014_dependency_injection/analyzer.js +0 -338
  45. package/rules/common/C017_constructor_logic/analyzer.js +0 -314
  46. package/rules/common/C019_log_level_usage/analyzer.js +0 -362
  47. package/rules/common/C019_log_level_usage/config.json +0 -121
  48. package/rules/common/C029_catch_block_logging/analyzer.js +0 -373
  49. package/rules/common/C029_catch_block_logging/config.json +0 -59
  50. package/rules/common/C031_validation_separation/analyzer.js +0 -186
  51. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +0 -292
  52. package/rules/common/C042_boolean_name_prefix/analyzer.js +0 -300
  53. package/rules/common/C043_no_console_or_print/analyzer.js +0 -304
  54. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +0 -351
  55. package/rules/common/C075_explicit_return_types/analyzer.js +0 -103
  56. package/rules/common/C076_single_test_behavior/analyzer.js +0 -121
  57. package/rules/docs/C002_no_duplicate_code.md +0 -57
  58. package/rules/docs/C031_validation_separation.md +0 -72
  59. package/rules/index.js +0 -149
  60. package/rules/migration/converter.js +0 -385
  61. package/rules/migration/mapping.json +0 -164
  62. package/rules/security/S026_json_schema_validation/analyzer.js +0 -251
  63. package/rules/security/S026_json_schema_validation/config.json +0 -27
  64. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +0 -263
  65. package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
  66. package/rules/security/S029_csrf_protection/analyzer.js +0 -264
  67. package/rules/tests/C002_no_duplicate_code.test.js +0 -50
  68. package/rules/universal/C010/generic.js +0 -0
  69. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  70. package/rules/utils/ast-utils.js +0 -191
  71. package/rules/utils/base-analyzer.js +0 -98
  72. package/rules/utils/pattern-matchers.js +0 -239
  73. package/rules/utils/rule-helpers.js +0 -264
  74. package/rules/utils/severity-constants.js +0 -93
@@ -1,304 +0,0 @@
1
- /**
2
- * C043 Rule: No console.log or print in production code
3
- * Prevents usage of console logging and print statements in production
4
- * Severity: warning
5
- * Category: Quality
6
- */
7
-
8
- const fs = require('fs');
9
- const path = require('path');
10
-
11
- class C043NoConsoleOrPrintAnalyzer {
12
- constructor() {
13
- this.ruleId = 'C043';
14
- this.ruleName = 'No Console Or Print';
15
- this.description = 'Do not use console.log or print in production code';
16
- this.severity = 'warning';
17
-
18
- // Enhanced patterns to catch various console and print usages
19
- // Negative lookahead to avoid matches within strings or comments
20
- this.consolePatterns = [
21
- /\bconsole\.(?:log|debug|info|table|time|timeEnd|count|group|groupCollapsed|trace|dir|dirxml|profile|profileEnd)\s*\(/g,
22
- /\bprint\s*\(/g,
23
- /\balert\s*\(/g,
24
- /\bconfirm\s*\(/g,
25
- /\bprompt\s*\(/g
26
- ];
27
-
28
- // Also check if the line is within a comment or string
29
- function isInStringOrComment(line, matchIndex) {
30
- // Check for single/double quotes before the match
31
- const beforeMatch = line.substring(0, matchIndex);
32
- const afterMatch = line.substring(matchIndex);
33
-
34
- // Count quotes before match
35
- const singleQuotes = (beforeMatch.match(/'/g) || []).length;
36
- const doubleQuotes = (beforeMatch.match(/"/g) || []).length;
37
- const backticks = (beforeMatch.match(/`/g) || []).length;
38
-
39
- // If odd number of quotes, we're inside a string
40
- if (singleQuotes % 2 !== 0 || doubleQuotes % 2 !== 0 || backticks % 2 !== 0) {
41
- return true;
42
- }
43
-
44
- // Check for comments
45
- if (beforeMatch.includes('//') || beforeMatch.includes('/*')) {
46
- return true;
47
- }
48
-
49
- return false;
50
- }
51
-
52
- // Patterns for print statements
53
- this.printPatterns = [
54
- /\bprint\s*\(/g,
55
- /\balert\s*\(/g,
56
- /\bconfirm\s*\(/g,
57
- /\bprompt\s*\(/g
58
- ];
59
-
60
- // Allowed console methods (errors/warnings might be OK)
61
- this.allowedConsoleMethods = ['error', 'warn'];
62
-
63
- // Development context patterns that might allow console
64
- this.devContextPatterns = [
65
- /if\s*\(\s*(__DEV__|DEBUG|process\.env\.NODE_ENV)/,
66
- /if\s*\(\s*process\.env\.NODE_ENV\s*===\s*['"`]development['"`]/,
67
- /if\s*\(\s*ENABLE_LOGGING|FEATURES\.debug/
68
- ];
69
- }
70
-
71
- async analyze(files, language, config) {
72
- const violations = [];
73
-
74
- for (const filePath of files) {
75
- try {
76
- const fileContent = fs.readFileSync(filePath, 'utf8');
77
- const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
78
- violations.push(...fileViolations);
79
- } catch (error) {
80
- console.warn(`C043 analysis error for ${filePath}:`, error.message);
81
- }
82
- }
83
-
84
- return violations;
85
- }
86
-
87
- async analyzeFile(filePath, fileContent, language, config) {
88
- const violations = [];
89
-
90
- try {
91
- // Skip test files (console.log often used in tests)
92
- if (this.isTestFile(filePath)) {
93
- return violations;
94
- }
95
-
96
- // Skip development/debug files
97
- if (this.isDevelopmentFile(filePath)) {
98
- return violations;
99
- }
100
-
101
- const lines = fileContent.split('\n');
102
- let inTemplateString = false;
103
- let templateStartLine = -1;
104
-
105
- for (let i = 0; i < lines.length; i++) {
106
- const line = lines[i];
107
- const lineNumber = i + 1;
108
-
109
- // Track template literal state
110
- const backtickCount = (line.match(/`/g) || []).length;
111
- if (backtickCount % 2 === 1) {
112
- if (!inTemplateString) {
113
- inTemplateString = true;
114
- templateStartLine = i;
115
- } else {
116
- inTemplateString = false;
117
- templateStartLine = -1;
118
- }
119
- }
120
-
121
- // Skip if inside template literal
122
- if (inTemplateString && templateStartLine !== i) {
123
- continue;
124
- }
125
-
126
- // Skip comments and strings (basic check)
127
- if (this.isInCommentOrString(line)) {
128
- continue;
129
- }
130
-
131
- // Skip if in development context
132
- if (this.isInDevelopmentContext(lines, i)) {
133
- continue;
134
- }
135
-
136
- // Check for console.* calls
137
- for (const pattern of this.consolePatterns) {
138
- let match;
139
- while ((match = pattern.exec(line)) !== null) {
140
- // Skip if the match is within a string or comment
141
- if (this.isInCommentOrString(line)) {
142
- continue;
143
- }
144
-
145
- const method = match[1] || 'log';
146
-
147
- // Skip allowed methods
148
- if (this.allowedConsoleMethods.includes(method)) {
149
- continue;
150
- }
151
-
152
- violations.push({
153
- ruleId: this.ruleId,
154
- severity: this.severity,
155
- message: `Do not use console.${method}() in production code. Use proper logging instead.`,
156
- filePath: filePath,
157
- line: lineNumber,
158
- column: match.index + 1,
159
- source: line.trim(),
160
- suggestion: `Consider using a proper logging library (logger.${method}())`
161
- });
162
- }
163
- pattern.lastIndex = 0; // Reset regex
164
- }
165
-
166
- // Check for print/alert calls
167
- for (const pattern of this.printPatterns) {
168
- let match;
169
- while ((match = pattern.exec(line)) !== null) {
170
- // Skip if the match is within a string or comment
171
- if (this.isInCommentOrString(line)) {
172
- continue;
173
- }
174
-
175
- const method = match[0].split('(')[0].trim();
176
-
177
- violations.push({
178
- ruleId: this.ruleId,
179
- severity: this.severity,
180
- message: `Do not use ${method}() in production code. Use proper logging or UI notifications instead.`,
181
- filePath: filePath,
182
- line: lineNumber,
183
- column: match.index + 1,
184
- source: line.trim(),
185
- suggestion: `Consider using a logging library or proper UI notification system`
186
- });
187
- }
188
- pattern.lastIndex = 0; // Reset regex
189
- }
190
- }
191
-
192
- } catch (error) {
193
- console.warn(`C043 analysis error for ${filePath}:`, error.message);
194
- }
195
-
196
- return violations;
197
- }
198
-
199
- isTestFile(filePath) {
200
- const testPatterns = [
201
- /\.test\.(js|ts|jsx|tsx)$/,
202
- /\.spec\.(js|ts|jsx|tsx)$/,
203
- /\/__tests__\//,
204
- /\/tests?\//,
205
- /\.e2e\./,
206
- /\.integration\./,
207
- /test\.config\./,
208
- /jest\.config\./
209
- ];
210
-
211
- return testPatterns.some(pattern => pattern.test(filePath));
212
- }
213
-
214
- isDevelopmentFile(filePath) {
215
- const devPatterns = [
216
- /\.dev\.(js|ts|jsx|tsx)$/,
217
- /\.development\./,
218
- /\.debug\./,
219
- /\/dev\//,
220
- /\/debug\//,
221
- /\/development\//
222
- ];
223
-
224
- return devPatterns.some(pattern => pattern.test(filePath));
225
- }
226
-
227
- isInCommentOrString(line) {
228
- const trimmed = line.trim();
229
-
230
- // Single line comment
231
- if (trimmed.startsWith('//')) {
232
- return true;
233
- }
234
-
235
- // Multi-line comment (basic check)
236
- if (trimmed.startsWith('/*') || trimmed.startsWith('*')) {
237
- return true;
238
- }
239
-
240
- // Check for console usage within quotes (simple approach)
241
- // This is a heuristic - if there are more opening quotes than closing before console, likely inside string
242
- const beforeConsole = line.split(/console\.|print\(|alert\(|confirm\(|prompt\(/)[0];
243
-
244
- if (beforeConsole) {
245
- const singleQuotes = (beforeConsole.match(/'/g) || []).length;
246
- const doubleQuotes = (beforeConsole.match(/"/g) || []).length;
247
- const backticks = (beforeConsole.match(/`/g) || []).length;
248
-
249
- // If any odd count, we're probably inside a string
250
- if (singleQuotes % 2 === 1 || doubleQuotes % 2 === 1 || backticks % 2 === 1) {
251
- return true;
252
- }
253
- }
254
-
255
- return false;
256
- }
257
-
258
- isInDevelopmentContext(lines, currentLineIndex) {
259
- // Look backwards for development context (like if (__DEV__))
260
- const lookBehindLines = 5;
261
- const startIndex = Math.max(0, currentLineIndex - lookBehindLines);
262
-
263
- for (let i = startIndex; i <= currentLineIndex; i++) {
264
- const line = lines[i];
265
-
266
- // Check for development context patterns
267
- for (const pattern of this.devContextPatterns) {
268
- if (pattern.test(line)) {
269
- // Check if current line is within the same block
270
- if (this.isWithinBlock(lines, i, currentLineIndex)) {
271
- return true;
272
- }
273
- }
274
- }
275
- }
276
-
277
- return false;
278
- }
279
-
280
- isWithinBlock(lines, conditionLineIndex, currentLineIndex) {
281
- // Simple block detection based on braces
282
- let braceCount = 0;
283
-
284
- for (let i = conditionLineIndex; i <= currentLineIndex; i++) {
285
- const line = lines[i];
286
-
287
- // Count opening/closing braces
288
- const openBraces = (line.match(/\{/g) || []).length;
289
- const closeBraces = (line.match(/\}/g) || []).length;
290
-
291
- braceCount += openBraces - closeBraces;
292
-
293
- // If we're at current line and still have positive brace count,
294
- // we're likely inside the block
295
- if (i === currentLineIndex && braceCount > 0) {
296
- return true;
297
- }
298
- }
299
-
300
- return false;
301
- }
302
- }
303
-
304
- module.exports = C043NoConsoleOrPrintAnalyzer;
@@ -1,351 +0,0 @@
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
- // 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
- // Allowed centralized retry utilities
21
- this.allowedRetryUtils = [
22
- 'RetryUtil', 'retryWithBackoff', 'withRetry', 'retry',
23
- 'retryAsync', 'retryPromise', 'retryOperation',
24
- 'exponentialBackoff', 'linearBackoff'
25
- ];
26
-
27
- // Detected retry patterns for duplicate checking
28
- this.retryPatterns = [];
29
- }
30
-
31
- async analyze(files, language, options = {}) {
32
- const violations = [];
33
-
34
- for (const filePath of files) {
35
- if (options.verbose) {
36
- console.log(`🔍 Running C047 analysis on ${require('path').basename(filePath)}`);
37
- }
38
-
39
- try {
40
- const content = require('fs').readFileSync(filePath, 'utf8');
41
- this.retryPatterns = []; // Reset for each file
42
- const fileViolations = this.analyzeFile(content, filePath);
43
- violations.push(...fileViolations);
44
- } catch (error) {
45
- console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
46
- }
47
- }
48
-
49
- return violations;
50
- }
51
-
52
- analyzeFile(content, filePath) {
53
- const violations = [];
54
- const lines = content.split('\n');
55
-
56
- // Find all retry patterns in the file
57
- this.findRetryPatterns(lines, filePath);
58
-
59
- // Check for duplicates
60
- const duplicateGroups = this.findDuplicateRetryLogic();
61
-
62
- // Report violations for duplicate patterns - only once per function
63
- const reportedFunctions = new Set();
64
-
65
- duplicateGroups.forEach(group => {
66
- if (group.patterns.length > 1) {
67
- // Find the first occurrence of each function to report
68
- const functionViolations = new Map();
69
-
70
- group.patterns.forEach(pattern => {
71
- const functionName = this.getFunctionNameForLine(lines, pattern.line - 1);
72
- const functionKey = `${functionName}_${Math.floor(pattern.line / 20)}`; // Group by function and rough location
73
-
74
- if (!functionViolations.has(functionKey)) {
75
- functionViolations.set(functionKey, pattern);
76
- }
77
- });
78
-
79
- // Report one violation per function
80
- functionViolations.forEach(pattern => {
81
- violations.push({
82
- file: filePath,
83
- line: pattern.line,
84
- column: pattern.column,
85
- message: `Duplicate retry logic detected (${group.patterns.length} similar patterns found). Consider using a centralized retry utility like RetryUtil.withRetry() or retryWithBackoff().`,
86
- severity: 'warning',
87
- ruleId: this.ruleId,
88
- type: 'duplicate_retry_logic',
89
- duplicateCount: group.patterns.length
90
- });
91
- });
92
- }
93
- });
94
-
95
- return violations;
96
- }
97
-
98
- findRetryPatterns(lines, filePath) {
99
- for (let i = 0; i < lines.length; i++) {
100
- const line = lines[i];
101
- const trimmedLine = line.trim().toLowerCase();
102
-
103
- // Skip comments and empty lines
104
- if (!trimmedLine || trimmedLine.startsWith('//') || trimmedLine.startsWith('/*')) {
105
- continue;
106
- }
107
-
108
- // Skip if using allowed retry utilities
109
- if (this.usesAllowedRetryUtil(line)) {
110
- continue;
111
- }
112
-
113
- // Pattern 1: For/while loop with retry indicators
114
- if (this.isRetryLoopPattern(line, lines, i)) {
115
- const pattern = this.extractRetryPattern(lines, i, 'loop');
116
- if (pattern) {
117
- this.retryPatterns.push({
118
- ...pattern,
119
- line: i + 1,
120
- column: line.indexOf(line.trim()) + 1
121
- });
122
- }
123
- }
124
-
125
- // Pattern 2: Variable declarations with retry indicators
126
- else if (this.isRetryVariableDeclaration(trimmedLine)) {
127
- const pattern = this.extractRetryPattern(lines, i, 'variable');
128
- if (pattern) {
129
- this.retryPatterns.push({
130
- ...pattern,
131
- line: i + 1,
132
- column: line.indexOf(line.trim()) + 1
133
- });
134
- }
135
- }
136
-
137
- // Pattern 3: Recursive function with retry logic
138
- else if (this.isRetryFunctionPattern(lines, i)) {
139
- const pattern = this.extractRetryPattern(lines, i, 'recursive');
140
- if (pattern) {
141
- this.retryPatterns.push({
142
- ...pattern,
143
- line: i + 1,
144
- column: line.indexOf(line.trim()) + 1
145
- });
146
- }
147
- }
148
- }
149
- }
150
-
151
- isRetryLoopPattern(line, lines, index) {
152
- const trimmedLine = line.trim().toLowerCase();
153
-
154
- // Check for for/while loops with retry indicators
155
- if ((trimmedLine.includes('for') || trimmedLine.includes('while')) &&
156
- (trimmedLine.includes('(') && trimmedLine.includes(')'))) {
157
-
158
- // Look in the loop condition and surrounding context for retry indicators
159
- const contextLines = lines.slice(Math.max(0, index - 2), Math.min(lines.length, index + 10));
160
- const contextText = contextLines.join('\n').toLowerCase();
161
-
162
- return this.retryIndicators.some(indicator => contextText.includes(indicator)) &&
163
- (contextText.includes('try') || contextText.includes('catch') || contextText.includes('error'));
164
- }
165
-
166
- return false;
167
- }
168
-
169
- isRetryVariableDeclaration(line) {
170
- // Check for variable declarations with retry-related names
171
- const declarationPatterns = [
172
- /(?:const|let|var)\s+.*(?:retry|attempt|tries|maxretries|maxattempts)/,
173
- /(?:retry|attempt|tries|maxretries|maxattempts)\s*[:=]/
174
- ];
175
-
176
- return declarationPatterns.some(pattern => pattern.test(line));
177
- }
178
-
179
- isRetryFunctionPattern(lines, index) {
180
- const line = lines[index].trim().toLowerCase();
181
-
182
- // Check for function declarations with retry indicators
183
- if ((line.includes('function') || line.includes('=>')) &&
184
- this.retryIndicators.some(indicator => line.includes(indicator))) {
185
-
186
- // Look for retry logic in function body
187
- const functionBody = this.getFunctionBody(lines, index);
188
- return functionBody &&
189
- this.retryIndicators.some(indicator => functionBody.includes(indicator)) &&
190
- (functionBody.includes('try') || functionBody.includes('catch') ||
191
- functionBody.includes('throw') || functionBody.includes('error'));
192
- }
193
-
194
- return false;
195
- }
196
-
197
- usesAllowedRetryUtil(line) {
198
- return this.allowedRetryUtils.some(util =>
199
- line.includes(util) && (line.includes('.') || line.includes('('))
200
- );
201
- }
202
-
203
- extractRetryPattern(lines, startIndex, type) {
204
- // Extract the pattern characteristics for similarity comparison
205
- const contextLines = lines.slice(startIndex, Math.min(lines.length, startIndex + 20));
206
- const contextText = contextLines.join('\n').toLowerCase();
207
-
208
- // Extract key characteristics
209
- const characteristics = {
210
- type: type,
211
- hasForLoop: contextText.includes('for'),
212
- hasWhileLoop: contextText.includes('while'),
213
- hasDoWhile: contextText.includes('do') && contextText.includes('while'),
214
- hasTryCatch: contextText.includes('try') && contextText.includes('catch'),
215
- hasMaxRetries: /max.*(?:retry|attempt|tries)/.test(contextText),
216
- hasBackoff: contextText.includes('backoff') || contextText.includes('delay') || contextText.includes('timeout'),
217
- hasExponential: contextText.includes('exponential') || /math\.pow|2\s*\*\*|\*\s*2/.test(contextText),
218
- hasLinear: contextText.includes('linear') || /\*\s*(?:attempt|retry)/.test(contextText),
219
- hasSetTimeout: contextText.includes('settimeout') || contextText.includes('promise') && contextText.includes('resolve'),
220
- signature: this.generatePatternSignature(contextText)
221
- };
222
-
223
- return characteristics;
224
- }
225
-
226
- generatePatternSignature(contextText) {
227
- // Create a normalized signature for pattern matching
228
- let signature = '';
229
-
230
- if (contextText.includes('for')) signature += 'FOR_';
231
- if (contextText.includes('while')) signature += 'WHILE_';
232
- if (/max.*(?:retry|attempt)/.test(contextText)) signature += 'MAX_';
233
- if (contextText.includes('try') && contextText.includes('catch')) signature += 'TRYCATCH_';
234
- if (contextText.includes('settimeout') || contextText.includes('delay')) signature += 'DELAY_';
235
- if (contextText.includes('exponential') || /math\.pow/.test(contextText)) signature += 'EXPONENTIAL_';
236
- if (contextText.includes('throw')) signature += 'THROW_';
237
-
238
- return signature || 'GENERIC_RETRY';
239
- }
240
-
241
- getFunctionBody(lines, startIndex) {
242
- // Extract function body for analysis
243
- let braceDepth = 0;
244
- let foundStartBrace = false;
245
- const bodyLines = [];
246
-
247
- for (let i = startIndex; i < lines.length; i++) {
248
- const line = lines[i];
249
-
250
- for (const char of line) {
251
- if (char === '{') {
252
- braceDepth++;
253
- foundStartBrace = true;
254
- } else if (char === '}') {
255
- braceDepth--;
256
- }
257
- }
258
-
259
- if (foundStartBrace) {
260
- bodyLines.push(line);
261
-
262
- if (braceDepth === 0) {
263
- break;
264
- }
265
- }
266
- }
267
-
268
- return bodyLines.join('\n').toLowerCase();
269
- }
270
-
271
- findDuplicateRetryLogic() {
272
- const groups = [];
273
- const processed = new Set();
274
-
275
- this.retryPatterns.forEach((pattern, index) => {
276
- if (processed.has(index)) return;
277
-
278
- const similarPatterns = [pattern];
279
- processed.add(index);
280
-
281
- // Find similar patterns
282
- this.retryPatterns.forEach((otherPattern, otherIndex) => {
283
- if (otherIndex !== index && !processed.has(otherIndex)) {
284
- if (this.areSimilarPatterns(pattern, otherPattern)) {
285
- similarPatterns.push(otherPattern);
286
- processed.add(otherIndex);
287
- }
288
- }
289
- });
290
-
291
- if (similarPatterns.length > 0) {
292
- groups.push({
293
- signature: pattern.signature,
294
- patterns: similarPatterns
295
- });
296
- }
297
- });
298
-
299
- return groups;
300
- }
301
-
302
- areSimilarPatterns(pattern1, pattern2) {
303
- // Check if two patterns are similar enough to be considered duplicates
304
-
305
- // Same signature indicates very similar patterns
306
- if (pattern1.signature === pattern2.signature) {
307
- return true;
308
- }
309
-
310
- // Compare characteristics
311
- const similarities = [
312
- pattern1.hasForLoop === pattern2.hasForLoop,
313
- pattern1.hasWhileLoop === pattern2.hasWhileLoop,
314
- pattern1.hasTryCatch === pattern2.hasTryCatch,
315
- pattern1.hasMaxRetries === pattern2.hasMaxRetries,
316
- pattern1.hasBackoff === pattern2.hasBackoff,
317
- pattern1.hasSetTimeout === pattern2.hasSetTimeout
318
- ].filter(Boolean).length;
319
-
320
- // Consider patterns similar if they share at least 4 out of 6 characteristics
321
- return similarities >= 4;
322
- }
323
-
324
- getFunctionNameForLine(lines, lineIndex) {
325
- // Look backwards to find the function declaration for this line
326
- for (let i = lineIndex; i >= 0; i--) {
327
- const line = lines[i].trim();
328
-
329
- // Match function declarations
330
- const functionMatch = line.match(/(?:function|async\s+function)\s+(\w+)/);
331
- if (functionMatch) {
332
- return functionMatch[1];
333
- }
334
-
335
- // Match arrow functions assigned to variables
336
- const arrowMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=.*=>/);
337
- if (arrowMatch) {
338
- return arrowMatch[1];
339
- }
340
-
341
- // Stop at class/module boundaries
342
- if (line.includes('class ') || line.includes('module.exports') || line.includes('export')) {
343
- break;
344
- }
345
- }
346
-
347
- return 'anonymous';
348
- }
349
- }
350
-
351
- module.exports = C047Analyzer;