@sun-asterisk/sunlint 1.3.2 → 1.3.3
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.
- package/CHANGELOG.md +38 -0
- package/README.md +5 -3
- package/config/rules/enhanced-rules-registry.json +144 -33
- package/core/analysis-orchestrator.js +167 -42
- package/core/auto-performance-manager.js +243 -0
- package/core/cli-action-handler.js +9 -1
- package/core/cli-program.js +19 -5
- package/core/constants/defaults.js +56 -0
- package/core/performance-optimizer.js +271 -0
- package/docs/FILE_LIMITS_COMPLETION_REPORT.md +151 -0
- package/docs/FILE_LIMITS_EXPLANATION.md +190 -0
- package/docs/PERFORMANCE.md +311 -0
- package/docs/PERFORMANCE_MIGRATION_GUIDE.md +368 -0
- package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +255 -0
- package/docs/QUICK_FILE_LIMITS.md +64 -0
- package/docs/SIMPLIFIED_USAGE_GUIDE.md +208 -0
- package/engines/heuristic-engine.js +182 -5
- package/package.json +2 -1
- package/rules/common/C048_no_bypass_architectural_layers/analyzer.js +180 -0
- package/rules/common/C048_no_bypass_architectural_layers/config.json +50 -0
- package/rules/common/C048_no_bypass_architectural_layers/symbol-based-analyzer.js +235 -0
- package/rules/common/C052_parsing_or_data_transformation/analyzer.js +180 -0
- package/rules/common/C052_parsing_or_data_transformation/config.json +50 -0
- package/rules/common/C052_parsing_or_data_transformation/symbol-based-analyzer.js +132 -0
- package/rules/index.js +2 -0
- package/rules/security/S017_use_parameterized_queries/README.md +128 -0
- package/rules/security/S017_use_parameterized_queries/analyzer.js +286 -0
- package/rules/security/S017_use_parameterized_queries/config.json +109 -0
- package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +541 -0
- package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +777 -0
- package/rules/security/S031_secure_session_cookies/README.md +127 -0
- package/rules/security/S031_secure_session_cookies/analyzer.js +245 -0
- package/rules/security/S031_secure_session_cookies/config.json +86 -0
- package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +196 -0
- package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +1084 -0
- package/rules/security/S032_httponly_session_cookies/FRAMEWORK_SUPPORT.md +209 -0
- package/rules/security/S032_httponly_session_cookies/README.md +184 -0
- package/rules/security/S032_httponly_session_cookies/analyzer.js +282 -0
- package/rules/security/S032_httponly_session_cookies/config.json +96 -0
- package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +715 -0
- package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +1348 -0
- package/rules/security/S033_samesite_session_cookies/README.md +227 -0
- package/rules/security/S033_samesite_session_cookies/analyzer.js +242 -0
- package/rules/security/S033_samesite_session_cookies/config.json +87 -0
- package/rules/security/S033_samesite_session_cookies/regex-based-analyzer.js +703 -0
- package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +732 -0
- package/rules/security/S034_host_prefix_session_cookies/README.md +204 -0
- package/rules/security/S034_host_prefix_session_cookies/analyzer.js +290 -0
- package/rules/security/S034_host_prefix_session_cookies/config.json +62 -0
- package/rules/security/S034_host_prefix_session_cookies/regex-based-analyzer.js +478 -0
- package/rules/security/S034_host_prefix_session_cookies/symbol-based-analyzer.js +277 -0
- package/rules/security/S035_path_session_cookies/README.md +257 -0
- package/rules/security/S035_path_session_cookies/analyzer.js +316 -0
- package/rules/security/S035_path_session_cookies/config.json +99 -0
- package/rules/security/S035_path_session_cookies/regex-based-analyzer.js +724 -0
- package/rules/security/S035_path_session_cookies/symbol-based-analyzer.js +373 -0
- package/scripts/batch-processing-demo.js +334 -0
- package/scripts/performance-test.js +541 -0
- package/scripts/quick-performance-test.js +108 -0
|
@@ -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
|
|