@jskit-ai/architecture-inspector 0.1.0 → 0.1.2

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.
@@ -0,0 +1,474 @@
1
+ import ts from "typescript";
2
+ import { resolveScopedApiBasePath } from "@jskit-ai/kernel/shared/surface";
3
+ import { isFunctionLike, unwrapExpression } from "./functions.js";
4
+
5
+ // Partial string evaluation for operation paths and identities. Unknown parts
6
+ // become SEGMENT (one path segment) or ANY (any length); results are bounded.
7
+ const SEGMENT = "\u0002";
8
+ const ANY = "\u0003";
9
+ const MAX_PATTERNS = 12;
10
+ const MAX_DEPTH = 8;
11
+ const MAX_UNROLLED_ELEMENTS = 64;
12
+ const VUE_VALUE_WRAPPERS = Object.freeze(["computed", "ref", "shallowRef", "unref", "toValue", "readonly"]);
13
+
14
+ function concat(left, right) {
15
+ const result = [];
16
+ for (const first of left) {
17
+ for (const second of right) {
18
+ result.push(first + second);
19
+ if (result.length >= MAX_PATTERNS) {
20
+ return result;
21
+ }
22
+ }
23
+ }
24
+ return result;
25
+ }
26
+
27
+ function union(...lists) {
28
+ return [...new Set(lists.flat())].slice(0, MAX_PATTERNS);
29
+ }
30
+
31
+ function isLiteralPattern(value) {
32
+ return typeof value === "string" && !value.includes(SEGMENT) && !value.includes(ANY);
33
+ }
34
+
35
+ function emptyEnvironment() {
36
+ return { indexes: new Map(), values: new Map() };
37
+ }
38
+
39
+ function normalizedSegment(value) {
40
+ return value.startsWith("/") || value.startsWith(ANY) || value === "" ? value : `/${value}`;
41
+ }
42
+
43
+ /**
44
+ * @param {object} dependencies
45
+ * @param {import("./types.js").CallSymbols} dependencies.symbols
46
+ * @param {import("./types.js").FileOf} dependencies.fileOf
47
+ */
48
+ function createEvaluator({ symbols, fileOf }) {
49
+ const unrolled = new Map();
50
+
51
+ function functionReturns(fn) {
52
+ if (!fn.body) {
53
+ return [];
54
+ }
55
+ if (!ts.isBlock(fn.body)) {
56
+ return [fn.body];
57
+ }
58
+ const results = [];
59
+ const visit = (node) => {
60
+ if (node !== fn && isFunctionLike(node)) {
61
+ return;
62
+ }
63
+ if (ts.isReturnStatement(node) && node.expression) {
64
+ results.push(node.expression);
65
+ }
66
+ ts.forEachChild(node, visit);
67
+ };
68
+ visit(fn.body);
69
+ return results;
70
+ }
71
+
72
+ function propertyInitializer(objectLiteral, name) {
73
+ const entry = objectLiteral.properties.find((property) =>
74
+ property.name && !ts.isComputedPropertyName(property.name) && property.name.text === name);
75
+ if (!entry) {
76
+ return undefined;
77
+ }
78
+ if (ts.isPropertyAssignment(entry)) {
79
+ return entry.initializer;
80
+ }
81
+ if (ts.isShorthandPropertyAssignment(entry)) {
82
+ return entry.name;
83
+ }
84
+ return entry;
85
+ }
86
+
87
+ // `Object.freeze(x)` and parentheses are transparent for constant data.
88
+ function constantExpression(node) {
89
+ const value = unwrapExpression(node);
90
+ if (value && ts.isCallExpression(value) && value.arguments.length === 1 &&
91
+ ts.isPropertyAccessExpression(value.expression) && value.expression.name.text === "freeze" &&
92
+ ts.isIdentifier(value.expression.expression) && value.expression.expression.text === "Object") {
93
+ return unwrapExpression(value.arguments[0]);
94
+ }
95
+ return value;
96
+ }
97
+
98
+ // The constant a const-bound identifier holds, or undefined.
99
+ function constantInitializer(identifier) {
100
+ const declaration = symbols.declarationOf(identifier);
101
+ if (!declaration || !ts.isVariableDeclaration(declaration) || !declaration.initializer || !fileOf(declaration)) {
102
+ return undefined;
103
+ }
104
+ const list = declaration.parent;
105
+ if (!ts.isVariableDeclarationList(list) || !(list.flags & ts.NodeFlags.Const)) {
106
+ return undefined;
107
+ }
108
+ return declaration.initializer;
109
+ }
110
+
111
+ // Literal JSON-like data, following const bindings: { value } or undefined.
112
+ function literalValue(node, depth = 0) {
113
+ const value = constantExpression(node);
114
+ if (!value || depth > MAX_DEPTH) {
115
+ return undefined;
116
+ }
117
+ if (ts.isStringLiteralLike(value)) {
118
+ return { value: value.text };
119
+ }
120
+ if (ts.isNumericLiteral(value)) {
121
+ return { value: Number(value.text) };
122
+ }
123
+ if (value.kind === ts.SyntaxKind.TrueKeyword || value.kind === ts.SyntaxKind.FalseKeyword) {
124
+ return { value: value.kind === ts.SyntaxKind.TrueKeyword };
125
+ }
126
+ if (value.kind === ts.SyntaxKind.NullKeyword) {
127
+ return { value: null };
128
+ }
129
+ if (ts.isTemplateExpression(value)) {
130
+ const text = evaluate(value);
131
+ return text.length === 1 && isLiteralPattern(text[0]) ? { value: text[0] } : undefined;
132
+ }
133
+ if (ts.isArrayLiteralExpression(value)) {
134
+ const items = [];
135
+ for (const element of value.elements) {
136
+ const item = ts.isSpreadElement(element) ? undefined : literalValue(element, depth + 1);
137
+ if (!item) {
138
+ return undefined;
139
+ }
140
+ items.push(item.value);
141
+ }
142
+ return { value: items };
143
+ }
144
+ if (ts.isObjectLiteralExpression(value)) {
145
+ const result = {};
146
+ for (const property of value.properties) {
147
+ if (!ts.isPropertyAssignment(property) || ts.isComputedPropertyName(property.name)) {
148
+ return undefined;
149
+ }
150
+ const item = literalValue(property.initializer, depth + 1);
151
+ if (!item) {
152
+ return undefined;
153
+ }
154
+ result[property.name.text] = item.value;
155
+ }
156
+ return { value: result };
157
+ }
158
+ if (ts.isIdentifier(value)) {
159
+ const initializer = constantInitializer(value);
160
+ return initializer ? literalValue(initializer, depth + 1) : undefined;
161
+ }
162
+ if (ts.isPropertyAccessExpression(value)) {
163
+ const base = literalValue(value.expression, depth + 1);
164
+ const item = base?.value && typeof base.value === "object" && !Array.isArray(base.value) ? base.value[value.name.text] : undefined;
165
+ return item === undefined ? undefined : { value: item };
166
+ }
167
+ return undefined;
168
+ }
169
+
170
+ // A literal array of at most 64 literal elements, or null.
171
+ function literalArray(node) {
172
+ const result = literalValue(node);
173
+ return Array.isArray(result?.value) && result.value.length <= MAX_UNROLLED_ELEMENTS ? result.value : null;
174
+ }
175
+
176
+ function bindingPath(declaration) {
177
+ const pathParts = [];
178
+ let current = declaration;
179
+ while (current && ts.isBindingElement(current)) {
180
+ const pattern = current.parent;
181
+ if (ts.isObjectBindingPattern(pattern)) {
182
+ const key = current.propertyName || current.name;
183
+ if (!ts.isIdentifier(key) && !ts.isStringLiteralLike(key)) {
184
+ return null;
185
+ }
186
+ pathParts.unshift(key.text);
187
+ } else {
188
+ pathParts.unshift(pattern.elements.indexOf(current));
189
+ }
190
+ current = pattern.parent;
191
+ }
192
+ return current ? { root: current, path: pathParts } : null;
193
+ }
194
+
195
+ // An identifier bound per element of a literal array by `.map(callback)` or
196
+ // `for (... of array)`: { scope, values } with one value per element.
197
+ function unrolledBinding(declaration) {
198
+ if (!declaration) {
199
+ return null;
200
+ }
201
+ if (unrolled.has(declaration)) {
202
+ return unrolled.get(declaration);
203
+ }
204
+ unrolled.set(declaration, null);
205
+ const located = bindingPath(declaration);
206
+ let scope = null;
207
+ let elements = null;
208
+ if (located && ts.isParameter(located.root)) {
209
+ const callback = located.root.parent;
210
+ const call = callback?.parent;
211
+ if (isFunctionLike(callback) && callback.parameters[0] === located.root && call && ts.isCallExpression(call) &&
212
+ call.arguments[0] === callback && ts.isPropertyAccessExpression(call.expression) && call.expression.name.text === "map") {
213
+ scope = callback;
214
+ elements = literalArray(call.expression.expression);
215
+ }
216
+ } else if (located && ts.isVariableDeclaration(located.root)) {
217
+ const statement = located.root.parent?.parent;
218
+ if (statement && ts.isForOfStatement(statement) && statement.initializer === located.root.parent) {
219
+ scope = statement;
220
+ elements = literalArray(statement.expression);
221
+ }
222
+ }
223
+ if (!scope || !elements) {
224
+ return null;
225
+ }
226
+ const values = elements.map((element) => located.path.reduce((value, key) => (value == null ? undefined : value[key]), element));
227
+ const binding = { scope, values };
228
+ unrolled.set(declaration, binding);
229
+ return binding;
230
+ }
231
+
232
+ function scalarPatterns(value) {
233
+ return typeof value === "string" || typeof value === "number" ? [String(value)] : [ANY];
234
+ }
235
+
236
+ function evaluateIdentifier(node, environment, depth) {
237
+ const declaration = symbols.declarationOf(node);
238
+ if (!declaration) {
239
+ return [ANY];
240
+ }
241
+ if (environment.values.has(declaration)) {
242
+ return environment.values.get(declaration);
243
+ }
244
+ const binding = unrolledBinding(declaration);
245
+ if (binding) {
246
+ if (environment.indexes.has(binding.scope)) {
247
+ return scalarPatterns(binding.values[environment.indexes.get(binding.scope)]);
248
+ }
249
+ return union(...binding.values.map(scalarPatterns));
250
+ }
251
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer && fileOf(declaration)) {
252
+ return evaluate(declaration.initializer, environment, depth + 1);
253
+ }
254
+ // A parameter default is the value when callers omit it.
255
+ if ((ts.isParameter(declaration) || ts.isBindingElement(declaration)) && declaration.initializer && fileOf(declaration)) {
256
+ return evaluate(declaration.initializer, environment, depth + 1);
257
+ }
258
+ return [ANY];
259
+ }
260
+
261
+ function evaluatePropertyAccess(node, environment, depth) {
262
+ if (node.name.text === "value") {
263
+ return evaluate(node.expression, environment, depth + 1);
264
+ }
265
+ const base = unwrapExpression(node.expression);
266
+ if (ts.isIdentifier(base)) {
267
+ const initializer = constantInitializer(base);
268
+ const object = initializer && constantExpression(initializer);
269
+ if (object && ts.isObjectLiteralExpression(object)) {
270
+ const value = propertyInitializer(object, node.name.text);
271
+ if (value && !isFunctionLike(value)) {
272
+ return evaluate(value, environment, depth + 1);
273
+ }
274
+ }
275
+ }
276
+ return [ANY];
277
+ }
278
+
279
+ function calleeName(callee) {
280
+ if (ts.isIdentifier(callee)) {
281
+ return callee.text;
282
+ }
283
+ return ts.isPropertyAccessExpression(callee) ? callee.name.text : "";
284
+ }
285
+
286
+ // Whether an expression is the result of JSKIT's `usePaths()`.
287
+ function isPathsReceiver(expression) {
288
+ let value = unwrapExpression(expression);
289
+ if (value && ts.isIdentifier(value)) {
290
+ const declaration = symbols.declarationOf(value);
291
+ value = declaration && ts.isVariableDeclaration(declaration) ? unwrapExpression(declaration.initializer) : null;
292
+ }
293
+ return Boolean(value) && ts.isCallExpression(value) && ts.isIdentifier(value.expression) &&
294
+ symbols.isImportOf(value.expression, "@jskit-ai/shell-web", ["usePaths"]);
295
+ }
296
+
297
+ function isPathsApi(callee) {
298
+ if (ts.isPropertyAccessExpression(callee) && callee.name.text === "api") {
299
+ return isPathsReceiver(callee.expression);
300
+ }
301
+ if (ts.isIdentifier(callee)) {
302
+ const declaration = symbols.declarationOf(callee);
303
+ const key = declaration && ts.isBindingElement(declaration) ? (declaration.propertyName || declaration.name) : null;
304
+ const owner = declaration?.parent?.parent;
305
+ return Boolean(key) && ts.isIdentifier(key) && key.text === "api" && owner && ts.isVariableDeclaration(owner) &&
306
+ isPathsReceiver(owner.initializer);
307
+ }
308
+ return false;
309
+ }
310
+
311
+ function objectArgumentPatterns(argument, name, environment, depth, fallback) {
312
+ const object = unwrapExpression(argument);
313
+ if (!object || !ts.isObjectLiteralExpression(object)) {
314
+ return argument ? [ANY] : [fallback];
315
+ }
316
+ const value = propertyInitializer(object, name);
317
+ return value ? evaluate(value, environment, depth + 1) : [fallback];
318
+ }
319
+
320
+ // Inlines a local project helper whose returns can be evaluated.
321
+ function inlineHelper(callee, args, environment, depth) {
322
+ const declaration = symbols.declarationOf(callee);
323
+ let fn = declaration;
324
+ if (fn && ts.isVariableDeclaration(fn)) {
325
+ fn = unwrapExpression(fn.initializer);
326
+ }
327
+ if (!fn || !isFunctionLike(fn) || !fileOf(fn)) {
328
+ return null;
329
+ }
330
+ const values = new Map(environment.values);
331
+ fn.parameters.forEach((parameter, index) => {
332
+ const argument = args[index];
333
+ if (ts.isIdentifier(parameter.name)) {
334
+ const value = argument ? evaluate(argument, environment, depth + 1)
335
+ : parameter.initializer ? evaluate(parameter.initializer, environment, depth + 1) : [""];
336
+ values.set(parameter, value);
337
+ } else if (ts.isObjectBindingPattern(parameter.name)) {
338
+ for (const element of parameter.name.elements) {
339
+ const key = element.propertyName || element.name;
340
+ if (ts.isIdentifier(key) && ts.isIdentifier(element.name)) {
341
+ const fallback = element.initializer ? evaluate(element.initializer, environment, depth + 1) : [""];
342
+ const object = unwrapExpression(argument);
343
+ const property = object && ts.isObjectLiteralExpression(object) ? propertyInitializer(object, key.text) : undefined;
344
+ values.set(element, property ? evaluate(property, environment, depth + 1) : argument && !object?.properties ? [ANY] : fallback);
345
+ }
346
+ }
347
+ }
348
+ });
349
+ const returns = functionReturns(fn);
350
+ if (!returns.length) {
351
+ return null;
352
+ }
353
+ const inner = { indexes: environment.indexes, values };
354
+ return union(...returns.map((expression) => evaluate(expression, inner, depth + 1)));
355
+ }
356
+
357
+ function evaluateCall(node, environment, depth) {
358
+ const callee = unwrapExpression(node.expression);
359
+ const name = calleeName(callee);
360
+ const args = node.arguments;
361
+ if (ts.isIdentifier(callee) && VUE_VALUE_WRAPPERS.includes(name) && symbols.isImportOf(callee, "vue", VUE_VALUE_WRAPPERS)) {
362
+ const value = unwrapExpression(args[0]);
363
+ if (value && isFunctionLike(value)) {
364
+ return union(...functionReturns(value).map((expression) => evaluate(expression, environment, depth + 1)));
365
+ }
366
+ return value ? evaluate(value, environment, depth + 1) : [ANY];
367
+ }
368
+ const stringDeclaration = name === "String" && ts.isIdentifier(callee) ? symbols.declarationOf(callee) : null;
369
+ if (ts.isIdentifier(callee) && name === "String" && !(stringDeclaration && fileOf(stringDeclaration))) {
370
+ return args[0] ? evaluate(args[0], environment, depth + 1) : [""];
371
+ }
372
+ if (ts.isPropertyAccessExpression(callee) && ["trim", "toString"].includes(name)) {
373
+ return evaluate(callee.expression, environment, depth + 1);
374
+ }
375
+ if (ts.isPropertyAccessExpression(callee) && name === "toLowerCase") {
376
+ return evaluate(callee.expression, environment, depth + 1).map((value) => value.toLowerCase());
377
+ }
378
+ if (/^encode/iu.test(name)) {
379
+ const value = args[0] ? evaluate(args[0], environment, depth + 1) : [SEGMENT];
380
+ return value.map((entry) => (isLiteralPattern(entry) ? entry : SEGMENT));
381
+ }
382
+ if (["replace", "replaceAll"].includes(name) && ts.isPropertyAccessExpression(callee) &&
383
+ args[0] && ts.isRegularExpressionLiteral(args[0]) && args[1] && ts.isStringLiteralLike(args[1])) {
384
+ const text = args[0].text;
385
+ const expression = new RegExp(text.slice(1, text.lastIndexOf("/")), text.slice(text.lastIndexOf("/") + 1));
386
+ return evaluate(callee.expression, environment, depth + 1).map((entry) => entry.replace(expression, args[1].text));
387
+ }
388
+ if (isPathsApi(callee)) {
389
+ // usePaths().api(suffix): the API base path and suffix under the
390
+ // surface's root route base.
391
+ const suffix = args[0] ? evaluate(args[0], environment, depth + 1) : [ANY];
392
+ return suffix.map((relativePath) => resolveScopedApiBasePath({ relativePath: normalizedSegment(relativePath), strictParams: false }));
393
+ }
394
+ if (ts.isIdentifier(callee) && symbols.isImportOf(callee, "@jskit-ai/kernel", ["resolveScopedApiBasePath"])) {
395
+ const routeBases = objectArgumentPatterns(args[0], "routeBase", environment, depth, "/");
396
+ const relativePaths = objectArgumentPatterns(args[0], "relativePath", environment, depth, "/");
397
+ const result = [];
398
+ for (const routeBase of routeBases) {
399
+ for (const relativePath of relativePaths) {
400
+ result.push(resolveScopedApiBasePath({ routeBase, relativePath, strictParams: false }));
401
+ }
402
+ }
403
+ return union(result);
404
+ }
405
+ if (ts.isIdentifier(callee)) {
406
+ return inlineHelper(callee, args, environment, depth) || [ANY];
407
+ }
408
+ return [ANY];
409
+ }
410
+
411
+ function evaluate(node, environment = emptyEnvironment(), depth = 0) {
412
+ const value = unwrapExpression(node);
413
+ if (!value || depth > MAX_DEPTH) {
414
+ return [ANY];
415
+ }
416
+ if (ts.isStringLiteralLike(value)) {
417
+ return [value.text];
418
+ }
419
+ if (ts.isNumericLiteral(value)) {
420
+ return [String(Number(value.text))];
421
+ }
422
+ if (ts.isTemplateExpression(value)) {
423
+ let result = [value.head.text];
424
+ for (const span of value.templateSpans) {
425
+ result = concat(concat(result, evaluate(span.expression, environment, depth + 1)), [span.literal.text]);
426
+ }
427
+ return result;
428
+ }
429
+ if (ts.isBinaryExpression(value)) {
430
+ const operator = value.operatorToken.kind;
431
+ if (operator === ts.SyntaxKind.PlusToken) {
432
+ return concat(evaluate(value.left, environment, depth + 1), evaluate(value.right, environment, depth + 1));
433
+ }
434
+ if (operator === ts.SyntaxKind.BarBarToken || operator === ts.SyntaxKind.QuestionQuestionToken) {
435
+ return union(evaluate(value.left, environment, depth + 1), evaluate(value.right, environment, depth + 1));
436
+ }
437
+ return [ANY];
438
+ }
439
+ if (ts.isConditionalExpression(value)) {
440
+ return union(evaluate(value.whenTrue, environment, depth + 1), evaluate(value.whenFalse, environment, depth + 1));
441
+ }
442
+ if (ts.isAwaitExpression(value)) {
443
+ return evaluate(value.expression, environment, depth + 1);
444
+ }
445
+ if (ts.isIdentifier(value)) {
446
+ return evaluateIdentifier(value, environment, depth);
447
+ }
448
+ if (ts.isPropertyAccessExpression(value)) {
449
+ return evaluatePropertyAccess(value, environment, depth);
450
+ }
451
+ if (ts.isCallExpression(value)) {
452
+ return evaluateCall(value, environment, depth);
453
+ }
454
+ return [ANY];
455
+ }
456
+
457
+ // One literal string, or null when any part is dynamic or alternatives exist.
458
+ function evaluateLiteral(node, environment) {
459
+ const values = evaluate(node, environment);
460
+ return values.length === 1 && isLiteralPattern(values[0]) ? values[0] : null;
461
+ }
462
+
463
+ return {
464
+ evaluate,
465
+ evaluateLiteral,
466
+ literalValue,
467
+ literalArray,
468
+ unrolledBinding,
469
+ functionReturns,
470
+ propertyInitializer
471
+ };
472
+ }
473
+
474
+ export { ANY, SEGMENT, createEvaluator, isLiteralPattern };
@@ -0,0 +1,123 @@
1
+ import ts from "typescript";
2
+ import { isFunctionLike, unwrapExpression } from "./functions.js";
3
+
4
+ const MAX_DEPTH = 6;
5
+ // Node globals TypeScript's default libraries do not declare. Calls to them
6
+ // are provably external.
7
+ const NODE_GLOBALS = new Set(["process", "Buffer", "require", "module", "exports", "__dirname", "__filename", "global", "setImmediate", "clearImmediate"]);
8
+ // Emit functions and model refs Vue's `<script setup>` macros return are
9
+ // Vue's own values.
10
+ const VUE_EXTERNAL_VALUES = new Set(["defineEmits", "defineModel"]);
11
+
12
+ function isComponentFile(file) {
13
+ return Boolean(file) && file.endsWith(".vue");
14
+ }
15
+
16
+ function isNullish(expression) {
17
+ const value = unwrapExpression(expression);
18
+ return value.kind === ts.SyntaxKind.NullKeyword || (ts.isIdentifier(value) && value.text === "undefined");
19
+ }
20
+
21
+ /**
22
+ * Whether values are provably produced by a package or builtin, so calls on
23
+ * them leave the project.
24
+ * @param {object} dependencies
25
+ * @param {import("typescript").TypeChecker} dependencies.checker
26
+ * @param {import("./types.js").CallSymbols} dependencies.symbols
27
+ * @param {import("./types.js").FileOf} dependencies.fileOf
28
+ */
29
+ function createExternalValues({ checker, symbols, fileOf }) {
30
+ // Whether a value is provably produced by a package or builtin.
31
+ function isExternalValue(expression, depth = 0) {
32
+ const root = symbols.receiverRoot(expression);
33
+ if (!root || depth > MAX_DEPTH) {
34
+ return false;
35
+ }
36
+ if (ts.isIdentifier(root)) {
37
+ if (symbols.isPackageImport(root)) {
38
+ return true;
39
+ }
40
+ const symbol = symbols.symbolAt(root);
41
+ if (!symbol) {
42
+ return NODE_GLOBALS.has(root.text) || (isComponentFile(fileOf(root)) && VUE_EXTERNAL_VALUES.has(root.text));
43
+ }
44
+ if (symbols.isLibrarySymbol(symbol)) {
45
+ return true;
46
+ }
47
+ const declaration = symbols.declarationOf(root);
48
+ return Boolean(declaration) && isExternalDeclaration(declaration, depth + 1);
49
+ }
50
+ return ts.isStringLiteralLike(root) || ts.isArrayLiteralExpression(root) || ts.isRegularExpressionLiteral(root) ||
51
+ ts.isTemplateExpression(root) || ts.isNumericLiteral(root);
52
+ }
53
+
54
+ // Whether every value a declared name can hold comes from a package or
55
+ // builtin: its initializer and later assignments, the iterable it is taken
56
+ // from, the value it is destructured from, or the external call a callback
57
+ // parameter is supplied by.
58
+ function isExternalDeclaration(declaration, depth) {
59
+ let current = declaration;
60
+ while (current && ts.isBindingElement(current)) {
61
+ current = current.parent.parent;
62
+ }
63
+ if (!current || depth > MAX_DEPTH) {
64
+ return false;
65
+ }
66
+ if (ts.isParameter(current)) {
67
+ const fn = current.parent;
68
+ const call = fn.parent;
69
+ return (ts.isCallExpression(call) || ts.isNewExpression(call)) && Boolean(call.arguments?.includes(fn)) &&
70
+ isExternalCallee(call.expression, depth + 1);
71
+ }
72
+ if (!ts.isVariableDeclaration(current)) {
73
+ return false;
74
+ }
75
+ const statement = current.parent?.parent;
76
+ if (statement && ts.isForOfStatement(statement) && statement.initializer === current.parent) {
77
+ return isExternalValue(statement.expression, depth + 1);
78
+ }
79
+ const values = [current.initializer, ...assignedValues(current)].filter((value) => value && !isNullish(value));
80
+ return values.length > 0 && values.every((value) => isExternalValue(value, depth + 1));
81
+ }
82
+
83
+ function isExternalCallee(expression, depth) {
84
+ const callee = unwrapExpression(expression);
85
+ if (ts.isIdentifier(callee)) {
86
+ const symbol = symbols.symbolAt(callee);
87
+ return symbol ? symbols.isPackageImport(callee) || symbols.isLibrarySymbol(symbol) : NODE_GLOBALS.has(callee.text);
88
+ }
89
+ return ts.isPropertyAccessExpression(callee) && isExternalValue(callee.expression, depth);
90
+ }
91
+
92
+ // The values later assigned to a `let` or `var` name within its scope.
93
+ const assignments = new Map();
94
+ function assignedValues(declaration) {
95
+ if (!ts.isIdentifier(declaration.name) || declaration.parent.flags & ts.NodeFlags.Const) {
96
+ return [];
97
+ }
98
+ if (!assignments.has(declaration)) {
99
+ const symbol = checker.getSymbolAtLocation(declaration.name);
100
+ let scope = declaration.parent;
101
+ while (scope && !isFunctionLike(scope) && !ts.isSourceFile(scope)) {
102
+ scope = scope.parent;
103
+ }
104
+ const values = [];
105
+ const visit = (node) => {
106
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
107
+ ts.isIdentifier(node.left) && node.left.text === declaration.name.text && checker.getSymbolAtLocation(node.left) === symbol) {
108
+ values.push(node.right);
109
+ }
110
+ ts.forEachChild(node, visit);
111
+ };
112
+ if (scope) {
113
+ visit(scope);
114
+ }
115
+ assignments.set(declaration, values);
116
+ }
117
+ return assignments.get(declaration);
118
+ }
119
+
120
+ return Object.freeze({ isExternalValue, isExternalDeclaration });
121
+ }
122
+
123
+ export { NODE_GLOBALS, createExternalValues, isComponentFile };