@sun-asterisk/sunlint 1.2.1 → 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 +55 -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,431 @@
1
+ /**
2
+ * AST-based C043 Analyzer
3
+ * Mirrors ESLint's sophisticated implementation for perfect accuracy
4
+ * Leverages SunLint's existing AST infrastructure
5
+ */
6
+
7
+ const astRegistry = require('../../../core/ast-modules');
8
+ const fs = require('fs');
9
+
10
+ class C043NoConsoleOrPrintAnalyzer {
11
+ constructor() {
12
+ this.ruleId = 'C043';
13
+ this.ruleName = 'No Console Or Print';
14
+ this.description = 'Do not use console.log or print in production code';
15
+ this.severity = 'warning';
16
+ this.astRegistry = astRegistry;
17
+
18
+ // Configuration mirroring ESLint's C043 rule
19
+ this.config = {
20
+ allowedMethods: new Set(['error', 'warn']),
21
+ allowInDevelopment: true,
22
+ allowInTests: true,
23
+
24
+ testFilePatterns: [
25
+ '.test.', '.spec.', '__tests__', '/test/', '/tests/',
26
+ '.test.ts', '.test.js', '.spec.ts', '.spec.js',
27
+ 'test.tsx', 'spec.tsx',
28
+ '.stories.', '.story.' // Include stories files like ESLint
29
+ ],
30
+
31
+ developmentPatterns: [
32
+ '.dev.', '.development.', '.debug.', '/dev/', '/development/'
33
+ ],
34
+
35
+ developmentFlags: new Set([
36
+ '__DEV__', 'DEBUG', 'process.env.NODE_ENV', 'process.env.ENVIRONMENT',
37
+ 'process.env.ENABLE_LOGGING', 'FEATURES.debug', 'BUILD_TYPE'
38
+ ]),
39
+
40
+ consoleMethods: new Set([
41
+ 'log', 'info', 'debug', 'trace', 'dir', 'dirxml', 'table',
42
+ 'count', 'countReset', 'time', 'timeEnd', 'timeLog',
43
+ 'assert', 'clear', 'group', 'groupCollapsed', 'groupEnd',
44
+ 'profile', 'profileEnd', 'timeStamp'
45
+ ]),
46
+
47
+ forbiddenFunctions: ['print', 'alert', 'confirm', 'prompt']
48
+ };
49
+ }
50
+
51
+ async analyze(files, language, config) {
52
+ const violations = [];
53
+
54
+ // Batch processing to avoid memory issues
55
+ const batchSize = 100;
56
+ const totalFiles = files.length;
57
+
58
+ for (let i = 0; i < totalFiles; i += batchSize) {
59
+ const batch = files.slice(i, i + batchSize);
60
+
61
+ for (const filePath of batch) {
62
+ try {
63
+ // Skip test files if configured
64
+ if (this.config.allowInTests && this.isTestFile(filePath)) {
65
+ continue;
66
+ }
67
+
68
+ // Skip development files if configured
69
+ if (this.config.allowInDevelopment && this.isDevelopmentFile(filePath)) {
70
+ continue;
71
+ }
72
+
73
+ const fileContent = fs.readFileSync(filePath, 'utf8');
74
+
75
+ // Skip empty files or very large files (>1MB)
76
+ if (!fileContent.trim() || fileContent.length > 1024 * 1024) {
77
+ continue;
78
+ }
79
+
80
+ const fileLanguage = this.getLanguageFromPath(filePath);
81
+
82
+ // Use regex-based analysis for now (AST can be added later)
83
+ // AST parsing can be slow on large files, so use fast regex approach
84
+ const regexViolations = await this.analyzeFileWithRegex(filePath, fileContent, language, config);
85
+ violations.push(...regexViolations);
86
+
87
+ } catch (error) {
88
+ // Skip problematic files silently to avoid stopping entire analysis
89
+ console.warn(`C043 skipping ${filePath}: ${error.message}`);
90
+ }
91
+ }
92
+
93
+ // Give Node.js a chance to breathe between batches
94
+ if (i + batchSize < totalFiles) {
95
+ await new Promise(resolve => setImmediate(resolve));
96
+ }
97
+ }
98
+
99
+ return violations;
100
+ }
101
+
102
+ getLanguageFromPath(filePath) {
103
+ const ext = filePath.split('.').pop().toLowerCase();
104
+
105
+ const languageMap = {
106
+ 'js': 'javascript',
107
+ 'jsx': 'javascript',
108
+ 'ts': 'typescript',
109
+ 'tsx': 'typescript',
110
+ 'mjs': 'javascript',
111
+ 'cjs': 'javascript'
112
+ };
113
+
114
+ return languageMap[ext] || 'javascript';
115
+ }
116
+
117
+ analyzeAST(ast, filePath, fileContent) {
118
+ const violations = [];
119
+ const lines = fileContent.split('\n');
120
+
121
+ // Define visitor for AST traversal
122
+ const visitor = {
123
+ CallExpression: (node) => {
124
+ // Check for console method calls
125
+ if (this.isConsoleCall(node)) {
126
+ const methodName = this.getConsoleMethodName(node);
127
+
128
+ // Skip allowed methods (error, warn)
129
+ if (this.config.allowedMethods.has(methodName)) {
130
+ return;
131
+ }
132
+
133
+ // Check if in development context
134
+ if (this.config.allowInDevelopment && this.isInDevelopmentContext(node, ast)) {
135
+ return;
136
+ }
137
+
138
+ // Create violation
139
+ const location = this.getNodeLocation(node);
140
+ if (location && location.line <= lines.length) {
141
+ violations.push({
142
+ ruleId: this.ruleId,
143
+ severity: this.severity,
144
+ message: `Do not use console.${methodName}() in production code. Use proper logging instead.`,
145
+ filePath: filePath,
146
+ line: location.line,
147
+ column: location.column,
148
+ source: lines[location.line - 1]?.trim() || '',
149
+ suggestion: `Consider using a proper logging library (logger.${methodName}())`
150
+ });
151
+ }
152
+ }
153
+
154
+ // Check for forbidden functions (print, alert, etc.)
155
+ if (this.isForbiddenFunctionCall(node)) {
156
+ const functionName = this.getFunctionName(node);
157
+ const location = this.getNodeLocation(node);
158
+
159
+ if (location && location.line <= lines.length) {
160
+ violations.push({
161
+ ruleId: this.ruleId,
162
+ severity: this.severity,
163
+ message: `Do not use ${functionName}() in production code. Use proper logging or UI notifications instead.`,
164
+ filePath: filePath,
165
+ line: location.line,
166
+ column: location.column,
167
+ source: lines[location.line - 1]?.trim() || '',
168
+ suggestion: `Consider using a logging library or proper UI notification system`
169
+ });
170
+ }
171
+ }
172
+ }
173
+ };
174
+
175
+ // Traverse AST
176
+ this.traverseAST(ast, visitor);
177
+
178
+ return violations;
179
+ }
180
+
181
+ // AST Helper Methods
182
+ isConsoleCall(node) {
183
+ return node.type === 'CallExpression' &&
184
+ node.callee &&
185
+ node.callee.type === 'MemberExpression' &&
186
+ node.callee.object &&
187
+ node.callee.object.type === 'Identifier' &&
188
+ node.callee.object.name === 'console' &&
189
+ node.callee.property &&
190
+ this.config.consoleMethods.has(node.callee.property.name);
191
+ }
192
+
193
+ getConsoleMethodName(node) {
194
+ if (node.callee && node.callee.property) {
195
+ return node.callee.property.name;
196
+ }
197
+ return 'log';
198
+ }
199
+
200
+ isForbiddenFunctionCall(node) {
201
+ return node.type === 'CallExpression' &&
202
+ node.callee &&
203
+ node.callee.type === 'Identifier' &&
204
+ this.config.forbiddenFunctions.includes(node.callee.name);
205
+ }
206
+
207
+ getFunctionName(node) {
208
+ if (node.callee && node.callee.name) {
209
+ return node.callee.name;
210
+ }
211
+ return 'unknown';
212
+ }
213
+
214
+ isInDevelopmentContext(node, ast) {
215
+ // Check if node is within an if statement checking development flags
216
+ let parent = node.parent;
217
+
218
+ while (parent) {
219
+ if (parent.type === 'IfStatement') {
220
+ const test = parent.test;
221
+ if (this.isDevelopmentCondition(test)) {
222
+ return true;
223
+ }
224
+ }
225
+ parent = parent.parent;
226
+ }
227
+
228
+ return false;
229
+ }
230
+
231
+ isDevelopmentCondition(node) {
232
+ if (!node) return false;
233
+
234
+ // Check for various development condition patterns
235
+ if (node.type === 'Identifier' && this.config.developmentFlags.has(node.name)) {
236
+ return true;
237
+ }
238
+
239
+ if (node.type === 'MemberExpression') {
240
+ const source = this.nodeToString(node);
241
+ return Array.from(this.config.developmentFlags).some(flag => source.includes(flag));
242
+ }
243
+
244
+ if (node.type === 'BinaryExpression') {
245
+ return this.isDevelopmentCondition(node.left) || this.isDevelopmentCondition(node.right);
246
+ }
247
+
248
+ return false;
249
+ }
250
+
251
+ nodeToString(node) {
252
+ // Simple node to string conversion for pattern matching
253
+ if (node.type === 'Identifier') {
254
+ return node.name;
255
+ }
256
+ if (node.type === 'MemberExpression') {
257
+ return `${this.nodeToString(node.object)}.${node.property.name}`;
258
+ }
259
+ if (node.type === 'Literal') {
260
+ return String(node.value);
261
+ }
262
+ return '';
263
+ }
264
+
265
+ getNodeLocation(node) {
266
+ if (node.loc) {
267
+ return {
268
+ line: node.loc.start.line,
269
+ column: node.loc.start.column + 1
270
+ };
271
+ }
272
+ return null;
273
+ }
274
+
275
+ traverseAST(node, visitor) {
276
+ if (!node || typeof node !== 'object') return;
277
+
278
+ // Prevent infinite recursion
279
+ if (node._visited) return;
280
+ node._visited = true;
281
+
282
+ try {
283
+ // Visit current node
284
+ if (visitor[node.type]) {
285
+ visitor[node.type](node);
286
+ }
287
+
288
+ // Traverse children with depth limit
289
+ const maxDepth = 100;
290
+ if ((node._depth || 0) > maxDepth) return;
291
+
292
+ for (const key in node) {
293
+ if (key === 'parent' || key === '_visited' || key === '_depth') continue; // Avoid circular references
294
+
295
+ const child = node[key];
296
+ if (Array.isArray(child)) {
297
+ for (const item of child) {
298
+ if (item && typeof item === 'object') {
299
+ item.parent = node; // Set parent reference
300
+ item._depth = (node._depth || 0) + 1;
301
+ this.traverseAST(item, visitor);
302
+ }
303
+ }
304
+ } else if (child && typeof child === 'object') {
305
+ child.parent = node; // Set parent reference
306
+ child._depth = (node._depth || 0) + 1;
307
+ this.traverseAST(child, visitor);
308
+ }
309
+ }
310
+ } finally {
311
+ // Clean up to prevent memory leaks
312
+ delete node._visited;
313
+ }
314
+ }
315
+
316
+ // File Classification Methods
317
+ isTestFile(filePath) {
318
+ return this.config.testFilePatterns.some(pattern =>
319
+ filePath.includes(pattern)
320
+ );
321
+ }
322
+
323
+ isDevelopmentFile(filePath) {
324
+ return this.config.developmentPatterns.some(pattern =>
325
+ filePath.includes(pattern)
326
+ );
327
+ }
328
+
329
+ // Regex Fallback Methods (for compatibility)
330
+ async analyzeWithRegexFallback(files, language, config) {
331
+ const violations = [];
332
+
333
+ for (const filePath of files) {
334
+ try {
335
+ const fileContent = fs.readFileSync(filePath, 'utf8');
336
+ const fileViolations = await this.analyzeFileWithRegex(filePath, fileContent, language, config);
337
+ violations.push(...fileViolations);
338
+ } catch (error) {
339
+ console.warn(`C043 regex fallback analysis error for ${filePath}:`, error.message);
340
+ }
341
+ }
342
+
343
+ return violations;
344
+ }
345
+
346
+ async analyzeFileWithRegex(filePath, fileContent, language, config) {
347
+ const violations = [];
348
+
349
+ // Skip test files and development files
350
+ if (this.isTestFile(filePath) || this.isDevelopmentFile(filePath)) {
351
+ return violations;
352
+ }
353
+
354
+ const lines = fileContent.split('\n');
355
+
356
+ // Simple regex patterns for fallback
357
+ const consolePattern = /\bconsole\.(log|debug|info|trace|dir|table|count|time|clear|group|assert|profile)\s*\(/g;
358
+ const forbiddenPattern = /\b(print|alert|confirm|prompt)\s*\(/g;
359
+
360
+ for (let i = 0; i < lines.length; i++) {
361
+ const line = lines[i];
362
+ const lineNumber = i + 1;
363
+
364
+ // Skip comments and strings (basic check)
365
+ if (this.isLineCommentOrString(line)) {
366
+ continue;
367
+ }
368
+
369
+ // Check console calls
370
+ consolePattern.lastIndex = 0; // Reset regex state
371
+ let match;
372
+ while ((match = consolePattern.exec(line)) !== null) {
373
+ const method = match[1];
374
+ if (!this.config.allowedMethods.has(method)) {
375
+ violations.push({
376
+ ruleId: this.ruleId,
377
+ severity: this.severity,
378
+ message: `Do not use console.${method}() in production code. Use proper logging instead.`,
379
+ filePath: filePath,
380
+ line: lineNumber,
381
+ column: match.index + 1,
382
+ source: line.trim(),
383
+ suggestion: `Consider using a proper logging library (logger.${method}())`
384
+ });
385
+ }
386
+ // Prevent infinite loop - limit to first match per line
387
+ break;
388
+ }
389
+
390
+ // Check forbidden functions
391
+ forbiddenPattern.lastIndex = 0; // Reset regex state
392
+ while ((match = forbiddenPattern.exec(line)) !== null) {
393
+ const functionName = match[1];
394
+ violations.push({
395
+ ruleId: this.ruleId,
396
+ severity: this.severity,
397
+ message: `Do not use ${functionName}() in production code. Use proper logging or UI notifications instead.`,
398
+ filePath: filePath,
399
+ line: lineNumber,
400
+ column: match.index + 1,
401
+ source: line.trim(),
402
+ suggestion: `Consider using a logging library or proper UI notification system`
403
+ });
404
+ // Prevent infinite loop - limit to first match per line
405
+ break;
406
+ }
407
+ }
408
+
409
+ return violations;
410
+ }
411
+
412
+ isLineCommentOrString(line) {
413
+ const trimmed = line.trim();
414
+
415
+ // Single line comment
416
+ if (trimmed.startsWith('//') || trimmed.startsWith('*')) {
417
+ return true;
418
+ }
419
+
420
+ // Simple string check - if more quotes before console than after, likely in string
421
+ const beforeConsole = line.split(/console\.|print\(|alert\(/)[0];
422
+ if (beforeConsole) {
423
+ const quotes = (beforeConsole.match(/['"]/g) || []).length;
424
+ return quotes % 2 === 1;
425
+ }
426
+
427
+ return false;
428
+ }
429
+ }
430
+
431
+ module.exports = C043NoConsoleOrPrintAnalyzer;