@sun-asterisk/sunlint 1.2.2 → 1.3.0

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 (64) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/CONTRIBUTING.md +533 -70
  3. package/README.md +16 -2
  4. package/config/engines/engines-enhanced.json +86 -0
  5. package/config/engines/semantic-config.json +114 -0
  6. package/config/eslint-rule-mapping.json +50 -38
  7. package/config/rules/enhanced-rules-registry.json +2503 -0
  8. package/config/rules/rules-registry-generated.json +785 -837
  9. package/core/adapters/sunlint-rule-adapter.js +25 -30
  10. package/core/analysis-orchestrator.js +42 -2
  11. package/core/categories.js +52 -0
  12. package/core/category-constants.js +39 -0
  13. package/core/cli-action-handler.js +32 -5
  14. package/core/config-manager.js +111 -0
  15. package/core/config-merger.js +61 -0
  16. package/core/constants/categories.js +168 -0
  17. package/core/constants/defaults.js +165 -0
  18. package/core/constants/engines.js +185 -0
  19. package/core/constants/index.js +30 -0
  20. package/core/constants/rules.js +215 -0
  21. package/core/file-targeting-service.js +128 -7
  22. package/core/interfaces/rule-plugin.interface.js +207 -0
  23. package/core/plugin-manager.js +448 -0
  24. package/core/rule-selection-service.js +42 -15
  25. package/core/semantic-engine.js +560 -0
  26. package/core/semantic-rule-base.js +433 -0
  27. package/core/unified-rule-registry.js +484 -0
  28. package/docs/CONSTANTS-ARCHITECTURE.md +288 -0
  29. package/engines/core/base-engine.js +249 -0
  30. package/engines/engine-factory.js +275 -0
  31. package/engines/eslint-engine.js +171 -19
  32. package/engines/heuristic-engine.js +511 -78
  33. package/integrations/eslint/plugin/index.js +27 -27
  34. package/package.json +10 -6
  35. package/rules/common/C003_no_vague_abbreviations/analyzer.js +1 -1
  36. package/rules/common/C029_catch_block_logging/analyzer.js +17 -5
  37. package/rules/common/C047_no_duplicate_retry_logic/c047-semantic-rule.js +278 -0
  38. package/rules/common/C047_no_duplicate_retry_logic/symbol-analyzer-enhanced.js +968 -0
  39. package/rules/common/C047_no_duplicate_retry_logic/symbol-config.json +71 -0
  40. package/rules/index.js +7 -0
  41. package/scripts/category-manager.js +150 -0
  42. package/scripts/generate-rules-registry.js +88 -0
  43. package/scripts/migrate-rule-registry.js +157 -0
  44. package/scripts/validate-system.js +48 -0
  45. package/.sunlint.json +0 -35
  46. package/config/README.md +0 -88
  47. package/config/engines/eslint-rule-mapping.json +0 -74
  48. package/config/schemas/sunlint-schema.json +0 -0
  49. package/config/testing/test-s005-working.ts +0 -22
  50. package/core/multi-rule-runner.js +0 -0
  51. package/engines/tree-sitter-parser.js +0 -0
  52. package/engines/universal-ast-engine.js +0 -0
  53. package/rules/common/C029_catch_block_logging/analyzer-backup.js +0 -426
  54. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +0 -130
  55. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +0 -487
  56. package/rules/common/C029_catch_block_logging/analyzer-simple.js +0 -110
  57. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +0 -441
  58. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +0 -127
  59. package/rules/common/C029_catch_block_logging/ast-analyzer.js +0 -133
  60. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +0 -408
  61. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +0 -454
  62. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +0 -700
  63. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +0 -568
  64. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +0 -459
@@ -85,33 +85,33 @@ const s058 = require("./rules/security/s058-no-ssrf.js");
85
85
 
86
86
  module.exports = {
87
87
  rules: {
88
- "c002": c002,
89
- "c003": c003,
90
- "c006": c006,
91
- "c010": c010,
92
- "c013": c013,
93
- "c014": c014,
94
- "c017": c017,
95
- "c018": c018,
96
- "c030": c030,
97
- "c035": c035,
98
- "c023": c023,
99
- "c029": c029,
100
- "c041": c041,
101
- "c042": c042,
102
- "c043": c043,
103
- "c047": c047,
104
- "c072": c072,
105
- "c075": c075,
106
- "c076": c076,
107
- "t002": t002,
108
- "t003": t003,
109
- "t004": t004,
110
- "t007": t007,
111
- "t010": t010,
112
- "t019": t019,
113
- "t020": t020,
114
- "t021": t021,
88
+ "c002-no-duplicate-code": c002,
89
+ "c003-no-vague-abbreviations": c003,
90
+ "c006-function-name-verb-noun": c006,
91
+ "c010-limit-block-nesting": c010,
92
+ "c013-no-dead-code": c013,
93
+ "c014-abstract-dependency-preferred": c014,
94
+ "c017-limit-constructor-logic": c017,
95
+ "c018-no-generic-throw": c018,
96
+ "c030-use-custom-error-classes": c030,
97
+ "c035-no-empty-catch": c035,
98
+ "c023-no-duplicate-variable-name-in-scope": c023,
99
+ "c029-catch-block-logging": c029,
100
+ "c041-no-config-inline": c041,
101
+ "c042-boolean-name-prefix": c042,
102
+ "c043-no-console-or-print": c043,
103
+ "c047-no-duplicate-retry-logic": c047,
104
+ "c072-one-assert-per-test": c072,
105
+ "c075-explicit-function-return-types": c075,
106
+ "c076-single-behavior-per-test": c076,
107
+ "t002-interface-prefix-i": t002,
108
+ "t003-ts-ignore-reason": t003,
109
+ "t004-no-empty-type": t004,
110
+ "t007-no-fn-in-constructor": t007,
111
+ "t010-no-nested-union-tuple": t010,
112
+ "t019-no-this-assign": t019,
113
+ "t020-no-default-multi-export": t020,
114
+ "t021-limit-nested-generics": t021,
115
115
  // Security rules
116
116
  "typescript_s001": s001,
117
117
  "typescript_s002": s002,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sunlint",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -40,17 +40,18 @@
40
40
  "demo:file-targeting": "./demo-file-targeting.sh",
41
41
  "lint": "node cli.js --config=.sunlint.json --input=.",
42
42
  "lint:eslint-integration": "node cli.js --all --eslint-integration --input=.",
43
- "build": "npm run copy-rules && echo 'Build completed with rules copy'",
43
+ "build": "npm run copy-rules && npm run generate-registry && echo 'Build completed with rules copy and registry generation'",
44
44
  "copy-rules": "node scripts/copy-rules.js",
45
+ "generate-registry": "node scripts/generate-rules-registry.js",
45
46
  "clean": "rm -rf coverage/ *.log reports/ *.tgz",
46
47
  "postpack": "echo '📦 Package created successfully! Size: ' && ls -lh *.tgz | awk '{print $5}'",
47
48
  "start": "node cli.js --help",
48
49
  "version": "node cli.js --version",
49
- "pack": "npm run copy-rules && npm pack",
50
+ "pack": "npm run copy-rules && npm run generate-registry && npm pack",
50
51
  "publish:github": "npm publish --registry=https://npm.pkg.github.com",
51
52
  "publish:npmjs": "npm publish --registry=https://registry.npmjs.org",
52
53
  "publish:test": "npm publish --dry-run --registry=https://registry.npmjs.org",
53
- "prepublishOnly": "npm run clean && npm run copy-rules"
54
+ "prepublishOnly": "npm run clean && npm run copy-rules && npm run generate-registry"
54
55
  },
55
56
  "keywords": [
56
57
  "linting",
@@ -99,7 +100,8 @@
99
100
  "espree": "^10.3.0",
100
101
  "minimatch": "^10.0.3",
101
102
  "node-fetch": "^3.3.2",
102
- "table": "^6.8.2"
103
+ "table": "^6.8.2",
104
+ "ts-morph": "^26.0.0"
103
105
  },
104
106
  "peerDependencies": {
105
107
  "typescript": ">=4.0.0"
@@ -126,8 +128,10 @@
126
128
  },
127
129
  "devDependencies": {
128
130
  "@types/node": "^22.10.1",
131
+ "eslint-plugin-import": "^2.32.0",
129
132
  "glob": "^11.0.3",
130
- "jest": "^29.7.0"
133
+ "jest": "^29.7.0",
134
+ "typescript": "^5.8.3"
131
135
  },
132
136
  "bugs": {
133
137
  "url": "https://github.com/sun-asterisk/engineer-excellence/issues"
@@ -295,7 +295,7 @@ class C003NoVagueAbbreviations {
295
295
  const violation = this.checkVariableName(variableName, content, lineNumber, match.index);
296
296
  if (violation) {
297
297
  violations.push({
298
- rule: 'C003',
298
+ ruleId: 'C003',
299
299
  severity: 'warning',
300
300
  message: violation.message,
301
301
  line: lineNumber,
@@ -8,30 +8,42 @@ const fs = require('fs');
8
8
  const path = require('path');
9
9
 
10
10
  class C029Analyzer {
11
- constructor() {
11
+ constructor(options = {}) {
12
12
  this.ruleId = 'C029';
13
13
  this.ruleName = 'Enhanced Catch Block Error Logging';
14
14
  this.description = 'Mọi catch block phải log nguyên nhân lỗi đầy đủ và bảo toàn context (Smart Pipeline 3-stage analysis)';
15
+ this.verbose = options.verbose || false;
15
16
 
16
17
  // Load Smart Pipeline as primary analyzer
17
18
  this.smartPipeline = null;
18
19
 
19
20
  try {
20
21
  this.smartPipeline = require('./analyzer-smart-pipeline.js');
21
- console.log('🎯 C029: Smart Pipeline loaded (3-stage: Regex → AST → Data Flow)');
22
+ if (this.verbose) {
23
+ console.log('[DEBUG] 🎯 C029: Smart Pipeline loaded (3-stage: Regex → AST → Data Flow)');
24
+ }
22
25
  } catch (error) {
23
- console.warn('⚠️ C029: Smart Pipeline failed, using fallback:', error.message);
26
+ if (this.verbose) {
27
+ console.warn('[DEBUG] ⚠️ C029: Smart Pipeline failed, using fallback:', error.message);
28
+ }
24
29
  this.smartPipeline = null;
25
30
  }
26
31
  }
27
32
 
28
33
  async analyze(files, language, options = {}) {
34
+ // Store verbose option for this analysis
35
+ this.verbose = options.verbose || this.verbose || false;
36
+
29
37
  // Use Smart Pipeline as primary choice
30
38
  if (this.smartPipeline) {
31
- console.log('🎯 C029: Using Smart Pipeline (3-stage analysis)...');
39
+ if (this.verbose) {
40
+ console.log('[DEBUG] 🎯 C029: Using Smart Pipeline (3-stage analysis)...');
41
+ }
32
42
  return await this.smartPipeline.analyze(files, language, options);
33
43
  } else {
34
- console.log('🔍 C029: Using fallback regex analysis...');
44
+ if (this.verbose) {
45
+ console.log('[DEBUG] 🔍 C029: Using fallback regex analysis...');
46
+ }
35
47
  return await this.analyzeWithRegex(files, language, options);
36
48
  }
37
49
  }
@@ -0,0 +1,278 @@
1
+ /**
2
+ * C047 Semantic Rule - Adapted for Shared Symbol Table
3
+ */
4
+
5
+ const C047SymbolAnalyzerEnhanced = require('./symbol-analyzer-enhanced');
6
+
7
+ class C047SemanticRule extends C047SymbolAnalyzerEnhanced {
8
+ constructor(options = {}) {
9
+ super();
10
+ this.options = options;
11
+ this.verbose = options.verbose || false; // Store verbose setting
12
+ this.currentViolations = []; // Store violations for heuristic engine compatibility
13
+ }
14
+
15
+ /**
16
+ * Initialize the semantic rule (required by heuristic engine)
17
+ */
18
+ async initialize(semanticEngine = null) {
19
+ if (this.verbose) {
20
+ console.log(`[DEBUG] 🔧 Initializing C047 semantic rule...`);
21
+ }
22
+ // Store semantic engine reference if provided
23
+ if (semanticEngine) {
24
+ this.semanticEngine = semanticEngine;
25
+ }
26
+ // Load configuration from parent class
27
+ await this.loadConfiguration();
28
+ if (this.verbose) {
29
+ console.log(`[DEBUG] ✅ C047 semantic rule initialized`);
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Analyze single file (required by heuristic engine)
35
+ */
36
+ async analyzeFile(filePath, options = {}) {
37
+ if (this.verbose) {
38
+ console.log(`[DEBUG] 🔍 C047: Analyzing file ${filePath}`);
39
+ }
40
+ try {
41
+ // Use parent analyze method for single file
42
+ const violations = await this.analyze([filePath], 'typescript', options);
43
+ this.currentViolations = violations || [];
44
+ if (this.verbose || options.verbose) {
45
+ console.log(`✅ C047: Found ${this.currentViolations.length} violations in ${filePath}`);
46
+ }
47
+ } catch (error) {
48
+ if (this.verbose || options.verbose) {
49
+ console.error(`❌ C047 analysis failed for ${filePath}:`, error.message);
50
+ }
51
+ this.currentViolations = [];
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Get violations (required by heuristic engine)
57
+ */
58
+ getViolations() {
59
+ return this.currentViolations;
60
+ }
61
+
62
+ /**
63
+ * Clear violations (required by heuristic engine)
64
+ */
65
+ clearViolations() {
66
+ this.currentViolations = [];
67
+ }
68
+
69
+ /**
70
+ * New method: Analyze using shared Symbol Table
71
+ * This is more efficient than creating separate ts-morph projects
72
+ */
73
+ async analyzeWithSymbolTable(symbolTable, options = {}) {
74
+ if (this.verbose) {
75
+ console.log(`[DEBUG] 🔍 C047 Semantic Rule: Using shared Symbol Table...`);
76
+ }
77
+ const startTime = Date.now();
78
+
79
+ try {
80
+ // Skip the project initialization since we use shared Symbol Table
81
+ if (this.verbose) {
82
+ console.log(`[DEBUG] 📋 Step 1: Using shared configuration...`);
83
+ }
84
+ await this.loadConfiguration();
85
+
86
+ // Use shared Symbol Table instead of creating new project
87
+ if (this.verbose) {
88
+ console.log(`[DEBUG] 🏗️ Step 2: Using shared Symbol Table...`);
89
+ }
90
+ this.project = symbolTable.project;
91
+
92
+ // Detect retry patterns using cached symbols
93
+ if (this.verbose) {
94
+ console.log(`[DEBUG] 🔍 Step 3: Detecting retry patterns with Symbol Table...`);
95
+ }
96
+ const allRetryPatterns = await this.detectRetryPatternsWithSymbolTable(symbolTable, options);
97
+ if (this.verbose) {
98
+ console.log(`[DEBUG] ✅ Pattern detection complete: ${allRetryPatterns.length} patterns`);
99
+ }
100
+
101
+ // Group by layers and flows
102
+ if (this.verbose) {
103
+ console.log(`[DEBUG] 📊 Step 4: Grouping patterns...`);
104
+ }
105
+ const layeredPatterns = this.groupByLayersAndFlows(allRetryPatterns);
106
+ if (this.verbose) {
107
+ console.log(`[DEBUG] ✅ Grouping complete`);
108
+ }
109
+
110
+ // Apply violation detection logic
111
+ if (this.verbose) {
112
+ console.log(`[DEBUG] ⚠️ Step 5: Detecting violations...`);
113
+ }
114
+ const violations = this.detectViolations(layeredPatterns);
115
+ if (this.verbose) {
116
+ console.log(`[DEBUG] ✅ Violation detection complete: ${violations.length} violations`);
117
+ }
118
+
119
+ const duration = Date.now() - startTime;
120
+ if (this.verbose) {
121
+ console.log(`[DEBUG] 🎯 C047 Semantic analysis complete in ${duration}ms!`);
122
+ }
123
+
124
+ if (options.verbose) {
125
+ this.printAnalysisStats(allRetryPatterns, layeredPatterns, violations);
126
+ }
127
+
128
+ return violations;
129
+
130
+ } catch (error) {
131
+ console.error('❌ C047 Semantic rule failed:', error.message);
132
+ return [];
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Detect retry patterns using cached Symbol Table
138
+ */
139
+ async detectRetryPatternsWithSymbolTable(symbolTable, options) {
140
+ if (this.verbose) {
141
+ console.log(`[DEBUG] 🔍 Detecting retry patterns with Symbol Table...`);
142
+ }
143
+ const allPatterns = [];
144
+
145
+ // Use cached source files from Symbol Table
146
+ const sourceFiles = symbolTable.sourceFiles;
147
+ if (this.verbose) {
148
+ console.log(`[DEBUG] 📄 Found ${sourceFiles.length} source files in Symbol Table`);
149
+ }
150
+
151
+ for (let i = 0; i < sourceFiles.length; i++) {
152
+ const sourceFile = sourceFiles[i];
153
+ const fileName = sourceFile.getBaseName();
154
+
155
+ if (this.verbose || options.verbose) {
156
+ console.log(`[DEBUG] 🔍 Analyzing ${i + 1}/${sourceFiles.length}: ${fileName}`);
157
+ }
158
+
159
+ try {
160
+ // Check if symbols are already cached
161
+ const cachedSymbols = symbolTable.getSymbols(sourceFile.getFilePath());
162
+ let filePatterns;
163
+
164
+ if (cachedSymbols && this.options.useSymbolCache) {
165
+ // Use cached symbols for faster analysis
166
+ filePatterns = await this.analyzeWithCachedSymbols(sourceFile, cachedSymbols);
167
+ } else {
168
+ // Fallback to direct AST analysis
169
+ filePatterns = await this.analyzeSourceFile(sourceFile);
170
+ }
171
+
172
+ allPatterns.push(...filePatterns);
173
+
174
+ if (this.verbose || options.verbose) {
175
+ console.log(`[DEBUG] ✅ Found ${filePatterns.length} patterns in ${fileName}`);
176
+ }
177
+ } catch (error) {
178
+ if (this.verbose) {
179
+ console.warn(`[DEBUG] ⚠️ Error analyzing ${fileName}: ${error.message}`);
180
+ }
181
+ }
182
+ }
183
+
184
+ if (this.verbose) {
185
+ console.log(`[DEBUG] 🎯 Total patterns detected: ${allPatterns.length}`);
186
+ }
187
+ return allPatterns;
188
+ }
189
+
190
+ /**
191
+ * Analyze using pre-cached symbols (faster)
192
+ */
193
+ async analyzeWithCachedSymbols(sourceFile, cachedSymbols) {
194
+ const patterns = [];
195
+ const filePath = sourceFile.getFilePath() || sourceFile.getBaseName();
196
+
197
+ if (this.verbose) {
198
+ console.log(`[DEBUG] 📁 Analyzing ${require('path').basename(filePath)} with cached symbols`);
199
+ }
200
+
201
+ // Process cached classes
202
+ for (const classSymbol of cachedSymbols.classes) {
203
+ if (this.verbose) {
204
+ console.log(`[DEBUG] 📦 Cached class: ${classSymbol.name}`);
205
+ }
206
+
207
+ for (const methodName of classSymbol.methods) {
208
+ const fullFunctionName = `${classSymbol.name}.${methodName}`;
209
+ if (this.verbose) {
210
+ console.log(`[DEBUG] 🎯 Cached method: ${fullFunctionName}`);
211
+ }
212
+
213
+ // Get the actual AST node for detailed analysis
214
+ const classNode = sourceFile.getClasses().find(c => c.getName() === classSymbol.name);
215
+ if (classNode) {
216
+ const methodNode = classNode.getMethods().find(m => m.getName() === methodName);
217
+ if (methodNode) {
218
+ const patterns_found = await this.analyzeFunction(methodNode, fullFunctionName, filePath);
219
+ patterns.push(...patterns_found);
220
+ }
221
+ }
222
+ }
223
+ }
224
+
225
+ // Process cached functions
226
+ for (const functionSymbol of cachedSymbols.functions) {
227
+ if (this.verbose) {
228
+ console.log(`[DEBUG] 🔧 Cached function: ${functionSymbol.name}`);
229
+ }
230
+
231
+ const functionNode = sourceFile.getFunctions().find(f => f.getName() === functionSymbol.name);
232
+ if (functionNode) {
233
+ const patterns_found = await this.analyzeFunction(functionNode, functionSymbol.name, filePath);
234
+ patterns.push(...patterns_found);
235
+ }
236
+ }
237
+
238
+ // Process cached variables (for React components)
239
+ for (const variableSymbol of cachedSymbols.variables) {
240
+ if (this.verbose) {
241
+ console.log(`[DEBUG] ⚡ Cached variable: ${variableSymbol.name}`);
242
+ }
243
+
244
+ const varDecl = sourceFile.getVariableDeclarations().find(v => v.getName() === variableSymbol.name);
245
+ if (varDecl) {
246
+ const initializer = varDecl.getInitializer();
247
+ if (initializer && (initializer.getKind() === require('ts-morph').SyntaxKind.ArrowFunction ||
248
+ initializer.getKind() === require('ts-morph').SyntaxKind.FunctionExpression)) {
249
+
250
+ // Check for useQuery calls with retry
251
+ const useQueryPatterns = this.detectUseQueryRetryPatterns(initializer, variableSymbol.name, filePath);
252
+ patterns.push(...useQueryPatterns);
253
+
254
+ // Also analyze for standard retry patterns
255
+ const patterns_found = await this.analyzeFunction(initializer, variableSymbol.name, filePath);
256
+ patterns.push(...patterns_found);
257
+ }
258
+ }
259
+ }
260
+
261
+ if (this.verbose) {
262
+ console.log(`[DEBUG] 📊 Total patterns found with cached symbols: ${patterns.length}`);
263
+ }
264
+ return patterns;
265
+ }
266
+
267
+ /**
268
+ * Traditional analyze method (for backward compatibility)
269
+ */
270
+ async analyze(files, language, options = {}) {
271
+ if (this.verbose) {
272
+ console.log(`[DEBUG] ⚠️ C047: Using traditional analysis (consider upgrading to Symbol Table)`);
273
+ }
274
+ return super.analyze(files, language, options);
275
+ }
276
+ }
277
+
278
+ module.exports = C047SemanticRule;