@sun-asterisk/sunlint 1.2.2 → 1.3.1

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 +107 -1
  2. package/CONTRIBUTING.md +1654 -66
  3. package/README.md +19 -6
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/engines/engines-enhanced.json +86 -0
  7. package/config/engines/semantic-config.json +114 -0
  8. package/config/eslint-rule-mapping.json +50 -38
  9. package/config/large-project.json +143 -0
  10. package/config/presets/all.json +0 -1
  11. package/config/release.json +70 -0
  12. package/config/rule-analysis-strategies.js +23 -4
  13. package/config/rules/S027-categories.json +122 -0
  14. package/config/rules/enhanced-rules-registry.json +2564 -0
  15. package/config/rules/rules-registry-generated.json +785 -837
  16. package/config/rules/rules-registry.json +13 -1
  17. package/core/adapters/sunlint-rule-adapter.js +25 -30
  18. package/core/analysis-orchestrator.js +42 -2
  19. package/core/categories.js +52 -0
  20. package/core/category-constants.js +39 -0
  21. package/core/cli-action-handler.js +53 -32
  22. package/core/cli-program.js +11 -3
  23. package/core/config-manager.js +111 -0
  24. package/core/config-merger.js +88 -0
  25. package/core/constants/categories.js +168 -0
  26. package/core/constants/defaults.js +165 -0
  27. package/core/constants/engines.js +185 -0
  28. package/core/constants/index.js +30 -0
  29. package/core/constants/rules.js +215 -0
  30. package/core/enhanced-rules-registry.js +3 -3
  31. package/core/file-targeting-service.js +128 -7
  32. package/core/interfaces/rule-plugin.interface.js +207 -0
  33. package/core/plugin-manager.js +448 -0
  34. package/core/rule-selection-service.js +42 -15
  35. package/core/semantic-engine.js +658 -0
  36. package/core/semantic-rule-base.js +433 -0
  37. package/core/unified-rule-registry.js +484 -0
  38. package/docs/COMMAND-EXAMPLES.md +134 -0
  39. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  40. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  41. package/engines/core/base-engine.js +249 -0
  42. package/engines/engine-factory.js +275 -0
  43. package/engines/eslint-engine.js +171 -19
  44. package/engines/heuristic-engine.js +569 -78
  45. package/integrations/eslint/plugin/index.js +26 -28
  46. package/origin-rules/common-en.md +8 -8
  47. package/package.json +10 -6
  48. package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
  49. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  50. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  51. package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
  52. package/rules/common/C033_separate_service_repository/README.md +78 -0
  53. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  54. package/rules/common/C033_separate_service_repository/config.json +50 -0
  55. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  56. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  57. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  58. package/rules/common/C035_error_logging_context/analyzer.js +230 -0
  59. package/rules/common/C035_error_logging_context/config.json +54 -0
  60. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  61. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  62. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  63. package/rules/common/C040_centralized_validation/config.json +46 -0
  64. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  65. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  66. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  67. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  68. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  69. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  70. package/rules/common/C076_explicit_function_types/README.md +30 -0
  71. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  72. package/rules/common/C076_explicit_function_types/config.json +15 -0
  73. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  74. package/rules/index.js +8 -0
  75. package/rules/parser/rule-parser.js +13 -2
  76. package/rules/security/S005_no_origin_auth/README.md +226 -0
  77. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  78. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  79. package/rules/security/S005_no_origin_auth/config.json +85 -0
  80. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  81. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  82. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  83. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  84. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  85. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  86. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  87. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  88. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  89. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  90. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  91. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  92. package/scripts/category-manager.js +150 -0
  93. package/scripts/generate-rules-registry.js +88 -0
  94. package/scripts/migrate-rule-registry.js +157 -0
  95. package/scripts/prepare-release.sh +1 -1
  96. package/scripts/validate-system.js +48 -0
  97. package/.sunlint.json +0 -35
  98. package/config/README.md +0 -88
  99. package/config/engines/eslint-rule-mapping.json +0 -74
  100. package/config/schemas/sunlint-schema.json +0 -0
  101. package/config/testing/test-s005-working.ts +0 -22
  102. package/core/multi-rule-runner.js +0 -0
  103. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  104. package/docs/FUTURE_PACKAGES.md +0 -83
  105. package/docs/HEURISTIC_VS_AI.md +0 -113
  106. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  107. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  108. package/docs/RELEASE_GUIDE.md +0 -230
  109. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  110. package/engines/tree-sitter-parser.js +0 -0
  111. package/engines/universal-ast-engine.js +0 -0
  112. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  113. package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
  114. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
  115. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
  116. package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
  117. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
  118. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
  119. package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
  120. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
  121. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
  122. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
  123. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
  124. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
@@ -0,0 +1,433 @@
1
+ /**
2
+ * SunLint Semantic Rule Base
3
+ * Base class for semantic analysis rules using shared Symbol Table
4
+ *
5
+ * Provides common functionality for semantic rules in SunLint
6
+ */
7
+
8
+ const path = require('path');
9
+
10
+ class SemanticRuleBase {
11
+ constructor(ruleId, config = {}) {
12
+ this.ruleId = ruleId;
13
+ this.config = {
14
+ // Rule metadata
15
+ category: config.category || 'semantic',
16
+ severity: config.severity || 'warning',
17
+ description: config.description || '',
18
+
19
+ // Analysis options
20
+ crossFileAnalysis: config.crossFileAnalysis !== false,
21
+ requiresTypeChecker: config.requiresTypeChecker || false,
22
+ cacheResults: config.cacheResults !== false,
23
+
24
+ // Performance
25
+ timeout: config.timeout || 30000, // 30 seconds
26
+ maxFiles: config.maxFiles || 1000,
27
+
28
+ ...config
29
+ };
30
+
31
+ this.semanticEngine = null;
32
+ this.violations = [];
33
+ this.stats = {
34
+ filesAnalyzed: 0,
35
+ violationsFound: 0,
36
+ analysisTime: 0,
37
+ cacheHits: 0
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Initialize rule with SemanticEngine instance
43
+ */
44
+ initialize(semanticEngine) {
45
+ this.semanticEngine = semanticEngine;
46
+
47
+ if (!this.semanticEngine || !this.semanticEngine.initialized) {
48
+ throw new Error(`${this.ruleId}: SemanticEngine is required and must be initialized`);
49
+ }
50
+
51
+ console.log(`🔧 Rule ${this.ruleId} initialized with semantic analysis`);
52
+ }
53
+
54
+ /**
55
+ * Main analysis method - to be overridden by specific rules
56
+ */
57
+ async analyze(filePaths, options = {}) {
58
+ const startTime = Date.now();
59
+ this.violations = [];
60
+
61
+ try {
62
+ console.log(`🔍 ${this.ruleId}: Starting semantic analysis...`);
63
+
64
+ // Filter and validate files
65
+ const validFiles = await this.filterFiles(filePaths);
66
+
67
+ if (validFiles.length === 0) {
68
+ console.log(`ℹ️ ${this.ruleId}: No valid files to analyze`);
69
+ return this.generateReport();
70
+ }
71
+
72
+ // Analyze each file
73
+ for (const filePath of validFiles) {
74
+ await this.analyzeFile(filePath, options);
75
+ this.stats.filesAnalyzed++;
76
+
77
+ // Check timeout
78
+ if (Date.now() - startTime > this.config.timeout) {
79
+ console.warn(`⚠️ ${this.ruleId}: Analysis timeout reached`);
80
+ break;
81
+ }
82
+ }
83
+
84
+ this.stats.analysisTime = Date.now() - startTime;
85
+ this.stats.violationsFound = this.violations.length;
86
+
87
+ console.log(`✅ ${this.ruleId}: Analysis complete - ${this.violations.length} violations found`);
88
+
89
+ return this.generateReport();
90
+
91
+ } catch (error) {
92
+ console.error(`❌ ${this.ruleId}: Analysis failed:`, error.message);
93
+ throw error;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Analyze single file - to be overridden by specific rules
99
+ */
100
+ async analyzeFile(filePath, options = {}) {
101
+ throw new Error(`${this.ruleId}: analyzeFile() method must be implemented by subclass`);
102
+ }
103
+
104
+ /**
105
+ * Filter files based on rule requirements
106
+ */
107
+ async filterFiles(filePaths) {
108
+ const filtered = [];
109
+
110
+ for (const filePath of filePaths) {
111
+ // Check file extension
112
+ if (this.isValidFileType(filePath)) {
113
+ // Check if file exists in Symbol Table
114
+ try {
115
+ const symbolTable = await this.semanticEngine.getSymbolTable(filePath);
116
+ if (symbolTable) {
117
+ filtered.push(filePath);
118
+ }
119
+ } catch (error) {
120
+ console.warn(`⚠️ ${this.ruleId}: Cannot analyze ${filePath}:`, error.message);
121
+ }
122
+ }
123
+ }
124
+
125
+ return filtered.slice(0, this.config.maxFiles);
126
+ }
127
+
128
+ /**
129
+ * Check if file type is supported by this rule
130
+ */
131
+ isValidFileType(filePath) {
132
+ const supportedExtensions = ['.ts', '.tsx', '.js', '.jsx'];
133
+ const ext = path.extname(filePath);
134
+ return supportedExtensions.includes(ext);
135
+ }
136
+
137
+ /**
138
+ * Get Symbol Table for a file with caching
139
+ */
140
+ async getSymbolTable(filePath) {
141
+ const startTime = Date.now();
142
+
143
+ try {
144
+ const symbolTable = await this.semanticEngine.getSymbolTable(filePath);
145
+
146
+ if (symbolTable) {
147
+ this.stats.cacheHits++;
148
+ }
149
+
150
+ return symbolTable;
151
+
152
+ } catch (error) {
153
+ console.warn(`⚠️ ${this.ruleId}: Failed to get symbol table for ${filePath}:`, error.message);
154
+ return null;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Add a violation
160
+ */
161
+ addViolation(violation) {
162
+ const enhancedViolation = {
163
+ ruleId: this.ruleId,
164
+ category: this.config.category,
165
+ severity: this.config.severity,
166
+ timestamp: Date.now(),
167
+
168
+ // Required fields
169
+ filePath: violation.filePath,
170
+ line: violation.line,
171
+ column: violation.column || 1,
172
+ message: violation.message,
173
+
174
+ // Optional fields
175
+ endLine: violation.endLine,
176
+ endColumn: violation.endColumn,
177
+ suggestion: violation.suggestion,
178
+ codeSnippet: violation.codeSnippet,
179
+
180
+ // Semantic analysis context
181
+ symbolContext: violation.symbolContext,
182
+ crossFileReferences: violation.crossFileReferences,
183
+ semanticDetails: violation.semanticDetails,
184
+
185
+ ...violation
186
+ };
187
+
188
+ this.violations.push(enhancedViolation);
189
+ }
190
+
191
+ /**
192
+ * Common semantic analysis utilities
193
+ */
194
+
195
+ /**
196
+ * Find function calls by name with semantic context
197
+ */
198
+ findFunctionCalls(symbolTable, functionName) {
199
+ return symbolTable.functionCalls.filter(call =>
200
+ call.functionName === functionName ||
201
+ call.functionName.includes(functionName)
202
+ );
203
+ }
204
+
205
+ /**
206
+ * Find method calls on specific objects
207
+ */
208
+ findMethodCalls(symbolTable, objectName, methodName) {
209
+ return symbolTable.methodCalls.filter(call =>
210
+ call.objectName === objectName && call.methodName === methodName
211
+ );
212
+ }
213
+
214
+ /**
215
+ * Check if a function call is within a retry context
216
+ */
217
+ isInRetryContext(symbolTable, functionCall) {
218
+ // Check parent call stack for retry patterns
219
+ const retryPatterns = ['retry', 'retries', 'withRetry', 'retryWhen'];
220
+
221
+ if (functionCall.parentContext) {
222
+ return retryPatterns.some(pattern =>
223
+ functionCall.parentContext.includes(pattern)
224
+ );
225
+ }
226
+
227
+ // Check nearby calls (previous/next lines)
228
+ const nearbyLines = this.getNearbyLines(symbolTable, functionCall.line, 5);
229
+ return nearbyLines.some(line =>
230
+ retryPatterns.some(pattern => line.includes(pattern))
231
+ );
232
+ }
233
+
234
+ /**
235
+ * Get nearby lines for context analysis
236
+ */
237
+ getNearbyLines(symbolTable, targetLine, range = 3) {
238
+ const lines = [];
239
+
240
+ // Collect all calls around target line
241
+ const allCalls = [
242
+ ...symbolTable.functionCalls,
243
+ ...symbolTable.methodCalls,
244
+ ...symbolTable.hooks
245
+ ];
246
+
247
+ const nearbyCalls = allCalls.filter(call =>
248
+ Math.abs(call.line - targetLine) <= range
249
+ );
250
+
251
+ return nearbyCalls.map(call => ({
252
+ line: call.line,
253
+ text: call.functionName || call.methodName || call.hookName
254
+ }));
255
+ }
256
+
257
+ /**
258
+ * Analyze React hooks for retry patterns
259
+ */
260
+ analyzeHooksForRetry(symbolTable) {
261
+ const retryHooks = [];
262
+
263
+ symbolTable.hooks.forEach(hook => {
264
+ if (hook.isQueryHook && hook.retryConfig.hasRetryConfig) {
265
+ retryHooks.push({
266
+ ...hook,
267
+ hasMultiLayerRetry: this.checkMultiLayerRetry(symbolTable, hook)
268
+ });
269
+ }
270
+ });
271
+
272
+ return retryHooks;
273
+ }
274
+
275
+ /**
276
+ * Check for multi-layer retry patterns
277
+ */
278
+ checkMultiLayerRetry(symbolTable, queryHook) {
279
+ // Look for additional retry mechanisms near the query hook
280
+ const nearbyLines = this.getNearbyLines(symbolTable, queryHook.line, 10);
281
+
282
+ // Check for retry patterns in nearby code
283
+ const retryPatterns = nearbyLines.filter(line =>
284
+ /retry|retries|attempt/i.test(line.text)
285
+ );
286
+
287
+ return retryPatterns.length > 1; // Multiple retry mechanisms
288
+ }
289
+
290
+ /**
291
+ * Cross-file analysis utilities
292
+ */
293
+
294
+ /**
295
+ * Find symbol usages across files
296
+ */
297
+ async findCrossFileUsages(symbolName, excludeFiles = []) {
298
+ if (!this.config.crossFileAnalysis) {
299
+ return [];
300
+ }
301
+
302
+ const usages = [];
303
+ const allFiles = this.semanticEngine.project.getSourceFiles();
304
+
305
+ for (const sourceFile of allFiles) {
306
+ const filePath = sourceFile.getFilePath();
307
+
308
+ if (excludeFiles.includes(filePath)) {
309
+ continue;
310
+ }
311
+
312
+ const symbolTable = await this.getSymbolTable(filePath);
313
+ if (!symbolTable) continue;
314
+
315
+ // Search in various symbol collections
316
+ const foundUsages = [
317
+ ...this.searchInCollection(symbolTable.functionCalls, symbolName),
318
+ ...this.searchInCollection(symbolTable.methodCalls, symbolName),
319
+ ...this.searchInCollection(symbolTable.imports, symbolName),
320
+ ...this.searchInCollection(symbolTable.variables, symbolName)
321
+ ];
322
+
323
+ foundUsages.forEach(usage => {
324
+ usages.push({
325
+ ...usage,
326
+ filePath,
327
+ crossFileReference: true
328
+ });
329
+ });
330
+ }
331
+
332
+ return usages;
333
+ }
334
+
335
+ searchInCollection(collection, symbolName) {
336
+ return collection.filter(item =>
337
+ item.name === symbolName ||
338
+ item.functionName === symbolName ||
339
+ item.methodName === symbolName ||
340
+ (item.namedImports && item.namedImports.some(imp => imp.name === symbolName))
341
+ );
342
+ }
343
+
344
+ /**
345
+ * Generate analysis report
346
+ */
347
+ generateReport() {
348
+ return {
349
+ ruleId: this.ruleId,
350
+ config: this.config,
351
+ violations: this.violations,
352
+ stats: this.stats,
353
+ summary: {
354
+ filesAnalyzed: this.stats.filesAnalyzed,
355
+ violationsFound: this.stats.violationsFound,
356
+ analysisTime: this.stats.analysisTime,
357
+ averageTimePerFile: this.stats.filesAnalyzed > 0
358
+ ? Math.round(this.stats.analysisTime / this.stats.filesAnalyzed)
359
+ : 0
360
+ }
361
+ };
362
+ }
363
+
364
+ /**
365
+ * Cleanup resources
366
+ */
367
+ cleanup() {
368
+ this.violations = [];
369
+ this.stats = {
370
+ filesAnalyzed: 0,
371
+ violationsFound: 0,
372
+ analysisTime: 0,
373
+ cacheHits: 0
374
+ };
375
+ }
376
+
377
+ /**
378
+ * Validation helpers
379
+ */
380
+
381
+ validateRequiredFields(violation) {
382
+ const required = ['filePath', 'line', 'message'];
383
+ const missing = required.filter(field => !violation[field]);
384
+
385
+ if (missing.length > 0) {
386
+ throw new Error(`${this.ruleId}: Missing required violation fields: ${missing.join(', ')}`);
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Code snippet extraction
392
+ */
393
+ extractCodeSnippet(symbolTable, line, range = 2) {
394
+ // This would need implementation based on source file access
395
+ // For now, return a placeholder
396
+ return {
397
+ startLine: Math.max(1, line - range),
398
+ endLine: line + range,
399
+ code: `// Code snippet around line ${line}`
400
+ };
401
+ }
402
+
403
+ /**
404
+ * Get current violations
405
+ */
406
+ getViolations() {
407
+ return this.violations;
408
+ }
409
+
410
+ /**
411
+ * Clear violations (for reuse)
412
+ */
413
+ clearViolations() {
414
+ this.violations = [];
415
+ this.stats.violationsFound = 0;
416
+ }
417
+
418
+ /**
419
+ * Suggestion generation
420
+ */
421
+ generateSuggestion(violationType, context = {}) {
422
+ // Base suggestions - to be overridden by specific rules
423
+ const suggestions = {
424
+ 'multi-layer-retry': 'Consider consolidating retry logic into a single mechanism to avoid conflicts',
425
+ 'redundant-retry': 'Remove redundant retry configuration to simplify error handling',
426
+ 'missing-retry': 'Add retry configuration for better resilience'
427
+ };
428
+
429
+ return suggestions[violationType] || 'Review this code for best practices';
430
+ }
431
+ }
432
+
433
+ module.exports = SemanticRuleBase;