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,445 @@
1
+ // src/scanner/parsers/go.mjs
2
+ // Go parser with Wire, Fx, Dig DI framework support
3
+
4
+ import { readFileSync, existsSync } from 'fs';
5
+
6
+ /**
7
+ * Parse a Go file and extract functions, types, imports
8
+ * @param {Object|string} file - File object or path
9
+ * @returns {Object} - Parse result
10
+ */
11
+ export async function parse(file) {
12
+ const filePath = typeof file === 'string' ? file : file.path;
13
+ const relativePath = typeof file === 'string' ? file : file.relativePath;
14
+
15
+ if (!existsSync(filePath)) {
16
+ return createEmptyResult(filePath, relativePath, 'File not found');
17
+ }
18
+
19
+ let content;
20
+ try {
21
+ content = readFileSync(filePath, 'utf-8');
22
+ } catch (error) {
23
+ return createEmptyResult(filePath, relativePath, `Read error: ${error.message}`);
24
+ }
25
+
26
+ try {
27
+ const lines = content.split('\n');
28
+ const functions = [];
29
+ const classes = []; // Structs and interfaces in Go
30
+ const exports = [];
31
+ const imports = [];
32
+ let packageName = null;
33
+
34
+ // Extract package declaration
35
+ const packageMatch = content.match(/^\s*package\s+(\w+)/m);
36
+ if (packageMatch) {
37
+ packageName = packageMatch[1];
38
+ }
39
+
40
+ // Extract imports
41
+ // Single import: import "fmt"
42
+ // Multiple imports: import ( "fmt" \n "os" )
43
+ const singleImportPattern = /^\s*import\s+"([^"]+)"/gm;
44
+ let match;
45
+ while ((match = singleImportPattern.exec(content)) !== null) {
46
+ const lineNum = content.substring(0, match.index).split('\n').length;
47
+ imports.push({
48
+ module: match[1],
49
+ type: 'single',
50
+ line: lineNum
51
+ });
52
+ }
53
+
54
+ // Multi-import block
55
+ const importBlockMatch = content.match(/import\s*\(([\s\S]*?)\)/);
56
+ if (importBlockMatch) {
57
+ const blockStart = content.indexOf(importBlockMatch[0]);
58
+ const blockLines = importBlockMatch[1].split('\n');
59
+ let blockLineNum = content.substring(0, blockStart).split('\n').length;
60
+
61
+ for (const importLine of blockLines) {
62
+ blockLineNum++;
63
+ const importMatch = importLine.match(/^\s*(?:(\w+)\s+)?["']([^"']+)["']/);
64
+ if (importMatch) {
65
+ imports.push({
66
+ module: importMatch[2],
67
+ alias: importMatch[1] || null,
68
+ type: 'block',
69
+ line: blockLineNum
70
+ });
71
+ }
72
+ }
73
+ }
74
+
75
+ // Parse functions and methods
76
+ const funcPattern = /^func\s+(?:\(([^)]+)\)\s+)?(\w+)\s*\(([^)]*)\)(?:\s*\(([^)]*)\)|\s*(\w+))?/gm;
77
+ while ((match = funcPattern.exec(content)) !== null) {
78
+ const lineNum = content.substring(0, match.index).split('\n').length;
79
+ const receiver = match[1] || null;
80
+ const funcName = match[2];
81
+ const params = match[3] || '';
82
+ const returnType = match[4] || match[5] || null;
83
+
84
+ const funcInfo = {
85
+ name: funcName,
86
+ type: receiver ? 'method' : 'function',
87
+ receiver: receiver ? parseReceiver(receiver) : null,
88
+ line: lineNum,
89
+ endLine: findBlockEnd(lines, lineNum - 1),
90
+ params: parseGoParams(params),
91
+ returnType,
92
+ signature: `func ${receiver ? `(${receiver}) ` : ''}${funcName}(${params})`,
93
+ exported: funcName[0] === funcName[0].toUpperCase() // Exported if starts with uppercase
94
+ };
95
+
96
+ funcInfo.lineCount = funcInfo.endLine - funcInfo.line + 1;
97
+
98
+ // Check for main function
99
+ if (funcName === 'main' && packageName === 'main' && !receiver) {
100
+ funcInfo.isMainFunction = true;
101
+ }
102
+
103
+ // Check for init function
104
+ if (funcName === 'init' && !receiver) {
105
+ funcInfo.isInitFunction = true;
106
+ }
107
+
108
+ functions.push(funcInfo);
109
+
110
+ // Add method to corresponding struct
111
+ if (receiver && classes.length > 0) {
112
+ const receiverType = funcInfo.receiver?.type?.replace('*', '');
113
+ const struct = classes.find(c => c.name === receiverType);
114
+ if (struct) {
115
+ struct.methods.push(funcInfo);
116
+ }
117
+ }
118
+ }
119
+
120
+ // Parse struct types
121
+ const structPattern = /^type\s+(\w+)\s+struct\s*\{/gm;
122
+ while ((match = structPattern.exec(content)) !== null) {
123
+ const lineNum = content.substring(0, match.index).split('\n').length;
124
+ const structName = match[1];
125
+
126
+ const structInfo = {
127
+ name: structName,
128
+ type: 'struct',
129
+ line: lineNum,
130
+ endLine: findBlockEnd(lines, lineNum - 1),
131
+ fields: [],
132
+ methods: [],
133
+ exported: structName[0] === structName[0].toUpperCase()
134
+ };
135
+
136
+ structInfo.lineCount = structInfo.endLine - structInfo.line + 1;
137
+
138
+ // Parse fields
139
+ parseStructFields(lines, structInfo);
140
+
141
+ // Find methods already parsed
142
+ structInfo.methods = functions.filter(f =>
143
+ f.receiver?.type?.replace('*', '') === structName
144
+ );
145
+
146
+ classes.push(structInfo);
147
+ }
148
+
149
+ // Parse interface types
150
+ const interfacePattern = /^type\s+(\w+)\s+interface\s*\{/gm;
151
+ while ((match = interfacePattern.exec(content)) !== null) {
152
+ const lineNum = content.substring(0, match.index).split('\n').length;
153
+ const interfaceName = match[1];
154
+
155
+ const interfaceInfo = {
156
+ name: interfaceName,
157
+ type: 'interface',
158
+ line: lineNum,
159
+ endLine: findBlockEnd(lines, lineNum - 1),
160
+ methods: [],
161
+ exported: interfaceName[0] === interfaceName[0].toUpperCase()
162
+ };
163
+
164
+ interfaceInfo.lineCount = interfaceInfo.endLine - interfaceInfo.line + 1;
165
+
166
+ // Parse interface methods
167
+ parseInterfaceMethods(lines, interfaceInfo);
168
+
169
+ classes.push(interfaceInfo);
170
+ }
171
+
172
+ // Determine exports (public functions, types, and constants)
173
+ exports.push(
174
+ ...functions.filter(f => f.exported && !f.receiver).map(f => ({
175
+ name: f.name,
176
+ type: 'function',
177
+ line: f.line
178
+ })),
179
+ ...classes.filter(c => c.exported).map(c => ({
180
+ name: c.name,
181
+ type: c.type,
182
+ line: c.line
183
+ }))
184
+ );
185
+
186
+ // Parse const and var declarations
187
+ const constPattern = /^(?:const|var)\s+(\w+)\s+/gm;
188
+ while ((match = constPattern.exec(content)) !== null) {
189
+ const name = match[1];
190
+ if (name[0] === name[0].toUpperCase()) {
191
+ const lineNum = content.substring(0, match.index).split('\n').length;
192
+ exports.push({
193
+ name,
194
+ type: 'const/var',
195
+ line: lineNum
196
+ });
197
+ }
198
+ }
199
+
200
+ // Check for DI framework usage
201
+ const usesWire = content.includes('wire.Build') || content.includes('wire.NewSet');
202
+ const usesFx = content.includes('fx.New') || content.includes('fx.Provide');
203
+ const usesDig = content.includes('dig.New') || content.includes('container.Provide');
204
+
205
+ return {
206
+ file: { path: filePath, relativePath },
207
+ content,
208
+ functions,
209
+ classes,
210
+ exports,
211
+ imports,
212
+ annotations: [], // Go doesn't have decorators
213
+ lines: lines.length,
214
+ size: content.length,
215
+ parseMethod: 'go-regex',
216
+ metadata: {
217
+ packageName,
218
+ hasMainFunction: functions.some(f => f.isMainFunction),
219
+ hasInitFunction: functions.some(f => f.isInitFunction),
220
+ isMainPackage: packageName === 'main',
221
+ usesWire,
222
+ usesFx,
223
+ usesDig,
224
+ isTestFile: relativePath.endsWith('_test.go')
225
+ }
226
+ };
227
+
228
+ } catch (error) {
229
+ return createEmptyResult(filePath, relativePath, `Parse error: ${error.message}`);
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Parse method receiver
235
+ */
236
+ function parseReceiver(receiver) {
237
+ const match = receiver.match(/(\w+)\s+([\w*]+)/);
238
+ if (match) {
239
+ return {
240
+ name: match[1],
241
+ type: match[2]
242
+ };
243
+ }
244
+ return { name: '', type: receiver.trim() };
245
+ }
246
+
247
+ /**
248
+ * Parse Go function parameters
249
+ */
250
+ function parseGoParams(paramsStr) {
251
+ if (!paramsStr || !paramsStr.trim()) return [];
252
+
253
+ const params = [];
254
+ let current = '';
255
+ let depth = 0;
256
+
257
+ for (const char of paramsStr) {
258
+ if (char === '[' || char === '(' || char === '{') depth++;
259
+ else if (char === ']' || char === ')' || char === '}') depth--;
260
+ else if (char === ',' && depth === 0) {
261
+ if (current.trim()) {
262
+ params.push(parseGoParam(current.trim()));
263
+ }
264
+ current = '';
265
+ continue;
266
+ }
267
+ current += char;
268
+ }
269
+
270
+ if (current.trim()) {
271
+ params.push(parseGoParam(current.trim()));
272
+ }
273
+
274
+ return params;
275
+ }
276
+
277
+ /**
278
+ * Parse a single Go parameter
279
+ */
280
+ function parseGoParam(paramStr) {
281
+ // Handle: name type, name, name ...type
282
+ const parts = paramStr.trim().split(/\s+/);
283
+ if (parts.length >= 2) {
284
+ return {
285
+ name: parts[0],
286
+ type: parts.slice(1).join(' ')
287
+ };
288
+ }
289
+ return { name: '', type: parts[0] };
290
+ }
291
+
292
+ /**
293
+ * Parse struct fields
294
+ */
295
+ function parseStructFields(lines, structInfo) {
296
+ for (let i = structInfo.line; i < structInfo.endLine - 1 && i < lines.length; i++) {
297
+ const line = lines[i].trim();
298
+
299
+ // Skip empty lines, comments, and the struct declaration itself
300
+ if (!line || line.startsWith('//') || line.startsWith('/*') || line === '{') {
301
+ continue;
302
+ }
303
+
304
+ // Field pattern: Name Type `json:"name"`
305
+ const fieldMatch = line.match(/^(\w+)\s+([\w*\[\].]+)(?:\s+`([^`]+)`)?/);
306
+ if (fieldMatch) {
307
+ structInfo.fields.push({
308
+ name: fieldMatch[1],
309
+ type: fieldMatch[2],
310
+ tags: fieldMatch[3] || null,
311
+ exported: fieldMatch[1][0] === fieldMatch[1][0].toUpperCase()
312
+ });
313
+ }
314
+
315
+ // Embedded type: *EmbeddedStruct
316
+ const embeddedMatch = line.match(/^\*?(\w+)$/);
317
+ if (embeddedMatch) {
318
+ structInfo.fields.push({
319
+ name: embeddedMatch[1],
320
+ type: embeddedMatch[1],
321
+ embedded: true
322
+ });
323
+ }
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Parse interface methods
329
+ */
330
+ function parseInterfaceMethods(lines, interfaceInfo) {
331
+ for (let i = interfaceInfo.line; i < interfaceInfo.endLine - 1 && i < lines.length; i++) {
332
+ const line = lines[i].trim();
333
+
334
+ if (!line || line.startsWith('//') || line === '{') continue;
335
+
336
+ // Method pattern: MethodName(params) (returns)
337
+ const methodMatch = line.match(/^(\w+)\s*\(([^)]*)\)(?:\s*\(([^)]*)\)|\s*(\w+))?/);
338
+ if (methodMatch) {
339
+ interfaceInfo.methods.push({
340
+ name: methodMatch[1],
341
+ params: parseGoParams(methodMatch[2]),
342
+ returnType: methodMatch[3] || methodMatch[4] || null
343
+ });
344
+ }
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Find end of a block (matching braces)
350
+ */
351
+ function findBlockEnd(lines, startIndex) {
352
+ let braceCount = 0;
353
+ let started = false;
354
+
355
+ for (let i = startIndex; i < lines.length; i++) {
356
+ const line = lines[i];
357
+
358
+ for (const char of line) {
359
+ if (char === '{') {
360
+ braceCount++;
361
+ started = true;
362
+ } else if (char === '}') {
363
+ braceCount--;
364
+ if (started && braceCount === 0) {
365
+ return i + 1;
366
+ }
367
+ }
368
+ }
369
+ }
370
+
371
+ return startIndex + 1;
372
+ }
373
+
374
+ /**
375
+ * Create empty result
376
+ */
377
+ function createEmptyResult(filePath, relativePath, error) {
378
+ return {
379
+ file: { path: filePath, relativePath },
380
+ content: '',
381
+ functions: [],
382
+ classes: [],
383
+ exports: [],
384
+ imports: [],
385
+ annotations: [],
386
+ lines: 0,
387
+ size: 0,
388
+ error,
389
+ parseMethod: 'none'
390
+ };
391
+ }
392
+
393
+ /**
394
+ * Check if a Go file is an entry point
395
+ */
396
+ export function isEntryPoint(parseResult) {
397
+ // main package with main function
398
+ if (parseResult.metadata?.isMainPackage && parseResult.metadata?.hasMainFunction) {
399
+ return { isEntry: true, reason: 'Is main package with main()' };
400
+ }
401
+
402
+ // Test files
403
+ if (parseResult.metadata?.isTestFile) {
404
+ return { isEntry: true, reason: 'Is test file' };
405
+ }
406
+
407
+ // Wire, Fx, Dig providers are entry points for DI
408
+ if (parseResult.metadata?.usesWire || parseResult.metadata?.usesFx || parseResult.metadata?.usesDig) {
409
+ return { isEntry: true, reason: 'Uses DI framework (Wire/Fx/Dig)' };
410
+ }
411
+
412
+ return { isEntry: false };
413
+ }
414
+
415
+ /**
416
+ * Check if a struct/type has DI registrations
417
+ */
418
+ export function hasDIRegistration(parseResult, typeName) {
419
+ const content = parseResult.content || '';
420
+
421
+ // Check for Wire providers
422
+ if (content.includes(`wire.Struct(new(${typeName})`) ||
423
+ content.includes(`wire.Bind(new(${typeName})`)) {
424
+ return { hasDI: true, framework: 'wire' };
425
+ }
426
+
427
+ // Check for Fx provides
428
+ if (content.includes(`fx.Provide(New${typeName}`) ||
429
+ content.includes(`fx.Provide(func() *${typeName}`)) {
430
+ return { hasDI: true, framework: 'fx' };
431
+ }
432
+
433
+ // Check for Dig provides
434
+ if (content.includes(`Provide(func() *${typeName}`)) {
435
+ return { hasDI: true, framework: 'dig' };
436
+ }
437
+
438
+ return { hasDI: false };
439
+ }
440
+
441
+ export default {
442
+ parse,
443
+ isEntryPoint,
444
+ hasDIRegistration
445
+ };