@cplieger/deadset-ts 8.0.0-dev.16 → 8.0.0-dev.18

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 (161) hide show
  1. package/dist/src/alias-chain.js +0 -5
  2. package/dist/src/amd.js +81 -0
  3. package/dist/src/analysis.js +82 -91
  4. package/dist/src/augmentations.js +24 -25
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +279 -43
  7. package/dist/src/commands/tools.js +5 -9
  8. package/dist/src/component-files.js +3 -2
  9. package/dist/src/conformance-record.js +1 -1
  10. package/dist/src/consumers.js +6 -9
  11. package/dist/src/contextual-uses.js +1 -17
  12. package/dist/src/contract/config.generated.js +4 -1
  13. package/dist/src/contract/contract.generated.js +2 -2
  14. package/dist/src/contract/report.generated.js +6 -2
  15. package/dist/src/dependencies.js +25 -29
  16. package/dist/src/destructuring.js +1 -19
  17. package/dist/src/element-access.js +179 -164
  18. package/dist/src/entries/build-scripts.js +2 -2
  19. package/dist/src/entries/configuration-files.js +3 -7
  20. package/dist/src/entries/conventions.js +2 -2
  21. package/dist/src/entries/html-entries.js +7 -10
  22. package/dist/src/entries/mdx-entries.js +2 -2
  23. package/dist/src/entries/static-module.js +2 -1
  24. package/dist/src/exempt.js +0 -26
  25. package/dist/src/exposure.js +400 -0
  26. package/dist/src/facts.js +4 -4
  27. package/dist/src/findings/completion.js +44 -21
  28. package/dist/src/findings/emitters.js +22 -33
  29. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  30. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  31. package/dist/src/findings/intra-function.js +3 -3
  32. package/dist/src/findings/reads-and-writes.js +71 -15
  33. package/dist/src/findings/unused-declarations.js +2 -22
  34. package/dist/src/findings/visibility-narrowing.js +14 -20
  35. package/dist/src/framework-lifecycle.js +41 -14
  36. package/dist/src/import-bindings.js +73 -28
  37. package/dist/src/index/sites.js +557 -0
  38. package/dist/src/indexed-access.js +523 -478
  39. package/dist/src/interface-satisfaction.js +1 -1
  40. package/dist/src/inventory.js +18 -0
  41. package/dist/src/keyof-comparisons.js +124 -103
  42. package/dist/src/{components.js → liveness/components.js} +1 -1
  43. package/dist/src/{graph.js → liveness/graph.js} +45 -20
  44. package/dist/src/liveness/matrix.js +99 -0
  45. package/dist/src/liveness/sweep.js +558 -0
  46. package/dist/src/load/expected-module.js +2 -2
  47. package/dist/src/load/specifiers.js +2 -4
  48. package/dist/src/load/synthesize.js +5 -0
  49. package/dist/src/load/workspace-resolution.js +2 -2
  50. package/dist/src/module-declarations.js +70 -85
  51. package/dist/src/overrides.js +134 -112
  52. package/dist/src/paths.js +46 -7
  53. package/dist/src/platform-calls.js +2 -2
  54. package/dist/src/platform-symbols.js +13 -19
  55. package/dist/src/query.js +46 -0
  56. package/dist/src/references/read-rule.js +1 -0
  57. package/dist/src/references/read-rules.js +48 -0
  58. package/dist/src/references/sink.js +237 -0
  59. package/dist/src/references/sources.js +450 -0
  60. package/dist/src/references.js +110 -1285
  61. package/dist/src/roots/configured-declarations.js +3 -0
  62. package/dist/src/roots/published-reach.js +4 -4
  63. package/dist/src/roots/root-set.js +1 -1
  64. package/dist/src/roots/unseen-loads.js +38 -10
  65. package/dist/src/serialization-contract.js +8 -3
  66. package/dist/src/stores.js +4 -2
  67. package/dist/src/symbol-keys.js +89 -83
  68. package/dist/src/tests/file-class.js +76 -0
  69. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  70. package/dist/src/{test-support.js → tests/support.js} +2 -11
  71. package/dist/src/type-parameter-reads.js +178 -139
  72. package/dist/src/unimplemented-callees.js +262 -242
  73. package/dist/src/verbs/analyze.js +3 -3
  74. package/dist/src/verbs/explain.js +75 -132
  75. package/dist/src/verbs/print-retained.js +3 -3
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/alias-chain.ts +0 -7
  79. package/src/amd.ts +130 -0
  80. package/src/analysis.ts +125 -128
  81. package/src/augmentations.ts +42 -28
  82. package/src/bindings.ts +460 -0
  83. package/src/calls.ts +338 -46
  84. package/src/commands/tools.ts +5 -11
  85. package/src/component-files.ts +4 -2
  86. package/src/conformance-record.ts +1 -1
  87. package/src/consumers.ts +8 -11
  88. package/src/contextual-uses.ts +2 -22
  89. package/src/contract/config.generated.ts +4 -1
  90. package/src/contract/contract.generated.ts +2 -2
  91. package/src/contract/report.generated.ts +6 -2
  92. package/src/dependencies.ts +27 -28
  93. package/src/destructuring.ts +1 -31
  94. package/src/element-access.ts +188 -182
  95. package/src/entries/build-scripts.ts +2 -2
  96. package/src/entries/configuration-files.ts +11 -8
  97. package/src/entries/conventions.ts +2 -1
  98. package/src/entries/html-entries.ts +15 -14
  99. package/src/entries/mdx-entries.ts +2 -2
  100. package/src/entries/static-module.ts +2 -1
  101. package/src/exempt.ts +31 -46
  102. package/src/exposure.ts +492 -0
  103. package/src/facts.ts +5 -3
  104. package/src/finding.ts +1 -1
  105. package/src/findings/boundary.ts +9 -0
  106. package/src/findings/completion.ts +48 -23
  107. package/src/findings/emitter.ts +1 -1
  108. package/src/findings/emitters.ts +23 -41
  109. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  110. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  111. package/src/findings/interfaces.ts +3 -3
  112. package/src/findings/intra-function.ts +3 -3
  113. package/src/findings/reads-and-writes.ts +99 -26
  114. package/src/findings/unused-declarations.ts +4 -28
  115. package/src/findings/visibility-narrowing.ts +25 -26
  116. package/src/framework-lifecycle.ts +42 -14
  117. package/src/import-bindings.ts +98 -29
  118. package/src/index/sites.ts +781 -0
  119. package/src/indexed-access.ts +570 -527
  120. package/src/interface-satisfaction.ts +2 -2
  121. package/src/inventory.ts +19 -0
  122. package/src/keyof-comparisons.ts +131 -116
  123. package/src/{components.ts → liveness/components.ts} +3 -3
  124. package/src/{graph.ts → liveness/graph.ts} +93 -27
  125. package/src/liveness/matrix.ts +162 -0
  126. package/src/liveness/sweep.ts +855 -0
  127. package/src/load/expected-module.ts +8 -2
  128. package/src/load/specifiers.ts +8 -5
  129. package/src/load/synthesize.ts +6 -0
  130. package/src/load/workspace-resolution.ts +9 -2
  131. package/src/module-declarations.ts +74 -98
  132. package/src/overrides.ts +162 -134
  133. package/src/paths.ts +54 -8
  134. package/src/platform-calls.ts +3 -3
  135. package/src/platform-symbols.ts +11 -27
  136. package/src/query.ts +81 -0
  137. package/src/references/read-rule.ts +60 -0
  138. package/src/references/read-rules.ts +55 -0
  139. package/src/references/sink.ts +314 -0
  140. package/src/references/sources.ts +632 -0
  141. package/src/references.ts +175 -1733
  142. package/src/report.ts +1 -1
  143. package/src/roots/configured-declarations.ts +3 -0
  144. package/src/roots/published-reach.ts +9 -4
  145. package/src/roots/root-set.ts +1 -1
  146. package/src/roots/unseen-loads.ts +49 -11
  147. package/src/serialization-contract.ts +8 -3
  148. package/src/stores.ts +15 -3
  149. package/src/symbol-keys.ts +102 -102
  150. package/src/tests/file-class.ts +135 -0
  151. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  152. package/src/{test-support.ts → tests/support.ts} +9 -20
  153. package/src/type-parameter-reads.ts +187 -158
  154. package/src/unimplemented-callees.ts +297 -280
  155. package/src/verbs/analyze.ts +3 -2
  156. package/src/verbs/explain.ts +83 -158
  157. package/src/verbs/print-retained.ts +3 -3
  158. package/dist/src/matrix.js +0 -273
  159. package/dist/src/sweep.js +0 -222
  160. package/src/matrix.ts +0 -463
  161. package/src/sweep.ts +0 -361
package/src/calls.ts CHANGED
@@ -1,43 +1,66 @@
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 as an argument, or as an element of an array
5
- * literal or a property value of an object literal written as one, at any depth, and a
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
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,
32
+ isVariableDeclaration,
21
33
  type CallExpression,
22
34
  type Decorator,
23
35
  type Expression,
24
36
  type Node,
37
+ type ParameterDeclaration,
38
+ SyntaxKind,
25
39
  } from "@typescript/native/ast";
26
- import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/sync";
40
+ import {
41
+ ObjectFlags,
42
+ SymbolFlags,
43
+ type Symbol as TSSymbol,
44
+ type Type,
45
+ } from "@typescript/native/sync";
27
46
  import type { AliasChains } from "./alias-chain.ts";
28
47
  import { nodeKey, type Inventory } from "./inventory.ts";
29
48
  import { must } from "./query.ts";
30
49
  import type { ProjectView } from "./session.ts";
50
+ import { unwrapped } from "./unwrap.ts";
31
51
 
32
52
  /** One value written where a call passes it, and the node its declaration is resolved from. */
33
53
  interface PassedValue {
34
54
  /** Where the value is written, which is where a record of it sits. */
35
55
  readonly site: Node;
36
- /** The name the value is resolved through, or the shorthand assignment that holds it. */
37
- readonly name: Node;
56
+ /** The name the value is resolved through, the shorthand assignment that holds it, or none. */
57
+ readonly name: Node | undefined;
38
58
  readonly shorthand: boolean;
39
59
  }
40
60
 
61
+ /** A passed value that names its declaration. */
62
+ type NamedValue = PassedValue & { readonly name: Node };
63
+
41
64
  /** One class of the target a call passes or a decorator is attached to, and where. */
42
65
  interface ClassUse {
43
66
  /** The inventory's identifier of the class. */
@@ -109,44 +132,114 @@ export function resolvedTargets<Brand>(
109
132
  return targets;
110
133
  }
111
134
 
112
- /** The values one expression passes: itself, or what an array or object literal holds. */
113
- function passedIn(expression: Expression, found: PassedValue[]): void {
114
- if (isParenthesizedExpression(expression)) {
115
- passedIn(expression.expression, found);
116
- return;
117
- }
118
- if (isArrayLiteralExpression(expression)) {
119
- for (const element of expression.elements) {
120
- passedIn(isSpreadElement(element) ? element.expression : element, found);
121
- }
122
- 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;
123
140
  }
124
- if (isObjectLiteralExpression(expression)) {
125
- for (const property of expression.properties) {
126
- if (isPropertyAssignment(property)) {
127
- passedIn(property.initializer, found);
128
- } else if (isShorthandPropertyAssignment(property)) {
129
- found.push({ site: property.name, name: property, shorthand: true });
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
+ }
130
164
  }
165
+ } else if (!isOmittedExpression(expression)) {
166
+ found.push({ site: expression, name: calleeName(expression), shorthand: false });
131
167
  }
132
- return;
133
168
  }
134
- const name = calleeName(expression);
135
- if (name !== undefined) {
136
- found.push({ site: expression, name, shorthand: false });
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
+ }
137
197
  }
198
+ return [expression];
138
199
  }
139
200
 
140
201
  /** The values one argument passes, a spread argument's included. */
141
- export function valuesPassed(argument: Expression): readonly PassedValue[] {
202
+ function leavesPassed(argument: Expression): readonly PassedValue[] {
142
203
  const found: PassedValue[] = [];
143
- passedIn(isSpreadElement(argument) ? argument.expression : argument, found);
204
+ passedIn(argument, found);
144
205
  return found;
145
206
  }
146
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
+
147
236
  /**
148
- * The classes of the target each call passes, by the call. A value that names no
149
- * class of the target, a class of another program included, passes nothing here.
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.
150
243
  */
151
244
  export function classesPassed<Brand>(
152
245
  project: ProjectView<Brand>,
@@ -154,33 +247,232 @@ export function classesPassed<Brand>(
154
247
  chains: AliasChains,
155
248
  calls: readonly CallExpression[],
156
249
  ): ReadonlyMap<CallExpression, readonly ClassUse[]> {
157
- const passed = new Map(
158
- calls.map((call) => [call, call.arguments.flatMap((argument) => valuesPassed(argument))]),
159
- );
160
- const all = [...passed.values()].flat();
250
+ const passed = new Map(calls.map((call) => [call, call.arguments.flatMap(leavesPassed)]));
161
251
  const targets = resolvedTargets(
162
252
  project,
163
- all.filter((value) => !value.shorthand).map((value) => value.name),
253
+ [...passed.values()].flat().flatMap((value) => (value.shorthand ? [] : (value.name ?? []))),
164
254
  );
165
255
  const classes = new Set(
166
256
  held.symbols.filter((symbol) => symbol.kind === "class").map((symbol) => symbol.id),
167
257
  );
168
- const classesOf = (value: PassedValue): readonly string[] => {
169
- const symbol = value.shorthand
170
- ? must(project.shorthandValueAt(project.handle(value.name)))
171
- : targets.get(value.name);
172
- return symbol === undefined
173
- ? []
174
- : chains.declarationsOf(symbol).filter((id) => classes.has(id));
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
+ };
270
+ const reached = (symbol: TSSymbol, seen: Set<number>): readonly string[] => {
271
+ const ids = chains.declarationsOf(symbol).filter((id) => classes.has(id));
272
+ if (ids.length > 0 || seen.has(symbol.id)) {
273
+ return ids;
274
+ }
275
+ seen.add(symbol.id);
276
+ const through = (written: Expression): readonly string[] =>
277
+ leavesPassed(written).flatMap((value) => classesOf(value, seen));
278
+ return symbol.declarations.flatMap((handle) => {
279
+ const node = project.declarationAt(handle)?.node;
280
+ if (node === undefined) {
281
+ return [];
282
+ }
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
+ ];
291
+ });
175
292
  };
176
293
  return new Map(
177
- [...passed].map(([call, values]) => [
178
- call,
179
- values.flatMap((value) => classesOf(value).map((id) => ({ id, site: value.site }))),
180
- ]),
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
+ }),
181
303
  );
182
304
  }
183
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)]),
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
+ });
474
+ }
475
+
184
476
  /** One class declaration of the target and the decorators written on it. */
185
477
  interface DecoratedClass {
186
478
  readonly id: string;
@@ -6,7 +6,7 @@
6
6
  * the command unread.
7
7
  */
8
8
 
9
- import { resolvePath } from "../paths.ts";
9
+ import { hasURLScheme, isPathSpecifier, resolvePath } from "../paths.ts";
10
10
  import type { Word } from "./shell.ts";
11
11
 
12
12
  /** One module a runtime loads before its entry, and the resolution its option names it by. */
@@ -92,12 +92,6 @@ function optionsOf(words: readonly Word[], options: Options): Read | undefined {
92
92
  return { values, at: words.length, ended: false };
93
93
  }
94
94
 
95
- /** A specifier that starts with a URL scheme, `file:` and `node:` among them. */
96
- const SCHEME = /^[a-z][a-z\d+.-]*:/iu;
97
-
98
- /** A specifier written as a path: `.`, `..`, or one starting `./`, `../` or `/`. */
99
- const RELATIVE = /^(?:\.{1,2}(?:\/|$)|\/)/u;
100
-
101
95
  /**
102
96
  * The path a URL's path text names, percent-decoded as Node's `fileURLToPath` does, with a
103
97
  * backslash read as the URL parser reads it, or `undefined` where it does not decode or
@@ -127,19 +121,19 @@ export function preloadTarget(
127
121
  ): { readonly path: string } | { readonly package: string } | undefined {
128
122
  const { specifier, resolution } = preload;
129
123
  if (resolution === "require") {
130
- return RELATIVE.test(specifier)
124
+ return isPathSpecifier(specifier)
131
125
  ? { path: resolvePath(dir, specifier) }
132
- : SCHEME.test(specifier)
126
+ : hasURLScheme(specifier)
133
127
  ? undefined
134
128
  : { package: specifier };
135
129
  }
136
- if (!RELATIVE.test(specifier) && !SCHEME.test(specifier)) {
130
+ if (!isPathSpecifier(specifier) && !hasURLScheme(specifier)) {
137
131
  return { package: specifier };
138
132
  }
139
133
  const local = /^file:(?:\/\/(?=\/))?/iu.exec(specifier);
140
134
  const rest = specifier.slice(local?.[0].length ?? 0);
141
135
  // `file://host/…` names a file on another host.
142
- if ((SCHEME.test(specifier) && local === null) || rest.startsWith("//")) {
136
+ if ((hasURLScheme(specifier) && local === null) || rest.startsWith("//")) {
143
137
  return undefined;
144
138
  }
145
139
  const path = urlPath(rest);
@@ -6,6 +6,8 @@
6
6
  * attribute names are appended past the file's end.
7
7
  */
8
8
 
9
+ import { isPathRelativeURL, startsRelativePath } from "./paths.ts";
10
+
9
11
  /** The language one block is written in, as the module's virtual extension spells it. */
10
12
  type BlockLanguage = "ts" | "tsx" | "js" | "jsx";
11
13
 
@@ -465,10 +467,10 @@ function actionOf(text: string): string {
465
467
  /** Whether a `src` value names a file: no scheme, no root-relative path, not empty. */
466
468
  function specifierOf(value: string): string | undefined {
467
469
  const trimmed = value.trim();
468
- if (trimmed === "" || trimmed.startsWith("/") || /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(trimmed)) {
470
+ if (!isPathRelativeURL(trimmed)) {
469
471
  return undefined;
470
472
  }
471
- return trimmed.startsWith("./") || trimmed.startsWith("../") ? trimmed : `./${trimmed}`;
473
+ return startsRelativePath(trimmed) ? trimmed : `./${trimmed}`;
472
474
  }
473
475
 
474
476
  /** The line-start `<script` tags one block's content holds. */
@@ -4,7 +4,7 @@ export const RECORD = {
4
4
  conformance: {
5
5
  corpusVersion: "6.0.0",
6
6
  result: "pass",
7
- digest: "sha256:d8d2a417c5cb9b3829739424d9cfd3bd7b631231d8f420300656f6df86f5a6d1",
7
+ digest: "sha256:140f095d52fa8140de070f46d13a132b723c634ecf24a8b96e6a92c410c4b848",
8
8
  },
9
9
  gaps: [],
10
10
  } as const;
package/src/consumers.ts CHANGED
@@ -4,15 +4,16 @@ import { runDirectories } from "./entries/run-directories.ts";
4
4
  import type { Host } from "./host.ts";
5
5
  import { inventory } from "./inventory/enumerate.ts";
6
6
  import { relativePath } from "./paths.ts";
7
- import { references, type ConsumerSide, type Reference } from "./references.ts";
7
+ import { references, type ConsumerSide, type References } from "./references.ts";
8
8
  import { narrowedTo, type ProjectView } from "./session.ts";
9
+ import { patternTestFile } from "./tests/file-class.ts";
9
10
 
10
11
  /**
11
12
  * Every reference one consumer project's own files make to a declaration of the target.
12
13
  * The target's declarations are enumerated over the target's files the consumer's
13
14
  * program holds, against the target root, so each carries the identifier the target's
14
- * own projects give it. A reference from a consumer's test file is a test reference
15
- * unless the configuration counts it as a production one.
15
+ * own projects give it. A reference from a consumer's test file is a test reference;
16
+ * whether the run counts it as a production one is the sweep's mode.
16
17
  */
17
18
  export function consumerReferences<Brand>(
18
19
  project: ProjectView<Brand>,
@@ -21,19 +22,15 @@ export function consumerReferences<Brand>(
21
22
  consumer: ConsumerSide,
22
23
  config: PartialConfig,
23
24
  parse: ParseFile,
24
- ): readonly Reference[] {
25
+ ): References {
25
26
  const held = inventory(
26
27
  narrowedTo(project, (file) => relativePath(targetRoot, file.fileName) !== undefined),
27
28
  host,
28
29
  targetRoot,
29
30
  );
30
- const found = references(project, held, targetRoot, {
31
- testFiles: config.ts.test_files,
31
+ return references(project, held, targetRoot, {
32
+ testFile: patternTestFile(config.ts.test_files, consumer.root),
32
33
  consumer,
33
34
  directories: runDirectories(host, consumer.root, parse),
34
- }).references;
35
- if (config.analysis.consumer_tests !== "production") {
36
- return found;
37
- }
38
- return found.map((reference) => (reference.test ? { ...reference, test: false } : reference));
35
+ });
39
36
  }