depgraph-core 1.0.2 → 1.5.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/.vscode/depgraph-output.json +4634 -0
- package/README.md +208 -105
- package/depgraph-output.json +1609 -212
- package/depgraph.js +1666 -162
- package/docs/README.md +56 -0
- package/docs/architecture.md +89 -0
- package/docs/data-types.md +218 -0
- package/docs/language-registry.md +205 -0
- package/docs/stage-collector.md +109 -0
- package/docs/stage-graph.md +157 -0
- package/docs/stage-impact.md +154 -0
- package/docs/stage-metrics.md +113 -0
- package/docs/stage-output.md +144 -0
- package/docs/stage-parser.md +144 -0
- package/package.json +4 -1
package/depgraph.js
CHANGED
|
@@ -83,205 +83,1481 @@ var require_javascript = __commonJS({
|
|
|
83
83
|
"dist/languages/javascript.js"(exports2) {
|
|
84
84
|
"use strict";
|
|
85
85
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
86
|
+
exports2.jsEntityPatterns = void 0;
|
|
86
87
|
var registry_1 = require_registry();
|
|
87
88
|
var constants_1 = require_constants();
|
|
88
89
|
function estimateComplexity(code, name) {
|
|
89
|
-
const
|
|
90
|
-
}`, "m")
|
|
91
|
-
|
|
90
|
+
const lines = code.split("\n");
|
|
91
|
+
const nameRegex = new RegExp(`(?:(?:async\\s+)?function(?:\\s*\\*|\\s+)|(?:const|let|var)\\s+)${name}\\b|\\b${name}\\s*(?:<[^>]*>)?\\s*\\(`, "m");
|
|
92
|
+
const defLineIdx = lines.findIndex((l) => nameRegex.test(l));
|
|
93
|
+
if (defLineIdx === -1)
|
|
94
|
+
return "low";
|
|
95
|
+
let startLine = defLineIdx;
|
|
96
|
+
while (startLine < lines.length && !lines[startLine].includes("{")) {
|
|
97
|
+
startLine++;
|
|
98
|
+
}
|
|
99
|
+
if (startLine >= lines.length)
|
|
100
|
+
return "low";
|
|
101
|
+
let braceCount = 0;
|
|
102
|
+
let started = false;
|
|
103
|
+
const bodyLines = [];
|
|
104
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
105
|
+
const line = lines[i];
|
|
106
|
+
for (const char of line) {
|
|
107
|
+
if (char === "{") {
|
|
108
|
+
braceCount++;
|
|
109
|
+
started = true;
|
|
110
|
+
} else if (char === "}") {
|
|
111
|
+
braceCount--;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
bodyLines.push(line);
|
|
115
|
+
if (started && braceCount <= 0) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const body = bodyLines.join("\n");
|
|
120
|
+
const branches = (body.match(/\b(if|else\s+if|for|while|switch|case|catch|&&|\|\||\?\?)\b|\?[^:]*:/g) || []).length;
|
|
121
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.low)
|
|
122
|
+
return "low";
|
|
123
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.medium)
|
|
124
|
+
return "medium";
|
|
125
|
+
return "high";
|
|
126
|
+
}
|
|
127
|
+
exports2.jsEntityPatterns = [
|
|
128
|
+
// React components: wrapped in memo/forwardRef
|
|
129
|
+
{
|
|
130
|
+
regex: /^(?:export\s+)?(?:default\s+)?(?:const|let|var)\s+([A-Z]\w*)\s*=\s*(?:React\.)?(?:memo|forwardRef)\(/gm,
|
|
131
|
+
type: "component"
|
|
132
|
+
},
|
|
133
|
+
// React components: PascalCase arrow functions
|
|
134
|
+
{
|
|
135
|
+
regex: /^(?:export\s+)?(?:default\s+)?(?:const|let|var)\s+([A-Z]\w*)\s*=\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_]\w*)\s*=>/gm,
|
|
136
|
+
type: "component"
|
|
137
|
+
},
|
|
138
|
+
// React components: PascalCase function declarations
|
|
139
|
+
{
|
|
140
|
+
regex: /^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+([A-Z]\w*)\s*(?:<[^>]*>)?\s*\(/gm,
|
|
141
|
+
type: "component"
|
|
142
|
+
},
|
|
143
|
+
// React hooks: camelCase starting with "use" (arrow functions or const assignments)
|
|
144
|
+
{
|
|
145
|
+
regex: /^(?:export\s+)?(?:default\s+)?(?:const|let|var)\s+(use[A-Z]\w*)\s*=/gm,
|
|
146
|
+
type: "hook"
|
|
147
|
+
},
|
|
148
|
+
// React hooks: camelCase starting with "use" (function declarations)
|
|
149
|
+
{
|
|
150
|
+
regex: /^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(use[A-Z]\w*)\s*(?:<[^>]*>)?\s*\(/gm,
|
|
151
|
+
type: "hook"
|
|
152
|
+
},
|
|
153
|
+
// regular and async function declarations (including generator functions)
|
|
154
|
+
{
|
|
155
|
+
regex: /^(?:export\s+)?(?:default\s+)?(?:async\s+)?function(?:\s*\*\s*|\s+)([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\(/gm,
|
|
156
|
+
type: "function"
|
|
157
|
+
},
|
|
158
|
+
// arrow functions assigned to const / let / var
|
|
159
|
+
{
|
|
160
|
+
regex: /^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_]\w*)\s*=\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_]\w*)\s*=>/gm,
|
|
161
|
+
type: "function"
|
|
162
|
+
},
|
|
163
|
+
// function expressions assigned to const / let / var
|
|
164
|
+
{
|
|
165
|
+
regex: /^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_]\w*)\s*=\s*(?:async\s*)?function/gm,
|
|
166
|
+
type: "function"
|
|
167
|
+
},
|
|
168
|
+
// classes (regular, exported, abstract)
|
|
169
|
+
{
|
|
170
|
+
regex: /^(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+([A-Za-z_]\w*)/gm,
|
|
171
|
+
type: "class"
|
|
172
|
+
},
|
|
173
|
+
// TypeScript interfaces
|
|
174
|
+
{
|
|
175
|
+
regex: /^(?:export\s+)?(?:default\s+)?interface\s+([A-Za-z_]\w*)/gm,
|
|
176
|
+
type: "interface"
|
|
177
|
+
},
|
|
178
|
+
// TypeScript types
|
|
179
|
+
{
|
|
180
|
+
regex: /^(?:export\s+)?(?:default\s+)?type\s+([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*=/gm,
|
|
181
|
+
type: "type"
|
|
182
|
+
},
|
|
183
|
+
// TypeScript enums (regular or const enum)
|
|
184
|
+
{
|
|
185
|
+
regex: /^(?:export\s+)?(?:const\s+)?enum\s+([A-Za-z_]\w*)/gm,
|
|
186
|
+
type: "class"
|
|
187
|
+
},
|
|
188
|
+
// Express / router routes (capture group 1 = method, group 2 = path — skipped in gitdiff context matching)
|
|
189
|
+
{
|
|
190
|
+
regex: /(?:app|router|server)\.(get|post|put|delete|patch|options|head)\s*\(\s*['"]([^'"]+)['"]/gm,
|
|
191
|
+
type: "api"
|
|
192
|
+
}
|
|
193
|
+
];
|
|
194
|
+
function extractEntities(code, filePath) {
|
|
195
|
+
const entities = [];
|
|
196
|
+
for (const { regex, type } of exports2.jsEntityPatterns) {
|
|
197
|
+
let match;
|
|
198
|
+
regex.lastIndex = 0;
|
|
199
|
+
while ((match = regex.exec(code)) !== null) {
|
|
200
|
+
const upToMatch = code.slice(0, match.index);
|
|
201
|
+
const line = upToMatch.split("\n").length;
|
|
202
|
+
if (type === "api") {
|
|
203
|
+
entities.push({
|
|
204
|
+
name: `${match[1].toUpperCase()} ${match[2]}`,
|
|
205
|
+
type: "api",
|
|
206
|
+
line,
|
|
207
|
+
complexity: "low"
|
|
208
|
+
});
|
|
209
|
+
} else {
|
|
210
|
+
const name = match[1];
|
|
211
|
+
if (entities.some((e) => e.name === name))
|
|
212
|
+
continue;
|
|
213
|
+
entities.push({
|
|
214
|
+
name,
|
|
215
|
+
type,
|
|
216
|
+
line,
|
|
217
|
+
complexity: estimateComplexity(code, name)
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return entities;
|
|
223
|
+
}
|
|
224
|
+
function extractImports(code) {
|
|
225
|
+
const imports = [];
|
|
226
|
+
const combinedPattern = /^import\s+(?:type\s+)?([A-Za-z_$]\w*)\s*,\s*(?:\{([^}]+)\}|\*\s+as\s+([A-Za-z_$]\w*))\s+from\s+['"]([^'"]+)['"]/gm;
|
|
227
|
+
let match;
|
|
228
|
+
while ((match = combinedPattern.exec(code)) !== null) {
|
|
229
|
+
const defaultName = match[1];
|
|
230
|
+
const namedClause = match[2];
|
|
231
|
+
const nsName = match[3];
|
|
232
|
+
const source = match[4];
|
|
233
|
+
const names = [defaultName];
|
|
234
|
+
if (namedClause) {
|
|
235
|
+
const parsedNamed = namedClause.split(",").map((n) => n.trim().replace(/^type\s+/, "").replace(/\s+as\s+\w+$/, "").trim()).filter((n) => n.length > 0);
|
|
236
|
+
names.push(...parsedNamed);
|
|
237
|
+
}
|
|
238
|
+
if (nsName) {
|
|
239
|
+
names.push(nsName);
|
|
240
|
+
}
|
|
241
|
+
imports.push({
|
|
242
|
+
source,
|
|
243
|
+
names: [...new Set(names)],
|
|
244
|
+
isLocal: source.startsWith(".") || source.startsWith("/")
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const namedPattern = /^import\s+(?:type\s+)?\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/gm;
|
|
248
|
+
while ((match = namedPattern.exec(code)) !== null) {
|
|
249
|
+
const source = match[2];
|
|
250
|
+
if (imports.some((i) => i.source === source))
|
|
251
|
+
continue;
|
|
252
|
+
const names = match[1].split(",").map((n) => n.trim().replace(/^type\s+/, "").replace(/\s+as\s+\w+$/, "").trim()).filter((n) => n.length > 0);
|
|
253
|
+
imports.push({
|
|
254
|
+
source,
|
|
255
|
+
names: [...new Set(names)],
|
|
256
|
+
isLocal: source.startsWith(".") || source.startsWith("/")
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const defaultPattern = /^import\s+(?:type\s+)?([A-Za-z_$]\w*)\s+from\s+['"]([^'"]+)['"]/gm;
|
|
260
|
+
while ((match = defaultPattern.exec(code)) !== null) {
|
|
261
|
+
const source = match[2];
|
|
262
|
+
if (imports.some((i) => i.source === source))
|
|
263
|
+
continue;
|
|
264
|
+
imports.push({
|
|
265
|
+
source,
|
|
266
|
+
names: [match[1]],
|
|
267
|
+
isLocal: source.startsWith(".") || source.startsWith("/")
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const nsPattern = /^import\s+\*\s+as\s+([A-Za-z_$]\w*)\s+from\s+['"]([^'"]+)['"]/gm;
|
|
271
|
+
while ((match = nsPattern.exec(code)) !== null) {
|
|
272
|
+
const source = match[2];
|
|
273
|
+
if (imports.some((i) => i.source === source))
|
|
274
|
+
continue;
|
|
275
|
+
imports.push({
|
|
276
|
+
source,
|
|
277
|
+
names: [match[1]],
|
|
278
|
+
isLocal: source.startsWith(".") || source.startsWith("/")
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
const requirePattern = /(?:const|let|var)\s+\{?([^}=]+)\}?\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/gm;
|
|
282
|
+
while ((match = requirePattern.exec(code)) !== null) {
|
|
283
|
+
const rawNames = match[1];
|
|
284
|
+
const source = match[2];
|
|
285
|
+
const names = rawNames.split(",").map((n) => n.trim().replace(/^\w+:\s*/, "").trim()).filter((n) => n.length > 0);
|
|
286
|
+
imports.push({
|
|
287
|
+
source,
|
|
288
|
+
names: [...new Set(names)],
|
|
289
|
+
isLocal: source.startsWith(".") || source.startsWith("/")
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
const reexportPattern = /^export\s+(?:\{([^}]+)\}|\*\s+as\s+([A-Za-z_$]\w*)|\*)\s+from\s+['"]([^'"]+)['"]/gm;
|
|
293
|
+
while ((match = reexportPattern.exec(code)) !== null) {
|
|
294
|
+
const namedClause = match[1];
|
|
295
|
+
const nsAlias = match[2];
|
|
296
|
+
const source = match[3];
|
|
297
|
+
let names = [];
|
|
298
|
+
if (namedClause) {
|
|
299
|
+
names = namedClause.split(",").map((n) => n.trim().replace(/^type\s+/, "").replace(/\s+as\s+\w+$/, "").trim()).filter((n) => n.length > 0);
|
|
300
|
+
} else if (nsAlias) {
|
|
301
|
+
names = [nsAlias];
|
|
302
|
+
} else {
|
|
303
|
+
names = ["*"];
|
|
304
|
+
}
|
|
305
|
+
imports.push({
|
|
306
|
+
source,
|
|
307
|
+
names: [...new Set(names)],
|
|
308
|
+
isLocal: source.startsWith(".") || source.startsWith("/")
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return imports;
|
|
312
|
+
}
|
|
313
|
+
function extractExports(code) {
|
|
314
|
+
const exports3 = [];
|
|
315
|
+
const namedPattern = /^export\s+(?:default\s+)?(?:async\s+|abstract\s+)?(?:function(?:\s*\*|\s+)|class|const|let|var|type|interface|enum)\s+([A-Za-z_$]\w*)/gm;
|
|
316
|
+
let match;
|
|
317
|
+
while ((match = namedPattern.exec(code)) !== null) {
|
|
318
|
+
exports3.push(match[1]);
|
|
319
|
+
}
|
|
320
|
+
const defaultIdentPattern = /^export\s+default\s+([A-Za-z_$]\w*)\s*(?:;|$)/gm;
|
|
321
|
+
while ((match = defaultIdentPattern.exec(code)) !== null) {
|
|
322
|
+
if (!["function", "class", "interface", "abstract"].includes(match[1])) {
|
|
323
|
+
exports3.push(match[1]);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const listPattern = /^export\s+\{([^}]+)\}(?!\s*from)/gm;
|
|
327
|
+
while ((match = listPattern.exec(code)) !== null) {
|
|
328
|
+
const names = match[1].split(",").map((n) => n.trim().replace(/^type\s+/, "").replace(/^\w+\s+as\s+/, "").trim()).filter((n) => n.length > 0);
|
|
329
|
+
exports3.push(...names);
|
|
330
|
+
}
|
|
331
|
+
const reexportPattern = /^export\s+\{([^}]+)\}\s+from/gm;
|
|
332
|
+
while ((match = reexportPattern.exec(code)) !== null) {
|
|
333
|
+
const names = match[1].split(",").map((n) => n.trim().replace(/^type\s+/, "").replace(/^\w+\s+as\s+/, "").trim()).filter((n) => n.length > 0);
|
|
334
|
+
exports3.push(...names);
|
|
335
|
+
}
|
|
336
|
+
return [...new Set(exports3)];
|
|
337
|
+
}
|
|
338
|
+
var JavaScriptParser = {
|
|
339
|
+
lang: "js",
|
|
340
|
+
extensions: [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"],
|
|
341
|
+
extractEntities,
|
|
342
|
+
extractImports,
|
|
343
|
+
extractExports,
|
|
344
|
+
entityPatterns: exports2.jsEntityPatterns
|
|
345
|
+
};
|
|
346
|
+
(0, registry_1.registerParser)(JavaScriptParser);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// dist/languages/python.js
|
|
351
|
+
var require_python = __commonJS({
|
|
352
|
+
"dist/languages/python.js"(exports2) {
|
|
353
|
+
"use strict";
|
|
354
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
355
|
+
exports2.pyEntityPatterns = void 0;
|
|
356
|
+
var registry_1 = require_registry();
|
|
357
|
+
var constants_1 = require_constants();
|
|
358
|
+
function escapeRegex(s) {
|
|
359
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
360
|
+
}
|
|
361
|
+
function estimateComplexity(code, name) {
|
|
362
|
+
const lines = code.split("\n");
|
|
363
|
+
const defRegex = new RegExp(`^[ \\t]*(?:async\\s+)?def\\s+${escapeRegex(name)}\\s*\\(`, "m");
|
|
364
|
+
const defLine = lines.findIndex((l) => defRegex.test(l));
|
|
365
|
+
if (defLine === -1)
|
|
366
|
+
return "low";
|
|
367
|
+
let bodyStart = defLine;
|
|
368
|
+
while (bodyStart < lines.length && !lines[bodyStart].includes(":")) {
|
|
369
|
+
bodyStart++;
|
|
370
|
+
}
|
|
371
|
+
bodyStart++;
|
|
372
|
+
const bodyLines = [];
|
|
373
|
+
for (let i = bodyStart; i < lines.length; i++) {
|
|
374
|
+
const line = lines[i];
|
|
375
|
+
if (line.trim() === "")
|
|
376
|
+
continue;
|
|
377
|
+
if (!line.match(/^\s+/))
|
|
378
|
+
break;
|
|
379
|
+
bodyLines.push(line);
|
|
380
|
+
}
|
|
381
|
+
const branches = (bodyLines.join("\n").match(/\b(if|elif|else|for|while|except|and|or|match|case)\b/g) || []).length;
|
|
382
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.low)
|
|
383
|
+
return "low";
|
|
384
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.medium)
|
|
385
|
+
return "medium";
|
|
386
|
+
return "high";
|
|
387
|
+
}
|
|
388
|
+
exports2.pyEntityPatterns = [
|
|
389
|
+
// functions and methods (including async def)
|
|
390
|
+
{
|
|
391
|
+
regex: /^[ \t]*(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\(/gm,
|
|
392
|
+
type: "function"
|
|
393
|
+
},
|
|
394
|
+
// classes (with optional generic parameters [T] and base classes (Base))
|
|
395
|
+
{
|
|
396
|
+
regex: /^[ \t]*class\s+([A-Za-z_]\w*)(?:\s*\[[^\]]*\])?(?:\s*\([^)]*\))?\s*:/gm,
|
|
397
|
+
type: "class"
|
|
398
|
+
}
|
|
399
|
+
];
|
|
400
|
+
function extractEntities(code, filePath) {
|
|
401
|
+
const entities = [];
|
|
402
|
+
for (const { regex, type } of exports2.pyEntityPatterns) {
|
|
403
|
+
regex.lastIndex = 0;
|
|
404
|
+
let match;
|
|
405
|
+
while ((match = regex.exec(code)) !== null) {
|
|
406
|
+
const name = match[1];
|
|
407
|
+
if (type === "function" && name.startsWith("__") && name.endsWith("__"))
|
|
408
|
+
continue;
|
|
409
|
+
const upToMatch = code.slice(0, match.index);
|
|
410
|
+
const line = upToMatch.split("\n").length;
|
|
411
|
+
if (entities.some((e) => e.name === name && e.line === line))
|
|
412
|
+
continue;
|
|
413
|
+
entities.push({
|
|
414
|
+
name,
|
|
415
|
+
type,
|
|
416
|
+
line,
|
|
417
|
+
complexity: type === "function" ? estimateComplexity(code, name) : "low"
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return entities;
|
|
422
|
+
}
|
|
423
|
+
function stripAlias(name) {
|
|
424
|
+
return name.replace(/\s+as\s+[A-Za-z_]\w*$/, "").trim();
|
|
425
|
+
}
|
|
426
|
+
function extractImports(code) {
|
|
427
|
+
const imports = [];
|
|
428
|
+
const normalised = code.replace(/^(from\s+[\w.]+\s+import\s*)\(\s*([\s\S]*?)\)/gm, (_, prefix, body) => prefix + body.replace(/\s*\n\s*/g, ", "));
|
|
429
|
+
const fromPattern = /^from\s+([\w.]+)\s+import\s+(.+)$/gm;
|
|
430
|
+
let match;
|
|
431
|
+
while ((match = fromPattern.exec(normalised)) !== null) {
|
|
432
|
+
const source = match[1];
|
|
433
|
+
const rawNames = match[2].replace(/#.*$/, "");
|
|
434
|
+
const names = rawNames.split(",").map((n) => stripAlias(n.trim())).filter((n) => n.length > 0 && n !== "*");
|
|
435
|
+
imports.push({ source, names, isLocal: source.startsWith(".") });
|
|
436
|
+
}
|
|
437
|
+
const importPattern = /^import\s+([^#\n]+)/gm;
|
|
438
|
+
while ((match = importPattern.exec(normalised)) !== null) {
|
|
439
|
+
const modules = match[1].split(",").map((m) => m.trim());
|
|
440
|
+
for (const mod of modules) {
|
|
441
|
+
if (!mod)
|
|
442
|
+
continue;
|
|
443
|
+
const cleanMod = stripAlias(mod);
|
|
444
|
+
if (!cleanMod)
|
|
445
|
+
continue;
|
|
446
|
+
imports.push({
|
|
447
|
+
source: cleanMod,
|
|
448
|
+
names: [cleanMod],
|
|
449
|
+
isLocal: cleanMod.startsWith(".")
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return imports;
|
|
454
|
+
}
|
|
455
|
+
function extractExports(code) {
|
|
456
|
+
const allMatch = code.match(/__all__\s*=\s*[\[\(]([\s\S]*?)[\]\)]/);
|
|
457
|
+
if (!allMatch)
|
|
458
|
+
return [];
|
|
459
|
+
return allMatch[1].split(",").map((n) => n.trim().replace(/['"]/g, "").replace(/#.*$/, "").trim()).filter((n) => n.length > 0);
|
|
460
|
+
}
|
|
461
|
+
var PythonParser = {
|
|
462
|
+
lang: "py",
|
|
463
|
+
extensions: [".py"],
|
|
464
|
+
extractEntities,
|
|
465
|
+
extractImports,
|
|
466
|
+
extractExports,
|
|
467
|
+
entityPatterns: exports2.pyEntityPatterns
|
|
468
|
+
};
|
|
469
|
+
(0, registry_1.registerParser)(PythonParser);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// dist/languages/go.js
|
|
474
|
+
var require_go = __commonJS({
|
|
475
|
+
"dist/languages/go.js"(exports2) {
|
|
476
|
+
"use strict";
|
|
477
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
478
|
+
exports2.goEntityPatterns = void 0;
|
|
479
|
+
var registry_1 = require_registry();
|
|
480
|
+
var constants_1 = require_constants();
|
|
481
|
+
function estimateComplexity(code, name) {
|
|
482
|
+
const lines = code.split("\n");
|
|
483
|
+
const funcRegex = new RegExp(`func\\s+(?:\\([^)]*\\)\\s+)?${name}\\s*(?:\\[[^\\]]*\\])?\\s*\\(`, "m");
|
|
484
|
+
const defLineIdx = lines.findIndex((l) => funcRegex.test(l));
|
|
485
|
+
if (defLineIdx === -1)
|
|
486
|
+
return "low";
|
|
487
|
+
let startLine = defLineIdx;
|
|
488
|
+
while (startLine < lines.length && !lines[startLine].includes("{")) {
|
|
489
|
+
startLine++;
|
|
490
|
+
}
|
|
491
|
+
if (startLine >= lines.length)
|
|
492
|
+
return "low";
|
|
493
|
+
let braceCount = 0;
|
|
494
|
+
let started = false;
|
|
495
|
+
const bodyLines = [];
|
|
496
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
497
|
+
const line = lines[i];
|
|
498
|
+
for (const char of line) {
|
|
499
|
+
if (char === "{") {
|
|
500
|
+
braceCount++;
|
|
501
|
+
started = true;
|
|
502
|
+
} else if (char === "}") {
|
|
503
|
+
braceCount--;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
bodyLines.push(line);
|
|
507
|
+
if (started && braceCount <= 0) {
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const body = bodyLines.join("\n");
|
|
512
|
+
const branches = (body.match(/\b(if|else\s+if|for|switch|case|select|&&|\|\|)\b/g) || []).length;
|
|
513
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.low)
|
|
514
|
+
return "low";
|
|
515
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.medium)
|
|
516
|
+
return "medium";
|
|
517
|
+
return "high";
|
|
518
|
+
}
|
|
519
|
+
exports2.goEntityPatterns = [
|
|
520
|
+
// functions and methods with optional receiver and type parameters (generics)
|
|
521
|
+
{
|
|
522
|
+
regex: /^func\s+(?:\([^)]*\)\s+)?([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*\(/gm,
|
|
523
|
+
type: "function"
|
|
524
|
+
},
|
|
525
|
+
// type declarations (structs, interfaces) with optional type parameters
|
|
526
|
+
{
|
|
527
|
+
regex: /^type\s+([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s+(?:struct|interface)/gm,
|
|
528
|
+
type: "class"
|
|
529
|
+
},
|
|
530
|
+
// type aliases and custom types (e.g. type HandlerFunc func(...), type MyInt int)
|
|
531
|
+
{
|
|
532
|
+
regex: /^type\s+([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s+(?!(?:struct|interface)\b)[A-Za-z_\[\]\*]/gm,
|
|
533
|
+
type: "type"
|
|
534
|
+
}
|
|
535
|
+
];
|
|
536
|
+
function extractEntities(code, filePath) {
|
|
537
|
+
const entities = [];
|
|
538
|
+
for (const { regex, type } of exports2.goEntityPatterns) {
|
|
539
|
+
regex.lastIndex = 0;
|
|
540
|
+
let match;
|
|
541
|
+
while ((match = regex.exec(code)) !== null) {
|
|
542
|
+
const name = match[1];
|
|
543
|
+
if (entities.some((e) => e.name === name))
|
|
544
|
+
continue;
|
|
545
|
+
const upToMatch = code.slice(0, match.index);
|
|
546
|
+
const line = upToMatch.split("\n").length;
|
|
547
|
+
entities.push({
|
|
548
|
+
name,
|
|
549
|
+
type,
|
|
550
|
+
line,
|
|
551
|
+
complexity: type === "function" ? estimateComplexity(code, name) : "low"
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const blockTypePattern = /^type\s*\(\s*\n?([\s\S]*?)\n\s*\)/gm;
|
|
556
|
+
let blockMatch;
|
|
557
|
+
while ((blockMatch = blockTypePattern.exec(code)) !== null) {
|
|
558
|
+
const blockContent = blockMatch[1];
|
|
559
|
+
const blockStartLine = code.slice(0, blockMatch.index).split("\n").length;
|
|
560
|
+
const lines = blockContent.split("\n");
|
|
561
|
+
let braceDepth = 0;
|
|
562
|
+
for (let i = 0; i < lines.length; i++) {
|
|
563
|
+
const line = lines[i].trim();
|
|
564
|
+
if (braceDepth === 0 && line.length > 0 && !line.startsWith("//")) {
|
|
565
|
+
const structInterfaceMatch = line.match(/^([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s+(struct|interface)/);
|
|
566
|
+
if (structInterfaceMatch) {
|
|
567
|
+
const name = structInterfaceMatch[1];
|
|
568
|
+
if (!entities.some((e) => e.name === name)) {
|
|
569
|
+
entities.push({
|
|
570
|
+
name,
|
|
571
|
+
type: "class",
|
|
572
|
+
line: blockStartLine + i + 1,
|
|
573
|
+
complexity: "low"
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
} else {
|
|
577
|
+
const aliasMatch = line.match(/^([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s+(?:=\s*)?(?!(?:struct|interface)\b)[A-Za-z_\[\]\*]/);
|
|
578
|
+
if (aliasMatch) {
|
|
579
|
+
const name = aliasMatch[1];
|
|
580
|
+
if (!entities.some((e) => e.name === name)) {
|
|
581
|
+
entities.push({
|
|
582
|
+
name,
|
|
583
|
+
type: "type",
|
|
584
|
+
line: blockStartLine + i + 1,
|
|
585
|
+
complexity: "low"
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
for (const ch of line) {
|
|
592
|
+
if (ch === "{")
|
|
593
|
+
braceDepth++;
|
|
594
|
+
else if (ch === "}")
|
|
595
|
+
braceDepth--;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return entities;
|
|
600
|
+
}
|
|
601
|
+
function extractImports(code) {
|
|
602
|
+
const imports = [];
|
|
603
|
+
const singlePattern = /^import\s+(?:([A-Za-z_.\w]+)\s+)?["']([^"']+)["']/gm;
|
|
604
|
+
let match;
|
|
605
|
+
while ((match = singlePattern.exec(code)) !== null) {
|
|
606
|
+
const alias = match[1];
|
|
607
|
+
const source = match[2];
|
|
608
|
+
const pkgName = alias && alias !== "_" && alias !== "." ? alias : source.split("/").pop() || source;
|
|
609
|
+
imports.push({
|
|
610
|
+
source,
|
|
611
|
+
names: [pkgName],
|
|
612
|
+
isLocal: source.startsWith(".") || source.startsWith("/")
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
const blockPattern = /import\s*\(\s*([\s\S]*?)\s*\)/gm;
|
|
616
|
+
while ((match = blockPattern.exec(code)) !== null) {
|
|
617
|
+
const lines = match[1].split("\n");
|
|
618
|
+
for (const line of lines) {
|
|
619
|
+
const cleanLine = line.replace(/\/\/.*$/, "").trim();
|
|
620
|
+
const pkgMatch = cleanLine.match(/^(?:([A-Za-z_.\w]+)\s+)?["']([^"']+)["']/);
|
|
621
|
+
if (pkgMatch) {
|
|
622
|
+
const alias = pkgMatch[1];
|
|
623
|
+
const source = pkgMatch[2];
|
|
624
|
+
const pkgName = alias && alias !== "_" && alias !== "." ? alias : source.split("/").pop() || source;
|
|
625
|
+
imports.push({
|
|
626
|
+
source,
|
|
627
|
+
names: [pkgName],
|
|
628
|
+
isLocal: source.startsWith(".") || source.startsWith("/")
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return imports;
|
|
634
|
+
}
|
|
635
|
+
function extractExports(code) {
|
|
636
|
+
const exports3 = [];
|
|
637
|
+
const funcPattern = /^func\s+(?:\([^)]*\)\s+)?([A-Z]\w*)\s*(?:\[[^\]]*\])?\s*\(/gm;
|
|
638
|
+
let match;
|
|
639
|
+
while ((match = funcPattern.exec(code)) !== null) {
|
|
640
|
+
exports3.push(match[1]);
|
|
641
|
+
}
|
|
642
|
+
const typePattern = /^type\s+([A-Z]\w*)/gm;
|
|
643
|
+
while ((match = typePattern.exec(code)) !== null) {
|
|
644
|
+
exports3.push(match[1]);
|
|
645
|
+
}
|
|
646
|
+
const blockTypePattern = /^type\s*\(\s*\n?([\s\S]*?)\n\s*\)/gm;
|
|
647
|
+
let blockTypeMatch;
|
|
648
|
+
while ((blockTypeMatch = blockTypePattern.exec(code)) !== null) {
|
|
649
|
+
const lines = blockTypeMatch[1].split("\n");
|
|
650
|
+
let braceDepth = 0;
|
|
651
|
+
for (const line of lines) {
|
|
652
|
+
const trimmed = line.trim();
|
|
653
|
+
if (braceDepth === 0 && trimmed.length > 0 && !trimmed.startsWith("//")) {
|
|
654
|
+
const m = trimmed.match(/^([A-Z]\w*)/);
|
|
655
|
+
if (m)
|
|
656
|
+
exports3.push(m[1]);
|
|
657
|
+
}
|
|
658
|
+
for (const ch of trimmed) {
|
|
659
|
+
if (ch === "{")
|
|
660
|
+
braceDepth++;
|
|
661
|
+
else if (ch === "}")
|
|
662
|
+
braceDepth--;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const constVarPattern = /^(?:const|var)\s+([A-Z]\w*)/gm;
|
|
667
|
+
while ((match = constVarPattern.exec(code)) !== null) {
|
|
668
|
+
exports3.push(match[1]);
|
|
669
|
+
}
|
|
670
|
+
const blockConstVarPattern = /^(?:const|var)\s*\(\s*\n?([\s\S]*?)\n\s*\)/gm;
|
|
671
|
+
while ((match = blockConstVarPattern.exec(code)) !== null) {
|
|
672
|
+
const lines = match[1].split("\n");
|
|
673
|
+
for (const line of lines) {
|
|
674
|
+
const trimmed = line.trim();
|
|
675
|
+
if (!trimmed.startsWith("//")) {
|
|
676
|
+
const m = trimmed.match(/^([A-Z]\w*)/);
|
|
677
|
+
if (m)
|
|
678
|
+
exports3.push(m[1]);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return [...new Set(exports3)];
|
|
683
|
+
}
|
|
684
|
+
var GoParser = {
|
|
685
|
+
lang: "go",
|
|
686
|
+
extensions: [".go"],
|
|
687
|
+
extractEntities,
|
|
688
|
+
extractImports,
|
|
689
|
+
extractExports,
|
|
690
|
+
entityPatterns: exports2.goEntityPatterns
|
|
691
|
+
};
|
|
692
|
+
(0, registry_1.registerParser)(GoParser);
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
// dist/languages/csharp.js
|
|
697
|
+
var require_csharp = __commonJS({
|
|
698
|
+
"dist/languages/csharp.js"(exports2) {
|
|
699
|
+
"use strict";
|
|
700
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
701
|
+
exports2.csharpEntityPatterns = void 0;
|
|
702
|
+
var registry_1 = require_registry();
|
|
703
|
+
var constants_1 = require_constants();
|
|
704
|
+
function estimateComplexity(code, name) {
|
|
705
|
+
const lines = code.split("\n");
|
|
706
|
+
const methodRegex = new RegExp(`(?:(?:public|private|protected|internal|static|async|virtual|override|abstract|sealed|partial)\\s+)+[\\w<>\\[\\],?]+\\s+${name}\\s*(?:<[^>]*>)?\\s*\\(`, "m");
|
|
707
|
+
const defLineIdx = lines.findIndex((l) => methodRegex.test(l) || new RegExp(`\\b${name}\\s*\\(`, "m").test(l));
|
|
708
|
+
if (defLineIdx === -1)
|
|
709
|
+
return "low";
|
|
710
|
+
let startLine = defLineIdx;
|
|
711
|
+
while (startLine < lines.length && !lines[startLine].includes("{")) {
|
|
712
|
+
startLine++;
|
|
713
|
+
}
|
|
714
|
+
if (startLine >= lines.length)
|
|
715
|
+
return "low";
|
|
716
|
+
let braceCount = 0;
|
|
717
|
+
let started = false;
|
|
718
|
+
const bodyLines = [];
|
|
719
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
720
|
+
const line = lines[i];
|
|
721
|
+
for (const char of line) {
|
|
722
|
+
if (char === "{") {
|
|
723
|
+
braceCount++;
|
|
724
|
+
started = true;
|
|
725
|
+
} else if (char === "}") {
|
|
726
|
+
braceCount--;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
bodyLines.push(line);
|
|
730
|
+
if (started && braceCount <= 0) {
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
const body = bodyLines.join("\n");
|
|
735
|
+
const branches = (body.match(/\b(if|else\s+if|for|foreach|while|do|switch|case|catch|&&|\|\||\?\?)\b|\?[^:]*:/g) || []).length;
|
|
736
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.low)
|
|
737
|
+
return "low";
|
|
738
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.medium)
|
|
739
|
+
return "medium";
|
|
740
|
+
return "high";
|
|
741
|
+
}
|
|
742
|
+
exports2.csharpEntityPatterns = [
|
|
743
|
+
// Classes, structs, records, interfaces, enums
|
|
744
|
+
{
|
|
745
|
+
regex: /^[ \t]*(?:(?:public|private|protected|internal|static|abstract|sealed|partial)\s+)*(?:class|interface|enum|struct|record(?:\s+(?:class|struct))?)\s+([A-Za-z_]\w*)/gm,
|
|
746
|
+
type: "class"
|
|
747
|
+
},
|
|
748
|
+
// Methods (constructors, instance methods, async/static methods)
|
|
749
|
+
{
|
|
750
|
+
regex: /^[ \t]*(?:(?:public|private|protected|internal|static|async|virtual|override|abstract|sealed|partial)\s+)+(?:(?:async\s+)?[\w<>[\]?,]+\s+)?([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\(/gm,
|
|
751
|
+
type: "function"
|
|
752
|
+
}
|
|
753
|
+
];
|
|
754
|
+
function extractEntities(code, filePath) {
|
|
755
|
+
const entities = [];
|
|
756
|
+
for (const { regex, type } of exports2.csharpEntityPatterns) {
|
|
757
|
+
regex.lastIndex = 0;
|
|
758
|
+
let match;
|
|
759
|
+
while ((match = regex.exec(code)) !== null) {
|
|
760
|
+
const name = match[1];
|
|
761
|
+
if (["if", "for", "foreach", "while", "switch", "catch", "lock", "using", "get", "set"].includes(name)) {
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const upToMatch = code.slice(0, match.index);
|
|
765
|
+
const line = upToMatch.split("\n").length;
|
|
766
|
+
if (entities.some((e) => e.name === name && e.line === line))
|
|
767
|
+
continue;
|
|
768
|
+
entities.push({
|
|
769
|
+
name,
|
|
770
|
+
type,
|
|
771
|
+
line,
|
|
772
|
+
complexity: type === "function" ? estimateComplexity(code, name) : "low"
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return entities;
|
|
777
|
+
}
|
|
778
|
+
function extractImports(code) {
|
|
779
|
+
const imports = [];
|
|
780
|
+
const usingPattern = /^[ \t]*(?:global\s+)?using\s+(?:static\s+)?(?:([A-Za-z_]\w*)\s*=\s*)?([A-Za-z_][\w.]*(?:<[^>]*>)?)\s*;/gm;
|
|
781
|
+
let match;
|
|
782
|
+
while ((match = usingPattern.exec(code)) !== null) {
|
|
783
|
+
const alias = match[1];
|
|
784
|
+
const targetFqn = match[2].trim();
|
|
785
|
+
const simpleName = alias || targetFqn.split(".").pop() || targetFqn;
|
|
786
|
+
const isLocal = !targetFqn.startsWith("System") && !targetFqn.startsWith("Microsoft");
|
|
787
|
+
imports.push({
|
|
788
|
+
source: targetFqn,
|
|
789
|
+
names: [simpleName],
|
|
790
|
+
isLocal
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
return imports;
|
|
794
|
+
}
|
|
795
|
+
function extractExports(code) {
|
|
796
|
+
const exports3 = [];
|
|
797
|
+
const typePattern = /^[ \t]*(?:public|internal)\s+(?:(?:static|abstract|sealed|partial)\s+)*(?:class|interface|enum|struct|record(?:\s+(?:class|struct))?)\s+([A-Za-z_]\w*)/gm;
|
|
798
|
+
let match;
|
|
799
|
+
while ((match = typePattern.exec(code)) !== null) {
|
|
800
|
+
exports3.push(match[1]);
|
|
801
|
+
}
|
|
802
|
+
const methodPattern = /^[ \t]*(?:public|internal)\s+(?:(?:static|async|virtual|override|abstract|sealed|partial)\s+)*(?:[\w<>[\]?,]+\s+)?([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\(/gm;
|
|
803
|
+
while ((match = methodPattern.exec(code)) !== null) {
|
|
804
|
+
const name = match[1];
|
|
805
|
+
if (!["if", "for", "foreach", "while", "switch", "catch", "lock", "using", "get", "set"].includes(name)) {
|
|
806
|
+
exports3.push(name);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return [...new Set(exports3)];
|
|
810
|
+
}
|
|
811
|
+
var CSharpParser = {
|
|
812
|
+
lang: "cs",
|
|
813
|
+
extensions: [".cs"],
|
|
814
|
+
extractEntities,
|
|
815
|
+
extractImports,
|
|
816
|
+
extractExports,
|
|
817
|
+
entityPatterns: exports2.csharpEntityPatterns
|
|
818
|
+
};
|
|
819
|
+
(0, registry_1.registerParser)(CSharpParser);
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// dist/languages/java.js
|
|
824
|
+
var require_java = __commonJS({
|
|
825
|
+
"dist/languages/java.js"(exports2) {
|
|
826
|
+
"use strict";
|
|
827
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
828
|
+
exports2.javaEntityPatterns = void 0;
|
|
829
|
+
var registry_1 = require_registry();
|
|
830
|
+
var constants_1 = require_constants();
|
|
831
|
+
function escapeRegex(s) {
|
|
832
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
833
|
+
}
|
|
834
|
+
function estimateComplexity(code, name) {
|
|
835
|
+
const lines = code.split("\n");
|
|
836
|
+
const defRegex = new RegExp(`(?:^|\\s)${escapeRegex(name)}\\s*\\(`, "m");
|
|
837
|
+
const defLineIdx = lines.findIndex((l) => defRegex.test(l));
|
|
838
|
+
if (defLineIdx === -1)
|
|
839
|
+
return "low";
|
|
840
|
+
let startLine = defLineIdx;
|
|
841
|
+
while (startLine < lines.length && !lines[startLine].includes("{")) {
|
|
842
|
+
startLine++;
|
|
843
|
+
}
|
|
844
|
+
if (startLine >= lines.length)
|
|
845
|
+
return "low";
|
|
846
|
+
let braceCount = 0;
|
|
847
|
+
let started = false;
|
|
848
|
+
const bodyLines = [];
|
|
849
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
850
|
+
const line = lines[i];
|
|
851
|
+
for (const ch of line) {
|
|
852
|
+
if (ch === "{") {
|
|
853
|
+
braceCount++;
|
|
854
|
+
started = true;
|
|
855
|
+
} else if (ch === "}") {
|
|
856
|
+
braceCount--;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
bodyLines.push(line);
|
|
860
|
+
if (started && braceCount <= 0)
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
const body = bodyLines.join("\n");
|
|
864
|
+
const branches = (body.match(/\b(if|else\s+if|else|for|while|do|switch|case|catch|&&|\|\|)\b/g) || []).length;
|
|
865
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.low)
|
|
866
|
+
return "low";
|
|
867
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.medium)
|
|
868
|
+
return "medium";
|
|
869
|
+
return "high";
|
|
870
|
+
}
|
|
871
|
+
exports2.javaEntityPatterns = [
|
|
872
|
+
// class / abstract class / final class
|
|
873
|
+
{
|
|
874
|
+
regex: /^[ \t]*(?:(?:public|protected|private|abstract|final|static)\s+)*class\s+([A-Za-z_]\w*)(?:\s*<[^{]*?)?\s*(?:extends\s+\S+\s*)?(?:implements\s+[^{]+)?\s*\{/gm,
|
|
875
|
+
type: "class"
|
|
876
|
+
},
|
|
877
|
+
// interface
|
|
878
|
+
{
|
|
879
|
+
regex: /^[ \t]*(?:(?:public|protected|private|abstract|static)\s+)*interface\s+([A-Za-z_]\w*)(?:\s*<[^{]*?)?\s*(?:extends\s+[^{]+)?\s*\{/gm,
|
|
880
|
+
type: "interface"
|
|
881
|
+
},
|
|
882
|
+
// record (Java 14+)
|
|
883
|
+
{
|
|
884
|
+
regex: /^[ \t]*(?:(?:public|protected|private|final|static)\s+)*record\s+([A-Za-z_]\w*)\s*\(/gm,
|
|
885
|
+
type: "class"
|
|
886
|
+
},
|
|
887
|
+
// enum
|
|
888
|
+
{
|
|
889
|
+
regex: /^[ \t]*(?:(?:public|protected|private|static)\s+)*enum\s+([A-Za-z_]\w*)\s*(?:implements\s+[^{]+)?\s*\{/gm,
|
|
890
|
+
type: "class"
|
|
891
|
+
},
|
|
892
|
+
// annotation type
|
|
893
|
+
{
|
|
894
|
+
regex: /^[ \t]*(?:(?:public|protected|private|abstract|static)\s+)*@interface\s+([A-Za-z_]\w*)\s*\{/gm,
|
|
895
|
+
type: "interface"
|
|
896
|
+
},
|
|
897
|
+
// method declarations (with return type before the name)
|
|
898
|
+
{
|
|
899
|
+
regex: /^[ \t]*(?:(?:public|protected|private|static|final|abstract|synchronized|native|default|override)\s+)*(?:<[^>]*>\s+)?(?:[\w.<>\[\]]+\s+)+([A-Za-z_]\w*)\s*\([^)]*\)\s*(?:throws\s+[\w,\s]+)?\s*\{/gm,
|
|
900
|
+
type: "function"
|
|
901
|
+
}
|
|
902
|
+
];
|
|
903
|
+
function extractEntities(code, _filePath) {
|
|
904
|
+
const entities = [];
|
|
905
|
+
for (const { regex, type } of exports2.javaEntityPatterns) {
|
|
906
|
+
regex.lastIndex = 0;
|
|
907
|
+
let match;
|
|
908
|
+
while ((match = regex.exec(code)) !== null) {
|
|
909
|
+
const name = match[1];
|
|
910
|
+
if (["if", "else", "for", "while", "do", "switch", "try", "catch", "return", "new", "void", "this", "super"].includes(name))
|
|
911
|
+
continue;
|
|
912
|
+
const upToMatch = code.slice(0, match.index);
|
|
913
|
+
const line = upToMatch.split("\n").length;
|
|
914
|
+
if (entities.some((e) => e.name === name && e.line === line))
|
|
915
|
+
continue;
|
|
916
|
+
entities.push({
|
|
917
|
+
name,
|
|
918
|
+
type,
|
|
919
|
+
line,
|
|
920
|
+
complexity: type === "function" ? estimateComplexity(code, name) : "low"
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return entities;
|
|
925
|
+
}
|
|
926
|
+
function extractImports(code) {
|
|
927
|
+
const imports = [];
|
|
928
|
+
const importPattern = /^import\s+((?:static)\s+)?([\w.]+(?:\.\*)?)?\s*;/gm;
|
|
929
|
+
let match;
|
|
930
|
+
while ((match = importPattern.exec(code)) !== null) {
|
|
931
|
+
const isStatic = Boolean(match[1]);
|
|
932
|
+
const fullPath = (match[2] || "").trim();
|
|
933
|
+
if (!fullPath)
|
|
934
|
+
continue;
|
|
935
|
+
const isWildcard = fullPath.endsWith(".*");
|
|
936
|
+
const cleanPath = isWildcard ? fullPath.slice(0, -2) : fullPath;
|
|
937
|
+
const segments = cleanPath.split(".");
|
|
938
|
+
let source;
|
|
939
|
+
let name;
|
|
940
|
+
if (isStatic) {
|
|
941
|
+
name = segments.pop() || cleanPath;
|
|
942
|
+
source = segments.join(".") || cleanPath;
|
|
943
|
+
} else {
|
|
944
|
+
name = segments[segments.length - 1] || cleanPath;
|
|
945
|
+
source = cleanPath;
|
|
946
|
+
}
|
|
947
|
+
imports.push({
|
|
948
|
+
source,
|
|
949
|
+
names: [name],
|
|
950
|
+
isLocal: false
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
return imports;
|
|
954
|
+
}
|
|
955
|
+
function extractExports(code) {
|
|
956
|
+
const exports3 = [];
|
|
957
|
+
const typePattern = /^public\s+(?:(?:abstract|final|static)\s+)*(?:class|interface|enum|record|@interface)\s+([A-Za-z_]\w*)/gm;
|
|
958
|
+
let match;
|
|
959
|
+
while ((match = typePattern.exec(code)) !== null) {
|
|
960
|
+
exports3.push(match[1]);
|
|
961
|
+
}
|
|
962
|
+
const methodPattern = /^[ \t]*public\s+(?:(?:static|final|abstract|synchronized|native|default)\s+)*(?:<[^>]*>\s+)?(?:[\w.<>\[\]]+\s+)+([A-Za-z_]\w*)\s*\(/gm;
|
|
963
|
+
while ((match = methodPattern.exec(code)) !== null) {
|
|
964
|
+
const name = match[1];
|
|
965
|
+
if (!["if", "for", "while", "switch", "class", "interface", "enum"].includes(name)) {
|
|
966
|
+
exports3.push(name);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return [...new Set(exports3)];
|
|
970
|
+
}
|
|
971
|
+
var JavaParser = {
|
|
972
|
+
lang: "java",
|
|
973
|
+
extensions: [".java"],
|
|
974
|
+
extractEntities,
|
|
975
|
+
extractImports,
|
|
976
|
+
extractExports,
|
|
977
|
+
entityPatterns: exports2.javaEntityPatterns
|
|
978
|
+
};
|
|
979
|
+
(0, registry_1.registerParser)(JavaParser);
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
// dist/languages/kotlin.js
|
|
984
|
+
var require_kotlin = __commonJS({
|
|
985
|
+
"dist/languages/kotlin.js"(exports2) {
|
|
986
|
+
"use strict";
|
|
987
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
988
|
+
exports2.kotlinEntityPatterns = void 0;
|
|
989
|
+
var registry_1 = require_registry();
|
|
990
|
+
var constants_1 = require_constants();
|
|
991
|
+
function escapeRegex(s) {
|
|
992
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
993
|
+
}
|
|
994
|
+
function estimateComplexity(code, name) {
|
|
995
|
+
const lines = code.split("\n");
|
|
996
|
+
const defRegex = new RegExp(`(?:^|\\s)fun\\s+(?:<[^>]*>\\s+)?(?:\\w[\\w.]*\\.)?${escapeRegex(name)}\\s*(?:\\(|<)`, "m");
|
|
997
|
+
const defLineIdx = lines.findIndex((l) => defRegex.test(l));
|
|
998
|
+
if (defLineIdx === -1)
|
|
999
|
+
return "low";
|
|
1000
|
+
let startLine = defLineIdx;
|
|
1001
|
+
while (startLine < lines.length && !lines[startLine].includes("{")) {
|
|
1002
|
+
startLine++;
|
|
1003
|
+
}
|
|
1004
|
+
if (startLine >= lines.length)
|
|
1005
|
+
return "low";
|
|
1006
|
+
let braceCount = 0;
|
|
1007
|
+
let started = false;
|
|
1008
|
+
const bodyLines = [];
|
|
1009
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
1010
|
+
const line = lines[i];
|
|
1011
|
+
for (const ch of line) {
|
|
1012
|
+
if (ch === "{") {
|
|
1013
|
+
braceCount++;
|
|
1014
|
+
started = true;
|
|
1015
|
+
} else if (ch === "}") {
|
|
1016
|
+
braceCount--;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
bodyLines.push(line);
|
|
1020
|
+
if (started && braceCount <= 0)
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
const body = bodyLines.join("\n");
|
|
1024
|
+
const branches = (body.match(/\b(if|else\s+if|else|for|while|when|catch|&&|\|\|)\b/g) || []).length;
|
|
1025
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.low)
|
|
1026
|
+
return "low";
|
|
1027
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.medium)
|
|
1028
|
+
return "medium";
|
|
1029
|
+
return "high";
|
|
1030
|
+
}
|
|
1031
|
+
exports2.kotlinEntityPatterns = [
|
|
1032
|
+
// class declarations (including data class, sealed class, abstract class, inner class)
|
|
1033
|
+
// The trailing brace is optional: abstract classes may have no body on the same line.
|
|
1034
|
+
// We anchor by requiring the class name to be followed by whitespace, <, (, :, { or EOL.
|
|
1035
|
+
{
|
|
1036
|
+
regex: /^[ \t]*(?:(?:public|private|protected|internal|abstract|sealed|data|open|inner|inline|value|annotation)\s+)*class\s+([A-Za-z_]\w*)(?=[\s<(:,{\n]|$)/gm,
|
|
1037
|
+
type: "class"
|
|
1038
|
+
},
|
|
1039
|
+
// object declarations (singleton objects and companion objects)
|
|
1040
|
+
{
|
|
1041
|
+
regex: /^[ \t]*(?:(?:public|private|protected|internal)\s+)*(?:companion\s+)?object\s+([A-Za-z_]\w*)\s*(?::\s*[^{]+)?\s*\{/gm,
|
|
1042
|
+
type: "class"
|
|
1043
|
+
},
|
|
1044
|
+
// interface declarations
|
|
1045
|
+
{
|
|
1046
|
+
regex: /^[ \t]*(?:(?:public|private|protected|internal|sealed|fun)\s+)*interface\s+([A-Za-z_]\w*)(?:\s*<[^{]*)?(?:\s*:\s*[^{]+)?\s*\{/gm,
|
|
1047
|
+
type: "interface"
|
|
1048
|
+
},
|
|
1049
|
+
// enum class
|
|
1050
|
+
{
|
|
1051
|
+
regex: /^[ \t]*(?:(?:public|private|protected|internal)\s+)*enum\s+class\s+([A-Za-z_]\w*)\s*(?:\([^)]*\))?\s*\{/gm,
|
|
1052
|
+
type: "class"
|
|
1053
|
+
},
|
|
1054
|
+
// function declarations (including suspend, inline, operator, extension functions)
|
|
1055
|
+
{
|
|
1056
|
+
regex: /^[ \t]*(?:(?:public|private|protected|internal|override|open|final|abstract|suspend|inline|operator|infix|tailrec|external|actual|expect)\s+)*fun\s+(?:<[^>]*>\s+)?(?:[\w.]+\.)?([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\(/gm,
|
|
1057
|
+
type: "function"
|
|
1058
|
+
}
|
|
1059
|
+
];
|
|
1060
|
+
function extractEntities(code, _filePath) {
|
|
1061
|
+
const entities = [];
|
|
1062
|
+
for (const { regex, type } of exports2.kotlinEntityPatterns) {
|
|
1063
|
+
regex.lastIndex = 0;
|
|
1064
|
+
let match;
|
|
1065
|
+
while ((match = regex.exec(code)) !== null) {
|
|
1066
|
+
const name = match[1];
|
|
1067
|
+
if (!name)
|
|
1068
|
+
continue;
|
|
1069
|
+
const upToMatch = code.slice(0, match.index);
|
|
1070
|
+
const line = upToMatch.split("\n").length;
|
|
1071
|
+
if (entities.some((e) => e.name === name && e.line === line))
|
|
1072
|
+
continue;
|
|
1073
|
+
entities.push({
|
|
1074
|
+
name,
|
|
1075
|
+
type,
|
|
1076
|
+
line,
|
|
1077
|
+
complexity: type === "function" ? estimateComplexity(code, name) : "low"
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
return entities;
|
|
1082
|
+
}
|
|
1083
|
+
function extractImports(code) {
|
|
1084
|
+
const imports = [];
|
|
1085
|
+
const importPattern = /^import\s+([\w.]+?)(\.\*)?\s*(?:as\s+(\w+))?\s*$/gm;
|
|
1086
|
+
let match;
|
|
1087
|
+
while ((match = importPattern.exec(code)) !== null) {
|
|
1088
|
+
const fullPath = match[1];
|
|
1089
|
+
const isWild = Boolean(match[2]);
|
|
1090
|
+
const alias = match[3];
|
|
1091
|
+
if (isWild)
|
|
1092
|
+
continue;
|
|
1093
|
+
const lastName = fullPath.split(".").pop() || fullPath;
|
|
1094
|
+
const localName = alias || lastName;
|
|
1095
|
+
imports.push({
|
|
1096
|
+
source: fullPath,
|
|
1097
|
+
names: [localName],
|
|
1098
|
+
isLocal: false
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
return imports;
|
|
1102
|
+
}
|
|
1103
|
+
function extractExports(code) {
|
|
1104
|
+
const exports3 = [];
|
|
1105
|
+
const patterns = [
|
|
1106
|
+
/^(?:(?:public|open|abstract|sealed|data|inline|value)\s+)*class\s+([A-Za-z_]\w*)/gm,
|
|
1107
|
+
/^(?:(?:public)\s+)?object\s+([A-Za-z_]\w*)/gm,
|
|
1108
|
+
/^(?:(?:public|sealed|fun)\s+)*interface\s+([A-Za-z_]\w*)/gm,
|
|
1109
|
+
/^(?:(?:public|open|inline|suspend|operator|infix|tailrec)\s+)*fun\s+(?:<[^>]*>\s+)?([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\(/gm
|
|
1110
|
+
];
|
|
1111
|
+
for (const pattern of patterns) {
|
|
1112
|
+
let match;
|
|
1113
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
1114
|
+
if (match[1])
|
|
1115
|
+
exports3.push(match[1]);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return [...new Set(exports3)];
|
|
1119
|
+
}
|
|
1120
|
+
var KotlinParser = {
|
|
1121
|
+
lang: "kotlin",
|
|
1122
|
+
extensions: [".kt", ".kts"],
|
|
1123
|
+
extractEntities,
|
|
1124
|
+
extractImports,
|
|
1125
|
+
extractExports,
|
|
1126
|
+
entityPatterns: exports2.kotlinEntityPatterns
|
|
1127
|
+
};
|
|
1128
|
+
(0, registry_1.registerParser)(KotlinParser);
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// dist/languages/php.js
|
|
1133
|
+
var require_php = __commonJS({
|
|
1134
|
+
"dist/languages/php.js"(exports2) {
|
|
1135
|
+
"use strict";
|
|
1136
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
1137
|
+
exports2.phpEntityPatterns = void 0;
|
|
1138
|
+
var registry_1 = require_registry();
|
|
1139
|
+
var constants_1 = require_constants();
|
|
1140
|
+
function escapeRegex(s) {
|
|
1141
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1142
|
+
}
|
|
1143
|
+
function estimateComplexity(code, name) {
|
|
1144
|
+
const lines = code.split("\n");
|
|
1145
|
+
const defRegex = new RegExp(`(?:^|\\s)function\\s+${escapeRegex(name)}\\s*\\(`, "m");
|
|
1146
|
+
const defLineIdx = lines.findIndex((l) => defRegex.test(l));
|
|
1147
|
+
if (defLineIdx === -1)
|
|
1148
|
+
return "low";
|
|
1149
|
+
let startLine = defLineIdx;
|
|
1150
|
+
while (startLine < lines.length && !lines[startLine].includes("{")) {
|
|
1151
|
+
startLine++;
|
|
1152
|
+
}
|
|
1153
|
+
if (startLine >= lines.length)
|
|
92
1154
|
return "low";
|
|
93
|
-
|
|
94
|
-
|
|
1155
|
+
let braceCount = 0;
|
|
1156
|
+
let started = false;
|
|
1157
|
+
const bodyLines = [];
|
|
1158
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
1159
|
+
const line = lines[i];
|
|
1160
|
+
for (const ch of line) {
|
|
1161
|
+
if (ch === "{") {
|
|
1162
|
+
braceCount++;
|
|
1163
|
+
started = true;
|
|
1164
|
+
} else if (ch === "}") {
|
|
1165
|
+
braceCount--;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
bodyLines.push(line);
|
|
1169
|
+
if (started && braceCount <= 0)
|
|
1170
|
+
break;
|
|
1171
|
+
}
|
|
1172
|
+
const body = bodyLines.join("\n");
|
|
1173
|
+
const branches = (body.match(/\b(if|elseif|else|for|foreach|while|do|switch|case|catch|&&|\|\|)\b/g) || []).length;
|
|
95
1174
|
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.low)
|
|
96
1175
|
return "low";
|
|
97
1176
|
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.medium)
|
|
98
1177
|
return "medium";
|
|
99
1178
|
return "high";
|
|
100
1179
|
}
|
|
101
|
-
|
|
1180
|
+
exports2.phpEntityPatterns = [
|
|
1181
|
+
// class (abstract class, final class, readonly class, etc.)
|
|
1182
|
+
{
|
|
1183
|
+
regex: /^[ \t]*(?:(?:abstract|final|readonly)\s+)*class\s+([A-Za-z_]\w*)(?:\s+extends\s+\S+)?(?:\s+implements\s+[^{]+)?\s*\{/gm,
|
|
1184
|
+
type: "class"
|
|
1185
|
+
},
|
|
1186
|
+
// interface
|
|
1187
|
+
{
|
|
1188
|
+
regex: /^[ \t]*interface\s+([A-Za-z_]\w*)(?:\s+extends\s+[^{]+)?\s*\{/gm,
|
|
1189
|
+
type: "interface"
|
|
1190
|
+
},
|
|
1191
|
+
// trait
|
|
1192
|
+
{
|
|
1193
|
+
regex: /^[ \t]*trait\s+([A-Za-z_]\w*)\s*\{/gm,
|
|
1194
|
+
type: "class"
|
|
1195
|
+
},
|
|
1196
|
+
// enum (PHP 8.1+)
|
|
1197
|
+
{
|
|
1198
|
+
regex: /^[ \t]*enum\s+([A-Za-z_]\w*)(?:\s*:\s*\w+)?(?:\s+implements\s+[^{]+)?\s*\{/gm,
|
|
1199
|
+
type: "class"
|
|
1200
|
+
},
|
|
1201
|
+
// function and method declarations
|
|
1202
|
+
{
|
|
1203
|
+
regex: /^[ \t]*(?:(?:public|protected|private|static|abstract|final|readonly)\s+)*function\s+([A-Za-z_]\w*)\s*\(/gm,
|
|
1204
|
+
type: "function"
|
|
1205
|
+
}
|
|
1206
|
+
];
|
|
1207
|
+
function extractEntities(code, _filePath) {
|
|
102
1208
|
const entities = [];
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
1209
|
+
for (const { regex, type } of exports2.phpEntityPatterns) {
|
|
1210
|
+
regex.lastIndex = 0;
|
|
1211
|
+
let match;
|
|
1212
|
+
while ((match = regex.exec(code)) !== null) {
|
|
1213
|
+
const name = match[1];
|
|
1214
|
+
if (!name)
|
|
1215
|
+
continue;
|
|
1216
|
+
const upToMatch = code.slice(0, match.index);
|
|
1217
|
+
const line = upToMatch.split("\n").length;
|
|
1218
|
+
if (entities.some((e) => e.name === name && e.line === line))
|
|
1219
|
+
continue;
|
|
1220
|
+
entities.push({
|
|
1221
|
+
name,
|
|
1222
|
+
type,
|
|
1223
|
+
line,
|
|
1224
|
+
complexity: type === "function" ? estimateComplexity(code, name) : "low"
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return entities;
|
|
1229
|
+
}
|
|
1230
|
+
function extractImports(code) {
|
|
1231
|
+
const imports = [];
|
|
1232
|
+
const usePattern = /^use\s+([\w\\]+(?:\s*\{[^}]*\})?)\s*(?:as\s+(\w+)\s*)?;/gm;
|
|
1233
|
+
let match;
|
|
1234
|
+
while ((match = usePattern.exec(code)) !== null) {
|
|
1235
|
+
const raw = match[1].trim();
|
|
1236
|
+
const alias = match[2]?.trim();
|
|
1237
|
+
if (raw.includes("{")) {
|
|
1238
|
+
const prefixMatch = raw.match(/^([\w\\]+)\\?\s*\{([^}]*)\}/);
|
|
1239
|
+
if (prefixMatch) {
|
|
1240
|
+
const prefix = prefixMatch[1];
|
|
1241
|
+
const items = prefixMatch[2].split(",");
|
|
1242
|
+
for (const item of items) {
|
|
1243
|
+
const parts = item.trim().split(/\s+as\s+/i);
|
|
1244
|
+
const fullName = (prefix + "\\" + parts[0].trim()).replace(/\\+/g, "\\");
|
|
1245
|
+
const lastName = parts[1] || parts[0].trim().split("\\").pop() || fullName;
|
|
1246
|
+
imports.push({ source: fullName, names: [lastName.trim()], isLocal: false });
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
} else {
|
|
1250
|
+
const fullPath = raw;
|
|
1251
|
+
const lastName = alias || fullPath.split("\\").pop() || fullPath;
|
|
1252
|
+
imports.push({ source: fullPath, names: [lastName.trim()], isLocal: false });
|
|
144
1253
|
}
|
|
1254
|
+
}
|
|
1255
|
+
const includePattern = /(?:require|include)(?:_once)?\s*[^;'"]*?['"]([^'"]+)['"]/gm;
|
|
1256
|
+
while ((match = includePattern.exec(code)) !== null) {
|
|
1257
|
+
const source = match[1];
|
|
1258
|
+
const name = source.split("/").pop()?.replace(/\.php$/i, "") || source;
|
|
1259
|
+
imports.push({ source, names: [name], isLocal: true });
|
|
1260
|
+
}
|
|
1261
|
+
return imports;
|
|
1262
|
+
}
|
|
1263
|
+
function extractExports(code) {
|
|
1264
|
+
const exports3 = [];
|
|
1265
|
+
const patterns = [
|
|
1266
|
+
/^(?:(?:abstract|final|readonly)\s+)*class\s+([A-Za-z_]\w*)/gm,
|
|
1267
|
+
/^interface\s+([A-Za-z_]\w*)/gm,
|
|
1268
|
+
/^trait\s+([A-Za-z_]\w*)/gm,
|
|
1269
|
+
/^enum\s+([A-Za-z_]\w*)/gm,
|
|
1270
|
+
/^function\s+([A-Za-z_]\w*)\s*\(/gm
|
|
145
1271
|
];
|
|
146
|
-
for (const
|
|
1272
|
+
for (const pattern of patterns) {
|
|
147
1273
|
let match;
|
|
1274
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
1275
|
+
if (match[1])
|
|
1276
|
+
exports3.push(match[1]);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return [...new Set(exports3)];
|
|
1280
|
+
}
|
|
1281
|
+
var PhpParser = {
|
|
1282
|
+
lang: "php",
|
|
1283
|
+
extensions: [".php", ".phtml", ".php3", ".php4", ".php5", ".php7"],
|
|
1284
|
+
extractEntities,
|
|
1285
|
+
extractImports,
|
|
1286
|
+
extractExports,
|
|
1287
|
+
entityPatterns: exports2.phpEntityPatterns
|
|
1288
|
+
};
|
|
1289
|
+
(0, registry_1.registerParser)(PhpParser);
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
// dist/languages/ruby.js
|
|
1294
|
+
var require_ruby = __commonJS({
|
|
1295
|
+
"dist/languages/ruby.js"(exports2) {
|
|
1296
|
+
"use strict";
|
|
1297
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
1298
|
+
exports2.rubyEntityPatterns = void 0;
|
|
1299
|
+
var registry_1 = require_registry();
|
|
1300
|
+
var constants_1 = require_constants();
|
|
1301
|
+
function escapeRegex(s) {
|
|
1302
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1303
|
+
}
|
|
1304
|
+
function sanitizeMethodName(name) {
|
|
1305
|
+
if (name.endsWith("!"))
|
|
1306
|
+
return `${name.slice(0, -1)}_bang`;
|
|
1307
|
+
if (name.endsWith("?"))
|
|
1308
|
+
return `${name.slice(0, -1)}_pred`;
|
|
1309
|
+
if (name.endsWith("="))
|
|
1310
|
+
return `${name.slice(0, -1)}_eq`;
|
|
1311
|
+
return name;
|
|
1312
|
+
}
|
|
1313
|
+
function estimateComplexity(code, name) {
|
|
1314
|
+
const lines = code.split("\n");
|
|
1315
|
+
const defRegex = new RegExp(`^[ \\t]*def\\s+(?:self\\.)?${escapeRegex(name)}(?:[!?=])?\\s*(\\(|$)`, "m");
|
|
1316
|
+
const defLineIdx = lines.findIndex((l) => defRegex.test(l));
|
|
1317
|
+
if (defLineIdx === -1)
|
|
1318
|
+
return "low";
|
|
1319
|
+
const bodyLines = [];
|
|
1320
|
+
let depth = 0;
|
|
1321
|
+
for (let i = defLineIdx; i < lines.length; i++) {
|
|
1322
|
+
const line = lines[i];
|
|
1323
|
+
const trimmed = line.trim();
|
|
1324
|
+
if (/\b(def|class|module|do\b|begin|if(?!.*\bend\b)|unless(?!.*\bend\b)|while(?!.*\bend\b)|until(?!.*\bend\b)|for\s|case\b)\b/.test(trimmed)) {
|
|
1325
|
+
depth++;
|
|
1326
|
+
}
|
|
1327
|
+
if (/\bend\b/.test(trimmed)) {
|
|
1328
|
+
depth--;
|
|
1329
|
+
if (depth <= 0) {
|
|
1330
|
+
bodyLines.push(line);
|
|
1331
|
+
break;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
bodyLines.push(line);
|
|
1335
|
+
}
|
|
1336
|
+
const body = bodyLines.join("\n");
|
|
1337
|
+
const branches = (body.match(/\b(if|elsif|else|unless|while|until|for|rescue|when|and|or|&&|\|\|)\b/g) || []).length;
|
|
1338
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.low)
|
|
1339
|
+
return "low";
|
|
1340
|
+
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.medium)
|
|
1341
|
+
return "medium";
|
|
1342
|
+
return "high";
|
|
1343
|
+
}
|
|
1344
|
+
exports2.rubyEntityPatterns = [
|
|
1345
|
+
// class declarations (class Foo, class Foo < Bar)
|
|
1346
|
+
{
|
|
1347
|
+
regex: /^[ \t]*class\s+([A-Z]\w*(?:::[A-Z]\w*)*)\s*(?:<\s*\S+)?\s*$/gm,
|
|
1348
|
+
type: "class"
|
|
1349
|
+
},
|
|
1350
|
+
// module declarations
|
|
1351
|
+
{
|
|
1352
|
+
regex: /^[ \t]*module\s+([A-Z]\w*(?:::[A-Z]\w*)*)\s*$/gm,
|
|
1353
|
+
type: "class"
|
|
1354
|
+
},
|
|
1355
|
+
// singleton methods: def self.method_name[!?=]
|
|
1356
|
+
{
|
|
1357
|
+
regex: /^[ \t]*def\s+self\.([A-Za-z_]\w*[!?=]?)\s*(?:\(|$)/gm,
|
|
1358
|
+
type: "function"
|
|
1359
|
+
},
|
|
1360
|
+
// instance methods: def method_name[!?=]
|
|
1361
|
+
{
|
|
1362
|
+
regex: /^[ \t]*def\s+([A-Za-z_]\w*[!?=]?)\s*(?:\(|$)/gm,
|
|
1363
|
+
type: "function"
|
|
1364
|
+
}
|
|
1365
|
+
];
|
|
1366
|
+
function extractEntities(code, _filePath) {
|
|
1367
|
+
const entities = [];
|
|
1368
|
+
for (const { regex, type } of exports2.rubyEntityPatterns) {
|
|
148
1369
|
regex.lastIndex = 0;
|
|
1370
|
+
let match;
|
|
149
1371
|
while ((match = regex.exec(code)) !== null) {
|
|
1372
|
+
const rawName = match[1];
|
|
1373
|
+
if (!rawName)
|
|
1374
|
+
continue;
|
|
1375
|
+
const name = type === "function" ? sanitizeMethodName(rawName) : rawName;
|
|
150
1376
|
const upToMatch = code.slice(0, match.index);
|
|
151
1377
|
const line = upToMatch.split("\n").length;
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
const name = match[1];
|
|
161
|
-
if (entities.some((e) => e.name === name))
|
|
162
|
-
continue;
|
|
163
|
-
entities.push({
|
|
164
|
-
name,
|
|
165
|
-
type,
|
|
166
|
-
line,
|
|
167
|
-
complexity: estimateComplexity(code, name)
|
|
168
|
-
});
|
|
169
|
-
}
|
|
1378
|
+
if (entities.some((e) => e.name === name && e.line === line))
|
|
1379
|
+
continue;
|
|
1380
|
+
entities.push({
|
|
1381
|
+
name,
|
|
1382
|
+
type,
|
|
1383
|
+
line,
|
|
1384
|
+
complexity: type === "function" ? estimateComplexity(code, rawName) : "low"
|
|
1385
|
+
});
|
|
170
1386
|
}
|
|
171
1387
|
}
|
|
172
1388
|
return entities;
|
|
173
1389
|
}
|
|
174
1390
|
function extractImports(code) {
|
|
175
1391
|
const imports = [];
|
|
176
|
-
const
|
|
1392
|
+
const requirePattern = /^[ \t]*require\s+['"]([^'"]+)['"]/gm;
|
|
177
1393
|
let match;
|
|
178
|
-
while ((match =
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
imports.push({
|
|
182
|
-
source,
|
|
183
|
-
names,
|
|
184
|
-
isLocal: source.startsWith(".")
|
|
185
|
-
});
|
|
1394
|
+
while ((match = requirePattern.exec(code)) !== null) {
|
|
1395
|
+
const source = match[1];
|
|
1396
|
+
const name = source.split("/").pop() || source;
|
|
1397
|
+
imports.push({ source, names: [name], isLocal: false });
|
|
186
1398
|
}
|
|
187
|
-
const
|
|
188
|
-
while ((match =
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
isLocal: match[2].startsWith(".")
|
|
193
|
-
});
|
|
1399
|
+
const relPattern = /^[ \t]*require_relative\s+['"]([^'"]+)['"]/gm;
|
|
1400
|
+
while ((match = relPattern.exec(code)) !== null) {
|
|
1401
|
+
const source = match[1];
|
|
1402
|
+
const name = source.split("/").pop() || source;
|
|
1403
|
+
imports.push({ source, names: [name], isLocal: true });
|
|
194
1404
|
}
|
|
195
|
-
const
|
|
196
|
-
while ((match =
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
1405
|
+
const loadPattern = /^[ \t]*load\s+['"]([^'"]+)['"]/gm;
|
|
1406
|
+
while ((match = loadPattern.exec(code)) !== null) {
|
|
1407
|
+
const source = match[1];
|
|
1408
|
+
const name = source.split("/").pop()?.replace(/\.rb$/, "") || source;
|
|
1409
|
+
imports.push({ source, names: [name], isLocal: true });
|
|
1410
|
+
}
|
|
1411
|
+
const mixinPattern = /^[ \t]*(?:include|extend|prepend)\s+([A-Z]\w*(?:::[A-Z]\w*)*)/gm;
|
|
1412
|
+
while ((match = mixinPattern.exec(code)) !== null) {
|
|
1413
|
+
const source = match[1];
|
|
1414
|
+
const name = source.split("::").pop() || source;
|
|
1415
|
+
imports.push({ source, names: [name], isLocal: false });
|
|
203
1416
|
}
|
|
204
1417
|
return imports;
|
|
205
1418
|
}
|
|
206
1419
|
function extractExports(code) {
|
|
207
1420
|
const exports3 = [];
|
|
208
|
-
const
|
|
1421
|
+
const typePattern = /^(?:class|module)\s+([A-Z]\w*(?:::[A-Z]\w*)*)/gm;
|
|
209
1422
|
let match;
|
|
210
|
-
while ((match =
|
|
1423
|
+
while ((match = typePattern.exec(code)) !== null) {
|
|
211
1424
|
exports3.push(match[1]);
|
|
212
1425
|
}
|
|
213
|
-
const
|
|
214
|
-
while ((match =
|
|
215
|
-
const
|
|
216
|
-
exports3.push(...
|
|
1426
|
+
const attrPattern = /^[ \t]*attr_(?:reader|writer|accessor)\s+(.+)$/gm;
|
|
1427
|
+
while ((match = attrPattern.exec(code)) !== null) {
|
|
1428
|
+
const syms = match[1].split(",").map((s) => s.trim().replace(/^:/, ""));
|
|
1429
|
+
exports3.push(...syms.filter(Boolean));
|
|
1430
|
+
}
|
|
1431
|
+
const pubFuncPattern = /^[ \t]*(?:module_function|public)\s+def\s+([A-Za-z_]\w*[!?=]?)/gm;
|
|
1432
|
+
while ((match = pubFuncPattern.exec(code)) !== null) {
|
|
1433
|
+
exports3.push(sanitizeMethodName(match[1]));
|
|
217
1434
|
}
|
|
218
1435
|
return [...new Set(exports3)];
|
|
219
1436
|
}
|
|
220
|
-
var
|
|
221
|
-
lang: "
|
|
222
|
-
extensions: [".
|
|
1437
|
+
var RubyParser = {
|
|
1438
|
+
lang: "ruby",
|
|
1439
|
+
extensions: [".rb", ".rake", ".gemspec"],
|
|
223
1440
|
extractEntities,
|
|
224
1441
|
extractImports,
|
|
225
|
-
extractExports
|
|
1442
|
+
extractExports,
|
|
1443
|
+
entityPatterns: exports2.rubyEntityPatterns
|
|
226
1444
|
};
|
|
227
|
-
(0, registry_1.registerParser)(
|
|
1445
|
+
(0, registry_1.registerParser)(RubyParser);
|
|
228
1446
|
}
|
|
229
1447
|
});
|
|
230
1448
|
|
|
231
|
-
// dist/languages/
|
|
232
|
-
var
|
|
233
|
-
"dist/languages/
|
|
1449
|
+
// dist/languages/swift.js
|
|
1450
|
+
var require_swift = __commonJS({
|
|
1451
|
+
"dist/languages/swift.js"(exports2) {
|
|
234
1452
|
"use strict";
|
|
235
1453
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
1454
|
+
exports2.swiftEntityPatterns = void 0;
|
|
236
1455
|
var registry_1 = require_registry();
|
|
237
1456
|
var constants_1 = require_constants();
|
|
1457
|
+
function escapeRegex(s) {
|
|
1458
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1459
|
+
}
|
|
238
1460
|
function estimateComplexity(code, name) {
|
|
239
1461
|
const lines = code.split("\n");
|
|
240
|
-
const
|
|
241
|
-
|
|
1462
|
+
const defRegex = new RegExp(`(?:^|\\s)(?:func\\s+${escapeRegex(name)}|init|deinit|subscript)\\s*(?:<[^>]*>)?\\s*\\(`, "m");
|
|
1463
|
+
const defLineIdx = lines.findIndex((l) => defRegex.test(l));
|
|
1464
|
+
if (defLineIdx === -1)
|
|
1465
|
+
return "low";
|
|
1466
|
+
let startLine = defLineIdx;
|
|
1467
|
+
while (startLine < lines.length && !lines[startLine].includes("{")) {
|
|
1468
|
+
startLine++;
|
|
1469
|
+
}
|
|
1470
|
+
if (startLine >= lines.length)
|
|
242
1471
|
return "low";
|
|
1472
|
+
let braceCount = 0;
|
|
1473
|
+
let started = false;
|
|
243
1474
|
const bodyLines = [];
|
|
244
|
-
for (let i =
|
|
1475
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
245
1476
|
const line = lines[i];
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
1477
|
+
for (const ch of line) {
|
|
1478
|
+
if (ch === "{") {
|
|
1479
|
+
braceCount++;
|
|
1480
|
+
started = true;
|
|
1481
|
+
} else if (ch === "}") {
|
|
1482
|
+
braceCount--;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
250
1485
|
bodyLines.push(line);
|
|
1486
|
+
if (started && braceCount <= 0)
|
|
1487
|
+
break;
|
|
251
1488
|
}
|
|
252
1489
|
const body = bodyLines.join("\n");
|
|
253
|
-
const branches = (body.match(/\b(if|
|
|
1490
|
+
const branches = (body.match(/\b(if|else\s+if|else|for\s|while|repeat|switch|case|catch|guard|&&|\|\|)\b/g) || []).length;
|
|
254
1491
|
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.low)
|
|
255
1492
|
return "low";
|
|
256
1493
|
if (branches <= constants_1.COMPLEXITY_THRESHOLDS.medium)
|
|
257
1494
|
return "medium";
|
|
258
1495
|
return "high";
|
|
259
1496
|
}
|
|
260
|
-
|
|
1497
|
+
exports2.swiftEntityPatterns = [
|
|
1498
|
+
// class (including final class, open class, public class)
|
|
1499
|
+
{
|
|
1500
|
+
regex: /^[ \t]*(?:(?:public|internal|private|fileprivate|open|final|@MainActor)\s+)*class\s+([A-Za-z_]\w*)(?:\s*<[^{]*?)?\s*(?::\s*[^{]+)?\s*\{/gm,
|
|
1501
|
+
type: "class"
|
|
1502
|
+
},
|
|
1503
|
+
// struct
|
|
1504
|
+
{
|
|
1505
|
+
regex: /^[ \t]*(?:(?:public|internal|private|fileprivate)\s+)*struct\s+([A-Za-z_]\w*)(?:\s*<[^{]*?)?\s*(?::\s*[^{]+)?\s*\{/gm,
|
|
1506
|
+
type: "class"
|
|
1507
|
+
},
|
|
1508
|
+
// enum
|
|
1509
|
+
{
|
|
1510
|
+
regex: /^[ \t]*(?:(?:public|internal|private|fileprivate|indirect)\s+)*enum\s+([A-Za-z_]\w*)(?:\s*<[^{]*?)?\s*(?::\s*[^{]+)?\s*\{/gm,
|
|
1511
|
+
type: "class"
|
|
1512
|
+
},
|
|
1513
|
+
// protocol
|
|
1514
|
+
{
|
|
1515
|
+
regex: /^[ \t]*(?:(?:public|internal|private|fileprivate)\s+)*protocol\s+([A-Za-z_]\w*)(?:\s*<[^{]*?)?\s*(?::\s*[^{]+)?\s*\{/gm,
|
|
1516
|
+
type: "interface"
|
|
1517
|
+
},
|
|
1518
|
+
// actor (Swift 5.5+)
|
|
1519
|
+
{
|
|
1520
|
+
regex: /^[ \t]*(?:(?:public|internal|private|fileprivate|distributed)\s+)*actor\s+([A-Za-z_]\w*)(?:\s*:\s*[^{]+)?\s*\{/gm,
|
|
1521
|
+
type: "class"
|
|
1522
|
+
},
|
|
1523
|
+
// extension (cross-file method container, same type as class in Python extractor)
|
|
1524
|
+
// Supports: extension Foo, extension Array where Element: Comparable
|
|
1525
|
+
{
|
|
1526
|
+
regex: /^[ \t]*extension\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)(?:\s*<[^>{]*>)?(?:\s*:\s*[^{]+)?(?:\s+where\s+[^{]+)?\s*\{/gm,
|
|
1527
|
+
type: "class"
|
|
1528
|
+
},
|
|
1529
|
+
// function declarations (including mutating, static, class func, override, async, throws)
|
|
1530
|
+
{
|
|
1531
|
+
regex: /^[ \t]*(?:(?:public|internal|private|fileprivate|open|override|static|class|mutating|nonmutating|dynamic|final|required|convenience|async|throws|rethrows|nonisolated|@discardableResult|@objc|@MainActor)\s+)*func\s+([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\(/gm,
|
|
1532
|
+
type: "function"
|
|
1533
|
+
},
|
|
1534
|
+
// init declarations
|
|
1535
|
+
{
|
|
1536
|
+
regex: /^[ \t]*(?:(?:public|internal|private|fileprivate|override|required|convenience)\s+)*init\??\s*(?:<[^>]*>)?\s*\(/gm,
|
|
1537
|
+
type: "function"
|
|
1538
|
+
},
|
|
1539
|
+
// deinit
|
|
1540
|
+
{
|
|
1541
|
+
regex: /^[ \t]*deinit\s*\{/gm,
|
|
1542
|
+
type: "function"
|
|
1543
|
+
},
|
|
1544
|
+
// subscript
|
|
1545
|
+
{
|
|
1546
|
+
regex: /^[ \t]*(?:(?:public|internal|private|fileprivate|static|override)\s+)*subscript\s*(?:<[^>]*>)?\s*\(/gm,
|
|
1547
|
+
type: "function"
|
|
1548
|
+
}
|
|
1549
|
+
];
|
|
1550
|
+
function extractEntities(code, _filePath) {
|
|
261
1551
|
const entities = [];
|
|
262
|
-
const
|
|
263
|
-
// regular functions
|
|
264
|
-
{
|
|
265
|
-
regex: /^(?:async\s+)?def\s+(\w+)\s*\(/gm,
|
|
266
|
-
type: "function"
|
|
267
|
-
},
|
|
268
|
-
// classes
|
|
269
|
-
{
|
|
270
|
-
regex: /^class\s+(\w+)(?:\s*\([^)]*\))?\s*:/gm,
|
|
271
|
-
type: "class"
|
|
272
|
-
}
|
|
273
|
-
];
|
|
274
|
-
for (const { regex, type } of patterns) {
|
|
1552
|
+
for (const { regex, type } of exports2.swiftEntityPatterns) {
|
|
275
1553
|
regex.lastIndex = 0;
|
|
276
1554
|
let match;
|
|
277
1555
|
while ((match = regex.exec(code)) !== null) {
|
|
278
|
-
const name = match[1];
|
|
279
|
-
if (type === "function" && name.startsWith("__") && name.endsWith("__"))
|
|
280
|
-
continue;
|
|
281
|
-
if (entities.some((e) => e.name === name))
|
|
282
|
-
continue;
|
|
1556
|
+
const name = match[1] ?? (/\binit\b/.test(match[0]) ? "init" : /\bdeinit\b/.test(match[0]) ? "deinit" : "subscript");
|
|
283
1557
|
const upToMatch = code.slice(0, match.index);
|
|
284
1558
|
const line = upToMatch.split("\n").length;
|
|
1559
|
+
if (entities.some((e) => e.name === name && e.line === line))
|
|
1560
|
+
continue;
|
|
285
1561
|
entities.push({
|
|
286
1562
|
name,
|
|
287
1563
|
type,
|
|
@@ -294,40 +1570,53 @@ var require_python = __commonJS({
|
|
|
294
1570
|
}
|
|
295
1571
|
function extractImports(code) {
|
|
296
1572
|
const imports = [];
|
|
297
|
-
const
|
|
1573
|
+
const importPattern = /^[ \t]*import\s+(?:(?:class|struct|enum|func|var|let|typealias)\s+)?([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/gm;
|
|
298
1574
|
let match;
|
|
299
|
-
while ((match = fromPattern.exec(code)) !== null) {
|
|
300
|
-
const source = match[1];
|
|
301
|
-
const names = match[2].split(",").map((n) => n.trim()).filter((n) => n.length > 0);
|
|
302
|
-
const isLocal = source.startsWith(".");
|
|
303
|
-
imports.push({ source, names, isLocal });
|
|
304
|
-
}
|
|
305
|
-
const importPattern = /^import\s+([\w.]+)/gm;
|
|
306
1575
|
while ((match = importPattern.exec(code)) !== null) {
|
|
307
|
-
const
|
|
1576
|
+
const fullPath = match[1];
|
|
1577
|
+
const moduleName = fullPath.split(".")[0];
|
|
1578
|
+
if (["class", "struct", "enum", "func", "var", "let", "typealias"].includes(moduleName))
|
|
1579
|
+
continue;
|
|
308
1580
|
imports.push({
|
|
309
|
-
source,
|
|
310
|
-
names: [
|
|
1581
|
+
source: moduleName,
|
|
1582
|
+
names: [moduleName],
|
|
311
1583
|
isLocal: false
|
|
312
|
-
// bare imports are always external
|
|
313
1584
|
});
|
|
314
1585
|
}
|
|
315
1586
|
return imports;
|
|
316
1587
|
}
|
|
317
1588
|
function extractExports(code) {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
1589
|
+
const exports3 = [];
|
|
1590
|
+
const publicPatterns = [
|
|
1591
|
+
// public/open class, struct, enum, protocol, actor, extension
|
|
1592
|
+
/^(?:(?:public|open|final)\s+)*(?:class|struct|enum|protocol|actor)\s+([A-Za-z_]\w*)/gm,
|
|
1593
|
+
// public extension is not really an export, but surfaces it for graph linking
|
|
1594
|
+
/^(?:public\s+)?extension\s+([A-Za-z_]\w*)/gm,
|
|
1595
|
+
// public func
|
|
1596
|
+
/^[ \t]*(?:public|open)\s+(?:(?:static|class|override|mutating|async|throws|nonisolated)\s+)*func\s+([A-Za-z_]\w*)/gm,
|
|
1597
|
+
// public var / let
|
|
1598
|
+
/^[ \t]*(?:public|open)\s+(?:(?:static|class|lazy|private\(set\)|internal\(set\))\s+)*(?:var|let)\s+([A-Za-z_]\w*)/gm,
|
|
1599
|
+
// public init
|
|
1600
|
+
/^[ \t]*(?:public|open)\s+(?:required\s+|convenience\s+)?init/gm
|
|
1601
|
+
];
|
|
1602
|
+
for (const pattern of publicPatterns) {
|
|
1603
|
+
let match;
|
|
1604
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
1605
|
+
if (match[1])
|
|
1606
|
+
exports3.push(match[1]);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
return [...new Set(exports3)];
|
|
322
1610
|
}
|
|
323
|
-
var
|
|
324
|
-
lang: "
|
|
325
|
-
extensions: [".
|
|
1611
|
+
var SwiftParser = {
|
|
1612
|
+
lang: "swift",
|
|
1613
|
+
extensions: [".swift"],
|
|
326
1614
|
extractEntities,
|
|
327
1615
|
extractImports,
|
|
328
|
-
extractExports
|
|
1616
|
+
extractExports,
|
|
1617
|
+
entityPatterns: exports2.swiftEntityPatterns
|
|
329
1618
|
};
|
|
330
|
-
(0, registry_1.registerParser)(
|
|
1619
|
+
(0, registry_1.registerParser)(SwiftParser);
|
|
331
1620
|
}
|
|
332
1621
|
});
|
|
333
1622
|
|
|
@@ -405,13 +1694,17 @@ var require_parser = __commonJS({
|
|
|
405
1694
|
const parser = (0, registry_1.getLanguageParser)(ext);
|
|
406
1695
|
if (!parser)
|
|
407
1696
|
return null;
|
|
1697
|
+
const isHashCommentLang = [".py", ".rb", ".sh", ".bash", ".ps1"].includes(ext);
|
|
1698
|
+
const commentChar = isHashCommentLang ? "#" : "//";
|
|
408
1699
|
const cleanCode = code.split("\n").map((line) => {
|
|
409
|
-
const commentIndex = line.indexOf(
|
|
1700
|
+
const commentIndex = line.indexOf(commentChar);
|
|
410
1701
|
if (commentIndex === -1)
|
|
411
1702
|
return line;
|
|
412
1703
|
const before = line.slice(0, commentIndex);
|
|
413
|
-
const
|
|
414
|
-
|
|
1704
|
+
const inDouble = (before.match(/"/g) || []).length % 2 !== 0;
|
|
1705
|
+
const inSingle = (before.match(/'/g) || []).length % 2 !== 0;
|
|
1706
|
+
const inBacktick = (before.match(/`/g) || []).length % 2 !== 0;
|
|
1707
|
+
return inDouble || inSingle || inBacktick ? line : line.slice(0, commentIndex);
|
|
415
1708
|
}).join("\n");
|
|
416
1709
|
const lines = code.split("\n").length;
|
|
417
1710
|
const entities = parser.extractEntities(cleanCode, filePath);
|
|
@@ -812,6 +2105,157 @@ var require_output = __commonJS({
|
|
|
812
2105
|
}
|
|
813
2106
|
});
|
|
814
2107
|
|
|
2108
|
+
// dist/stages/gitdiff.js
|
|
2109
|
+
var require_gitdiff = __commonJS({
|
|
2110
|
+
"dist/stages/gitdiff.js"(exports2) {
|
|
2111
|
+
"use strict";
|
|
2112
|
+
var __importDefault2 = exports2 && exports2.__importDefault || function(mod) {
|
|
2113
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
2114
|
+
};
|
|
2115
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
2116
|
+
exports2.getChangedEntities = getChangedEntities;
|
|
2117
|
+
var child_process_1 = require("child_process");
|
|
2118
|
+
var path_1 = __importDefault2(require("path"));
|
|
2119
|
+
var registry_1 = require_registry();
|
|
2120
|
+
function getChangedEntities(options) {
|
|
2121
|
+
const diff = runGitDiff(options);
|
|
2122
|
+
if (!diff)
|
|
2123
|
+
return [];
|
|
2124
|
+
return parseDiff(diff, options.projectDir);
|
|
2125
|
+
}
|
|
2126
|
+
function runGitDiff(options) {
|
|
2127
|
+
const { projectDir: projectDir2, mode, commit, from, to } = options;
|
|
2128
|
+
let command;
|
|
2129
|
+
if (mode === "uncommitted") {
|
|
2130
|
+
command = "git diff HEAD";
|
|
2131
|
+
} else if (mode === "last-commit") {
|
|
2132
|
+
if (commit) {
|
|
2133
|
+
command = `git diff ${commit}~1 ${commit}`;
|
|
2134
|
+
} else {
|
|
2135
|
+
command = "git diff HEAD~1 HEAD";
|
|
2136
|
+
}
|
|
2137
|
+
} else if (mode === "branches") {
|
|
2138
|
+
if (!from || !to) {
|
|
2139
|
+
console.warn("\u26A0 --from and --to are required for branch comparison");
|
|
2140
|
+
return null;
|
|
2141
|
+
}
|
|
2142
|
+
command = `git diff ${from}...${to}`;
|
|
2143
|
+
} else {
|
|
2144
|
+
return null;
|
|
2145
|
+
}
|
|
2146
|
+
try {
|
|
2147
|
+
const result = (0, child_process_1.execSync)(command, {
|
|
2148
|
+
cwd: projectDir2,
|
|
2149
|
+
encoding: "utf-8",
|
|
2150
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2151
|
+
});
|
|
2152
|
+
return result || null;
|
|
2153
|
+
} catch (err) {
|
|
2154
|
+
console.warn(`\u26A0 Git command failed: ${command}`);
|
|
2155
|
+
console.warn(` Make sure ${projectDir2} is a git repository`);
|
|
2156
|
+
return null;
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
function parseDiff(diff, projectDir2) {
|
|
2160
|
+
const entities = [];
|
|
2161
|
+
const lines = diff.split("\n");
|
|
2162
|
+
let currentFile = "";
|
|
2163
|
+
let changeType = "modified";
|
|
2164
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2165
|
+
const line = lines[i];
|
|
2166
|
+
if (line.startsWith("diff --git")) {
|
|
2167
|
+
const fileMatch = line.match(/b\/(.+)$/);
|
|
2168
|
+
if (fileMatch) {
|
|
2169
|
+
currentFile = fileMatch[1];
|
|
2170
|
+
}
|
|
2171
|
+
changeType = "modified";
|
|
2172
|
+
continue;
|
|
2173
|
+
}
|
|
2174
|
+
if (line.startsWith("new file mode")) {
|
|
2175
|
+
changeType = "added";
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
if (line.startsWith("deleted file mode")) {
|
|
2179
|
+
changeType = "deleted";
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
if (line.startsWith("index ")) {
|
|
2183
|
+
if (changeType === "modified")
|
|
2184
|
+
changeType = "modified";
|
|
2185
|
+
continue;
|
|
2186
|
+
}
|
|
2187
|
+
if (line.startsWith("@@")) {
|
|
2188
|
+
const contextMatch = line.match(/@@[^@]*@@\s*(.+)$/);
|
|
2189
|
+
if (contextMatch) {
|
|
2190
|
+
const context = contextMatch[1].trim();
|
|
2191
|
+
const entity = extractEntityFromContext(context, currentFile);
|
|
2192
|
+
if (entity) {
|
|
2193
|
+
const description = buildDescription(lines, i, entity.name);
|
|
2194
|
+
const alreadyFound = entities.some((e) => e.name === entity.name && e.file === currentFile);
|
|
2195
|
+
if (!alreadyFound) {
|
|
2196
|
+
entities.push({
|
|
2197
|
+
name: entity.name,
|
|
2198
|
+
file: currentFile,
|
|
2199
|
+
changeType,
|
|
2200
|
+
description
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
return entities;
|
|
2209
|
+
}
|
|
2210
|
+
function extractEntityFromContext(context, file) {
|
|
2211
|
+
const ext = path_1.default.extname(file).toLowerCase();
|
|
2212
|
+
const parser = (0, registry_1.getLanguageParser)(ext);
|
|
2213
|
+
if (parser?.entityPatterns) {
|
|
2214
|
+
for (const { regex, type } of parser.entityPatterns) {
|
|
2215
|
+
if (type === "api")
|
|
2216
|
+
continue;
|
|
2217
|
+
const singleLineRegex = new RegExp(regex.source, regex.flags.replace("g", ""));
|
|
2218
|
+
const m = singleLineRegex.exec(context);
|
|
2219
|
+
if (m?.[1])
|
|
2220
|
+
return { name: m[1], type };
|
|
2221
|
+
}
|
|
2222
|
+
return null;
|
|
2223
|
+
}
|
|
2224
|
+
if ([".java", ".cs"].includes(ext)) {
|
|
2225
|
+
const m = context.match(/(?:public|private|protected|static|override|async|virtual)\s+\S+\s+(\w+)\s*\(/);
|
|
2226
|
+
if (m)
|
|
2227
|
+
return { name: m[1], type: "method" };
|
|
2228
|
+
}
|
|
2229
|
+
return null;
|
|
2230
|
+
}
|
|
2231
|
+
function buildDescription(lines, contextIdx, entityName) {
|
|
2232
|
+
const added = [];
|
|
2233
|
+
const removed = [];
|
|
2234
|
+
for (let i = contextIdx + 1; i < Math.min(contextIdx + 20, lines.length); i++) {
|
|
2235
|
+
const line = lines[i];
|
|
2236
|
+
if (line.startsWith("@@") || line.startsWith("diff"))
|
|
2237
|
+
break;
|
|
2238
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
2239
|
+
added.push(line.slice(1).trim());
|
|
2240
|
+
}
|
|
2241
|
+
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
2242
|
+
removed.push(line.slice(1).trim());
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
if (added.length === 0 && removed.length > 0) {
|
|
2246
|
+
return `${entityName}: ${removed.length} line(s) removed`;
|
|
2247
|
+
}
|
|
2248
|
+
if (added.length > 0 && removed.length === 0) {
|
|
2249
|
+
return `${entityName}: ${added.length} line(s) added`;
|
|
2250
|
+
}
|
|
2251
|
+
if (added.length > 0 && removed.length > 0) {
|
|
2252
|
+
return `${entityName}: ${removed.length} line(s) changed to ${added.length} new line(s)`;
|
|
2253
|
+
}
|
|
2254
|
+
return `${entityName}: modified`;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
});
|
|
2258
|
+
|
|
815
2259
|
// dist/main.js
|
|
816
2260
|
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
817
2261
|
return mod && mod.__esModule ? mod : { "default": mod };
|
|
@@ -819,6 +2263,13 @@ var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
|
819
2263
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
820
2264
|
require_javascript();
|
|
821
2265
|
require_python();
|
|
2266
|
+
require_go();
|
|
2267
|
+
require_csharp();
|
|
2268
|
+
require_java();
|
|
2269
|
+
require_kotlin();
|
|
2270
|
+
require_php();
|
|
2271
|
+
require_ruby();
|
|
2272
|
+
require_swift();
|
|
822
2273
|
var fs_1 = __importDefault(require("fs"));
|
|
823
2274
|
var collector_1 = require_collector();
|
|
824
2275
|
var parser_1 = require_parser();
|
|
@@ -826,6 +2277,7 @@ var graph_1 = require_graph();
|
|
|
826
2277
|
var metrics_1 = require_metrics();
|
|
827
2278
|
var impact_1 = require_impact();
|
|
828
2279
|
var output_1 = require_output();
|
|
2280
|
+
var gitdiff_1 = require_gitdiff();
|
|
829
2281
|
var args = process.argv.slice(2);
|
|
830
2282
|
var noColor = args.includes("--no-color");
|
|
831
2283
|
var verbose = args.includes("--verbose");
|
|
@@ -846,11 +2298,11 @@ function getFlag(flag) {
|
|
|
846
2298
|
}
|
|
847
2299
|
function printHelp() {
|
|
848
2300
|
console.log(`
|
|
849
|
-
${bold("DepGraph
|
|
2301
|
+
${bold("DepGraph")} ${dim("v1.5.0")}
|
|
850
2302
|
${dim("Dependency mapping \xB7 Impact simulation \xB7 Developer intelligence")}
|
|
851
2303
|
|
|
852
2304
|
${bold("USAGE")}
|
|
853
|
-
|
|
2305
|
+
depgraph ${cyan("<projectDir>")} ${dim("[options]")}
|
|
854
2306
|
|
|
855
2307
|
${bold("OPTIONS")}
|
|
856
2308
|
${cyan("--output")} ${dim("<file>")} Output path ${dim("(default: ./depgraph-output.json)")}
|
|
@@ -859,24 +2311,43 @@ ${bold("OPTIONS")}
|
|
|
859
2311
|
${cyan("--no-color")} Disable colors ${dim("(for CI)")}
|
|
860
2312
|
${cyan("--help, -h")} Show this help message
|
|
861
2313
|
|
|
2314
|
+
${bold("GIT FLAGS")}
|
|
2315
|
+
${cyan("--git-impact")} Auto-detect changes from git diff
|
|
2316
|
+
${cyan("--commit")} ${dim("<sha>")} Analyze a specific commit
|
|
2317
|
+
${cyan("--from")} ${dim("<branch>")} Compare from this branch
|
|
2318
|
+
${cyan("--to")} ${dim("<branch>")} Compare to this branch
|
|
2319
|
+
|
|
862
2320
|
${bold("EXAMPLES")}
|
|
863
2321
|
${dim("# Map a project")}
|
|
864
|
-
|
|
2322
|
+
depgraph ./my-app
|
|
865
2323
|
|
|
866
2324
|
${dim("# Map with custom output")}
|
|
867
|
-
|
|
2325
|
+
depgraph ./my-app --output ./reports/graph.json
|
|
868
2326
|
|
|
869
2327
|
${dim("# Simulate a change")}
|
|
870
|
-
|
|
2328
|
+
depgraph ./my-app --impact "getUserById" "removing userId param"
|
|
871
2329
|
|
|
872
2330
|
${dim("# CI mode")}
|
|
873
|
-
|
|
2331
|
+
depgraph ./src --no-color --output ./ci/depgraph.json
|
|
2332
|
+
|
|
2333
|
+
${bold("GIT EXAMPLES")}
|
|
2334
|
+
${dim("# Analyze uncommitted changes")}
|
|
2335
|
+
depgraph ./src --git-impact
|
|
2336
|
+
|
|
2337
|
+
${dim("# Analyze last commit")}
|
|
2338
|
+
depgraph ./src --git-impact --commit HEAD
|
|
2339
|
+
|
|
2340
|
+
${dim("# Compare two branches")}
|
|
2341
|
+
depgraph ./src --git-impact --from main --to feature/my-branch
|
|
2342
|
+
|
|
2343
|
+
${dim("# Specific commit")}
|
|
2344
|
+
depgraph ./src --git-impact --commit abc1234
|
|
874
2345
|
`);
|
|
875
2346
|
}
|
|
876
2347
|
function printBanner() {
|
|
877
2348
|
console.log(`
|
|
878
2349
|
${bold("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}
|
|
879
|
-
${bold(" DepGraph
|
|
2350
|
+
${bold(" DepGraph")} ${dim("v1.0.0")}
|
|
880
2351
|
${bold("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}
|
|
881
2352
|
`);
|
|
882
2353
|
}
|
|
@@ -936,6 +2407,11 @@ var projectDir = args[0];
|
|
|
936
2407
|
var outputPath = getFlag("--output") ?? "./depgraph-output.json";
|
|
937
2408
|
var impactTarget = getFlag("--impact");
|
|
938
2409
|
var impactDesc = impactTarget ? args[args.indexOf("--impact") + 2] ?? "no description provided" : void 0;
|
|
2410
|
+
var gitImpact = args.includes("--git-impact");
|
|
2411
|
+
var gitCommit = getFlag("--commit");
|
|
2412
|
+
var gitFrom = getFlag("--from");
|
|
2413
|
+
var gitTo = getFlag("--to");
|
|
2414
|
+
var gitMode = gitFrom && gitTo ? "branches" : gitCommit ? "last-commit" : "uncommitted";
|
|
939
2415
|
printBanner();
|
|
940
2416
|
console.log(`${bold("\u{1F50D} Scanning")} ${cyan(projectDir)}
|
|
941
2417
|
`);
|
|
@@ -959,6 +2435,34 @@ try {
|
|
|
959
2435
|
if (impactTarget) {
|
|
960
2436
|
impact = (0, impact_1.simulateImpact)(metrics, impactTarget, impactDesc ?? "");
|
|
961
2437
|
printImpact(impact);
|
|
2438
|
+
} else if (gitImpact) {
|
|
2439
|
+
console.log(`
|
|
2440
|
+
${bold("\u{1F50D} Reading git diff...")}`);
|
|
2441
|
+
const changed = (0, gitdiff_1.getChangedEntities)({
|
|
2442
|
+
projectDir,
|
|
2443
|
+
mode: gitMode,
|
|
2444
|
+
commit: gitCommit,
|
|
2445
|
+
from: gitFrom,
|
|
2446
|
+
to: gitTo
|
|
2447
|
+
});
|
|
2448
|
+
if (changed.length === 0) {
|
|
2449
|
+
console.log(`
|
|
2450
|
+
${green("\u2713")} No changed entities found in diff`);
|
|
2451
|
+
} else {
|
|
2452
|
+
console.log(`
|
|
2453
|
+
${bold(`Found ${changed.length} changed entity(s):`)}`);
|
|
2454
|
+
for (const entity of changed) {
|
|
2455
|
+
console.log(` ${dim("\u2192")} ${entity.name} ${dim(`(${entity.file})`)}`);
|
|
2456
|
+
}
|
|
2457
|
+
console.log(`
|
|
2458
|
+
${bold("Running impact simulation...")}`);
|
|
2459
|
+
for (const entity of changed) {
|
|
2460
|
+
console.log(`
|
|
2461
|
+
${dim("\u2500".repeat(42))}`);
|
|
2462
|
+
const result = (0, impact_1.simulateImpact)(metrics, entity.name, entity.description);
|
|
2463
|
+
printImpact(result);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
962
2466
|
}
|
|
963
2467
|
(0, output_1.writeOutput)(metrics, parsed, outputPath, impact);
|
|
964
2468
|
} catch (err) {
|