@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
@@ -1,37 +1,125 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const ts = require('typescript');
4
- const { PatternMatcher } = require('../../utils/pattern-matchers');
5
- const { RuleHelper } = require('../../utils/rule-helpers');
1
+ const C019SystemLogAnalyzer = require('./system-log-analyzer.js');
2
+ const C019PatternAnalyzer = require('./pattern-analyzer.js');
6
3
 
7
4
  class C019Analyzer {
8
- constructor() {
5
+ constructor(semanticEngine = null) {
9
6
  this.ruleId = 'C019';
10
7
  this.ruleName = 'Log Level Usage';
11
- this.description = 'Không sử dụng log mức error cho lỗi không nghiêm trọng';
8
+ this.description = 'Comprehensive logging analysis: levels, patterns, performance, and system requirements';
9
+ this.semanticEngine = semanticEngine;
10
+ this.verbose = false;
11
+
12
+ // Initialize analyzers - consolidated architecture
13
+ this.systemAnalyzer = new C019SystemLogAnalyzer(semanticEngine);
14
+ this.patternAnalyzer = new C019PatternAnalyzer();
12
15
  this.aiAnalyzer = null;
13
16
  }
14
17
 
15
- async analyze(files, language, config) {
16
- const violations = [];
18
+ async initialize(semanticEngine = null) {
19
+ if (semanticEngine) {
20
+ this.semanticEngine = semanticEngine;
21
+ }
22
+ this.verbose = semanticEngine?.verbose || false;
23
+
24
+ await this.systemAnalyzer.initialize(semanticEngine);
25
+ await this.patternAnalyzer.initialize({ verbose: this.verbose });
26
+ }
27
+
28
+ async analyzeFileBasic(filePath, options = {}) {
29
+ const allViolations = [];
30
+
31
+ try {
32
+ // Run comprehensive system-level analysis (Primary - AST)
33
+ if (this.semanticEngine?.isSymbolEngineReady?.() && this.semanticEngine.project) {
34
+ if (this.verbose) {
35
+ console.log(`[DEBUG] 🎯 C019: Using comprehensive system-level analysis for ${filePath.split('/').pop()}`);
36
+ }
37
+
38
+ try {
39
+ const systemViolations = await this.systemAnalyzer.analyzeFileBasic(filePath, options);
40
+ allViolations.push(...systemViolations);
41
+
42
+ if (this.verbose) {
43
+ console.log(`[DEBUG] 🎯 C019: System analysis found ${systemViolations.length} violations`);
44
+ }
45
+ } catch (systemError) {
46
+ if (this.verbose) {
47
+ console.warn(`[DEBUG] ⚠️ C019: System analysis failed: ${systemError.message}`);
48
+ }
49
+ }
50
+
51
+ if (allViolations.length > 0) {
52
+ return this.deduplicateViolations(allViolations);
53
+ }
54
+ }
55
+
56
+ // Fall back to pattern-based analysis (Secondary - Regex)
57
+ if (this.verbose) {
58
+ console.log(`[DEBUG] 🔄 C019: Running pattern-based analysis for ${filePath.split('/').pop()}`);
59
+ }
60
+
61
+ const patternViolations = await this.patternAnalyzer.analyzeFileBasic(filePath, options);
62
+ allViolations.push(...patternViolations);
63
+
64
+ if (this.verbose) {
65
+ console.log(`[DEBUG] 🔄 C019: Pattern analysis found ${patternViolations.length} violations`);
66
+ }
67
+
68
+ return this.deduplicateViolations(allViolations);
69
+
70
+ } catch (error) {
71
+ if (this.verbose) {
72
+ console.error(`[DEBUG] ❌ C019: Analysis failed: ${error.message}`);
73
+ }
74
+ throw new Error(`C019 analysis failed: ${error.message}`);
75
+ }
76
+ }
77
+
78
+ deduplicateViolations(violations) {
79
+ // Remove duplicates based on file, line, and type
80
+ const seen = new Set();
81
+ return violations.filter(violation => {
82
+ const key = `${violation.filePath}:${violation.line}:${violation.type}`;
83
+ if (seen.has(key)) return false;
84
+ seen.add(key);
85
+ return true;
86
+ });
87
+ }
17
88
 
89
+ async analyzeFiles(files, options = {}) {
90
+ const allViolations = [];
91
+ for (const filePath of files) {
92
+ try {
93
+ const violations = await this.analyzeFileBasic(filePath, options);
94
+ allViolations.push(...violations);
95
+ } catch (error) {
96
+ console.warn(`C019: Skipping ${filePath}: ${error.message}`);
97
+ }
98
+ }
99
+ return allViolations;
100
+ }
101
+
102
+ // Legacy method for backward compatibility
103
+ async analyze(files, language, config = {}) {
18
104
  // Initialize AI analyzer if enabled
19
105
  if (config.ai && config.ai.enabled) {
20
106
  this.aiAnalyzer = new AIAnalyzer(config.ai);
21
107
  console.log('🤖 AI analysis enabled for C019');
22
108
  }
23
109
 
110
+ const allViolations = [];
111
+
24
112
  for (const filePath of files) {
25
113
  try {
26
- const fileContent = fs.readFileSync(filePath, 'utf8');
114
+ const fileContent = require('fs').readFileSync(filePath, 'utf8');
27
115
  const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
28
- violations.push(...fileViolations);
116
+ allViolations.push(...fileViolations);
29
117
  } catch (error) {
30
118
  console.error(`Error analyzing file ${filePath}:`, error.message);
31
119
  }
32
120
  }
33
121
 
34
- return violations;
122
+ return allViolations;
35
123
  }
36
124
 
37
125
  async analyzeFile(filePath, content, language, config) {
@@ -40,7 +128,7 @@ class C019Analyzer {
40
128
  // Try AI analysis first if enabled
41
129
  if (this.aiAnalyzer) {
42
130
  try {
43
- console.log(`🤖 Running AI analysis on ${path.basename(filePath)}`);
131
+ console.log(`🤖 Running AI analysis on ${require('path').basename(filePath)}`);
44
132
  const aiViolations = await this.aiAnalyzer.analyzeWithAI(filePath, content, {
45
133
  name: this.ruleName,
46
134
  description: this.description,
@@ -48,315 +136,20 @@ class C019Analyzer {
48
136
  });
49
137
 
50
138
  if (aiViolations && aiViolations.length > 0) {
51
- console.log(`🎯 AI found ${aiViolations.length} violations`);
52
- return aiViolations;
139
+ violations.push(...aiViolations);
140
+ return violations;
53
141
  }
54
142
  } catch (error) {
55
- console.warn('🤖 AI analysis failed, falling back to pattern analysis:', error.message);
143
+ console.warn(`AI analysis failed for ${filePath}, falling back to heuristic analysis`);
56
144
  }
57
145
  }
58
-
59
- // Fallback to pattern-based analysis
60
- if (config.verbose) {
61
- console.log(`🔍 Running pattern analysis on ${path.basename(filePath)}`);
62
- }
63
- switch (language) {
64
- case 'typescript':
65
- case 'javascript':
66
- return this.analyzeTypeScript(filePath, content, config);
67
- case 'dart':
68
- return this.analyzeDart(filePath, content, config);
69
- case 'kotlin':
70
- return this.analyzeKotlin(filePath, content, config);
71
- default:
72
- return [];
73
- }
74
- }
75
-
76
- async analyzeTypeScript(filePath, content, config) {
77
- const violations = [];
78
- const lines = content.split('\n');
79
-
80
- // Parse TypeScript/JavaScript code
81
- const sourceFile = ts.createSourceFile(
82
- filePath,
83
- content,
84
- ts.ScriptTarget.Latest,
85
- true
86
- );
87
-
88
- const visit = (node) => {
89
- // Look for method calls that might be logging
90
- if (ts.isCallExpression(node)) {
91
- const callText = node.getText(sourceFile);
92
-
93
- // Check if this is an error-level log call
94
- const isErrorLog = this.isErrorLogCall(callText);
95
-
96
- if (isErrorLog) {
97
- const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
98
- const column = sourceFile.getLineAndCharacterOfPosition(node.getStart()).character + 1;
99
- const lineText = lines[line - 1]?.trim() || '';
100
-
101
- // Analyze the log message for inappropriate error usage
102
- const logMessage = this.extractLogMessage(node, sourceFile);
103
- const analysis = this.analyzeLogMessage(logMessage, callText);
104
-
105
- if (analysis.isViolation) {
106
- violations.push({
107
- ruleId: this.ruleId,
108
- file: filePath,
109
- line,
110
- column,
111
- message: analysis.reason,
112
- severity: analysis.severity || 'warning',
113
- code: lineText,
114
- type: analysis.type,
115
- confidence: analysis.confidence || 0.8,
116
- suggestion: analysis.suggestion
117
- });
118
- }
119
- }
120
- }
121
-
122
- ts.forEachChild(node, visit);
123
- };
124
-
125
- visit(sourceFile);
126
- return violations;
127
- }
128
-
129
- async analyzeDart(filePath, content, config) {
130
- const violations = [];
131
- const lines = content.split('\n');
132
-
133
- // Pattern-based analysis for Dart
134
- const errorLogPatterns = [
135
- /log\.error\(/g,
136
- /logger\.error\(/g,
137
- /Logger\.error\(/g,
138
- /print\(.*error.*\)/gi,
139
- /_logger\.error\(/g
140
- ];
141
-
142
- lines.forEach((line, index) => {
143
- const lineNumber = index + 1;
144
- const trimmedLine = line.trim();
145
-
146
- errorLogPatterns.forEach(pattern => {
147
- const matches = trimmedLine.match(pattern);
148
- if (matches) {
149
- const analysis = this.analyzeLogMessage(trimmedLine, trimmedLine);
150
-
151
- if (analysis.isViolation) {
152
- violations.push({
153
- ruleId: this.ruleId,
154
- file: filePath,
155
- line: lineNumber,
156
- column: line.indexOf(matches[0]) + 1,
157
- message: analysis.reason,
158
- severity: analysis.severity || 'warning',
159
- code: trimmedLine,
160
- type: analysis.type,
161
- confidence: analysis.confidence || 0.7,
162
- suggestion: analysis.suggestion
163
- });
164
- }
165
- }
166
- });
167
- });
168
-
169
- return violations;
170
- }
171
-
172
- async analyzeKotlin(filePath, content, config) {
173
- const violations = [];
174
- const lines = content.split('\n');
175
-
176
- // Pattern-based analysis for Kotlin
177
- const errorLogPatterns = [
178
- /Log\.e\(/g,
179
- /logger\.error\(/g,
180
- /log\.error\(/g,
181
- /Timber\.e\(/g
182
- ];
183
-
184
- lines.forEach((line, index) => {
185
- const lineNumber = index + 1;
186
- const trimmedLine = line.trim();
187
-
188
- errorLogPatterns.forEach(pattern => {
189
- const matches = trimmedLine.match(pattern);
190
- if (matches) {
191
- const analysis = this.analyzeLogMessage(trimmedLine, trimmedLine);
192
-
193
- if (analysis.isViolation) {
194
- violations.push({
195
- ruleId: this.ruleId,
196
- file: filePath,
197
- line: lineNumber,
198
- column: line.indexOf(matches[0]) + 1,
199
- message: analysis.reason,
200
- severity: analysis.severity || 'warning',
201
- code: trimmedLine,
202
- type: analysis.type,
203
- confidence: analysis.confidence || 0.7,
204
- suggestion: analysis.suggestion
205
- });
206
- }
207
- }
208
- });
209
- });
210
-
211
- return violations;
212
- }
213
-
214
- isErrorLogCall(callText) {
215
- const errorLogPatterns = [
216
- 'console.error(',
217
- 'logger.error(',
218
- 'log.error(',
219
- '.error(',
220
- 'Logger.error(',
221
- 'winston.error(',
222
- 'bunyan.error('
223
- ];
224
-
225
- return errorLogPatterns.some(pattern => callText.includes(pattern));
226
- }
227
-
228
- extractLogMessage(callNode, sourceFile) {
229
- // Try to extract the log message from the call expression
230
- if (callNode.arguments && callNode.arguments.length > 0) {
231
- const firstArg = callNode.arguments[0];
232
- if (ts.isStringLiteral(firstArg) || ts.isTemplateExpression(firstArg)) {
233
- return firstArg.getText(sourceFile).replace(/['"`]/g, '');
234
- }
235
- }
236
- return '';
237
- }
238
-
239
- analyzeLogMessage(message, fullCall) {
240
- const config = {
241
- errorKeywords: [
242
- 'not found', 'invalid', 'unauthorized', 'forbidden',
243
- 'validation failed', 'bad request', 'cache miss',
244
- 'retry', 'fallback', 'user error', 'input error',
245
- 'validation', 'invalid input', 'missing parameter'
246
- ],
247
- legitimateErrorKeywords: [
248
- 'exception', 'crash', 'fatal', 'critical', 'emergency',
249
- 'database', 'connection', 'timeout', 'security breach',
250
- 'system error', 'memory', 'disk space', 'internal server error',
251
- 'unhandled exception', 'stack overflow'
252
- ]
253
- };
254
-
255
- const lowerMessage = message.toLowerCase();
256
- const lowerCall = fullCall.toLowerCase();
257
-
258
- // Skip if this is in a catch block (legitimate error logging)
259
- const isCatchBlockContext = this.isCatchBlockContext(fullCall);
260
- if (isCatchBlockContext) {
261
- return { isViolation: false };
262
- }
263
-
264
- // Check for legitimate error usage
265
- const hasLegitimateError = config.legitimateErrorKeywords.some(keyword =>
266
- lowerMessage.includes(keyword) || lowerCall.includes(keyword)
267
- );
268
-
269
- if (hasLegitimateError) {
270
- return { isViolation: false };
271
- }
272
-
273
- // Check for inappropriate error usage
274
- const hasInappropriateError = config.errorKeywords.some(keyword =>
275
- lowerMessage.includes(keyword)
276
- );
277
-
278
- if (hasInappropriateError) {
279
- return {
280
- isViolation: true,
281
- reason: 'Error log level used for non-critical issue - should use warn/info level',
282
- severity: 'warning',
283
- type: 'inappropriate_error_level',
284
- confidence: 0.9,
285
- suggestion: 'Consider using logger.warn() or logger.info() instead'
286
- };
287
- }
288
-
289
- // Validation patterns
290
- if (lowerMessage.includes('validation') || lowerMessage.includes('invalid')) {
291
- return {
292
- isViolation: true,
293
- reason: 'Validation errors should typically use warn level',
294
- severity: 'warning',
295
- type: 'validation_error_level',
296
- confidence: 0.85,
297
- suggestion: 'Use logger.warn() for validation failures'
298
- };
299
- }
300
-
301
- // User input patterns
302
- if (lowerMessage.includes('user') && (lowerMessage.includes('input') || lowerMessage.includes('request'))) {
303
- return {
304
- isViolation: true,
305
- reason: 'User input errors should not use error level',
306
- severity: 'warning',
307
- type: 'user_input_error_level',
308
- confidence: 0.8,
309
- suggestion: 'Use logger.warn() or logger.info() for user input issues'
310
- };
311
- }
312
-
313
- // Authorization patterns
314
- if (lowerMessage.includes('unauthorized') || lowerMessage.includes('forbidden')) {
315
- return {
316
- isViolation: true,
317
- reason: 'Authorization failures are typically business logic, not system errors',
318
- severity: 'info',
319
- type: 'auth_error_level',
320
- confidence: 0.7,
321
- suggestion: 'Consider logger.warn() for authorization failures'
322
- };
323
- }
324
-
325
- // Generic error call without clear context
326
- if (message === '' || message.length < 10) {
327
- // Don't flag if it's clearly a system error with error object concatenation
328
- if (fullCall.includes('+ error') || fullCall.includes('${error}') ||
329
- fullCall.includes('+ e') || fullCall.includes('${e}') ||
330
- fullCall.includes('error:') || fullCall.includes('failed:')) {
331
- return { isViolation: false };
332
- }
333
-
334
- return {
335
- isViolation: true,
336
- reason: 'Generic error log without specific message - might be inappropriate',
337
- severity: 'info',
338
- type: 'generic_error_level',
339
- confidence: 0.6,
340
- suggestion: 'Add specific error message and verify if error level is appropriate'
341
- };
342
- }
343
-
344
- return { isViolation: false };
345
- }
346
-
347
- isCatchBlockContext(callText) {
348
- // Simple heuristic: if the call contains error/e parameters common in catch blocks
349
- const catchPatterns = [
350
- 'console.error(error)',
351
- 'console.error(e)',
352
- 'logger.error(error)',
353
- 'logger.error(e)',
354
- 'console.error("', // followed by message and error
355
- 'logger.error("'
356
- ];
357
146
 
358
- return catchPatterns.some(pattern => callText.includes(pattern));
147
+ // Use the new analyzer architecture
148
+ const heuristicViolations = await this.analyzeFileBasic(filePath, config);
149
+ violations.push(...heuristicViolations);
150
+
151
+ return violations;
359
152
  }
360
153
  }
361
154
 
362
- module.exports = new C019Analyzer();
155
+ module.exports = C019Analyzer;
@@ -0,0 +1,88 @@
1
+ // Pattern-based fallback analyzer for C019
2
+ const fs = require('fs');
3
+
4
+ class C019PatternAnalyzer {
5
+ constructor() {
6
+ this.verbose = false;
7
+
8
+ // Configuration for pattern matching
9
+ this.config = {
10
+ // Patterns that suggest inappropriate ERROR usage
11
+ inappropriateErrorPatterns: [
12
+ // Business logic rejections
13
+ 'validation.*(?:failed|error)', 'invalid.*(?:input|parameter|format)',
14
+ 'user.*(?:not.*found|unauthorized|forbidden)', 'permission.*denied',
15
+ 'authentication.*(?:failed|invalid)', 'authorization.*failed',
16
+
17
+ // Client-side errors
18
+ 'bad.*request', 'missing.*(?:parameter|field)', 'invalid.*request',
19
+ 'malformed.*(?:json|xml|payload)', 'unsupported.*(?:format|type)',
20
+
21
+ // Recoverable operations
22
+ 'retry.*(?:attempt|failed)', 'fallback.*(?:triggered|used)',
23
+ 'cache.*(?:miss|expired)', 'rate.*limit.*exceeded',
24
+
25
+ // Expected business flows
26
+ 'user.*not.*found', 'resource.*not.*found', 'item.*not.*available',
27
+ 'quota.*exceeded', 'limit.*reached', 'threshold.*exceeded'
28
+ ]
29
+ };
30
+ }
31
+
32
+ async initialize(options = {}) {
33
+ this.verbose = options.verbose || false;
34
+ }
35
+
36
+ async analyzeFileBasic(filePath, options = {}) {
37
+ const violations = [];
38
+
39
+ try {
40
+ const content = fs.readFileSync(filePath, 'utf8');
41
+ const lines = content.split('\n');
42
+
43
+ if (this.verbose) {
44
+ console.log(`[DEBUG] 🔍 C019: Pattern-based analysis of ${filePath.split('/').pop()}`);
45
+ }
46
+
47
+ for (let i = 0; i < lines.length; i++) {
48
+ const line = lines[i];
49
+ const lineNumber = i + 1;
50
+
51
+ // Simple pattern matching for error logs
52
+ const errorLogMatch = line.match(/(console|logger|log|winston|bunyan)\.error\(/i);
53
+ if (errorLogMatch) {
54
+ // Check for inappropriate patterns in the line
55
+ const hasInappropriatePattern = this.config.inappropriateErrorPatterns.some(pattern =>
56
+ new RegExp(pattern, 'i').test(line)
57
+ );
58
+
59
+ if (hasInappropriatePattern) {
60
+ violations.push({
61
+ ruleId: 'C019',
62
+ message: 'Log level "error" may be inappropriate for this context. Consider using "warn" or "info".',
63
+ filePath: filePath,
64
+ line: lineNumber,
65
+ column: errorLogMatch.index + 1,
66
+ severity: 'warning',
67
+ category: 'logging',
68
+ confidence: 0.5,
69
+ suggestion: 'Review the error context and use appropriate log level (warn/info for expected errors)'
70
+ });
71
+ }
72
+ }
73
+ }
74
+
75
+ if (this.verbose) {
76
+ console.log(`[DEBUG] 🔍 C019: Pattern analysis found ${violations.length} violations`);
77
+ }
78
+ } catch (error) {
79
+ if (this.verbose) {
80
+ console.error(`[DEBUG] ❌ C019: Pattern analysis error: ${error.message}`);
81
+ }
82
+ }
83
+
84
+ return violations;
85
+ }
86
+ }
87
+
88
+ module.exports = C019PatternAnalyzer;