@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
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C017 Constructor Logic - Semantic Analyzer (Phase 2)
|
|
3
|
+
* Uses ts-morph for precise symbol-based analysis
|
|
4
|
+
* Detects complex logic in constructors with high accuracy
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const SemanticRuleBase = require('../../../core/semantic-rule-base');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
class C017SemanticAnalyzer extends SemanticRuleBase {
|
|
11
|
+
constructor(ruleId = 'C017') {
|
|
12
|
+
super(ruleId);
|
|
13
|
+
this.ruleName = 'C017 - No Complex Logic in Constructors (Semantic)';
|
|
14
|
+
this.description = 'Constructors should only handle dependency injection and simple initialization';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Analyze a single file using ts-morph semantic analysis
|
|
19
|
+
* @param {string} filePath - Path to the file to analyze
|
|
20
|
+
* @param {Object} options - Analysis options
|
|
21
|
+
*/
|
|
22
|
+
async analyzeFile(filePath, options = {}) {
|
|
23
|
+
if (!this.semanticEngine) {
|
|
24
|
+
throw new Error('Semantic engine not initialized');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Get file's absolute path
|
|
29
|
+
const absolutePath = path.resolve(filePath);
|
|
30
|
+
|
|
31
|
+
// Get source file from semantic engine's project
|
|
32
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(absolutePath);
|
|
33
|
+
if (!sourceFile) {
|
|
34
|
+
if (options.verbose) {
|
|
35
|
+
console.log(`⚠️ [C017-Semantic] Could not load source file: ${filePath}`);
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Find all class declarations
|
|
41
|
+
const classes = sourceFile.getClasses();
|
|
42
|
+
|
|
43
|
+
for (const classDecl of classes) {
|
|
44
|
+
const constructors = classDecl.getConstructors();
|
|
45
|
+
|
|
46
|
+
for (const constructor of constructors) {
|
|
47
|
+
await this.analyzeConstructor(constructor, filePath, options);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (options.verbose) {
|
|
53
|
+
console.warn(`⚠️ [C017-Semantic] Error analyzing ${filePath}:`, error.message);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Analyze a constructor node for complex logic
|
|
60
|
+
* @param {ts.ConstructorDeclaration} constructor - Constructor node
|
|
61
|
+
* @param {string} filePath - File path
|
|
62
|
+
* @param {Object} options - Analysis options
|
|
63
|
+
*/
|
|
64
|
+
async analyzeConstructor(constructor, filePath, options) {
|
|
65
|
+
const body = constructor.getBody();
|
|
66
|
+
if (!body) {
|
|
67
|
+
// Constructor without body (interface, abstract, etc.)
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const statements = body.getStatements();
|
|
72
|
+
|
|
73
|
+
for (const statement of statements) {
|
|
74
|
+
const violation = this.analyzeStatement(statement, constructor, filePath);
|
|
75
|
+
if (violation) {
|
|
76
|
+
this.addViolation(violation);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Analyze a statement for complex logic patterns
|
|
83
|
+
* @param {ts.Statement} statement - Statement node
|
|
84
|
+
* @param {ts.ConstructorDeclaration} constructor - Parent constructor
|
|
85
|
+
* @param {string} filePath - File path
|
|
86
|
+
* @returns {Object|null} Violation object or null
|
|
87
|
+
*/
|
|
88
|
+
analyzeStatement(statement, constructor, filePath) {
|
|
89
|
+
const statementKind = statement.getKind();
|
|
90
|
+
const line = statement.getStartLineNumber();
|
|
91
|
+
const column = statement.getStart() - statement.getStartLinePos() + 1;
|
|
92
|
+
|
|
93
|
+
// Check for complex logic patterns
|
|
94
|
+
switch (statementKind) {
|
|
95
|
+
case this.SyntaxKind.IfStatement:
|
|
96
|
+
return this.createViolation(
|
|
97
|
+
filePath, line, column,
|
|
98
|
+
'Conditional logic (if statement) found in constructor',
|
|
99
|
+
'conditional_logic',
|
|
100
|
+
statement.getText()
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
case this.SyntaxKind.ForStatement:
|
|
104
|
+
case this.SyntaxKind.ForInStatement:
|
|
105
|
+
case this.SyntaxKind.ForOfStatement:
|
|
106
|
+
case this.SyntaxKind.WhileStatement:
|
|
107
|
+
case this.SyntaxKind.DoStatement:
|
|
108
|
+
return this.createViolation(
|
|
109
|
+
filePath, line, column,
|
|
110
|
+
'Loop logic found in constructor',
|
|
111
|
+
'loop_logic',
|
|
112
|
+
statement.getText()
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
case this.SyntaxKind.SwitchStatement:
|
|
116
|
+
return this.createViolation(
|
|
117
|
+
filePath, line, column,
|
|
118
|
+
'Switch statement found in constructor',
|
|
119
|
+
'switch_logic',
|
|
120
|
+
statement.getText()
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
case this.SyntaxKind.TryStatement:
|
|
124
|
+
return this.createViolation(
|
|
125
|
+
filePath, line, column,
|
|
126
|
+
'Exception handling (try/catch) found in constructor',
|
|
127
|
+
'exception_handling',
|
|
128
|
+
statement.getText()
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
case this.SyntaxKind.ExpressionStatement:
|
|
132
|
+
return this.analyzeExpressionStatement(statement, filePath);
|
|
133
|
+
|
|
134
|
+
default:
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Analyze expression statements for complex patterns
|
|
141
|
+
* @param {ts.ExpressionStatement} statement - Expression statement
|
|
142
|
+
* @param {string} filePath - File path
|
|
143
|
+
* @returns {Object|null} Violation object or null
|
|
144
|
+
*/
|
|
145
|
+
analyzeExpressionStatement(statement, filePath) {
|
|
146
|
+
const expression = statement.getExpression();
|
|
147
|
+
const line = statement.getStartLineNumber();
|
|
148
|
+
const column = statement.getStart() - statement.getStartLinePos() + 1;
|
|
149
|
+
|
|
150
|
+
// Check for async operations
|
|
151
|
+
if (this.isAsyncOperation(expression)) {
|
|
152
|
+
return this.createViolation(
|
|
153
|
+
filePath, line, column,
|
|
154
|
+
'Asynchronous operation found in constructor',
|
|
155
|
+
'async_operation',
|
|
156
|
+
statement.getText()
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check for complex method calls
|
|
161
|
+
if (this.isComplexMethodCall(expression)) {
|
|
162
|
+
return this.createViolation(
|
|
163
|
+
filePath, line, column,
|
|
164
|
+
'Complex method call found in constructor',
|
|
165
|
+
'complex_method_call',
|
|
166
|
+
statement.getText()
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Allow simple assignments and DI setup
|
|
171
|
+
if (this.isSimpleAssignment(expression) || this.isConfigurationSetup(expression)) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if expression is an async operation
|
|
180
|
+
* @param {ts.Expression} expression - Expression node
|
|
181
|
+
* @returns {boolean} True if async operation
|
|
182
|
+
*/
|
|
183
|
+
isAsyncOperation(expression) {
|
|
184
|
+
const text = expression.getText();
|
|
185
|
+
|
|
186
|
+
// Await expressions
|
|
187
|
+
if (expression.getKind() === this.SyntaxKind.AwaitExpression) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Promise chains
|
|
192
|
+
if (text.includes('.then(') || text.includes('.catch(') || text.includes('.finally(')) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Check if expression is a complex method call
|
|
201
|
+
* @param {ts.Expression} expression - Expression node
|
|
202
|
+
* @returns {boolean} True if complex method call
|
|
203
|
+
*/
|
|
204
|
+
isComplexMethodCall(expression) {
|
|
205
|
+
if (expression.getKind() !== this.SyntaxKind.CallExpression) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const callExpr = expression;
|
|
210
|
+
const methodName = this.getMethodName(callExpr);
|
|
211
|
+
|
|
212
|
+
// Allow certain initialization methods
|
|
213
|
+
const allowedMethods = [
|
|
214
|
+
'makeObservable', 'makeAutoObservable', // MobX
|
|
215
|
+
'bind', 'bindAll', // Method binding
|
|
216
|
+
'Object.assign', 'Object.create', // Object utilities
|
|
217
|
+
'Array.from', 'Array.of', // Array utilities
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
if (allowedMethods.some(method => methodName.includes(method))) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check for chained calls (more than 2 levels = complex)
|
|
225
|
+
const chainLength = this.getChainLength(callExpr);
|
|
226
|
+
return chainLength > 2;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if expression is a simple assignment
|
|
231
|
+
* @param {ts.Expression} expression - Expression node
|
|
232
|
+
* @returns {boolean} True if simple assignment
|
|
233
|
+
*/
|
|
234
|
+
isSimpleAssignment(expression) {
|
|
235
|
+
if (expression.getKind() !== this.SyntaxKind.BinaryExpression) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const binaryExpr = expression;
|
|
240
|
+
const operator = binaryExpr.getOperatorToken();
|
|
241
|
+
|
|
242
|
+
return operator.getKind() === this.SyntaxKind.EqualsToken;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Check if expression is configuration setup
|
|
247
|
+
* @param {ts.Expression} expression - Expression node
|
|
248
|
+
* @returns {boolean} True if configuration setup
|
|
249
|
+
*/
|
|
250
|
+
isConfigurationSetup(expression) {
|
|
251
|
+
const text = expression.getText();
|
|
252
|
+
|
|
253
|
+
// Configuration patterns
|
|
254
|
+
const configPatterns = [
|
|
255
|
+
/new\s+\w+Client\s*\(/, // AWS clients, HTTP clients
|
|
256
|
+
/new\s+\w+\s*\(\s*\{/, // Configuration objects
|
|
257
|
+
/\.getInstance\s*\(/, // Singleton patterns
|
|
258
|
+
/\.create\s*\(/, // Factory patterns
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
return configPatterns.some(pattern => pattern.test(text));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get method name from call expression
|
|
266
|
+
* @param {ts.CallExpression} callExpr - Call expression
|
|
267
|
+
* @returns {string} Method name
|
|
268
|
+
*/
|
|
269
|
+
getMethodName(callExpr) {
|
|
270
|
+
const expression = callExpr.getExpression();
|
|
271
|
+
|
|
272
|
+
if (expression.getKind() === this.SyntaxKind.PropertyAccessExpression) {
|
|
273
|
+
return expression.getName();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (expression.getKind() === this.SyntaxKind.Identifier) {
|
|
277
|
+
return expression.getText();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return expression.getText();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get chain length of method calls
|
|
285
|
+
* @param {ts.CallExpression} callExpr - Call expression
|
|
286
|
+
* @returns {number} Chain length
|
|
287
|
+
*/
|
|
288
|
+
getChainLength(callExpr) {
|
|
289
|
+
let current = callExpr.getExpression();
|
|
290
|
+
let length = 1;
|
|
291
|
+
|
|
292
|
+
while (current.getKind() === this.SyntaxKind.PropertyAccessExpression) {
|
|
293
|
+
const propAccess = current;
|
|
294
|
+
current = propAccess.getExpression();
|
|
295
|
+
length++;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return length;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Create a violation object
|
|
303
|
+
* @param {string} filePath - File path
|
|
304
|
+
* @param {number} line - Line number
|
|
305
|
+
* @param {number} column - Column number
|
|
306
|
+
* @param {string} message - Violation message
|
|
307
|
+
* @param {string} type - Violation type
|
|
308
|
+
* @param {string} code - Code snippet
|
|
309
|
+
* @returns {Object} Violation object
|
|
310
|
+
*/
|
|
311
|
+
createViolation(filePath, line, column, message, type, code) {
|
|
312
|
+
return {
|
|
313
|
+
ruleId: this.ruleId,
|
|
314
|
+
file: filePath,
|
|
315
|
+
line: line,
|
|
316
|
+
column: column,
|
|
317
|
+
message: `Constructor contains complex logic: ${message}. Move to initialization methods`,
|
|
318
|
+
severity: 'warning',
|
|
319
|
+
code: code.trim(),
|
|
320
|
+
type: type,
|
|
321
|
+
confidence: 95, // High confidence with semantic analysis
|
|
322
|
+
analysisMethod: 'semantic',
|
|
323
|
+
suggestion: 'Move complex logic to separate initialization methods or lifecycle hooks'
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Get SyntaxKind enum from ts-morph
|
|
329
|
+
* @returns {Object} SyntaxKind enum
|
|
330
|
+
*/
|
|
331
|
+
get SyntaxKind() {
|
|
332
|
+
if (!this.semanticEngine) {
|
|
333
|
+
throw new Error('Semantic engine not initialized');
|
|
334
|
+
}
|
|
335
|
+
return this.semanticEngine.project.getTypeChecker().compilerObject.SyntaxKind ||
|
|
336
|
+
require('typescript').SyntaxKind;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
module.exports = C017SemanticAnalyzer;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C018 Main Analyzer - Do not throw generic errors
|
|
3
|
+
* Primary: Always provide detailed messages and context.
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const C018SymbolBasedAnalyzer = require('./symbol-based-analyzer');
|
|
8
|
+
const C018RegexBasedAnalyzer = require('./regex-based-analyzer');
|
|
9
|
+
|
|
10
|
+
class C018Analyzer {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
13
|
+
console.log(`🔧 [C018] Constructor called with options:`, !!options);
|
|
14
|
+
console.log(`🔧 [C018] Options type:`, typeof options, Object.keys(options || {}));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.ruleId = 'C018';
|
|
18
|
+
this.ruleName = 'Do not throw generic errors';
|
|
19
|
+
this.description = 'Do not throw generic errors; always provide detailed messages and context.';
|
|
20
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
21
|
+
this.verbose = options.verbose || false;
|
|
22
|
+
|
|
23
|
+
// Configuration
|
|
24
|
+
this.config = {
|
|
25
|
+
useSymbolBased: true, // Primary approach
|
|
26
|
+
fallbackToRegex: true, // Only when symbol fails completely
|
|
27
|
+
symbolBasedOnly: false // Can be set to true for pure mode
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Initialize both analyzers
|
|
31
|
+
try {
|
|
32
|
+
this.symbolAnalyzer = new C018SymbolBasedAnalyzer(this.semanticEngine);
|
|
33
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
34
|
+
console.log(`🔧 [C018] Symbol analyzer created successfully`);
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error(`🔧 [C018] Error creating symbol analyzer:`, error);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
this.regexAnalyzer = new C018RegexBasedAnalyzer(this.semanticEngine);
|
|
42
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
43
|
+
console.log(`🔧 [C018] Regex analyzer created successfully`);
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`🔧 [C018] Error creating regex analyzer:`, error);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
50
|
+
console.log(`🔧 [C018] Constructor completed`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Initialize with semantic engine
|
|
56
|
+
*/
|
|
57
|
+
async initialize(semanticEngine = null) {
|
|
58
|
+
if (semanticEngine) {
|
|
59
|
+
this.semanticEngine = semanticEngine;
|
|
60
|
+
}
|
|
61
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
62
|
+
|
|
63
|
+
// Initialize both analyzers
|
|
64
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
65
|
+
await this.regexAnalyzer.initialize(semanticEngine);
|
|
66
|
+
|
|
67
|
+
// Ensure verbose flag is propagated
|
|
68
|
+
this.regexAnalyzer.verbose = this.verbose;
|
|
69
|
+
this.symbolAnalyzer.verbose = this.verbose;
|
|
70
|
+
|
|
71
|
+
if (this.verbose) {
|
|
72
|
+
console.log(`🔧 [C018 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async analyze(files, language, options = {}) {
|
|
77
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
78
|
+
console.log(`🔧 [C018] analyze() method called with ${files.length} files, language: ${language}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const violations = [];
|
|
82
|
+
|
|
83
|
+
for (const filePath of files) {
|
|
84
|
+
try {
|
|
85
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
86
|
+
console.log(`🔧 [C018] Processing file: ${filePath}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
90
|
+
violations.push(...fileViolations);
|
|
91
|
+
|
|
92
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
93
|
+
console.log(`🔧 [C018] File ${filePath}: Found ${fileViolations.length} violations`);
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.warn(`❌ [C018] Analysis failed for ${filePath}:`, error.message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
101
|
+
console.log(`🔧 [C018] Total violations found: ${violations.length}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return violations;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async analyzeFile(filePath, options = {}) {
|
|
108
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
109
|
+
console.log(`🔧 [C018] analyzeFile() called for: ${filePath}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
113
|
+
if (this.config.useSymbolBased &&
|
|
114
|
+
this.semanticEngine?.project &&
|
|
115
|
+
this.semanticEngine?.initialized) {
|
|
116
|
+
try {
|
|
117
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
118
|
+
console.log(`🔧 [C018] Trying symbol-based analysis...`);
|
|
119
|
+
}
|
|
120
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
121
|
+
if (sourceFile) {
|
|
122
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
123
|
+
console.log(`🔧 [C018] Source file found, analyzing with symbol-based...`);
|
|
124
|
+
}
|
|
125
|
+
const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
|
|
126
|
+
|
|
127
|
+
// Mark violations with analysis strategy
|
|
128
|
+
violations.forEach(v => v.analysisStrategy = 'symbol-based');
|
|
129
|
+
|
|
130
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
131
|
+
console.log(`✅ [C018] Symbol-based analysis: ${violations.length} violations`);
|
|
132
|
+
}
|
|
133
|
+
return violations; // Return even if 0 violations - symbol analysis completed successfully
|
|
134
|
+
} else {
|
|
135
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
136
|
+
console.log(`⚠️ [C018] Source file not found in project`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.warn(`⚠️ [C018] Symbol analysis failed: ${error.message}`);
|
|
141
|
+
// Continue to fallback
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
145
|
+
console.log(`🔄 [C018] Symbol analysis conditions check:`);
|
|
146
|
+
console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
|
|
147
|
+
console.log(` - semanticEngine: ${!!this.semanticEngine}`);
|
|
148
|
+
console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
|
|
149
|
+
console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
|
|
150
|
+
console.log(`🔄 [C018] Symbol analysis unavailable, using regex fallback`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 2. Fallback to regex-based analysis
|
|
155
|
+
if (this.config.fallbackToRegex) {
|
|
156
|
+
try {
|
|
157
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
158
|
+
console.log(`🔧 [C018] Trying regex-based analysis...`);
|
|
159
|
+
}
|
|
160
|
+
const violations = await this.regexAnalyzer.analyzeFileBasic(filePath, options);
|
|
161
|
+
|
|
162
|
+
// Mark violations with analysis strategy
|
|
163
|
+
violations.forEach(v => v.analysisStrategy = 'regex-fallback');
|
|
164
|
+
|
|
165
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
166
|
+
console.log(`🔄 [C018] Regex-based analysis: ${violations.length} violations`);
|
|
167
|
+
}
|
|
168
|
+
return violations;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error(`❌ [C018] Regex analysis failed: ${error.message}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (options?.verbose) {
|
|
175
|
+
console.log(`🔧 [C018] No analysis methods succeeded, returning empty`);
|
|
176
|
+
}
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
181
|
+
console.log(`🔧 [C018] analyzeFileBasic() called for: ${filePath}`);
|
|
182
|
+
console.log(`🔧 [C018] semanticEngine exists: ${!!this.semanticEngine}`);
|
|
183
|
+
console.log(`🔧 [C018] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
|
|
184
|
+
console.log(`🔧 [C018] regexAnalyzer exists: ${!!this.regexAnalyzer}`);
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
// Try symbol-based analysis first
|
|
188
|
+
if (this.semanticEngine?.isSymbolEngineReady?.() &&
|
|
189
|
+
this.semanticEngine.project) {
|
|
190
|
+
|
|
191
|
+
if (this.verbose) {
|
|
192
|
+
console.log(`🔍 [C018] Using symbol-based analysis for ${filePath}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
|
|
196
|
+
return violations;
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (this.verbose) {
|
|
200
|
+
console.warn(`⚠️ [C018] Symbol analysis failed: ${error.message}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Fallback to regex-based analysis
|
|
205
|
+
if (this.verbose) {
|
|
206
|
+
console.log(`🔄 [C018] Using regex-based analysis (fallback) for ${filePath}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log(`🔧 [C018] About to call regexAnalyzer.analyzeFileBasic()`);
|
|
210
|
+
try {
|
|
211
|
+
const result = await this.regexAnalyzer.analyzeFileBasic(filePath, options);
|
|
212
|
+
console.log(`🔧 [C018] Regex analyzer returned: ${result.length} violations`);
|
|
213
|
+
return result;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error(`🔧 [C018] Error in regex analyzer:`, error);
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Methods for compatibility with different engine invocation patterns
|
|
222
|
+
*/
|
|
223
|
+
async analyzeFileWithSymbols(filePath, options = {}) {
|
|
224
|
+
return this.analyzeFile(filePath, options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async analyzeWithSemantics(filePath, options = {}) {
|
|
228
|
+
return this.analyzeFile(filePath, options);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = C018Analyzer;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "C018",
|
|
3
|
+
"name": "C018_do_not_throw_generic_errors",
|
|
4
|
+
"category": "architecture",
|
|
5
|
+
"description": "C018 - Always provide detailed messages and context.",
|
|
6
|
+
"severity": "warning",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"semantic": {
|
|
9
|
+
"enabled": true,
|
|
10
|
+
"priority": "high",
|
|
11
|
+
"fallback": "heuristic"
|
|
12
|
+
},
|
|
13
|
+
"patterns": {
|
|
14
|
+
"include": [
|
|
15
|
+
"**/*.js",
|
|
16
|
+
"**/*.ts",
|
|
17
|
+
"**/*.jsx",
|
|
18
|
+
"**/*.tsx"
|
|
19
|
+
],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"**/*.test.*",
|
|
22
|
+
"**/*.spec.*",
|
|
23
|
+
"**/*.mock.*",
|
|
24
|
+
"**/test/**",
|
|
25
|
+
"**/tests/**",
|
|
26
|
+
"**/spec/**"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"options": {
|
|
30
|
+
"strictMode": false,
|
|
31
|
+
"allowedDbMethods": [],
|
|
32
|
+
"repositoryPatterns": [
|
|
33
|
+
"*Repository*",
|
|
34
|
+
"*Repo*",
|
|
35
|
+
"*DAO*",
|
|
36
|
+
"*Store*"
|
|
37
|
+
],
|
|
38
|
+
"servicePatterns": [
|
|
39
|
+
"*Service*",
|
|
40
|
+
"*UseCase*",
|
|
41
|
+
"*Handler*",
|
|
42
|
+
"*Manager*"
|
|
43
|
+
],
|
|
44
|
+
"complexityThreshold": {
|
|
45
|
+
"methodLength": 200,
|
|
46
|
+
"cyclomaticComplexity": 5,
|
|
47
|
+
"nestedDepth": 3
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|