@sun-asterisk/sunlint 1.3.1 → 1.3.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 (66) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/CONTRIBUTING.md +210 -1691
  3. package/config/rule-analysis-strategies.js +17 -1
  4. package/config/rules/enhanced-rules-registry.json +369 -1135
  5. package/config/rules/rules-registry-generated.json +1 -1
  6. package/core/enhanced-rules-registry.js +2 -1
  7. package/core/semantic-engine.js +15 -3
  8. package/core/semantic-rule-base.js +4 -2
  9. package/engines/heuristic-engine.js +65 -4
  10. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  11. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  12. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  13. package/origin-rules/common-en.md +11 -7
  14. package/package.json +1 -1
  15. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  16. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  17. package/rules/common/C006_function_naming/analyzer.js +29 -3
  18. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  19. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  20. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  21. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  22. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  23. package/rules/common/C013_no_dead_code/config.json +61 -0
  24. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  25. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  26. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  27. package/rules/common/C014_dependency_injection/config.json +26 -0
  28. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  29. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  30. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  31. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  32. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  33. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  34. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  35. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  36. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  37. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  38. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  39. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  40. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  41. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  42. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  43. package/rules/common/C035_error_logging_context/analyzer.js +3 -1
  44. package/rules/index.js +5 -1
  45. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  46. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  47. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  48. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  49. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  50. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  51. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  52. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  53. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  54. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  55. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  56. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  57. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  58. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  59. package/rules/security/S055_content_type_validation/README.md +176 -0
  60. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  61. package/rules/security/S055_content_type_validation/config.json +48 -0
  62. package/rules/utils/rule-helpers.js +140 -1
  63. package/scripts/consolidate-config.js +116 -0
  64. package/config/rules/S027-categories.json +0 -122
  65. package/config/rules/rules-registry.json +0 -777
  66. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -1,206 +1,104 @@
1
1
  /**
2
- * Heuristic analyzer for: C013 Do not leave dead code
3
- * Purpose: Detect unreachable code after return, throw, break, continue statements
2
+ * Symbol-based analyzer for C013 - No Dead Code
3
+ * Uses AST analysis for accurate dead code detection
4
4
  */
5
5
 
6
- class C013Analyzer {
7
- constructor() {
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ class C013NoDeadCodeAnalyzer {
10
+ constructor(semanticEngine = null) {
8
11
  this.ruleId = 'C013';
9
12
  this.ruleName = 'No Dead Code';
10
- this.description = 'Avoid unreachable code after return, throw, break, continue statements';
13
+ this.semanticEngine = semanticEngine;
14
+ this.symbolBasedAnalyzer = null;
15
+ this.verbose = false;
11
16
  }
12
17
 
13
- async analyze(files, language, options = {}) {
14
- const violations = [];
18
+ initialize(options = {}) {
19
+ this.verbose = options.verbose || false;
15
20
 
16
- for (const filePath of files) {
17
- if (options.verbose) {
18
- console.log(`🔍 Running C013 analysis on ${require('path').basename(filePath)}`);
19
- }
21
+ try {
22
+ const SymbolBasedAnalyzer = require('./symbol-based-analyzer.js');
23
+ this.symbolBasedAnalyzer = new SymbolBasedAnalyzer(this.semanticEngine);
24
+ this.symbolBasedAnalyzer.initialize(options);
20
25
 
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}`);
26
+ if (this.verbose) {
27
+ console.log(`[DEBUG] 🎯 C013: Symbol-based analyzer loaded`);
28
+ }
29
+ } catch (error) {
30
+ if (this.verbose) {
31
+ console.log(`[DEBUG] ⚠️ C013: Symbol-based analyzer failed to load: ${error.message}`);
27
32
  }
33
+ throw error; // Fail fast if symbol-based analyzer can't load
28
34
  }
29
-
30
- return violations;
31
35
  }
32
36
 
33
- analyzeFile(content, filePath) {
37
+ async analyze(files, language, options = {}) {
34
38
  const violations = [];
35
- const lines = content.split('\n');
36
39
 
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
- }
40
+ if (options.verbose) {
41
+ console.log(`🔍 Running C013 dead code analysis on ${files.length} files`);
62
42
  }
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);
43
+
44
+ // Check language support
45
+ if (!this.supportsLanguage(language)) {
46
+ if (options.verbose) {
47
+ console.log(`[DEBUG] ⚠️ C013: Language ${language} not supported. Skipping analysis.`);
113
48
  }
49
+ return violations;
114
50
  }
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;
51
+
52
+ // Pass semantic engine to symbol-based analyzer if available
53
+ if (this.symbolBasedAnalyzer && options.semanticEngine) {
54
+ this.symbolBasedAnalyzer.semanticEngine = options.semanticEngine;
130
55
  }
131
56
 
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
57
+ // Use symbol-based analysis (AST-based, high accuracy)
58
+ if (this.symbolBasedAnalyzer) {
59
+ try {
60
+ if (options.verbose) {
61
+ console.log(`[DEBUG] 🎯 C013: Using symbol-based analysis for ${language}`);
146
62
  }
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);
63
+
64
+ const symbolViolations = await this.symbolBasedAnalyzer.analyze(files, language, options);
65
+ violations.push(...symbolViolations);
66
+
67
+ if (options.verbose) {
68
+ console.log(`[DEBUG] 🎯 C013: Symbol-based analysis found ${symbolViolations.length} violations`);
165
69
  }
70
+
71
+ return violations;
72
+ } catch (error) {
73
+ if (options.verbose) {
74
+ console.log(`[DEBUG] ⚠️ C013: Symbol-based analysis failed: ${error.message}`);
75
+ }
76
+ throw error; // Don't fallback, fail fast for debugging
166
77
  }
167
78
  }
168
-
169
- return unreachableLines;
79
+
80
+ throw new Error('Symbol-based analyzer not available');
170
81
  }
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;
82
+
83
+ supportsLanguage(language) {
84
+ // Symbol-based analyzer supports TypeScript and JavaScript
85
+ const supportedLanguages = ['typescript', 'javascript', 'ts', 'js'];
86
+ return supportedLanguages.includes(language?.toLowerCase());
183
87
  }
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;
88
+
89
+ createViolation(filePath, line, column, message, type = 'general') {
90
+ return {
91
+ ruleId: this.ruleId,
92
+ ruleName: this.ruleName,
93
+ severity: 'warning',
94
+ message,
95
+ filePath,
96
+ line,
97
+ column,
98
+ type,
99
+ source: 'symbol-based'
100
+ };
203
101
  }
204
102
  }
205
103
 
206
- module.exports = C013Analyzer;
104
+ module.exports = C013NoDeadCodeAnalyzer;
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "No Dead Code",
3
+ "description": "Detect and eliminate dead code including commented out code, unused variables, and unreachable statements",
4
+ "category": "quality",
5
+ "severity": "warning",
6
+ "languages": ["typescript", "javascript"],
7
+ "enabled": true,
8
+ "type": "hybrid",
9
+ "strategy": {
10
+ "preferred": "ast",
11
+ "fallbacks": ["ast", "regex"],
12
+ "accuracy": {
13
+ "ast": 90,
14
+ "regex": 70
15
+ }
16
+ },
17
+ "maxComplexity": 10,
18
+ "detectionOptions": {
19
+ "commentedCode": {
20
+ "enabled": true,
21
+ "minLineLength": 10,
22
+ "codePatterns": [
23
+ "function\\s+\\w+",
24
+ "const\\s+\\w+\\s*=",
25
+ "let\\s+\\w+\\s*=",
26
+ "var\\s+\\w+\\s*=",
27
+ "if\\s*\\(",
28
+ "for\\s*\\(",
29
+ "while\\s*\\(",
30
+ "return\\s+",
31
+ "console\\.",
32
+ "import\\s+",
33
+ "export\\s+"
34
+ ]
35
+ },
36
+ "unusedVariables": {
37
+ "enabled": true,
38
+ "ignorePrefixes": ["_", "$"],
39
+ "ignoreDestructured": false
40
+ },
41
+ "unusedFunctions": {
42
+ "enabled": true,
43
+ "ignorePrefixes": ["_"],
44
+ "ignoreExported": true
45
+ },
46
+ "unreachableCode": {
47
+ "enabled": true,
48
+ "afterStatements": ["return", "throw", "break", "continue"]
49
+ },
50
+ "unusedImports": {
51
+ "enabled": false,
52
+ "note": "Complex analysis - disabled by default"
53
+ }
54
+ },
55
+ "excludePatterns": [
56
+ "test",
57
+ "spec",
58
+ "mock",
59
+ "fixture"
60
+ ]
61
+ }
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Regex-based analyzer for C013 - No Dead Code
3
+ * Purpose: Simple fallback detection for commented code and basic dead code patterns
4
+ */
5
+
6
+ const { CommentDetector } = require('../../utils/rule-helpers');
7
+
8
+ class C013RegexBasedAnalyzer {
9
+ constructor() {
10
+ this.ruleId = 'C013';
11
+ this.ruleName = 'No Dead Code (Regex-Based)';
12
+ this.verbose = false;
13
+ }
14
+
15
+ initialize(options = {}) {
16
+ this.verbose = options.verbose || false;
17
+
18
+ if (this.verbose) {
19
+ console.log(`[DEBUG] 🔧 C013 Regex-Based: Analyzer initialized`);
20
+ }
21
+ }
22
+
23
+ async analyze(files, language, options = {}) {
24
+ const violations = [];
25
+
26
+ for (const filePath of files) {
27
+ if (options.verbose) {
28
+ console.log(`🔍 Running C013 regex analysis on ${require('path').basename(filePath)}`);
29
+ }
30
+
31
+ try {
32
+ const content = require('fs').readFileSync(filePath, 'utf8');
33
+ const fileViolations = this.analyzeFile(content, filePath);
34
+ violations.push(...fileViolations);
35
+ } catch (error) {
36
+ console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
37
+ }
38
+ }
39
+
40
+ return violations;
41
+ }
42
+
43
+ analyzeFile(content, filePath) {
44
+ const violations = [];
45
+ const lines = content.split('\n');
46
+
47
+ // 1. Detect commented out code
48
+ const commentedCodeViolations = this.detectCommentedCode(lines, filePath);
49
+ violations.push(...commentedCodeViolations);
50
+
51
+ // 2. Detect simple unreachable code patterns
52
+ const unreachableCodeViolations = this.detectUnreachableCode(lines, filePath);
53
+ violations.push(...unreachableCodeViolations);
54
+
55
+ return violations;
56
+ }
57
+
58
+ detectCommentedCode(lines, filePath) {
59
+ const violations = [];
60
+
61
+ // Use CommentDetector to filter out comment lines
62
+ const filteredLines = CommentDetector.filterCommentLines(lines);
63
+
64
+ // High-confidence patterns for commented out code (reduced false positives)
65
+ const highConfidencePatterns = [
66
+ /^(function\s+\w+\s*\()/, // function declarations
67
+ /^(const\s+\w+\s*=\s*)/, // const assignments
68
+ /^(let\s+\w+\s*=\s*)/, // let assignments
69
+ /^(if\s*\([^)]+\)\s*{)/, // if statements with braces
70
+ /^(for\s*\([^)]*\)\s*{)/, // for loops with braces
71
+ /^(while\s*\([^)]+\)\s*{)/, // while loops with braces
72
+ /^(return\s+[^;]+;?)/, // return statements
73
+ /^(import\s+.*from)/, // import statements
74
+ /^(export\s+(default\s+)?)/, // export statements
75
+ /^(class\s+\w+)/, // class declarations
76
+ /^(\w+\s*\([^)]*\)\s*{)/ // method calls with braces
77
+ ];
78
+
79
+ for (let i = 0; i < filteredLines.length; i++) {
80
+ const { line, lineNumber, isComment } = filteredLines[i];
81
+
82
+ // Only process comment lines
83
+ if (!isComment) continue;
84
+
85
+ const trimmedLine = line.trim();
86
+
87
+ // Skip obvious documentation comments
88
+ if (this.isDocumentationComment(trimmedLine)) {
89
+ continue;
90
+ }
91
+
92
+ // Extract comment content
93
+ let commentContent = '';
94
+ if (trimmedLine.startsWith('//')) {
95
+ commentContent = trimmedLine.substring(2).trim();
96
+ } else if (trimmedLine.startsWith('/*') && trimmedLine.endsWith('*/')) {
97
+ commentContent = trimmedLine.substring(2, trimmedLine.length - 2).trim();
98
+ } else {
99
+ continue; // Skip other comment types
100
+ }
101
+
102
+ // Skip very short comments or obvious explanations
103
+ if (commentContent.length < 10 || this.isExplanatoryComment(commentContent)) {
104
+ continue;
105
+ }
106
+
107
+ // Check for high-confidence code patterns
108
+ const isLikelyCode = highConfidencePatterns.some(pattern => pattern.test(commentContent));
109
+
110
+ if (isLikelyCode && this.looksLikeRealCode(commentContent)) {
111
+ violations.push({
112
+ file: filePath,
113
+ line: lineNumber,
114
+ column: line.indexOf(trimmedLine.charAt(0)) + 1,
115
+ message: `Commented out code detected: "${commentContent.substring(0, 50)}...". Remove dead code or use Git for version history.`,
116
+ severity: 'warning',
117
+ ruleId: this.ruleId,
118
+ type: 'commented-code'
119
+ });
120
+ }
121
+ }
122
+
123
+ return violations;
124
+ }
125
+
126
+ isExplanatoryComment(commentContent) {
127
+ const explanatoryStarters = [
128
+ 'this', 'the', 'we', 'it', 'you', 'note:', 'warning:', 'todo:', 'fixme:',
129
+ 'explanation', 'reason', 'because', 'when', 'if you', 'make sure',
130
+ 'see', 'check', 'ensure', 'verify', 'remember', 'important'
131
+ ];
132
+
133
+ const lowerText = commentContent.toLowerCase();
134
+ return explanatoryStarters.some(starter => lowerText.startsWith(starter));
135
+ }
136
+
137
+ isDocumentationComment(line) {
138
+ // Skip JSDoc and obvious documentation
139
+ if (line.startsWith('/**') || line.startsWith('*') || line.includes('TODO') || line.includes('FIXME')) {
140
+ return true;
141
+ }
142
+
143
+ // Skip comments that are clearly explanatory
144
+ const explanatoryWords = ['note', 'todo', 'fixme', 'hack', 'bug', 'issue', 'warning', 'caution'];
145
+ const commentText = line.substring(2).toLowerCase().trim();
146
+
147
+ return explanatoryWords.some(word => commentText.startsWith(word));
148
+ }
149
+
150
+ looksLikeRealCode(commentContent) {
151
+ // Must have code-like characteristics
152
+ const hasCodeCharacteristics = (
153
+ commentContent.includes('(') ||
154
+ commentContent.includes('{') ||
155
+ commentContent.includes('=') ||
156
+ commentContent.includes(';') ||
157
+ commentContent.includes('.') ||
158
+ /\w+\.\w+/.test(commentContent) ||
159
+ /const\s+\w+/.test(commentContent) ||
160
+ /let\s+\w+/.test(commentContent) ||
161
+ /return\s+/.test(commentContent) ||
162
+ /console\./.test(commentContent) ||
163
+ /\w+\s*=\s*/.test(commentContent)
164
+ );
165
+
166
+ // And be substantial enough (lowered threshold)
167
+ const isLongEnough = commentContent.length >= 10;
168
+
169
+ // And not be obvious explanatory text
170
+ const isNotExplanatory = !this.isExplanatoryComment(commentContent);
171
+
172
+ return hasCodeCharacteristics && isLongEnough && isNotExplanatory;
173
+ }
174
+
175
+ isExplanatoryComment(text) {
176
+ const explanatoryStarters = [
177
+ 'this', 'the', 'we', 'it', 'you', 'note:', 'warning:', 'todo:', 'fixme:',
178
+ 'explanation', 'reason', 'because', 'when', 'if you', 'make sure',
179
+ 'describes', 'explanation:', 'note that', 'important:', 'remember'
180
+ ];
181
+
182
+ const lowerText = text.toLowerCase().trim();
183
+ return explanatoryStarters.some(starter => lowerText.startsWith(starter));
184
+ }
185
+
186
+ checkCommentBlock(lines, startIndex, filePath) {
187
+ let commentBlock = '';
188
+ let endIndex = startIndex;
189
+
190
+ // Collect the full comment block
191
+ for (let j = startIndex; j < lines.length; j++) {
192
+ commentBlock += lines[j] + '\n';
193
+ if (lines[j].includes('*/')) {
194
+ endIndex = j;
195
+ break;
196
+ }
197
+ }
198
+
199
+ // Clean the comment block
200
+ const cleanedComment = commentBlock
201
+ .replace(/\/\*|\*\/|\*\s*/g, '')
202
+ .trim();
203
+
204
+ // Check if it looks like commented code
205
+ if (cleanedComment.length >= 50 && this.blockLooksLikeCode(cleanedComment)) {
206
+ return {
207
+ file: filePath,
208
+ line: startIndex + 1,
209
+ column: lines[startIndex].indexOf('/*') + 1,
210
+ message: `Commented out code block detected. Remove dead code or use Git for version history.`,
211
+ severity: 'warning',
212
+ ruleId: this.ruleId,
213
+ type: 'commented-code-block'
214
+ };
215
+ }
216
+
217
+ return null;
218
+ }
219
+
220
+ blockLooksLikeCode(text) {
221
+ const codeIndicators = [
222
+ /function\s+\w+/g,
223
+ /const\s+\w+\s*=/g,
224
+ /let\s+\w+\s*=/g,
225
+ /if\s*\(/g,
226
+ /for\s*\(/g,
227
+ /return\s+/g,
228
+ /\w+\s*=\s*\w+/g,
229
+ /\w+\.\w+/g
230
+ ];
231
+
232
+ let indicatorCount = 0;
233
+ for (const indicator of codeIndicators) {
234
+ const matches = text.match(indicator);
235
+ if (matches) {
236
+ indicatorCount += matches.length;
237
+ }
238
+ }
239
+
240
+ // If we find multiple code indicators, it's likely commented code
241
+ return indicatorCount >= 3;
242
+ }
243
+
244
+ detectUnreachableCode(lines, filePath) {
245
+ const violations = [];
246
+
247
+ for (let i = 0; i < lines.length; i++) {
248
+ const line = lines[i].trim();
249
+
250
+ // Skip empty lines and comments
251
+ if (!line || line.startsWith('//') || line.startsWith('/*') || line.startsWith('*')) {
252
+ continue;
253
+ }
254
+
255
+ // Look for simple return statements
256
+ if (this.isSimpleReturn(line)) {
257
+ // Check if there's code after this return within the same block
258
+ const unreachableLines = this.findUnreachableCodeAfterReturn(lines, i);
259
+
260
+ for (const unreachableLine of unreachableLines) {
261
+ violations.push({
262
+ file: filePath,
263
+ line: unreachableLine + 1,
264
+ column: 1,
265
+ message: `Unreachable code detected after return statement. Remove dead code.`,
266
+ severity: 'warning',
267
+ ruleId: this.ruleId,
268
+ type: 'unreachable-code'
269
+ });
270
+ }
271
+ }
272
+ }
273
+
274
+ return violations;
275
+ }
276
+
277
+ isSimpleReturn(line) {
278
+ // Match simple return statements
279
+ const cleanLine = line.replace(/;?\s*$/, '');
280
+
281
+ return (
282
+ /^return\s*;?\s*$/.test(cleanLine) ||
283
+ /^return\s+[^{}\[\(]+;?\s*$/.test(cleanLine) ||
284
+ /^return\s+(true|false|null|undefined|\d+|"[^"]*"|'[^']*')\s*;?\s*$/.test(cleanLine)
285
+ );
286
+ }
287
+
288
+ findUnreachableCodeAfterReturn(lines, returnLineIndex) {
289
+ const unreachableLines = [];
290
+
291
+ // Look for code after the return statement until we hit a closing brace
292
+ for (let i = returnLineIndex + 1; i < lines.length; i++) {
293
+ const line = lines[i].trim();
294
+
295
+ // Skip empty lines and comments
296
+ if (!line || line.startsWith('//') || line.startsWith('/*') || line.startsWith('*')) {
297
+ continue;
298
+ }
299
+
300
+ // Stop if we hit a closing brace (end of function/block)
301
+ if (line === '}' || line === '};' || line.startsWith('} ')) {
302
+ break;
303
+ }
304
+
305
+ // Stop if we hit catch/finally blocks (these are reachable)
306
+ if (line.includes('catch') || line.includes('finally') ||
307
+ line.startsWith('} catch') || line.startsWith('} finally') ||
308
+ line === '} catch (e) {' || line.match(/}\s*catch\s*\(/)) {
309
+ break;
310
+ }
311
+
312
+ // This looks like unreachable code
313
+ if (this.isExecutableCode(line)) {
314
+ unreachableLines.push(i);
315
+ break; // Only report the first unreachable line to avoid spam
316
+ }
317
+ }
318
+
319
+ return unreachableLines;
320
+ }
321
+
322
+ isExecutableCode(line) {
323
+ // Exclude lines that are just structural
324
+ if (line === '}' || line === '};' || line === '},' || line.match(/^\s*}\s*$/)) {
325
+ return false;
326
+ }
327
+
328
+ // Exclude catch/finally blocks and their variations
329
+ if (line.includes('catch') || line.includes('finally') ||
330
+ line.startsWith('} catch') || line.startsWith('} finally') ||
331
+ line.match(/}\s*catch\s*\(/) || line.match(/}\s*finally\s*\{/)) {
332
+ return false;
333
+ }
334
+
335
+ // Exclude plain closing braces with catch/finally
336
+ if (line.match(/^\s*}\s*(catch|finally)/)) {
337
+ return false;
338
+ }
339
+
340
+ // This looks like executable code
341
+ return true;
342
+ }
343
+ }
344
+
345
+ module.exports = C013RegexBasedAnalyzer;