@cplieger/deadset-ts 7.0.0-dev.2 → 7.0.0-dev.4

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 (137) hide show
  1. package/README.md +1 -1
  2. package/dist/src/alias-chain.js +2 -2
  3. package/dist/src/analysis.js +89 -44
  4. package/dist/src/augmentations.js +14 -12
  5. package/dist/src/calls.js +2 -2
  6. package/dist/src/class-members.js +1 -1
  7. package/dist/src/components.js +62 -20
  8. package/dist/src/configuration-files.js +6 -6
  9. package/dist/src/configured-declarations.js +2 -2
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/contextual-uses.js +71 -14
  12. package/dist/src/convention-rows.js +6 -2
  13. package/dist/src/conventions.js +4 -2
  14. package/dist/src/dependencies.js +81 -27
  15. package/dist/src/deprecation.js +1 -1
  16. package/dist/src/destructuring.js +2 -2
  17. package/dist/src/discover.js +1 -1
  18. package/dist/src/element-access.js +341 -0
  19. package/dist/src/entry-points.js +2 -2
  20. package/dist/src/enum-group.js +2 -2
  21. package/dist/src/erased-stores.js +317 -0
  22. package/dist/src/findings/completion.js +46 -15
  23. package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
  24. package/dist/src/findings/emitters.js +1 -1
  25. package/dist/src/findings/self-check.js +17 -4
  26. package/dist/src/findings-pass.js +6 -5
  27. package/dist/src/framework-lifecycle.js +1 -1
  28. package/dist/src/html-entries.js +1 -1
  29. package/dist/src/implementations.js +2 -2
  30. package/dist/src/in-operands.js +30 -0
  31. package/dist/src/indexed-access.js +264 -125
  32. package/dist/src/injection-container.js +1 -1
  33. package/dist/src/interface-satisfaction.js +2 -2
  34. package/dist/src/intra-function-parts.js +26 -12
  35. package/dist/src/inventory.js +26 -1
  36. package/dist/src/keyof-comparisons.js +170 -0
  37. package/dist/src/kinds.js +17 -7
  38. package/dist/src/manifest.js +6 -16
  39. package/dist/src/matrix.js +20 -2
  40. package/dist/src/member-names.js +1 -1
  41. package/dist/src/module-declarations.js +1 -1
  42. package/dist/src/namespace-objects.js +1 -1
  43. package/dist/src/outside-tests.js +1 -1
  44. package/dist/src/overrides.js +2 -2
  45. package/dist/src/query.js +4 -1
  46. package/dist/src/references.js +13 -2
  47. package/dist/src/reflective-lookup.js +2 -2
  48. package/dist/src/report.js +1 -1
  49. package/dist/src/reporters/sarif.js +4 -0
  50. package/dist/src/roots.js +1 -1
  51. package/dist/src/serialization-contract.js +45 -16
  52. package/dist/src/session.js +15 -8
  53. package/dist/src/signatures.js +1 -1
  54. package/dist/src/stores.js +1 -1
  55. package/dist/src/suppress-file.js +58 -6
  56. package/dist/src/suppress-inline.js +14 -5
  57. package/dist/src/suppress.js +54 -24
  58. package/dist/src/symbol-keys.js +1 -1
  59. package/dist/src/type-errors.js +1 -1
  60. package/dist/src/type-query-alias.js +1 -1
  61. package/dist/src/unimplemented-callees.js +312 -0
  62. package/dist/src/value-flow.js +1 -1
  63. package/dist/src/value-reach.js +8 -8
  64. package/dist/src/verbs/analyze.js +11 -9
  65. package/dist/src/workspace-programs.js +2 -2
  66. package/jsr.json +1 -1
  67. package/package.json +3 -3
  68. package/src/alias-chain.ts +2 -2
  69. package/src/analysis.ts +135 -52
  70. package/src/augmentations.ts +14 -11
  71. package/src/calls.ts +2 -2
  72. package/src/class-members.ts +2 -2
  73. package/src/component-layer.ts +1 -1
  74. package/src/components.ts +103 -21
  75. package/src/configuration-files.ts +6 -5
  76. package/src/configured-declarations.ts +2 -6
  77. package/src/conformance-record.ts +1 -1
  78. package/src/contextual-uses.ts +82 -18
  79. package/src/convention-rows.ts +6 -2
  80. package/src/conventions.ts +11 -2
  81. package/src/decorator.ts +1 -1
  82. package/src/dependencies.ts +108 -31
  83. package/src/deprecation.ts +1 -1
  84. package/src/destructuring.ts +2 -2
  85. package/src/discover.ts +2 -2
  86. package/src/element-access.ts +446 -0
  87. package/src/entry-points.ts +2 -2
  88. package/src/enum-group.ts +2 -2
  89. package/src/erased-stores.ts +413 -0
  90. package/src/file-facts.ts +3 -1
  91. package/src/findings/completion.ts +54 -20
  92. package/src/findings/dependencies-and-module-machinery.ts +1 -1
  93. package/src/findings/emitters.ts +1 -1
  94. package/src/findings/self-check.ts +18 -4
  95. package/src/findings-pass.ts +12 -5
  96. package/src/framework-lifecycle.ts +2 -2
  97. package/src/html-entries.ts +1 -1
  98. package/src/implementations.ts +2 -2
  99. package/src/in-operands.ts +45 -0
  100. package/src/indexed-access.ts +304 -146
  101. package/src/injection-container.ts +1 -1
  102. package/src/interface-satisfaction.ts +2 -2
  103. package/src/intra-function-parts.ts +42 -17
  104. package/src/inventory.ts +34 -2
  105. package/src/keyof-comparisons.ts +211 -0
  106. package/src/kinds.ts +20 -7
  107. package/src/manifest.ts +6 -21
  108. package/src/matrix.ts +34 -2
  109. package/src/member-names.ts +1 -1
  110. package/src/module-declarations.ts +1 -1
  111. package/src/namespace-objects.ts +1 -1
  112. package/src/outside-tests.ts +1 -1
  113. package/src/overrides.ts +2 -2
  114. package/src/position.ts +1 -1
  115. package/src/query.ts +12 -2
  116. package/src/references.ts +25 -3
  117. package/src/reflective-lookup.ts +2 -2
  118. package/src/report.ts +3 -1
  119. package/src/reporters/sarif.ts +4 -0
  120. package/src/roots.ts +2 -2
  121. package/src/serialization-contract.ts +55 -16
  122. package/src/session.ts +39 -32
  123. package/src/signatures.ts +1 -1
  124. package/src/source-files.ts +1 -1
  125. package/src/stores.ts +1 -1
  126. package/src/suppress-file.ts +68 -5
  127. package/src/suppress-inline.ts +15 -5
  128. package/src/suppress.ts +63 -28
  129. package/src/symbol-keys.ts +2 -2
  130. package/src/type-errors.ts +2 -2
  131. package/src/type-query-alias.ts +1 -1
  132. package/src/unimplemented-callees.ts +411 -0
  133. package/src/value-flow.ts +1 -1
  134. package/src/value-reach.ts +12 -8
  135. package/src/verbs/analyze.ts +15 -9
  136. package/src/workflow-steps.ts +1 -1
  137. package/src/workspace-programs.ts +4 -4
@@ -4,8 +4,8 @@
4
4
  * that parameter, and a call of a method of a generic receiver fixes the receiver's by its
5
5
  * type arguments. The type query of a type-query alias is no reference.
6
6
  */
7
- import { isArrowFunction, isCallExpression, isClassDeclaration, isClassExpression, isFunctionDeclaration, isFunctionExpression, isIdentifier, isNonNullExpression, isParenthesizedExpression, isNamedImports, isNamespaceImport, isImportDeclaration, isIndexedAccessTypeNode, isInterfaceDeclaration, isLiteralTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isParenthesizedTypeNode, isPropertyAccessExpression, isStringLiteral, isTypeAliasDeclaration, isTypeReferenceNode, isUnionTypeNode, isVariableDeclaration, } from "@typescript/native/unstable/ast";
8
- import { SymbolFlags } from "@typescript/native/unstable/sync";
7
+ import { isArrowFunction, isBindingElement, isCallExpression, isClassDeclaration, isClassExpression, isFunctionDeclaration, isFunctionExpression, isFunctionTypeNode, isIdentifier, isIndexedAccessTypeNode, isInterfaceDeclaration, isLiteralTypeNode, isMappedTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isParenthesizedTypeNode, isPropertyAccessExpression, isPropertySignatureDeclaration, isStringLiteral, isTypeAliasDeclaration, isTypeOperatorNode, isTypeReferenceNode, isUnionTypeNode, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
8
+ import { SignatureKind, SymbolFlags, } from "@typescript/native/sync";
9
9
  import { nodeKey } from "./inventory.js";
10
10
  import { renderPosition } from "./position.js";
11
11
  import { UNANSWERED } from "./query.js";
@@ -16,6 +16,7 @@ function isGeneric(node) {
16
16
  isMethodSignatureDeclaration(node) ||
17
17
  isArrowFunction(node) ||
18
18
  isFunctionExpression(node) ||
19
+ isFunctionTypeNode(node) ||
19
20
  isTypeAliasDeclaration(node) ||
20
21
  isInterfaceDeclaration(node) ||
21
22
  isClassDeclaration(node) ||
@@ -26,6 +27,29 @@ function isGeneric(node) {
26
27
  function isReceiver(node) {
27
28
  return isInterfaceDeclaration(node) || isClassDeclaration(node) || isClassExpression(node);
28
29
  }
30
+ /**
31
+ * The class or the interface one member function belongs to: a method's parent, or for a
32
+ * function type a property declares, the declaration that holds the property.
33
+ */
34
+ function receiverOf(member) {
35
+ const parent = member.parent;
36
+ if (isFunctionTypeNode(member) &&
37
+ parent !== undefined &&
38
+ isPropertySignatureDeclaration(parent)) {
39
+ return parent.parent;
40
+ }
41
+ return parent;
42
+ }
43
+ /** Whether a member function of a generic receiver declares no type parameter of its own. */
44
+ function isReceiverMember(node) {
45
+ if (!(isMethodDeclaration(node) ||
46
+ isMethodSignatureDeclaration(node) ||
47
+ isFunctionTypeNode(node))) {
48
+ return false;
49
+ }
50
+ const receiver = receiverOf(node);
51
+ return receiver !== undefined && isReceiver(receiver) && isGeneric(receiver);
52
+ }
29
53
  function unwrappedType(node) {
30
54
  return isParenthesizedTypeNode(node) ? unwrappedType(node.type) : node;
31
55
  }
@@ -86,6 +110,28 @@ function namedParameter(node, enclosing) {
86
110
  }
87
111
  return undefined;
88
112
  }
113
+ /**
114
+ * The constraint of the mapped type whose type parameter one type node names whole: a
115
+ * mapped type instantiates what it holds once per constituent of that constraint.
116
+ */
117
+ function mappedConstraint(node) {
118
+ const type = unwrappedType(node);
119
+ if (!isTypeReferenceNode(type) ||
120
+ !isIdentifier(type.typeName) ||
121
+ type.typeArguments !== undefined) {
122
+ return undefined;
123
+ }
124
+ const spelled = type.typeName.text;
125
+ for (let at = type.parent; at !== undefined; at = at.parent) {
126
+ if (isMappedTypeNode(at)) {
127
+ const { name, constraint } = at.typeParameter;
128
+ if (isIdentifier(name) && name.text === spelled) {
129
+ return constraint;
130
+ }
131
+ }
132
+ }
133
+ return undefined;
134
+ }
89
135
  /** The name a site writes to reach one generic: its own, or its variable's. */
90
136
  function calledName(generic) {
91
137
  const own = generic.name;
@@ -93,15 +139,23 @@ function calledName(generic) {
93
139
  return own.text;
94
140
  }
95
141
  const parent = generic.parent;
96
- return parent !== undefined && isVariableDeclaration(parent) && isIdentifier(parent.name)
142
+ return parent !== undefined &&
143
+ (isVariableDeclaration(parent) || isPropertySignatureDeclaration(parent)) &&
144
+ isIdentifier(parent.name)
97
145
  ? parent.name.text
98
146
  : undefined;
99
147
  }
100
148
  /** The generic a declaration a symbol names is: the function a variable is initialized with included. */
101
149
  function genericOf(node) {
102
- if (isGeneric(node)) {
150
+ if (isGeneric(node) || isReceiverMember(node)) {
103
151
  return node;
104
152
  }
153
+ if (isPropertySignatureDeclaration(node)) {
154
+ // The typings mark the annotation present; a signature written without one has none.
155
+ const declared = node.type;
156
+ const type = declared === undefined ? undefined : unwrappedType(declared);
157
+ return type !== undefined && isFunctionTypeNode(type) ? genericOf(type) : undefined;
158
+ }
105
159
  if (isVariableDeclaration(node) &&
106
160
  node.initializer !== undefined &&
107
161
  isGeneric(node.initializer)) {
@@ -109,29 +163,60 @@ function genericOf(node) {
109
163
  }
110
164
  return undefined;
111
165
  }
166
+ /** The operand of each `keyof` one type node holds, through every type node it is built of. */
167
+ function keyofOperands(node) {
168
+ const found = [];
169
+ const visit = (at) => {
170
+ if (isTypeOperatorNode(at) && at.operator === SyntaxKind.KeyOfKeyword) {
171
+ found.push(at.type);
172
+ return;
173
+ }
174
+ at.forEachChild(visit);
175
+ };
176
+ visit(node);
177
+ return found;
178
+ }
112
179
  /**
113
180
  * Each indexed access inside one generic whose index names a type parameter of it, read
114
181
  * from its own text: the object side is a type parameter of the same generic, one of the
115
- * receiver it is a method of, or a type it writes.
182
+ * receiver it is a member of, or a type it writes. A type parameter whose constraint holds
183
+ * `keyof X` is an index into `X` as `X[K]` is, and so is a parameter whose declared type
184
+ * holds `keyof X`, indexed by the argument a call passes it.
116
185
  */
117
186
  function carriedIn(generic) {
118
187
  const found = [];
119
- const receiver = generic.parent;
188
+ const receiver = receiverOf(generic);
120
189
  const outer = receiver !== undefined && isReceiver(receiver) && isGeneric(receiver) ? receiver : undefined;
190
+ const sideOf = (object) => {
191
+ const own = namedParameter(object, [generic]);
192
+ const fromReceiver = own === undefined && outer !== undefined ? namedParameter(object, [outer]) : undefined;
193
+ return own !== undefined
194
+ ? { kind: "parameter", index: own.index }
195
+ : fromReceiver !== undefined
196
+ ? { kind: "receiver", index: fromReceiver.index }
197
+ : { kind: "written", node: object };
198
+ };
199
+ (generic.typeParameters ?? []).forEach((parameter, index) => {
200
+ const constraint = parameter.constraint;
201
+ for (const object of constraint === undefined ? [] : keyofOperands(constraint)) {
202
+ found.push({ object: sideOf(object), index: { kind: "parameter", index } });
203
+ }
204
+ });
205
+ (generic.parameters ?? []).forEach((parameter, index) => {
206
+ for (const object of parameter.type === undefined ? [] : keyofOperands(parameter.type)) {
207
+ if (namedParameter(object, [generic]) === undefined) {
208
+ found.push({ object: sideOf(object), index: { kind: "argument", index } });
209
+ }
210
+ }
211
+ });
121
212
  const visit = (node) => {
122
213
  if (isIndexedAccessTypeNode(node)) {
123
214
  const index = namedParameter(node.indexType, [generic]);
124
215
  if (index !== undefined) {
125
- const own = namedParameter(node.objectType, [generic]);
126
- const fromReceiver = own === undefined && outer !== undefined
127
- ? namedParameter(node.objectType, [outer])
128
- : undefined;
129
- const object = own !== undefined
130
- ? { kind: "parameter", index: own.index }
131
- : fromReceiver !== undefined
132
- ? { kind: "receiver", index: fromReceiver.index }
133
- : { kind: "written", node: node.objectType };
134
- found.push({ object, index: { kind: "parameter", index: index.index } });
216
+ found.push({
217
+ object: sideOf(node.objectType),
218
+ index: { kind: "parameter", index: index.index },
219
+ });
135
220
  }
136
221
  }
137
222
  if (node !== generic && isGeneric(node)) {
@@ -153,11 +238,8 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
153
238
  const carried = new Map();
154
239
  const names = new Set();
155
240
  const sites = [];
156
- /**
157
- * Calls passing a literal argument whose callee a package binding heads, or that call a
158
- * method, which a dependency's generic may fix.
159
- */
160
- const literalCalls = [];
241
+ /** Calls passing an argument, which a generic declared outside the own files may fix. */
242
+ const argumentCalls = [];
161
243
  const carry = (generic, one) => {
162
244
  const known = carried.get(generic) ?? [];
163
245
  const same = known.some((other) => JSON.stringify(sideKey(other)) === JSON.stringify(sideKey(one)));
@@ -177,7 +259,6 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
177
259
  continue;
178
260
  }
179
261
  const aliasTypes = new Set(typeQueryAliases(file).map((alias) => alias.type));
180
- const imported = packageBindings(file);
181
262
  const visit = (node, from, enclosing) => {
182
263
  const own = held.declarations.get(nodeKey(file, node));
183
264
  const holder = own ?? from;
@@ -206,13 +287,8 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
206
287
  if (name !== undefined) {
207
288
  const site = { file, from: holder, node, name, enclosing: inside };
208
289
  sites.push(site);
209
- const literal = (node.arguments ?? []).some((argument) => isStringLiteral(argument) || isNumericLiteral(argument));
210
- const head = rootName(callee);
211
- const method = isPropertyAccessExpression(callee);
212
- if (literal &&
213
- isCallExpression(node) &&
214
- (method || (head !== undefined && imported.has(head)))) {
215
- literalCalls.push(site);
290
+ if (isCallExpression(node) && node.arguments.length > 0) {
291
+ argumentCalls.push(site);
216
292
  }
217
293
  }
218
294
  }
@@ -240,6 +316,47 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
240
316
  return generic === undefined ? [] : [generic];
241
317
  });
242
318
  };
319
+ /**
320
+ * The type a call's callee is read off: the receiver of a property access, the value an
321
+ * object binding pattern destructures the callee from, or the value whose member a
322
+ * variable holding the callee is initialized with.
323
+ */
324
+ const receiverTypeOf = (callee) => {
325
+ if (isPropertyAccessExpression(callee)) {
326
+ const type = queries.typeAt(callee.expression);
327
+ const args = type === UNANSWERED || type === undefined ? [] : queries.typeArguments(type);
328
+ if (type !== UNANSWERED && type !== undefined && args !== UNANSWERED && args.length > 0) {
329
+ return type;
330
+ }
331
+ }
332
+ const name = isPropertyAccessExpression(callee) ? callee.name : callee;
333
+ const [named] = queries.symbolsAt([name]);
334
+ const symbol = named === UNANSWERED || named === undefined || (named.flags & SymbolFlags.Alias) === 0
335
+ ? named
336
+ : queries.aliased(named);
337
+ if (symbol === UNANSWERED || symbol === undefined) {
338
+ return undefined;
339
+ }
340
+ for (const handle of symbol.declarations) {
341
+ const node = project.declarationAt(handle)?.node;
342
+ const holder = node !== undefined &&
343
+ isBindingElement(node) &&
344
+ isObjectBindingPattern(node.parent) &&
345
+ isVariableDeclaration(node.parent.parent)
346
+ ? node.parent.parent.initializer
347
+ : node !== undefined &&
348
+ isVariableDeclaration(node) &&
349
+ node.initializer !== undefined &&
350
+ isPropertyAccessExpression(node.initializer)
351
+ ? node.initializer.expression
352
+ : undefined;
353
+ const type = holder === undefined ? undefined : queries.typeAt(holder);
354
+ if (type !== UNANSWERED && type !== undefined) {
355
+ return type;
356
+ }
357
+ }
358
+ return undefined;
359
+ };
243
360
  /** What one site fixes for one side of a carried access. */
244
361
  const fixedAt = (site, side, generic) => {
245
362
  if (side.kind === "written") {
@@ -248,23 +365,26 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
248
365
  const { node } = site;
249
366
  if (side.kind === "receiver") {
250
367
  const callee = isCallExpression(node) ? node.expression : undefined;
251
- if (callee === undefined || !isPropertyAccessExpression(callee)) {
252
- return undefined;
253
- }
254
- const receiver = queries.typeAt(callee.expression);
255
- if (receiver === UNANSWERED || receiver === undefined) {
368
+ const receiver = callee === undefined ? undefined : receiverTypeOf(callee);
369
+ if (receiver === undefined) {
256
370
  return undefined;
257
371
  }
258
372
  const args = queries.typeArguments(receiver);
259
373
  const type = args === UNANSWERED ? undefined : args[side.index];
260
374
  return type === undefined ? undefined : { kind: "type", type };
261
375
  }
376
+ if (side.kind === "argument") {
377
+ const argument = isTypeReferenceNode(node) ? undefined : node.arguments?.[side.index];
378
+ const type = argument === undefined ? undefined : queries.typeAt(argument);
379
+ return type === UNANSWERED || type === undefined ? undefined : { kind: "type", type };
380
+ }
262
381
  const written = node.typeArguments?.[side.index];
263
382
  if (written !== undefined) {
264
383
  const passed = namedParameter(written, site.enclosing);
265
- return passed === undefined
266
- ? { kind: "node", node: written }
267
- : { kind: "passed", generic: passed.generic, index: passed.index };
384
+ const mapped = passed === undefined ? mappedConstraint(written) : undefined;
385
+ return passed !== undefined
386
+ ? { kind: "passed", generic: passed.generic, index: passed.index }
387
+ : { kind: "node", node: mapped ?? written };
268
388
  }
269
389
  if (isTypeReferenceNode(node)) {
270
390
  return undefined;
@@ -358,40 +478,120 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
358
478
  }
359
479
  });
360
480
  }
361
- // A generic function or method declared outside the project's own files carries what
362
- // its own text indexes by its type parameters and its receiver's.
363
- const outside = literalCalls.filter((site) => !resolvedGenerics.has(site));
364
- const methods = outside.length === 0
365
- ? []
366
- : queries.symbolsAt(outside.map((site) => {
367
- const callee = site.node.expression;
368
- return isPropertyAccessExpression(callee) ? callee.name : callee;
369
- }));
481
+ // A generic declared outside the project's own files carries what its own text indexes
482
+ // by its type parameters and its receiver's. Only a call passing an argument the compiler
483
+ // types as a literal can fix an index, so only such a call's signature is resolved.
484
+ const outside = argumentCalls.filter((site) => !resolvedGenerics.has(site));
485
+ const argumentNodes = argumentCalls.flatMap((site) => [
486
+ ...site.node.arguments,
487
+ ]);
488
+ const argumentTypes = queries.typesAt(argumentNodes);
489
+ const literalArgument = new Set();
490
+ argumentTypes.forEach((type, at) => {
491
+ const node = argumentNodes[at];
492
+ if (node !== undefined &&
493
+ type !== UNANSWERED &&
494
+ type !== undefined &&
495
+ (literalTypeNames(type, partsOf)?.length ?? 0) > 0) {
496
+ literalArgument.add(node);
497
+ }
498
+ });
370
499
  const own = project.ownPaths();
371
- outside.forEach((site, at) => {
372
- const named = methods[at];
373
- const symbol = named === UNANSWERED || named === undefined || (named.flags & SymbolFlags.Alias) === 0
374
- ? named
375
- : queries.aliased(named);
376
- if (symbol === UNANSWERED ||
377
- symbol === undefined ||
378
- (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) === 0) {
379
- return;
500
+ for (const site of outside) {
501
+ if (!site.node.arguments.some((one) => literalArgument.has(one))) {
502
+ continue;
380
503
  }
381
- for (const handle of symbol.declarations) {
382
- if (own.has(handle.path)) {
383
- continue;
504
+ const resolved = queries.resolvedSignature(site.node);
505
+ const handle = resolved === UNANSWERED || resolved === undefined ? undefined : resolved.declaration;
506
+ if (handle === undefined || own.has(handle.path)) {
507
+ continue;
508
+ }
509
+ const node = project.declarationAt(handle)?.node;
510
+ const generic = node === undefined ? undefined : genericOf(node);
511
+ if (generic === undefined) {
512
+ continue;
513
+ }
514
+ for (const one of carriedIn(generic)) {
515
+ readAt(site, generic, one);
516
+ }
517
+ }
518
+ for (const site of argumentCalls) {
519
+ if (site.node.arguments.some((one) => literalArgument.has(one))) {
520
+ accesses.push(...instantiatedAccesses(site));
521
+ }
522
+ }
523
+ /**
524
+ * The indexed accesses the generic signature a call instantiates holds, read through the
525
+ * checker: the signature its receiver's instantiation gives, so an object type its
526
+ * receiver fixes is concrete whatever declaration spells the receiver's type, a type
527
+ * literal a generic function returns among them. An index naming a type parameter is the
528
+ * literal type the call passes at each parameter of that type.
529
+ */
530
+ function instantiatedAccesses(site) {
531
+ const resolved = queries.resolvedSignature(site.node);
532
+ const generic = resolved === UNANSWERED || resolved === undefined ? undefined : queries.genericOf(resolved);
533
+ if (resolved === UNANSWERED ||
534
+ resolved === undefined ||
535
+ generic === UNANSWERED ||
536
+ generic === undefined) {
537
+ return [];
538
+ }
539
+ const count = site.node.arguments.length;
540
+ const parameters = [];
541
+ for (let at = 0; at < count; at += 1) {
542
+ const type = queries.parameterType(generic, at);
543
+ if (type !== UNANSWERED && type !== undefined) {
544
+ parameters[at] = type;
384
545
  }
385
- const node = project.declarationAt(handle)?.node;
386
- const generic = node === undefined ? undefined : genericOf(node);
387
- if (generic === undefined) {
546
+ }
547
+ const result = queries.returnTypeOf(generic);
548
+ const held = [
549
+ ...parameters,
550
+ ...(result === UNANSWERED || result === undefined ? [] : [result]),
551
+ ];
552
+ // A parameter of a callback and a type argument, a rest tuple's among them, hold the
553
+ // accesses one level down: `fn: (payload: M[K]) => void` and `...[M[K]]`.
554
+ const nested = held.flatMap((type) => [
555
+ ...partsOf(type),
556
+ ...partsOf(type).flatMap((part) => {
557
+ const args = queries.typeArguments(part);
558
+ const calls = queries.signaturesOf(part, SignatureKind.Call);
559
+ return [
560
+ ...(args === UNANSWERED ? [] : args),
561
+ ...(calls === UNANSWERED ? [] : calls).flatMap((signature) => signature.parameters.flatMap((_parameter, index) => {
562
+ const one = queries.parameterType(signature, index);
563
+ return one === UNANSWERED || one === undefined ? [] : [one];
564
+ })),
565
+ ];
566
+ }),
567
+ ]);
568
+ const found = [];
569
+ for (const type of nested) {
570
+ const access = queries.indexedAccessOf(type);
571
+ if (access === UNANSWERED || access === undefined || access.object.isTypeParameter()) {
388
572
  continue;
389
573
  }
390
- for (const one of carriedIn(generic)) {
391
- readAt(site, generic, one);
574
+ const names = access.index.isTypeParameter()
575
+ ? parameters.flatMap((parameter, at) => {
576
+ if (parameter.id !== access.index.id) {
577
+ return [];
578
+ }
579
+ const passed = queries.typeAtPosition(resolved, at);
580
+ return passed === UNANSWERED ? [] : (literalTypeNames(passed, partsOf) ?? []);
581
+ })
582
+ : (literalTypeNames(access.index, partsOf) ?? []);
583
+ if (names.length > 0) {
584
+ found.push({
585
+ file: site.file,
586
+ from: site.from,
587
+ at: site.node,
588
+ object: access.object,
589
+ index: names,
590
+ });
392
591
  }
393
592
  }
394
- });
593
+ return found;
594
+ }
395
595
  const typeNodes = accesses.flatMap((one) => [
396
596
  ...("kind" in one.object ? [one.object] : []),
397
597
  ...(!Array.isArray(one.index) && "kind" in one.index ? [one.index] : []),
@@ -443,67 +643,6 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
443
643
  }
444
644
  return found;
445
645
  }
446
- /**
447
- * The local names one file binds to a package: each name an import declaration with a
448
- * bare specifier binds, and each variable initialized by a call or a construction whose
449
- * callee such a name heads, read in source order.
450
- */
451
- function packageBindings(file) {
452
- const names = new Set();
453
- for (const statement of file.statements) {
454
- if (!isImportDeclaration(statement) ||
455
- !isStringLiteral(statement.moduleSpecifier) ||
456
- /^[./]/u.test(statement.moduleSpecifier.text)) {
457
- continue;
458
- }
459
- const clause = statement.importClause;
460
- if (clause?.name !== undefined) {
461
- names.add(clause.name.text);
462
- }
463
- const bindings = clause?.namedBindings;
464
- if (bindings !== undefined && isNamespaceImport(bindings)) {
465
- names.add(bindings.name.text);
466
- }
467
- else if (bindings !== undefined && isNamedImports(bindings)) {
468
- bindings.elements.forEach((element) => names.add(element.name.text));
469
- }
470
- }
471
- if (names.size === 0) {
472
- return names;
473
- }
474
- const visit = (node) => {
475
- if (isVariableDeclaration(node) &&
476
- isIdentifier(node.name) &&
477
- node.initializer !== undefined &&
478
- (isCallExpression(node.initializer) || isNewExpression(node.initializer))) {
479
- const head = rootName(node.initializer.expression);
480
- if (head !== undefined && names.has(head)) {
481
- names.add(node.name.text);
482
- }
483
- }
484
- node.forEachChild(visit);
485
- };
486
- visit(file);
487
- return names;
488
- }
489
- /** The identifier an expression is headed by, through property accesses and calls. */
490
- function rootName(expression) {
491
- let at = expression;
492
- for (;;) {
493
- if (isIdentifier(at)) {
494
- return at.text;
495
- }
496
- if (isPropertyAccessExpression(at) || isCallExpression(at) || isNewExpression(at)) {
497
- at = at.expression;
498
- continue;
499
- }
500
- if (isParenthesizedExpression(at) || isNonNullExpression(at)) {
501
- at = at.expression;
502
- continue;
503
- }
504
- return undefined;
505
- }
506
- }
507
646
  /** What makes two carried accesses one: the kind and position of each side, or its written node. */
508
647
  function sideKey(one) {
509
648
  const key = (side) => side.kind === "written" ? ["written", side.node.pos, side.node.end] : [side.kind, side.index];
@@ -5,7 +5,7 @@
5
5
  * class's included. A constructor and its parameters are no declarations of the
6
6
  * inventory, so the class stands for them and the registration referencing it keeps it.
7
7
  */
8
- import { isClassDeclaration, isClassExpression, isConstructorDeclaration, isGetAccessorDeclaration, isPropertyDeclaration, isSetAccessorDeclaration, } from "@typescript/native/unstable/ast";
8
+ import { isClassDeclaration, isClassExpression, isConstructorDeclaration, isGetAccessorDeclaration, isPropertyDeclaration, isSetAccessorDeclaration, } from "@typescript/native/ast";
9
9
  import { aliasChains } from "./alias-chain.js";
10
10
  import { calleeName, callsOf, classesPassed, decoratorsOf, spelledDecorator } from "./calls.js";
11
11
  import { classMembers } from "./class-members.js";
@@ -6,8 +6,8 @@
6
6
  * class, and a position typed by a type parameter reaches its constraint. The contextual
7
7
  * lookup has no batch form, so it is asked only of a value typed as a target class.
8
8
  */
9
- import { isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isHeritageClause, isMethodDeclaration, isNewExpression, isObjectLiteralExpression, isSetAccessorDeclaration, ModifierFlags, SyntaxKind, } from "@typescript/native/unstable/ast";
10
- import { SymbolFlags } from "@typescript/native/unstable/sync";
9
+ import { isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isHeritageClause, isMethodDeclaration, isNewExpression, isObjectLiteralExpression, isSetAccessorDeclaration, ModifierFlags, SyntaxKind, } from "@typescript/native/ast";
10
+ import { SymbolFlags } from "@typescript/native/sync";
11
11
  import { aliasChains } from "./alias-chain.js";
12
12
  import { emptyImplementation } from "./implementations.js";
13
13
  import { nodeKey } from "./inventory.js";
@@ -4,7 +4,7 @@
4
4
  * call sites that use or discard it, each statement control flow cannot reach, each
5
5
  * write to a local that no read reaches, and each case an earlier case covers.
6
6
  */
7
- import { isArrayBindingPattern, isArrowFunction, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isBindingElement, isCallExpression, isCaseClause, isCatchClause, isClassDeclaration, isClassExpression, isContinueStatement, isDecorator, isDefaultClause, isEmptyStatement, isExportAssignment, isExportSpecifier, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isHeritageClause, isIdentifier, isIfStatement, isImportClause, isImportSpecifier, isInterfaceDeclaration, isMethodDeclaration, isModuleDeclaration, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isStringLiteral, isSwitchStatement, isThrowStatement, isTypeAliasDeclaration, isTypeReferenceNode, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/unstable/ast";
7
+ import { isArrayBindingPattern, isArrowFunction, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isBindingElement, isCallExpression, isCaseClause, isCatchClause, isClassDeclaration, isClassExpression, isContinueStatement, isDecorator, isDefaultClause, isEmptyStatement, isExportAssignment, isExportSpecifier, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isHeritageClause, isIdentifier, isIfStatement, isImportClause, isImportSpecifier, isInterfaceDeclaration, isMethodDeclaration, isModuleDeclaration, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isStringLiteral, isSwitchStatement, isThrowStatement, isTypeAliasDeclaration, isTypeReferenceNode, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
8
8
  import { nodeKey } from "./inventory.js";
9
9
  import { positionKey, renderPosition } from "./position.js";
10
10
  import { isAnswered, UNANSWERED } from "./query.js";
@@ -46,16 +46,15 @@ function calleeName(call) {
46
46
  }
47
47
  return isPropertyAccessExpression(callee) && isIdentifier(callee.name) ? callee.name : undefined;
48
48
  }
49
- /**
50
- * Whether a call's value is discarded: the call is a statement of its own, awaited as a
51
- * statement, or the operand of `void`.
52
- */
53
- function discarded(call) {
49
+ /** What a call does with its value. */
50
+ function callUse(call) {
54
51
  const parent = outerParent(call);
55
52
  if (isExpressionStatement(parent) || isVoidExpression(parent)) {
56
- return true;
53
+ return "discarded";
57
54
  }
58
- return isAwaitExpression(parent) && isExpressionStatement(outerParent(parent));
55
+ return isAwaitExpression(parent) && isExpressionStatement(outerParent(parent))
56
+ ? "awaited"
57
+ : "used";
59
58
  }
60
59
  /** Whether one statement leaves its statement list on every path: the rest of the list never runs. */
61
60
  function abrupt(statement) {
@@ -348,7 +347,7 @@ export function projectParts(project, held, references, targetRoot) {
348
347
  if (isCallExpression(node)) {
349
348
  const name = calleeName(node);
350
349
  if (name !== undefined) {
351
- calls.set(position(name), discarded(node));
350
+ calls.set(position(name), callUse(node));
352
351
  }
353
352
  }
354
353
  if (isImportSpecifier(node) || isExportSpecifier(node)) {
@@ -728,7 +727,9 @@ function deadStores(file, root, block, holder, locals, ids) {
728
727
  * whether it is used is the call sites' to say, which the run decides over every project.
729
728
  */
730
729
  function resultParts(project, root, sites, references, calls) {
731
- const discardedAt = new Set(references.filter((one) => calls.get(positionKey(one.position)) === true).map((one) => one.to));
730
+ const discardedAt = new Set(references
731
+ .filter((one) => (calls.get(positionKey(one.position)) ?? "used") !== "used")
732
+ .map((one) => one.to));
732
733
  const found = [];
733
734
  for (const { file, declared } of sites) {
734
735
  const { signature, node, id } = declared;
@@ -750,6 +751,8 @@ function resultParts(project, root, sites, references, calls) {
750
751
  continue;
751
752
  }
752
753
  const at = signature.type ?? node.name ?? node;
754
+ const valued = project.queries.nonNullable(result);
755
+ const members = isAnswered(valued) ? project.queries.propertiesOf(valued) : UNANSWERED;
753
756
  found.push({
754
757
  code: UNUSED_RESULT,
755
758
  declaration: id,
@@ -758,6 +761,7 @@ function resultParts(project, root, sites, references, calls) {
758
761
  position: spanOf(file, root, at),
759
762
  message: "result is discarded at every call site",
760
763
  used: false,
764
+ thenable: !isAnswered(members) || members.some((member) => member.name === "then"),
761
765
  });
762
766
  }
763
767
  return found;
@@ -789,6 +793,7 @@ export function intraFunctionFacts(projects, references, exempted) {
789
793
  }
790
794
  const valued = new Set();
791
795
  const called = new Map();
796
+ const awaited = new Set();
792
797
  for (const reference of references) {
793
798
  if (reference.use === "decorator" || reference.use === "evaluation") {
794
799
  continue;
@@ -796,7 +801,10 @@ export function intraFunctionFacts(projects, references, exempted) {
796
801
  const at = positionKey(reference.position);
797
802
  const call = reference.consumer === undefined ? calls.get(at) : undefined;
798
803
  if (call !== undefined) {
799
- called.set(reference.to, (called.get(reference.to) ?? true) && call);
804
+ called.set(reference.to, (called.get(reference.to) ?? true) && call !== "used");
805
+ if (call === "awaited") {
806
+ awaited.add(reference.to);
807
+ }
800
808
  }
801
809
  else if (reference.consumer !== undefined || !specifiers.has(at)) {
802
810
  valued.add(reference.to);
@@ -810,6 +818,12 @@ export function intraFunctionFacts(projects, references, exempted) {
810
818
  const freeButValued = new Set(signatures
811
819
  .map((one) => one.declaration)
812
820
  .filter((id) => !fixed.has(id) && !exempted.has(id) && valued.has(id)));
813
- const discardedEverywhere = new Set([...called].filter(([id, all]) => all && !valued.has(id)).map(([id]) => id));
821
+ // Awaiting a thenable result sequences on it, so a call that awaits one uses it.
822
+ const sequenced = new Set([...parts.values()]
823
+ .filter((part) => part.thenable === true && awaited.has(part.declaration))
824
+ .map((part) => part.declaration));
825
+ const discardedEverywhere = new Set([...called]
826
+ .filter(([id, all]) => all && !valued.has(id) && !sequenced.has(id))
827
+ .map(([id]) => id));
814
828
  return { parts: [...parts.values()], free, freeButValued, discardedEverywhere };
815
829
  }