@sun-asterisk/sunlint 1.2.0 → 1.2.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 (77) hide show
  1. package/config/rule-analysis-strategies.js +18 -2
  2. package/engines/eslint-engine.js +9 -11
  3. package/engines/heuristic-engine.js +63 -31
  4. package/package.json +2 -1
  5. package/rules/README.md +252 -0
  6. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  7. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  8. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  9. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  10. package/rules/common/C006_function_naming/analyzer.js +504 -0
  11. package/rules/common/C006_function_naming/config.json +86 -0
  12. package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
  13. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  14. package/rules/common/C012_command_query_separation/analyzer.js +481 -0
  15. package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
  16. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  17. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  18. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  19. package/rules/common/C019_log_level_usage/analyzer.js +362 -0
  20. package/rules/common/C019_log_level_usage/config.json +121 -0
  21. package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
  22. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
  23. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
  24. package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
  25. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
  26. package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
  27. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
  28. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
  29. package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
  30. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
  31. package/rules/common/C029_catch_block_logging/config.json +59 -0
  32. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
  33. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
  34. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
  35. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
  36. package/rules/common/C031_validation_separation/analyzer.js +186 -0
  37. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  38. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
  39. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  40. package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
  41. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
  42. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  43. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  44. package/rules/docs/C002_no_duplicate_code.md +57 -0
  45. package/rules/docs/C031_validation_separation.md +72 -0
  46. package/rules/index.js +155 -0
  47. package/rules/migration/converter.js +385 -0
  48. package/rules/migration/mapping.json +164 -0
  49. package/rules/parser/constants.js +31 -0
  50. package/rules/parser/file-config.js +80 -0
  51. package/rules/parser/rule-parser-simple.js +305 -0
  52. package/rules/parser/rule-parser.js +527 -0
  53. package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
  54. package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
  55. package/rules/security/S023_no_json_injection/analyzer.js +278 -0
  56. package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
  57. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  58. package/rules/security/S026_json_schema_validation/config.json +27 -0
  59. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
  60. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  61. package/rules/security/S029_csrf_protection/analyzer.js +330 -0
  62. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  63. package/rules/universal/C010/generic.js +0 -0
  64. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  65. package/rules/utils/ast-utils.js +191 -0
  66. package/rules/utils/base-analyzer.js +98 -0
  67. package/rules/utils/pattern-matchers.js +239 -0
  68. package/rules/utils/rule-helpers.js +264 -0
  69. package/rules/utils/severity-constants.js +93 -0
  70. package/scripts/generate_insights.js +188 -0
  71. package/scripts/merge-reports.js +0 -424
  72. package/scripts/test-scripts/README.md +0 -22
  73. package/scripts/test-scripts/test-c041-comparison.js +0 -114
  74. package/scripts/test-scripts/test-c041-eslint.js +0 -67
  75. package/scripts/test-scripts/test-eslint-rules.js +0 -146
  76. package/scripts/test-scripts/test-real-world.js +0 -44
  77. package/scripts/test-scripts/test-rules-on-real-projects.js +0 -86
@@ -0,0 +1,362 @@
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');
6
+
7
+ class C019Analyzer {
8
+ constructor() {
9
+ this.ruleId = 'C019';
10
+ 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';
12
+ this.aiAnalyzer = null;
13
+ }
14
+
15
+ async analyze(files, language, config) {
16
+ const violations = [];
17
+
18
+ // Initialize AI analyzer if enabled
19
+ if (config.ai && config.ai.enabled) {
20
+ this.aiAnalyzer = new AIAnalyzer(config.ai);
21
+ console.log('🤖 AI analysis enabled for C019');
22
+ }
23
+
24
+ for (const filePath of files) {
25
+ try {
26
+ const fileContent = fs.readFileSync(filePath, 'utf8');
27
+ const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
28
+ violations.push(...fileViolations);
29
+ } catch (error) {
30
+ console.error(`Error analyzing file ${filePath}:`, error.message);
31
+ }
32
+ }
33
+
34
+ return violations;
35
+ }
36
+
37
+ async analyzeFile(filePath, content, language, config) {
38
+ let violations = [];
39
+
40
+ // Try AI analysis first if enabled
41
+ if (this.aiAnalyzer) {
42
+ try {
43
+ console.log(`🤖 Running AI analysis on ${path.basename(filePath)}`);
44
+ const aiViolations = await this.aiAnalyzer.analyzeWithAI(filePath, content, {
45
+ name: this.ruleName,
46
+ description: this.description,
47
+ ruleId: this.ruleId
48
+ });
49
+
50
+ if (aiViolations && aiViolations.length > 0) {
51
+ console.log(`🎯 AI found ${aiViolations.length} violations`);
52
+ return aiViolations;
53
+ }
54
+ } catch (error) {
55
+ console.warn('🤖 AI analysis failed, falling back to pattern analysis:', error.message);
56
+ }
57
+ }
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
+
358
+ return catchPatterns.some(pattern => callText.includes(pattern));
359
+ }
360
+ }
361
+
362
+ module.exports = new C019Analyzer();
@@ -0,0 +1,121 @@
1
+ {
2
+ "ruleId": "C019",
3
+ "name": "Log Level Usage",
4
+ "description": "Không sử dụng log mức error cho lỗi không nghiêm trọng",
5
+ "category": "logging",
6
+ "severity": "warning",
7
+ "languages": ["typescript", "dart", "kotlin"],
8
+ "version": "1.0.0",
9
+ "status": "stable",
10
+ "tags": ["logging", "error-handling", "severity"],
11
+ "config": {
12
+ "errorKeywords": [
13
+ "not found",
14
+ "invalid",
15
+ "unauthorized",
16
+ "forbidden",
17
+ "validation failed",
18
+ "bad request",
19
+ "cache miss",
20
+ "retry",
21
+ "fallback",
22
+ "user error",
23
+ "input error",
24
+ "validation",
25
+ "invalid input",
26
+ "missing parameter"
27
+ ],
28
+ "legitimateErrorKeywords": [
29
+ "exception",
30
+ "crash",
31
+ "fatal",
32
+ "critical",
33
+ "emergency",
34
+ "database",
35
+ "connection",
36
+ "timeout",
37
+ "security breach",
38
+ "system error",
39
+ "memory",
40
+ "disk space",
41
+ "internal server error",
42
+ "unhandled exception",
43
+ "stack overflow"
44
+ ],
45
+ "languageSpecific": {
46
+ "typescript": {
47
+ "loggerPatterns": [
48
+ "console.error(",
49
+ "logger.error(",
50
+ "log.error(",
51
+ ".error(",
52
+ "Logger.error(",
53
+ "winston.error(",
54
+ "bunyan.error("
55
+ ]
56
+ },
57
+ "dart": {
58
+ "loggerPatterns": [
59
+ "log.error(",
60
+ "logger.error(",
61
+ "Logger.error(",
62
+ "_logger.error(",
63
+ "print("
64
+ ]
65
+ },
66
+ "kotlin": {
67
+ "loggerPatterns": [
68
+ "Log.e(",
69
+ "logger.error(",
70
+ "log.error(",
71
+ "Timber.e("
72
+ ]
73
+ }
74
+ }
75
+ },
76
+ "examples": {
77
+ "violations": [
78
+ {
79
+ "language": "typescript",
80
+ "code": "logger.error('User not found');",
81
+ "reason": "User not found is a business logic issue, not a system error"
82
+ },
83
+ {
84
+ "language": "typescript",
85
+ "code": "console.error('Invalid input provided');",
86
+ "reason": "Input validation should use warn level"
87
+ },
88
+ {
89
+ "language": "dart",
90
+ "code": "logger.error('Validation failed for user input');",
91
+ "reason": "Validation failures are expected business logic"
92
+ }
93
+ ],
94
+ "valid": [
95
+ {
96
+ "language": "typescript",
97
+ "code": "logger.error('Database connection failed');",
98
+ "reason": "Database issues are legitimate system errors"
99
+ },
100
+ {
101
+ "language": "typescript",
102
+ "code": "logger.warn('User not found');",
103
+ "reason": "Appropriate warning level for business logic"
104
+ },
105
+ {
106
+ "language": "dart",
107
+ "code": "logger.error('Unhandled exception in payment processing');",
108
+ "reason": "Unhandled exceptions are legitimate errors"
109
+ }
110
+ ]
111
+ },
112
+ "fixes": {
113
+ "autoFixable": false,
114
+ "suggestions": [
115
+ "Use logger.warn() for business logic issues",
116
+ "Use logger.info() for informational messages",
117
+ "Reserve logger.error() for system-level problems",
118
+ "Add specific error context to help determine appropriate level"
119
+ ]
120
+ }
121
+ }