@jskit-ai/architecture-inspector 0.1.1 → 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.
- package/README.md +46 -13
- package/package.json +2 -2
- package/src/calls/cache.js +3 -2
- package/src/calls/evaluate.js +5 -0
- package/src/calls/external.js +123 -0
- package/src/calls/features.js +118 -20
- package/src/calls/functions.js +74 -1
- package/src/calls/index.js +52 -9
- package/src/calls/resolve.js +122 -183
- package/src/calls/symbols.js +23 -0
- package/src/calls/types.js +9 -0
- package/src/inspectVueArchitecture.js +6 -1309
- package/src/previewLocator.js +2 -2
- package/src/vue/modules.js +293 -0
- package/src/vue/pages.js +551 -0
- package/src/vue/routes.js +589 -0
- package/src/vue/syntax.js +222 -0
package/src/calls/resolve.js
CHANGED
|
@@ -2,19 +2,15 @@ import { EventEmitter } from "node:events";
|
|
|
2
2
|
import { Stats } from "node:fs";
|
|
3
3
|
import { Duplex } from "node:stream";
|
|
4
4
|
import ts from "typescript";
|
|
5
|
-
import { isFunctionLike, unwrapExpression } from "./functions.js";
|
|
5
|
+
import { isCalledInPlace, isFunctionLike, owningParameter, parameterNames, runsDuring, unwrapExpression } from "./functions.js";
|
|
6
6
|
import { isClassLike } from "./symbols.js";
|
|
7
7
|
import { isLiteralPattern } from "./evaluate.js";
|
|
8
|
+
import { NODE_GLOBALS, createExternalValues, isComponentFile } from "./external.js";
|
|
8
9
|
|
|
9
10
|
const MAX_TARGETS = 16;
|
|
10
11
|
const MAX_TYPE_DEPTH = 6;
|
|
11
|
-
//
|
|
12
|
-
// are provably external.
|
|
13
|
-
const NODE_GLOBALS = new Set(["process", "Buffer", "require", "module", "exports", "__dirname", "__filename", "global", "setImmediate", "clearImmediate"]);
|
|
14
|
-
// Vue's `<script setup>` compiler macros are globals of component scripts;
|
|
15
|
-
// emit functions and model refs they return are Vue's own values.
|
|
12
|
+
// Vue's `<script setup>` compiler macros are globals of component scripts.
|
|
16
13
|
const VUE_MACROS = new Set(["defineProps", "defineEmits", "defineExpose", "defineOptions", "defineSlots", "defineModel", "withDefaults"]);
|
|
17
|
-
const VUE_EXTERNAL_VALUES = new Set(["defineEmits", "defineModel"]);
|
|
18
14
|
// Node objects untyped receivers most often hold; TypeScript's libraries do
|
|
19
15
|
// not declare their members, so the names are read from Node itself.
|
|
20
16
|
const NODE_MEMBER_SOURCES = Object.freeze([EventEmitter.prototype, Stats.prototype, Duplex.prototype]);
|
|
@@ -41,15 +37,6 @@ function codeSide(file) {
|
|
|
41
37
|
return "shared";
|
|
42
38
|
}
|
|
43
39
|
|
|
44
|
-
function isComponentFile(file) {
|
|
45
|
-
return Boolean(file) && file.endsWith(".vue");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function isNullish(expression) {
|
|
49
|
-
const value = unwrapExpression(expression);
|
|
50
|
-
return value.kind === ts.SyntaxKind.NullKeyword || (ts.isIdentifier(value) && value.text === "undefined");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
40
|
function compatibleSides(left, right) {
|
|
54
41
|
return left === "shared" || right === "shared" || left === right;
|
|
55
42
|
}
|
|
@@ -78,47 +65,6 @@ function libraryMemberNames(program) {
|
|
|
78
65
|
return names;
|
|
79
66
|
}
|
|
80
67
|
|
|
81
|
-
// A function that runs while the code around it runs: one passed straight to
|
|
82
|
-
// a call, or called in place.
|
|
83
|
-
function isCalledInPlace(fn) {
|
|
84
|
-
let child = fn;
|
|
85
|
-
let parent = fn.parent;
|
|
86
|
-
while (parent && ts.isParenthesizedExpression(parent)) {
|
|
87
|
-
child = parent;
|
|
88
|
-
parent = parent.parent;
|
|
89
|
-
}
|
|
90
|
-
return Boolean(parent) && (ts.isCallExpression(parent) || ts.isNewExpression(parent)) &&
|
|
91
|
-
(parent.expression === child || Boolean(parent.arguments?.includes(child)));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Whether code at `node` runs while `fn` runs: no function between them is
|
|
95
|
-
// stored, returned or declared to run later.
|
|
96
|
-
function runsDuring(node, fn) {
|
|
97
|
-
for (let current = node.parent; current; current = current.parent) {
|
|
98
|
-
if (current === fn) {
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
if (isFunctionLike(current) && !isCalledInPlace(current)) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// The names a function's parameters bind, destructured ones included.
|
|
109
|
-
function parameterNames(fn) {
|
|
110
|
-
const names = new Set();
|
|
111
|
-
const visit = (name) => {
|
|
112
|
-
if (ts.isIdentifier(name)) {
|
|
113
|
-
names.add(name.text);
|
|
114
|
-
} else if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
115
|
-
name.elements.forEach((element) => ts.isBindingElement(element) && visit(element.name));
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
fn.parameters.forEach((parameter) => visit(parameter.name));
|
|
119
|
-
return names;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
68
|
function constructorOf(implementation) {
|
|
123
69
|
if (!isClassLike(implementation)) {
|
|
124
70
|
return implementation;
|
|
@@ -126,16 +72,50 @@ function constructorOf(implementation) {
|
|
|
126
72
|
return implementation.members.find((member) => ts.isConstructorDeclaration(member) && member.body) || null;
|
|
127
73
|
}
|
|
128
74
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Call resolution over one program: TypeScript checker targets, defineFeature
|
|
77
|
+
* injection, argument flow, literal-array unrolling and receiver names, each
|
|
78
|
+
* labelled with its certainty. Provably external calls are dropped.
|
|
79
|
+
* @param {object} dependencies
|
|
80
|
+
* @param {import("typescript").Program} dependencies.program
|
|
81
|
+
* @param {import("typescript").TypeChecker} dependencies.checker
|
|
82
|
+
* @param {import("./types.js").CallSymbols} dependencies.symbols
|
|
83
|
+
* @param {import("./types.js").CallEvaluator} dependencies.evaluator
|
|
84
|
+
* @param {import("./types.js").FeatureBindings["bindings"]} dependencies.bindings
|
|
85
|
+
* @param {Map<string, import("./types.js").FeatureBindings["actions"]>} dependencies.actionsById
|
|
86
|
+
* @param {import("./types.js").FileOf} dependencies.fileOf
|
|
87
|
+
* @param {Map<string, import("typescript").Node[]>} dependencies.functionNames
|
|
88
|
+
*/
|
|
132
89
|
function createResolver({ program, checker, symbols, evaluator, bindings, actionsById, fileOf, functionNames }) {
|
|
133
90
|
const builtinMembers = libraryMemberNames(program);
|
|
91
|
+
const { isExternalValue, isExternalDeclaration } = createExternalValues({ checker, symbols, fileOf });
|
|
92
|
+
/** @type {Map<import("typescript").Node, import("typescript").CallExpression[]>} */
|
|
134
93
|
const callSites = new Map();
|
|
135
94
|
const direct = new Map();
|
|
95
|
+
// The functions each indexed call reaches, and the calls whose index entry
|
|
96
|
+
// came from a guess (several values or an ambiguous injection).
|
|
97
|
+
/** @type {Map<import("typescript").CallExpression, import("typescript").Node[]>} */
|
|
98
|
+
const indexedTargets = new Map();
|
|
99
|
+
/** @type {Set<import("typescript").CallExpression>} */
|
|
100
|
+
const probableSites = new Set();
|
|
136
101
|
|
|
137
102
|
function implementationsFor(symbol) {
|
|
138
|
-
|
|
103
|
+
const found = symbols.symbolImplementations(symbol).map(constructorOf).filter(Boolean);
|
|
104
|
+
return found.length ? found : declaredMemberImplementations(symbol);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// A member a type declares without a value (`{ run: typeof run }` or
|
|
108
|
+
// `@property {typeof run} run`) holds the function or class its type names.
|
|
109
|
+
function declaredMemberImplementations(symbol) {
|
|
110
|
+
const declarations = symbol?.declarations || [];
|
|
111
|
+
if (!declarations.length || !declarations.every((declaration) => ts.isPropertySignature(declaration) || ts.isJSDocPropertyTag(declaration))) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
const type = checker.getTypeOfSymbol(symbol);
|
|
115
|
+
if (!type.getCallSignatures().length && !type.getConstructSignatures().length) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
return symbols.symbolImplementations(type.getSymbol()).map(constructorOf).filter(Boolean);
|
|
139
119
|
}
|
|
140
120
|
|
|
141
121
|
// Pass 1: what the checker resolves on its own, and the call-site index
|
|
@@ -179,14 +159,26 @@ function createResolver({ program, checker, symbols, evaluator, bindings, action
|
|
|
179
159
|
return { kind: "other" };
|
|
180
160
|
}
|
|
181
161
|
|
|
182
|
-
// Records that a call reaches these functions, for argument flow.
|
|
183
|
-
|
|
162
|
+
// Records that a call reaches these functions, for argument flow. A
|
|
163
|
+
// probable site's values are never exact.
|
|
164
|
+
function indexSite(call, targets, probable = false) {
|
|
165
|
+
const known = indexedTargets.get(call) || [];
|
|
184
166
|
for (const target of targets) {
|
|
167
|
+
if (known.includes(target)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
known.push(target);
|
|
185
171
|
if (!callSites.has(target)) {
|
|
186
172
|
callSites.set(target, []);
|
|
187
173
|
}
|
|
188
174
|
callSites.get(target).push(call);
|
|
189
175
|
}
|
|
176
|
+
if (known.length) {
|
|
177
|
+
indexedTargets.set(call, known);
|
|
178
|
+
}
|
|
179
|
+
if (probable) {
|
|
180
|
+
probableSites.add(call);
|
|
181
|
+
}
|
|
190
182
|
}
|
|
191
183
|
|
|
192
184
|
// The checker's own targets of each call; returns them per call.
|
|
@@ -199,28 +191,6 @@ function createResolver({ program, checker, symbols, evaluator, bindings, action
|
|
|
199
191
|
});
|
|
200
192
|
}
|
|
201
193
|
|
|
202
|
-
// The function (and parameter position) a parameter or destructured
|
|
203
|
-
// parameter element belongs to, with its property path.
|
|
204
|
-
function owningParameter(declaration) {
|
|
205
|
-
const pathParts = [];
|
|
206
|
-
let current = declaration;
|
|
207
|
-
while (current && ts.isBindingElement(current)) {
|
|
208
|
-
if (!ts.isObjectBindingPattern(current.parent)) {
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
const key = current.propertyName || current.name;
|
|
212
|
-
if (!ts.isIdentifier(key) && !ts.isStringLiteralLike(key)) {
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
pathParts.unshift(key.text);
|
|
216
|
-
current = current.parent.parent;
|
|
217
|
-
}
|
|
218
|
-
if (!current || !ts.isParameter(current) || !isFunctionLike(current.parent)) {
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
return { fn: current.parent, index: current.parent.parameters.indexOf(current), path: pathParts };
|
|
222
|
-
}
|
|
223
|
-
|
|
224
194
|
// The value one call gives a parameter: the argument, or the property of an
|
|
225
195
|
// object literal argument a destructured parameter takes, with the rest of
|
|
226
196
|
// its property path.
|
|
@@ -242,7 +212,13 @@ function createResolver({ program, checker, symbols, evaluator, bindings, action
|
|
|
242
212
|
|
|
243
213
|
function argumentValues(declaration) {
|
|
244
214
|
const owner = owningParameter(declaration);
|
|
245
|
-
|
|
215
|
+
if (!owner) {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
return (callSites.get(owner.fn) || []).map((call) => {
|
|
219
|
+
const value = argumentValue(call, owner);
|
|
220
|
+
return value && { ...value, probable: probableSites.has(call) };
|
|
221
|
+
}).filter(Boolean);
|
|
246
222
|
}
|
|
247
223
|
|
|
248
224
|
function propertyValueExpressions(property) {
|
|
@@ -389,11 +365,24 @@ function createResolver({ program, checker, symbols, evaluator, bindings, action
|
|
|
389
365
|
return { resolution: "unresolved", certainty: "unresolved", method: "none", targets: [] };
|
|
390
366
|
}
|
|
391
367
|
|
|
392
|
-
// The functions a passed value can be, added to `targets`.
|
|
393
|
-
|
|
368
|
+
// The functions a passed value can be, added to `targets`. With `passedOn`,
|
|
369
|
+
// the path of parameters followed so far, a parameter the value passes on
|
|
370
|
+
// holds what its own callers give it; a callback a helper runs (passedCall)
|
|
371
|
+
// is never followed to another caller's value.
|
|
372
|
+
function addValueTargets(value, targets, exact = false, passedOn = null) {
|
|
394
373
|
const found = value.path.length
|
|
395
374
|
? [...targetsFromTypes(valueTypes({ expression: value.expression, path: value.path.slice(0, -1) }, 0, new Set()), [value.path.at(-1)]).targets.keys()]
|
|
396
375
|
: symbols.expressionImplementations(value.expression, 0).map(constructorOf).filter(Boolean);
|
|
376
|
+
const expression = unwrapExpression(value.expression);
|
|
377
|
+
const parameter = passedOn && !found.length && !value.path.length && ts.isIdentifier(expression) ? symbols.declarationOf(expression) : null;
|
|
378
|
+
if (parameter && (ts.isParameter(parameter) || ts.isBindingElement(parameter)) &&
|
|
379
|
+
!passedOn.includes(parameter) && passedOn.length < MAX_TYPE_DEPTH) {
|
|
380
|
+
const values = argumentValues(parameter);
|
|
381
|
+
for (const passed of values) {
|
|
382
|
+
addValueTargets(passed, targets, exact && values.length === 1 && !passed.probable, [...passedOn, parameter]);
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
397
386
|
for (const target of found) {
|
|
398
387
|
targets.set(target, { exact: exact && found.length === 1 && !value.path.length, via: "argument-flow" });
|
|
399
388
|
}
|
|
@@ -419,7 +408,7 @@ function createResolver({ program, checker, symbols, evaluator, bindings, action
|
|
|
419
408
|
}
|
|
420
409
|
const values = argumentValues(declaration);
|
|
421
410
|
for (const value of values) {
|
|
422
|
-
addValueTargets(value, targets, values.length === 1);
|
|
411
|
+
addValueTargets(value, targets, values.length === 1 && !value.probable, []);
|
|
423
412
|
}
|
|
424
413
|
return targets;
|
|
425
414
|
}
|
|
@@ -431,6 +420,46 @@ function createResolver({ program, checker, symbols, evaluator, bindings, action
|
|
|
431
420
|
return direct.get(call).targets || [];
|
|
432
421
|
}
|
|
433
422
|
|
|
423
|
+
// The functions a call reaches as the index knows them: the checker's, or
|
|
424
|
+
// those injection or argument flow resolved.
|
|
425
|
+
function siteTargets(call) {
|
|
426
|
+
return indexedTargets.get(call) || checkerTargets(call);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// The calls whose callee is a parameter or a destructured name: those
|
|
430
|
+
// injection or argument flow can resolve. What a call's callee is declared
|
|
431
|
+
// as depends only on its own file.
|
|
432
|
+
function boundCandidates(calls) {
|
|
433
|
+
return calls.filter((call) => {
|
|
434
|
+
checkerTargets(call);
|
|
435
|
+
const declaration = direct.get(call).declaration;
|
|
436
|
+
return declaration && (ts.isParameter(declaration) || ts.isBindingElement(declaration));
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Calls the checker cannot resolve but injection or argument flow can are
|
|
441
|
+
// call sites too, so argument flow and the callbacks a call passes reach
|
|
442
|
+
// through them. Each round can expose more; the targets and guesses only
|
|
443
|
+
// grow, so the rounds stop. Takes the candidates of every file, since one
|
|
444
|
+
// file's arguments decide another's targets, and returns each candidate's
|
|
445
|
+
// targets and whether they are a guess.
|
|
446
|
+
function indexBoundCalls(candidates) {
|
|
447
|
+
for (let changed = true; changed;) {
|
|
448
|
+
changed = false;
|
|
449
|
+
for (const call of candidates) {
|
|
450
|
+
const found = boundFunctionTargets(direct.get(call).declaration, call);
|
|
451
|
+
const targets = found.size > MAX_TARGETS ? [] : [...found.keys()];
|
|
452
|
+
const probable = targets.length !== 1 || !found.get(targets[0]).exact;
|
|
453
|
+
const known = indexedTargets.get(call) || [];
|
|
454
|
+
if (targets.some((target) => !known.includes(target)) || (probable && targets.length && !probableSites.has(call))) {
|
|
455
|
+
indexSite(call, targets, probable);
|
|
456
|
+
changed = true;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return candidates.map((call) => ({ targets: indexedTargets.get(call) || [], probable: probableSites.has(call) }));
|
|
461
|
+
}
|
|
462
|
+
|
|
434
463
|
// The parameters ({ index, path }) a function calls while it runs: in its
|
|
435
464
|
// own body, in callbacks it passes on, or by forwarding them to a function
|
|
436
465
|
// that does. A cycle adds nothing.
|
|
@@ -462,7 +491,7 @@ function createResolver({ program, checker, symbols, evaluator, bindings, action
|
|
|
462
491
|
}
|
|
463
492
|
node.arguments.forEach((argument, position) => {
|
|
464
493
|
const forwarded = own(argument);
|
|
465
|
-
for (const target of forwarded ?
|
|
494
|
+
for (const target of forwarded ? siteTargets(node) : []) {
|
|
466
495
|
for (const inner of invokedParameters(target)) {
|
|
467
496
|
if (inner.index === position) {
|
|
468
497
|
add(forwarded.index, [...forwarded.path, ...inner.path]);
|
|
@@ -486,11 +515,11 @@ function createResolver({ program, checker, symbols, evaluator, bindings, action
|
|
|
486
515
|
return null;
|
|
487
516
|
}
|
|
488
517
|
const targets = new Map();
|
|
489
|
-
for (const callee of
|
|
518
|
+
for (const callee of siteTargets(call)) {
|
|
490
519
|
for (const parameter of invokedParameters(callee)) {
|
|
491
520
|
const value = argumentValue(call, parameter);
|
|
492
521
|
if (value) {
|
|
493
|
-
addValueTargets(value, targets,
|
|
522
|
+
addValueTargets(value, targets, !probableSites.has(call));
|
|
494
523
|
}
|
|
495
524
|
}
|
|
496
525
|
}
|
|
@@ -498,96 +527,6 @@ function createResolver({ program, checker, symbols, evaluator, bindings, action
|
|
|
498
527
|
return result?.resolution === "unresolved" ? null : result;
|
|
499
528
|
}
|
|
500
529
|
|
|
501
|
-
// Whether a value is provably produced by a package or builtin.
|
|
502
|
-
function isExternalValue(expression, depth = 0) {
|
|
503
|
-
const root = symbols.receiverRoot(expression);
|
|
504
|
-
if (!root || depth > MAX_TYPE_DEPTH) {
|
|
505
|
-
return false;
|
|
506
|
-
}
|
|
507
|
-
if (ts.isIdentifier(root)) {
|
|
508
|
-
if (symbols.isPackageImport(root)) {
|
|
509
|
-
return true;
|
|
510
|
-
}
|
|
511
|
-
const symbol = symbols.symbolAt(root);
|
|
512
|
-
if (!symbol) {
|
|
513
|
-
return NODE_GLOBALS.has(root.text) || (isComponentFile(fileOf(root)) && VUE_EXTERNAL_VALUES.has(root.text));
|
|
514
|
-
}
|
|
515
|
-
if (symbols.isLibrarySymbol(symbol)) {
|
|
516
|
-
return true;
|
|
517
|
-
}
|
|
518
|
-
const declaration = symbols.declarationOf(root);
|
|
519
|
-
return Boolean(declaration) && isExternalDeclaration(declaration, depth + 1);
|
|
520
|
-
}
|
|
521
|
-
return ts.isStringLiteralLike(root) || ts.isArrayLiteralExpression(root) || ts.isRegularExpressionLiteral(root) ||
|
|
522
|
-
ts.isTemplateExpression(root) || ts.isNumericLiteral(root);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Whether every value a declared name can hold comes from a package or
|
|
526
|
-
// builtin: its initializer and later assignments, the iterable it is taken
|
|
527
|
-
// from, the value it is destructured from, or the external call a callback
|
|
528
|
-
// parameter is supplied by.
|
|
529
|
-
function isExternalDeclaration(declaration, depth) {
|
|
530
|
-
let current = declaration;
|
|
531
|
-
while (current && ts.isBindingElement(current)) {
|
|
532
|
-
current = current.parent.parent;
|
|
533
|
-
}
|
|
534
|
-
if (!current || depth > MAX_TYPE_DEPTH) {
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
537
|
-
if (ts.isParameter(current)) {
|
|
538
|
-
const fn = current.parent;
|
|
539
|
-
const call = fn.parent;
|
|
540
|
-
return (ts.isCallExpression(call) || ts.isNewExpression(call)) && Boolean(call.arguments?.includes(fn)) &&
|
|
541
|
-
isExternalCallee(call.expression, depth + 1);
|
|
542
|
-
}
|
|
543
|
-
if (!ts.isVariableDeclaration(current)) {
|
|
544
|
-
return false;
|
|
545
|
-
}
|
|
546
|
-
const statement = current.parent?.parent;
|
|
547
|
-
if (statement && ts.isForOfStatement(statement) && statement.initializer === current.parent) {
|
|
548
|
-
return isExternalValue(statement.expression, depth + 1);
|
|
549
|
-
}
|
|
550
|
-
const values = [current.initializer, ...assignedValues(current)].filter((value) => value && !isNullish(value));
|
|
551
|
-
return values.length > 0 && values.every((value) => isExternalValue(value, depth + 1));
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function isExternalCallee(expression, depth) {
|
|
555
|
-
const callee = unwrapExpression(expression);
|
|
556
|
-
if (ts.isIdentifier(callee)) {
|
|
557
|
-
const symbol = symbols.symbolAt(callee);
|
|
558
|
-
return symbol ? symbols.isPackageImport(callee) || symbols.isLibrarySymbol(symbol) : NODE_GLOBALS.has(callee.text);
|
|
559
|
-
}
|
|
560
|
-
return ts.isPropertyAccessExpression(callee) && isExternalValue(callee.expression, depth);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// The values later assigned to a `let` or `var` name within its scope.
|
|
564
|
-
const assignments = new Map();
|
|
565
|
-
function assignedValues(declaration) {
|
|
566
|
-
if (!ts.isIdentifier(declaration.name) || declaration.parent.flags & ts.NodeFlags.Const) {
|
|
567
|
-
return [];
|
|
568
|
-
}
|
|
569
|
-
if (!assignments.has(declaration)) {
|
|
570
|
-
const symbol = checker.getSymbolAtLocation(declaration.name);
|
|
571
|
-
let scope = declaration.parent;
|
|
572
|
-
while (scope && !isFunctionLike(scope) && !ts.isSourceFile(scope)) {
|
|
573
|
-
scope = scope.parent;
|
|
574
|
-
}
|
|
575
|
-
const values = [];
|
|
576
|
-
const visit = (node) => {
|
|
577
|
-
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
578
|
-
ts.isIdentifier(node.left) && node.left.text === declaration.name.text && checker.getSymbolAtLocation(node.left) === symbol) {
|
|
579
|
-
values.push(node.right);
|
|
580
|
-
}
|
|
581
|
-
ts.forEachChild(node, visit);
|
|
582
|
-
};
|
|
583
|
-
if (scope) {
|
|
584
|
-
visit(scope);
|
|
585
|
-
}
|
|
586
|
-
assignments.set(declaration, values);
|
|
587
|
-
}
|
|
588
|
-
return assignments.get(declaration);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
530
|
function receiverHint(receiver, candidates) {
|
|
592
531
|
const text = receiver?.getText().split(".").pop()?.replace(/Service$/u, "").toLowerCase();
|
|
593
532
|
if (!text || !/^[a-z][\w]*$/u.test(text)) {
|
|
@@ -713,7 +652,7 @@ function createResolver({ program, checker, symbols, evaluator, bindings, action
|
|
|
713
652
|
}
|
|
714
653
|
}
|
|
715
654
|
|
|
716
|
-
return { indexCalls, indexSite, resolve, passedCall, builtinMembers };
|
|
655
|
+
return { indexCalls, indexSite, boundCandidates, indexBoundCalls, resolve, passedCall, builtinMembers };
|
|
717
656
|
}
|
|
718
657
|
|
|
719
658
|
export { codeSide, createResolver };
|
package/src/calls/symbols.js
CHANGED
|
@@ -88,6 +88,9 @@ function createSymbols(context) {
|
|
|
88
88
|
if (ts.isShorthandPropertyAssignment(declaration)) {
|
|
89
89
|
return symbolImplementations(checker.getShorthandAssignmentValueSymbol(declaration), depth + 1);
|
|
90
90
|
}
|
|
91
|
+
if (ts.isBindingElement(declaration)) {
|
|
92
|
+
return bindingElementImplementations(declaration, depth + 1);
|
|
93
|
+
}
|
|
91
94
|
if (ts.isExportSpecifier(declaration) || ts.isImportSpecifier(declaration) || ts.isImportClause(declaration)) {
|
|
92
95
|
return symbolImplementations(checker.getSymbolAtLocation(declaration.name), depth + 1);
|
|
93
96
|
}
|
|
@@ -104,6 +107,26 @@ function createSymbols(context) {
|
|
|
104
107
|
return [];
|
|
105
108
|
}
|
|
106
109
|
|
|
110
|
+
// A name an object pattern binds (`const { a: b } = source`, a parameter's
|
|
111
|
+
// pattern, or one nested in another) holds the property it reads from the
|
|
112
|
+
// type of what the pattern destructures.
|
|
113
|
+
function bindingElementImplementations(element, depth) {
|
|
114
|
+
const key = element.propertyName || element.name;
|
|
115
|
+
if (!ts.isObjectBindingPattern(element.parent) || element.dotDotDotToken || !(ts.isIdentifier(key) || ts.isStringLiteralLike(key))) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
const property = checker.getTypeAtLocation(element.parent).getProperty(key.text);
|
|
119
|
+
const found = new Set();
|
|
120
|
+
for (const declaration of property?.declarations || []) {
|
|
121
|
+
if (declaration !== element) {
|
|
122
|
+
for (const implementation of declarationImplementations(declaration, depth)) {
|
|
123
|
+
found.add(implementation);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return [...found];
|
|
128
|
+
}
|
|
129
|
+
|
|
107
130
|
function expressionImplementations(expression, depth) {
|
|
108
131
|
const value = unwrapExpression(expression);
|
|
109
132
|
if (!value || depth > MAX_DEPTH) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The collaborators the call layer's factories hand each other over one
|
|
2
|
+
// loaded call program.
|
|
3
|
+
|
|
4
|
+
/** @typedef {ReturnType<typeof import("./program.js").loadCallProgram>["fileOf"]} FileOf */
|
|
5
|
+
/** @typedef {ReturnType<typeof import("./symbols.js").createSymbols>} CallSymbols */
|
|
6
|
+
/** @typedef {ReturnType<typeof import("./evaluate.js").createEvaluator>} CallEvaluator */
|
|
7
|
+
/** @typedef {ReturnType<typeof import("./features.js").bindFeatures>} FeatureBindings */
|
|
8
|
+
|
|
9
|
+
export {};
|