@cplieger/deadset-ts 3.4.0 → 3.5.0
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 +1 -1
- package/dist/src/components.js +61 -106
- package/dist/src/config.js +6 -0
- package/dist/src/kinds.js +40 -0
- package/dist/src/print.js +10 -1
- package/dist/src/resolve.js +90 -9
- package/dist/src/run.js +9 -3
- package/dist/src/schema.js +37 -1
- package/dist/src/scope.js +55 -21
- package/dist/src/serialization-contract.js +109 -19
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/components.ts +67 -130
- package/src/config.ts +37 -2
- package/src/kinds.ts +41 -0
- package/src/print.ts +17 -1
- package/src/resolve.ts +123 -9
- package/src/run.ts +10 -3
- package/src/schema.ts +47 -2
- package/src/scope.ts +63 -22
- package/src/serialization-contract.ts +138 -20
- package/src/version.ts +1 -1
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
* `JSON.stringify` or a call `ts.serializers` names, and those and `toJSON` and
|
|
4
4
|
* `toString` where a value is passed to an `unknown` or `any` parameter of a function
|
|
5
5
|
* outside the analysis. A function of the project handing such a parameter of its own
|
|
6
|
-
* to a destination is one, holding back what that destination does, to a fixpoint.
|
|
6
|
+
* to a destination is one, holding back what that destination does, to a fixpoint. A
|
|
7
|
+
* function invoked through `Function.prototype.call`, `apply` or `bind` is the callee,
|
|
8
|
+
* given the arguments those methods pass it.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import {
|
|
12
|
+
isArrayLiteralExpression,
|
|
10
13
|
isArrowFunction,
|
|
14
|
+
isCallExpression,
|
|
11
15
|
isClassDeclaration,
|
|
12
16
|
isClassExpression,
|
|
13
17
|
isClassStaticBlockDeclaration,
|
|
@@ -20,6 +24,7 @@ import {
|
|
|
20
24
|
isNumericLiteral,
|
|
21
25
|
isObjectLiteralExpression,
|
|
22
26
|
isParenthesizedExpression,
|
|
27
|
+
isPropertyAccessExpression,
|
|
23
28
|
isSetAccessorDeclaration,
|
|
24
29
|
isSpreadElement,
|
|
25
30
|
isStringLiteral,
|
|
@@ -32,6 +37,7 @@ import {
|
|
|
32
37
|
type ParameterDeclaration,
|
|
33
38
|
} from "@typescript/native/unstable/ast";
|
|
34
39
|
import {
|
|
40
|
+
SignatureKind,
|
|
35
41
|
SymbolFlags,
|
|
36
42
|
TypeFlags,
|
|
37
43
|
type Symbol as TSSymbol,
|
|
@@ -164,6 +170,76 @@ function evaluated(argument: Expression): Expression {
|
|
|
164
170
|
return isParenthesizedExpression(value) ? evaluated(value.expression) : value;
|
|
165
171
|
}
|
|
166
172
|
|
|
173
|
+
/** The methods of `Function.prototype` that invoke a function with arguments of their own. */
|
|
174
|
+
const FUNCTION_METHODS: ReadonlySet<string> = new Set(["call", "apply", "bind"]);
|
|
175
|
+
|
|
176
|
+
/** The interfaces of the standard library that declare those methods for a function value. */
|
|
177
|
+
const FUNCTION_INTERFACES: ReadonlySet<string> = new Set([
|
|
178
|
+
"Function",
|
|
179
|
+
"CallableFunction",
|
|
180
|
+
"NewableFunction",
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* One call as the function it invokes sees it: the expression naming that function,
|
|
185
|
+
* and the argument each of its parameters binds, in order.
|
|
186
|
+
*/
|
|
187
|
+
interface Invocation {
|
|
188
|
+
readonly callee: Expression;
|
|
189
|
+
readonly arguments: readonly Expression[];
|
|
190
|
+
/** Whether the call reaches the callee through a method of `Function.prototype`. */
|
|
191
|
+
readonly indirect: boolean;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** A call through a method of `Function.prototype`: the method's name and the invocation. */
|
|
195
|
+
interface MethodForm {
|
|
196
|
+
readonly method: Node;
|
|
197
|
+
readonly invocation: Invocation;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function unparenthesized(expression: Expression): Expression {
|
|
201
|
+
return isParenthesizedExpression(expression)
|
|
202
|
+
? unparenthesized(expression.expression)
|
|
203
|
+
: expression;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The invocation a call writes through a method of `Function.prototype`, read from
|
|
208
|
+
* the syntax alone: `f.call(self, a)`, `f.apply(self, [a])`, `f.bind(self, a)`, which
|
|
209
|
+
* binds `a`, and `f.bind(self, a)(b)`, which passes `b` after it. An `apply` argument
|
|
210
|
+
* that is no array literal is the value of the first parameter, whose elements it
|
|
211
|
+
* carries. Whether the method is the prototype's is resolution's to say.
|
|
212
|
+
*/
|
|
213
|
+
function throughMethod(call: CallExpression): MethodForm | undefined {
|
|
214
|
+
const expression = unparenthesized(call.expression);
|
|
215
|
+
if (isPropertyAccessExpression(expression) && FUNCTION_METHODS.has(expression.name.text)) {
|
|
216
|
+
const [, ...rest] = call.arguments;
|
|
217
|
+
let passed: readonly Expression[] = rest;
|
|
218
|
+
if (expression.name.text === "apply") {
|
|
219
|
+
const list = rest[0] === undefined ? undefined : unparenthesized(rest[0]);
|
|
220
|
+
passed = list === undefined ? [] : isArrayLiteralExpression(list) ? list.elements : [list];
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
method: expression.name,
|
|
224
|
+
invocation: { callee: expression.expression, arguments: passed, indirect: true },
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (isCallExpression(expression)) {
|
|
228
|
+
const bound = unparenthesized(expression.expression);
|
|
229
|
+
if (isPropertyAccessExpression(bound) && bound.name.text === "bind") {
|
|
230
|
+
return {
|
|
231
|
+
method: bound.name,
|
|
232
|
+
invocation: {
|
|
233
|
+
callee: bound.expression,
|
|
234
|
+
arguments: [...expression.arguments.slice(1), ...call.arguments],
|
|
235
|
+
indirect: true,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
|
|
167
243
|
/** What one walk of the project's own files found: its calls and its functions' parameters. */
|
|
168
244
|
interface Walked {
|
|
169
245
|
readonly sites: readonly Site[];
|
|
@@ -256,10 +332,11 @@ function handedOn<Brand>(
|
|
|
256
332
|
project: ProjectView<Brand>,
|
|
257
333
|
sites: readonly Site[],
|
|
258
334
|
erased: ReadonlyMap<number, Parameter>,
|
|
335
|
+
invocationOf: (call: CallExpression) => Invocation,
|
|
259
336
|
): readonly Forwarded[] {
|
|
260
337
|
const spellings = new Set([...erased.values()].map((parameter) => parameter.name.getText()));
|
|
261
338
|
const candidates = sites.flatMap((site) =>
|
|
262
|
-
site.call.arguments.flatMap((argument, index) =>
|
|
339
|
+
invocationOf(site.call).arguments.flatMap((argument, index) =>
|
|
263
340
|
valuesPassed(argument)
|
|
264
341
|
.filter((value) => spellings.has(value.name.getText()))
|
|
265
342
|
.map((value) => ({ site, argument: index, value })),
|
|
@@ -279,6 +356,8 @@ function handedOn<Brand>(
|
|
|
279
356
|
|
|
280
357
|
/** The destination of each call's arguments, with the project's forwarding functions settled. */
|
|
281
358
|
interface Destinations {
|
|
359
|
+
/** The function one call invokes and the arguments its parameters bind. */
|
|
360
|
+
invocation(call: CallExpression): Invocation;
|
|
282
361
|
/** The destination one argument of one call is, if it is one. */
|
|
283
362
|
at(call: CallExpression, argument: number): Destination | undefined;
|
|
284
363
|
/** Whether a call can be a destination at any argument, read without a round trip. */
|
|
@@ -304,14 +383,20 @@ function destinations<Brand>(
|
|
|
304
383
|
const named = resolveEntries(project, held, [JSON_STRINGIFY, ...input.ts.serializers]).filter(
|
|
305
384
|
namesADeclaration,
|
|
306
385
|
);
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
386
|
+
const forms = new Map<CallExpression, MethodForm>();
|
|
387
|
+
for (const site of walked.sites) {
|
|
388
|
+
const form = throughMethod(site.call);
|
|
389
|
+
if (form !== undefined) {
|
|
390
|
+
forms.set(site.call, form);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const callees = resolvedTargets(project, [
|
|
394
|
+
...walked.sites.flatMap((site) => calleeName(site.call.expression) ?? []),
|
|
395
|
+
...[...forms.values()].flatMap((form) => {
|
|
396
|
+
const name = calleeName(form.invocation.callee);
|
|
397
|
+
return name === undefined ? [form.method] : [form.method, name];
|
|
398
|
+
}),
|
|
399
|
+
]);
|
|
315
400
|
const erased = erasedParameters(project, walked.parameters);
|
|
316
401
|
const byFunction = new Map<string, Parameter[]>();
|
|
317
402
|
for (const parameter of erased.values()) {
|
|
@@ -324,17 +409,47 @@ function destinations<Brand>(
|
|
|
324
409
|
!own.has(handle.path) &&
|
|
325
410
|
!input.consumers.some((root) => handle.path === root || handle.path.startsWith(`${root}/`)),
|
|
326
411
|
);
|
|
412
|
+
const invocation = (call: CallExpression): Invocation => {
|
|
413
|
+
const form = forms.get(call);
|
|
414
|
+
const method = form === undefined ? undefined : callees.get(form.method);
|
|
415
|
+
// Only the standard library's function methods pass their arguments on: the
|
|
416
|
+
// program's own `call` and `Reflect.apply` invoke nothing here.
|
|
417
|
+
if (
|
|
418
|
+
form !== undefined &&
|
|
419
|
+
method !== undefined &&
|
|
420
|
+
outside(method) &&
|
|
421
|
+
FUNCTION_INTERFACES.has(method.getParent()?.name ?? "")
|
|
422
|
+
) {
|
|
423
|
+
return form.invocation;
|
|
424
|
+
}
|
|
425
|
+
return { callee: call.expression, arguments: call.arguments, indirect: false };
|
|
426
|
+
};
|
|
427
|
+
const calleeOf = (call: CallExpression): TSSymbol | undefined => {
|
|
428
|
+
const name = calleeName(invocation(call).callee);
|
|
429
|
+
return name === undefined ? undefined : callees.get(name);
|
|
430
|
+
};
|
|
327
431
|
const erasedArguments = new Map<CallExpression, ReadonlySet<number>>();
|
|
328
432
|
const erasedAt = (call: CallExpression, argument: number): boolean => {
|
|
329
433
|
let indexes = erasedArguments.get(call);
|
|
330
434
|
if (indexes === undefined) {
|
|
331
|
-
const
|
|
435
|
+
const invoked = invocation(call);
|
|
436
|
+
// Through a method of the prototype the resolved signature is the method's, so
|
|
437
|
+
// the parameters are read off the callee's own signatures, any one erasing.
|
|
438
|
+
const type = invoked.indirect ? checker.getTypeAtLocation(invoked.callee) : undefined;
|
|
439
|
+
const signatures = invoked.indirect
|
|
440
|
+
? type === undefined
|
|
441
|
+
? []
|
|
442
|
+
: checker.getSignaturesOfType(type, SignatureKind.Call)
|
|
443
|
+
: [checker.getResolvedSignature(call)].filter((one) => one !== undefined);
|
|
332
444
|
indexes = new Set(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
445
|
+
invoked.arguments.flatMap((_unused, index) =>
|
|
446
|
+
signatures.some((signature) => {
|
|
447
|
+
const parameter = checker.getParameterType(signature, index);
|
|
448
|
+
return parameter !== undefined && (parameter.flags & ERASED) !== 0;
|
|
449
|
+
})
|
|
450
|
+
? [index]
|
|
451
|
+
: [],
|
|
452
|
+
),
|
|
338
453
|
);
|
|
339
454
|
erasedArguments.set(call, indexes);
|
|
340
455
|
}
|
|
@@ -355,7 +470,10 @@ function destinations<Brand>(
|
|
|
355
470
|
const declared = chains.declarationsOf(target);
|
|
356
471
|
if (declared.length === 0) {
|
|
357
472
|
return outside(target) && erasedAt(call, argument)
|
|
358
|
-
? {
|
|
473
|
+
? {
|
|
474
|
+
reach: "conversions",
|
|
475
|
+
callee: invocation(call).callee.getText().replace(/\s+/gu, ""),
|
|
476
|
+
}
|
|
359
477
|
: undefined;
|
|
360
478
|
}
|
|
361
479
|
for (const fn of declared) {
|
|
@@ -370,7 +488,7 @@ function destinations<Brand>(
|
|
|
370
488
|
return undefined;
|
|
371
489
|
};
|
|
372
490
|
|
|
373
|
-
const forwarded = handedOn(project, walked.sites, erased);
|
|
491
|
+
const forwarded = handedOn(project, walked.sites, erased, invocation);
|
|
374
492
|
for (let moved = true; moved;) {
|
|
375
493
|
moved = false;
|
|
376
494
|
for (const one of forwarded) {
|
|
@@ -400,7 +518,7 @@ function destinations<Brand>(
|
|
|
400
518
|
(byFunction.get(fn) ?? []).some((parameter) => forwards.has(keyOf(parameter))),
|
|
401
519
|
);
|
|
402
520
|
};
|
|
403
|
-
return { at, mayReceive };
|
|
521
|
+
return { invocation, at, mayReceive };
|
|
404
522
|
}
|
|
405
523
|
|
|
406
524
|
/** One argument whose value carries classes of the target into a destination. */
|
|
@@ -420,7 +538,7 @@ function flowsOf<Brand>(
|
|
|
420
538
|
const reach = valueReach(project, held);
|
|
421
539
|
const arguments_ = sites.flatMap((site) =>
|
|
422
540
|
to.mayReceive(site.call)
|
|
423
|
-
? site.call.arguments.flatMap((argument, index) => {
|
|
541
|
+
? to.invocation(site.call).arguments.flatMap((argument, index) => {
|
|
424
542
|
const value = evaluated(argument);
|
|
425
543
|
return holdsNoInstance(value) ? [] : [{ site, argument: index, value }];
|
|
426
544
|
})
|
package/src/version.ts
CHANGED