@sun-asterisk/sunlint 1.2.2 → 1.3.1
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 +107 -1
- package/CONTRIBUTING.md +1654 -66
- package/README.md +19 -6
- package/config/ci-cd.json +54 -0
- package/config/development.json +56 -0
- package/config/engines/engines-enhanced.json +86 -0
- package/config/engines/semantic-config.json +114 -0
- package/config/eslint-rule-mapping.json +50 -38
- 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 +23 -4
- package/config/rules/S027-categories.json +122 -0
- package/config/rules/enhanced-rules-registry.json +2564 -0
- package/config/rules/rules-registry-generated.json +785 -837
- package/config/rules/rules-registry.json +13 -1
- package/core/adapters/sunlint-rule-adapter.js +25 -30
- package/core/analysis-orchestrator.js +42 -2
- package/core/categories.js +52 -0
- package/core/category-constants.js +39 -0
- package/core/cli-action-handler.js +53 -32
- package/core/cli-program.js +11 -3
- package/core/config-manager.js +111 -0
- package/core/config-merger.js +88 -0
- package/core/constants/categories.js +168 -0
- package/core/constants/defaults.js +165 -0
- package/core/constants/engines.js +185 -0
- package/core/constants/index.js +30 -0
- package/core/constants/rules.js +215 -0
- package/core/enhanced-rules-registry.js +3 -3
- package/core/file-targeting-service.js +128 -7
- package/core/interfaces/rule-plugin.interface.js +207 -0
- package/core/plugin-manager.js +448 -0
- package/core/rule-selection-service.js +42 -15
- package/core/semantic-engine.js +658 -0
- package/core/semantic-rule-base.js +433 -0
- package/core/unified-rule-registry.js +484 -0
- package/docs/COMMAND-EXAMPLES.md +134 -0
- package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
- package/docs/LARGE-PROJECT-GUIDE.md +324 -0
- package/engines/core/base-engine.js +249 -0
- package/engines/engine-factory.js +275 -0
- package/engines/eslint-engine.js +171 -19
- package/engines/heuristic-engine.js +569 -78
- package/integrations/eslint/plugin/index.js +26 -28
- package/origin-rules/common-en.md +8 -8
- package/package.json +10 -6
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
- 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 +230 -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/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
- package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
- package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -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 +8 -0
- 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/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/scripts/category-manager.js +150 -0
- package/scripts/generate-rules-registry.js +88 -0
- package/scripts/migrate-rule-registry.js +157 -0
- package/scripts/prepare-release.sh +1 -1
- package/scripts/validate-system.js +48 -0
- package/.sunlint.json +0 -35
- package/config/README.md +0 -88
- package/config/engines/eslint-rule-mapping.json +0 -74
- package/config/schemas/sunlint-schema.json +0 -0
- package/config/testing/test-s005-working.ts +0 -22
- package/core/multi-rule-runner.js +0 -0
- 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/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
- package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
- package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
- package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
- package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
- package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
- package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
- package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
- package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
- package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
- package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
- package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
- package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
|
@@ -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;
|
|
@@ -8,30 +8,42 @@ const fs = require('fs');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
|
|
10
10
|
class C029Analyzer {
|
|
11
|
-
constructor() {
|
|
11
|
+
constructor(options = {}) {
|
|
12
12
|
this.ruleId = 'C029';
|
|
13
13
|
this.ruleName = 'Enhanced Catch Block Error Logging';
|
|
14
14
|
this.description = 'Mọi catch block phải log nguyên nhân lỗi đầy đủ và bảo toàn context (Smart Pipeline 3-stage analysis)';
|
|
15
|
+
this.verbose = options.verbose || false;
|
|
15
16
|
|
|
16
17
|
// Load Smart Pipeline as primary analyzer
|
|
17
18
|
this.smartPipeline = null;
|
|
18
19
|
|
|
19
20
|
try {
|
|
20
21
|
this.smartPipeline = require('./analyzer-smart-pipeline.js');
|
|
21
|
-
|
|
22
|
+
if (this.verbose) {
|
|
23
|
+
console.log('[DEBUG] 🎯 C029: Smart Pipeline loaded (3-stage: Regex → AST → Data Flow)');
|
|
24
|
+
}
|
|
22
25
|
} catch (error) {
|
|
23
|
-
|
|
26
|
+
if (this.verbose) {
|
|
27
|
+
console.warn('[DEBUG] ⚠️ C029: Smart Pipeline failed, using fallback:', error.message);
|
|
28
|
+
}
|
|
24
29
|
this.smartPipeline = null;
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
async analyze(files, language, options = {}) {
|
|
34
|
+
// Store verbose option for this analysis
|
|
35
|
+
this.verbose = options.verbose || this.verbose || false;
|
|
36
|
+
|
|
29
37
|
// Use Smart Pipeline as primary choice
|
|
30
38
|
if (this.smartPipeline) {
|
|
31
|
-
|
|
39
|
+
if (this.verbose) {
|
|
40
|
+
console.log('[DEBUG] 🎯 C029: Using Smart Pipeline (3-stage analysis)...');
|
|
41
|
+
}
|
|
32
42
|
return await this.smartPipeline.analyze(files, language, options);
|
|
33
43
|
} else {
|
|
34
|
-
|
|
44
|
+
if (this.verbose) {
|
|
45
|
+
console.log('[DEBUG] 🔍 C029: Using fallback regex analysis...');
|
|
46
|
+
}
|
|
35
47
|
return await this.analyzeWithRegex(files, language, options);
|
|
36
48
|
}
|
|
37
49
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# C033: Separate Service and Repository Logic
|
|
2
|
+
|
|
3
|
+
## Rule Description
|
|
4
|
+
|
|
5
|
+
Enforces proper separation between Service and Repository layers in your application architecture:
|
|
6
|
+
|
|
7
|
+
- **Services** should contain business logic and use repositories for data access
|
|
8
|
+
- **Repositories** should contain only data access logic, no business rules
|
|
9
|
+
|
|
10
|
+
## Architecture
|
|
11
|
+
|
|
12
|
+
This rule uses a **hybrid analysis approach**:
|
|
13
|
+
|
|
14
|
+
1. **Primary: Symbol-based Analysis** (using ts-morph)
|
|
15
|
+
- AST parsing and symbol resolution
|
|
16
|
+
- Accurate detection of database operations
|
|
17
|
+
- Excludes queue/job operations automatically
|
|
18
|
+
|
|
19
|
+
2. **Fallback: Regex-based Analysis**
|
|
20
|
+
- Pattern matching for when symbol analysis fails
|
|
21
|
+
- Handles edge cases and complex code structures
|
|
22
|
+
|
|
23
|
+
## Files Structure
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
C033_separate_service_repository/
|
|
27
|
+
├── analyzer.js # Main hybrid orchestrator
|
|
28
|
+
├── symbol-based-analyzer.js # Primary AST-based analysis
|
|
29
|
+
├── regex-based-analyzer.js # Fallback pattern matching
|
|
30
|
+
├── config.json # Rule configuration
|
|
31
|
+
└── README.md # This documentation
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## What this rule detects
|
|
35
|
+
|
|
36
|
+
### Violations in Service files:
|
|
37
|
+
- Direct database calls (`repository.createQueryBuilder()`, `dataSource.createQueryBuilder()`)
|
|
38
|
+
- Direct ORM operations (`entity.save()`, `entity.find()`)
|
|
39
|
+
- SQL queries embedded in service methods
|
|
40
|
+
- **Excludes**: Queue/job operations (`job.remove()`, `job.isFailed()`, etc.)
|
|
41
|
+
|
|
42
|
+
### Violations in Repository files:
|
|
43
|
+
- Complex business logic (filtering, calculations, validations)
|
|
44
|
+
- Business rules and workflows
|
|
45
|
+
- Complex conditional logic for data processing
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
|
|
49
|
+
See test cases in the standard test fixtures location:
|
|
50
|
+
|
|
51
|
+
- **Violations**: `examples/rule-test-fixtures/rules/C033_separate_service_repository/violations/test-cases.js`
|
|
52
|
+
- **Clean code**: `examples/rule-test-fixtures/rules/C033_separate_service_repository/clean/good-examples.js`
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Test violations
|
|
58
|
+
node cli.js --rule=C033 --input=examples/rule-test-fixtures/rules/C033_separate_service_repository/violations --engine=heuristic
|
|
59
|
+
|
|
60
|
+
# Test clean code
|
|
61
|
+
node cli.js --rule=C033 --input=examples/rule-test-fixtures/rules/C033_separate_service_repository/clean --engine=heuristic
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Technical Implementation
|
|
65
|
+
|
|
66
|
+
- **Primary Analysis**: Semantic analysis using ts-morph for AST traversal
|
|
67
|
+
- **Fallback**: Regex pattern matching for environments without ts-morph
|
|
68
|
+
- **Engine**: Heuristic engine (registered in enhanced-rules-registry.js)
|
|
69
|
+
- **File Detection**: Classifies files as Service/Repository based on naming patterns
|
|
70
|
+
|
|
71
|
+
## Philosophy
|
|
72
|
+
|
|
73
|
+
This rule enforces the Repository Pattern and Domain-Driven Design principles:
|
|
74
|
+
|
|
75
|
+
1. **Separation of Concerns**: Business logic in Services, data access in Repositories
|
|
76
|
+
2. **Testability**: Each layer can be tested independently
|
|
77
|
+
3. **Maintainability**: Changes to business rules don't affect data access code
|
|
78
|
+
4. **Flexibility**: Data storage can be changed without affecting business logic
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C033 Main Analyzer - Symbol-based with minimal regex fallback
|
|
3
|
+
* Primary: Symbol-based analysis (95% cases)
|
|
4
|
+
* Fallback: Regex-based only when symbol analysis completely fails
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const C033SymbolBasedAnalyzer = require('./symbol-based-analyzer');
|
|
8
|
+
const C033RegexBasedAnalyzer = require('./regex-based-analyzer');
|
|
9
|
+
|
|
10
|
+
class C033Analyzer {
|
|
11
|
+
constructor(semanticEngine = null) {
|
|
12
|
+
this.ruleId = 'C033';
|
|
13
|
+
this.ruleName = 'Separate Service and Repository Logic';
|
|
14
|
+
this.description = 'Tách logic xử lý và truy vấn dữ liệu trong service layer - Repository chỉ chứa CRUD, Service chứa business logic';
|
|
15
|
+
this.semanticEngine = semanticEngine;
|
|
16
|
+
this.verbose = false;
|
|
17
|
+
|
|
18
|
+
// Initialize analyzers
|
|
19
|
+
this.symbolBasedAnalyzer = new C033SymbolBasedAnalyzer(semanticEngine);
|
|
20
|
+
this.regexBasedAnalyzer = new C033RegexBasedAnalyzer(semanticEngine);
|
|
21
|
+
|
|
22
|
+
// Configuration
|
|
23
|
+
this.config = {
|
|
24
|
+
useSymbolBased: true, // Primary approach
|
|
25
|
+
fallbackToRegex: true, // Only when symbol fails completely
|
|
26
|
+
symbolBasedOnly: false // Can be set to true for pure mode
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize with semantic engine
|
|
32
|
+
*/
|
|
33
|
+
async initialize(semanticEngine = null) {
|
|
34
|
+
if (semanticEngine) {
|
|
35
|
+
this.semanticEngine = semanticEngine;
|
|
36
|
+
}
|
|
37
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
38
|
+
|
|
39
|
+
// Initialize both analyzers
|
|
40
|
+
await this.symbolBasedAnalyzer.initialize(semanticEngine);
|
|
41
|
+
await this.regexBasedAnalyzer.initialize(semanticEngine);
|
|
42
|
+
|
|
43
|
+
if (this.verbose) {
|
|
44
|
+
console.log(`[DEBUG] 🔧 C033: Analyzer initialized - Symbol-based: ✅, Regex fallback: ${this.config.fallbackToRegex ? '✅' : '❌'}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async analyze(files, language, options = {}) {
|
|
49
|
+
const violations = [];
|
|
50
|
+
let symbolCount = 0;
|
|
51
|
+
let regexCount = 0;
|
|
52
|
+
|
|
53
|
+
for (const filePath of files) {
|
|
54
|
+
try {
|
|
55
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
56
|
+
violations.push(...fileViolations);
|
|
57
|
+
|
|
58
|
+
// Count strategy usage
|
|
59
|
+
const strategy = fileViolations[0]?.analysisStrategy;
|
|
60
|
+
if (strategy === 'symbol-based') symbolCount++;
|
|
61
|
+
else if (strategy === 'regex-fallback') regexCount++;
|
|
62
|
+
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (this.verbose) {
|
|
65
|
+
console.warn(`[C033] Analysis failed for ${filePath}:`, error.message);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Summary of strategy usage
|
|
71
|
+
if (this.verbose && (symbolCount > 0 || regexCount > 0)) {
|
|
72
|
+
console.log(`📊 [C033-SUMMARY] Analysis strategy usage:`);
|
|
73
|
+
console.log(` 🧠 Symbol-based: ${symbolCount} files`);
|
|
74
|
+
console.log(` 🔄 Regex-fallback: ${regexCount} files`);
|
|
75
|
+
console.log(` 📈 Coverage: ${symbolCount}/${symbolCount + regexCount} files used primary strategy`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return violations;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async analyzeFile(filePath, options = {}) {
|
|
82
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
83
|
+
if (this.config.useSymbolBased && this.semanticEngine?.project) {
|
|
84
|
+
try {
|
|
85
|
+
const sourceFile = this.semanticEngine.project.getSourceFileByFilePath(filePath);
|
|
86
|
+
if (sourceFile) {
|
|
87
|
+
const violations = await this.symbolBasedAnalyzer.analyzeFileWithSymbols(filePath, options);
|
|
88
|
+
|
|
89
|
+
if (this.verbose) {
|
|
90
|
+
console.log(`🧠 [C033-SYMBOL] ${filePath}: Found ${violations.length} violations`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return violations.map(v => ({ ...v, analysisStrategy: 'symbol-based' }));
|
|
94
|
+
} else {
|
|
95
|
+
if (this.verbose) {
|
|
96
|
+
console.log(`⚠️ [C033-SYMBOL] ${filePath}: Source file not found in ts-morph project, falling back to regex`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (this.verbose) {
|
|
101
|
+
console.warn(`❌ [C033-SYMBOL] ${filePath}: Symbol analysis failed, falling back to regex:`, error.message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
if (this.verbose) {
|
|
106
|
+
const reason = !this.config.useSymbolBased ? 'Symbol-based disabled' : 'No semantic engine';
|
|
107
|
+
console.log(`⚠️ [C033] ${filePath}: Skipping symbol analysis (${reason}), using regex`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 2. Fallback to Regex-based analysis (only if symbol fails or unavailable)
|
|
112
|
+
if (this.config.fallbackToRegex && !this.config.symbolBasedOnly) {
|
|
113
|
+
try {
|
|
114
|
+
const violations = await this.regexBasedAnalyzer.analyzeFileBasic(filePath, options);
|
|
115
|
+
|
|
116
|
+
if (this.verbose) {
|
|
117
|
+
console.log(`🔄 [C033-REGEX] ${filePath}: Found ${violations.length} violations`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return violations.map(v => ({ ...v, analysisStrategy: 'regex-fallback' }));
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (this.verbose) {
|
|
123
|
+
console.warn(`❌ [C033-REGEX] ${filePath}: Regex fallback also failed:`, error.message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Legacy compatibility methods
|
|
132
|
+
async analyzeWithSemantics(filePath, options = {}) {
|
|
133
|
+
return await this.analyzeFile(filePath, options);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
137
|
+
// Force regex-based for legacy compatibility
|
|
138
|
+
const violations = await this.regexBasedAnalyzer.analyzeFileBasic(filePath, options);
|
|
139
|
+
return violations.map(v => ({ ...v, analysisStrategy: 'regex-legacy' }));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Configuration methods
|
|
143
|
+
enableSymbolBasedOnly() {
|
|
144
|
+
this.config.symbolBasedOnly = true;
|
|
145
|
+
this.config.fallbackToRegex = false;
|
|
146
|
+
if (this.verbose) {
|
|
147
|
+
console.log(`[C033] Switched to symbol-based only mode`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
enableHybridMode() {
|
|
152
|
+
this.config.symbolBasedOnly = false;
|
|
153
|
+
this.config.fallbackToRegex = true;
|
|
154
|
+
if (this.verbose) {
|
|
155
|
+
console.log(`[C033] Switched to hybrid mode (symbol-based + regex fallback)`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = C033Analyzer;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "C033_separate_service_repository",
|
|
3
|
+
"name": "C033_separate_service_repository",
|
|
4
|
+
"category": "architecture",
|
|
5
|
+
"description": "C033 - Tách logic xử lý và truy vấn dữ liệu trong service layer",
|
|
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
|
+
}
|