@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.
Files changed (124) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/CONTRIBUTING.md +1654 -66
  3. package/README.md +19 -6
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/engines/engines-enhanced.json +86 -0
  7. package/config/engines/semantic-config.json +114 -0
  8. package/config/eslint-rule-mapping.json +50 -38
  9. package/config/large-project.json +143 -0
  10. package/config/presets/all.json +0 -1
  11. package/config/release.json +70 -0
  12. package/config/rule-analysis-strategies.js +23 -4
  13. package/config/rules/S027-categories.json +122 -0
  14. package/config/rules/enhanced-rules-registry.json +2564 -0
  15. package/config/rules/rules-registry-generated.json +785 -837
  16. package/config/rules/rules-registry.json +13 -1
  17. package/core/adapters/sunlint-rule-adapter.js +25 -30
  18. package/core/analysis-orchestrator.js +42 -2
  19. package/core/categories.js +52 -0
  20. package/core/category-constants.js +39 -0
  21. package/core/cli-action-handler.js +53 -32
  22. package/core/cli-program.js +11 -3
  23. package/core/config-manager.js +111 -0
  24. package/core/config-merger.js +88 -0
  25. package/core/constants/categories.js +168 -0
  26. package/core/constants/defaults.js +165 -0
  27. package/core/constants/engines.js +185 -0
  28. package/core/constants/index.js +30 -0
  29. package/core/constants/rules.js +215 -0
  30. package/core/enhanced-rules-registry.js +3 -3
  31. package/core/file-targeting-service.js +128 -7
  32. package/core/interfaces/rule-plugin.interface.js +207 -0
  33. package/core/plugin-manager.js +448 -0
  34. package/core/rule-selection-service.js +42 -15
  35. package/core/semantic-engine.js +658 -0
  36. package/core/semantic-rule-base.js +433 -0
  37. package/core/unified-rule-registry.js +484 -0
  38. package/docs/COMMAND-EXAMPLES.md +134 -0
  39. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  40. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  41. package/engines/core/base-engine.js +249 -0
  42. package/engines/engine-factory.js +275 -0
  43. package/engines/eslint-engine.js +171 -19
  44. package/engines/heuristic-engine.js +569 -78
  45. package/integrations/eslint/plugin/index.js +26 -28
  46. package/origin-rules/common-en.md +8 -8
  47. package/package.json +10 -6
  48. package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
  49. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  50. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  51. package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
  52. package/rules/common/C033_separate_service_repository/README.md +78 -0
  53. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  54. package/rules/common/C033_separate_service_repository/config.json +50 -0
  55. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  56. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  57. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  58. package/rules/common/C035_error_logging_context/analyzer.js +230 -0
  59. package/rules/common/C035_error_logging_context/config.json +54 -0
  60. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  61. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  62. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  63. package/rules/common/C040_centralized_validation/config.json +46 -0
  64. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  65. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  66. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  67. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  68. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  69. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  70. package/rules/common/C076_explicit_function_types/README.md +30 -0
  71. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  72. package/rules/common/C076_explicit_function_types/config.json +15 -0
  73. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  74. package/rules/index.js +8 -0
  75. package/rules/parser/rule-parser.js +13 -2
  76. package/rules/security/S005_no_origin_auth/README.md +226 -0
  77. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  78. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  79. package/rules/security/S005_no_origin_auth/config.json +85 -0
  80. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  81. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  82. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  83. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  84. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  85. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  86. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  87. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  88. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  89. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  90. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  91. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  92. package/scripts/category-manager.js +150 -0
  93. package/scripts/generate-rules-registry.js +88 -0
  94. package/scripts/migrate-rule-registry.js +157 -0
  95. package/scripts/prepare-release.sh +1 -1
  96. package/scripts/validate-system.js +48 -0
  97. package/.sunlint.json +0 -35
  98. package/config/README.md +0 -88
  99. package/config/engines/eslint-rule-mapping.json +0 -74
  100. package/config/schemas/sunlint-schema.json +0 -0
  101. package/config/testing/test-s005-working.ts +0 -22
  102. package/core/multi-rule-runner.js +0 -0
  103. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  104. package/docs/FUTURE_PACKAGES.md +0 -83
  105. package/docs/HEURISTIC_VS_AI.md +0 -113
  106. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  107. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  108. package/docs/RELEASE_GUIDE.md +0 -230
  109. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  110. package/engines/tree-sitter-parser.js +0 -0
  111. package/engines/universal-ast-engine.js +0 -0
  112. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  113. package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
  114. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
  115. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
  116. package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
  117. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
  118. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
  119. package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
  120. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
  121. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
  122. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
  123. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
  124. 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
- console.log('🎯 C029: Smart Pipeline loaded (3-stage: Regex → AST → Data Flow)');
22
+ if (this.verbose) {
23
+ console.log('[DEBUG] 🎯 C029: Smart Pipeline loaded (3-stage: Regex → AST → Data Flow)');
24
+ }
22
25
  } catch (error) {
23
- console.warn('⚠️ C029: Smart Pipeline failed, using fallback:', error.message);
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
- console.log('🎯 C029: Using Smart Pipeline (3-stage analysis)...');
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
- console.log('🔍 C029: Using fallback regex analysis...');
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
+ }