@sun-asterisk/sunlint 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/CONTRIBUTING.md +249 -605
  3. package/README.md +3 -4
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/large-project.json +143 -0
  7. package/config/presets/all.json +0 -1
  8. package/config/release.json +70 -0
  9. package/config/rule-analysis-strategies.js +38 -3
  10. package/config/rules/enhanced-rules-registry.json +474 -1179
  11. package/config/rules/rules-registry-generated.json +3 -3
  12. package/core/cli-action-handler.js +24 -30
  13. package/core/cli-program.js +11 -3
  14. package/core/config-merger.js +29 -2
  15. package/core/enhanced-rules-registry.js +3 -2
  16. package/core/semantic-engine.js +129 -19
  17. package/core/semantic-rule-base.js +4 -2
  18. package/core/unified-rule-registry.js +1 -1
  19. package/docs/COMMAND-EXAMPLES.md +134 -0
  20. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  21. package/engines/heuristic-engine.js +135 -16
  22. package/integrations/eslint/plugin/index.js +0 -2
  23. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  24. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  25. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  26. package/origin-rules/common-en.md +19 -15
  27. package/package.json +1 -1
  28. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  29. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  30. package/rules/common/C006_function_naming/analyzer.js +29 -3
  31. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  32. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  33. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  34. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  35. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  36. package/rules/common/C013_no_dead_code/config.json +61 -0
  37. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  38. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  39. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  40. package/rules/common/C014_dependency_injection/config.json +26 -0
  41. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  42. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  43. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  44. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  45. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  46. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  47. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  48. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  49. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  50. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  51. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  52. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  53. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  54. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  55. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  56. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  57. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  58. package/rules/common/C033_separate_service_repository/README.md +78 -0
  59. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  60. package/rules/common/C033_separate_service_repository/config.json +50 -0
  61. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  62. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  63. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  64. package/rules/common/C035_error_logging_context/analyzer.js +232 -0
  65. package/rules/common/C035_error_logging_context/config.json +54 -0
  66. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  67. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  68. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  69. package/rules/common/C040_centralized_validation/config.json +46 -0
  70. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  71. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  72. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  73. package/rules/common/C076_explicit_function_types/README.md +30 -0
  74. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  75. package/rules/common/C076_explicit_function_types/config.json +15 -0
  76. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  77. package/rules/index.js +6 -1
  78. package/rules/parser/rule-parser.js +13 -2
  79. package/rules/security/S005_no_origin_auth/README.md +226 -0
  80. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  81. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  82. package/rules/security/S005_no_origin_auth/config.json +85 -0
  83. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  84. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  85. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  86. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  87. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  88. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  89. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  90. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  91. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  92. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  93. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  94. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  95. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  96. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  97. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  98. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  99. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  100. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  101. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  102. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  103. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  104. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  105. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  106. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  107. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  108. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  109. package/rules/security/S055_content_type_validation/README.md +176 -0
  110. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  111. package/rules/security/S055_content_type_validation/config.json +48 -0
  112. package/rules/utils/rule-helpers.js +140 -1
  113. package/scripts/consolidate-config.js +116 -0
  114. package/scripts/prepare-release.sh +1 -1
  115. package/config/rules/rules-registry.json +0 -765
  116. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  117. package/docs/FUTURE_PACKAGES.md +0 -83
  118. package/docs/HEURISTIC_VS_AI.md +0 -113
  119. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  120. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  121. package/docs/RELEASE_GUIDE.md +0 -230
  122. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  123. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  124. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -0,0 +1,387 @@
1
+ /**
2
+ * C018 Regex-based Analyzer - Do not throw generic errors
3
+ * Purpose: Fallback pattern matching when symbol analysis fails
4
+ */
5
+
6
+ class C018RegexBasedAnalyzer {
7
+ constructor(semanticEngine = null) {
8
+ this.ruleId = 'C018';
9
+ this.ruleName = 'Do not throw generic errors (Regex-Based)';
10
+ this.semanticEngine = semanticEngine;
11
+ this.verbose = false;
12
+
13
+ // Patterns for identifying catch blocks (supports TypeScript type annotations)
14
+ this.catchPattern = /catch\s*\(\s*(\w+)(?:\s*:\s*\w+(?:\s*\|\s*\w+)*)?\s*\)\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/gs;
15
+
16
+ // throw error patterns
17
+ this.exceptionPatterns = [
18
+ /throw\s+\w+/g,
19
+ ];
20
+
21
+ // Sensitive data patterns
22
+ this.sensitivePatterns = [
23
+ /password|passwd|pwd/gi,
24
+ /token|jwt|auth|secret|key/gi,
25
+ /ssn|social|credit|card|cvv/gi
26
+ ];
27
+
28
+ // String concatenation patterns (non-structured)
29
+ this.stringConcatPatterns = [
30
+ /\+\s*["'`]/g, // string concatenation
31
+ /["'`]\s*\+/g, // string concatenation
32
+ /\$\{.*\}/g // template literals (basic)
33
+ ];
34
+
35
+ this.structuredPatterns = [
36
+ /\{\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*:\s*[^}]+\}/g, // structured object format
37
+ ]
38
+
39
+ // Ensure error messages should explain what happened, why, and in what context
40
+ this.explanationPatterns = [
41
+ /\b(because|due to|failed to|cannot|invalid|missing|not found)\b/i,
42
+ ];
43
+ this.guidancePatterns = [
44
+ /\b(please|ensure|make sure|check|try|use)\b/i,
45
+ ];
46
+ }
47
+
48
+ async initialize(semanticEngine = null) {
49
+ if (semanticEngine) {
50
+ this.semanticEngine = semanticEngine;
51
+ }
52
+ this.verbose = semanticEngine?.verbose || false;
53
+
54
+ if (this.verbose) {
55
+ console.log(`🔧 [C018 Regex-Based] Analyzer initialized`);
56
+ }
57
+ }
58
+
59
+ async analyzeFileBasic(filePath, options = {}) {
60
+ const fs = require('fs');
61
+ const path = require('path');
62
+ const violations = [];
63
+
64
+ if (this.verbose) {
65
+ console.log(`🔧 [C018 Regex] Starting analysis for: ${filePath}`);
66
+ }
67
+
68
+ try {
69
+ const content = fs.readFileSync(filePath, 'utf8');
70
+
71
+ if (this.verbose) {
72
+ console.log(`🔧 [C018 Regex] File content length: ${content.length}`);
73
+ }
74
+
75
+ const lines = content.split('\n');
76
+ const catchBlocks = this.findCatchBlocks(content);
77
+
78
+ if (this.verbose) {
79
+ console.log(`🔧 [C018 Regex] Found ${catchBlocks.length} catch blocks`);
80
+ }
81
+
82
+ for (const block of catchBlocks) {
83
+ const blockViolations = this.analyzeCatchBlockContent(block, lines, filePath);
84
+ if (this.verbose && blockViolations.length > 0) {
85
+ console.log(`🔧 [C018 Regex] Block violations: ${blockViolations.length}`);
86
+ }
87
+ violations.push(...blockViolations);
88
+ }
89
+
90
+ if (this.verbose) {
91
+ console.log(`🔧 [C018 Regex] Total violations found: ${violations.length}`);
92
+ }
93
+
94
+ return violations;
95
+ } catch (error) {
96
+ if (this.verbose) {
97
+ console.error(`🔧 [C018 Regex] Error analyzing ${filePath}:`, error);
98
+ }
99
+ return [];
100
+ }
101
+ } /**
102
+ * Find catch blocks in content using regex
103
+ */
104
+ findCatchBlocks(content) {
105
+ const catchBlocks = [];
106
+ let match;
107
+
108
+ // Reset regex
109
+ this.catchPattern.lastIndex = 0;
110
+
111
+ while ((match = this.catchPattern.exec(content)) !== null) {
112
+ const fullMatch = match[0];
113
+ const errorVar = match[1];
114
+ const blockContent = match[2];
115
+
116
+ // Calculate line number
117
+ const beforeMatch = content.substring(0, match.index);
118
+ const lineNumber = beforeMatch.split('\n').length;
119
+
120
+ catchBlocks.push({
121
+ fullMatch,
122
+ errorVar,
123
+ blockContent,
124
+ lineNumber,
125
+ startIndex: match.index
126
+ });
127
+ }
128
+
129
+ return catchBlocks;
130
+ }
131
+
132
+ /**
133
+ * Analyze catch block content for logging violations
134
+ */
135
+ analyzeCatchBlockContent(catchBlock, lines, filePath) {
136
+ const violations = [];
137
+ const { blockContent, lineNumber, errorVar } = catchBlock;
138
+
139
+ // Find log calls in catch block
140
+ const ErrorCalls = this.findExceptionCallsInContent(blockContent);
141
+
142
+ if (ErrorCalls.length === 0) {
143
+ // No logging - C018's concern, not ours
144
+ return violations;
145
+ }
146
+
147
+ // Analyze each log call
148
+ for (const errCall of ErrorCalls) {
149
+ const logLineNumber = lineNumber + errCall.relativeLineNumber;
150
+ // Check for generic error throws without context
151
+ if (errCall.content.includes('new Error(')) {
152
+ violations.push({
153
+ ruleId: this.ruleId,
154
+ severity: 'error',
155
+ message: 'Throwing generic Error without context',
156
+ source: this.ruleId,
157
+ file: filePath,
158
+ line: logLineNumber,
159
+ column: errCall.column,
160
+ description: `[REGEX-FALLBACK] Generic error thrown without context. Use structured error objects.`,
161
+ suggestion: 'Use specific error types or structured error objects with context',
162
+ category: 'error-handling'
163
+ });
164
+ }
165
+ // Check for generic error direct ex: throw error
166
+ if (errCall.content.includes(`throw ${errorVar}`)) {
167
+ violations.push({
168
+ ruleId: this.ruleId,
169
+ severity: 'error',
170
+ message: 'Throwing caught error directly without context',
171
+ source: this.ruleId,
172
+ file: filePath,
173
+ line: logLineNumber,
174
+ column: errCall.column,
175
+ description: `[REGEX-FALLBACK] Caught error thrown directly without additional context. Use structured error objects.`,
176
+ suggestion: 'Use structured error objects with context instead of throwing caught errors directly',
177
+ category: 'error-handling'
178
+ });
179
+ }
180
+
181
+
182
+ // Check for non-structured logging (string concatenation)
183
+ if (this.hasStringConcatenation(errCall.content)) {
184
+ violations.push({
185
+ ruleId: this.ruleId,
186
+ severity: 'warning',
187
+ message: 'Error logging should use structured format instead of string concatenation',
188
+ source: this.ruleId,
189
+ file: filePath,
190
+ line: logLineNumber,
191
+ column: errCall.column,
192
+ description: `[REGEX-FALLBACK] String concatenation detected in error logging. Use structured object format.`,
193
+ suggestion: 'Use logger.error("message", { error: e.message, context: {...} }) instead of string concatenation',
194
+ category: 'error-handling'
195
+ });
196
+ }
197
+
198
+ // Check for sensitive data
199
+ const sensitiveData = this.findSensitiveData(errCall.content);
200
+ if (sensitiveData.length > 0) {
201
+ violations.push({
202
+ ruleId: this.ruleId,
203
+ severity: 'error',
204
+ message: 'Error logging contains potentially sensitive data',
205
+ source: this.ruleId,
206
+ file: filePath,
207
+ line: logLineNumber,
208
+ column: errCall.column,
209
+ description: `[REGEX-FALLBACK] Sensitive patterns detected: ${sensitiveData.join(', ')}. Mask or exclude sensitive data.`,
210
+ suggestion: 'Mask sensitive data or exclude entirely from logs',
211
+ category: 'security'
212
+ });
213
+ }
214
+
215
+ // Check messages should explain what happened
216
+ const explaintionData = this.findExplanationData(errCall.content);
217
+ if (!explaintionData.length) {
218
+ violations.push({
219
+ ruleId: this.ruleId,
220
+ severity: 'warning',
221
+ message: 'Error logging should explain what happened',
222
+ source: this.ruleId,
223
+ file: filePath,
224
+ line: logLineNumber,
225
+ column: errCall.column,
226
+ description: `[SYMBOL-BASED] Error message should explain what happened, why, and in what context.`,
227
+ suggestion: 'Use structured error objects with context: { message: "Error occurred", context: "Request failed because todo something." } }',
228
+ category: 'error-handling'
229
+ });
230
+ }
231
+
232
+ // Check messages should provide guidance
233
+ const guidanceData = this.findGuidanceData(errCall.content);
234
+ if (!guidanceData.length) {
235
+ violations.push({
236
+ ruleId: this.ruleId,
237
+ severity: 'warning',
238
+ message: 'Error logging should provide guidance on next steps',
239
+ source: this.ruleId,
240
+ file: filePath,
241
+ line: logLineNumber,
242
+ column: errCall.column,
243
+ description: `[SYMBOL-BASED] Error message should provide guidance on next steps.`,
244
+ suggestion: 'Use structured error objects with guidance: { message: "Error occurred", guidance: "Please check the input and try again." } }',
245
+ category: 'error-handling'
246
+ });
247
+ }
248
+ }
249
+
250
+ return violations;
251
+ }
252
+
253
+ /**
254
+ * Find log calls within catch block content
255
+ */
256
+ findExceptionCallsInContent(content) {
257
+ const ErrorCalls = [];
258
+ const lines = content.split('\n');
259
+
260
+ for (let i = 0; i < lines.length; i++) {
261
+ const line = lines[i];
262
+
263
+ for (const pattern of this.exceptionPatterns) {
264
+ pattern.lastIndex = 0; // Reset regex
265
+ const match = pattern.exec(line);
266
+
267
+ if (match) {
268
+ ErrorCalls.push({
269
+ content: line.trim(),
270
+ relativeLineNumber: i,
271
+ column: match.index + 1,
272
+ method: match[1] || 'unknown'
273
+ });
274
+ }
275
+ }
276
+ }
277
+
278
+ return ErrorCalls;
279
+ }
280
+
281
+ /**
282
+ * Check if log call uses string concatenation
283
+ */
284
+ hasStringConcatenation(content) {
285
+ return this.stringConcatPatterns.some(pattern => {
286
+ pattern.lastIndex = 0;
287
+ return pattern.test(content);
288
+ });
289
+ }
290
+
291
+ /**
292
+ * Find sensitive data patterns in content
293
+ */
294
+ findSensitiveData(content) {
295
+ const found = [];
296
+
297
+ for (const pattern of this.sensitivePatterns) {
298
+ pattern.lastIndex = 0;
299
+ const matches = content.match(pattern);
300
+ if (matches) {
301
+ found.push(...matches.map(m => m.toLowerCase()));
302
+ }
303
+ }
304
+
305
+ return [...new Set(found)]; // Remove duplicates
306
+ }
307
+
308
+ // Validate structure patterns
309
+ validateStructuredPatterns(content) {
310
+ for (const pattern of this.structuredPatterns) {
311
+ pattern.lastIndex = 0;
312
+ if (pattern.test(content)) {
313
+ return true; // Structured format found
314
+ }
315
+ }
316
+ return false; // No structured format found
317
+ }
318
+
319
+ /**
320
+ * Find explanation data patterns in content
321
+ */
322
+ findExplanationData(content) {
323
+ const found = [];
324
+
325
+ for (const pattern of this.explanationPatterns) {
326
+ pattern.lastIndex = 0;
327
+ const matches = content.match(pattern);
328
+ if (matches) {
329
+ found.push(...matches.map(m => m.toLowerCase()));
330
+ }
331
+ }
332
+
333
+ return [...new Set(found)]; // Remove duplicates
334
+ }
335
+
336
+ /**
337
+ * Find guidance data patterns in content
338
+ */
339
+ findGuidanceData(content) {
340
+ const found = [];
341
+
342
+ for (const pattern of this.guidancePatterns) {
343
+ pattern.lastIndex = 0;
344
+ const matches = content.match(pattern);
345
+ if (matches) {
346
+ found.push(...matches.map(m => m.toLowerCase()));
347
+ }
348
+ }
349
+
350
+ return [...new Set(found)]; // Remove duplicates
351
+ }
352
+
353
+ async analyze(files, language, options = {}) {
354
+ if (this.verbose) {
355
+ console.log(`🔧 [C018 Regex] analyze() called with ${files.length} files, language: ${language}`);
356
+ }
357
+
358
+ const violations = [];
359
+
360
+ for (const filePath of files) {
361
+ try {
362
+ if (this.verbose) {
363
+ console.log(`🔧 [C018 Regex] Processing file: ${filePath}`);
364
+ }
365
+
366
+ const fileViolations = await this.analyzeFileBasic(filePath, options);
367
+ violations.push(...fileViolations);
368
+
369
+ if (this.verbose) {
370
+ console.log(`🔧 [C018 Regex] File ${filePath}: Found ${fileViolations.length} violations`);
371
+ }
372
+ } catch (error) {
373
+ if (this.verbose) {
374
+ console.warn(`❌ [C018 Regex] Analysis failed for ${filePath}:`, error.message);
375
+ }
376
+ }
377
+ }
378
+
379
+ if (this.verbose) {
380
+ console.log(`🔧 [C018 Regex] Total violations found: ${violations.length}`);
381
+ }
382
+
383
+ return violations;
384
+ }
385
+ }
386
+
387
+ module.exports = C018RegexBasedAnalyzer;
@@ -0,0 +1,314 @@
1
+ /**
2
+ * C018 Symbol-based Analyzer - Advanced Do not throw generic errors
3
+ * Purpose: Use AST + Symbol Resolution to analyze log content quality in catch blocks
4
+ */
5
+
6
+ const { SyntaxKind } = require('ts-morph');
7
+
8
+ class C018SymbolBasedAnalyzer {
9
+ constructor(semanticEngine = null) {
10
+ this.ruleId = 'C018';
11
+ this.ruleName = 'Error Always provide detailed messages and context. (Symbol-Based)';
12
+ this.semanticEngine = semanticEngine;
13
+ this.verbose = false;
14
+
15
+ // Sensitive data patterns to flag (more specific to avoid false positives)
16
+ this.sensitivePatterns = [
17
+ 'password', 'passwd', 'pwd', 'pass',
18
+ 'token', 'jwt', 'secret', 'privatekey', 'publickey', 'apikey', 'accesskey',
19
+ 'ssn', 'social', 'creditcard', 'cardnumber', 'cvv', 'pin',
20
+ 'authorization', 'bearer'
21
+ ];
22
+
23
+ // Ensure error messages should explain what happened, why, and in what context
24
+ this.explanationPatterns = [
25
+ 'because', 'due to', 'failed to', 'cannot', 'invalid', 'missing', 'not found',
26
+ ];
27
+ this.guidancePatterns = [
28
+ 'please', 'ensure', 'make sure', 'check', 'try', 'use',
29
+ ];
30
+ }
31
+
32
+ async initialize(semanticEngine = null) {
33
+ if (semanticEngine) {
34
+ this.semanticEngine = semanticEngine;
35
+ }
36
+ this.verbose = semanticEngine?.verbose || false;
37
+
38
+ if (process.env.SUNLINT_DEBUG) {
39
+ console.log(`🔧 [C018 Symbol-Based] Analyzer initialized, verbose: ${this.verbose}`);
40
+ }
41
+ }
42
+
43
+ async analyzeFileBasic(filePath, options = {}) {
44
+ // This is the main entry point called by the hybrid analyzer
45
+ return await this.analyzeFileWithSymbols(filePath, options);
46
+ }
47
+
48
+ async analyzeFileWithSymbols(filePath, options = {}) {
49
+ const violations = [];
50
+
51
+ // Enable verbose mode if requested
52
+ const verbose = options.verbose || this.verbose;
53
+
54
+ if (!this.semanticEngine?.project) {
55
+ if (verbose) {
56
+ console.warn('[C018 Symbol-Based] No semantic engine available, skipping analysis');
57
+ }
58
+ return violations;
59
+ }
60
+
61
+ if (verbose) {
62
+ console.log(`🔍 [C018 Symbol-Based] Starting analysis for ${filePath}`);
63
+ }
64
+
65
+ try {
66
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
67
+ if (!sourceFile) {
68
+ return violations;
69
+ }
70
+
71
+ // Find all try-catch statements in the file
72
+ const tryCatchStatements = sourceFile.getDescendantsOfKind(SyntaxKind.TryStatement);
73
+
74
+ if (verbose) {
75
+ console.log(`🔍 [C018 Symbol-Based] Found ${tryCatchStatements.length} try-catch statements`);
76
+ }
77
+
78
+ for (const tryStatement of tryCatchStatements) {
79
+ const catchClause = tryStatement.getCatchClause();
80
+ if (catchClause) {
81
+ const catchViolations = this.analyzeCatchBlock(catchClause, sourceFile, filePath, verbose);
82
+ violations.push(...catchViolations);
83
+ }
84
+ }
85
+
86
+ if (verbose) {
87
+ console.log(`🔍 [C018 Symbol-Based] Total violations found: ${violations.length}`);
88
+ }
89
+
90
+ return violations;
91
+ } catch (error) {
92
+ if (verbose) {
93
+ console.warn(`[C018 Symbol-Based] Analysis failed for ${filePath}:`, error.message);
94
+ }
95
+ return violations;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Analyze catch block for logging context violations
101
+ */
102
+ analyzeCatchBlock(catchClause, sourceFile, filePath, verbose = false) {
103
+ const violations = [];
104
+
105
+ if (verbose) {
106
+ console.log(`🔍 [C018 Symbol-Based] Analyzing catch block in ${filePath}`);
107
+ }
108
+
109
+ // Get catch parameter (e, error, err, etc.)
110
+ const catchParameter = catchClause.getVariableDeclaration();
111
+ const errorVarName = catchParameter?.getName() || 'e';
112
+
113
+ if (verbose) {
114
+ console.log(`🔍 [C018 Symbol-Based] Error variable name: ${errorVarName}`);
115
+ }
116
+
117
+ // Find all log calls within catch block
118
+ const catchBlock = catchClause.getBlock();
119
+ const throwStatements = catchBlock.getDescendantsOfKind(SyntaxKind.ThrowStatement);
120
+
121
+ if (verbose) {
122
+ console.log(`🔍 [C018 Symbol-Based] Error variable name: ${errorVarName}`);
123
+ }
124
+
125
+ if (throwStatements.length === 0) {
126
+ // No logging found - but this is C029's concern, not C018
127
+ // We only analyze existing logs for quality
128
+ return violations;
129
+ }
130
+
131
+ // Analyze each log call for context quality
132
+ for (const throwStatement of throwStatements) {
133
+ if (verbose) {
134
+ console.log(`🔍 [C018 Symbol-Based] Analyzing throwStatement call: ${throwStatement.getText()}`);
135
+ }
136
+
137
+ const throwViolations = this.analyzeThrowCall(throwStatement, errorVarName, sourceFile, filePath, verbose);
138
+ violations.push(...throwViolations);
139
+ }
140
+
141
+ return violations;
142
+ }
143
+
144
+ /**
145
+ * Analyze individual log call for context quality
146
+ */
147
+ analyzeThrowCall(throwStatement, errorVarName, sourceFile, filePath, verbose = false) {
148
+ const violations = [];
149
+ const lineNumber = throwStatement.getStartLineNumber();
150
+ const columnNumber = throwStatement.getStart() - throwStatement.getStartLinePos();
151
+ const exp = throwStatement.getExpression();
152
+
153
+ if (!exp) {
154
+ return violations; // No arguments to analyze;
155
+ }
156
+
157
+ // Case: throw e (identifier)
158
+ if (exp.getKind() === SyntaxKind.Identifier) {
159
+ violations.push({
160
+ ruleId: this.ruleId,
161
+ severity: 'error',
162
+ message: 'Throwing caught error directly without context',
163
+ source: this.ruleId,
164
+ file: filePath,
165
+ line: lineNumber,
166
+ column: columnNumber,
167
+ description: `[SYMBOL-BASED] Caught error thrown directly without additional context. Use structured error objects.`,
168
+ suggestion: 'Use structured error objects with context instead of throwing caught errors directly',
169
+ category: 'error-handling'
170
+ });
171
+ }
172
+
173
+ const args = [];
174
+
175
+ // Case: throw new Error("...")
176
+ if (exp.getKind() === SyntaxKind.NewExpression) {
177
+ const newExp = exp.asKind(SyntaxKind.NewExpression);
178
+ const arg = newExp.getArguments().map(arg => arg);
179
+
180
+ args.push(...arg);
181
+ }
182
+
183
+ const analysis = this.analyzeThrowArguments(args, errorVarName, verbose);
184
+
185
+ // Analyze throw structure and content
186
+
187
+ // Check for violations
188
+ if (!analysis.isStructured) {
189
+ violations.push({
190
+ ruleId: this.ruleId,
191
+ severity: 'warning',
192
+ message: 'Error logging should use structured format (object) instead of string concatenation',
193
+ source: this.ruleId,
194
+ file: filePath,
195
+ line: lineNumber,
196
+ column: columnNumber,
197
+ description: `[SYMBOL-BASED] Non-structured logging detected. Use object format for better parsing and monitoring.`,
198
+ suggestion: 'Use logger.error("message", { error: e.message, context: {...} }) instead of string concatenation',
199
+ category: 'error-handling'
200
+ });
201
+ }
202
+
203
+ if (analysis.hasSensitiveData) {
204
+ violations.push({
205
+ ruleId: this.ruleId,
206
+ severity: 'error',
207
+ message: 'Error logging contains potentially sensitive data',
208
+ source: this.ruleId,
209
+ file: filePath,
210
+ line: lineNumber,
211
+ column: columnNumber,
212
+ description: `[SYMBOL-BASED] Sensitive patterns detected: ${analysis.sensitivePatterns.join(', ')}. Mask or exclude sensitive data.`,
213
+ suggestion: 'Mask sensitive data: password.substring(0,2) + "***" or exclude entirely',
214
+ category: 'security'
215
+ });
216
+ }
217
+
218
+ if (!analysis.hasExplanation) {
219
+ violations.push({
220
+ ruleId: this.ruleId,
221
+ severity: 'warning',
222
+ message: 'Error logging should explain what happened',
223
+ source: this.ruleId,
224
+ file: filePath,
225
+ line: lineNumber,
226
+ column: columnNumber,
227
+ description: `[SYMBOL-BASED] Error message should explain what happened, why, and in what context.`,
228
+ suggestion: 'Use structured error objects with context: { message: "Error occurred", context: "Request failed because todo something." } }',
229
+ category: 'error-handling'
230
+ });
231
+ }
232
+
233
+ if (!analysis.hasGuidance) {
234
+ violations.push({
235
+ ruleId: this.ruleId,
236
+ severity: 'warning',
237
+ message: 'Error logging should provide guidance on what to do next',
238
+ source: this.ruleId,
239
+ file: filePath,
240
+ line: lineNumber,
241
+ column: columnNumber,
242
+ description: `[SYMBOL-BASED] Error message should provide guidance on what to do next.`,
243
+ suggestion: 'Use structured error objects with guidance: { message: "Error occurred", guidance: "Please check the input data and try again." }',
244
+ category: 'error-handling'
245
+ });
246
+ }
247
+
248
+ return violations;
249
+ }
250
+
251
+ /**
252
+ * Analyze log arguments for structure, context, and sensitive data
253
+ */
254
+ analyzeThrowArguments(args, errorVarName, verbose = false) {
255
+ const analysis = {
256
+ isStructured: false,
257
+ hasSensitiveData: false,
258
+ hasExplanation: false,
259
+ hasGuidance: false,
260
+ sensitivePatterns: []
261
+ };
262
+
263
+ // Check if any argument is an object (structured logging)
264
+ for (const arg of args) {
265
+ if (arg.getKind() === SyntaxKind.ObjectLiteralExpression) {
266
+ analysis.isStructured = true;
267
+ analysis.hasExplanation = true; // Assume structured logs have explanations
268
+ analysis.hasGuidance = true; // Assume structured logs have guidance
269
+ break;
270
+ }
271
+ }
272
+
273
+ // If not structured, check for string concatenation patterns
274
+ if (!analysis.isStructured) {
275
+ for (const arg of args) {
276
+ const argText = arg.getText().toLowerCase();
277
+ this.validateForSensitiveDataInText(argText, analysis);
278
+ this.validateErrorMessage(argText, analysis);
279
+ }
280
+ }
281
+
282
+ return analysis;
283
+ }
284
+
285
+ /**
286
+ * Check text for sensitive data patterns
287
+ */
288
+ validateForSensitiveDataInText(text, analysis) {
289
+ for (const pattern of this.sensitivePatterns) {
290
+ if (text.includes(pattern)) {
291
+ analysis.hasSensitiveData = true;
292
+ analysis.sensitivePatterns.push(pattern);
293
+ }
294
+ }
295
+ }
296
+
297
+ validateErrorMessage(text, analysis) {
298
+ // Rule 1: Explanation
299
+ for (const patternE of this.explanationPatterns) {
300
+ if (text.includes(patternE)) {
301
+ analysis.hasExplanation = true;
302
+ }
303
+ }
304
+
305
+ // Rule 2: Guidance
306
+ for (const patternG of this.guidancePatterns) {
307
+ if (text.includes(patternG)) {
308
+ analysis.hasGuidance = true;
309
+ }
310
+ }
311
+ }
312
+ }
313
+
314
+ module.exports = C018SymbolBasedAnalyzer;