@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,389 +0,0 @@
1
- /**
2
- * C010 Rule: Limit Block Nesting Depth
3
- * Prevents deep nesting of blocks (if/for/while/switch/try) to improve code readability
4
- * Maximum recommended depth: 3 levels
5
- * Severity: warning
6
- * Category: Maintainability
7
- */
8
-
9
- const fs = require('fs');
10
- const path = require('path');
11
-
12
- class C010LimitBlockNestingAnalyzer {
13
- constructor() {
14
- this.ruleId = 'C010';
15
- this.ruleName = 'Limit Block Nesting';
16
- this.description = 'Do not exceed maximum block nesting depth for better readability';
17
- this.severity = 'warning';
18
- this.maxDepth = 3;
19
-
20
- // Control flow blocks that create nesting - Match ESLint rule exactly
21
- // Only: if, for, while, do-while, switch, try-catch statements
22
- this.blockPatterns = [
23
- // if/else patterns - handle both same-line and multi-line blocks
24
- { pattern: /^\s*if\s*\(.*\)\s*\{/, type: 'if', opens: true },
25
- { pattern: /^\s*if\s*\(.*\)\s*$/, type: 'if-pending', opens: false, needsBrace: true },
26
- { pattern: /^\s*else\s*if\s*\(.*\)\s*\{/, type: 'else-if', opens: true },
27
- { pattern: /^\s*else\s*if\s*\(.*\)\s*$/, type: 'else-if-pending', opens: false, needsBrace: true },
28
- { pattern: /^\s*else\s*\{/, type: 'else', opens: true },
29
- { pattern: /^\s*else\s*$/, type: 'else-pending', opens: false, needsBrace: true },
30
-
31
- // Loop patterns - handle both same-line and multi-line blocks
32
- { pattern: /^\s*for\s*\(.*\)\s*\{/, type: 'for', opens: true },
33
- { pattern: /^\s*for\s*\(.*\)\s*$/, type: 'for-pending', opens: false, needsBrace: true },
34
- { pattern: /^\s*while\s*\(.*\)\s*\{/, type: 'while', opens: true },
35
- { pattern: /^\s*while\s*\(.*\)\s*$/, type: 'while-pending', opens: false, needsBrace: true },
36
- { pattern: /^\s*do\s*\{/, type: 'do-while', opens: true },
37
- { pattern: /^\s*do\s*$/, type: 'do-while-pending', opens: false, needsBrace: true },
38
-
39
- // Switch statements (not individual case blocks)
40
- { pattern: /^\s*switch\s*\(.*\)\s*\{/, type: 'switch', opens: true },
41
- { pattern: /^\s*switch\s*\(.*\)\s*$/, type: 'switch-pending', opens: false, needsBrace: true },
42
-
43
- // Try-catch patterns
44
- { pattern: /^\s*try\s*\{/, type: 'try', opens: true },
45
- { pattern: /^\s*try\s*$/, type: 'try-pending', opens: false, needsBrace: true },
46
- { pattern: /^\s*catch\s*\(.*\)\s*\{/, type: 'catch', opens: true },
47
- { pattern: /^\s*catch\s*\(.*\)\s*$/, type: 'catch-pending', opens: false, needsBrace: true },
48
- { pattern: /^\s*finally\s*\{/, type: 'finally', opens: true },
49
- { pattern: /^\s*finally\s*$/, type: 'finally-pending', opens: false, needsBrace: true },
50
-
51
- // With statements (rarely used but included for completeness)
52
- { pattern: /^\s*with\s*\(.*\)\s*\{/, type: 'with', opens: true },
53
- { pattern: /^\s*with\s*\(.*\)\s*$/, type: 'with-pending', opens: false, needsBrace: true },
54
-
55
- // Standalone opening brace (follows pending blocks)
56
- { pattern: /^\s*\{\s*$/, type: 'brace-block', opens: true }
57
- ];
58
-
59
- // Track pending blocks that expect a brace on next line
60
- this.pendingBlocks = [];
61
-
62
- // Patterns for inline blocks (without braces)
63
- this.inlineBlockPatterns = [
64
- { pattern: /^\s*if\s*\(.*\)\s*[^{]/, type: 'if-inline' },
65
- { pattern: /^\s*else\s+if\s*\(.*\)\s*[^{]/, type: 'else-if-inline' },
66
- { pattern: /^\s*else\s+[^{]/, type: 'else-inline' },
67
- { pattern: /^\s*for\s*\(.*\)\s*[^{]/, type: 'for-inline' },
68
- { pattern: /^\s*while\s*\(.*\)\s*[^{]/, type: 'while-inline' }
69
- ];
70
- }
71
-
72
- async analyze(files, language, config = {}) {
73
- const violations = [];
74
-
75
- if (config?.rules?.C010?.maxDepth) {
76
- this.maxDepth = config.rules.C010.maxDepth;
77
- }
78
-
79
- for (const filePath of files) {
80
- try {
81
- if (this.isTestFile(filePath)) {
82
- continue;
83
- }
84
-
85
- const fileContent = fs.readFileSync(filePath, 'utf8');
86
- const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
87
- violations.push(...fileViolations);
88
- } catch (error) {
89
- console.warn(`C010 analysis error for ${filePath}:`, error.message);
90
- }
91
- }
92
-
93
- return violations;
94
- }
95
-
96
- async analyzeFile(filePath, fileContent, language, config) {
97
- const violations = [];
98
-
99
- // Reset pending blocks for each file
100
- this.pendingBlocks = [];
101
-
102
- try {
103
- const lines = fileContent.split('\n');
104
- let controlFlowStack = []; // Only track control flow blocks
105
- let commentState = { inMultiLine: false, startChar: 0 };
106
-
107
- for (let i = 0; i < lines.length; i++) {
108
- const line = lines[i];
109
- const lineNumber = i + 1;
110
-
111
- // Update comment state and skip if in comment
112
- commentState = this.updateCommentState(line, commentState);
113
- if (this.isLineInComment(line, commentState)) {
114
- continue;
115
- }
116
-
117
- const trimmedLine = line.trim();
118
- if (!trimmedLine) continue;
119
-
120
- // Track control flow statements
121
- const controlFlowMatch = this.detectControlFlow(trimmedLine);
122
- if (controlFlowMatch) {
123
- // Check if this line has opening brace (same line)
124
- if (trimmedLine.includes('{')) {
125
- controlFlowStack.push({
126
- type: controlFlowMatch.type,
127
- line: lineNumber,
128
- column: this.getBlockStartColumn(line)
129
- });
130
-
131
- // Check depth violation at the control flow statement
132
- if (controlFlowStack.length > this.maxDepth) {
133
- violations.push(this.createViolation(
134
- filePath,
135
- lineNumber,
136
- this.getBlockStartColumn(line),
137
- line,
138
- controlFlowStack.length,
139
- controlFlowStack
140
- ));
141
- }
142
- } else {
143
- // Look ahead for opening brace on next line
144
- let braceLineIndex = -1;
145
- for (let j = i + 1; j < Math.min(i + 3, lines.length); j++) {
146
- if (lines[j].trim() === '{') {
147
- braceLineIndex = j;
148
- break;
149
- }
150
- if (lines[j].trim() !== '') break; // Stop if non-empty, non-brace line
151
- }
152
-
153
- if (braceLineIndex >= 0) {
154
- controlFlowStack.push({
155
- type: controlFlowMatch.type,
156
- line: braceLineIndex + 1,
157
- column: this.getBlockStartColumn(lines[braceLineIndex])
158
- });
159
-
160
- // Check depth violation at the opening brace
161
- if (controlFlowStack.length > this.maxDepth) {
162
- violations.push(this.createViolation(
163
- filePath,
164
- braceLineIndex + 1,
165
- this.getBlockStartColumn(lines[braceLineIndex]),
166
- lines[braceLineIndex],
167
- controlFlowStack.length,
168
- controlFlowStack
169
- ));
170
- }
171
- }
172
- }
173
- }
174
-
175
- // Handle closing braces - only remove if we have control flow blocks
176
- const closeBraces = (line.match(/\}/g) || []).length;
177
- for (let j = 0; j < closeBraces && controlFlowStack.length > 0; j++) {
178
- controlFlowStack.pop();
179
- }
180
- }
181
-
182
- } catch (error) {
183
- console.warn(`C010 analysis error for ${filePath}:`, error.message);
184
- }
185
-
186
- return violations;
187
- }
188
-
189
- detectControlFlow(line) {
190
- // Match control flow keywords that create nesting
191
- const patterns = [
192
- { pattern: /^\s*if\s*\(/, type: 'if' },
193
- { pattern: /^\s*else\s+if\s*\(/, type: 'else-if' },
194
- { pattern: /^\s*else\s*$/, type: 'else' },
195
- { pattern: /^\s*for\s*\(/, type: 'for' },
196
- { pattern: /^\s*while\s*\(/, type: 'while' },
197
- { pattern: /^\s*do\s*$/, type: 'do-while' },
198
- { pattern: /^\s*switch\s*\(/, type: 'switch' },
199
- { pattern: /^\s*try\s*$/, type: 'try' },
200
- { pattern: /^\s*catch\s*\(/, type: 'catch' },
201
- { pattern: /^\s*finally\s*$/, type: 'finally' },
202
- { pattern: /^\s*with\s*\(/, type: 'with' },
203
- // Handle closing brace followed by control flow
204
- { pattern: /^\s*}\s*else\s+if\s*\(/, type: 'else-if' },
205
- { pattern: /^\s*}\s*else\s*$/, type: 'else' },
206
- { pattern: /^\s*}\s*catch\s*\(/, type: 'catch' },
207
- { pattern: /^\s*}\s*finally\s*$/, type: 'finally' }
208
- ];
209
-
210
- for (const pattern of patterns) {
211
- if (pattern.pattern.test(line)) {
212
- return { type: pattern.type };
213
- }
214
- }
215
-
216
- return null;
217
- }
218
-
219
- updateCommentState(line, currentState) {
220
- let { inMultiLine, startChar } = currentState;
221
- let pos = startChar;
222
-
223
- while (pos < line.length) {
224
- if (!inMultiLine) {
225
- // Check for single line comment
226
- if (line.substr(pos, 2) === '//') {
227
- return { inMultiLine: false, startChar: line.length };
228
- }
229
- // Check for multi-line comment start
230
- if (line.substr(pos, 2) === '/*') {
231
- inMultiLine = true;
232
- pos += 2;
233
- continue;
234
- }
235
- } else {
236
- // Check for multi-line comment end
237
- if (line.substr(pos, 2) === '*/') {
238
- inMultiLine = false;
239
- pos += 2;
240
- continue;
241
- }
242
- }
243
- pos++;
244
- }
245
-
246
- return { inMultiLine, startChar: 0 };
247
- }
248
-
249
- isLineInComment(line, commentState) {
250
- const trimmed = line.trim();
251
-
252
- // Single line comment
253
- if (trimmed.startsWith('//')) return true;
254
-
255
- // JSDoc style comment
256
- if (trimmed.startsWith('*') && !trimmed.startsWith('*/')) return true;
257
-
258
- // In multi-line comment
259
- if (commentState.inMultiLine) {
260
- // Unless the line ends the comment, it's a comment line
261
- return !line.includes('*/');
262
- }
263
-
264
- return false;
265
- }
266
-
267
- detectBlockOpening(trimmedLine, fullLine) {
268
- // First check if this is a standalone opening brace that follows a pending block
269
- if (trimmedLine === '{' && this.pendingBlocks.length > 0) {
270
- const pendingBlock = this.pendingBlocks.pop();
271
- return {
272
- opens: true,
273
- type: pendingBlock.type.replace('-pending', ''),
274
- column: this.getBlockStartColumn(fullLine),
275
- inline: false
276
- };
277
- }
278
-
279
- // Check for block patterns
280
- for (const blockPattern of this.blockPatterns) {
281
- if (blockPattern.pattern.test(trimmedLine)) {
282
- if (blockPattern.needsBrace) {
283
- // This is a pending block, add to pending list
284
- this.pendingBlocks.push({
285
- type: blockPattern.type,
286
- line: fullLine
287
- });
288
- return { opens: false };
289
- } else {
290
- return {
291
- opens: blockPattern.opens,
292
- type: blockPattern.type,
293
- column: this.getBlockStartColumn(fullLine),
294
- inline: false
295
- };
296
- }
297
- }
298
- }
299
-
300
- return { opens: false };
301
- }
302
-
303
- detectInlineBlock(trimmedLine) {
304
- // Skip if line ends with { or ;
305
- if (trimmedLine.endsWith('{') || trimmedLine.endsWith(';')) {
306
- return null;
307
- }
308
-
309
- for (const pattern of this.inlineBlockPatterns) {
310
- if (pattern.pattern.test(trimmedLine)) {
311
- return { type: pattern.type };
312
- }
313
- }
314
-
315
- return null;
316
- }
317
-
318
- isClosingBrace(line) {
319
- // Match closing brace, possibly followed by else/catch/finally
320
- return /^\s*}\s*(else|catch|finally)?\s*(\{|$)/.test(line);
321
- }
322
-
323
- handleClosingBrace(blockStack) {
324
- if (blockStack.length > 0) {
325
- // Remove the most recent block
326
- blockStack.pop();
327
- }
328
- }
329
-
330
- calculateEffectiveDepth(blockStack) {
331
- // Count only non-inline blocks for depth calculation
332
- return blockStack.filter(block => !block.inline).length;
333
- }
334
-
335
- getBlockStartColumn(line) {
336
- const match = line.match(/^\s*/);
337
- return match ? match[0].length + 1 : 1;
338
- }
339
-
340
- isTestFile(filePath) {
341
- const testPatterns = [
342
- /\.test\.(js|ts|jsx|tsx)$/,
343
- /\.spec\.(js|ts|jsx|tsx)$/,
344
- /\/__tests__\//,
345
- /\/tests?\//,
346
- /\.e2e\./,
347
- /test\.config\./,
348
- /jest\.config\./,
349
- /vitest\.config\./,
350
- /cypress\//
351
- ];
352
-
353
- return testPatterns.some(pattern => pattern.test(filePath));
354
- }
355
-
356
- createViolation(filePath, lineNumber, column, sourceLine, depth, blockStack) {
357
- return {
358
- ruleId: this.ruleId,
359
- severity: this.severity,
360
- message: `Block nesting depth ${depth} exceeds maximum of ${this.maxDepth}. Consider refactoring to reduce complexity.`,
361
- filePath: filePath,
362
- line: lineNumber,
363
- column: column,
364
- source: sourceLine.trim(),
365
- suggestion: this.getSuggestion(depth),
366
- nestingStack: blockStack.map(b => ({
367
- type: b.type,
368
- line: b.line,
369
- inline: b.inline || false
370
- }))
371
- };
372
- }
373
-
374
- getSuggestion(currentDepth) {
375
- const suggestions = [
376
- "Extract nested logic into separate functions",
377
- "Use early returns to reduce nesting",
378
- "Consider using guard clauses",
379
- "Break complex conditions into meaningful variables",
380
- "Use strategy pattern for complex conditional logic",
381
- "Consider using a state machine for complex flow control"
382
- ];
383
-
384
- const index = Math.min(currentDepth - this.maxDepth - 1, suggestions.length - 1);
385
- return suggestions[Math.max(0, index)];
386
- }
387
- }
388
-
389
- module.exports = C010LimitBlockNestingAnalyzer;
@@ -1,206 +0,0 @@
1
- /**
2
- * Heuristic analyzer for: C013 – Do not leave dead code
3
- * Purpose: Detect unreachable code after return, throw, break, continue statements
4
- */
5
-
6
- class C013Analyzer {
7
- constructor() {
8
- this.ruleId = 'C013';
9
- this.ruleName = 'No Dead Code';
10
- this.description = 'Avoid unreachable code after return, throw, break, continue statements';
11
- }
12
-
13
- async analyze(files, language, options = {}) {
14
- const violations = [];
15
-
16
- for (const filePath of files) {
17
- if (options.verbose) {
18
- console.log(`🔍 Running C013 analysis on ${require('path').basename(filePath)}`);
19
- }
20
-
21
- try {
22
- const content = require('fs').readFileSync(filePath, 'utf8');
23
- const fileViolations = this.analyzeFile(content, filePath);
24
- violations.push(...fileViolations);
25
- } catch (error) {
26
- console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
27
- }
28
- }
29
-
30
- return violations;
31
- }
32
-
33
- analyzeFile(content, filePath) {
34
- const violations = [];
35
- const lines = content.split('\n');
36
-
37
- for (let i = 0; i < lines.length; i++) {
38
- const line = lines[i].trim();
39
-
40
- // Skip empty lines and comments
41
- if (!line || line.startsWith('//') || line.startsWith('/*') || line.startsWith('*')) {
42
- continue;
43
- }
44
-
45
- // Only look for return statements (not throw in catch blocks)
46
- if (this.isSimpleReturn(line)) {
47
- // Check if there are non-comment, non-empty lines after this return
48
- // within the same function scope
49
- const unreachableLines = this.findUnreachableCodeSimple(lines, i);
50
-
51
- for (const unreachableLine of unreachableLines) {
52
- violations.push({
53
- file: filePath,
54
- line: unreachableLine + 1,
55
- column: 1,
56
- message: `Unreachable code detected after return statement. Remove dead code or restructure logic.`,
57
- severity: 'warning',
58
- ruleId: this.ruleId
59
- });
60
- }
61
- }
62
- }
63
-
64
- return violations;
65
- }
66
-
67
- isSimpleReturn(line) {
68
- // Only detect simple return statements that actually complete, not multiline returns
69
- const cleanLine = line.replace(/;?\s*$/, '');
70
-
71
- return (
72
- // Complete return statements with semicolon or at end of line
73
- /^return\s*;?\s*$/.test(cleanLine) ||
74
- /^return\s+[^{}\[\(]+;?\s*$/.test(cleanLine) ||
75
- // Single-line returns with simple values
76
- /^return\s+(true|false|null|undefined|\d+|"[^"]*"|'[^']*')\s*;?\s*$/.test(cleanLine) ||
77
- // Handle single-line conditional returns
78
- /}\s*else\s*return\s+[^{}\[\(]+;?\s*$/.test(line) ||
79
- /}\s*return\s+[^{}\[\(]+;?\s*$/.test(line)
80
- );
81
- }
82
-
83
- findUnreachableCodeSimple(lines, terminatingLineIndex) {
84
- const unreachableLines = [];
85
-
86
- // Look for code after the return statement until we hit a closing brace or new function
87
- for (let i = terminatingLineIndex + 1; i < lines.length; i++) {
88
- const line = lines[i].trim();
89
-
90
- // Skip empty lines and comments
91
- if (!line || line.startsWith('//') || line.startsWith('/*') || line.startsWith('*')) {
92
- continue;
93
- }
94
-
95
- // Stop if we hit a closing brace (end of function/block)
96
- if (line === '}' || line === '};' || line.startsWith('} ')) {
97
- break;
98
- }
99
-
100
- // Stop if we hit catch/finally (these are reachable)
101
- if (line.includes('catch') || line.includes('finally')) {
102
- break;
103
- }
104
-
105
- // Stop if we hit a new function or method definition
106
- if (line.includes('function') || line.includes('=>') || line.match(/^\w+\s*\(/)) {
107
- break;
108
- }
109
-
110
- // This looks like unreachable code
111
- if (this.isExecutableCode(line)) {
112
- unreachableLines.push(i);
113
- }
114
- }
115
-
116
- return unreachableLines;
117
- }
118
-
119
- findUnreachableCode(lines, terminatingLineIndex, content) {
120
- const unreachableLines = [];
121
- let braceDepth = this.getCurrentBraceDepth(lines, terminatingLineIndex, content);
122
- let currentDepth = braceDepth;
123
- let inTryCatchFinally = false;
124
-
125
- // Check if we're inside a try-catch-finally context
126
- const contextBefore = lines.slice(0, terminatingLineIndex).join(' ');
127
- if (contextBefore.includes('try') || contextBefore.includes('catch') || contextBefore.includes('finally')) {
128
- // Need more sophisticated detection of try-catch-finally blocks
129
- inTryCatchFinally = true;
130
- }
131
-
132
- // Look for unreachable code after the terminating statement
133
- for (let i = terminatingLineIndex + 1; i < lines.length; i++) {
134
- const line = lines[i].trim();
135
-
136
- // Skip empty lines and comments
137
- if (!line || line.startsWith('//') || line.startsWith('/*') || line.startsWith('*')) {
138
- continue;
139
- }
140
-
141
- // Special handling for try-catch-finally: finally blocks are always reachable
142
- if (line.includes('finally') || (inTryCatchFinally && line === '}')) {
143
- // Don't mark finally blocks or their closing braces as unreachable
144
- if (line.includes('finally')) {
145
- inTryCatchFinally = false; // Reset after seeing finally
146
- }
147
- continue;
148
- }
149
-
150
- // Update brace depth
151
- const openBraces = (line.match(/{/g) || []).length;
152
- const closeBraces = (line.match(/}/g) || []).length;
153
- currentDepth += openBraces - closeBraces;
154
-
155
- // If we've exited the current block scope, stop looking
156
- if (currentDepth < braceDepth) {
157
- break;
158
- }
159
-
160
- // If we're still in the same block scope, this is potentially unreachable
161
- if (currentDepth === braceDepth) {
162
- // Check if this line contains executable code (not just closing braces)
163
- if (this.isExecutableCode(line)) {
164
- unreachableLines.push(i);
165
- }
166
- }
167
- }
168
-
169
- return unreachableLines;
170
- }
171
-
172
- getCurrentBraceDepth(lines, lineIndex, content) {
173
- // Calculate the brace depth at the given line
174
- let depth = 0;
175
- const textUpToLine = lines.slice(0, lineIndex + 1).join('\n');
176
-
177
- for (let char of textUpToLine) {
178
- if (char === '{') depth++;
179
- if (char === '}') depth--;
180
- }
181
-
182
- return depth;
183
- }
184
-
185
- isExecutableCode(line) {
186
- // Exclude lines that are just structural (closing braces, etc.)
187
- if (line === '}' || line === '};' || line === '},' || line.match(/^\s*}\s*$/)) {
188
- return false;
189
- }
190
-
191
- // Exclude catch/finally blocks (they are reachable)
192
- if (line.includes('catch') || line.includes('finally')) {
193
- return false;
194
- }
195
-
196
- // Exclude case/default statements (they might be reachable)
197
- if (line.startsWith('case ') || line.startsWith('default:')) {
198
- return false;
199
- }
200
-
201
- // This looks like executable code
202
- return true;
203
- }
204
- }
205
-
206
- module.exports = C013Analyzer;