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.
- package/README.md +52 -22
- package/bin/runner.js +26 -0
- package/package.json +10 -37
- package/dist/ast-utils.d.ts +0 -149
- package/dist/ast-utils.js +0 -36
- package/dist/ast-utils.js.map +0 -1
- package/dist/cache.d.ts +0 -14
- package/dist/cache.js +0 -36
- package/dist/cache.js.map +0 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -68
- package/dist/cli.js.map +0 -1
- package/dist/config-loader.d.ts +0 -4
- package/dist/config-loader.js +0 -84
- package/dist/config-loader.js.map +0 -1
- package/dist/coverage-tracker.d.ts +0 -39
- package/dist/coverage-tracker.js +0 -61
- package/dist/coverage-tracker.js.map +0 -1
- package/dist/engine.d.ts +0 -17
- package/dist/engine.js +0 -215
- package/dist/engine.js.map +0 -1
- package/dist/framework-plugins.d.ts +0 -16
- package/dist/framework-plugins.js +0 -127
- package/dist/framework-plugins.js.map +0 -1
- package/dist/fs-utils.d.ts +0 -32
- package/dist/fs-utils.js +0 -415
- package/dist/fs-utils.js.map +0 -1
- package/dist/graph.d.ts +0 -42
- package/dist/graph.js +0 -583
- package/dist/graph.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -451
- package/dist/index.js.map +0 -1
- package/dist/instrument.d.ts +0 -5
- package/dist/instrument.js +0 -108
- package/dist/instrument.js.map +0 -1
- package/dist/layer2.d.ts +0 -6
- package/dist/layer2.js +0 -224
- package/dist/layer2.js.map +0 -1
- package/dist/layer3.d.ts +0 -7
- package/dist/layer3.js +0 -346
- package/dist/layer3.js.map +0 -1
- package/dist/layer4.d.ts +0 -8
- package/dist/layer4.js +0 -627
- package/dist/layer4.js.map +0 -1
- package/dist/layer5.d.ts +0 -6
- package/dist/layer5.js +0 -141
- package/dist/layer5.js.map +0 -1
- package/dist/layer6.d.ts +0 -26
- package/dist/layer6.js +0 -268
- package/dist/layer6.js.map +0 -1
- package/dist/layer7.d.ts +0 -28
- package/dist/layer7.js +0 -267
- package/dist/layer7.js.map +0 -1
- package/dist/parser.d.ts +0 -20
- package/dist/parser.js +0 -832
- package/dist/parser.js.map +0 -1
- package/dist/plugins/angular-plugin.d.ts +0 -7
- package/dist/plugins/angular-plugin.js +0 -60
- package/dist/plugins/angular-plugin.js.map +0 -1
- package/dist/plugins/astro-plugin.d.ts +0 -7
- package/dist/plugins/astro-plugin.js +0 -88
- package/dist/plugins/astro-plugin.js.map +0 -1
- package/dist/plugins/babel-plugin.d.ts +0 -8
- package/dist/plugins/babel-plugin.js +0 -132
- package/dist/plugins/babel-plugin.js.map +0 -1
- package/dist/plugins/esbuild-plugin.d.ts +0 -8
- package/dist/plugins/esbuild-plugin.js +0 -121
- package/dist/plugins/esbuild-plugin.js.map +0 -1
- package/dist/plugins/nestjs-plugin.d.ts +0 -7
- package/dist/plugins/nestjs-plugin.js +0 -69
- package/dist/plugins/nestjs-plugin.js.map +0 -1
- package/dist/plugins/nextjs-plugin.d.ts +0 -7
- package/dist/plugins/nextjs-plugin.js +0 -66
- package/dist/plugins/nextjs-plugin.js.map +0 -1
- package/dist/plugins/nuxtjs-plugin.d.ts +0 -7
- package/dist/plugins/nuxtjs-plugin.js +0 -80
- package/dist/plugins/nuxtjs-plugin.js.map +0 -1
- package/dist/plugins/svelte-plugin.d.ts +0 -7
- package/dist/plugins/svelte-plugin.js +0 -38
- package/dist/plugins/svelte-plugin.js.map +0 -1
- package/dist/plugins/vuejs-plugin.d.ts +0 -7
- package/dist/plugins/vuejs-plugin.js +0 -63
- package/dist/plugins/vuejs-plugin.js.map +0 -1
- package/dist/reporters.d.ts +0 -3
- package/dist/reporters.js +0 -75
- package/dist/reporters.js.map +0 -1
- package/dist/semantic-graph.d.ts +0 -44
- package/dist/semantic-graph.js +0 -88
- package/dist/semantic-graph.js.map +0 -1
- package/dist/symbolic-engine.d.ts +0 -19
- package/dist/symbolic-engine.js +0 -51
- package/dist/symbolic-engine.js.map +0 -1
- package/dist/topology-manager.d.ts +0 -23
- package/dist/topology-manager.js +0 -144
- package/dist/topology-manager.js.map +0 -1
- package/dist/types.d.ts +0 -265
- package/dist/types.js +0 -11
- package/dist/types.js.map +0 -1
- package/dist/workspace.d.ts +0 -6
- package/dist/workspace.js +0 -105
- 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
|