swynx-lite 1.0.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 (36) hide show
  1. package/README.md +113 -0
  2. package/bin/swynx-lite +3 -0
  3. package/package.json +47 -0
  4. package/src/clean.mjs +280 -0
  5. package/src/cli.mjs +264 -0
  6. package/src/config.mjs +121 -0
  7. package/src/output/console.mjs +298 -0
  8. package/src/output/json.mjs +76 -0
  9. package/src/output/progress.mjs +57 -0
  10. package/src/scan.mjs +143 -0
  11. package/src/security.mjs +62 -0
  12. package/src/shared/fixer/barrel-cleaner.mjs +192 -0
  13. package/src/shared/fixer/import-cleaner.mjs +237 -0
  14. package/src/shared/fixer/quarantine.mjs +218 -0
  15. package/src/shared/scanner/analysers/buildSystems.mjs +647 -0
  16. package/src/shared/scanner/analysers/configParsers.mjs +1086 -0
  17. package/src/shared/scanner/analysers/deadcode.mjs +6194 -0
  18. package/src/shared/scanner/analysers/entryPointDetector.mjs +634 -0
  19. package/src/shared/scanner/analysers/generatedCode.mjs +297 -0
  20. package/src/shared/scanner/analysers/imports.mjs +60 -0
  21. package/src/shared/scanner/discovery.mjs +240 -0
  22. package/src/shared/scanner/parse-worker.mjs +82 -0
  23. package/src/shared/scanner/parsers/assets.mjs +44 -0
  24. package/src/shared/scanner/parsers/csharp.mjs +400 -0
  25. package/src/shared/scanner/parsers/css.mjs +60 -0
  26. package/src/shared/scanner/parsers/go.mjs +445 -0
  27. package/src/shared/scanner/parsers/java.mjs +364 -0
  28. package/src/shared/scanner/parsers/javascript.mjs +823 -0
  29. package/src/shared/scanner/parsers/kotlin.mjs +350 -0
  30. package/src/shared/scanner/parsers/python.mjs +497 -0
  31. package/src/shared/scanner/parsers/registry.mjs +233 -0
  32. package/src/shared/scanner/parsers/rust.mjs +427 -0
  33. package/src/shared/scanner/scan-dead-code.mjs +316 -0
  34. package/src/shared/security/patterns.mjs +349 -0
  35. package/src/shared/security/proximity.mjs +84 -0
  36. package/src/shared/security/scanner.mjs +269 -0
@@ -0,0 +1,634 @@
1
+ // src/scanner/analysers/entryPointDetector.mjs
2
+ // Unified entry point detection for multi-language dead code analysis
3
+
4
+ import { readFileSync, existsSync } from 'fs';
5
+ import { join, dirname, basename, extname, relative } from 'path';
6
+ import { globSync } from 'glob';
7
+ import { collectConfigEntryPoints } from './configParsers.mjs';
8
+ import { detectBuildSystems, getPackageDirectories } from './buildSystems.mjs';
9
+
10
+ /**
11
+ * Default entry point file patterns (language-agnostic)
12
+ */
13
+ const DEFAULT_ENTRY_PATTERNS = [
14
+ // JavaScript/TypeScript
15
+ /^index\.(m?js|jsx?|tsx?)$/,
16
+ /^main\.(m?js|jsx?|tsx?)$/,
17
+ /^app\.(m?js|jsx?|tsx?)$/,
18
+ /^server\.(m?js|jsx?|tsx?)$/,
19
+ /^cli\.(m?js|jsx?|tsx?)$/,
20
+ /^entry\.(m?js|jsx?|tsx?)$/,
21
+ /src\/index\.(m?js|jsx?|tsx?)$/,
22
+ /src\/main\.(m?js|jsx?|tsx?)$/,
23
+ /src\/app\.(m?js|jsx?|tsx?)$/,
24
+
25
+ // Python
26
+ /^__main__\.py$/,
27
+ /^main\.py$/,
28
+ /^app\.py$/,
29
+ /^manage\.py$/,
30
+ /^wsgi\.py$/,
31
+ /^asgi\.py$/,
32
+
33
+ // Go
34
+ /^main\.go$/,
35
+ /cmd\/[^/]+\/main\.go$/,
36
+
37
+ // Java
38
+ /^Main\.java$/,
39
+ /Application\.java$/,
40
+ /SpringBootApplication/,
41
+
42
+ // C#
43
+ /^Program\.cs$/,
44
+ /^Startup\.cs$/,
45
+
46
+ // Ruby
47
+ /^Rakefile$/,
48
+ /^config\.ru$/,
49
+ /^application\.rb$/,
50
+
51
+ // PHP
52
+ /^index\.php$/,
53
+ /^artisan$/,
54
+
55
+ // Rust
56
+ /^main\.rs$/,
57
+ /src\/main\.rs$/,
58
+ /src\/bin\/[^/]+\.rs$/
59
+ ];
60
+
61
+ /**
62
+ * DI decorator patterns by language/framework
63
+ */
64
+ const DI_DECORATORS_BY_LANGUAGE = {
65
+ javascript: [
66
+ 'Service', 'Injectable', 'Controller', 'Module', 'Component',
67
+ 'Entity', 'Repository', 'Resolver', 'Guard', 'Pipe',
68
+ 'EventSubscriber', 'Subscriber', 'Singleton'
69
+ ],
70
+ java: [
71
+ 'Service', 'Component', 'Repository', 'Controller', 'RestController',
72
+ 'Configuration', 'Bean', 'Autowired', 'Inject', 'Named', 'Singleton',
73
+ 'Entity', 'ManagedBean', 'Stateless', 'Stateful'
74
+ ],
75
+ csharp: [
76
+ 'Controller', 'ApiController', 'Service', 'Scoped', 'Singleton', 'Transient',
77
+ 'Entity', 'Table', 'DbContext', 'Injectable'
78
+ ],
79
+ python: [
80
+ 'app.route', 'router.get', 'router.post', 'router.put', 'router.delete',
81
+ 'task', 'shared_task', 'celery.task'
82
+ ]
83
+ };
84
+
85
+ /**
86
+ * Unified entry point detection result
87
+ */
88
+ class EntryPointResult {
89
+ constructor() {
90
+ this.entryPoints = new Map(); // filePath -> { reason, source, confidence, isDynamic }
91
+ this.sources = {
92
+ packageJson: [],
93
+ html: [],
94
+ bundlerConfig: [],
95
+ ciConfig: [],
96
+ diAnnotation: [],
97
+ convention: [],
98
+ buildSystem: []
99
+ };
100
+ }
101
+
102
+ add(filePath, info) {
103
+ const existing = this.entryPoints.get(filePath);
104
+ if (!existing || info.confidence > (existing.confidence || 0)) {
105
+ this.entryPoints.set(filePath, info);
106
+ }
107
+ if (info.source && this.sources[info.source]) {
108
+ this.sources[info.source].push(filePath);
109
+ }
110
+ }
111
+
112
+ has(filePath) {
113
+ return this.entryPoints.has(filePath);
114
+ }
115
+
116
+ get(filePath) {
117
+ return this.entryPoints.get(filePath);
118
+ }
119
+
120
+ getAll() {
121
+ return [...this.entryPoints.entries()].map(([file, info]) => ({
122
+ file,
123
+ ...info
124
+ }));
125
+ }
126
+
127
+ getFiles() {
128
+ return new Set(this.entryPoints.keys());
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Extract entry points from package.json
134
+ */
135
+ function extractPackageJsonEntries(packageJson, projectPath = '') {
136
+ const entries = [];
137
+
138
+ if (!packageJson) return entries;
139
+
140
+ // main field
141
+ if (packageJson.main) {
142
+ entries.push({
143
+ path: packageJson.main.replace(/^\.\//, ''),
144
+ reason: 'Package main entry',
145
+ source: 'packageJson',
146
+ confidence: 0.9
147
+ });
148
+ }
149
+
150
+ // module field (ESM entry)
151
+ if (packageJson.module) {
152
+ entries.push({
153
+ path: packageJson.module.replace(/^\.\//, ''),
154
+ reason: 'Package module entry (ESM)',
155
+ source: 'packageJson',
156
+ confidence: 0.9
157
+ });
158
+ }
159
+
160
+ // bin field
161
+ if (packageJson.bin) {
162
+ const bins = typeof packageJson.bin === 'string'
163
+ ? [packageJson.bin]
164
+ : Object.values(packageJson.bin);
165
+ for (const bin of bins) {
166
+ entries.push({
167
+ path: bin.replace(/^\.\//, ''),
168
+ reason: 'Package bin entry',
169
+ source: 'packageJson',
170
+ confidence: 0.95
171
+ });
172
+ }
173
+ }
174
+
175
+ // exports field
176
+ if (packageJson.exports) {
177
+ const extractExports = (exp, path = '') => {
178
+ if (typeof exp === 'string') {
179
+ entries.push({
180
+ path: exp.replace(/^\.\//, ''),
181
+ reason: `Package exports entry${path ? ` (${path})` : ''}`,
182
+ source: 'packageJson',
183
+ confidence: 0.9
184
+ });
185
+ } else if (typeof exp === 'object' && exp !== null) {
186
+ for (const [key, value] of Object.entries(exp)) {
187
+ extractExports(value, key);
188
+ }
189
+ }
190
+ };
191
+ extractExports(packageJson.exports);
192
+ }
193
+
194
+ // scripts (extract referenced files)
195
+ if (packageJson.scripts) {
196
+ for (const [name, script] of Object.entries(packageJson.scripts)) {
197
+ // Match node/npx/ts-node commands
198
+ const matches = script.matchAll(/(?:node|npx|ts-node|tsx)\s+([^\s&|;]+)/g);
199
+ for (const match of matches) {
200
+ const file = match[1].replace(/^\.\//, '');
201
+ if (file.match(/\.(m?js|ts|tsx?)$/)) {
202
+ entries.push({
203
+ path: file,
204
+ reason: `Referenced in npm script "${name}"`,
205
+ source: 'packageJson',
206
+ confidence: 0.85
207
+ });
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ return entries;
214
+ }
215
+
216
+ /**
217
+ * Extract entry points from HTML files
218
+ */
219
+ function extractHtmlEntries(projectPath) {
220
+ const entries = [];
221
+
222
+ if (!projectPath) return entries;
223
+
224
+ const htmlPatterns = [
225
+ '*.html',
226
+ 'public/*.html',
227
+ 'src/*.html',
228
+ 'static/*.html',
229
+ 'views/**/*.html',
230
+ 'templates/**/*.html'
231
+ ];
232
+
233
+ for (const pattern of htmlPatterns) {
234
+ try {
235
+ const htmlFiles = globSync(pattern, {
236
+ cwd: projectPath,
237
+ nodir: true,
238
+ ignore: ['node_modules/**', 'dist/**', 'build/**']
239
+ });
240
+
241
+ for (const htmlFile of htmlFiles) {
242
+ const fullPath = join(projectPath, htmlFile);
243
+ try {
244
+ const content = readFileSync(fullPath, 'utf-8');
245
+ const htmlDir = dirname(htmlFile);
246
+
247
+ // Match script tags with src attribute
248
+ const scriptPattern = /<script[^>]*\ssrc=["']([^"']+)["'][^>]*>/gi;
249
+ let match;
250
+ while ((match = scriptPattern.exec(content)) !== null) {
251
+ let src = match[1];
252
+
253
+ // Skip external scripts
254
+ if (src.startsWith('http://') || src.startsWith('https://') || src.startsWith('//')) {
255
+ continue;
256
+ }
257
+
258
+ // Resolve relative paths
259
+ if (src.startsWith('./')) {
260
+ src = join(htmlDir, src.slice(2));
261
+ } else if (src.startsWith('/')) {
262
+ src = src.slice(1);
263
+ } else if (!src.includes('://')) {
264
+ src = join(htmlDir, src);
265
+ }
266
+
267
+ src = src.replace(/\\/g, '/').replace(/^\.\//, '');
268
+
269
+ entries.push({
270
+ path: src,
271
+ reason: `Referenced in ${htmlFile}`,
272
+ source: 'html',
273
+ confidence: 0.9
274
+ });
275
+ }
276
+ } catch {
277
+ // Ignore read errors
278
+ }
279
+ }
280
+ } catch {
281
+ // Ignore glob errors
282
+ }
283
+ }
284
+
285
+ return entries;
286
+ }
287
+
288
+ /**
289
+ * Check if a file matches entry point patterns
290
+ */
291
+ function matchesEntryPattern(filePath, customPatterns = []) {
292
+ const allPatterns = [...DEFAULT_ENTRY_PATTERNS, ...customPatterns];
293
+
294
+ for (const pattern of allPatterns) {
295
+ if (pattern.test(filePath)) {
296
+ return {
297
+ matches: true,
298
+ pattern: pattern.toString(),
299
+ confidence: 0.7
300
+ };
301
+ }
302
+ }
303
+
304
+ return { matches: false };
305
+ }
306
+
307
+ /**
308
+ * Detect entry points from multi-language build systems
309
+ */
310
+ function detectBuildSystemEntries(projectPath) {
311
+ const entries = [];
312
+
313
+ if (!projectPath) return entries;
314
+
315
+ const buildSystems = detectBuildSystems(projectPath);
316
+
317
+ for (const system of buildSystems) {
318
+ // Each build system may define entry points differently
319
+ switch (system.type) {
320
+ case 'gradle':
321
+ case 'maven':
322
+ // Java: look for src/main/java/**/Application.java
323
+ try {
324
+ const javaFiles = globSync('src/main/java/**/*Application.java', {
325
+ cwd: projectPath,
326
+ nodir: true
327
+ });
328
+ for (const file of javaFiles) {
329
+ entries.push({
330
+ path: file,
331
+ reason: `Spring Boot application (${system.type})`,
332
+ source: 'buildSystem',
333
+ confidence: 0.9
334
+ });
335
+ }
336
+ } catch { /* ignore */ }
337
+ break;
338
+
339
+ case 'cargo':
340
+ // Rust: src/main.rs and src/bin/*.rs
341
+ if (existsSync(join(projectPath, 'src/main.rs'))) {
342
+ entries.push({
343
+ path: 'src/main.rs',
344
+ reason: 'Cargo binary entry',
345
+ source: 'buildSystem',
346
+ confidence: 0.95
347
+ });
348
+ }
349
+ try {
350
+ const binFiles = globSync('src/bin/*.rs', {
351
+ cwd: projectPath,
352
+ nodir: true
353
+ });
354
+ for (const file of binFiles) {
355
+ entries.push({
356
+ path: file,
357
+ reason: 'Cargo binary entry',
358
+ source: 'buildSystem',
359
+ confidence: 0.95
360
+ });
361
+ }
362
+ } catch { /* ignore */ }
363
+ break;
364
+
365
+ case 'go':
366
+ // Go: main.go and cmd/*/main.go
367
+ if (existsSync(join(projectPath, 'main.go'))) {
368
+ entries.push({
369
+ path: 'main.go',
370
+ reason: 'Go main entry',
371
+ source: 'buildSystem',
372
+ confidence: 0.95
373
+ });
374
+ }
375
+ try {
376
+ const cmdFiles = globSync('cmd/*/main.go', {
377
+ cwd: projectPath,
378
+ nodir: true
379
+ });
380
+ for (const file of cmdFiles) {
381
+ entries.push({
382
+ path: file,
383
+ reason: 'Go cmd entry',
384
+ source: 'buildSystem',
385
+ confidence: 0.95
386
+ });
387
+ }
388
+ } catch { /* ignore */ }
389
+ break;
390
+
391
+ case 'dotnet':
392
+ // C#: Program.cs
393
+ if (existsSync(join(projectPath, 'Program.cs'))) {
394
+ entries.push({
395
+ path: 'Program.cs',
396
+ reason: '.NET Program entry',
397
+ source: 'buildSystem',
398
+ confidence: 0.95
399
+ });
400
+ }
401
+ break;
402
+
403
+ case 'python':
404
+ // Python: __main__.py, manage.py
405
+ for (const file of ['__main__.py', 'manage.py', 'app.py', 'main.py']) {
406
+ if (existsSync(join(projectPath, file))) {
407
+ entries.push({
408
+ path: file,
409
+ reason: 'Python entry point',
410
+ source: 'buildSystem',
411
+ confidence: 0.9
412
+ });
413
+ }
414
+ }
415
+ break;
416
+ }
417
+ }
418
+
419
+ return entries;
420
+ }
421
+
422
+ /**
423
+ * Unified entry point detector
424
+ */
425
+ export class EntryPointDetector {
426
+ constructor(options = {}) {
427
+ this.projectPath = options.projectPath || process.cwd();
428
+ this.packageJson = options.packageJson || {};
429
+ this.customPatterns = options.customPatterns || [];
430
+ this.diDecorators = options.diDecorators || DI_DECORATORS_BY_LANGUAGE.javascript;
431
+ this.result = new EntryPointResult();
432
+ this.initialized = false;
433
+ }
434
+
435
+ /**
436
+ * Initialize detection - collect all entry points from various sources
437
+ */
438
+ initialize() {
439
+ if (this.initialized) return this.result;
440
+
441
+ // 1. Package.json entries
442
+ const pkgEntries = extractPackageJsonEntries(this.packageJson, this.projectPath);
443
+ for (const entry of pkgEntries) {
444
+ this.result.add(entry.path, entry);
445
+ }
446
+
447
+ // 2. HTML entries
448
+ const htmlEntries = extractHtmlEntries(this.projectPath);
449
+ for (const entry of htmlEntries) {
450
+ this.result.add(entry.path, entry);
451
+ }
452
+
453
+ // 3. Bundler/CI config entries
454
+ const configData = collectConfigEntryPoints(this.projectPath);
455
+ for (const entryPath of configData.entries) {
456
+ this.result.add(entryPath, {
457
+ reason: 'Bundler/CI config entry',
458
+ source: 'bundlerConfig',
459
+ confidence: 0.85
460
+ });
461
+ }
462
+
463
+ // 4. Build system entries
464
+ const buildEntries = detectBuildSystemEntries(this.projectPath);
465
+ for (const entry of buildEntries) {
466
+ this.result.add(entry.path, entry);
467
+ }
468
+
469
+ this.initialized = true;
470
+ return this.result;
471
+ }
472
+
473
+ /**
474
+ * Check if a file is an entry point
475
+ * @param {string} filePath - Relative file path
476
+ * @param {Object} options - Additional context (classes, decorators, etc.)
477
+ */
478
+ isEntryPoint(filePath, options = {}) {
479
+ this.initialize();
480
+
481
+ const normalizedPath = filePath.replace(/^\.\//, '');
482
+
483
+ // 1. Check pre-collected entries
484
+ const preCollected = this.result.get(normalizedPath);
485
+ if (preCollected) {
486
+ return { isEntry: true, ...preCollected };
487
+ }
488
+
489
+ // 2. Check pattern matches
490
+ const patternMatch = matchesEntryPattern(normalizedPath, this.customPatterns);
491
+ if (patternMatch.matches) {
492
+ return {
493
+ isEntry: true,
494
+ reason: 'Matches entry point pattern',
495
+ source: 'convention',
496
+ confidence: patternMatch.confidence
497
+ };
498
+ }
499
+
500
+ // 3. Check DI decorators on classes
501
+ if (options.classes?.length) {
502
+ for (const cls of options.classes) {
503
+ if (cls.decorators?.length) {
504
+ const diMatch = cls.decorators.find(d =>
505
+ this.diDecorators.includes(d.name)
506
+ );
507
+ if (diMatch) {
508
+ return {
509
+ isEntry: true,
510
+ reason: `Class ${cls.name} has DI decorator: @${diMatch.name}`,
511
+ source: 'diAnnotation',
512
+ confidence: 0.9,
513
+ isDynamic: true
514
+ };
515
+ }
516
+ }
517
+ }
518
+ }
519
+
520
+ // 4. Check for framework-specific patterns in file metadata
521
+ if (options.metadata) {
522
+ // Python frameworks
523
+ if (options.metadata.hasMainBlock) {
524
+ return {
525
+ isEntry: true,
526
+ reason: 'Has __main__ block',
527
+ source: 'convention',
528
+ confidence: 0.95
529
+ };
530
+ }
531
+ if (options.metadata.isCelery) {
532
+ return {
533
+ isEntry: true,
534
+ reason: 'Celery task file',
535
+ source: 'diAnnotation',
536
+ confidence: 0.9,
537
+ isDynamic: true
538
+ };
539
+ }
540
+
541
+ // Go frameworks
542
+ if (options.metadata.hasMainFunction && options.metadata.isMainPackage) {
543
+ return {
544
+ isEntry: true,
545
+ reason: 'Go main package with main()',
546
+ source: 'convention',
547
+ confidence: 0.95
548
+ };
549
+ }
550
+ if (options.metadata.usesWire || options.metadata.usesFx || options.metadata.usesDig) {
551
+ return {
552
+ isEntry: true,
553
+ reason: 'Uses Go DI framework',
554
+ source: 'diAnnotation',
555
+ confidence: 0.9,
556
+ isDynamic: true
557
+ };
558
+ }
559
+
560
+ // Java frameworks
561
+ if (options.metadata.isSpringComponent) {
562
+ return {
563
+ isEntry: true,
564
+ reason: 'Spring component',
565
+ source: 'diAnnotation',
566
+ confidence: 0.9,
567
+ isDynamic: true
568
+ };
569
+ }
570
+
571
+ // C# frameworks
572
+ if (options.metadata.hasMainMethod || options.metadata.hasTopLevelStatements) {
573
+ return {
574
+ isEntry: true,
575
+ reason: 'C# entry point',
576
+ source: 'convention',
577
+ confidence: 0.95
578
+ };
579
+ }
580
+ }
581
+
582
+ return { isEntry: false };
583
+ }
584
+
585
+ /**
586
+ * Get all detected entry point files
587
+ */
588
+ getEntryPointFiles() {
589
+ this.initialize();
590
+ return this.result.getFiles();
591
+ }
592
+
593
+ /**
594
+ * Get detailed entry point information
595
+ */
596
+ getEntryPointDetails() {
597
+ this.initialize();
598
+ return this.result.getAll();
599
+ }
600
+
601
+ /**
602
+ * Get entry points grouped by source
603
+ */
604
+ getEntryPointsBySource() {
605
+ this.initialize();
606
+ return this.result.sources;
607
+ }
608
+ }
609
+
610
+ /**
611
+ * Create a detector with default configuration
612
+ */
613
+ export function createEntryPointDetector(projectPath, packageJson = {}, options = {}) {
614
+ return new EntryPointDetector({
615
+ projectPath,
616
+ packageJson,
617
+ ...options
618
+ });
619
+ }
620
+
621
+ /**
622
+ * Quick check if a file is likely an entry point (without full initialization)
623
+ */
624
+ export function isLikelyEntryPoint(filePath) {
625
+ return matchesEntryPattern(filePath).matches;
626
+ }
627
+
628
+ export default {
629
+ EntryPointDetector,
630
+ createEntryPointDetector,
631
+ isLikelyEntryPoint,
632
+ DEFAULT_ENTRY_PATTERNS,
633
+ DI_DECORATORS_BY_LANGUAGE
634
+ };