@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.
- package/CHANGELOG.md +73 -0
- package/README.md +5 -3
- package/config/rules/enhanced-rules-registry.json +144 -33
- package/core/analysis-orchestrator.js +173 -42
- package/core/auto-performance-manager.js +243 -0
- package/core/cli-action-handler.js +24 -2
- 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/engine-factory.js +7 -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,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S035 Symbol-Based Analyzer - Set Path attribute for Session Cookies
|
|
3
|
+
* Uses TypeScript compiler API for semantic analysis
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { SyntaxKind } = require("typescript");
|
|
7
|
+
|
|
8
|
+
class S035SymbolBasedAnalyzer {
|
|
9
|
+
constructor(semanticEngine = null) {
|
|
10
|
+
this.semanticEngine = semanticEngine;
|
|
11
|
+
this.ruleId = "S035";
|
|
12
|
+
this.ruleName = "Set Path attribute for Session Cookies";
|
|
13
|
+
this.category = "security";
|
|
14
|
+
this.violations = [];
|
|
15
|
+
|
|
16
|
+
// Session cookie indicators
|
|
17
|
+
this.sessionIndicators = [
|
|
18
|
+
"session",
|
|
19
|
+
"sessionid",
|
|
20
|
+
"sessid",
|
|
21
|
+
"jsessionid",
|
|
22
|
+
"phpsessid",
|
|
23
|
+
"asp.net_sessionid",
|
|
24
|
+
"connect.sid",
|
|
25
|
+
"auth",
|
|
26
|
+
"token",
|
|
27
|
+
"jwt",
|
|
28
|
+
"csrf",
|
|
29
|
+
"refresh",
|
|
30
|
+
"user",
|
|
31
|
+
"login",
|
|
32
|
+
"authentication",
|
|
33
|
+
"session_id",
|
|
34
|
+
"sid",
|
|
35
|
+
"auth_token",
|
|
36
|
+
"userid",
|
|
37
|
+
"user_id",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// Cookie methods that need security checking
|
|
41
|
+
this.cookieMethods = [
|
|
42
|
+
"setCookie",
|
|
43
|
+
"cookie",
|
|
44
|
+
"set",
|
|
45
|
+
"append",
|
|
46
|
+
"session",
|
|
47
|
+
"setHeader",
|
|
48
|
+
"writeHead",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// Acceptable Path values - should be specific paths, not just "/"
|
|
52
|
+
this.acceptableValues = [
|
|
53
|
+
"/app",
|
|
54
|
+
"/admin",
|
|
55
|
+
"/api",
|
|
56
|
+
"/auth",
|
|
57
|
+
"/user",
|
|
58
|
+
"/secure",
|
|
59
|
+
"/dashboard",
|
|
60
|
+
"/login",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
// Root path "/" is acceptable but not recommended for security
|
|
64
|
+
this.rootPath = "/";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Initialize analyzer with semantic engine
|
|
69
|
+
*/
|
|
70
|
+
async initialize(semanticEngine) {
|
|
71
|
+
this.semanticEngine = semanticEngine;
|
|
72
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
73
|
+
console.log(`🔧 [S035] Symbol analyzer initialized`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Main analysis method for source file
|
|
79
|
+
*/
|
|
80
|
+
async analyze(sourceFile, filePath) {
|
|
81
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
82
|
+
console.log(`🔍 [S035] Symbol analysis starting for: ${filePath}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.violations = [];
|
|
86
|
+
this.currentFile = sourceFile;
|
|
87
|
+
this.currentFilePath = filePath;
|
|
88
|
+
|
|
89
|
+
this.visitNode(sourceFile);
|
|
90
|
+
|
|
91
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
92
|
+
console.log(
|
|
93
|
+
`🔍 [S035] Symbol analysis completed: ${this.violations.length} violations`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this.violations;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
visitNode(node) {
|
|
101
|
+
// Check for res.cookie() calls
|
|
102
|
+
if (this.isCallExpression(node)) {
|
|
103
|
+
this.checkCookieCall(node);
|
|
104
|
+
this.checkSetHeaderCall(node);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check for session middleware configuration
|
|
108
|
+
if (this.isSessionMiddleware(node)) {
|
|
109
|
+
this.checkSessionMiddleware(node);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Recursively visit child nodes
|
|
113
|
+
node.forEachChild((child) => this.visitNode(child));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
isCallExpression(node) {
|
|
117
|
+
return node.getKind() === SyntaxKind.CallExpression;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
isSessionMiddleware(node) {
|
|
121
|
+
if (node.getKind() !== SyntaxKind.CallExpression) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const expression = node.getExpression();
|
|
126
|
+
const text = expression.getText();
|
|
127
|
+
|
|
128
|
+
return text === "session" || text.includes("session(");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
checkCookieCall(node) {
|
|
132
|
+
const expression = node.getExpression();
|
|
133
|
+
|
|
134
|
+
// Check if it's res.cookie() call
|
|
135
|
+
if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
|
|
136
|
+
const propertyAccess = expression;
|
|
137
|
+
const property = propertyAccess.getName();
|
|
138
|
+
|
|
139
|
+
if (property === "cookie") {
|
|
140
|
+
const args = node.getArguments();
|
|
141
|
+
if (args.length >= 1) {
|
|
142
|
+
const cookieName = this.extractStringValue(args[0]);
|
|
143
|
+
|
|
144
|
+
if (cookieName && this.isSessionCookie(cookieName)) {
|
|
145
|
+
this.checkCookieOptions(node, args, cookieName);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
checkSetHeaderCall(node) {
|
|
153
|
+
const expression = node.getExpression();
|
|
154
|
+
|
|
155
|
+
if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
|
|
156
|
+
const propertyAccess = expression;
|
|
157
|
+
const property = propertyAccess.getName();
|
|
158
|
+
|
|
159
|
+
if (property === "setHeader") {
|
|
160
|
+
const args = node.getArguments();
|
|
161
|
+
if (args.length >= 2) {
|
|
162
|
+
const headerName = this.extractStringValue(args[0]);
|
|
163
|
+
|
|
164
|
+
if (headerName && headerName.toLowerCase() === "set-cookie") {
|
|
165
|
+
const headerValue = this.extractStringValue(args[1]);
|
|
166
|
+
if (headerValue) {
|
|
167
|
+
this.checkSetCookieHeader(node, headerValue);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
checkSessionMiddleware(node) {
|
|
176
|
+
const args = node.getArguments();
|
|
177
|
+
if (
|
|
178
|
+
args.length >= 1 &&
|
|
179
|
+
args[0].getKind() === SyntaxKind.ObjectLiteralExpression
|
|
180
|
+
) {
|
|
181
|
+
const config = args[0];
|
|
182
|
+
const nameProperty = this.findProperty(config, "name");
|
|
183
|
+
const cookieProperty = this.findProperty(config, "cookie");
|
|
184
|
+
|
|
185
|
+
if (nameProperty) {
|
|
186
|
+
const nameValue = this.extractStringValue(
|
|
187
|
+
nameProperty.getInitializer()
|
|
188
|
+
);
|
|
189
|
+
if (nameValue && this.isSessionCookie(nameValue)) {
|
|
190
|
+
// Check if cookie configuration has path
|
|
191
|
+
if (
|
|
192
|
+
cookieProperty &&
|
|
193
|
+
cookieProperty.getInitializer().getKind() ===
|
|
194
|
+
SyntaxKind.ObjectLiteralExpression
|
|
195
|
+
) {
|
|
196
|
+
const cookieConfig = cookieProperty.getInitializer();
|
|
197
|
+
this.checkCookieConfigForPath(node, cookieConfig, nameValue);
|
|
198
|
+
} else {
|
|
199
|
+
this.addViolation(
|
|
200
|
+
node,
|
|
201
|
+
`Session middleware cookie "${nameValue}" should specify Path attribute to limit access scope`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
checkCookieOptions(node, args, cookieName) {
|
|
210
|
+
if (
|
|
211
|
+
args.length >= 3 &&
|
|
212
|
+
args[2].getKind() === SyntaxKind.ObjectLiteralExpression
|
|
213
|
+
) {
|
|
214
|
+
const options = args[2];
|
|
215
|
+
this.checkCookieConfigForPath(node, options, cookieName);
|
|
216
|
+
} else {
|
|
217
|
+
this.addViolation(
|
|
218
|
+
node,
|
|
219
|
+
`Session cookie "${cookieName}" should specify Path attribute to limit access scope`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
checkCookieConfigForPath(node, config, cookieName) {
|
|
225
|
+
const pathProperty = this.findProperty(config, "path");
|
|
226
|
+
|
|
227
|
+
if (!pathProperty) {
|
|
228
|
+
this.addViolation(
|
|
229
|
+
node,
|
|
230
|
+
`Session cookie "${cookieName}" should specify Path attribute to limit access scope`
|
|
231
|
+
);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const pathValue = this.extractStringValue(pathProperty.getInitializer());
|
|
236
|
+
if (!pathValue) {
|
|
237
|
+
this.addViolation(
|
|
238
|
+
node,
|
|
239
|
+
`Session cookie "${cookieName}" should have a valid Path attribute value`
|
|
240
|
+
);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Check if path is too broad (root path "/")
|
|
245
|
+
if (pathValue === this.rootPath) {
|
|
246
|
+
this.addViolation(
|
|
247
|
+
node,
|
|
248
|
+
`Session cookie "${cookieName}" uses root path "/", consider using a more specific path to limit access scope`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
checkSetCookieHeader(node, headerValue) {
|
|
254
|
+
// Extract cookie name from Set-Cookie header
|
|
255
|
+
const cookieMatch = headerValue.match(/^([^=]+)=/);
|
|
256
|
+
if (!cookieMatch) return;
|
|
257
|
+
|
|
258
|
+
const cookieName = cookieMatch[1].trim();
|
|
259
|
+
if (!this.isSessionCookie(cookieName)) return;
|
|
260
|
+
|
|
261
|
+
// Check if Path attribute is present
|
|
262
|
+
const pathMatch = headerValue.match(/Path=([^;\\s]*)/i);
|
|
263
|
+
if (!pathMatch) {
|
|
264
|
+
this.addViolation(
|
|
265
|
+
node,
|
|
266
|
+
`Session cookie "${cookieName}" in Set-Cookie header should specify Path attribute`
|
|
267
|
+
);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const pathValue = pathMatch[1];
|
|
272
|
+
if (pathValue === this.rootPath) {
|
|
273
|
+
this.addViolation(
|
|
274
|
+
node,
|
|
275
|
+
`Session cookie "${cookieName}" uses root path "/", consider using a more specific path`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
isSessionCookie(cookieName) {
|
|
281
|
+
const lowerName = cookieName.toLowerCase();
|
|
282
|
+
return this.sessionIndicators.some((indicator) =>
|
|
283
|
+
lowerName.includes(indicator.toLowerCase())
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
extractStringValue(node) {
|
|
288
|
+
if (!node) return null;
|
|
289
|
+
|
|
290
|
+
const kind = node.getKind();
|
|
291
|
+
|
|
292
|
+
if (kind === SyntaxKind.StringLiteral) {
|
|
293
|
+
return node.getLiteralValue();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (kind === SyntaxKind.NoSubstitutionTemplateLiteral) {
|
|
297
|
+
return node.getLiteralValue();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
findProperty(objectLiteral, propertyName) {
|
|
304
|
+
const properties = objectLiteral.getProperties();
|
|
305
|
+
|
|
306
|
+
for (const property of properties) {
|
|
307
|
+
if (property.getKind() === SyntaxKind.PropertyAssignment) {
|
|
308
|
+
const name = property.getName();
|
|
309
|
+
if (name === propertyName) {
|
|
310
|
+
return property;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
addViolation(node, message) {
|
|
319
|
+
const start = node.getStart();
|
|
320
|
+
const sourceFile = node.getSourceFile();
|
|
321
|
+
const lineAndColumn = sourceFile.getLineAndColumnAtPos(start);
|
|
322
|
+
|
|
323
|
+
// Debug output to understand position issues
|
|
324
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
325
|
+
console.log(
|
|
326
|
+
`🔍 [S035] Violation at node kind: ${node.getKindName()}, text: "${node
|
|
327
|
+
.getText()
|
|
328
|
+
.substring(0, 50)}..."`
|
|
329
|
+
);
|
|
330
|
+
console.log(
|
|
331
|
+
`🔍 [S035] Position: line ${lineAndColumn.line + 1}, column ${
|
|
332
|
+
lineAndColumn.column + 1
|
|
333
|
+
}`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Fix line number calculation - ts-morph may have offset issues
|
|
338
|
+
// Use actual line calculation based on source file text
|
|
339
|
+
const sourceText = sourceFile.getFullText();
|
|
340
|
+
const actualLine = this.calculateActualLine(sourceText, start);
|
|
341
|
+
|
|
342
|
+
this.violations.push({
|
|
343
|
+
ruleId: this.ruleId,
|
|
344
|
+
ruleName: this.ruleName,
|
|
345
|
+
severity: "warning",
|
|
346
|
+
message: message,
|
|
347
|
+
line: actualLine,
|
|
348
|
+
column: lineAndColumn.column + 1,
|
|
349
|
+
source: "symbol-based",
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
353
|
+
console.log(`🔍 [S035] Added violation: ${message}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
calculateActualLine(sourceText, position) {
|
|
358
|
+
// Count newlines up to the position to get accurate line number
|
|
359
|
+
let lineCount = 1;
|
|
360
|
+
for (let i = 0; i < position; i++) {
|
|
361
|
+
if (sourceText[i] === "\n") {
|
|
362
|
+
lineCount++;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return lineCount;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
cleanup() {
|
|
369
|
+
this.violations = [];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
module.exports = S035SymbolBasedAnalyzer;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 🚀 SunLint Batch Processing Demo
|
|
5
|
+
* Demonstrates performance optimizations for large projects
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const { performance } = require('perf_hooks');
|
|
11
|
+
|
|
12
|
+
// Simulated large project scenarios
|
|
13
|
+
const DEMO_SCENARIOS = [
|
|
14
|
+
{
|
|
15
|
+
name: 'Startup Project',
|
|
16
|
+
files: 50,
|
|
17
|
+
rules: 20,
|
|
18
|
+
profile: 'fast',
|
|
19
|
+
description: '50 files, 20 rules - should complete in ~10s'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'Growing Startup',
|
|
23
|
+
files: 200,
|
|
24
|
+
rules: 35,
|
|
25
|
+
profile: 'balanced',
|
|
26
|
+
description: '200 files, 35 rules - should complete in ~30s'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'Enterprise Application',
|
|
30
|
+
files: 800,
|
|
31
|
+
rules: 60,
|
|
32
|
+
profile: 'careful',
|
|
33
|
+
description: '800 files, 60 rules - should complete in ~90s'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'Large Enterprise',
|
|
37
|
+
files: 1500,
|
|
38
|
+
rules: 73,
|
|
39
|
+
profile: 'conservative',
|
|
40
|
+
description: '1500 files, all 73 rules - should complete in ~180s'
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
class BatchProcessingDemo {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.results = [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 🎬 Run the demo
|
|
51
|
+
*/
|
|
52
|
+
async run() {
|
|
53
|
+
console.log('🚀 SunLint Batch Processing Performance Demo');
|
|
54
|
+
console.log('============================================\n');
|
|
55
|
+
|
|
56
|
+
console.log('🎯 Goal: Demonstrate that SunLint can handle large projects without timeouts/crashes\n');
|
|
57
|
+
|
|
58
|
+
for (const scenario of DEMO_SCENARIOS) {
|
|
59
|
+
await this.demoScenario(scenario);
|
|
60
|
+
console.log('');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.printSummary();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 🎭 Demo a specific scenario
|
|
68
|
+
*/
|
|
69
|
+
async demoScenario(scenario) {
|
|
70
|
+
console.log(`📊 ${scenario.name}`);
|
|
71
|
+
console.log(` ${scenario.description}`);
|
|
72
|
+
console.log(` Profile: ${scenario.profile}`);
|
|
73
|
+
|
|
74
|
+
const result = {
|
|
75
|
+
scenario: scenario.name,
|
|
76
|
+
success: false,
|
|
77
|
+
duration: 0,
|
|
78
|
+
memoryUsed: 0,
|
|
79
|
+
violationsFound: 0,
|
|
80
|
+
batchesProcessed: 0,
|
|
81
|
+
error: null
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const startTime = performance.now();
|
|
86
|
+
const memoryBefore = process.memoryUsage().heapUsed;
|
|
87
|
+
|
|
88
|
+
// Simulate batch processing
|
|
89
|
+
const batchResults = await this.simulateBatchProcessing(scenario);
|
|
90
|
+
|
|
91
|
+
const endTime = performance.now();
|
|
92
|
+
const memoryAfter = process.memoryUsage().heapUsed;
|
|
93
|
+
|
|
94
|
+
result.success = true;
|
|
95
|
+
result.duration = endTime - startTime;
|
|
96
|
+
result.memoryUsed = memoryAfter - memoryBefore;
|
|
97
|
+
result.violationsFound = batchResults.totalViolations;
|
|
98
|
+
result.batchesProcessed = batchResults.batchesProcessed;
|
|
99
|
+
|
|
100
|
+
console.log(` ✅ SUCCESS: ${(result.duration/1000).toFixed(2)}s`);
|
|
101
|
+
console.log(` 📊 Memory: ${Math.round(result.memoryUsed/1024/1024)}MB`);
|
|
102
|
+
console.log(` 🎯 Violations: ${result.violationsFound}`);
|
|
103
|
+
console.log(` 📦 Batches: ${result.batchesProcessed}`);
|
|
104
|
+
|
|
105
|
+
} catch (error) {
|
|
106
|
+
result.error = error.message;
|
|
107
|
+
console.log(` ❌ FAILED: ${error.message}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.results.push(result);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 🔄 Simulate optimized batch processing
|
|
115
|
+
*/
|
|
116
|
+
async simulateBatchProcessing(scenario) {
|
|
117
|
+
const batchSize = this.getBatchSize(scenario.profile);
|
|
118
|
+
const fileBatchSize = this.getFileBatchSize(scenario.profile);
|
|
119
|
+
|
|
120
|
+
// Calculate batches
|
|
121
|
+
const ruleBatches = Math.ceil(scenario.rules / batchSize);
|
|
122
|
+
const fileBatches = Math.ceil(scenario.files / fileBatchSize);
|
|
123
|
+
const totalBatches = ruleBatches * fileBatches;
|
|
124
|
+
|
|
125
|
+
let totalViolations = 0;
|
|
126
|
+
let batchesProcessed = 0;
|
|
127
|
+
|
|
128
|
+
// Simulate progressive batch processing
|
|
129
|
+
for (let ruleBatch = 0; ruleBatch < ruleBatches; ruleBatch++) {
|
|
130
|
+
for (let fileBatch = 0; fileBatch < fileBatches; fileBatch++) {
|
|
131
|
+
// Simulate batch processing time
|
|
132
|
+
const batchDelay = this.getBatchDelay(scenario.profile);
|
|
133
|
+
await this.sleep(batchDelay);
|
|
134
|
+
|
|
135
|
+
// Simulate violations found
|
|
136
|
+
const violationsInBatch = Math.floor(Math.random() * 5) + 1;
|
|
137
|
+
totalViolations += violationsInBatch;
|
|
138
|
+
batchesProcessed++;
|
|
139
|
+
|
|
140
|
+
// Progress indication
|
|
141
|
+
if (batchesProcessed % 5 === 0) {
|
|
142
|
+
const progress = (batchesProcessed / totalBatches * 100).toFixed(1);
|
|
143
|
+
console.log(` ⚡ Progress: ${progress}% (${batchesProcessed}/${totalBatches} batches)`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Simulate memory management
|
|
147
|
+
if (batchesProcessed % 10 === 0) {
|
|
148
|
+
await this.simulateMemoryCleanup();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
totalViolations,
|
|
155
|
+
batchesProcessed
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* ⚙️ Get batch size for performance profile
|
|
161
|
+
*/
|
|
162
|
+
getBatchSize(profile) {
|
|
163
|
+
const sizes = {
|
|
164
|
+
fast: 20,
|
|
165
|
+
balanced: 15,
|
|
166
|
+
careful: 10,
|
|
167
|
+
conservative: 5
|
|
168
|
+
};
|
|
169
|
+
return sizes[profile] || 10;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 📁 Get file batch size for performance profile
|
|
174
|
+
*/
|
|
175
|
+
getFileBatchSize(profile) {
|
|
176
|
+
const sizes = {
|
|
177
|
+
fast: 100,
|
|
178
|
+
balanced: 75,
|
|
179
|
+
careful: 50,
|
|
180
|
+
conservative: 25
|
|
181
|
+
};
|
|
182
|
+
return sizes[profile] || 50;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* ⏱️ Get batch processing delay (simulates real processing time)
|
|
187
|
+
*/
|
|
188
|
+
getBatchDelay(profile) {
|
|
189
|
+
const delays = {
|
|
190
|
+
fast: 50, // 50ms per batch
|
|
191
|
+
balanced: 100, // 100ms per batch
|
|
192
|
+
careful: 200, // 200ms per batch
|
|
193
|
+
conservative: 300 // 300ms per batch
|
|
194
|
+
};
|
|
195
|
+
return delays[profile] || 100;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 🧠 Simulate memory cleanup
|
|
200
|
+
*/
|
|
201
|
+
async simulateMemoryCleanup() {
|
|
202
|
+
// Simulate garbage collection
|
|
203
|
+
if (global.gc) {
|
|
204
|
+
global.gc();
|
|
205
|
+
}
|
|
206
|
+
await this.sleep(10); // Brief pause for cleanup
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 😴 Sleep utility
|
|
211
|
+
*/
|
|
212
|
+
sleep(ms) {
|
|
213
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 📊 Print results summary
|
|
218
|
+
*/
|
|
219
|
+
printSummary() {
|
|
220
|
+
console.log('📊 Batch Processing Performance Summary');
|
|
221
|
+
console.log('======================================\n');
|
|
222
|
+
|
|
223
|
+
const successful = this.results.filter(r => r.success);
|
|
224
|
+
const failed = this.results.filter(r => !r.success);
|
|
225
|
+
|
|
226
|
+
console.log(`Total Scenarios: ${this.results.length}`);
|
|
227
|
+
console.log(`Successful: ${successful.length} ✅`);
|
|
228
|
+
console.log(`Failed: ${failed.length} ❌`);
|
|
229
|
+
console.log(`Success Rate: ${((successful.length/this.results.length)*100).toFixed(1)}%\n`);
|
|
230
|
+
|
|
231
|
+
if (successful.length > 0) {
|
|
232
|
+
console.log('🎯 Performance Achievements:');
|
|
233
|
+
|
|
234
|
+
for (const result of successful) {
|
|
235
|
+
console.log(` ✅ ${result.scenario}: ${(result.duration/1000).toFixed(2)}s, ${Math.round(result.memoryUsed/1024/1024)}MB`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
console.log('');
|
|
239
|
+
|
|
240
|
+
// Performance analysis
|
|
241
|
+
const totalFiles = successful.reduce((sum, r) => {
|
|
242
|
+
const scenario = DEMO_SCENARIOS.find(s => s.name === r.scenario);
|
|
243
|
+
return sum + (scenario ? scenario.files : 0);
|
|
244
|
+
}, 0);
|
|
245
|
+
|
|
246
|
+
const totalRules = successful.reduce((sum, r) => {
|
|
247
|
+
const scenario = DEMO_SCENARIOS.find(s => s.name === r.scenario);
|
|
248
|
+
return sum + (scenario ? scenario.rules : 0);
|
|
249
|
+
}, 0);
|
|
250
|
+
|
|
251
|
+
const totalDuration = successful.reduce((sum, r) => sum + r.duration, 0);
|
|
252
|
+
const totalViolations = successful.reduce((sum, r) => sum + r.violationsFound, 0);
|
|
253
|
+
|
|
254
|
+
console.log('📈 Aggregate Performance:');
|
|
255
|
+
console.log(` 📁 Total files processed: ${totalFiles}`);
|
|
256
|
+
console.log(` 📋 Total rules executed: ${totalRules}`);
|
|
257
|
+
console.log(` ⏱️ Total time: ${(totalDuration/1000).toFixed(2)}s`);
|
|
258
|
+
console.log(` 🎯 Total violations: ${totalViolations}`);
|
|
259
|
+
console.log(` ⚡ Throughput: ${(totalFiles/(totalDuration/1000)).toFixed(1)} files/sec`);
|
|
260
|
+
console.log(` 📊 Rule execution rate: ${(totalRules/(totalDuration/1000)).toFixed(1)} rules/sec\n`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (failed.length > 0) {
|
|
264
|
+
console.log('❌ Failed Scenarios:');
|
|
265
|
+
for (const result of failed) {
|
|
266
|
+
console.log(` ❌ ${result.scenario}: ${result.error}`);
|
|
267
|
+
}
|
|
268
|
+
console.log('');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Recommendations
|
|
272
|
+
console.log('💡 Performance Optimization Recommendations:');
|
|
273
|
+
console.log('');
|
|
274
|
+
|
|
275
|
+
const largestSuccessful = successful.reduce((max, r) => {
|
|
276
|
+
const scenario = DEMO_SCENARIOS.find(s => s.name === r.scenario);
|
|
277
|
+
return (scenario && scenario.files > (max.files || 0)) ? scenario : max;
|
|
278
|
+
}, {});
|
|
279
|
+
|
|
280
|
+
if (largestSuccessful.files >= 1000) {
|
|
281
|
+
console.log('🏆 EXCELLENT: SunLint can handle enterprise-scale projects (1000+ files)');
|
|
282
|
+
console.log(' ✅ Batch processing prevents timeouts');
|
|
283
|
+
console.log(' ✅ Memory management prevents crashes');
|
|
284
|
+
console.log(' ✅ Progressive results provide good UX');
|
|
285
|
+
} else if (largestSuccessful.files >= 500) {
|
|
286
|
+
console.log('✅ GOOD: SunLint handles medium-large projects well (500+ files)');
|
|
287
|
+
console.log(' ➡️ Consider testing with larger projects');
|
|
288
|
+
} else {
|
|
289
|
+
console.log('⚠️ LIMITED: SunLint tested up to small-medium projects');
|
|
290
|
+
console.log(' ➡️ Need to test larger scenarios');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.log('');
|
|
294
|
+
console.log('🚀 Next Steps:');
|
|
295
|
+
console.log(' 1. Test with real large codebases');
|
|
296
|
+
console.log(' 2. Measure actual memory usage patterns');
|
|
297
|
+
console.log(' 3. Fine-tune batch sizes for different rule types');
|
|
298
|
+
console.log(' 4. Implement adaptive timeout strategies');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 🎛️ Show real CLI commands that would achieve these results
|
|
303
|
+
*/
|
|
304
|
+
showCliExamples() {
|
|
305
|
+
console.log('\\n🎛️ CLI Commands to Reproduce These Results:');
|
|
306
|
+
console.log('=============================================\\n');
|
|
307
|
+
|
|
308
|
+
for (const scenario of DEMO_SCENARIOS) {
|
|
309
|
+
console.log(`# ${scenario.name}`);
|
|
310
|
+
console.log(`sunlint --all --input=src \\\\`);
|
|
311
|
+
console.log(` --performance-profile=${scenario.profile} \\\\`);
|
|
312
|
+
console.log(` --max-files=${scenario.files} \\\\`);
|
|
313
|
+
console.log(` --progressive-results \\\\`);
|
|
314
|
+
console.log(` --adaptive-timeout \\\\`);
|
|
315
|
+
console.log(` --verbose`);
|
|
316
|
+
console.log('');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Run demo if called directly
|
|
322
|
+
if (require.main === module) {
|
|
323
|
+
const demo = new BatchProcessingDemo();
|
|
324
|
+
demo.run()
|
|
325
|
+
.then(() => {
|
|
326
|
+
demo.showCliExamples();
|
|
327
|
+
})
|
|
328
|
+
.catch(error => {
|
|
329
|
+
console.error('❌ Demo failed:', error);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = BatchProcessingDemo;
|