@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,368 @@
1
+ /**
2
+ * Symbol-based analyzer for C033 - Advanced semantic analysis
3
+ * Purpose: Use AST + Data Flow to detect true database access violations
4
+ */
5
+
6
+ class C033SymbolBasedAnalyzer {
7
+ constructor(semanticEngine = null) {
8
+ this.ruleId = 'C033';
9
+ this.ruleName = 'Separate Service and Repository Logic (Symbol-Based)';
10
+ this.semanticEngine = semanticEngine;
11
+ this.verbose = false;
12
+
13
+ // Known database/ORM symbols and interfaces
14
+ this.databaseSymbols = [
15
+ 'Repository', 'EntityManager', 'QueryBuilder', 'Connection',
16
+ 'PrismaClient', 'Model', 'Collection', 'Table'
17
+ ];
18
+
19
+ // ORM framework patterns
20
+ this.ormPatterns = {
21
+ typeorm: ['Repository', 'EntityManager', 'QueryRunner', 'QueryBuilder'],
22
+ prisma: ['PrismaClient', 'PrismaService'],
23
+ mongoose: ['Model', 'Document', 'Schema'],
24
+ sequelize: ['Model', 'Sequelize', 'QueryInterface'],
25
+ knex: ['Knex', 'QueryBuilder']
26
+ };
27
+ }
28
+
29
+ async initialize(semanticEngine = null) {
30
+ if (semanticEngine) {
31
+ this.semanticEngine = semanticEngine;
32
+ }
33
+ this.verbose = semanticEngine?.verbose || false;
34
+
35
+ if (this.verbose) {
36
+ console.log(`[DEBUG] 🔧 C033 Symbol-Based: Analyzer initialized`);
37
+ }
38
+ }
39
+
40
+ async analyze(files, language, options = {}) {
41
+ const violations = [];
42
+
43
+ if (!this.semanticEngine?.project) {
44
+ if (this.verbose) {
45
+ console.warn('[C033 Symbol-Based] No semantic engine available, skipping analysis');
46
+ }
47
+ return violations;
48
+ }
49
+
50
+ for (const filePath of files) {
51
+ try {
52
+ const fileViolations = await this.analyzeFileWithSymbols(filePath, options);
53
+ violations.push(...fileViolations);
54
+ } catch (error) {
55
+ if (this.verbose) {
56
+ console.warn(`[C033 Symbol-Based] Analysis failed for ${filePath}:`, error.message);
57
+ }
58
+ }
59
+ }
60
+
61
+ return violations;
62
+ }
63
+
64
+ async analyzeFileWithSymbols(filePath, options = {}) {
65
+ const violations = [];
66
+ const sourceFile = this.semanticEngine.project.getSourceFileByFilePath(filePath);
67
+
68
+ if (!sourceFile) {
69
+ return violations;
70
+ }
71
+
72
+ // 1. Classify file type using semantic analysis
73
+ const fileType = this.classifyFileSemanticType(sourceFile, filePath);
74
+
75
+ if (fileType !== 'service') {
76
+ return violations; // Only analyze Service files
77
+ }
78
+
79
+ // 2. Analyze call expressions in Service classes
80
+ const classes = sourceFile.getClasses();
81
+
82
+ for (const cls of classes) {
83
+ const className = cls.getName() || 'UnknownClass';
84
+
85
+ // Skip if not a Service class
86
+ if (!this.isServiceClass(cls)) {
87
+ continue;
88
+ }
89
+
90
+ const methods = cls.getMethods();
91
+
92
+ for (const method of methods) {
93
+ const methodViolations = this.analyzeMethodForDatabaseCalls(
94
+ method, sourceFile, className, filePath
95
+ );
96
+ violations.push(...methodViolations);
97
+ }
98
+ }
99
+
100
+ return violations;
101
+ }
102
+
103
+ /**
104
+ * Analyze method for direct database calls using symbol resolution
105
+ */
106
+ analyzeMethodForDatabaseCalls(method, sourceFile, className, filePath) {
107
+ const violations = [];
108
+ const methodName = method.getName();
109
+
110
+ // Get all call expressions in the method
111
+ const callExpressions = method.getDescendantsOfKind(this.getKind('CallExpression'));
112
+
113
+ for (const callExpr of callExpressions) {
114
+ const violation = this.analyzeCallExpression(callExpr, sourceFile, className, methodName, filePath);
115
+ if (violation) {
116
+ violations.push(violation);
117
+ }
118
+ }
119
+
120
+ return violations;
121
+ }
122
+
123
+ /**
124
+ * Analyze individual call expression using symbol resolution
125
+ */
126
+ analyzeCallExpression(callExpr, sourceFile, className, methodName, filePath) {
127
+ const expression = callExpr.getExpression();
128
+
129
+ // Handle property access (obj.method())
130
+ if (expression.getKind() === this.getKind('PropertyAccessExpression')) {
131
+ const propertyAccess = expression;
132
+ const object = propertyAccess.getExpression();
133
+ const property = propertyAccess.getName();
134
+
135
+ // Get the symbol/type of the object being called
136
+ const objectSymbol = this.getObjectSymbol(object);
137
+
138
+ if (this.isDatabaseOperation(objectSymbol, property)) {
139
+ // Check if it's going through repository (acceptable)
140
+ if (this.isRepositoryAccess(objectSymbol)) {
141
+ return null; // OK: Service -> Repository -> Database
142
+ }
143
+
144
+ // Direct database access in Service (violation)
145
+ const lineNumber = callExpr.getStartLineNumber();
146
+ const columnNumber = callExpr.getStart() - sourceFile.getLineStartPos(lineNumber - 1) + 1;
147
+
148
+ return {
149
+ ruleId: this.ruleId,
150
+ severity: 'warning',
151
+ message: `Service should not contain direct database calls`,
152
+ source: this.ruleId,
153
+ file: filePath,
154
+ line: lineNumber,
155
+ column: columnNumber,
156
+ description: `[SYMBOL-BASED] Direct database call '${property}()' on '${objectSymbol?.name || 'unknown'}' found in Service`,
157
+ suggestion: 'Use Repository pattern for data access',
158
+ category: 'architecture'
159
+ };
160
+ }
161
+ }
162
+
163
+ return null;
164
+ }
165
+
166
+ /**
167
+ * Get symbol information for an object expression
168
+ */
169
+ getObjectSymbol(objectExpr) {
170
+ try {
171
+ // Try to get the symbol of the expression
172
+ const symbol = objectExpr.getSymbol();
173
+ if (symbol) {
174
+ return {
175
+ name: symbol.getName(),
176
+ type: this.getSymbolType(symbol),
177
+ isDatabase: this.isSymbolDatabase(symbol)
178
+ };
179
+ }
180
+
181
+ // Fallback: analyze by text patterns
182
+ const text = objectExpr.getText();
183
+ return {
184
+ name: text,
185
+ type: this.inferTypeFromText(text),
186
+ isDatabase: this.isTextDatabase(text)
187
+ };
188
+ } catch (error) {
189
+ return null;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Check if operation is a database operation
195
+ */
196
+ isDatabaseOperation(objectSymbol, methodName) {
197
+ if (!objectSymbol) return false;
198
+
199
+ // Exclude queue/job operations (Bull.js, agenda, etc.)
200
+ if (this.isQueueOperation(objectSymbol, methodName)) {
201
+ return false;
202
+ }
203
+
204
+ // Known database method patterns
205
+ const dbMethods = [
206
+ 'findOneBy', 'findBy', 'findAndCount', 'findMany', 'findFirst',
207
+ 'save', 'insert', 'create', 'upsert',
208
+ 'update', 'patch', 'merge', 'set',
209
+ 'delete', 'remove', 'destroy',
210
+ 'query', 'execute', 'run',
211
+ 'createQueryBuilder', 'getRepository'
212
+ ];
213
+
214
+ return objectSymbol.isDatabase && dbMethods.includes(methodName);
215
+ }
216
+
217
+ /**
218
+ * Check if this is a queue/job operation (should be excluded)
219
+ */
220
+ isQueueOperation(objectSymbol, methodName) {
221
+ if (!objectSymbol) return false;
222
+
223
+ const queueMethods = [
224
+ 'remove', 'isFailed', 'isCompleted', 'isActive', 'isWaiting', 'isDelayed',
225
+ 'getJob', 'getJobs', 'add', 'process', 'on', 'off',
226
+ 'retry', 'moveToCompleted', 'moveToFailed'
227
+ ];
228
+
229
+ const queueTypes = ['queue', 'job', 'bull'];
230
+ const objectName = objectSymbol.name.toLowerCase();
231
+
232
+ // Enhanced detection for Bull.js Job objects
233
+ const isQueueMethod = queueMethods.includes(methodName);
234
+ const isQueueObject = queueTypes.some(type => objectName.includes(type)) ||
235
+ /job/i.test(objectName) ||
236
+ /queue/i.test(objectName);
237
+
238
+ if (this.verbose && (isQueueMethod || isQueueObject)) {
239
+ console.log(`[DEBUG] Queue Check: object="${objectName}", method="${methodName}", isQueue=${isQueueMethod && isQueueObject}`);
240
+ }
241
+
242
+ return isQueueMethod && isQueueObject;
243
+ }
244
+
245
+ /**
246
+ * Check if access is through repository (acceptable)
247
+ */
248
+ isRepositoryAccess(objectSymbol) {
249
+ if (!objectSymbol) return false;
250
+
251
+ const name = objectSymbol.name.toLowerCase();
252
+ return name.includes('repository') || name.includes('repo');
253
+ }
254
+
255
+ /**
256
+ * Check if symbol represents database object
257
+ */
258
+ isSymbolDatabase(symbol) {
259
+ try {
260
+ const type = symbol.getType();
261
+ const typeName = type.getSymbol()?.getName() || '';
262
+
263
+ return this.databaseSymbols.some(dbSymbol =>
264
+ typeName.includes(dbSymbol)
265
+ );
266
+ } catch (error) {
267
+ return false;
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Infer type from text patterns (fallback)
273
+ */
274
+ inferTypeFromText(text) {
275
+ const lowerText = text.toLowerCase();
276
+
277
+ // Check for known database object patterns
278
+ if (/manager|connection|client|prisma/i.test(lowerText)) {
279
+ return 'database';
280
+ }
281
+
282
+ if (/repository|repo/i.test(lowerText)) {
283
+ return 'repository';
284
+ }
285
+
286
+ return 'unknown';
287
+ }
288
+
289
+ /**
290
+ * Check if text represents database access
291
+ */
292
+ isTextDatabase(text) {
293
+ const lowerText = text.toLowerCase();
294
+ return /manager|connection|client|prisma|entitymanager/i.test(lowerText) &&
295
+ !/repository|repo/i.test(lowerText);
296
+ }
297
+
298
+ /**
299
+ * Classify file type using semantic analysis
300
+ */
301
+ classifyFileSemanticType(sourceFile, filePath) {
302
+ const fileName = sourceFile.getBaseName().toLowerCase();
303
+
304
+ // Check filename patterns
305
+ if (/service\.ts$|service\.js$/i.test(fileName)) return 'service';
306
+ if (/repository\.ts$|repository\.js$/i.test(fileName)) return 'repository';
307
+
308
+ // Check class patterns
309
+ const classes = sourceFile.getClasses();
310
+ for (const cls of classes) {
311
+ if (this.isServiceClass(cls)) return 'service';
312
+ if (this.isRepositoryClass(cls)) return 'repository';
313
+ }
314
+
315
+ return 'unknown';
316
+ }
317
+
318
+ /**
319
+ * Check if class is a Service class
320
+ */
321
+ isServiceClass(cls) {
322
+ const className = cls.getName()?.toLowerCase() || '';
323
+
324
+ // Check class name
325
+ if (/service$/.test(className)) return true;
326
+
327
+ // Check decorators
328
+ const decorators = cls.getDecorators();
329
+ return decorators.some(decorator => {
330
+ const decoratorName = decorator.getName().toLowerCase();
331
+ return decoratorName.includes('service') || decoratorName === 'injectable';
332
+ });
333
+ }
334
+
335
+ /**
336
+ * Check if class is a Repository class
337
+ */
338
+ isRepositoryClass(cls) {
339
+ const className = cls.getName()?.toLowerCase() || '';
340
+ return /repository$|repo$/.test(className);
341
+ }
342
+
343
+ /**
344
+ * Get TypeScript SyntaxKind
345
+ */
346
+ getKind(kindName) {
347
+ try {
348
+ const ts = require('typescript');
349
+ return ts.SyntaxKind[kindName];
350
+ } catch (error) {
351
+ // Fallback for ts-morph
352
+ return this.semanticEngine?.project?.getTypeChecker()?.compilerObject?.SyntaxKind?.[kindName] || 0;
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Get symbol type information
358
+ */
359
+ getSymbolType(symbol) {
360
+ try {
361
+ return symbol.getType().getText();
362
+ } catch (error) {
363
+ return 'unknown';
364
+ }
365
+ }
366
+ }
367
+
368
+ module.exports = C033SymbolBasedAnalyzer;
@@ -0,0 +1,99 @@
1
+ # C035 Analysis Strategy
2
+
3
+ ## Rule Focus: Log Content Quality in Catch Blocks
4
+
5
+ ### Detection Pipeline:
6
+
7
+ 1. **Find Catch Blocks** (Symbol-based)
8
+ - Use AST to detect try-catch structures
9
+ - Extract catch parameter name (e, error, err, etc.)
10
+
11
+ 2. **Locate Log Calls** (Symbol-based)
12
+ - Find all method calls within catch block
13
+ - Use symbol resolution to identify log methods:
14
+ - console.log, console.error, console.warn
15
+ - logger.log, logger.error, logger.warn, logger.info
16
+ - log.error, log.warn, log.info
17
+ - winston, bunyan, pino patterns
18
+
19
+ 3. **Analyze Log Content** (AST + String Analysis)
20
+ - Check log call arguments
21
+ - Detect structured vs string logging
22
+ - Validate required context elements
23
+ - Check for sensitive data exposure
24
+
25
+ ### Violations to Detect:
26
+
27
+ #### 1. **Missing Context Information**
28
+ ```javascript
29
+ // ❌ Bad - No context
30
+ catch(e) {
31
+ logger.error(e.message);
32
+ }
33
+
34
+ // ✅ Good - Has context
35
+ catch(e) {
36
+ logger.error('User creation failed', {
37
+ error: e.message,
38
+ stack: e.stack,
39
+ userId: user.id,
40
+ requestId: req.id
41
+ });
42
+ }
43
+ ```
44
+
45
+ #### 2. **Non-structured Logging**
46
+ ```javascript
47
+ // ❌ Bad - String concatenation
48
+ catch(e) {
49
+ logger.error("Error: " + e.message + " User: " + userId);
50
+ }
51
+
52
+ // ✅ Good - Structured object
53
+ catch(e) {
54
+ logger.error('Operation failed', {
55
+ error: e.message,
56
+ userId: userId,
57
+ context: 'user-service'
58
+ });
59
+ }
60
+ ```
61
+
62
+ #### 3. **Sensitive Data Exposure**
63
+ ```javascript
64
+ // ❌ Bad - Exposes sensitive data
65
+ catch(e) {
66
+ logger.error('Login failed', {
67
+ password: user.password,
68
+ token: authToken
69
+ });
70
+ }
71
+
72
+ // ✅ Good - Sensitive data masked
73
+ catch(e) {
74
+ logger.error('Login failed', {
75
+ username: user.username,
76
+ password: user.password.substring(0,2) + '***',
77
+ requestId: req.id
78
+ });
79
+ }
80
+ ```
81
+
82
+ ### Required Context Elements:
83
+ - Error message/stack trace
84
+ - Request/operation identifier (requestId, transactionId)
85
+ - User/entity identifier (userId, entityId)
86
+ - Operation context (service name, method name)
87
+
88
+ ### Sensitive Data Patterns to Flag:
89
+ - password, passwd, pwd
90
+ - token, jwt, auth, secret
91
+ - key, apikey, privatekey
92
+ - ssn, credit, card, cvv
93
+ - email (in some contexts)
94
+
95
+ ### Implementation Priority:
96
+ 1. **Phase 1**: Basic structure detection (structured vs string)
97
+ 2. **Phase 2**: Context validation (required fields)
98
+ 3. **Phase 3**: Sensitive data detection
99
+ 4. **Phase 4**: Advanced patterns (custom loggers)
@@ -0,0 +1,230 @@
1
+ /**
2
+ * C035 Main Analyzer - Error Logging Context Detection
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
5
+ */
6
+
7
+ const C035SymbolBasedAnalyzer = require('./symbol-based-analyzer.js');
8
+ const C035RegexBasedAnalyzer = require('./regex-based-analyzer.js');
9
+
10
+ class C035Analyzer {
11
+ constructor(options = {}) {
12
+ if (process.env.SUNLINT_DEBUG) {
13
+ console.log(`🔧 [C035] Constructor called with options:`, !!options);
14
+ console.log(`🔧 [C035] Options type:`, typeof options, Object.keys(options || {}));
15
+ }
16
+
17
+ this.ruleId = 'C035';
18
+ this.ruleName = 'Error Logging Context Analysis';
19
+ this.description = 'Khi xử lý lỗi, phải ghi log đầy đủ thông tin liên quan - structured logging with 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 C035SymbolBasedAnalyzer(this.semanticEngine);
33
+ if (process.env.SUNLINT_DEBUG) {
34
+ console.log(`🔧 [C035] Symbol analyzer created successfully`);
35
+ }
36
+ } catch (error) {
37
+ console.error(`🔧 [C035] Error creating symbol analyzer:`, error);
38
+ }
39
+
40
+ try {
41
+ this.regexAnalyzer = new C035RegexBasedAnalyzer(this.semanticEngine);
42
+ if (process.env.SUNLINT_DEBUG) {
43
+ console.log(`🔧 [C035] Regex analyzer created successfully`);
44
+ }
45
+ } catch (error) {
46
+ console.error(`🔧 [C035] Error creating regex analyzer:`, error);
47
+ }
48
+
49
+ if (process.env.SUNLINT_DEBUG) {
50
+ console.log(`🔧 [C035] 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(`🔧 [C035 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
73
+ }
74
+ }
75
+
76
+ async analyze(files, language, options = {}) {
77
+ if (process.env.SUNLINT_DEBUG) {
78
+ console.log(`🔧 [C035] 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(`🔧 [C035] 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(`🔧 [C035] File ${filePath}: Found ${fileViolations.length} violations`);
94
+ }
95
+ } catch (error) {
96
+ console.warn(`❌ [C035] Analysis failed for ${filePath}:`, error.message);
97
+ }
98
+ }
99
+
100
+ if (process.env.SUNLINT_DEBUG) {
101
+ console.log(`🔧 [C035] 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(`🔧 [C035] 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(`🔧 [C035] 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(`🔧 [C035] 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(`✅ [C035] 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(`⚠️ [C035] Source file not found in project`);
137
+ }
138
+ }
139
+ } catch (error) {
140
+ console.warn(`⚠️ [C035] Symbol analysis failed: ${error.message}`);
141
+ // Continue to fallback
142
+ }
143
+ } else {
144
+ if (process.env.SUNLINT_DEBUG) {
145
+ console.log(`🔄 [C035] 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(`🔄 [C035] 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(`🔧 [C035] 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(`🔄 [C035] Regex-based analysis: ${violations.length} violations`);
167
+ }
168
+ return violations;
169
+ } catch (error) {
170
+ console.error(`❌ [C035] Regex analysis failed: ${error.message}`);
171
+ }
172
+ }
173
+
174
+ console.log(`🔧 [C035] No analysis methods succeeded, returning empty`);
175
+ return [];
176
+ }
177
+
178
+ async analyzeFileBasic(filePath, options = {}) {
179
+ console.log(`🔧 [C035] analyzeFileBasic() called for: ${filePath}`);
180
+ console.log(`🔧 [C035] semanticEngine exists: ${!!this.semanticEngine}`);
181
+ console.log(`🔧 [C035] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
182
+ console.log(`🔧 [C035] regexAnalyzer exists: ${!!this.regexAnalyzer}`);
183
+
184
+ try {
185
+ // Try symbol-based analysis first
186
+ if (this.semanticEngine?.isSymbolEngineReady?.() &&
187
+ this.semanticEngine.project) {
188
+
189
+ if (this.verbose) {
190
+ console.log(`🔍 [C035] Using symbol-based analysis for ${filePath}`);
191
+ }
192
+
193
+ const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
194
+ return violations;
195
+ }
196
+ } catch (error) {
197
+ if (this.verbose) {
198
+ console.warn(`⚠️ [C035] Symbol analysis failed: ${error.message}`);
199
+ }
200
+ }
201
+
202
+ // Fallback to regex-based analysis
203
+ if (this.verbose) {
204
+ console.log(`🔄 [C035] Using regex-based analysis (fallback) for ${filePath}`);
205
+ }
206
+
207
+ console.log(`🔧 [C035] About to call regexAnalyzer.analyzeFileBasic()`);
208
+ try {
209
+ const result = await this.regexAnalyzer.analyzeFileBasic(filePath, options);
210
+ console.log(`🔧 [C035] Regex analyzer returned: ${result.length} violations`);
211
+ return result;
212
+ } catch (error) {
213
+ console.error(`🔧 [C035] Error in regex analyzer:`, error);
214
+ return [];
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Methods for compatibility with different engine invocation patterns
220
+ */
221
+ async analyzeFileWithSymbols(filePath, options = {}) {
222
+ return this.analyzeFile(filePath, options);
223
+ }
224
+
225
+ async analyzeWithSemantics(filePath, options = {}) {
226
+ return this.analyzeFile(filePath, options);
227
+ }
228
+ }
229
+
230
+ module.exports = C035Analyzer;