@cplieger/deadset-ts 7.0.0-dev.1 → 7.0.0-dev.3

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 (113) hide show
  1. package/dist/src/analysis.js +95 -60
  2. package/dist/src/augmentations.js +14 -12
  3. package/dist/src/component-layer.js +1 -1
  4. package/dist/src/components.js +62 -20
  5. package/dist/src/configuration-files.js +230 -30
  6. package/dist/src/conformance-record.js +1 -1
  7. package/dist/src/contextual-uses.js +104 -13
  8. package/dist/src/convention-rows.js +10 -0
  9. package/dist/src/conventions.js +35 -1
  10. package/dist/src/dependencies.js +90 -32
  11. package/dist/src/discover.js +1 -1
  12. package/dist/src/edges.js +1 -1
  13. package/dist/src/element-access.js +341 -0
  14. package/dist/src/erased-stores.js +317 -0
  15. package/dist/src/exempt-classes.js +9 -22
  16. package/dist/src/exempt.js +3 -15
  17. package/dist/src/findings/completion.js +78 -16
  18. package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
  19. package/dist/src/findings/emitters.js +1 -5
  20. package/dist/src/findings/self-check.js +27 -10
  21. package/dist/src/findings-pass.js +6 -5
  22. package/dist/src/in-operands.js +30 -0
  23. package/dist/src/indexed-access.js +264 -119
  24. package/dist/src/intra-function-parts.js +25 -11
  25. package/dist/src/inventory.js +28 -12
  26. package/dist/src/keyof-comparisons.js +170 -0
  27. package/dist/src/kinds.js +17 -7
  28. package/dist/src/manifest.js +6 -16
  29. package/dist/src/matrix.js +20 -2
  30. package/dist/src/query.js +3 -0
  31. package/dist/src/references.js +14 -3
  32. package/dist/src/report.js +4 -1
  33. package/dist/src/reporters/sarif.js +4 -0
  34. package/dist/src/roots.js +0 -1
  35. package/dist/src/run.js +0 -2
  36. package/dist/src/schema.js +1 -1
  37. package/dist/src/scope.js +2 -2
  38. package/dist/src/serialization-contract.js +43 -14
  39. package/dist/src/session.js +12 -8
  40. package/dist/src/suppress-file.js +58 -6
  41. package/dist/src/suppress-inline.js +14 -5
  42. package/dist/src/suppress.js +64 -35
  43. package/dist/src/unimplemented-callees.js +312 -0
  44. package/dist/src/value-reach.js +7 -7
  45. package/dist/src/verbs/analyze.js +11 -9
  46. package/dist/src/workspace-programs.js +0 -3
  47. package/dist/src/workspace-resolution.js +0 -2
  48. package/dist/src/workspace.js +0 -3
  49. package/jsr.json +1 -1
  50. package/package.json +3 -2
  51. package/src/analysis.ts +145 -82
  52. package/src/augmentations.ts +14 -11
  53. package/src/calls.ts +3 -3
  54. package/src/component-files.ts +7 -7
  55. package/src/component-layer.ts +2 -2
  56. package/src/components.ts +104 -22
  57. package/src/config.ts +4 -4
  58. package/src/configuration-files.ts +250 -31
  59. package/src/conformance-record.ts +1 -1
  60. package/src/contextual-uses.ts +135 -17
  61. package/src/convention-rows.ts +23 -0
  62. package/src/conventions.ts +53 -1
  63. package/src/dependencies.ts +119 -38
  64. package/src/discover.ts +3 -3
  65. package/src/edges.ts +3 -3
  66. package/src/element-access.ts +446 -0
  67. package/src/entry-points.ts +1 -1
  68. package/src/erased-stores.ts +413 -0
  69. package/src/exempt-classes.ts +10 -24
  70. package/src/exempt.ts +5 -29
  71. package/src/file-facts.ts +3 -1
  72. package/src/finding.ts +3 -1
  73. package/src/findings/completion.ts +97 -21
  74. package/src/findings/dependencies-and-module-machinery.ts +1 -1
  75. package/src/findings/emitters.ts +4 -9
  76. package/src/findings/self-check.ts +31 -14
  77. package/src/findings-pass.ts +14 -7
  78. package/src/graph.ts +2 -2
  79. package/src/html-entries.ts +1 -1
  80. package/src/in-operands.ts +45 -0
  81. package/src/indexed-access.ts +303 -137
  82. package/src/intra-function-parts.ts +41 -16
  83. package/src/inventory.ts +37 -33
  84. package/src/json-document.ts +1 -1
  85. package/src/keyof-comparisons.ts +211 -0
  86. package/src/kinds.ts +20 -7
  87. package/src/manifest.ts +7 -22
  88. package/src/matrix.ts +34 -2
  89. package/src/namespace-objects.ts +1 -1
  90. package/src/pnpm-workspace.ts +1 -1
  91. package/src/query.ts +10 -0
  92. package/src/references.ts +30 -7
  93. package/src/report.ts +11 -5
  94. package/src/reporters/sarif.ts +4 -0
  95. package/src/roots.ts +0 -3
  96. package/src/run.ts +0 -2
  97. package/src/schema.ts +2 -2
  98. package/src/scope.ts +2 -2
  99. package/src/serialization-contract.ts +53 -14
  100. package/src/session.ts +34 -31
  101. package/src/stores.ts +1 -1
  102. package/src/suppress-file.ts +68 -5
  103. package/src/suppress-inline.ts +17 -7
  104. package/src/suppress.ts +79 -47
  105. package/src/template-field.ts +1 -1
  106. package/src/unimplemented-callees.ts +411 -0
  107. package/src/value-reach.ts +12 -8
  108. package/src/verbs/analyze.ts +15 -9
  109. package/src/verbs/verb.ts +1 -4
  110. package/src/workspace-programs.ts +2 -9
  111. package/src/workspace-resolution.ts +0 -3
  112. package/src/workspace-run.ts +1 -1
  113. package/src/workspace.ts +1 -9
@@ -7,40 +7,48 @@
7
7
 
8
8
  import {
9
9
  isArrowFunction,
10
+ isBindingElement,
10
11
  isCallExpression,
11
12
  isClassDeclaration,
12
13
  isClassExpression,
13
14
  isFunctionDeclaration,
14
15
  isFunctionExpression,
16
+ isFunctionTypeNode,
15
17
  isIdentifier,
16
- isNonNullExpression,
17
- isParenthesizedExpression,
18
- isNamedImports,
19
- isNamespaceImport,
20
- isImportDeclaration,
21
18
  isIndexedAccessTypeNode,
22
19
  isInterfaceDeclaration,
23
20
  isLiteralTypeNode,
21
+ isMappedTypeNode,
24
22
  isMethodDeclaration,
25
23
  isMethodSignatureDeclaration,
26
24
  isNewExpression,
27
25
  isNoSubstitutionTemplateLiteral,
28
26
  isNumericLiteral,
27
+ isObjectBindingPattern,
29
28
  isParenthesizedTypeNode,
30
29
  isPropertyAccessExpression,
30
+ isPropertySignatureDeclaration,
31
31
  isStringLiteral,
32
32
  isTypeAliasDeclaration,
33
+ isTypeOperatorNode,
33
34
  isTypeReferenceNode,
34
35
  isUnionTypeNode,
35
36
  isVariableDeclaration,
37
+ SyntaxKind,
36
38
  type CallExpression,
39
+ type Expression,
37
40
  type NewExpression,
38
41
  type Node,
39
42
  type SourceFile,
40
43
  type TypeNode,
41
44
  type TypeReferenceNode,
42
45
  } from "@typescript/native/unstable/ast";
43
- import { SymbolFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/unstable/sync";
46
+ import {
47
+ SignatureKind,
48
+ SymbolFlags,
49
+ type Symbol as TSSymbol,
50
+ type Type,
51
+ } from "@typescript/native/unstable/sync";
44
52
  import type { AliasChains } from "./alias-chain.ts";
45
53
  import { nodeKey, type Inventory } from "./inventory.ts";
46
54
  import { renderPosition } from "./position.ts";
@@ -62,6 +70,7 @@ function isGeneric(node: Node): node is Generic {
62
70
  isMethodSignatureDeclaration(node) ||
63
71
  isArrowFunction(node) ||
64
72
  isFunctionExpression(node) ||
73
+ isFunctionTypeNode(node) ||
65
74
  isTypeAliasDeclaration(node) ||
66
75
  isInterfaceDeclaration(node) ||
67
76
  isClassDeclaration(node) ||
@@ -75,6 +84,35 @@ function isReceiver(node: Node): boolean {
75
84
  return isInterfaceDeclaration(node) || isClassDeclaration(node) || isClassExpression(node);
76
85
  }
77
86
 
87
+ /**
88
+ * The class or the interface one member function belongs to: a method's parent, or for a
89
+ * function type a property declares, the declaration that holds the property.
90
+ */
91
+ function receiverOf(member: Node): Node | undefined {
92
+ const parent = member.parent as Node | undefined;
93
+ if (
94
+ isFunctionTypeNode(member) &&
95
+ parent !== undefined &&
96
+ isPropertySignatureDeclaration(parent)
97
+ ) {
98
+ return parent.parent;
99
+ }
100
+ return parent;
101
+ }
102
+
103
+ /** Whether a member function of a generic receiver declares no type parameter of its own. */
104
+ function isReceiverMember(node: Node): node is Generic {
105
+ if (!(
106
+ isMethodDeclaration(node) ||
107
+ isMethodSignatureDeclaration(node) ||
108
+ isFunctionTypeNode(node)
109
+ )) {
110
+ return false;
111
+ }
112
+ const receiver = receiverOf(node);
113
+ return receiver !== undefined && isReceiver(receiver) && isGeneric(receiver);
114
+ }
115
+
78
116
  function unwrappedType(node: TypeNode): TypeNode {
79
117
  return isParenthesizedTypeNode(node) ? unwrappedType(node.type) : node;
80
118
  }
@@ -122,7 +160,8 @@ function literalTypeNames(
122
160
  type Side =
123
161
  | { readonly kind: "parameter"; readonly index: number }
124
162
  | { readonly kind: "written"; readonly node: TypeNode }
125
- | { readonly kind: "receiver"; readonly index: number };
163
+ | { readonly kind: "receiver"; readonly index: number }
164
+ | { readonly kind: "argument"; readonly index: number };
126
165
 
127
166
  /** An indexed access inside one generic whose index names a type parameter. */
128
167
  interface Carried {
@@ -190,6 +229,35 @@ function namedParameter(
190
229
  return undefined;
191
230
  }
192
231
 
232
+ /**
233
+ * The constraint of the mapped type whose type parameter one type node names whole: a
234
+ * mapped type instantiates what it holds once per constituent of that constraint.
235
+ */
236
+ function mappedConstraint(node: TypeNode): TypeNode | undefined {
237
+ const type = unwrappedType(node);
238
+ if (
239
+ !isTypeReferenceNode(type) ||
240
+ !isIdentifier(type.typeName) ||
241
+ type.typeArguments !== undefined
242
+ ) {
243
+ return undefined;
244
+ }
245
+ const spelled = type.typeName.text;
246
+ for (
247
+ let at = type.parent as Node | undefined;
248
+ at !== undefined;
249
+ at = at.parent as Node | undefined
250
+ ) {
251
+ if (isMappedTypeNode(at)) {
252
+ const { name, constraint } = at.typeParameter;
253
+ if (isIdentifier(name) && name.text === spelled) {
254
+ return constraint;
255
+ }
256
+ }
257
+ }
258
+ return undefined;
259
+ }
260
+
193
261
  /** The name a site writes to reach one generic: its own, or its variable's. */
194
262
  function calledName(generic: Generic): string | undefined {
195
263
  const own = (generic as { readonly name?: Node }).name;
@@ -197,16 +265,24 @@ function calledName(generic: Generic): string | undefined {
197
265
  return own.text;
198
266
  }
199
267
  const parent = generic.parent as Node | undefined;
200
- return parent !== undefined && isVariableDeclaration(parent) && isIdentifier(parent.name)
268
+ return parent !== undefined &&
269
+ (isVariableDeclaration(parent) || isPropertySignatureDeclaration(parent)) &&
270
+ isIdentifier(parent.name)
201
271
  ? parent.name.text
202
272
  : undefined;
203
273
  }
204
274
 
205
275
  /** The generic a declaration a symbol names is: the function a variable is initialized with included. */
206
276
  function genericOf(node: Node): Generic | undefined {
207
- if (isGeneric(node)) {
277
+ if (isGeneric(node) || isReceiverMember(node)) {
208
278
  return node;
209
279
  }
280
+ if (isPropertySignatureDeclaration(node)) {
281
+ // The typings mark the annotation present; a signature written without one has none.
282
+ const declared = (node as { readonly type?: TypeNode }).type;
283
+ const type = declared === undefined ? undefined : unwrappedType(declared);
284
+ return type !== undefined && isFunctionTypeNode(type) ? genericOf(type) : undefined;
285
+ }
210
286
  if (
211
287
  isVariableDeclaration(node) &&
212
288
  node.initializer !== undefined &&
@@ -217,32 +293,63 @@ function genericOf(node: Node): Generic | undefined {
217
293
  return undefined;
218
294
  }
219
295
 
296
+ /** The operand of each `keyof` one type node holds, through every type node it is built of. */
297
+ function keyofOperands(node: TypeNode): readonly TypeNode[] {
298
+ const found: TypeNode[] = [];
299
+ const visit = (at: Node): void => {
300
+ if (isTypeOperatorNode(at) && at.operator === SyntaxKind.KeyOfKeyword) {
301
+ found.push(at.type);
302
+ return;
303
+ }
304
+ at.forEachChild(visit);
305
+ };
306
+ visit(node);
307
+ return found;
308
+ }
309
+
220
310
  /**
221
311
  * Each indexed access inside one generic whose index names a type parameter of it, read
222
312
  * from its own text: the object side is a type parameter of the same generic, one of the
223
- * receiver it is a method of, or a type it writes.
313
+ * receiver it is a member of, or a type it writes. A type parameter whose constraint holds
314
+ * `keyof X` is an index into `X` as `X[K]` is, and so is a parameter whose declared type
315
+ * holds `keyof X`, indexed by the argument a call passes it.
224
316
  */
225
317
  function carriedIn(generic: Generic): readonly Carried[] {
226
318
  const found: Carried[] = [];
227
- const receiver = generic.parent as Node | undefined;
319
+ const receiver = receiverOf(generic);
228
320
  const outer =
229
321
  receiver !== undefined && isReceiver(receiver) && isGeneric(receiver) ? receiver : undefined;
322
+ const sideOf = (object: TypeNode): Side => {
323
+ const own = namedParameter(object, [generic]);
324
+ const fromReceiver =
325
+ own === undefined && outer !== undefined ? namedParameter(object, [outer]) : undefined;
326
+ return own !== undefined
327
+ ? { kind: "parameter", index: own.index }
328
+ : fromReceiver !== undefined
329
+ ? { kind: "receiver", index: fromReceiver.index }
330
+ : { kind: "written", node: object };
331
+ };
332
+ (generic.typeParameters ?? []).forEach((parameter, index) => {
333
+ const constraint = (parameter as { readonly constraint?: TypeNode }).constraint;
334
+ for (const object of constraint === undefined ? [] : keyofOperands(constraint)) {
335
+ found.push({ object: sideOf(object), index: { kind: "parameter", index } });
336
+ }
337
+ });
338
+ (generic.parameters ?? []).forEach((parameter, index) => {
339
+ for (const object of parameter.type === undefined ? [] : keyofOperands(parameter.type)) {
340
+ if (namedParameter(object, [generic]) === undefined) {
341
+ found.push({ object: sideOf(object), index: { kind: "argument", index } });
342
+ }
343
+ }
344
+ });
230
345
  const visit = (node: Node): void => {
231
346
  if (isIndexedAccessTypeNode(node)) {
232
347
  const index = namedParameter(node.indexType, [generic]);
233
348
  if (index !== undefined) {
234
- const own = namedParameter(node.objectType, [generic]);
235
- const fromReceiver =
236
- own === undefined && outer !== undefined
237
- ? namedParameter(node.objectType, [outer])
238
- : undefined;
239
- const object: Side =
240
- own !== undefined
241
- ? { kind: "parameter", index: own.index }
242
- : fromReceiver !== undefined
243
- ? { kind: "receiver", index: fromReceiver.index }
244
- : { kind: "written", node: node.objectType };
245
- found.push({ object, index: { kind: "parameter", index: index.index } });
349
+ found.push({
350
+ object: sideOf(node.objectType),
351
+ index: { kind: "parameter", index: index.index },
352
+ });
246
353
  }
247
354
  }
248
355
  if (node !== generic && isGeneric(node)) {
@@ -271,8 +378,8 @@ export function indexedAccessReferences<Brand>(
271
378
  const carried = new Map<Generic, Carried[]>();
272
379
  const names = new Set<string>();
273
380
  const sites: Site[] = [];
274
- /** Calls passing a literal argument whose callee a package binding heads, which a dependency's generic may fix. */
275
- const literalCalls: Site[] = [];
381
+ /** Calls passing an argument, which a generic declared outside the own files may fix. */
382
+ const argumentCalls: Site[] = [];
276
383
 
277
384
  const carry = (generic: Generic, one: Carried): boolean => {
278
385
  const known = carried.get(generic) ?? [];
@@ -296,7 +403,6 @@ export function indexedAccessReferences<Brand>(
296
403
  continue;
297
404
  }
298
405
  const aliasTypes = new Set(typeQueryAliases(file).map((alias) => alias.type));
299
- const imported = packageBindings(file);
300
406
  const visit = (node: Node, from: string, enclosing: readonly Generic[]): void => {
301
407
  const own = held.declarations.get(nodeKey(file, node));
302
408
  const holder = own ?? from;
@@ -327,12 +433,8 @@ export function indexedAccessReferences<Brand>(
327
433
  if (name !== undefined) {
328
434
  const site = { file, from: holder, node, name, enclosing: inside };
329
435
  sites.push(site);
330
- const literal = (node.arguments ?? []).some(
331
- (argument) => isStringLiteral(argument) || isNumericLiteral(argument),
332
- );
333
- const head = rootName(callee);
334
- if (literal && isCallExpression(node) && head !== undefined && imported.has(head)) {
335
- literalCalls.push(site);
436
+ if (isCallExpression(node) && node.arguments.length > 0) {
437
+ argumentCalls.push(site);
336
438
  }
337
439
  }
338
440
  }
@@ -363,6 +465,50 @@ export function indexedAccessReferences<Brand>(
363
465
  });
364
466
  };
365
467
 
468
+ /**
469
+ * The type a call's callee is read off: the receiver of a property access, the value an
470
+ * object binding pattern destructures the callee from, or the value whose member a
471
+ * variable holding the callee is initialized with.
472
+ */
473
+ const receiverTypeOf = (callee: Expression): Type | undefined => {
474
+ if (isPropertyAccessExpression(callee)) {
475
+ const type = queries.typeAt(callee.expression);
476
+ const args = type === UNANSWERED || type === undefined ? [] : queries.typeArguments(type);
477
+ if (type !== UNANSWERED && type !== undefined && args !== UNANSWERED && args.length > 0) {
478
+ return type;
479
+ }
480
+ }
481
+ const name = isPropertyAccessExpression(callee) ? callee.name : callee;
482
+ const [named] = queries.symbolsAt([name]);
483
+ const symbol =
484
+ named === UNANSWERED || named === undefined || (named.flags & SymbolFlags.Alias) === 0
485
+ ? named
486
+ : queries.aliased(named);
487
+ if (symbol === UNANSWERED || symbol === undefined) {
488
+ return undefined;
489
+ }
490
+ for (const handle of symbol.declarations) {
491
+ const node = project.declarationAt(handle)?.node;
492
+ const holder =
493
+ node !== undefined &&
494
+ isBindingElement(node) &&
495
+ isObjectBindingPattern(node.parent) &&
496
+ isVariableDeclaration(node.parent.parent)
497
+ ? node.parent.parent.initializer
498
+ : node !== undefined &&
499
+ isVariableDeclaration(node) &&
500
+ node.initializer !== undefined &&
501
+ isPropertyAccessExpression(node.initializer)
502
+ ? node.initializer.expression
503
+ : undefined;
504
+ const type = holder === undefined ? undefined : queries.typeAt(holder);
505
+ if (type !== UNANSWERED && type !== undefined) {
506
+ return type;
507
+ }
508
+ }
509
+ return undefined;
510
+ };
511
+
366
512
  /** What one site fixes for one side of a carried access. */
367
513
  const fixedAt = (site: Site, side: Side, generic: Generic): Fixed | undefined => {
368
514
  if (side.kind === "written") {
@@ -371,23 +517,26 @@ export function indexedAccessReferences<Brand>(
371
517
  const { node } = site;
372
518
  if (side.kind === "receiver") {
373
519
  const callee = isCallExpression(node) ? node.expression : undefined;
374
- if (callee === undefined || !isPropertyAccessExpression(callee)) {
375
- return undefined;
376
- }
377
- const receiver = queries.typeAt(callee.expression);
378
- if (receiver === UNANSWERED || receiver === undefined) {
520
+ const receiver = callee === undefined ? undefined : receiverTypeOf(callee);
521
+ if (receiver === undefined) {
379
522
  return undefined;
380
523
  }
381
524
  const args = queries.typeArguments(receiver);
382
525
  const type = args === UNANSWERED ? undefined : args[side.index];
383
526
  return type === undefined ? undefined : { kind: "type", type };
384
527
  }
528
+ if (side.kind === "argument") {
529
+ const argument = isTypeReferenceNode(node) ? undefined : node.arguments?.[side.index];
530
+ const type = argument === undefined ? undefined : queries.typeAt(argument);
531
+ return type === UNANSWERED || type === undefined ? undefined : { kind: "type", type };
532
+ }
385
533
  const written = node.typeArguments?.[side.index];
386
534
  if (written !== undefined) {
387
535
  const passed = namedParameter(written, site.enclosing);
388
- return passed === undefined
389
- ? { kind: "node", node: written }
390
- : { kind: "passed", generic: passed.generic, index: passed.index };
536
+ const mapped = passed === undefined ? mappedConstraint(written) : undefined;
537
+ return passed !== undefined
538
+ ? { kind: "passed", generic: passed.generic, index: passed.index }
539
+ : { kind: "node", node: mapped ?? written };
391
540
  }
392
541
  if (isTypeReferenceNode(node)) {
393
542
  return undefined;
@@ -492,46 +641,129 @@ export function indexedAccessReferences<Brand>(
492
641
  });
493
642
  }
494
643
 
495
- // A generic function or method declared outside the project's own files carries what
496
- // its own text indexes by its type parameters and its receiver's.
497
- const outside = literalCalls.filter((site) => !resolvedGenerics.has(site));
498
- const methods =
499
- outside.length === 0
500
- ? []
501
- : queries.symbolsAt(
502
- outside.map((site) => {
503
- const callee = (site.node as CallExpression).expression;
504
- return isPropertyAccessExpression(callee) ? callee.name : callee;
505
- }),
506
- );
644
+ // A generic declared outside the project's own files carries what its own text indexes
645
+ // by its type parameters and its receiver's. Only a call passing an argument the compiler
646
+ // types as a literal can fix an index, so only such a call's signature is resolved.
647
+ const outside = argumentCalls.filter((site) => !resolvedGenerics.has(site));
648
+ const argumentNodes = argumentCalls.flatMap((site) => [
649
+ ...(site.node as CallExpression).arguments,
650
+ ]);
651
+ const argumentTypes = queries.typesAt(argumentNodes);
652
+ const literalArgument = new Set<Node>();
653
+ argumentTypes.forEach((type, at) => {
654
+ const node = argumentNodes[at];
655
+ if (
656
+ node !== undefined &&
657
+ type !== UNANSWERED &&
658
+ type !== undefined &&
659
+ (literalTypeNames(type, partsOf)?.length ?? 0) > 0
660
+ ) {
661
+ literalArgument.add(node);
662
+ }
663
+ });
507
664
  const own = project.ownPaths();
508
- outside.forEach((site, at) => {
509
- const named = methods[at];
510
- const symbol =
511
- named === UNANSWERED || named === undefined || (named.flags & SymbolFlags.Alias) === 0
512
- ? named
513
- : queries.aliased(named);
665
+ for (const site of outside) {
666
+ if (!(site.node as CallExpression).arguments.some((one) => literalArgument.has(one))) {
667
+ continue;
668
+ }
669
+ const resolved = queries.resolvedSignature(site.node);
670
+ const handle =
671
+ resolved === UNANSWERED || resolved === undefined ? undefined : resolved.declaration;
672
+ if (handle === undefined || own.has(handle.path)) {
673
+ continue;
674
+ }
675
+ const node = project.declarationAt(handle)?.node;
676
+ const generic = node === undefined ? undefined : genericOf(node);
677
+ if (generic === undefined) {
678
+ continue;
679
+ }
680
+ for (const one of carriedIn(generic)) {
681
+ readAt(site, generic, one);
682
+ }
683
+ }
684
+ for (const site of argumentCalls) {
685
+ if ((site.node as CallExpression).arguments.some((one) => literalArgument.has(one))) {
686
+ accesses.push(...instantiatedAccesses(site));
687
+ }
688
+ }
689
+
690
+ /**
691
+ * The indexed accesses the generic signature a call instantiates holds, read through the
692
+ * checker: the signature its receiver's instantiation gives, so an object type its
693
+ * receiver fixes is concrete whatever declaration spells the receiver's type, a type
694
+ * literal a generic function returns among them. An index naming a type parameter is the
695
+ * literal type the call passes at each parameter of that type.
696
+ */
697
+ function instantiatedAccesses(site: Site): readonly Access[] {
698
+ const resolved = queries.resolvedSignature(site.node);
699
+ const generic =
700
+ resolved === UNANSWERED || resolved === undefined ? undefined : queries.genericOf(resolved);
514
701
  if (
515
- symbol === UNANSWERED ||
516
- symbol === undefined ||
517
- (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) === 0
702
+ resolved === UNANSWERED ||
703
+ resolved === undefined ||
704
+ generic === UNANSWERED ||
705
+ generic === undefined
518
706
  ) {
519
- return;
707
+ return [];
520
708
  }
521
- for (const handle of symbol.declarations) {
522
- if (own.has(handle.path)) {
523
- continue;
709
+ const count = (site.node as CallExpression).arguments.length;
710
+ const parameters: Type[] = [];
711
+ for (let at = 0; at < count; at += 1) {
712
+ const type = queries.parameterType(generic, at);
713
+ if (type !== UNANSWERED && type !== undefined) {
714
+ parameters[at] = type;
524
715
  }
525
- const node = project.declarationAt(handle)?.node;
526
- const generic = node === undefined ? undefined : genericOf(node);
527
- if (generic === undefined) {
716
+ }
717
+ const result = queries.returnTypeOf(generic);
718
+ const held: Type[] = [
719
+ ...parameters,
720
+ ...(result === UNANSWERED || result === undefined ? [] : [result]),
721
+ ];
722
+ // A parameter of a callback and a type argument, a rest tuple's among them, hold the
723
+ // accesses one level down: `fn: (payload: M[K]) => void` and `...[M[K]]`.
724
+ const nested = held.flatMap((type) => [
725
+ ...partsOf(type),
726
+ ...partsOf(type).flatMap((part) => {
727
+ const args = queries.typeArguments(part);
728
+ const calls = queries.signaturesOf(part, SignatureKind.Call);
729
+ return [
730
+ ...(args === UNANSWERED ? [] : args),
731
+ ...(calls === UNANSWERED ? [] : calls).flatMap((signature) =>
732
+ signature.parameters.flatMap((_parameter, index) => {
733
+ const one = queries.parameterType(signature, index);
734
+ return one === UNANSWERED || one === undefined ? [] : [one];
735
+ }),
736
+ ),
737
+ ];
738
+ }),
739
+ ]);
740
+ const found: Access[] = [];
741
+ for (const type of nested) {
742
+ const access = queries.indexedAccessOf(type);
743
+ if (access === UNANSWERED || access === undefined || access.object.isTypeParameter()) {
528
744
  continue;
529
745
  }
530
- for (const one of carriedIn(generic)) {
531
- readAt(site, generic, one);
746
+ const names = access.index.isTypeParameter()
747
+ ? parameters.flatMap((parameter, at) => {
748
+ if (parameter.id !== access.index.id) {
749
+ return [];
750
+ }
751
+ const passed = queries.typeAtPosition(resolved, at);
752
+ return passed === UNANSWERED ? [] : (literalTypeNames(passed, partsOf) ?? []);
753
+ })
754
+ : (literalTypeNames(access.index, partsOf) ?? []);
755
+ if (names.length > 0) {
756
+ found.push({
757
+ file: site.file,
758
+ from: site.from,
759
+ at: site.node,
760
+ object: access.object,
761
+ index: names,
762
+ });
532
763
  }
533
764
  }
534
- });
765
+ return found;
766
+ }
535
767
 
536
768
  const typeNodes = accesses.flatMap((one) => [
537
769
  ...("kind" in one.object ? [one.object] : []),
@@ -587,72 +819,6 @@ export function indexedAccessReferences<Brand>(
587
819
  return found;
588
820
  }
589
821
 
590
- /**
591
- * The local names one file binds to a package: each name an import declaration with a
592
- * bare specifier binds, and each variable initialized by a call or a construction whose
593
- * callee such a name heads, read in source order.
594
- */
595
- function packageBindings(file: SourceFile): ReadonlySet<string> {
596
- const names = new Set<string>();
597
- for (const statement of file.statements) {
598
- if (
599
- !isImportDeclaration(statement) ||
600
- !isStringLiteral(statement.moduleSpecifier) ||
601
- /^[./]/u.test(statement.moduleSpecifier.text)
602
- ) {
603
- continue;
604
- }
605
- const clause = statement.importClause;
606
- if (clause?.name !== undefined) {
607
- names.add(clause.name.text);
608
- }
609
- const bindings = clause?.namedBindings;
610
- if (bindings !== undefined && isNamespaceImport(bindings)) {
611
- names.add(bindings.name.text);
612
- } else if (bindings !== undefined && isNamedImports(bindings)) {
613
- bindings.elements.forEach((element) => names.add(element.name.text));
614
- }
615
- }
616
- if (names.size === 0) {
617
- return names;
618
- }
619
- const visit = (node: Node): void => {
620
- if (
621
- isVariableDeclaration(node) &&
622
- isIdentifier(node.name) &&
623
- node.initializer !== undefined &&
624
- (isCallExpression(node.initializer) || isNewExpression(node.initializer))
625
- ) {
626
- const head = rootName(node.initializer.expression);
627
- if (head !== undefined && names.has(head)) {
628
- names.add(node.name.text);
629
- }
630
- }
631
- node.forEachChild(visit);
632
- };
633
- visit(file);
634
- return names;
635
- }
636
-
637
- /** The identifier an expression is headed by, through property accesses and calls. */
638
- function rootName(expression: Node): string | undefined {
639
- let at: Node = expression;
640
- for (;;) {
641
- if (isIdentifier(at)) {
642
- return at.text;
643
- }
644
- if (isPropertyAccessExpression(at) || isCallExpression(at) || isNewExpression(at)) {
645
- at = at.expression;
646
- continue;
647
- }
648
- if (isParenthesizedExpression(at) || isNonNullExpression(at)) {
649
- at = at.expression;
650
- continue;
651
- }
652
- return undefined;
653
- }
654
- }
655
-
656
822
  /** What makes two carried accesses one: the kind and position of each side, or its written node. */
657
823
  function sideKey(one: Carried): readonly unknown[] {
658
824
  const key = (side: Side): unknown =>