@sun-asterisk/sunlint 1.3.2 → 1.3.4

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 (60) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +5 -3
  3. package/config/rules/enhanced-rules-registry.json +144 -33
  4. package/core/analysis-orchestrator.js +173 -42
  5. package/core/auto-performance-manager.js +243 -0
  6. package/core/cli-action-handler.js +24 -2
  7. package/core/cli-program.js +19 -5
  8. package/core/constants/defaults.js +56 -0
  9. package/core/performance-optimizer.js +271 -0
  10. package/docs/FILE_LIMITS_COMPLETION_REPORT.md +151 -0
  11. package/docs/FILE_LIMITS_EXPLANATION.md +190 -0
  12. package/docs/PERFORMANCE.md +311 -0
  13. package/docs/PERFORMANCE_MIGRATION_GUIDE.md +368 -0
  14. package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +255 -0
  15. package/docs/QUICK_FILE_LIMITS.md +64 -0
  16. package/docs/SIMPLIFIED_USAGE_GUIDE.md +208 -0
  17. package/engines/engine-factory.js +7 -0
  18. package/engines/heuristic-engine.js +182 -5
  19. package/package.json +2 -1
  20. package/rules/common/C048_no_bypass_architectural_layers/analyzer.js +180 -0
  21. package/rules/common/C048_no_bypass_architectural_layers/config.json +50 -0
  22. package/rules/common/C048_no_bypass_architectural_layers/symbol-based-analyzer.js +235 -0
  23. package/rules/common/C052_parsing_or_data_transformation/analyzer.js +180 -0
  24. package/rules/common/C052_parsing_or_data_transformation/config.json +50 -0
  25. package/rules/common/C052_parsing_or_data_transformation/symbol-based-analyzer.js +132 -0
  26. package/rules/index.js +2 -0
  27. package/rules/security/S017_use_parameterized_queries/README.md +128 -0
  28. package/rules/security/S017_use_parameterized_queries/analyzer.js +286 -0
  29. package/rules/security/S017_use_parameterized_queries/config.json +109 -0
  30. package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +541 -0
  31. package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +777 -0
  32. package/rules/security/S031_secure_session_cookies/README.md +127 -0
  33. package/rules/security/S031_secure_session_cookies/analyzer.js +245 -0
  34. package/rules/security/S031_secure_session_cookies/config.json +86 -0
  35. package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +196 -0
  36. package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +1084 -0
  37. package/rules/security/S032_httponly_session_cookies/FRAMEWORK_SUPPORT.md +209 -0
  38. package/rules/security/S032_httponly_session_cookies/README.md +184 -0
  39. package/rules/security/S032_httponly_session_cookies/analyzer.js +282 -0
  40. package/rules/security/S032_httponly_session_cookies/config.json +96 -0
  41. package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +715 -0
  42. package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +1348 -0
  43. package/rules/security/S033_samesite_session_cookies/README.md +227 -0
  44. package/rules/security/S033_samesite_session_cookies/analyzer.js +242 -0
  45. package/rules/security/S033_samesite_session_cookies/config.json +87 -0
  46. package/rules/security/S033_samesite_session_cookies/regex-based-analyzer.js +703 -0
  47. package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +732 -0
  48. package/rules/security/S034_host_prefix_session_cookies/README.md +204 -0
  49. package/rules/security/S034_host_prefix_session_cookies/analyzer.js +290 -0
  50. package/rules/security/S034_host_prefix_session_cookies/config.json +62 -0
  51. package/rules/security/S034_host_prefix_session_cookies/regex-based-analyzer.js +478 -0
  52. package/rules/security/S034_host_prefix_session_cookies/symbol-based-analyzer.js +277 -0
  53. package/rules/security/S035_path_session_cookies/README.md +257 -0
  54. package/rules/security/S035_path_session_cookies/analyzer.js +316 -0
  55. package/rules/security/S035_path_session_cookies/config.json +99 -0
  56. package/rules/security/S035_path_session_cookies/regex-based-analyzer.js +724 -0
  57. package/rules/security/S035_path_session_cookies/symbol-based-analyzer.js +373 -0
  58. package/scripts/batch-processing-demo.js +334 -0
  59. package/scripts/performance-test.js +541 -0
  60. package/scripts/quick-performance-test.js +108 -0
@@ -0,0 +1,50 @@
1
+ {
2
+ "id": "C048",
3
+ "name": "C048_do_not_bypass_architectural_layers",
4
+ "category": "architecture",
5
+ "description": "C048 - Do not bypass architectural layers (controller/service/repository)",
6
+ "severity": "warning",
7
+ "enabled": true,
8
+ "semantic": {
9
+ "enabled": true,
10
+ "priority": "high",
11
+ "fallback": "heuristic"
12
+ },
13
+ "patterns": {
14
+ "include": [
15
+ "**/*.js",
16
+ "**/*.ts",
17
+ "**/*.jsx",
18
+ "**/*.tsx"
19
+ ],
20
+ "exclude": [
21
+ "**/*.test.*",
22
+ "**/*.spec.*",
23
+ "**/*.mock.*",
24
+ "**/test/**",
25
+ "**/tests/**",
26
+ "**/spec/**"
27
+ ]
28
+ },
29
+ "options": {
30
+ "strictMode": false,
31
+ "allowedDbMethods": [],
32
+ "repositoryPatterns": [
33
+ "*Repository*",
34
+ "*Repo*",
35
+ "*DAO*",
36
+ "*Store*"
37
+ ],
38
+ "servicePatterns": [
39
+ "*Service*",
40
+ "*UseCase*",
41
+ "*Handler*",
42
+ "*Manager*"
43
+ ],
44
+ "complexityThreshold": {
45
+ "methodLength": 200,
46
+ "cyclomaticComplexity": 5,
47
+ "nestedDepth": 3
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * C048 Symbol-based Analyzer - Advanced Do not bypass architectural layers (controller/service/repository)
3
+ * Purpose: Use AST + Symbol Resolution to clear layered architecture, ensuring logic and data flow are well-structured and maintainable.
4
+ */
5
+
6
+ // c048-symbol-analyzer.js
7
+ const path = require("path");
8
+ const { SyntaxKind } = require("ts-morph");
9
+
10
+ class C048SymbolBasedAnalyzer {
11
+ constructor(semanticEngine = null) {
12
+ this.ruleId = "C048";
13
+ this.ruleName =
14
+ "Error bypass architectural layers (controller/service/repository) (Symbol-Based)";
15
+ this.semanticEngine = semanticEngine;
16
+ this.verbose = false;
17
+
18
+ this.layers = {
19
+ controller: "controller",
20
+ service: "service",
21
+ repository: "repository",
22
+ other: "other",
23
+ };
24
+
25
+ this.dirHints = {
26
+ controller: [/^controllers?$/i, /controller/i],
27
+ service: [/^services?$/i, /service/i],
28
+ repository: [/^repositories?$/i, /repository/i, /repos?$/i],
29
+ };
30
+
31
+ this.classSuffix = {
32
+ controller: /Controller$/,
33
+ service: /Service$/,
34
+ repository: /Repository$/,
35
+ };
36
+
37
+ // 🔧 FIX: arrays, not strings
38
+ this.forbiddenImports = {
39
+ controller: ["repository"], // Controllers must not import repositories
40
+ service: ["controller"], // Services must not import controllers
41
+ };
42
+ }
43
+
44
+ async initialize(semanticEngine = null) {
45
+ if (semanticEngine) {
46
+ this.semanticEngine = semanticEngine;
47
+ }
48
+ this.verbose = semanticEngine?.verbose || false;
49
+ if (process.env.SUNLINT_DEBUG) {
50
+ console.log(
51
+ `🔧 [C048 Symbol-Based] Analyzer initialized, verbose: ${this.verbose}`
52
+ );
53
+ }
54
+ }
55
+
56
+ async analyzeFileBasic(filePath, options = {}) {
57
+ return await this.analyzeFileWithSymbols(filePath, options);
58
+ }
59
+
60
+ async analyzeFileWithSymbols(filePath, options = {}) {
61
+ const violations = [];
62
+ const verbose = options.verbose || this.verbose;
63
+
64
+ if (!this.semanticEngine?.project) {
65
+ if (verbose) {
66
+ console.warn(
67
+ "[C048 Symbol-Based] No semantic engine available, skipping analysis"
68
+ );
69
+ }
70
+ return violations;
71
+ }
72
+
73
+ try {
74
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
75
+ if (!sourceFile) return violations;
76
+
77
+ const fileLayer = this.inferLayer(sourceFile);
78
+ if (verbose) {
79
+ console.log(`🔍 Analyzing ${filePath} → layer = ${fileLayer}`);
80
+ }
81
+
82
+ if (
83
+ ![this.layers.controller, this.layers.service].includes(fileLayer)
84
+ ) {
85
+ return violations;
86
+ }
87
+
88
+ const forbidden = new Set(this.forbiddenImports[fileLayer] || []);
89
+
90
+ // 1) Import-based violations
91
+ for (const imp of sourceFile.getImportDeclarations()) {
92
+ const targetLayer = this.getImportedTargetLayer(imp);
93
+ if (verbose) {
94
+ console.log(
95
+ ` import ${imp.getModuleSpecifierValue()} → layer ${targetLayer}`
96
+ );
97
+ }
98
+ if (!targetLayer) continue;
99
+ if (forbidden.has(targetLayer)) {
100
+ violations.push({
101
+ ruleId: this.ruleId,
102
+ severity: "warning",
103
+ message:
104
+ fileLayer === this.layers.controller
105
+ ? "Controllers should not import Repositories. Depend on a Service instead."
106
+ : "Services should not import Controllers. Keep presentation layer out of service logic.",
107
+ source: this.ruleId,
108
+ file: filePath,
109
+ line: imp.getStartLineNumber(),
110
+ column: imp.getStart() - imp.getStartLinePos(),
111
+ description: `[SYMBOL-BASED] Bypassing architectural layers detected: ${fileLayer} importing ${targetLayer}`,
112
+ suggestion: "Refactor to use appropriate layer dependencies",
113
+ category: "Architecture",
114
+ });
115
+ }
116
+ }
117
+
118
+ // 2) new XRepository() / new XController()
119
+ const badSuffix =
120
+ fileLayer === this.layers.controller
121
+ ? this.classSuffix.repository
122
+ : this.classSuffix.controller;
123
+
124
+ sourceFile.forEachDescendant((node) => {
125
+ if (node.getKind() === SyntaxKind.NewExpression) {
126
+ const expr = node;
127
+ const exprText = expr.getExpression().getText();
128
+ if (badSuffix.test(exprText)) {
129
+ violations.push({
130
+ ruleId: this.ruleId,
131
+ severity: "warning",
132
+ message:
133
+ fileLayer === this.layers.controller
134
+ ? "Controllers should not instantiate Repositories directly. Call a Service."
135
+ : "Services should not instantiate Controllers.",
136
+ source: this.ruleId,
137
+ file: filePath,
138
+ line: expr.getStartLineNumber(),
139
+ column: expr.getStart() - expr.getStartLinePos(),
140
+ description: `[SYMBOL-BASED] Bypassing architectural layers detected: ${fileLayer} instantiating ${exprText}`,
141
+ suggestion: "Refactor to use appropriate layer dependencies",
142
+ category: "Architecture",
143
+ });
144
+ }
145
+ }
146
+ });
147
+
148
+ // 3) property/param types referring to forbidden classes
149
+ sourceFile.forEachDescendant((node) => {
150
+ if (
151
+ node.getKind() === SyntaxKind.PropertyDeclaration ||
152
+ node.getKind() === SyntaxKind.Parameter ||
153
+ node.getKind() === SyntaxKind.VariableDeclaration
154
+ ) {
155
+ const typeNode = node.getTypeNode?.();
156
+ if (!typeNode) return;
157
+ const t = typeNode.getText();
158
+ if (badSuffix.test(t)) {
159
+ violations.push({
160
+ ruleId: this.ruleId,
161
+ severity: "warning",
162
+ message:
163
+ fileLayer === this.layers.controller
164
+ ? "Controllers should not hold Repository-typed members. Inject a Service instead."
165
+ : "Services should not reference Controller types.",
166
+ source: this.ruleId,
167
+ file: filePath,
168
+ line: node.getStartLineNumber(),
169
+ column: node.getStart() - node.getStartLinePos(),
170
+ description: `[SYMBOL-BASED] Bypassing architectural layers detected: ${fileLayer} using ${t} type`,
171
+ suggestion: "Refactor to use appropriate layer dependencies",
172
+ category: "Architecture",
173
+ });
174
+ }
175
+ }
176
+ });
177
+
178
+ if (verbose) {
179
+ console.log(
180
+ `🔍 [C048 Symbol-Based] Total violations found: ${violations.length}`
181
+ );
182
+ }
183
+
184
+ return violations;
185
+ } catch (error) {
186
+ if (verbose) {
187
+ console.warn(
188
+ `[C048 Symbol-Based] Analysis failed for ${filePath}:`,
189
+ error.message
190
+ );
191
+ }
192
+ return violations;
193
+ }
194
+ }
195
+
196
+ inferLayerFromPath(filePath) {
197
+ const parts = filePath.split(path.sep);
198
+ for (const seg of parts) {
199
+ for (const [layer, patterns] of Object.entries(this.dirHints)) {
200
+ if (patterns.some((re) => re.test(seg))) return layer;
201
+ }
202
+ }
203
+ const base = path.basename(filePath, path.extname(filePath));
204
+ for (const [layer, re] of Object.entries(this.classSuffix)) {
205
+ if (re.test(base)) return layer;
206
+ }
207
+ return this.layers.other;
208
+ }
209
+
210
+ inferLayerFromClasses(sourceFile) {
211
+ const classes = sourceFile.getClasses();
212
+ for (const cls of classes) {
213
+ const name = cls.getName() || "";
214
+ for (const [layer, re] of Object.entries(this.classSuffix)) {
215
+ if (re.test(name)) return layer;
216
+ }
217
+ }
218
+ return null;
219
+ }
220
+
221
+ inferLayer(sourceFile) {
222
+ return (
223
+ this.inferLayerFromClasses(sourceFile) ||
224
+ this.inferLayerFromPath(sourceFile.getFilePath())
225
+ );
226
+ }
227
+
228
+ getImportedTargetLayer(imp) {
229
+ const target = imp.getModuleSpecifierSourceFile();
230
+ if (!target) return null;
231
+ return this.inferLayer(target);
232
+ }
233
+ }
234
+
235
+ module.exports = C048SymbolBasedAnalyzer;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * C052 Main Analyzer - Parsing or data transformation logic must be separated from controllers
3
+ * Primary: Enforce separation of concerns — controllers should only handle requests and delegate processing, improving testability, maintainability, and reuse.
4
+ * Fallback: Regex-based for all other cases
5
+ */
6
+
7
+ const C052SymbolBasedAnalyzer = require('./symbol-based-analyzer');
8
+
9
+ class C052Analyzer {
10
+ constructor(options = {}) {
11
+ if (process.env.SUNLINT_DEBUG) {
12
+ console.log(`🔧 [C052] Constructor called with options:`, !!options);
13
+ console.log(`🔧 [C052] Options type:`, typeof options, Object.keys(options || {}));
14
+ }
15
+
16
+ this.ruleId = 'C052';
17
+ this.ruleName = 'Parsing or data transformation logic must be separated from controllers';
18
+ this.description = 'Enforce separation of concerns — controllers should only handle requests and delegate processing, improving testability, maintainability, and reuse.';
19
+ this.semanticEngine = options.semanticEngine || null;
20
+ this.verbose = options.verbose || false;
21
+
22
+ // Configuration
23
+ this.config = {
24
+ useSymbolBased: true, // Primary approach
25
+ fallbackToRegex: false, // Only when symbol fails completely
26
+ symbolBasedOnly: false // Can be set to true for pure mode
27
+ };
28
+
29
+ // Initialize both analyzers
30
+ try {
31
+ this.symbolAnalyzer = new C052SymbolBasedAnalyzer(this.semanticEngine);
32
+ if (process.env.SUNLINT_DEBUG) {
33
+ console.log(`🔧 [C052] Symbol analyzer created successfully`);
34
+ }
35
+ } catch (error) {
36
+ console.error(`🔧 [C052] Error creating symbol analyzer:`, error);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Initialize with semantic engine
42
+ */
43
+ async initialize(semanticEngine = null) {
44
+ if (semanticEngine) {
45
+ this.semanticEngine = semanticEngine;
46
+ }
47
+ this.verbose = semanticEngine?.verbose || false;
48
+
49
+ // Initialize both analyzers
50
+ await this.symbolAnalyzer.initialize(semanticEngine);
51
+
52
+ // Ensure verbose flag is propagated
53
+ this.symbolAnalyzer.verbose = this.verbose;
54
+
55
+ if (this.verbose) {
56
+ console.log(`🔧 [C052 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
57
+ }
58
+ }
59
+
60
+ async analyze(files, language, options = {}) {
61
+ if (process.env.SUNLINT_DEBUG) {
62
+ console.log(`🔧 [C052] analyze() method called with ${files.length} files, language: ${language}`);
63
+ }
64
+
65
+ const violations = [];
66
+
67
+ for (const filePath of files) {
68
+ try {
69
+ if (process.env.SUNLINT_DEBUG) {
70
+ console.log(`🔧 [C052] Processing file: ${filePath}`);
71
+ }
72
+
73
+ const fileViolations = await this.analyzeFile(filePath, options);
74
+ violations.push(...fileViolations);
75
+
76
+ if (process.env.SUNLINT_DEBUG) {
77
+ console.log(`🔧 [C052] File ${filePath}: Found ${fileViolations.length} violations`);
78
+ }
79
+ } catch (error) {
80
+ console.warn(`❌ [C052] Analysis failed for ${filePath}:`, error.message);
81
+ }
82
+ }
83
+
84
+ if (process.env.SUNLINT_DEBUG) {
85
+ console.log(`🔧 [C052] Total violations found: ${violations.length}`);
86
+ }
87
+
88
+ return violations;
89
+ }
90
+
91
+ async analyzeFile(filePath, options = {}) {
92
+ if (process.env.SUNLINT_DEBUG) {
93
+ console.log(`🔧 [C052] analyzeFile() called for: ${filePath}`);
94
+ }
95
+
96
+ // 1. Try Symbol-based analysis first (primary)
97
+ if (this.config.useSymbolBased &&
98
+ this.semanticEngine?.project &&
99
+ this.semanticEngine?.initialized) {
100
+ try {
101
+ if (process.env.SUNLINT_DEBUG) {
102
+ console.log(`🔧 [C052] Trying symbol-based analysis...`);
103
+ }
104
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
105
+ if (sourceFile) {
106
+ if (process.env.SUNLINT_DEBUG) {
107
+ console.log(`🔧 [C052] Source file found, analyzing with symbol-based...`);
108
+ }
109
+ const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
110
+
111
+ // Mark violations with analysis strategy
112
+ violations.forEach(v => v.analysisStrategy = 'symbol-based');
113
+
114
+ if (process.env.SUNLINT_DEBUG) {
115
+ console.log(`✅ [C052] Symbol-based analysis: ${violations.length} violations`);
116
+ }
117
+ return violations; // Return even if 0 violations - symbol analysis completed successfully
118
+ } else {
119
+ if (process.env.SUNLINT_DEBUG) {
120
+ console.log(`⚠️ [C052] Source file not found in project`);
121
+ }
122
+ }
123
+ } catch (error) {
124
+ console.warn(`⚠️ [C052] Symbol analysis failed: ${error.message}`);
125
+ // Continue to fallback
126
+ }
127
+ } else {
128
+ if (process.env.SUNLINT_DEBUG) {
129
+ console.log(`🔄 [C052] Symbol analysis conditions check:`);
130
+ console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
131
+ console.log(` - semanticEngine: ${!!this.semanticEngine}`);
132
+ console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
133
+ console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
134
+ console.log(`🔄 [C052] Symbol analysis unavailable, using regex fallback`);
135
+ }
136
+ }
137
+
138
+ if (options?.verbose) {
139
+ console.log(`🔧 [C052] No analysis methods succeeded, returning empty`);
140
+ }
141
+ return [];
142
+ }
143
+
144
+ async analyzeFileBasic(filePath, options = {}) {
145
+ console.log(`🔧 [C052] analyzeFileBasic() called for: ${filePath}`);
146
+ console.log(`🔧 [C052] semanticEngine exists: ${!!this.semanticEngine}`);
147
+ console.log(`🔧 [C052] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
148
+
149
+ try {
150
+ // Try symbol-based analysis first
151
+ if (this.semanticEngine?.isSymbolEngineReady?.() &&
152
+ this.semanticEngine.project) {
153
+
154
+ if (this.verbose) {
155
+ console.log(`🔍 [C052] Using symbol-based analysis for ${filePath}`);
156
+ }
157
+
158
+ const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
159
+ return violations;
160
+ }
161
+ } catch (error) {
162
+ if (this.verbose) {
163
+ console.warn(`⚠️ [C052] Symbol analysis failed: ${error.message}`);
164
+ }
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Methods for compatibility with different engine invocation patterns
170
+ */
171
+ async analyzeFileWithSymbols(filePath, options = {}) {
172
+ return this.analyzeFile(filePath, options);
173
+ }
174
+
175
+ async analyzeWithSemantics(filePath, options = {}) {
176
+ return this.analyzeFile(filePath, options);
177
+ }
178
+ }
179
+
180
+ module.exports = C052Analyzer;
@@ -0,0 +1,50 @@
1
+ {
2
+ "id": "C052",
3
+ "name": "C052_C052_parsing_or_data_transformation",
4
+ "category": "architecture",
5
+ "description": "C052 - Parsing or data transformation logic must be separated from controllers",
6
+ "severity": "warning",
7
+ "enabled": true,
8
+ "semantic": {
9
+ "enabled": true,
10
+ "priority": "high",
11
+ "fallback": "heuristic"
12
+ },
13
+ "patterns": {
14
+ "include": [
15
+ "**/*.js",
16
+ "**/*.ts",
17
+ "**/*.jsx",
18
+ "**/*.tsx"
19
+ ],
20
+ "exclude": [
21
+ "**/*.test.*",
22
+ "**/*.spec.*",
23
+ "**/*.mock.*",
24
+ "**/test/**",
25
+ "**/tests/**",
26
+ "**/spec/**"
27
+ ]
28
+ },
29
+ "options": {
30
+ "strictMode": false,
31
+ "allowedDbMethods": [],
32
+ "repositoryPatterns": [
33
+ "*Repository*",
34
+ "*Repo*",
35
+ "*DAO*",
36
+ "*Store*"
37
+ ],
38
+ "servicePatterns": [
39
+ "*Service*",
40
+ "*UseCase*",
41
+ "*Handler*",
42
+ "*Manager*"
43
+ ],
44
+ "complexityThreshold": {
45
+ "methodLength": 200,
46
+ "cyclomaticComplexity": 5,
47
+ "nestedDepth": 3
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * C052 Symbol-based Analyzer - Advanced Parsing or data transformation logic must be separated from controllers
3
+ * Purpose: Use AST + Symbol controllers should only handle requests and delegate processing, improving testability, maintainability, and reuse.
4
+ */
5
+
6
+ // c052-symbol-analyzer.js
7
+ const { SyntaxKind } = require("ts-morph");
8
+
9
+ class C052SymbolBasedAnalyzer {
10
+ constructor(semanticEngine = null) {
11
+ this.ruleId = "C052";
12
+ this.ruleName =
13
+ "Error bypass architectural layers (controller/service/repository) (Symbol-Based)";
14
+ this.semanticEngine = semanticEngine;
15
+ this.verbose = false;
16
+ }
17
+
18
+ async initialize(semanticEngine = null) {
19
+ if (semanticEngine) {
20
+ this.semanticEngine = semanticEngine;
21
+ }
22
+ this.verbose = semanticEngine?.verbose || false;
23
+ if (process.env.SUNLINT_DEBUG) {
24
+ console.log(
25
+ `🔧 [C052 Symbol-Based] Analyzer initialized, verbose: ${this.verbose}`
26
+ );
27
+ }
28
+ }
29
+
30
+ async analyzeFileBasic(filePath, options = {}) {
31
+ return await this.analyzeFileWithSymbols(filePath, options);
32
+ }
33
+
34
+ async analyzeFileWithSymbols(filePath, options = {}) {
35
+ const violations = [];
36
+ const verbose = options.verbose || this.verbose;
37
+
38
+ if (!this.semanticEngine?.project) {
39
+ if (verbose) {
40
+ console.warn(
41
+ "[C052 Symbol-Based] No semantic engine available, skipping analysis"
42
+ );
43
+ }
44
+ return violations;
45
+ }
46
+
47
+ // Only check controller files
48
+ if (!filePath.toLowerCase().includes('controller')) {
49
+ return violations;
50
+ }
51
+
52
+
53
+ try {
54
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
55
+ if (!sourceFile) return violations;
56
+
57
+ sourceFile.forEachDescendant((node) => {
58
+ // Check suspicious call expressions
59
+ if (node.getKind() === SyntaxKind.CallExpression) {
60
+ const expression = node.getExpression();
61
+ const exprText = expression.getText();
62
+
63
+ // Known suspicious functions
64
+ const suspicious = [
65
+ "parseInt",
66
+ "parseFloat",
67
+ "JSON.parse",
68
+ "JSON.stringify",
69
+ "toLowerCase",
70
+ "toUpperCase",
71
+ "trim",
72
+ "toISOString",
73
+ ];
74
+
75
+ for (const s of suspicious) {
76
+ if (exprText.includes(s)) {
77
+ violations.push({
78
+ ruleId: this.ruleId,
79
+ severity: "warning",
80
+ message: `Controller should not perform data parsing or transformation directly (uses ${s}).`,
81
+ source: this.ruleId,
82
+ file: filePath,
83
+ line: node.getStartLineNumber(),
84
+ column: node.getStart() - node.getStartLinePos(),
85
+ description: `[SYMBOL-BASED] Data parsing/transformation detected in controller (calls ${s})`,
86
+ suggestion: "Refactor to delegate parsing/transformation to a service",
87
+ category: "maintainability",
88
+ });
89
+ }
90
+ }
91
+ }
92
+
93
+ // Check if new Date(...) used
94
+ if (node.getKind() === SyntaxKind.NewExpression) {
95
+ const exp = node.getExpression();
96
+ if (exp && exp.getText() === "Date") {
97
+ violations.push({
98
+ ruleId: this.ruleId,
99
+ severity: "warning",
100
+ message: `Controller should not perform data parsing or transformation directly (uses new Date()).`,
101
+ source: this.ruleId,
102
+ file: filePath,
103
+ line: node.getStartLineNumber(),
104
+ column: node.getStart() - node.getStartLinePos(),
105
+ description: `[SYMBOL-BASED] Data parsing/transformation detected in controller (uses new Date())`,
106
+ suggestion: "Refactor to delegate parsing/transformation to a service",
107
+ category: "maintainability",
108
+ });
109
+ }
110
+ }
111
+ });
112
+
113
+ if (verbose) {
114
+ console.log(
115
+ `🔍 [C052 Symbol-Based] Total violations found: ${violations.length}`
116
+ );
117
+ }
118
+
119
+ return violations;
120
+ } catch (error) {
121
+ if (verbose) {
122
+ console.warn(
123
+ `[C052 Symbol-Based] Analysis failed for ${filePath}:`,
124
+ error.message
125
+ );
126
+ }
127
+ return violations;
128
+ }
129
+ }
130
+ }
131
+
132
+ module.exports = C052SymbolBasedAnalyzer;
package/rules/index.js CHANGED
@@ -61,6 +61,8 @@ const commonRules = {
61
61
  C031: loadRule('common', 'C031_validation_separation'),
62
62
  C041: loadRule('common', 'C041_no_sensitive_hardcode'),
63
63
  C042: loadRule('common', 'C042_boolean_name_prefix'),
64
+ C048: loadRule('common', 'C048_no_bypass_architectural_layers'),
65
+ C052: loadRule('common', 'C052_parsing_or_data_transformation'),
64
66
  C047: loadRule('common', 'C047_no_duplicate_retry_logic'),
65
67
  };
66
68