optiprune 2.1.4 → 2.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +52 -22
  2. package/bin/runner.js +26 -0
  3. package/package.json +10 -37
  4. package/dist/ast-utils.d.ts +0 -149
  5. package/dist/ast-utils.js +0 -36
  6. package/dist/ast-utils.js.map +0 -1
  7. package/dist/cache.d.ts +0 -14
  8. package/dist/cache.js +0 -36
  9. package/dist/cache.js.map +0 -1
  10. package/dist/cli.d.ts +0 -2
  11. package/dist/cli.js +0 -68
  12. package/dist/cli.js.map +0 -1
  13. package/dist/config-loader.d.ts +0 -4
  14. package/dist/config-loader.js +0 -84
  15. package/dist/config-loader.js.map +0 -1
  16. package/dist/coverage-tracker.d.ts +0 -39
  17. package/dist/coverage-tracker.js +0 -61
  18. package/dist/coverage-tracker.js.map +0 -1
  19. package/dist/engine.d.ts +0 -17
  20. package/dist/engine.js +0 -215
  21. package/dist/engine.js.map +0 -1
  22. package/dist/framework-plugins.d.ts +0 -16
  23. package/dist/framework-plugins.js +0 -127
  24. package/dist/framework-plugins.js.map +0 -1
  25. package/dist/fs-utils.d.ts +0 -32
  26. package/dist/fs-utils.js +0 -415
  27. package/dist/fs-utils.js.map +0 -1
  28. package/dist/graph.d.ts +0 -42
  29. package/dist/graph.js +0 -583
  30. package/dist/graph.js.map +0 -1
  31. package/dist/index.d.ts +0 -3
  32. package/dist/index.js +0 -451
  33. package/dist/index.js.map +0 -1
  34. package/dist/instrument.d.ts +0 -5
  35. package/dist/instrument.js +0 -108
  36. package/dist/instrument.js.map +0 -1
  37. package/dist/layer2.d.ts +0 -6
  38. package/dist/layer2.js +0 -224
  39. package/dist/layer2.js.map +0 -1
  40. package/dist/layer3.d.ts +0 -7
  41. package/dist/layer3.js +0 -346
  42. package/dist/layer3.js.map +0 -1
  43. package/dist/layer4.d.ts +0 -8
  44. package/dist/layer4.js +0 -627
  45. package/dist/layer4.js.map +0 -1
  46. package/dist/layer5.d.ts +0 -6
  47. package/dist/layer5.js +0 -141
  48. package/dist/layer5.js.map +0 -1
  49. package/dist/layer6.d.ts +0 -26
  50. package/dist/layer6.js +0 -268
  51. package/dist/layer6.js.map +0 -1
  52. package/dist/layer7.d.ts +0 -28
  53. package/dist/layer7.js +0 -267
  54. package/dist/layer7.js.map +0 -1
  55. package/dist/parser.d.ts +0 -20
  56. package/dist/parser.js +0 -832
  57. package/dist/parser.js.map +0 -1
  58. package/dist/plugins/angular-plugin.d.ts +0 -7
  59. package/dist/plugins/angular-plugin.js +0 -60
  60. package/dist/plugins/angular-plugin.js.map +0 -1
  61. package/dist/plugins/astro-plugin.d.ts +0 -7
  62. package/dist/plugins/astro-plugin.js +0 -88
  63. package/dist/plugins/astro-plugin.js.map +0 -1
  64. package/dist/plugins/babel-plugin.d.ts +0 -8
  65. package/dist/plugins/babel-plugin.js +0 -132
  66. package/dist/plugins/babel-plugin.js.map +0 -1
  67. package/dist/plugins/esbuild-plugin.d.ts +0 -8
  68. package/dist/plugins/esbuild-plugin.js +0 -121
  69. package/dist/plugins/esbuild-plugin.js.map +0 -1
  70. package/dist/plugins/nestjs-plugin.d.ts +0 -7
  71. package/dist/plugins/nestjs-plugin.js +0 -69
  72. package/dist/plugins/nestjs-plugin.js.map +0 -1
  73. package/dist/plugins/nextjs-plugin.d.ts +0 -7
  74. package/dist/plugins/nextjs-plugin.js +0 -66
  75. package/dist/plugins/nextjs-plugin.js.map +0 -1
  76. package/dist/plugins/nuxtjs-plugin.d.ts +0 -7
  77. package/dist/plugins/nuxtjs-plugin.js +0 -80
  78. package/dist/plugins/nuxtjs-plugin.js.map +0 -1
  79. package/dist/plugins/svelte-plugin.d.ts +0 -7
  80. package/dist/plugins/svelte-plugin.js +0 -38
  81. package/dist/plugins/svelte-plugin.js.map +0 -1
  82. package/dist/plugins/vuejs-plugin.d.ts +0 -7
  83. package/dist/plugins/vuejs-plugin.js +0 -63
  84. package/dist/plugins/vuejs-plugin.js.map +0 -1
  85. package/dist/reporters.d.ts +0 -3
  86. package/dist/reporters.js +0 -75
  87. package/dist/reporters.js.map +0 -1
  88. package/dist/semantic-graph.d.ts +0 -44
  89. package/dist/semantic-graph.js +0 -88
  90. package/dist/semantic-graph.js.map +0 -1
  91. package/dist/symbolic-engine.d.ts +0 -19
  92. package/dist/symbolic-engine.js +0 -51
  93. package/dist/symbolic-engine.js.map +0 -1
  94. package/dist/topology-manager.d.ts +0 -23
  95. package/dist/topology-manager.js +0 -144
  96. package/dist/topology-manager.js.map +0 -1
  97. package/dist/types.d.ts +0 -265
  98. package/dist/types.js +0 -11
  99. package/dist/types.js.map +0 -1
  100. package/dist/workspace.d.ts +0 -6
  101. package/dist/workspace.js +0 -105
  102. package/dist/workspace.js.map +0 -1
package/dist/parser.js DELETED
@@ -1,832 +0,0 @@
1
- import { parse as yukuParse, langFromPath, sourceTypeFromPath } from "yuku-parser";
2
- function isNode(value) {
3
- return value !== null && typeof value === "object" && typeof value.type === "string";
4
- }
5
- function asArray(value) {
6
- return Array.isArray(value) ? value : [];
7
- }
8
- // yuku-parser nodes carry byte offsets (start/end) but no loc object.
9
- // We compute line/column from the source text when needed.
10
- let _yukuSource = "";
11
- function setYukuSource(src) { _yukuSource = src; }
12
- function offsetToPosition(offset) {
13
- const before = _yukuSource.slice(0, Math.max(0, offset));
14
- const line = before.split("\n").length;
15
- const lastNewline = before.lastIndexOf("\n");
16
- const column = lastNewline === -1 ? before.length : before.length - lastNewline - 1;
17
- return { line, column };
18
- }
19
- function positionRange(node) {
20
- if (!node)
21
- return undefined;
22
- // Prefer pre-existing loc (e.g. from Babel-compatible AST shims)
23
- if (node.loc?.start && node.loc?.end) {
24
- const s = node.loc.start;
25
- const e = node.loc.end;
26
- return {
27
- start: { line: s.line, column: s.column },
28
- end: { line: e.line, column: e.column },
29
- };
30
- }
31
- // Fall back to computing from yuku-parser byte offsets
32
- if (typeof node.start === "number" && typeof node.end === "number") {
33
- return {
34
- start: offsetToPosition(node.start),
35
- end: offsetToPosition(node.end),
36
- };
37
- }
38
- return undefined;
39
- }
40
- function locationAtOffset(source, offset) {
41
- const before = source.slice(0, Math.max(0, offset));
42
- const line = before.split("\n").length;
43
- const lastNewline = before.lastIndexOf("\n");
44
- const column = lastNewline === -1 ? before.length : before.length - lastNewline - 1;
45
- return {
46
- start: { line, column },
47
- end: { line, column: column + 1 },
48
- };
49
- }
50
- function nodeStringValue(node) {
51
- if (!isNode(node)) {
52
- return undefined;
53
- }
54
- if ((node.type === "StringLiteral" || node.type === "Literal") && typeof node.value === "string") {
55
- return node.value;
56
- }
57
- return undefined;
58
- }
59
- function nodeIdentifierName(node) {
60
- if (!isNode(node)) {
61
- return undefined;
62
- }
63
- if (node.type === "Identifier" && typeof node.name === "string") {
64
- return node.name;
65
- }
66
- return undefined;
67
- }
68
- function propertyKeyName(node) {
69
- if (!isNode(node)) {
70
- return undefined;
71
- }
72
- if (node.type === "Identifier" && typeof node.name === "string") {
73
- return node.name;
74
- }
75
- if ((node.type === "StringLiteral" || node.type === "NumericLiteral" || node.type === "Literal") && node.value !== undefined) {
76
- return String(node.value);
77
- }
78
- return undefined;
79
- }
80
- function bindingNames(node) {
81
- if (!isNode(node)) {
82
- return [];
83
- }
84
- if (node.type === "Identifier") {
85
- return typeof node.name === "string" ? [node.name] : [];
86
- }
87
- if (node.type === "ObjectPattern") {
88
- return asArray(node.properties).flatMap((property) => {
89
- if (!isNode(property)) {
90
- return [];
91
- }
92
- if (property.type === "RestElement") {
93
- return bindingNames(property.argument);
94
- }
95
- return bindingNames(property.value);
96
- });
97
- }
98
- if (node.type === "ArrayPattern") {
99
- return asArray(node.elements).flatMap((element) => bindingNames(element));
100
- }
101
- if (node.type === "RestElement" || node.type === "AssignmentPattern") {
102
- return bindingNames(node.left ?? node.argument);
103
- }
104
- return [];
105
- }
106
- function addExport(exportsList, exportedAs, node, identifierNode, options = {}) {
107
- const candidate = {
108
- name: options.name ?? exportedAs,
109
- exportedAs,
110
- isDefault: options.isDefault ?? false,
111
- isReExport: options.isReExport ?? false,
112
- isWildcard: options.isWildcard ?? false,
113
- isTypeOnly: options.isTypeOnly ?? false,
114
- };
115
- if (options.members)
116
- candidate.members = options.members;
117
- // Use precise identifier node location if provided, otherwise default to full node
118
- const location = positionRange(identifierNode) ?? positionRange(node);
119
- if (location) {
120
- candidate.location = location;
121
- }
122
- if (!exportsList.some((item) => item.exportedAs === candidate.exportedAs && item.name === candidate.name)) {
123
- exportsList.push(candidate);
124
- }
125
- }
126
- function addEdge(edges, sourceFile, rawSpecifier, kind, node, importedNames = [], isTypeOnly = false, dynamicExpression) {
127
- const edge = {
128
- source: sourceFile,
129
- rawSpecifier,
130
- kind,
131
- importedNames,
132
- resolution: "unknown",
133
- isTypeOnly,
134
- dynamicExpression,
135
- };
136
- const location = positionRange(node);
137
- if (location) {
138
- edge.location = location;
139
- }
140
- edges.push(edge);
141
- }
142
- function importSpecifierNames(specifiers) {
143
- const names = [];
144
- for (const specifier of specifiers) {
145
- if (!isNode(specifier)) {
146
- continue;
147
- }
148
- if (specifier.type === "ImportDefaultSpecifier") {
149
- names.push("default");
150
- }
151
- else if (specifier.type === "ImportNamespaceSpecifier") {
152
- names.push("*");
153
- }
154
- else if (specifier.type === "ImportSpecifier") {
155
- names.push(propertyKeyName(specifier.imported) ?? "*");
156
- }
157
- }
158
- return names;
159
- }
160
- function exportSpecifierNames(specifiers, useLocal = false) {
161
- const names = [];
162
- for (const specifier of specifiers) {
163
- if (isNode(specifier)) {
164
- const nameNode = useLocal ? (specifier.local || specifier.exported) : specifier.exported;
165
- names.push(propertyKeyName(nameNode) ?? "*");
166
- }
167
- }
168
- return names;
169
- }
170
- function isRequireCall(node) {
171
- return node.type === "CallExpression" && nodeIdentifierName(node.callee) === "require";
172
- }
173
- function isDynamicImportCall(node) {
174
- return (node.type === "ImportExpression" ||
175
- (node.type === "CallExpression" && isNode(node.callee) && node.callee.type === "Import"));
176
- }
177
- function dynamicArgument(node) {
178
- if (node.type === "ImportExpression") {
179
- return node.source;
180
- }
181
- return asArray(node.arguments)[0];
182
- }
183
- function templateParts(node) {
184
- if (!isNode(node) || node.type !== "TemplateLiteral") {
185
- return undefined;
186
- }
187
- const expressions = asArray(node.expressions);
188
- const quasis = asArray(node.quasis);
189
- if (expressions.length === 0 || quasis.length < 2) {
190
- return undefined;
191
- }
192
- const first = quasis[0];
193
- const last = quasis[quasis.length - 1];
194
- const firstValue = isNode(first) && isNode(first.value) && typeof first.value.cooked === "string" ? first.value.cooked : "";
195
- const lastValue = isNode(last) && isNode(last.value) && typeof last.value.cooked === "string" ? last.value.cooked : "";
196
- return { prefix: firstValue, suffix: lastValue };
197
- }
198
- function walk(node, visitor, stack = []) {
199
- if (Array.isArray(node)) {
200
- for (const item of node) {
201
- walk(item, visitor, stack);
202
- }
203
- return;
204
- }
205
- if (!isNode(node)) {
206
- return;
207
- }
208
- const currentStack = [...stack, node];
209
- visitor(node, stack);
210
- // If this is a File node, we only want to walk into the program
211
- if (node.type === "File" && node.program) {
212
- walk(node.program, visitor, currentStack);
213
- return;
214
- }
215
- for (const [key, value] of Object.entries(node)) {
216
- if (key === "loc" ||
217
- key === "start" ||
218
- key === "end" ||
219
- key === "tokens" ||
220
- key === "comments" ||
221
- key === "errors" ||
222
- key === "extra") {
223
- continue;
224
- }
225
- if (Array.isArray(value) || isNode(value)) {
226
- walk(value, visitor, currentStack);
227
- }
228
- }
229
- }
230
- function extractAstModule(sourceText, file, ast, parserErrors) {
231
- const exportsList = [];
232
- const edges = [];
233
- const parseDiagnostics = parserErrors.map((error) => {
234
- const candidate = error;
235
- const diagnostic = {
236
- message: typeof candidate.message === "string" ? candidate.message : "Recoverable parser error",
237
- file,
238
- recovered: true,
239
- };
240
- if (typeof candidate.loc?.line === "number" && typeof candidate.loc?.column === "number") {
241
- diagnostic.location = {
242
- start: { line: candidate.loc.line, column: candidate.loc.column },
243
- end: { line: candidate.loc.line, column: candidate.loc.column + 1 },
244
- };
245
- }
246
- return diagnostic;
247
- });
248
- let hasUnknownDynamicBoundary = false;
249
- let hasUnresolvedCommonJsExports = false;
250
- const scannedDirectories = [];
251
- const dynamicImportCandidates = [];
252
- const localSymbolDeps = new Map();
253
- const localTypeMap = {};
254
- const localReferences = new Set();
255
- const getActiveDeclaration = (s) => {
256
- for (let i = s.length - 1; i >= 0; i--) {
257
- const n = s[i];
258
- if (!n)
259
- continue;
260
- if (n.type === "FunctionDeclaration" || n.type === "ClassDeclaration" || n.type === "TSInterfaceDeclaration" || n.type === "TSTypeAliasDeclaration" || n.type === "TSEnumDeclaration") {
261
- return nodeIdentifierName(n.id);
262
- }
263
- if (n.type === "VariableDeclarator") {
264
- const names = bindingNames(n.id);
265
- return names[0];
266
- }
267
- }
268
- return undefined;
269
- };
270
- walk(ast, (node, stack) => {
271
- // Compatibility: Map Yuku attached comments to leadingComments/trailingComments
272
- const yukuNode = node;
273
- // Track local variable types from annotations
274
- if (node.type === "VariableDeclarator") {
275
- const id = yukuNode.id;
276
- if (id && id.type === "Identifier") {
277
- const ta = id.typeAnnotation;
278
- if (ta && ta.type === "TSTypeAnnotation" && ta.typeAnnotation?.type === "TSTypeReference") {
279
- const typeName = nodeIdentifierName(ta.typeAnnotation.typeName);
280
- if (typeName) {
281
- localTypeMap[id.name] = typeName;
282
- }
283
- }
284
- }
285
- }
286
- if (Array.isArray(yukuNode.comments)) {
287
- yukuNode.leadingComments = yukuNode.comments
288
- .filter((c) => c.position === "before")
289
- .map((c) => ({
290
- type: c.type === "Line" ? "CommentLine" : "CommentBlock",
291
- value: c.value,
292
- }));
293
- yukuNode.trailingComments = yukuNode.comments
294
- .filter((c) => c.position === "after")
295
- .map((c) => ({
296
- type: c.type === "Line" ? "CommentLine" : "CommentBlock",
297
- value: c.value,
298
- }));
299
- }
300
- // Fix 3: Track local references
301
- if (node.type === "Identifier") {
302
- const parent = stack[stack.length - 1];
303
- if (parent) {
304
- let isRef = true;
305
- // Definitions/Names of exports are NOT references to other symbols
306
- if (parent.type === "ExportSpecifier" && (parent.local === node || parent.exported === node))
307
- isRef = false;
308
- if (parent.type === "ExportDefaultDeclaration" && parent.declaration === node)
309
- isRef = false;
310
- if (parent.type === "ImportSpecifier" || parent.type === "ImportDefaultSpecifier" || parent.type === "ImportNamespaceSpecifier")
311
- isRef = false;
312
- // Property keys are not references to top-level symbols
313
- if (parent.type === "MemberExpression" && parent.property === node && !parent.computed)
314
- isRef = false;
315
- if (parent.type === "ObjectProperty" && parent.key === node && !parent.computed)
316
- isRef = false;
317
- if (parent.type === "TSPropertySignature" && parent.key === node)
318
- isRef = false;
319
- if (parent.type === "TSMethodSignature" && parent.key === node)
320
- isRef = false;
321
- // Definitions are NOT references to the symbol itself
322
- if ((parent.type === "FunctionDeclaration" || parent.type === "ClassDeclaration" || parent.type === "TSInterfaceDeclaration" || parent.type === "TSTypeAliasDeclaration" || parent.type === "TSEnumDeclaration" || parent.type === "VariableDeclarator") && parent.id === node)
323
- isRef = false;
324
- if (isRef) {
325
- const active = getActiveDeclaration(stack);
326
- if (active) {
327
- if (active !== node.name) {
328
- if (!localSymbolDeps.has(active))
329
- localSymbolDeps.set(active, new Set());
330
- localSymbolDeps.get(active).add(node.name);
331
- }
332
- }
333
- else {
334
- // Top-level usage (code not inside a function/class)
335
- // Use empty string as the key for top-level references
336
- if (!localSymbolDeps.has(""))
337
- localSymbolDeps.set("", new Set());
338
- localSymbolDeps.get("").add(node.name);
339
- }
340
- localReferences.add(node.name);
341
- }
342
- }
343
- }
344
- if (node.type === "ImportDeclaration") {
345
- const specifier = nodeStringValue(node.source);
346
- if (specifier) {
347
- const isTypeOnly = node.importKind === "type";
348
- addEdge(edges, file, specifier, "import", node, importSpecifierNames(asArray(node.specifiers)), isTypeOnly);
349
- }
350
- return;
351
- }
352
- if (node.type === "ExportNamedDeclaration") {
353
- const specifier = nodeStringValue(node.source);
354
- if (specifier) {
355
- const isTypeOnly = node.exportKind === "type";
356
- const specifiers = asArray(node.specifiers);
357
- const localNames = [];
358
- for (const spec of specifiers) {
359
- if (isNode(spec) && spec.type === "ExportSpecifier") {
360
- const localName = propertyKeyName(spec.local || spec.exported) ?? "*";
361
- const exportedName = propertyKeyName(spec.exported) ?? "*";
362
- localNames.push(localName);
363
- addExport(exportsList, exportedName, node, spec.exported, {
364
- name: localName,
365
- isReExport: true,
366
- isTypeOnly: isTypeOnly
367
- });
368
- }
369
- }
370
- addEdge(edges, file, specifier, "export-from", node, localNames, isTypeOnly);
371
- }
372
- else if (isNode(node.declaration)) {
373
- const declaration = node.declaration;
374
- if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration" || declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration" || declaration.type === "TSEnumDeclaration") && nodeIdentifierName(declaration.id)) {
375
- // TSInterfaceDeclaration and TSTypeAliasDeclaration are pure type constructs erased
376
- // at compile time.
377
- // TSEnumDeclaration: 'const enum' is erased/inlined, but regular 'enum' emits a
378
- // runtime IIFE object. We only mark it as type-only if it's a 'const enum'.
379
- const isType = declaration.type === "TSInterfaceDeclaration" ||
380
- declaration.type === "TSTypeAliasDeclaration" ||
381
- (declaration.type === "TSEnumDeclaration" && declaration.const === true) ||
382
- node.exportKind === "type";
383
- // Member Extraction for Enums, Interfaces, and Classes
384
- const members = [];
385
- const decl = declaration;
386
- if (decl.type === "TSEnumDeclaration") {
387
- const enumMembers = decl.members || decl.body?.members || [];
388
- for (const member of asArray(enumMembers)) {
389
- const m = member;
390
- const name = nodeIdentifierName(m.id) || nodeStringValue(m.id);
391
- if (name)
392
- members.push({ name, location: positionRange(m) });
393
- }
394
- }
395
- else if (decl.type === "TSInterfaceDeclaration") {
396
- const body = decl.body?.body || [];
397
- for (const member of asArray(body)) {
398
- const m = member;
399
- const name = nodeIdentifierName(m.key) || nodeStringValue(m.key);
400
- if (name)
401
- members.push({ name, location: positionRange(m) });
402
- }
403
- }
404
- else if (decl.type === "ClassDeclaration") {
405
- const body = decl.body?.body || [];
406
- for (const member of asArray(body)) {
407
- const m = member;
408
- if (m.type === "MethodDefinition" || m.type === "PropertyDefinition" || m.type === "ClassProperty" || m.type === "ClassMethod") {
409
- const name = nodeIdentifierName(m.key) || nodeStringValue(m.key);
410
- if (name && name !== "constructor")
411
- members.push({ name, location: positionRange(m) });
412
- }
413
- }
414
- }
415
- addExport(exportsList, nodeIdentifierName(decl.id) ?? "unknown", node, decl.id, {
416
- isTypeOnly: isType,
417
- members: members.length > 0 ? members : undefined
418
- });
419
- }
420
- else if (declaration.type === "VariableDeclaration") {
421
- for (const declarator of asArray(declaration.declarations)) {
422
- if (isNode(declarator)) {
423
- for (const name of bindingNames(declarator.id)) {
424
- addExport(exportsList, name, node, declarator.id, { isTypeOnly: node.exportKind === "type" });
425
- }
426
- }
427
- }
428
- }
429
- }
430
- else {
431
- for (const exportedName of exportSpecifierNames(asArray(node.specifiers))) {
432
- addExport(exportsList, exportedName, node, undefined, { isTypeOnly: node.exportKind === "type" });
433
- }
434
- }
435
- return;
436
- }
437
- if (node.type === "ExportDefaultDeclaration") {
438
- addExport(exportsList, "default", node, undefined, { name: "default", isDefault: true, isTypeOnly: node.exportKind === "type" });
439
- return;
440
- }
441
- if (node.type === "ExportAllDeclaration") {
442
- const specifier = nodeStringValue(node.source);
443
- if (specifier) {
444
- addEdge(edges, file, specifier, "export-all", node, ["*"], node.exportKind === "type");
445
- addExport(exportsList, "*", node, undefined, { name: "*", isReExport: true, isWildcard: true, isTypeOnly: node.exportKind === "type" });
446
- }
447
- return;
448
- }
449
- if (isRequireCall(node)) {
450
- const specifier = nodeStringValue(asArray(node.arguments)[0]);
451
- if (specifier) {
452
- addEdge(edges, file, specifier, "require", node, ["*"]);
453
- }
454
- else {
455
- hasUnknownDynamicBoundary = true;
456
- }
457
- return;
458
- }
459
- if (isDynamicImportCall(node)) {
460
- const argument = dynamicArgument(node);
461
- let literal = nodeStringValue(argument);
462
- // Handle pathToFileURL(path.join(...)).href
463
- if (!literal && isNode(argument) && argument.type === "MemberExpression" && nodeIdentifierName(argument.property) === "href") {
464
- const obj = argument.object;
465
- if (isNode(obj) && obj.type === "CallExpression" && nodeIdentifierName(obj.callee) === "pathToFileURL") {
466
- literal = nodeStringValue(asArray(obj.arguments)[0]);
467
- // Even if it's still not a literal, templateParts might handle the inner call
468
- }
469
- }
470
- if (literal) {
471
- addEdge(edges, file, literal, "dynamic-literal", node, ["*"]);
472
- }
473
- else {
474
- const parts = templateParts(argument) || (isNode(argument) && argument.type === "CallExpression" && nodeIdentifierName(argument.callee) === "pathToFileURL" ? templateParts(asArray(argument.arguments)[0]) : undefined);
475
- if (parts) {
476
- const edge = {
477
- source: file,
478
- rawSpecifier: `${parts.prefix}${"${…}"}${parts.suffix}`,
479
- kind: "dynamic-pattern",
480
- importedNames: ["*"],
481
- resolution: "unknown",
482
- dynamicPattern: {
483
- prefix: parts.prefix,
484
- suffix: parts.suffix,
485
- baseDirectory: "", // Would need proper calculation
486
- candidates: []
487
- }
488
- };
489
- const location = positionRange(node);
490
- if (location)
491
- edge.location = location;
492
- edges.push(edge);
493
- }
494
- // Even if it's a pattern, we add it to dynamicImportCandidates so Layer 4
495
- // can try to resolve it more precisely if possible.
496
- {
497
- hasUnknownDynamicBoundary = true;
498
- const expressionText = sourceText.slice(node.start, node.end);
499
- const location = positionRange(node);
500
- if (location) {
501
- // Find the containing function or block to capture local variables
502
- let contextCode = "";
503
- const scopeNode = [...stack].reverse().find(n => n.type === "FunctionDeclaration" ||
504
- n.type === "FunctionExpression" ||
505
- n.type === "ArrowFunctionExpression" ||
506
- n.type === "ClassMethod" ||
507
- n.type === "ObjectMethod");
508
- // IMPROVEMENT: Capture both the top-level definitions and the specific
509
- // function body to ensure variables from outer scopes are available
510
- // AND the import logic is actually executed.
511
- let topLevelCode = "";
512
- const program = stack[0];
513
- if (program && program.type === "File" && program.program) {
514
- const topNodes = asArray(program.program.body);
515
- // Extract top-level declarations (const, let, var, function)
516
- topLevelCode = topNodes
517
- .filter((n) => n.type === "VariableDeclaration" ||
518
- n.type === "FunctionDeclaration" ||
519
- n.type === "ExportNamedDeclaration" ||
520
- n.type === "ExportDefaultDeclaration")
521
- .map((n) => sourceText.slice(n.start, n.end))
522
- .join("\n");
523
- }
524
- let bodyCode = "";
525
- if (scopeNode && isNode(scopeNode.body)) {
526
- const body = scopeNode.body;
527
- const start = body.start;
528
- if (typeof start === "number" && typeof node.start === "number") {
529
- const bStart = (body.type === "BlockStatement" ? start + 1 : start);
530
- const bEnd = body.end - (body.type === "BlockStatement" ? 1 : 0);
531
- bodyCode = sourceText.slice(bStart, bEnd);
532
- // Prepend parameters
533
- const params = scopeNode.params;
534
- if (Array.isArray(params)) {
535
- const paramNames = params.flatMap(p => bindingNames(p));
536
- if (paramNames.length > 0) {
537
- bodyCode = `var ${paramNames.join(', ')};\n${bodyCode}`;
538
- }
539
- }
540
- }
541
- }
542
- contextCode = `${topLevelCode}\n\n// --- Function Body ---\n${bodyCode || expressionText}`;
543
- dynamicImportCandidates.push({
544
- file,
545
- line: location.start.line,
546
- column: location.start.column,
547
- expression: expressionText,
548
- contextCode: contextCode,
549
- });
550
- }
551
- if (!parts) {
552
- addEdge(edges, file, "<unknown dynamic import>", "unknown-dynamic", node, ["*"], false, expressionText);
553
- }
554
- }
555
- }
556
- return;
557
- }
558
- // Detect readdir / readdirSync / fs.promises.readdir with smarter path resolution
559
- if (node.type === "CallExpression") {
560
- const callee = node.callee;
561
- // Match: readdir(...), readdirSync(...), fs.readdir(...), fs.readdirSync(...),
562
- // fs.promises.readdir(...), promises.readdir(...)
563
- const isReaddir = (callee.type === "Identifier" && (callee.name === "readdir" || callee.name === "readdirSync")) ||
564
- (callee.type === "MemberExpression" && (callee.property?.name === "readdir" || callee.property?.name === "readdirSync")) ||
565
- // fs.promises.readdir
566
- (callee.type === "MemberExpression" &&
567
- isNode(callee.object) &&
568
- callee.object.type === "MemberExpression" &&
569
- (callee.property?.name === "readdir" || callee.property?.name === "readdirSync"));
570
- if (isReaddir) {
571
- const arg = asArray(node.arguments)[0];
572
- let dir = nodeStringValue(arg);
573
- // Handle path.join(__dirname, 'plugins') or path.resolve(..., 'dir') or similar
574
- if (!dir && arg?.type === "CallExpression") {
575
- const methodName = arg.callee?.property?.name ?? arg.callee?.name;
576
- if (methodName === "join" || methodName === "resolve") {
577
- const joinArgs = asArray(arg.arguments);
578
- // Collect all string literal segments (skip __dirname / __filename / variables)
579
- const stringParts = joinArgs
580
- .map((a) => nodeStringValue(a))
581
- .filter((s) => s !== undefined && !s.startsWith("/"));
582
- if (stringParts.length > 0) {
583
- // Use the relative sub-path formed by the string literals
584
- dir = stringParts.join("/");
585
- }
586
- }
587
- }
588
- if (dir) {
589
- scannedDirectories.push(dir);
590
- }
591
- else {
592
- // If we can't resolve the directory but it's a variable,
593
- // we mark it as a potential dynamic scan boundary.
594
- hasUnknownDynamicBoundary = true;
595
- }
596
- }
597
- }
598
- if (node.type === "AssignmentExpression" && isNode(node.left) && node.left.type === "MemberExpression") {
599
- const objectName = nodeIdentifierName(node.left.object);
600
- const property = node.left.computed ? propertyKeyName(node.left.property) : nodeIdentifierName(node.left.property);
601
- if (objectName === "exports" && property) {
602
- addExport(exportsList, property, node);
603
- }
604
- else if (objectName === "exports") {
605
- hasUnresolvedCommonJsExports = true;
606
- }
607
- else if (objectName === "module" && property === "exports") {
608
- hasUnresolvedCommonJsExports = true;
609
- }
610
- else if (isNode(node.left.object) && node.left.object.type === "MemberExpression") {
611
- const moduleName = nodeIdentifierName(node.left.object.object);
612
- const moduleProperty = nodeIdentifierName(node.left.object.property);
613
- if (moduleName === "module" && moduleProperty === "exports" && property) {
614
- addExport(exportsList, property, node);
615
- }
616
- }
617
- else if (node.left.name === "exports") {
618
- hasUnresolvedCommonJsExports = true;
619
- }
620
- }
621
- });
622
- // Attach local references to exports
623
- for (const exp of exportsList) {
624
- const deps = localSymbolDeps.get(exp.name);
625
- if (deps) {
626
- exp.localReferences = Array.from(deps);
627
- }
628
- }
629
- const localSymbolMap = {};
630
- for (const [name, deps] of localSymbolDeps.entries()) {
631
- localSymbolMap[name] = Array.from(deps);
632
- }
633
- const module = {
634
- id: file,
635
- relativePath: file,
636
- parseStatus: parserErrors.length > 0 ? "recovered" : "parsed",
637
- parseDiagnostics,
638
- ast,
639
- sourceText,
640
- exports: exportsList,
641
- edges,
642
- hasUnknownDynamicBoundary,
643
- hasParseError: parserErrors.length > 0,
644
- hasUnresolvedCommonJsExports,
645
- scannedDirectories,
646
- dynamicImportCandidates,
647
- localSymbolMap,
648
- localTypeMap,
649
- localReferences: Array.from(localReferences),
650
- };
651
- return module;
652
- }
653
- function fallbackExports(sourceText, file) {
654
- const found = [];
655
- const regex = /\bexport\s+(?:async\s+)?(?:function|class|const|let|var)\s+([A-Za-z_$][\w$]*)|\bexport\s+default\b|\bexport\s*\{([^}]+)\}/g;
656
- for (const match of sourceText.matchAll(regex)) {
657
- const location = locationAtOffset(sourceText, match.index ?? 0);
658
- if (match[1]) {
659
- found.push({ name: match[1], exportedAs: match[1], isDefault: false, isReExport: false, isWildcard: false, location });
660
- }
661
- else if (match[0].includes("export default")) {
662
- found.push({ name: "default", exportedAs: "default", isDefault: true, isReExport: false, isWildcard: false, location });
663
- }
664
- else if (match[2]) {
665
- for (const part of match[2].split(",")) {
666
- const exportedAs = part.trim().split(/\s+as\s+/i).at(-1)?.trim();
667
- if (exportedAs) {
668
- found.push({ name: exportedAs, exportedAs, isDefault: exportedAs === "default", isReExport: false, isWildcard: false, location });
669
- }
670
- }
671
- }
672
- }
673
- return found;
674
- }
675
- function fallbackEdges(sourceText, file) {
676
- const edges = [];
677
- // 1. Detailed Import Pattern: import { a, b as c } from '...'
678
- const detailedImportRegex = /\bimport\s+(?:\{([^}]+)\}|([a-zA-Z_$][\w$]*)(?:\s*,\s*\{([^}]+)\})?|\*\s+as\s+([a-zA-Z_$][\w$]*))\s+from\s+["']([^"'\n]+)["']/g;
679
- for (const match of sourceText.matchAll(detailedImportRegex)) {
680
- const namedImports = match[1] || match[3];
681
- const defaultImport = match[2];
682
- const namespaceImport = match[4];
683
- const specifier = match[5];
684
- if (specifier) {
685
- const names = [];
686
- if (namespaceImport)
687
- names.push("*");
688
- if (defaultImport)
689
- names.push("default");
690
- if (namedImports) {
691
- namedImports.split(",").forEach(n => {
692
- const parts = n.trim().split(/\s+as\s+/i);
693
- const imported = parts[0]?.trim();
694
- if (imported)
695
- names.push(imported);
696
- });
697
- }
698
- edges.push({
699
- source: file,
700
- rawSpecifier: specifier,
701
- kind: "import",
702
- importedNames: names.length > 0 ? names : ["*"],
703
- resolution: "unknown",
704
- location: locationAtOffset(sourceText, match.index ?? 0),
705
- });
706
- }
707
- }
708
- // 2. Simple/Other Patterns
709
- const patterns = [
710
- { regex: /\bimport\s+["']([^"'\n]+)["']/g, kind: "import" }, // side-effect import
711
- { regex: /\bexport\s+(?:\*|\{[^}]*\})\s+from\s+["']([^"'\n]+)["']/g, kind: "export-from" },
712
- { regex: /\brequire\s*\(\s*["']([^"'\n]+)["']\s*\)/g, kind: "require" },
713
- { regex: /\bimport\s*\(\s*["']([^"'\n]+)["']\s*\)/g, kind: "dynamic-literal" },
714
- ];
715
- for (const { regex, kind } of patterns) {
716
- for (const match of sourceText.matchAll(regex)) {
717
- const specifier = match[1];
718
- if (specifier && !edges.some(e => e.rawSpecifier === specifier && e.location?.start.line === locationAtOffset(sourceText, match.index ?? 0).start.line)) {
719
- edges.push({
720
- source: file,
721
- rawSpecifier: specifier,
722
- kind,
723
- importedNames: ["*"],
724
- resolution: "unknown",
725
- location: locationAtOffset(sourceText, match.index ?? 0),
726
- });
727
- }
728
- }
729
- }
730
- for (const match of sourceText.matchAll(/\bimport\s*\(\s*`([^`]*)`\s*\)/g)) {
731
- const raw = match[1] ?? "";
732
- const expressionIndex = raw.indexOf("${");
733
- const closeIndex = raw.lastIndexOf("}");
734
- if (expressionIndex >= 0 && closeIndex > expressionIndex) {
735
- edges.push({
736
- source: file,
737
- rawSpecifier: `${raw.slice(0, expressionIndex)}${"${…}"}${raw.slice(closeIndex + 1)}`,
738
- kind: "dynamic-pattern",
739
- importedNames: ["*"],
740
- resolution: "unknown",
741
- location: locationAtOffset(sourceText, match.index ?? 0),
742
- });
743
- }
744
- }
745
- return edges;
746
- }
747
- function fallbackModule(sourceText, file, reason) {
748
- const originalMessage = reason instanceof Error ? reason.message : "Parser could not recover this file";
749
- return {
750
- id: file,
751
- relativePath: file,
752
- parseStatus: "fallback",
753
- parseDiagnostics: [
754
- {
755
- message: `${originalMessage} (Module parse failed, using regex fallback)`,
756
- file,
757
- recovered: false,
758
- },
759
- ],
760
- sourceText,
761
- exports: fallbackExports(sourceText, file),
762
- edges: fallbackEdges(sourceText, file),
763
- hasUnknownDynamicBoundary: false, // Parse error is not necessarily a dynamic boundary
764
- hasParseError: true,
765
- hasUnresolvedCommonJsExports: false,
766
- scannedDirectories: [],
767
- dynamicImportCandidates: [],
768
- };
769
- }
770
- export function parseModule(sourceText, file) {
771
- try {
772
- // RESILIENCE FIX: Handle literal \n sequences often found in generated tests or copy-pastes
773
- // This ensures the parser doesn't choke on "Invalid Unicode escape sequence"
774
- if (sourceText.includes('\\n')) {
775
- sourceText = sourceText.replace(/\\n/g, '\n');
776
- }
777
- // Use yuku-parser instead of @babel/parser.
778
- // yuku-parser infers the best language variant from the file extension;
779
- // fall back to tsx (covers JS/TS/JSX/TSX) when the extension is unknown.
780
- const lang = langFromPath(file) ?? "tsx";
781
- const sourceType = sourceTypeFromPath(file) ?? "module";
782
- setYukuSource(sourceText);
783
- const result = yukuParse(sourceText, { lang, sourceType, semanticErrors: false, attachComments: true });
784
- // Map yuku-parser diagnostics to the shape extractAstModule expects
785
- const parserErrors = result.diagnostics
786
- .filter((d) => d.severity === "error")
787
- .map((d) => ({
788
- message: d.message,
789
- loc: d.start != null ? (() => {
790
- const pos = offsetToPosition(d.start);
791
- return { line: pos.line, column: pos.column };
792
- })() : undefined,
793
- }));
794
- // Yuku returns errors in diagnostics. If there are errors, trigger fallback for tests that expect "Parse failed"
795
- if (result.diagnostics?.some(d => d.severity === 'error')) {
796
- const firstError = result.diagnostics[0]?.message ?? "Unknown parse error";
797
- return fallbackModule(sourceText, file, new Error("Parse failed: " + firstError));
798
- }
799
- // Wrap in a "File" node to match what Layer 5/7 might expect from Babel
800
- const ast = {
801
- type: "File",
802
- program: result.program,
803
- comments: result.comments
804
- };
805
- return extractAstModule(sourceText, file, ast, []);
806
- }
807
- catch (error) {
808
- return fallbackModule(sourceText, file, error);
809
- }
810
- }
811
- export function getNodeRange(node) {
812
- return isNode(node) ? positionRange(node) : undefined;
813
- }
814
- export function getNodeType(node) {
815
- return isNode(node) ? node.type : undefined;
816
- }
817
- export function getNodeProperty(node, property) {
818
- return isNode(node) ? node[property] : undefined;
819
- }
820
- export function getStringLiteral(node) {
821
- return nodeStringValue(node);
822
- }
823
- export function getIdentifier(node) {
824
- return nodeIdentifierName(node);
825
- }
826
- export function isAstNode(node) {
827
- return isNode(node);
828
- }
829
- export function walkAst(node, visitor) {
830
- walk(node, visitor);
831
- }
832
- //# sourceMappingURL=parser.js.map