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.
- package/README.md +113 -0
- package/bin/swynx-lite +3 -0
- package/package.json +47 -0
- package/src/clean.mjs +280 -0
- package/src/cli.mjs +264 -0
- package/src/config.mjs +121 -0
- package/src/output/console.mjs +298 -0
- package/src/output/json.mjs +76 -0
- package/src/output/progress.mjs +57 -0
- package/src/scan.mjs +143 -0
- package/src/security.mjs +62 -0
- package/src/shared/fixer/barrel-cleaner.mjs +192 -0
- package/src/shared/fixer/import-cleaner.mjs +237 -0
- package/src/shared/fixer/quarantine.mjs +218 -0
- package/src/shared/scanner/analysers/buildSystems.mjs +647 -0
- package/src/shared/scanner/analysers/configParsers.mjs +1086 -0
- package/src/shared/scanner/analysers/deadcode.mjs +6194 -0
- package/src/shared/scanner/analysers/entryPointDetector.mjs +634 -0
- package/src/shared/scanner/analysers/generatedCode.mjs +297 -0
- package/src/shared/scanner/analysers/imports.mjs +60 -0
- package/src/shared/scanner/discovery.mjs +240 -0
- package/src/shared/scanner/parse-worker.mjs +82 -0
- package/src/shared/scanner/parsers/assets.mjs +44 -0
- package/src/shared/scanner/parsers/csharp.mjs +400 -0
- package/src/shared/scanner/parsers/css.mjs +60 -0
- package/src/shared/scanner/parsers/go.mjs +445 -0
- package/src/shared/scanner/parsers/java.mjs +364 -0
- package/src/shared/scanner/parsers/javascript.mjs +823 -0
- package/src/shared/scanner/parsers/kotlin.mjs +350 -0
- package/src/shared/scanner/parsers/python.mjs +497 -0
- package/src/shared/scanner/parsers/registry.mjs +233 -0
- package/src/shared/scanner/parsers/rust.mjs +427 -0
- package/src/shared/scanner/scan-dead-code.mjs +316 -0
- package/src/shared/security/patterns.mjs +349 -0
- package/src/shared/security/proximity.mjs +84 -0
- 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
|
+
};
|