@sun-asterisk/sunlint 1.3.0 → 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.
- package/CHANGELOG.md +115 -1
- package/CONTRIBUTING.md +249 -605
- package/README.md +3 -4
- package/config/ci-cd.json +54 -0
- package/config/development.json +56 -0
- package/config/large-project.json +143 -0
- package/config/presets/all.json +0 -1
- package/config/release.json +70 -0
- package/config/rule-analysis-strategies.js +38 -3
- package/config/rules/enhanced-rules-registry.json +474 -1179
- package/config/rules/rules-registry-generated.json +3 -3
- package/core/cli-action-handler.js +24 -30
- package/core/cli-program.js +11 -3
- package/core/config-merger.js +29 -2
- package/core/enhanced-rules-registry.js +3 -2
- package/core/semantic-engine.js +129 -19
- package/core/semantic-rule-base.js +4 -2
- package/core/unified-rule-registry.js +1 -1
- package/docs/COMMAND-EXAMPLES.md +134 -0
- package/docs/LARGE-PROJECT-GUIDE.md +324 -0
- package/engines/heuristic-engine.js +135 -16
- package/integrations/eslint/plugin/index.js +0 -2
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
- package/origin-rules/common-en.md +19 -15
- package/package.json +1 -1
- package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
- package/rules/common/C006_function_naming/analyzer.js +29 -3
- package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
- package/rules/common/C010_limit_block_nesting/config.json +64 -0
- package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
- package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
- package/rules/common/C013_no_dead_code/analyzer.js +75 -177
- package/rules/common/C013_no_dead_code/config.json +61 -0
- package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
- package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
- package/rules/common/C014_dependency_injection/analyzer.js +48 -313
- package/rules/common/C014_dependency_injection/config.json +26 -0
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
- package/rules/common/C018_no_throw_generic_error/config.json +50 -0
- package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +110 -317
- package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
- package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
- package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
- package/rules/common/C023_no_duplicate_variable/config.json +50 -0
- package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
- package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
- package/rules/common/C033_separate_service_repository/README.md +78 -0
- package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
- package/rules/common/C033_separate_service_repository/config.json +50 -0
- package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
- package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
- package/rules/common/C035_error_logging_context/analyzer.js +232 -0
- package/rules/common/C035_error_logging_context/config.json +54 -0
- package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
- package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
- package/rules/common/C040_centralized_validation/analyzer.js +165 -0
- package/rules/common/C040_centralized_validation/config.json +46 -0
- package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
- package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
- package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
- package/rules/common/C076_explicit_function_types/README.md +30 -0
- package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
- package/rules/common/C076_explicit_function_types/config.json +15 -0
- package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
- package/rules/index.js +6 -1
- package/rules/parser/rule-parser.js +13 -2
- package/rules/security/S005_no_origin_auth/README.md +226 -0
- package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
- package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
- package/rules/security/S005_no_origin_auth/config.json +85 -0
- package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
- package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
- package/rules/security/S007_no_plaintext_otp/README.md +198 -0
- package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
- package/rules/security/S007_no_plaintext_otp/config.json +79 -0
- package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
- package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
- package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
- package/rules/security/S009_no_insecure_encryption/README.md +158 -0
- package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
- package/rules/security/S009_no_insecure_encryption/config.json +55 -0
- package/rules/security/S010_no_insecure_encryption/README.md +224 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
- package/rules/security/S010_no_insecure_encryption/config.json +48 -0
- package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
- package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
- package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
- package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
- package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
- package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
- package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
- package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
- package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
- package/rules/security/S055_content_type_validation/README.md +176 -0
- package/rules/security/S055_content_type_validation/analyzer.js +312 -0
- package/rules/security/S055_content_type_validation/config.json +48 -0
- package/rules/utils/rule-helpers.js +140 -1
- package/scripts/consolidate-config.js +116 -0
- package/scripts/prepare-release.sh +1 -1
- package/config/rules/rules-registry.json +0 -765
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
- package/docs/FUTURE_PACKAGES.md +0 -83
- package/docs/HEURISTIC_VS_AI.md +0 -113
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
- package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
- package/docs/RELEASE_GUIDE.md +0 -230
- package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
- package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
|
@@ -1,206 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Symbol-based analyzer for C013 - No Dead Code
|
|
3
|
+
* Uses AST analysis for accurate dead code detection
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
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.
|
|
13
|
+
this.semanticEngine = semanticEngine;
|
|
14
|
+
this.symbolBasedAnalyzer = null;
|
|
15
|
+
this.verbose = false;
|
|
11
16
|
}
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
initialize(options = {}) {
|
|
19
|
+
this.verbose = options.verbose || false;
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
console.
|
|
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
|
-
|
|
37
|
+
async analyze(files, language, options = {}) {
|
|
34
38
|
const violations = [];
|
|
35
|
-
const lines = content.split('\n');
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
79
|
+
|
|
80
|
+
throw new Error('Symbol-based analyzer not available');
|
|
170
81
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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 =
|
|
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;
|