@sun-asterisk/sunlint 1.3.18 → 1.3.19

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 (34) hide show
  1. package/config/rules/enhanced-rules-registry.json +77 -18
  2. package/core/cli-program.js +2 -1
  3. package/core/github-annotate-service.js +89 -0
  4. package/core/output-service.js +25 -0
  5. package/core/summary-report-service.js +30 -30
  6. package/package.json +3 -2
  7. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +392 -280
  8. package/rules/common/C017_constructor_logic/analyzer.js +137 -503
  9. package/rules/common/C017_constructor_logic/config.json +50 -0
  10. package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +463 -0
  11. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +463 -21
  12. package/rules/security/S011_secure_guid_generation/README.md +255 -0
  13. package/rules/security/S011_secure_guid_generation/analyzer.js +135 -0
  14. package/rules/security/S011_secure_guid_generation/config.json +56 -0
  15. package/rules/security/S011_secure_guid_generation/symbol-based-analyzer.js +609 -0
  16. package/rules/security/S028_file_upload_size_limits/README.md +537 -0
  17. package/rules/security/S028_file_upload_size_limits/analyzer.js +202 -0
  18. package/rules/security/S028_file_upload_size_limits/config.json +186 -0
  19. package/rules/security/S028_file_upload_size_limits/symbol-based-analyzer.js +530 -0
  20. package/rules/security/S041_session_token_invalidation/README.md +303 -0
  21. package/rules/security/S041_session_token_invalidation/analyzer.js +242 -0
  22. package/rules/security/S041_session_token_invalidation/config.json +175 -0
  23. package/rules/security/S041_session_token_invalidation/regex-based-analyzer.js +411 -0
  24. package/rules/security/S041_session_token_invalidation/symbol-based-analyzer.js +674 -0
  25. package/rules/security/S044_re_authentication_required/README.md +136 -0
  26. package/rules/security/S044_re_authentication_required/analyzer.js +242 -0
  27. package/rules/security/S044_re_authentication_required/config.json +161 -0
  28. package/rules/security/S044_re_authentication_required/regex-based-analyzer.js +329 -0
  29. package/rules/security/S044_re_authentication_required/symbol-based-analyzer.js +537 -0
  30. package/rules/security/S045_brute_force_protection/README.md +345 -0
  31. package/rules/security/S045_brute_force_protection/analyzer.js +336 -0
  32. package/rules/security/S045_brute_force_protection/config.json +139 -0
  33. package/rules/security/S045_brute_force_protection/symbol-based-analyzer.js +646 -0
  34. package/rules/common/C017_constructor_logic/semantic-analyzer.js +0 -340
@@ -0,0 +1,50 @@
1
+ {
2
+ "id": "C017",
3
+ "name": "C017_constructor_logic",
4
+ "category": "architecture",
5
+ "description": "C017 - Do not put business logic inside constructors.",
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
+ }
@@ -0,0 +1,463 @@
1
+ /**
2
+ * C017 Symbol-based Analyzer - Do not put business logic inside constructors
3
+ * Purpose: Ensure constructors only initialize objects, not perform business logic, to improve testability.
4
+ */
5
+
6
+ const { SyntaxKind } = require('ts-morph');
7
+
8
+ class C017SymbolBasedAnalyzer {
9
+ constructor(semanticEngine = null) {
10
+ this.ruleId = 'C017';
11
+ this.ruleName = 'Error put business logic inside constructors. (Symbol-Based)';
12
+ this.semanticEngine = semanticEngine;
13
+ this.verbose = false;
14
+ }
15
+
16
+ async initialize(semanticEngine = null) {
17
+ if (semanticEngine) {
18
+ this.semanticEngine = semanticEngine;
19
+ }
20
+ this.verbose = semanticEngine?.verbose || false;
21
+
22
+ if (process.env.SUNLINT_DEBUG) {
23
+ console.log(`🔧 [C017 Symbol-Based] Analyzer initialized, verbose: ${this.verbose}`);
24
+ }
25
+ }
26
+
27
+ async analyzeFileBasic(filePath, options = {}) {
28
+ // This is the main entry point called by the hybrid analyzer
29
+ return await this.analyzeFileWithSymbols(filePath, options);
30
+ }
31
+
32
+ async analyzeFileWithSymbols(filePath, options = {}) {
33
+ const violations = [];
34
+
35
+ // Enable verbose mode if requested
36
+ const verbose = options.verbose || this.verbose;
37
+
38
+ if (!this.semanticEngine?.project) {
39
+ if (verbose) {
40
+ console.warn('[C017 Symbol-Based] No semantic engine available, skipping analysis');
41
+ }
42
+ return violations;
43
+ }
44
+
45
+ if (verbose) {
46
+ console.log(`🔍 [C017 Symbol-Based] Starting analysis for ${filePath}`);
47
+ }
48
+
49
+ try {
50
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
51
+ if (!sourceFile) {
52
+ return violations;
53
+ }
54
+
55
+ // Find all constructor declarations
56
+ const constructors = sourceFile.getDescendantsOfKind(SyntaxKind.Constructor);
57
+
58
+ for (const constructor of constructors) {
59
+ this.analyzeConstructor(constructor, filePath, violations, verbose);
60
+ }
61
+
62
+ if (verbose) {
63
+ console.log(`🔍 [C017 Symbol-Based] Total violations found: ${violations.length}`);
64
+ }
65
+
66
+ return violations;
67
+ } catch (error) {
68
+ if (verbose) {
69
+ console.warn(`[C017 Symbol-Based] Analysis failed for ${filePath}:`, error.message);
70
+ }
71
+ return violations;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Analyze a constructor for business logic violations
77
+ */
78
+ analyzeConstructor(constructor, filePath, violations, verbose = false) {
79
+ const body = constructor.getBody();
80
+ if (!body) return;
81
+
82
+ const statements = body.getStatements();
83
+
84
+ for (const statement of statements) {
85
+ // Check for method calls (instance methods)
86
+ if (this.containsMethodCall(statement, verbose)) {
87
+ const { line, column } = this.getStatementPosition(statement);
88
+
89
+ violations.push({
90
+ ruleId: this.ruleId,
91
+ severity: 'warning',
92
+ message: 'Constructor calls instance methods - business logic should not be in constructors',
93
+ source: this.ruleId,
94
+ file: filePath,
95
+ line: line,
96
+ column: column,
97
+ description: `[SYMBOL-BASED] Instance method calls detected in constructor. Calling methods in constructors makes the class harder to test and can cause unexpected behavior if methods are overridden.`,
98
+ suggestion: 'Move method calls to a separate initialization method (e.g., init(), setup()) that can be called after object creation and easily mocked in tests.',
99
+ category: 'TESTABILITY'
100
+ });
101
+ }
102
+
103
+ // Check for API calls (fetch, axios, http requests)
104
+ if (this.containsApiCall(statement, verbose)) {
105
+ const { line, column } = this.getStatementPosition(statement);
106
+
107
+ violations.push({
108
+ ruleId: this.ruleId,
109
+ severity: 'warning',
110
+ message: 'Constructor contains API call - business logic should not be in constructors',
111
+ source: this.ruleId,
112
+ file: filePath,
113
+ line: line,
114
+ column: column,
115
+ description: `[SYMBOL-BASED] API calls detected in constructor. Constructors should only initialize fields and assign dependencies.`,
116
+ suggestion: 'Move API calls to a separate initialization method (e.g., init(), load(), or setup()) and call it after object creation.',
117
+ category: 'CODE_QUALITY'
118
+ });
119
+ }
120
+
121
+ // Check for complex logic (if/else, loops, switch) - with stricter rules
122
+ if (this.containsComplexLogic(statement, verbose)) {
123
+ const { line, column } = this.getStatementPosition(statement);
124
+
125
+ violations.push({
126
+ ruleId: this.ruleId,
127
+ severity: 'warning',
128
+ message: 'Constructor contains complex business logic - reduces testability',
129
+ source: this.ruleId,
130
+ file: filePath,
131
+ line: line,
132
+ column: column,
133
+ description: `[SYMBOL-BASED] Complex control flow (if/else, loops, switch) detected in constructor. This makes the class harder to test and instantiate.`,
134
+ suggestion: 'Extract complex logic into separate methods that can be called after object creation and mocked during testing.',
135
+ category: 'TESTABILITY'
136
+ });
137
+ }
138
+
139
+ // Check for logging operations
140
+ if (this.containsLogging(statement, verbose)) {
141
+ const { line, column } = this.getStatementPosition(statement);
142
+
143
+ violations.push({
144
+ ruleId: this.ruleId,
145
+ severity: 'info',
146
+ message: 'Constructor contains logging - side effects should be avoided',
147
+ source: this.ruleId,
148
+ file: filePath,
149
+ line: line,
150
+ column: column,
151
+ description: `[SYMBOL-BASED] Logging detected in constructor. Constructors should be side-effect free for predictability.`,
152
+ suggestion: 'Move logging to initialization methods or business logic methods.',
153
+ category: 'RELIABILITY'
154
+ });
155
+ }
156
+
157
+ // Check for file I/O operations
158
+ if (this.containsFileIO(statement, verbose)) {
159
+ const { line, column } = this.getStatementPosition(statement);
160
+
161
+ violations.push({
162
+ ruleId: this.ruleId,
163
+ severity: 'warning',
164
+ message: 'Constructor contains file I/O operations - business logic violation',
165
+ source: this.ruleId,
166
+ file: filePath,
167
+ line: line,
168
+ column: column,
169
+ description: `[SYMBOL-BASED] File I/O operations detected in constructor. This creates side effects and makes testing difficult.`,
170
+ suggestion: 'Move file operations to separate methods that can be called explicitly and mocked during testing.',
171
+ category: 'INTEGRATION'
172
+ });
173
+ }
174
+
175
+ // Check for database operations
176
+ if (this.containsDatabaseOperation(statement, verbose)) {
177
+ const { line, column } = this.getStatementPosition(statement);
178
+
179
+ violations.push({
180
+ ruleId: this.ruleId,
181
+ severity: 'warning',
182
+ message: 'Constructor contains database operations - violates constructor principles',
183
+ source: this.ruleId,
184
+ file: filePath,
185
+ line: line,
186
+ column: column,
187
+ description: `[SYMBOL-BASED] Database operations detected in constructor. This makes object creation slow and unpredictable.`,
188
+ suggestion: 'Move database operations to repository methods or service layer methods.',
189
+ category: 'INTEGRATION'
190
+ });
191
+ }
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Get the position of a statement
197
+ */
198
+ getStatementPosition(statement) {
199
+ try {
200
+ return {
201
+ line: statement.getStartLineNumber(),
202
+ column: statement.getStartLinePos ? statement.getStartLinePos() : 0
203
+ };
204
+ } catch {
205
+ return { line: 0, column: 0 };
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Check if statement contains instance method calls
211
+ */
212
+ containsMethodCall(statement, verbose = false) {
213
+ // Get all call expressions in the statement
214
+ const callExpressions = statement.getDescendantsOfKind(SyntaxKind.CallExpression);
215
+
216
+ for (const callExpr of callExpressions) {
217
+ const expression = callExpr.getExpression();
218
+ const callText = expression.getText();
219
+
220
+ // Check if it's a 'this.' method call
221
+ if (callText.startsWith('this.')) {
222
+ // Extract method name
223
+ const methodName = callText.replace('this.', '').split('(')[0];
224
+
225
+ // Ignore common safe operations
226
+ const safePatterns = [
227
+ 'toString',
228
+ 'valueOf',
229
+ 'hasOwnProperty',
230
+ 'isPrototypeOf',
231
+ 'propertyIsEnumerable'
232
+ ];
233
+
234
+ if (!safePatterns.includes(methodName)) {
235
+ // Check if it's a config service or environment variable access
236
+ if (!this.isSafeConfigAccess(callExpr)) {
237
+ if (verbose) {
238
+ console.log(` 🔧 Method call detected: ${callText}`);
239
+ }
240
+ return true;
241
+ }
242
+ }
243
+ }
244
+
245
+ // Also check for super method calls (not super() constructor call)
246
+ if (callText.startsWith('super.')) {
247
+ if (verbose) {
248
+ console.log(` 🔧 Super method call detected: ${callText}`);
249
+ }
250
+ return true;
251
+ }
252
+ }
253
+
254
+ return false;
255
+ }
256
+
257
+ /**
258
+ * Check if a method call is safe config/environment access
259
+ * These are allowed in constructors as they are initialization, not business logic
260
+ */
261
+ isSafeConfigAccess(callExpr) {
262
+ const expression = callExpr.getExpression();
263
+ const callText = expression.getText();
264
+
265
+ // Pattern: this.configService.get(...) or similar
266
+ // Pattern: this.envService.get(...) or similar
267
+ const configPatterns = [
268
+ /this\.(config|env|environment|settings?)Service\.get/i,
269
+ /this\.(config|env|environment|settings?)\.get/i,
270
+ /this\.(config|env|environment|settings?)Service\.read/i,
271
+ /this\.(config|env|environment|settings?)\.read/i,
272
+ ];
273
+
274
+ if (configPatterns.some(pattern => pattern.test(callText))) {
275
+ // Verify it's a simple get/read operation, not complex logic
276
+ const args = callExpr.getArguments();
277
+ // Should have 1-2 arguments (key, optional default value)
278
+ if (args.length <= 2) {
279
+ return true;
280
+ }
281
+ }
282
+
283
+ // Pattern: process.env access wrapped in a method
284
+ // Pattern: this.getString('KEY') where getString is a simple getter
285
+ const simpleGetterPatterns = [
286
+ /this\.get(String|Number|Boolean|Int|Float)/,
287
+ ];
288
+
289
+ if (simpleGetterPatterns.some(pattern => pattern.test(callText))) {
290
+ const args = callExpr.getArguments();
291
+ if (args.length <= 2) {
292
+ return true;
293
+ }
294
+ }
295
+
296
+ return false;
297
+ }
298
+
299
+ /**
300
+ * Check if statement contains API calls
301
+ */
302
+ containsApiCall(statement, verbose = false) {
303
+ const text = statement.getText();
304
+
305
+ // Common API call patterns
306
+ const apiPatterns = [
307
+ /\bfetch\s*\(/,
308
+ /\baxios\./,
309
+ /\b(get|post|put|delete|patch)\s*\(/,
310
+ /\.ajax\s*\(/,
311
+ /\bhttps?\./,
312
+ /\bHttpClient\./,
313
+ /\bXMLHttpRequest/,
314
+ /\bsuperagent\./,
315
+ /\brequest\s*\(/
316
+ ];
317
+
318
+ const hasApiCall = apiPatterns.some(pattern => pattern.test(text));
319
+
320
+ if (hasApiCall && verbose) {
321
+ console.log(` 📡 API call detected in constructor`);
322
+ }
323
+
324
+ return hasApiCall;
325
+ }
326
+
327
+ /**
328
+ * Check if statement contains complex logic
329
+ * Now more strict - flags most conditional logic
330
+ */
331
+ containsComplexLogic(statement, verbose = false) {
332
+ // Check for control flow structures
333
+ const hasIfStatement = statement.getDescendantsOfKind(SyntaxKind.IfStatement).length > 0;
334
+ const hasLoop = statement.getDescendantsOfKind(SyntaxKind.ForStatement).length > 0 ||
335
+ statement.getDescendantsOfKind(SyntaxKind.WhileStatement).length > 0 ||
336
+ statement.getDescendantsOfKind(SyntaxKind.DoStatement).length > 0 ||
337
+ statement.getDescendantsOfKind(SyntaxKind.ForOfStatement).length > 0 ||
338
+ statement.getDescendantsOfKind(SyntaxKind.ForInStatement).length > 0;
339
+ const hasSwitch = statement.getDescendantsOfKind(SyntaxKind.SwitchStatement).length > 0;
340
+ const hasTryCatch = statement.getDescendantsOfKind(SyntaxKind.TryStatement).length > 0;
341
+ const hasConditional = statement.getDescendantsOfKind(SyntaxKind.ConditionalExpression).length > 0; // ternary operators
342
+
343
+ // Be more strict with if statements
344
+ if (hasIfStatement) {
345
+ const ifStatements = statement.getDescendantsOfKind(SyntaxKind.IfStatement);
346
+
347
+ for (const ifStmt of ifStatements) {
348
+ const condition = ifStmt.getExpression().getText();
349
+ const thenStatement = ifStmt.getThenStatement();
350
+ const elseStatement = ifStmt.getElseStatement();
351
+
352
+ // Only allow very simple null/undefined checks for default parameter values
353
+ // Pattern: if (!param) { this.param = defaultValue; }
354
+ const isSimpleNullDefault =
355
+ /^!?\w+$/.test(condition) && // Simple variable check
356
+ !elseStatement && // No else clause
357
+ thenStatement &&
358
+ thenStatement.getText().match(/^{\s*this\.\w+\s*=\s*[^;]+;\s*}$/);
359
+
360
+ if (!isSimpleNullDefault) {
361
+ if (verbose) {
362
+ console.log(` 🔀 Complex if statement detected: ${condition}`);
363
+ }
364
+ return true;
365
+ }
366
+ }
367
+ }
368
+
369
+ const hasComplexLogic = hasLoop || hasSwitch || hasTryCatch || hasConditional;
370
+
371
+ if (hasComplexLogic && verbose) {
372
+ console.log(` 🔀 Complex logic detected in constructor`);
373
+ }
374
+
375
+ return hasComplexLogic;
376
+ }
377
+
378
+ /**
379
+ * Check if statement contains logging
380
+ */
381
+ containsLogging(statement, verbose = false) {
382
+ const text = statement.getText();
383
+
384
+ // Common logging patterns
385
+ const loggingPatterns = [
386
+ /\bconsole\.(log|warn|error|info|debug)/,
387
+ /\blogger\./,
388
+ /\bLog\./,
389
+ /\blogging\./,
390
+ /\bwinston\./,
391
+ /\bbunyan\./,
392
+ /\bpino\./,
393
+ /\.log\s*\(/
394
+ ];
395
+
396
+ const hasLogging = loggingPatterns.some(pattern => pattern.test(text));
397
+
398
+ if (hasLogging && verbose) {
399
+ console.log(` 📝 Logging detected in constructor`);
400
+ }
401
+
402
+ return hasLogging;
403
+ }
404
+
405
+ /**
406
+ * Check if statement contains file I/O operations
407
+ */
408
+ containsFileIO(statement, verbose = false) {
409
+ const text = statement.getText();
410
+
411
+ // File I/O patterns
412
+ const fileIOPatterns = [
413
+ /\bfs\.(readFile|writeFile|read|write|open|close)/,
414
+ /\breadFileSync/,
415
+ /\bwriteFileSync/,
416
+ /\bFileReader/,
417
+ /\bFile\./,
418
+ /\bpath\.read/,
419
+ /\bpath\.write/
420
+ ];
421
+
422
+ const hasFileIO = fileIOPatterns.some(pattern => pattern.test(text));
423
+
424
+ if (hasFileIO && verbose) {
425
+ console.log(` 📁 File I/O detected in constructor`);
426
+ }
427
+
428
+ return hasFileIO;
429
+ }
430
+
431
+ /**
432
+ * Check if statement contains database operations
433
+ */
434
+ containsDatabaseOperation(statement, verbose = false) {
435
+ const text = statement.getText();
436
+
437
+ // Database operation patterns
438
+ const dbPatterns = [
439
+ /\b(find|findOne|findById|save|update|delete|insert|create|query|exec)\s*\(/,
440
+ /\bmongoose\./,
441
+ /\bSequelize\./,
442
+ /\bTypeORM\./,
443
+ /\bPrisma\./,
444
+ /\.collection\s*\(/,
445
+ /\bdb\./,
446
+ /\bknex\./,
447
+ /SELECT\s+.*\s+FROM/i,
448
+ /INSERT\s+INTO/i,
449
+ /UPDATE\s+.*\s+SET/i,
450
+ /DELETE\s+FROM/i
451
+ ];
452
+
453
+ const hasDbOperation = dbPatterns.some(pattern => pattern.test(text));
454
+
455
+ if (hasDbOperation && verbose) {
456
+ console.log(` 💾 Database operation detected in constructor`);
457
+ }
458
+
459
+ return hasDbOperation;
460
+ }
461
+ }
462
+
463
+ module.exports = C017SymbolBasedAnalyzer;