@cplieger/deadset-ts 8.0.0-dev.17 → 8.0.0-dev.19
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/dist/src/analysis.js +30 -72
- package/dist/src/augmentations.js +1 -1
- package/dist/src/calls.js +267 -48
- package/dist/src/commands/held.js +499 -0
- package/dist/src/commands/read.js +16 -10
- package/dist/src/commands/shell.js +109 -15
- package/dist/src/commands/tools.js +1540 -149
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contract/contract.generated.js +9 -2
- package/dist/src/entries/conventions.js +7 -6
- package/dist/src/entries/test-runners.js +6 -40
- package/dist/src/entries/workflow-steps.js +202 -28
- package/dist/src/exempt.js +0 -26
- package/dist/src/exposure.js +400 -0
- package/dist/src/findings/completion.js +4 -17
- package/dist/src/findings/emitters.js +22 -33
- package/dist/src/findings/intra-function.js +3 -3
- package/dist/src/findings/non-code-artifacts.js +11 -5
- package/dist/src/findings/reads-and-writes.js +71 -15
- package/dist/src/findings/unused-declarations.js +2 -22
- package/dist/src/findings/visibility-narrowing.js +14 -20
- package/dist/src/inventory.js +18 -0
- package/dist/src/{components.js → liveness/components.js} +1 -1
- package/dist/src/{graph.js → liveness/graph.js} +38 -21
- package/dist/src/liveness/matrix.js +99 -0
- package/dist/src/liveness/sweep.js +558 -0
- package/dist/src/query.js +46 -0
- package/dist/src/references/sink.js +87 -1
- package/dist/src/references.js +1 -1
- package/dist/src/roots/manifest.js +47 -4
- package/dist/src/roots/published-reach.js +4 -4
- package/dist/src/roots/root-set.js +14 -16
- package/dist/src/text/named-files.js +257 -34
- package/dist/src/verbs/analyze.js +16 -1
- package/dist/src/verbs/explain.js +73 -152
- package/dist/src/verbs/print-retained.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +54 -108
- package/src/augmentations.ts +1 -1
- package/src/calls.ts +326 -52
- package/src/commands/held.ts +632 -0
- package/src/commands/read.ts +26 -12
- package/src/commands/shell.ts +117 -15
- package/src/commands/tools.ts +1972 -202
- package/src/conformance-record.ts +1 -1
- package/src/contract/contract.generated.ts +9 -2
- package/src/dependencies.ts +1 -1
- package/src/entries/conventions.ts +8 -12
- package/src/entries/test-runners.ts +12 -45
- package/src/entries/workflow-steps.ts +227 -38
- package/src/exempt.ts +31 -46
- package/src/exposure.ts +492 -0
- package/src/file-facts.ts +5 -0
- package/src/finding.ts +1 -1
- package/src/findings/boundary.ts +7 -0
- package/src/findings/completion.ts +5 -19
- package/src/findings/emitters.ts +23 -41
- package/src/findings/interfaces.ts +3 -3
- package/src/findings/intra-function.ts +3 -3
- package/src/findings/non-code-artifacts.ts +16 -5
- package/src/findings/reads-and-writes.ts +99 -26
- package/src/findings/unused-declarations.ts +4 -28
- package/src/findings/visibility-narrowing.ts +25 -26
- package/src/interface-satisfaction.ts +1 -1
- package/src/inventory.ts +19 -0
- package/src/{components.ts → liveness/components.ts} +3 -3
- package/src/{graph.ts → liveness/graph.ts} +74 -26
- package/src/liveness/matrix.ts +162 -0
- package/src/liveness/sweep.ts +855 -0
- package/src/query.ts +81 -0
- package/src/references/sink.ts +101 -0
- package/src/references.ts +8 -1
- package/src/roots/manifest.ts +53 -8
- package/src/roots/published-reach.ts +9 -4
- package/src/roots/root-set.ts +18 -16
- package/src/stores.ts +2 -1
- package/src/tests/file-class.ts +19 -2
- package/src/text/named-files.ts +333 -36
- package/src/verbs/analyze.ts +16 -1
- package/src/verbs/explain.ts +81 -199
- package/src/verbs/print-retained.ts +1 -1
- package/dist/src/matrix.js +0 -275
- package/dist/src/sweep.js +0 -234
- package/src/matrix.ts +0 -466
- package/src/sweep.ts +0 -384
package/src/calls.ts
CHANGED
|
@@ -1,30 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The calls and decorators of one project that the configured classes read: what each
|
|
3
3
|
* callee resolves to, and which of the target's classes a call passes or a decorator
|
|
4
|
-
* is attached to. A class is passed
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* is attached to. A class is passed by each value an argument can be, at any depth inside
|
|
5
|
+
* the array and object literals it writes, named directly or through variables, and a
|
|
6
|
+
* decorator written as a call is a call.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
isArrayLiteralExpression,
|
|
11
|
+
isArrowFunction,
|
|
12
|
+
isBinaryExpression,
|
|
13
|
+
isBindingElement,
|
|
11
14
|
isCallExpression,
|
|
12
15
|
isClassDeclaration,
|
|
16
|
+
isConditionalExpression,
|
|
13
17
|
isDecorator,
|
|
18
|
+
isFunctionDeclaration,
|
|
19
|
+
isFunctionExpression,
|
|
14
20
|
isIdentifier,
|
|
21
|
+
isMethodDeclaration,
|
|
15
22
|
isObjectLiteralExpression,
|
|
23
|
+
isParameterDeclaration,
|
|
16
24
|
isParenthesizedExpression,
|
|
17
25
|
isPropertyAccessExpression,
|
|
18
26
|
isPropertyAssignment,
|
|
27
|
+
isPropertyDeclaration,
|
|
19
28
|
isShorthandPropertyAssignment,
|
|
29
|
+
isOmittedExpression,
|
|
30
|
+
isSpreadAssignment,
|
|
20
31
|
isSpreadElement,
|
|
21
32
|
isVariableDeclaration,
|
|
22
33
|
type CallExpression,
|
|
23
34
|
type Decorator,
|
|
24
35
|
type Expression,
|
|
25
36
|
type Node,
|
|
37
|
+
type ParameterDeclaration,
|
|
38
|
+
SyntaxKind,
|
|
26
39
|
} from "@typescript/native/ast";
|
|
27
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
ObjectFlags,
|
|
42
|
+
SymbolFlags,
|
|
43
|
+
type Symbol as TSSymbol,
|
|
44
|
+
type Type,
|
|
45
|
+
} from "@typescript/native/sync";
|
|
28
46
|
import type { AliasChains } from "./alias-chain.ts";
|
|
29
47
|
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
30
48
|
import { must } from "./query.ts";
|
|
@@ -35,11 +53,14 @@ import { unwrapped } from "./unwrap.ts";
|
|
|
35
53
|
interface PassedValue {
|
|
36
54
|
/** Where the value is written, which is where a record of it sits. */
|
|
37
55
|
readonly site: Node;
|
|
38
|
-
/** The name the value is resolved through,
|
|
39
|
-
readonly name: Node;
|
|
56
|
+
/** The name the value is resolved through, the shorthand assignment that holds it, or none. */
|
|
57
|
+
readonly name: Node | undefined;
|
|
40
58
|
readonly shorthand: boolean;
|
|
41
59
|
}
|
|
42
60
|
|
|
61
|
+
/** A passed value that names its declaration. */
|
|
62
|
+
type NamedValue = PassedValue & { readonly name: Node };
|
|
63
|
+
|
|
43
64
|
/** One class of the target a call passes or a decorator is attached to, and where. */
|
|
44
65
|
interface ClassUse {
|
|
45
66
|
/** The inventory's identifier of the class. */
|
|
@@ -111,46 +132,114 @@ export function resolvedTargets<Brand>(
|
|
|
111
132
|
return targets;
|
|
112
133
|
}
|
|
113
134
|
|
|
114
|
-
/** The
|
|
115
|
-
function
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (isArrayLiteralExpression(expression)) {
|
|
121
|
-
for (const element of expression.elements) {
|
|
122
|
-
passedIn(isSpreadElement(element) ? element.expression : element, found);
|
|
123
|
-
}
|
|
124
|
-
return;
|
|
135
|
+
/** The declaration a shorthand property's value resolves to, an import followed to what it names. */
|
|
136
|
+
function shorthandTarget<Brand>(project: ProjectView<Brand>, node: Node): TSSymbol | undefined {
|
|
137
|
+
const value = must(project.shorthandValueAt(project.handle(node)));
|
|
138
|
+
if (value === undefined || (value.flags & SymbolFlags.Alias) === 0) {
|
|
139
|
+
return value;
|
|
125
140
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
141
|
+
const aliased = must(project.queries.aliased(value));
|
|
142
|
+
return aliased.declarations.length === 0 ? undefined : aliased;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The values one expression passes: each value it can be ({@link leavesOf}), or what an
|
|
147
|
+
* array or object literal it can be holds, at any depth.
|
|
148
|
+
*/
|
|
149
|
+
function passedIn(written: Expression, found: PassedValue[]): void {
|
|
150
|
+
for (const expression of leavesOf(written)) {
|
|
151
|
+
if (isArrayLiteralExpression(expression)) {
|
|
152
|
+
for (const element of expression.elements) {
|
|
153
|
+
passedIn(element, found);
|
|
154
|
+
}
|
|
155
|
+
} else if (isObjectLiteralExpression(expression)) {
|
|
156
|
+
for (const property of expression.properties) {
|
|
157
|
+
if (isPropertyAssignment(property)) {
|
|
158
|
+
passedIn(property.initializer, found);
|
|
159
|
+
} else if (isSpreadAssignment(property)) {
|
|
160
|
+
passedIn(property.expression, found);
|
|
161
|
+
} else if (isShorthandPropertyAssignment(property)) {
|
|
162
|
+
found.push({ site: property.name, name: property, shorthand: true });
|
|
163
|
+
}
|
|
132
164
|
}
|
|
165
|
+
} else if (!isOmittedExpression(expression)) {
|
|
166
|
+
found.push({ site: expression, name: calleeName(expression), shorthand: false });
|
|
133
167
|
}
|
|
134
|
-
return;
|
|
135
168
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** The operators whose value is one of their operands. */
|
|
172
|
+
const CHOOSING: ReadonlySet<SyntaxKind> = new Set([
|
|
173
|
+
SyntaxKind.QuestionQuestionToken,
|
|
174
|
+
SyntaxKind.BarBarToken,
|
|
175
|
+
SyntaxKind.AmpersandAmpersandToken,
|
|
176
|
+
]);
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The expressions one value can be: itself, or each branch of a conditional, each operand
|
|
180
|
+
* of `??`, `||` and `&&` and the last of a comma, inside any wrapper that changes no value.
|
|
181
|
+
* Each is read alone because the checker reduces a union by subtype, so the type at the
|
|
182
|
+
* whole drops a class structurally equal to, or a subtype of, another branch's.
|
|
183
|
+
*/
|
|
184
|
+
function leavesOf(written: Expression): readonly Expression[] {
|
|
185
|
+
const expression = unwrapped(isSpreadElement(written) ? written.expression : written);
|
|
186
|
+
if (isConditionalExpression(expression)) {
|
|
187
|
+
return [...leavesOf(expression.whenTrue), ...leavesOf(expression.whenFalse)];
|
|
188
|
+
}
|
|
189
|
+
if (isBinaryExpression(expression)) {
|
|
190
|
+
const operator = expression.operatorToken.kind;
|
|
191
|
+
if (CHOOSING.has(operator)) {
|
|
192
|
+
return [...leavesOf(expression.left), ...leavesOf(expression.right)];
|
|
193
|
+
}
|
|
194
|
+
if (operator === SyntaxKind.CommaToken) {
|
|
195
|
+
return leavesOf(expression.right);
|
|
196
|
+
}
|
|
139
197
|
}
|
|
198
|
+
return [expression];
|
|
140
199
|
}
|
|
141
200
|
|
|
142
201
|
/** The values one argument passes, a spread argument's included. */
|
|
143
|
-
|
|
202
|
+
function leavesPassed(argument: Expression): readonly PassedValue[] {
|
|
144
203
|
const found: PassedValue[] = [];
|
|
145
|
-
passedIn(
|
|
204
|
+
passedIn(argument, found);
|
|
146
205
|
return found;
|
|
147
206
|
}
|
|
148
207
|
|
|
208
|
+
/** The values one argument passes that name their declaration. */
|
|
209
|
+
export function valuesPassed(argument: Expression): readonly NamedValue[] {
|
|
210
|
+
return leavesPassed(argument).filter((value): value is NamedValue => value.name !== undefined);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* What one passed value holds: the declaration its name resolves to, and the classes and
|
|
215
|
+
* functions the checker's type at it holds ({@link constructedAt}). `known` holds names
|
|
216
|
+
* already resolved in a batch.
|
|
217
|
+
*/
|
|
218
|
+
function heldBy<Brand>(
|
|
219
|
+
project: ProjectView<Brand>,
|
|
220
|
+
value: PassedValue,
|
|
221
|
+
known?: ReadonlyMap<Node, TSSymbol>,
|
|
222
|
+
): { readonly named: TSSymbol | undefined; readonly typed: readonly TSSymbol[] } {
|
|
223
|
+
const { name } = value;
|
|
224
|
+
if (name === undefined) {
|
|
225
|
+
return { named: undefined, typed: constructedAt(project, value.site) };
|
|
226
|
+
}
|
|
227
|
+
if (value.shorthand) {
|
|
228
|
+
return { named: shorthandTarget(project, name), typed: [] };
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
named: known?.get(name) ?? resolvedTargets(project, [name]).get(name),
|
|
232
|
+
typed: constructedAt(project, value.site),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
149
236
|
/**
|
|
150
|
-
* The classes of the target each call passes, by the call
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
237
|
+
* The classes of the target each call passes, by the call: each class a value an argument
|
|
238
|
+
* can be holds as a constructor, as the checker types it. A name whose type erases the
|
|
239
|
+
* class is followed through what its declaration binds ({@link boundTo}) and, for a
|
|
240
|
+
* parameter, through what each call of its function passes at its place, so a class handed
|
|
241
|
+
* to a wrapper is passed. A class is used at each value that passes it. A class of another
|
|
242
|
+
* program passes nothing.
|
|
154
243
|
*/
|
|
155
244
|
export function classesPassed<Brand>(
|
|
156
245
|
project: ProjectView<Brand>,
|
|
@@ -158,45 +247,230 @@ export function classesPassed<Brand>(
|
|
|
158
247
|
chains: AliasChains,
|
|
159
248
|
calls: readonly CallExpression[],
|
|
160
249
|
): ReadonlyMap<CallExpression, readonly ClassUse[]> {
|
|
161
|
-
const passed = new Map(
|
|
162
|
-
calls.map((call) => [call, call.arguments.flatMap((argument) => valuesPassed(argument))]),
|
|
163
|
-
);
|
|
164
|
-
const all = [...passed.values()].flat();
|
|
250
|
+
const passed = new Map(calls.map((call) => [call, call.arguments.flatMap(leavesPassed)]));
|
|
165
251
|
const targets = resolvedTargets(
|
|
166
252
|
project,
|
|
167
|
-
|
|
253
|
+
[...passed.values()].flat().flatMap((value) => (value.shorthand ? [] : (value.name ?? []))),
|
|
168
254
|
);
|
|
169
255
|
const classes = new Set(
|
|
170
256
|
held.symbols.filter((symbol) => symbol.kind === "class").map((symbol) => symbol.id),
|
|
171
257
|
);
|
|
258
|
+
let calling: CallsOf | undefined;
|
|
259
|
+
const callsOfFunction = (symbol: TSSymbol): readonly CallExpression[] => {
|
|
260
|
+
calling ??= callsByDeclaration(project);
|
|
261
|
+
return symbol.declarations.flatMap((handle) => calling?.get(keyOf(handle)) ?? []);
|
|
262
|
+
};
|
|
263
|
+
const classesOf = (value: PassedValue, seen: Set<number>): readonly string[] => {
|
|
264
|
+
const { named, typed } = heldBy(project, value, targets);
|
|
265
|
+
return [
|
|
266
|
+
...typed.flatMap((symbol) => chains.declarationsOf(symbol).filter((id) => classes.has(id))),
|
|
267
|
+
...(named === undefined ? [] : reached(named, seen)),
|
|
268
|
+
];
|
|
269
|
+
};
|
|
172
270
|
const reached = (symbol: TSSymbol, seen: Set<number>): readonly string[] => {
|
|
173
271
|
const ids = chains.declarationsOf(symbol).filter((id) => classes.has(id));
|
|
174
272
|
if (ids.length > 0 || seen.has(symbol.id)) {
|
|
175
273
|
return ids;
|
|
176
274
|
}
|
|
177
275
|
seen.add(symbol.id);
|
|
276
|
+
const through = (written: Expression): readonly string[] =>
|
|
277
|
+
leavesPassed(written).flatMap((value) => classesOf(value, seen));
|
|
178
278
|
return symbol.declarations.flatMap((handle) => {
|
|
179
279
|
const node = project.declarationAt(handle)?.node;
|
|
180
|
-
if (node === undefined
|
|
280
|
+
if (node === undefined) {
|
|
181
281
|
return [];
|
|
182
282
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
283
|
+
if (isParameterDeclaration(node)) {
|
|
284
|
+
return argumentsAt(project, node, callsOfFunction).flatMap(through);
|
|
285
|
+
}
|
|
286
|
+
const bound = boundTo(project, node);
|
|
287
|
+
return [
|
|
288
|
+
...(bound.written === undefined ? [] : through(bound.written)),
|
|
289
|
+
...bound.symbols.flatMap((next) => reached(next, seen)),
|
|
290
|
+
];
|
|
186
291
|
});
|
|
187
292
|
};
|
|
188
|
-
const classesOf = (value: PassedValue): readonly string[] => {
|
|
189
|
-
const symbol = value.shorthand
|
|
190
|
-
? must(project.shorthandValueAt(project.handle(value.name)))
|
|
191
|
-
: targets.get(value.name);
|
|
192
|
-
return symbol === undefined ? [] : reached(symbol, new Set());
|
|
193
|
-
};
|
|
194
293
|
return new Map(
|
|
195
|
-
[...passed].map(([call, values]) =>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
294
|
+
[...passed].map(([call, values]) => {
|
|
295
|
+
const uses = new Map<string, ClassUse>();
|
|
296
|
+
for (const value of values) {
|
|
297
|
+
for (const id of classesOf(value, new Set())) {
|
|
298
|
+
uses.set(`${id}\u0000${String(value.site.pos)}`, { id, site: value.site });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return [call, [...uses.values()]];
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* The symbols of the classes and functions the checker's type at one expression holds as
|
|
308
|
+
* values, each member of a union: the static side of a class or a function is an
|
|
309
|
+
* anonymous type its declaration's symbol names, which an instance's type is not.
|
|
310
|
+
*/
|
|
311
|
+
function constructedAt<Brand>(project: ProjectView<Brand>, written: Node): readonly TSSymbol[] {
|
|
312
|
+
return heldIn(project, must(project.queries.typeAt(unwrapped(written))));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** The classes and functions one type holds as values, as {@link constructedAt} reads them. */
|
|
316
|
+
function heldIn<Brand>(project: ProjectView<Brand>, type: Type | undefined): readonly TSSymbol[] {
|
|
317
|
+
const { queries } = project;
|
|
318
|
+
return (type === undefined ? [] : must(queries.constituents(type))).flatMap((part) => {
|
|
319
|
+
if (!part.isObjectType() || (part.objectFlags & ObjectFlags.Anonymous) === 0) {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
return must(queries.symbolOfType(part)) ?? [];
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** What one declaration binds its name to: the value it writes, or the symbols it copies. */
|
|
327
|
+
interface Bound {
|
|
328
|
+
readonly written?: Expression;
|
|
329
|
+
readonly symbols: readonly TSSymbol[];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* What a variable, a property or a destructured binding binds its name to: a variable's
|
|
334
|
+
* or a property's initializer, the value a shorthand property names, or the property of
|
|
335
|
+
* the destructured value a binding element reads.
|
|
336
|
+
*/
|
|
337
|
+
function boundTo<Brand>(project: ProjectView<Brand>, node: Node): Bound {
|
|
338
|
+
if (isShorthandPropertyAssignment(node)) {
|
|
339
|
+
const value = shorthandTarget(project, node);
|
|
340
|
+
return { symbols: value === undefined ? [] : [value] };
|
|
341
|
+
}
|
|
342
|
+
if (isBindingElement(node)) {
|
|
343
|
+
const key = node.propertyName ?? node.name;
|
|
344
|
+
if (key === undefined || !isIdentifier(key)) {
|
|
345
|
+
return { symbols: [] };
|
|
346
|
+
}
|
|
347
|
+
const source = must(project.queries.typeAt(node.parent));
|
|
348
|
+
const property =
|
|
349
|
+
source === undefined
|
|
350
|
+
? undefined
|
|
351
|
+
: must(project.queries.propertiesOf(source)).find((one) => one.name === key.text);
|
|
352
|
+
return { symbols: property === undefined ? [] : [property] };
|
|
353
|
+
}
|
|
354
|
+
const written = initializerOf(node);
|
|
355
|
+
return written === undefined ? { symbols: [] } : { written, symbols: [] };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** The value a variable or a property declaration binds its name to, where it writes one. */
|
|
359
|
+
function initializerOf(node: Node): Expression | undefined {
|
|
360
|
+
return isVariableDeclaration(node) || isPropertyAssignment(node) || isPropertyDeclaration(node)
|
|
361
|
+
? node.initializer
|
|
362
|
+
: undefined;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** The project's calls, by each declaration their callee resolves to. */
|
|
366
|
+
type CallsOf = ReadonlyMap<string, readonly CallExpression[]>;
|
|
367
|
+
|
|
368
|
+
/** The key of one declaration a symbol names, the same for each symbol the checker makes of it. */
|
|
369
|
+
function keyOf(handle: { readonly path: string; readonly index: number }): string {
|
|
370
|
+
return `${handle.path}#${String(handle.index)}`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Every call of the project by the declarations its callee can be: the function or the
|
|
375
|
+
* method it names, or else each function the checker's type at it holds, wherever that
|
|
376
|
+
* value came from. A name is also followed through what its binding copies
|
|
377
|
+
* ({@link boundTo}), which is how a call reaches a function an annotation erases.
|
|
378
|
+
* Only the target's own declarations are keys, as only its own functions are asked for.
|
|
379
|
+
*/
|
|
380
|
+
function callsByDeclaration<Brand>(project: ProjectView<Brand>): CallsOf {
|
|
381
|
+
const calls = callsOf(project);
|
|
382
|
+
const own = project.ownPaths();
|
|
383
|
+
const callees = resolvedTargets(
|
|
384
|
+
project,
|
|
385
|
+
calls.flatMap((call) => calleeName(call.expression) ?? []),
|
|
386
|
+
);
|
|
387
|
+
const named = new Map<number, readonly string[]>();
|
|
388
|
+
const keysOf = (symbol: TSSymbol, seen: Set<number>): readonly string[] => {
|
|
389
|
+
const known = named.get(symbol.id);
|
|
390
|
+
if (known !== undefined || seen.has(symbol.id)) {
|
|
391
|
+
return known ?? [];
|
|
392
|
+
}
|
|
393
|
+
seen.add(symbol.id);
|
|
394
|
+
const keys = symbol.declarations
|
|
395
|
+
.filter((handle) => own.has(handle.path))
|
|
396
|
+
.flatMap((handle) => {
|
|
397
|
+
const node = project.declarationAt(handle)?.node;
|
|
398
|
+
const bound = node === undefined ? { symbols: [] } : boundTo(project, node);
|
|
399
|
+
const copied = [
|
|
400
|
+
...bound.symbols,
|
|
401
|
+
...(bound.written === undefined ? [] : leavesPassed(bound.written)).flatMap((value) => {
|
|
402
|
+
const { named: next, typed } = heldBy(project, value);
|
|
403
|
+
return [...typed, ...(next === undefined ? [] : [next])];
|
|
404
|
+
}),
|
|
405
|
+
];
|
|
406
|
+
return [keyOf(handle), ...copied.flatMap((next) => keysOf(next, seen))];
|
|
407
|
+
});
|
|
408
|
+
named.set(symbol.id, keys);
|
|
409
|
+
return keys;
|
|
410
|
+
};
|
|
411
|
+
const calleeOf = (call: CallExpression): TSSymbol | undefined => {
|
|
412
|
+
const name = calleeName(call.expression);
|
|
413
|
+
return name === undefined ? undefined : callees.get(name);
|
|
414
|
+
};
|
|
415
|
+
const declared = (call: CallExpression): boolean =>
|
|
416
|
+
((calleeOf(call)?.flags ?? 0) & (SymbolFlags.Function | SymbolFlags.Method)) !== 0;
|
|
417
|
+
const typed = calls.filter((call) => !declared(call));
|
|
418
|
+
const types = new Map(
|
|
419
|
+
project.queries
|
|
420
|
+
.typesAt(typed.map((call) => unwrapped(call.expression)))
|
|
421
|
+
.map((type, index) => [typed[index], must(type)]),
|
|
199
422
|
);
|
|
423
|
+
const found = new Map<string, CallExpression[]>();
|
|
424
|
+
for (const call of calls) {
|
|
425
|
+
const callee = calleeOf(call);
|
|
426
|
+
const held = [
|
|
427
|
+
...(callee === undefined ? [] : [callee]),
|
|
428
|
+
...(types.has(call) ? heldIn(project, types.get(call)) : []),
|
|
429
|
+
];
|
|
430
|
+
for (const key of new Set(held.flatMap((symbol) => keysOf(symbol, new Set())))) {
|
|
431
|
+
found.set(key, [...(found.get(key) ?? []), call]);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return found;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* What each call of the function a parameter belongs to passes at the parameter's place,
|
|
439
|
+
* every argument from there on for a rest parameter. The function is the one the checker's
|
|
440
|
+
* type at its own declaration names, so a function with no name of its own is called too.
|
|
441
|
+
* A `this` parameter takes no place, and an argument at or after a spread argument passes
|
|
442
|
+
* at every place the spread's length can move it to.
|
|
443
|
+
*/
|
|
444
|
+
function argumentsAt<Brand>(
|
|
445
|
+
project: ProjectView<Brand>,
|
|
446
|
+
parameter: ParameterDeclaration,
|
|
447
|
+
callsOfFunction: (symbol: TSSymbol) => readonly CallExpression[],
|
|
448
|
+
): readonly Expression[] {
|
|
449
|
+
const owner = parameter.parent;
|
|
450
|
+
if (
|
|
451
|
+
!isFunctionDeclaration(owner) &&
|
|
452
|
+
!isMethodDeclaration(owner) &&
|
|
453
|
+
!isArrowFunction(owner) &&
|
|
454
|
+
!isFunctionExpression(owner)
|
|
455
|
+
) {
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
const calls = new Set(constructedAt(project, owner).flatMap(callsOfFunction));
|
|
459
|
+
const [first] = owner.parameters;
|
|
460
|
+
const erased = first !== undefined && isIdentifier(first.name) && first.name.text === "this";
|
|
461
|
+
const place = owner.parameters.indexOf(parameter) - (erased ? 1 : 0);
|
|
462
|
+
const rest = parameter.dotDotDotToken !== undefined;
|
|
463
|
+
return [...calls].flatMap((call) => {
|
|
464
|
+
let spreads = 0;
|
|
465
|
+
return call.arguments.flatMap((argument, index) => {
|
|
466
|
+
const spread = isSpreadElement(argument);
|
|
467
|
+
const earliest = index - spreads;
|
|
468
|
+
spreads += spread ? 1 : 0;
|
|
469
|
+
const open = spread || earliest < index;
|
|
470
|
+
const lands = open ? rest || earliest <= place : rest ? index >= place : index === place;
|
|
471
|
+
return lands ? [spread ? argument.expression : argument] : [];
|
|
472
|
+
});
|
|
473
|
+
});
|
|
200
474
|
}
|
|
201
475
|
|
|
202
476
|
/** One class declaration of the target and the decorators written on it. */
|