@cplieger/deadset-ts 8.0.0-dev.20 → 8.0.0-dev.22

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.
Files changed (72) hide show
  1. package/dist/src/alias-chain.js +33 -2
  2. package/dist/src/analysis.js +18 -77
  3. package/dist/src/conformance-record.js +2 -2
  4. package/dist/src/contract/contract.generated.js +5 -4
  5. package/dist/src/dependencies.js +1 -1
  6. package/dist/src/exemptions/argument-places.js +131 -0
  7. package/dist/src/{calls.js → exemptions/calls.js} +126 -179
  8. package/dist/src/{exempt-classes.js → exemptions/classes.js} +1 -1
  9. package/dist/src/{decorator.js → exemptions/decorator.js} +19 -30
  10. package/dist/src/exemptions/detectors.js +48 -0
  11. package/dist/src/{enum-group.js → exemptions/enum-group.js} +7 -7
  12. package/dist/src/{erased-stores.js → exemptions/erased-stores.js} +4 -6
  13. package/dist/src/{framework-lifecycle.js → exemptions/framework-lifecycle.js} +31 -36
  14. package/dist/src/{generated-file.js → exemptions/generated-file.js} +4 -4
  15. package/dist/src/{injection-container.js → exemptions/injection-container.js} +19 -25
  16. package/dist/src/exemptions/installed-globals.js +173 -0
  17. package/dist/src/{interface-satisfaction.js → exemptions/interface-satisfaction.js} +52 -121
  18. package/dist/src/exemptions/lifecycle.js +150 -0
  19. package/dist/src/{reflective-lookup.js → exemptions/reflective-lookup.js} +3 -3
  20. package/dist/src/{serialization-contract.js → exemptions/serialization-contract.js} +415 -329
  21. package/dist/src/exemptions/sites.js +182 -0
  22. package/dist/src/{template-field.js → exemptions/template-field.js} +7 -7
  23. package/dist/src/{value-reach.js → exemptions/value-reach.js} +14 -7
  24. package/dist/src/findings/intra-function.js +50 -37
  25. package/dist/src/index/sites.js +1 -1
  26. package/dist/src/index/values.js +124 -0
  27. package/dist/src/intra-function-parts.js +1149 -222
  28. package/dist/src/liveness/sweep.js +2 -2
  29. package/dist/src/query.js +48 -0
  30. package/dist/src/roots/configured-declarations.js +1 -1
  31. package/dist/src/verbs/print-retained.js +23 -1
  32. package/jsr.json +1 -1
  33. package/package.json +2 -2
  34. package/src/alias-chain.ts +38 -2
  35. package/src/analysis.ts +19 -100
  36. package/src/bindings.ts +1 -1
  37. package/src/conformance-record.ts +2 -2
  38. package/src/contract/contract.generated.ts +5 -4
  39. package/src/dependencies.ts +1 -1
  40. package/src/exemptions/argument-places.ts +198 -0
  41. package/src/{calls.ts → exemptions/calls.ts} +190 -224
  42. package/src/{exempt-classes.ts → exemptions/classes.ts} +1 -1
  43. package/src/exemptions/decorator.ts +56 -0
  44. package/src/exemptions/detectors.ts +75 -0
  45. package/src/{enum-group.ts → exemptions/enum-group.ts} +9 -9
  46. package/src/{erased-stores.ts → exemptions/erased-stores.ts} +5 -8
  47. package/src/{framework-lifecycle.ts → exemptions/framework-lifecycle.ts} +39 -48
  48. package/src/{generated-file.ts → exemptions/generated-file.ts} +6 -6
  49. package/src/{injection-container.ts → exemptions/injection-container.ts} +28 -33
  50. package/src/exemptions/installed-globals.ts +255 -0
  51. package/src/{interface-satisfaction.ts → exemptions/interface-satisfaction.ts} +69 -160
  52. package/src/exemptions/lifecycle.ts +286 -0
  53. package/src/{reflective-lookup.ts → exemptions/reflective-lookup.ts} +5 -5
  54. package/src/{serialization-contract.ts → exemptions/serialization-contract.ts} +563 -429
  55. package/src/exemptions/sites.ts +293 -0
  56. package/src/{template-field.ts → exemptions/template-field.ts} +11 -11
  57. package/src/{value-reach.ts → exemptions/value-reach.ts} +16 -8
  58. package/src/facts.ts +1 -1
  59. package/src/findings/intra-function.ts +59 -37
  60. package/src/index/sites.ts +1 -1
  61. package/src/index/values.ts +170 -0
  62. package/src/intra-function-parts.ts +1435 -247
  63. package/src/liveness/sweep.ts +2 -2
  64. package/src/query.ts +109 -10
  65. package/src/roots/configured-declarations.ts +1 -2
  66. package/src/stores.ts +1 -1
  67. package/src/verbs/print-retained.ts +29 -1
  68. package/dist/src/exempt.js +0 -164
  69. package/dist/src/value-flow.js +0 -74
  70. package/src/decorator.ts +0 -78
  71. package/src/exempt.ts +0 -295
  72. package/src/value-flow.ts +0 -104
@@ -1,29 +1,20 @@
1
1
  /**
2
2
  * The calls and decorators of one project that the configured classes read: what each
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 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.
3
+ * call invokes, a call through a method of `Function.prototype` included, and which of the
4
+ * target's classes a call passes or a decorator is attached to. A class is passed by each
5
+ * value an argument can carry ({@link carriedValues}), named directly or through
6
+ * variables, and a decorator written as a call is a call.
7
7
  */
8
- import { isArrayLiteralExpression, isArrowFunction, isBinaryExpression, isBindingElement, isCallExpression, isClassDeclaration, isConditionalExpression, isDecorator, isFunctionDeclaration, isFunctionExpression, isIdentifier, isMethodDeclaration, isObjectLiteralExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isPropertyDeclaration, isShorthandPropertyAssignment, isOmittedExpression, isSpreadAssignment, isSpreadElement, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
8
+ import { isArrayLiteralExpression, isArrowFunction, isBindingElement, isCallExpression, isDecorator, isFunctionDeclaration, isFunctionExpression, isIdentifier, isMethodDeclaration, isParameterDeclaration, isPropertyAccessExpression, isPropertyAssignment, isPropertyDeclaration, isShorthandPropertyAssignment, isSpreadElement, isVariableDeclaration, } from "@typescript/native/ast";
9
9
  import { ObjectFlags, SymbolFlags, } from "@typescript/native/sync";
10
- import { nodeKey } from "./inventory.js";
11
- import { must } from "./query.js";
12
- import { unwrapped } from "./unwrap.js";
13
- /** The name node a callee or a decorator expression resolves through, where it has one. */
14
- export function calleeName(expression) {
15
- if (isParenthesizedExpression(expression)) {
16
- return calleeName(expression.expression);
17
- }
18
- if (isPropertyAccessExpression(expression)) {
19
- return expression.name;
20
- }
21
- return isIdentifier(expression) ? expression : undefined;
22
- }
10
+ import { resolvedTargets } from "../alias-chain.js";
11
+ import { carriedValues, nameOf } from "../index/values.js";
12
+ import { must } from "../query.js";
13
+ import { parenthesesRemoved, unwrapped } from "../unwrap.js";
23
14
  /** The callee one decorator names: its expression, or the callee of the call it writes. */
24
15
  export function decoratorCallee(decorator) {
25
16
  const expression = decorator.expression;
26
- return calleeName(isCallExpression(expression) ? expression.expression : expression);
17
+ return nameOf(isCallExpression(expression) ? expression.expression : expression);
27
18
  }
28
19
  /** How one decorator is spelled in a record: its expression, without a call's arguments. */
29
20
  export function spelledDecorator(decorator) {
@@ -35,36 +26,90 @@ export function spelledDecorator(decorator) {
35
26
  export function decoratorsOf(node) {
36
27
  return (node.modifiers ?? []).filter(isDecorator);
37
28
  }
29
+ /** The methods of `Function.prototype` that invoke a function with arguments of their own. */
30
+ const FUNCTION_METHODS = new Set(["call", "apply", "bind"]);
31
+ /** The interfaces of the standard library that declare those methods for a function value. */
32
+ const FUNCTION_INTERFACES = new Set([
33
+ "Function",
34
+ "CallableFunction",
35
+ "NewableFunction",
36
+ ]);
38
37
  /**
39
- * What each of a batch of nodes resolves to, every alias followed to the declaration
40
- * it stands for. A node the checker resolves to nothing, and an alias that resolves
41
- * to nothing, are absent from the answer. Each alias is followed once. A question the
42
- * checker does not answer stops the pass, as {@link must} stops.
38
+ * The invocation a call writes through a method of `Function.prototype`, read from
39
+ * the syntax alone: `f.call(self, a)`, `f.apply(self, [a])`, `f.bind(self, a)`, which
40
+ * passes `a` though nothing calls the bound function there, because the bound function
41
+ * carries `a` to wherever it is called and that call is not followed, and
42
+ * `f.bind(self, a)(b)`, which passes `b` after it. An `apply` argument
43
+ * that is no array literal is the value of the first parameter, whose elements it
44
+ * carries. Whether the method is the prototype's is resolution's to say.
43
45
  */
44
- export function resolvedTargets(project, nodes) {
45
- const followed = new Map();
46
- const targets = new Map();
47
- project
48
- .symbolsAt(nodes.map((node) => project.handle(node)))
49
- .map(must)
50
- .forEach((symbol, index) => {
51
- const node = nodes[index];
52
- if (symbol === undefined || node === undefined) {
53
- return;
46
+ function throughMethod(call) {
47
+ const expression = parenthesesRemoved(call.expression);
48
+ if (isPropertyAccessExpression(expression) && FUNCTION_METHODS.has(expression.name.text)) {
49
+ const [, ...rest] = call.arguments;
50
+ let passed = rest;
51
+ let listed = false;
52
+ if (expression.name.text === "apply") {
53
+ const list = rest[0] === undefined ? undefined : parenthesesRemoved(rest[0]);
54
+ listed = list !== undefined && !isArrayLiteralExpression(list);
55
+ passed = list === undefined ? [] : isArrayLiteralExpression(list) ? list.elements : [list];
54
56
  }
55
- let target = symbol;
56
- if ((symbol.flags & SymbolFlags.Alias) !== 0) {
57
- if (!followed.has(symbol.id)) {
58
- const aliased = must(project.queries.aliased(symbol));
59
- followed.set(symbol.id, aliased.declarations.length === 0 ? undefined : aliased);
60
- }
61
- target = followed.get(symbol.id);
57
+ return {
58
+ method: expression.name,
59
+ invocation: { callee: expression.expression, arguments: passed, indirect: true, listed },
60
+ };
61
+ }
62
+ if (isCallExpression(expression)) {
63
+ const bound = parenthesesRemoved(expression.expression);
64
+ if (isPropertyAccessExpression(bound) && bound.name.text === "bind") {
65
+ return {
66
+ method: bound.name,
67
+ invocation: {
68
+ callee: bound.expression,
69
+ arguments: [...expression.arguments.slice(1), ...call.arguments],
70
+ indirect: true,
71
+ listed: false,
72
+ },
73
+ };
62
74
  }
63
- if (target !== undefined) {
64
- targets.set(node, target);
75
+ }
76
+ return undefined;
77
+ }
78
+ /**
79
+ * What each of a project's calls invokes. A call through `call`, `apply` or `bind`
80
+ * invokes the function the method is read from only where the method is the standard
81
+ * library's: the program's own `call` and `Reflect.apply` pass nothing on.
82
+ */
83
+ export function invocations(project, calls) {
84
+ const forms = new Map();
85
+ for (const call of calls) {
86
+ const form = throughMethod(call);
87
+ if (form !== undefined) {
88
+ forms.set(call, form);
65
89
  }
66
- });
67
- return targets;
90
+ }
91
+ const callees = resolvedTargets(project, [
92
+ ...calls.flatMap((call) => nameOf(call.expression) ?? []),
93
+ ...[...forms.values()].flatMap((form) => {
94
+ const name = nameOf(form.invocation.callee);
95
+ return name === undefined ? [form.method] : [form.method, name];
96
+ }),
97
+ ]);
98
+ const standard = (method) => method.declarations.some((handle) => project.program.getSourceFileMetadata(handle.path)?.isDefaultLibrary === true) && FUNCTION_INTERFACES.has(must(project.queries.parentOf(method))?.name ?? "");
99
+ const of = (call) => {
100
+ const form = forms.get(call);
101
+ const method = form === undefined ? undefined : callees.get(form.method);
102
+ return form !== undefined && method !== undefined && standard(method)
103
+ ? form.invocation
104
+ : { callee: call.expression, arguments: call.arguments, indirect: false, listed: false };
105
+ };
106
+ return {
107
+ of,
108
+ callee: (call) => {
109
+ const name = nameOf(of(call).callee);
110
+ return name === undefined ? undefined : callees.get(name);
111
+ },
112
+ };
68
113
  }
69
114
  /** The declaration a shorthand property's value resolves to, an import followed to what it names. */
70
115
  function shorthandTarget(project, node) {
@@ -76,74 +121,7 @@ function shorthandTarget(project, node) {
76
121
  return aliased.declarations.length === 0 ? undefined : aliased;
77
122
  }
78
123
  /**
79
- * The values one expression passes: each value it can be ({@link leavesOf}), or what an
80
- * array or object literal it can be holds, at any depth.
81
- */
82
- function passedIn(written, found) {
83
- for (const expression of leavesOf(written)) {
84
- if (isArrayLiteralExpression(expression)) {
85
- for (const element of expression.elements) {
86
- passedIn(element, found);
87
- }
88
- }
89
- else if (isObjectLiteralExpression(expression)) {
90
- for (const property of expression.properties) {
91
- if (isPropertyAssignment(property)) {
92
- passedIn(property.initializer, found);
93
- }
94
- else if (isSpreadAssignment(property)) {
95
- passedIn(property.expression, found);
96
- }
97
- else if (isShorthandPropertyAssignment(property)) {
98
- found.push({ site: property.name, name: property, shorthand: true });
99
- }
100
- }
101
- }
102
- else if (!isOmittedExpression(expression)) {
103
- found.push({ site: expression, name: calleeName(expression), shorthand: false });
104
- }
105
- }
106
- }
107
- /** The operators whose value is one of their operands. */
108
- const CHOOSING = new Set([
109
- SyntaxKind.QuestionQuestionToken,
110
- SyntaxKind.BarBarToken,
111
- SyntaxKind.AmpersandAmpersandToken,
112
- ]);
113
- /**
114
- * The expressions one value can be: itself, or each branch of a conditional, each operand
115
- * of `??`, `||` and `&&` and the last of a comma, inside any wrapper that changes no value.
116
- * Each is read alone because the checker reduces a union by subtype, so the type at the
117
- * whole drops a class structurally equal to, or a subtype of, another branch's.
118
- */
119
- function leavesOf(written) {
120
- const expression = unwrapped(isSpreadElement(written) ? written.expression : written);
121
- if (isConditionalExpression(expression)) {
122
- return [...leavesOf(expression.whenTrue), ...leavesOf(expression.whenFalse)];
123
- }
124
- if (isBinaryExpression(expression)) {
125
- const operator = expression.operatorToken.kind;
126
- if (CHOOSING.has(operator)) {
127
- return [...leavesOf(expression.left), ...leavesOf(expression.right)];
128
- }
129
- if (operator === SyntaxKind.CommaToken) {
130
- return leavesOf(expression.right);
131
- }
132
- }
133
- return [expression];
134
- }
135
- /** The values one argument passes, a spread argument's included. */
136
- function leavesPassed(argument) {
137
- const found = [];
138
- passedIn(argument, found);
139
- return found;
140
- }
141
- /** The values one argument passes that name their declaration. */
142
- export function valuesPassed(argument) {
143
- return leavesPassed(argument).filter((value) => value.name !== undefined);
144
- }
145
- /**
146
- * What one passed value holds: the declaration its name resolves to, and the classes and
124
+ * What one carried value holds: the declaration its name resolves to, and the classes and
147
125
  * functions the checker's type at it holds ({@link constructedAt}). `known` holds names
148
126
  * already resolved in a batch.
149
127
  */
@@ -160,21 +138,27 @@ function heldBy(project, value, known) {
160
138
  typed: constructedAt(project, value.site),
161
139
  };
162
140
  }
141
+ /** The classes of the target each call passes, by the call, as {@link classesCarried} reads its invocation's arguments. */
142
+ export function classesPassed(project, held, chains, sites, calls) {
143
+ const carried = classesCarried(project, held, chains, sites, calls.map((call) => sites.invocations.of(call).arguments));
144
+ return new Map(calls.map((call, at) => [call, carried[at] ?? []]));
145
+ }
163
146
  /**
164
- * The classes of the target each call passes, by the call: each class a value an argument
165
- * can be holds as a constructor, as the checker types it. A name whose type erases the
166
- * class is followed through what its declaration binds ({@link boundTo}) and, for a
167
- * parameter, through what each call of its function passes at its place, so a class handed
168
- * to a wrapper is passed. A class is used at each value that passes it. A class of another
169
- * program passes nothing.
147
+ * The classes of the target each group of written values carries: each class a value
148
+ * can carry holds as a constructor, as the checker types it. A name whose type erases
149
+ * the class is followed through what its declaration binds ({@link boundTo}) and, for a
150
+ * parameter, through what each call of its function passes at its place, so a class
151
+ * handed to a wrapper is passed. A class is used at each value that carries it. A class
152
+ * of another program is carried by nothing.
170
153
  */
171
- export function classesPassed(project, held, chains, calls) {
172
- const passed = new Map(calls.map((call) => [call, call.arguments.flatMap(leavesPassed)]));
173
- const targets = resolvedTargets(project, [...passed.values()].flat().flatMap((value) => (value.shorthand ? [] : (value.name ?? []))));
154
+ export function classesCarried(project, held, chains, sites, groups) {
155
+ const { invocations: invoked } = sites;
156
+ const passed = groups.map((written) => written.flatMap(carriedValues));
157
+ const targets = resolvedTargets(project, passed.flat().flatMap((value) => (value.shorthand ? [] : (value.name ?? []))));
174
158
  const classes = new Set(held.symbols.filter((symbol) => symbol.kind === "class").map((symbol) => symbol.id));
175
159
  let calling;
176
160
  const callsOfFunction = (symbol) => {
177
- calling ??= callsByDeclaration(project);
161
+ calling ??= callsByDeclaration(project, sites.calls.map((one) => one.call), invoked);
178
162
  return symbol.declarations.flatMap((handle) => calling?.get(keyOf(handle)) ?? []);
179
163
  };
180
164
  const classesOf = (value, seen) => {
@@ -190,14 +174,14 @@ export function classesPassed(project, held, chains, calls) {
190
174
  return ids;
191
175
  }
192
176
  seen.add(symbol.id);
193
- const through = (written) => leavesPassed(written).flatMap((value) => classesOf(value, seen));
177
+ const through = (written) => carriedValues(written).flatMap((value) => classesOf(value, seen));
194
178
  return symbol.declarations.flatMap((handle) => {
195
179
  const node = project.declarationAt(handle)?.node;
196
180
  if (node === undefined) {
197
181
  return [];
198
182
  }
199
183
  if (isParameterDeclaration(node)) {
200
- return argumentsAt(project, node, callsOfFunction).flatMap(through);
184
+ return argumentsAt(project, node, callsOfFunction, invoked).flatMap(through);
201
185
  }
202
186
  const bound = boundTo(project, node);
203
187
  return [
@@ -206,15 +190,15 @@ export function classesPassed(project, held, chains, calls) {
206
190
  ];
207
191
  });
208
192
  };
209
- return new Map([...passed].map(([call, values]) => {
193
+ return passed.map((values) => {
210
194
  const uses = new Map();
211
195
  for (const value of values) {
212
196
  for (const id of classesOf(value, new Set())) {
213
197
  uses.set(`${id}\u0000${String(value.site.pos)}`, { id, site: value.site });
214
198
  }
215
199
  }
216
- return [call, [...uses.values()]];
217
- }));
200
+ return [...uses.values()];
201
+ });
218
202
  }
219
203
  /**
220
204
  * The symbols of the classes and functions the checker's type at one expression holds as
@@ -269,16 +253,14 @@ function keyOf(handle) {
269
253
  return `${handle.path}#${String(handle.index)}`;
270
254
  }
271
255
  /**
272
- * Every call of the project by the declarations its callee can be: the function or the
273
- * method it names, or else each function the checker's type at it holds, wherever that
274
- * value came from. A name is also followed through what its binding copies
256
+ * Every call of the project by the declarations its invoked function can be: the function
257
+ * or the method it names, or else each function the checker's type at it holds, wherever
258
+ * that value came from. A name is also followed through what its binding copies
275
259
  * ({@link boundTo}), which is how a call reaches a function an annotation erases.
276
260
  * Only the target's own declarations are keys, as only its own functions are asked for.
277
261
  */
278
- function callsByDeclaration(project) {
279
- const calls = callsOf(project);
262
+ function callsByDeclaration(project, calls, invoked) {
280
263
  const own = project.ownPaths();
281
- const callees = resolvedTargets(project, calls.flatMap((call) => calleeName(call.expression) ?? []));
282
264
  const named = new Map();
283
265
  const keysOf = (symbol, seen) => {
284
266
  const known = named.get(symbol.id);
@@ -293,7 +275,7 @@ function callsByDeclaration(project) {
293
275
  const bound = node === undefined ? { symbols: [] } : boundTo(project, node);
294
276
  const copied = [
295
277
  ...bound.symbols,
296
- ...(bound.written === undefined ? [] : leavesPassed(bound.written)).flatMap((value) => {
278
+ ...(bound.written === undefined ? [] : carriedValues(bound.written)).flatMap((value) => {
297
279
  const { named: next, typed } = heldBy(project, value);
298
280
  return [...typed, ...(next === undefined ? [] : [next])];
299
281
  }),
@@ -303,18 +285,14 @@ function callsByDeclaration(project) {
303
285
  named.set(symbol.id, keys);
304
286
  return keys;
305
287
  };
306
- const calleeOf = (call) => {
307
- const name = calleeName(call.expression);
308
- return name === undefined ? undefined : callees.get(name);
309
- };
310
- const declared = (call) => ((calleeOf(call)?.flags ?? 0) & (SymbolFlags.Function | SymbolFlags.Method)) !== 0;
288
+ const declared = (call) => ((invoked.callee(call)?.flags ?? 0) & (SymbolFlags.Function | SymbolFlags.Method)) !== 0;
311
289
  const typed = calls.filter((call) => !declared(call));
312
290
  const types = new Map(project.queries
313
- .typesAt(typed.map((call) => unwrapped(call.expression)))
291
+ .typesAt(typed.map((call) => unwrapped(invoked.of(call).callee)))
314
292
  .map((type, index) => [typed[index], must(type)]));
315
293
  const found = new Map();
316
294
  for (const call of calls) {
317
- const callee = calleeOf(call);
295
+ const callee = invoked.callee(call);
318
296
  const held = [
319
297
  ...(callee === undefined ? [] : [callee]),
320
298
  ...(types.has(call) ? heldIn(project, types.get(call)) : []),
@@ -327,12 +305,13 @@ function callsByDeclaration(project) {
327
305
  }
328
306
  /**
329
307
  * What each call of the function a parameter belongs to passes at the parameter's place,
330
- * every argument from there on for a rest parameter. The function is the one the checker's
331
- * type at its own declaration names, so a function with no name of its own is called too.
332
- * A `this` parameter takes no place, and an argument at or after a spread argument passes
333
- * at every place the spread's length can move it to.
308
+ * every argument from there on for a rest parameter, as the call's invocation binds them.
309
+ * The function is the one the checker's type at its own declaration names, so a function
310
+ * with no name of its own is called too. A `this` parameter takes no place, and an
311
+ * argument at or after a spread argument passes at every place the spread's length can
312
+ * move it to.
334
313
  */
335
- function argumentsAt(project, parameter, callsOfFunction) {
314
+ function argumentsAt(project, parameter, callsOfFunction, invoked) {
336
315
  const owner = parameter.parent;
337
316
  if (!isFunctionDeclaration(owner) &&
338
317
  !isMethodDeclaration(owner) &&
@@ -347,7 +326,7 @@ function argumentsAt(project, parameter, callsOfFunction) {
347
326
  const rest = parameter.dotDotDotToken !== undefined;
348
327
  return [...calls].flatMap((call) => {
349
328
  let spreads = 0;
350
- return call.arguments.flatMap((argument, index) => {
329
+ return invoked.of(call).arguments.flatMap((argument, index) => {
351
330
  const spread = isSpreadElement(argument);
352
331
  const earliest = index - spreads;
353
332
  spreads += spread ? 1 : 0;
@@ -357,35 +336,3 @@ function argumentsAt(project, parameter, callsOfFunction) {
357
336
  });
358
337
  });
359
338
  }
360
- /** Every class declaration of the target that carries a decorator. */
361
- export function decoratedClasses(project, held) {
362
- const found = [];
363
- for (const file of project.ownSourceFiles()) {
364
- const visit = (node) => {
365
- if (isClassDeclaration(node)) {
366
- const id = held.declarations.get(nodeKey(file, node));
367
- const decorators = decoratorsOf(node);
368
- if (id !== undefined && decorators.length > 0) {
369
- found.push({ id, decorators });
370
- }
371
- }
372
- node.forEachChild(visit);
373
- };
374
- file.forEachChild(visit);
375
- }
376
- return found;
377
- }
378
- /** Every call expression of the project's own files, decorators written as calls included. */
379
- export function callsOf(project) {
380
- const found = [];
381
- for (const file of project.ownSourceFiles()) {
382
- const visit = (node) => {
383
- if (isCallExpression(node)) {
384
- found.push(node);
385
- }
386
- node.forEachChild(visit);
387
- };
388
- file.forEachChild(visit);
389
- }
390
- return found;
391
- }
@@ -1,4 +1,4 @@
1
- import { EXEMPTION_CLASSES } from "./contract/contract.generated.js";
1
+ import { EXEMPTION_CLASSES } from "../contract/contract.generated.js";
2
2
  function runsOnTS(row) {
3
3
  const languages = row.languages;
4
4
  return languages.includes("ts");
@@ -7,8 +7,8 @@
7
7
  * static members of a decorated class included.
8
8
  */
9
9
  import { decoratorsOf, spelledDecorator } from "./calls.js";
10
- import { isParameterProperty, nodeKey, } from "./inventory.js";
11
- import { renderPosition } from "./position.js";
10
+ import { isParameterProperty } from "../inventory.js";
11
+ import { renderPosition } from "../position.js";
12
12
  /** The kinds of declaration a decorator is attached to and holds back. */
13
13
  const DECORATED = new Set(["class", "method", "class-member"]);
14
14
  /**
@@ -16,7 +16,7 @@ const DECORATED = new Set(["class", "method", "class-member"]);
16
16
  * class, recorded at the decorator.
17
17
  */
18
18
  export function decorator(input) {
19
- const { project, held, targetRoot } = input;
19
+ const { held, sites, targetRoot } = input;
20
20
  const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
21
21
  const membersOf = new Map();
22
22
  for (const symbol of held.symbols) {
@@ -30,32 +30,21 @@ export function decorator(input) {
30
30
  }
31
31
  }
32
32
  }
33
- const decorated = [];
34
33
  // A decorated class's container passes each constructor argument, which keeps the
35
- // parameter, not the member a parameter property stores it in.
36
- const parameterMembers = new Set();
37
- for (const file of project.ownSourceFiles()) {
38
- const visit = (node) => {
39
- const id = held.declarations.get(nodeKey(file, node));
40
- const symbol = id === undefined ? undefined : byId.get(id);
41
- if (id !== undefined && isParameterProperty(node)) {
42
- parameterMembers.add(id);
43
- }
44
- // A parameter property's decorator is attached to its parameter, and to no member.
45
- if (symbol !== undefined && DECORATED.has(symbol.kind) && !isParameterProperty(node)) {
46
- for (const attached of decoratorsOf(node)) {
47
- decorated.push({
48
- symbol,
49
- detail: `decorated by ${spelledDecorator(attached)}`,
50
- site: renderPosition(file, targetRoot, attached.getStart()),
51
- });
52
- }
53
- }
54
- node.forEachChild(visit);
55
- };
56
- file.forEachChild(visit);
57
- }
58
- return decorated.flatMap(({ symbol, detail, site }) => (symbol.kind === "class"
59
- ? (membersOf.get(symbol.id) ?? []).filter((member) => !parameterMembers.has(member.id))
60
- : [symbol]).map((member) => ({ id: member.id, detail, site })));
34
+ // parameter, not the member a parameter property stores it in. A parameter property's
35
+ // decorator is attached to its parameter, and to no member.
36
+ return sites.decorated.flatMap(({ id, node }) => {
37
+ const symbol = byId.get(id);
38
+ if (symbol === undefined || !DECORATED.has(symbol.kind) || isParameterProperty(node)) {
39
+ return [];
40
+ }
41
+ const members = symbol.kind === "class"
42
+ ? (membersOf.get(symbol.id) ?? []).filter((member) => !sites.parameterProperties.has(member.id))
43
+ : [symbol];
44
+ return decoratorsOf(node).flatMap((attached) => {
45
+ const detail = `decorated by ${spelledDecorator(attached)}`;
46
+ const site = renderPosition(node.getSourceFile(), targetRoot, attached.getStart());
47
+ return members.map((member) => ({ id: member.id, detail, site }));
48
+ });
49
+ });
61
50
  }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The exemption classes this analyzer detects, each by its detector, and what their
3
+ * detection reads of the run once for every project. A class without a detector here
4
+ * retains nothing.
5
+ */
6
+ import { decorator } from "./decorator.js";
7
+ import { enumGroup } from "./enum-group.js";
8
+ import { frameworkLifecycle } from "./framework-lifecycle.js";
9
+ import { generatedFile } from "./generated-file.js";
10
+ import { injectionContainer } from "./injection-container.js";
11
+ import { interfaceSatisfaction } from "./interface-satisfaction.js";
12
+ import { detect } from "./lifecycle.js";
13
+ import { reflectiveLookup } from "./reflective-lookup.js";
14
+ import { serializationContract } from "./serialization-contract.js";
15
+ import { readTemplates, templateField } from "./template-field.js";
16
+ export const DETECTORS = new Map([
17
+ ["interface-satisfaction", interfaceSatisfaction],
18
+ ["enum-group", enumGroup],
19
+ ["generated-file", generatedFile],
20
+ ["template-field", templateField],
21
+ ["reflective-lookup", reflectiveLookup],
22
+ ["decorator", decorator],
23
+ ["injection-container", injectionContainer],
24
+ ["framework-lifecycle", frameworkLifecycle],
25
+ ["serialization-contract", serializationContract],
26
+ ]);
27
+ /**
28
+ * The detection of one run: what every project's detectors read of the run, read once,
29
+ * and a function that runs them over one project. A switched-off class reads nothing,
30
+ * its templates included, and generated files are held only where the configuration
31
+ * leaves them out of the analysis.
32
+ */
33
+ export function detection(host, targetRoot, config, consumers, detectors) {
34
+ const disabled = new Set(config.exemptions.disabled);
35
+ const templates = disabled.has("template-field")
36
+ ? { delimiters: config.analysis.template_delimiters, files: [] }
37
+ : readTemplates(host, targetRoot, config.analysis);
38
+ const included = config.analysis.generated_files === "include";
39
+ return ({ project, held, generated }) => detect({
40
+ project,
41
+ held,
42
+ targetRoot,
43
+ templates,
44
+ ts: config.ts,
45
+ consumers,
46
+ ...(included ? {} : { generated }),
47
+ }, detectors, disabled);
48
+ }
@@ -7,16 +7,16 @@
7
7
  * function it annotates, where a number counts too. An enum's own value converts nothing.
8
8
  */
9
9
  import { isArrowFunction, isAsExpression, isElementAccessExpression, isEnumDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isParenthesizedTypeNode, isPropertyAccessExpression, isReturnStatement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
10
- import { parenthesesRemoved } from "./unwrap.js";
10
+ import { parenthesesRemoved } from "../unwrap.js";
11
11
  import { TypeFlags } from "@typescript/native/sync";
12
- import { aliasChains } from "./alias-chain.js";
13
- import { nodeKey } from "./inventory.js";
14
- import { renderPosition } from "./position.js";
15
- import { isAnswered, UNANSWERED } from "./query.js";
12
+ import { aliasChains } from "../alias-chain.js";
13
+ import { nodeKey } from "../inventory.js";
14
+ import { renderPosition } from "../position.js";
15
+ import { isAnswered, UNANSWERED } from "../query.js";
16
+ import { ERASED } from "./value-reach.js";
16
17
  /** The operand types a conversion produces an enum value from. */
17
18
  const NUMBER = TypeFlags.NumberLike;
18
19
  const STRING = TypeFlags.StringLike;
19
- const DECODED = TypeFlags.Any | TypeFlags.Unknown;
20
20
  /**
21
21
  * The name node a type node names its declaration by: the identifier of a type
22
22
  * reference, the right side of a qualified one, through parentheses. A type that is no
@@ -128,7 +128,7 @@ function detailOf(project, site, enumName, operand) {
128
128
  if (parts.some((part) => (part.flags & STRING) !== 0)) {
129
129
  return site.form === "assertion" ? "converted from string" : undefined;
130
130
  }
131
- if (parts.some((part) => (part.flags & DECODED) !== 0)) {
131
+ if (parts.some((part) => (part.flags & ERASED) !== 0)) {
132
132
  return `decoded as ${enumName}`;
133
133
  }
134
134
  return undefined;
@@ -7,12 +7,10 @@
7
7
  * declare hands the value to the code that declares it.
8
8
  */
9
9
  import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isElementAccessExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadElement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
10
- import { SymbolFlags, TypeFlags } from "@typescript/native/sync";
11
- import { must } from "./query.js";
12
- import { checkedTypes } from "./value-reach.js";
13
- import { parenthesesRemoved } from "./unwrap.js";
14
- /** The types that keep nothing of the value they are given. */
15
- const ERASED = TypeFlags.Any | TypeFlags.Unknown;
10
+ import { SymbolFlags } from "@typescript/native/sync";
11
+ import { must } from "../query.js";
12
+ import { checkedTypes, ERASED } from "./value-reach.js";
13
+ import { parenthesesRemoved } from "../unwrap.js";
16
14
  /** The array methods whose arguments become elements of the array they are called on. */
17
15
  const ELEMENT_STORES = new Set(["push", "unshift"]);
18
16
  /** The name an element access's literal index spells, or none. */