@cplieger/deadset-ts 8.0.0-dev.15 → 8.0.0-dev.17

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 (207) 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 +94 -54
  4. package/dist/src/augmentations.js +23 -24
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +25 -8
  7. package/dist/src/commands/read.js +6 -7
  8. package/dist/src/commands/tools.js +5 -9
  9. package/dist/src/component-files.js +3 -2
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/consumers.js +9 -10
  12. package/dist/src/contextual-uses.js +1 -17
  13. package/dist/src/contract/config.generated.js +5 -2
  14. package/dist/src/contract/contract.generated.js +119 -2
  15. package/dist/src/contract/report.generated.js +6 -2
  16. package/dist/src/dependencies.js +98 -71
  17. package/dist/src/destructuring.js +1 -19
  18. package/dist/src/element-access.js +179 -164
  19. package/dist/src/entries/build-scripts.js +222 -0
  20. package/dist/src/{configuration-files.js → entries/configuration-files.js} +22 -64
  21. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  22. package/dist/src/entries/conventions.js +739 -0
  23. package/dist/src/{html-entries.js → entries/html-entries.js} +9 -12
  24. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +3 -3
  25. package/dist/src/entries/run-directories.js +77 -0
  26. package/dist/src/entries/sources.js +144 -0
  27. package/dist/src/entries/static-module.js +670 -0
  28. package/dist/src/entries/test-runners.js +684 -0
  29. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  30. package/dist/src/extensions.js +37 -0
  31. package/dist/src/facts.js +38 -0
  32. package/dist/src/findings/completion.js +40 -4
  33. package/dist/src/findings/emitters.js +5 -8
  34. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  35. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  36. package/dist/src/findings/self-check.js +36 -20
  37. package/dist/src/findings-pass.js +5 -6
  38. package/dist/src/framework-lifecycle.js +42 -15
  39. package/dist/src/fs/manifest.js +15 -20
  40. package/dist/src/generated-file.js +172 -29
  41. package/dist/src/graph.js +14 -8
  42. package/dist/src/import-bindings.js +73 -28
  43. package/dist/src/index/sites.js +557 -0
  44. package/dist/src/index.js +1 -1
  45. package/dist/src/indexed-access.js +523 -478
  46. package/dist/src/injection-container.js +1 -1
  47. package/dist/src/interface-satisfaction.js +1 -1
  48. package/dist/src/inventory/commonjs.js +62 -17
  49. package/dist/src/inventory/enumerate.js +2 -1
  50. package/dist/src/javascript-file.js +26 -11
  51. package/dist/src/keyof-comparisons.js +124 -103
  52. package/dist/src/load/classify.js +28 -9
  53. package/dist/src/load/diagnostics.js +8 -1
  54. package/dist/src/load/discover.js +15 -7
  55. package/dist/src/load/expected-module.js +2 -2
  56. package/dist/src/load/plan.js +2 -2
  57. package/dist/src/load/specifiers.js +2 -4
  58. package/dist/src/load/synthesize.js +10 -5
  59. package/dist/src/load/workspace-manifests.js +1 -1
  60. package/dist/src/load/workspace-resolution.js +2 -2
  61. package/dist/src/matrix.js +12 -17
  62. package/dist/src/module-declarations.js +70 -85
  63. package/dist/src/overrides.js +134 -112
  64. package/dist/src/paths.js +56 -1
  65. package/dist/src/platform-calls.js +78 -0
  66. package/dist/src/platform-symbols.js +134 -0
  67. package/dist/src/references/read-rule.js +1 -0
  68. package/dist/src/references/read-rules.js +48 -0
  69. package/dist/src/references/sink.js +151 -0
  70. package/dist/src/references/sources.js +450 -0
  71. package/dist/src/references.js +111 -1116
  72. package/dist/src/resolve.js +1 -1
  73. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +7 -4
  74. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  75. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  76. package/dist/src/roots/published-reach.js +412 -0
  77. package/dist/src/roots/root-set.js +489 -0
  78. package/dist/src/roots/unseen-loads.js +236 -0
  79. package/dist/src/serialization-contract.js +9 -4
  80. package/dist/src/source-files.js +5 -9
  81. package/dist/src/stores.js +4 -2
  82. package/dist/src/suppress.js +30 -32
  83. package/dist/src/sweep.js +25 -13
  84. package/dist/src/symbol-keys.js +89 -83
  85. package/dist/src/tests/file-class.js +76 -0
  86. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  87. package/dist/src/{test-support.js → tests/support.js} +5 -12
  88. package/dist/src/type-parameter-reads.js +178 -139
  89. package/dist/src/unimplemented-callees.js +262 -242
  90. package/dist/src/verbs/analyze.js +15 -3
  91. package/dist/src/verbs/describe.js +5 -3
  92. package/dist/src/verbs/explain.js +50 -28
  93. package/dist/src/verbs/print-retained.js +2 -2
  94. package/jsr.json +1 -1
  95. package/package.json +3 -3
  96. package/src/alias-chain.ts +0 -7
  97. package/src/amd.ts +130 -0
  98. package/src/analysis.ts +156 -70
  99. package/src/augmentations.ts +41 -27
  100. package/src/automatic-types.ts +1 -1
  101. package/src/bindings.ts +460 -0
  102. package/src/calls.ts +25 -7
  103. package/src/commands/read.ts +6 -8
  104. package/src/commands/tools.ts +5 -11
  105. package/src/component-files.ts +4 -2
  106. package/src/conformance-record.ts +1 -1
  107. package/src/consumers.ts +12 -11
  108. package/src/contextual-uses.ts +2 -22
  109. package/src/contract/config.generated.ts +5 -2
  110. package/src/contract/contract.generated.ts +138 -2
  111. package/src/contract/report.generated.ts +6 -2
  112. package/src/dependencies.ts +117 -71
  113. package/src/destructuring.ts +1 -31
  114. package/src/element-access.ts +188 -182
  115. package/src/entries/build-scripts.ts +292 -0
  116. package/src/{configuration-files.ts → entries/configuration-files.ts} +33 -82
  117. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  118. package/src/{conventions.ts → entries/conventions.ts} +349 -523
  119. package/src/{html-entries.ts → entries/html-entries.ts} +19 -18
  120. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +5 -5
  121. package/src/entries/run-directories.ts +108 -0
  122. package/src/entries/sources.ts +266 -0
  123. package/src/entries/static-module.ts +858 -0
  124. package/src/entries/test-runners.ts +868 -0
  125. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  126. package/src/exempt.ts +3 -2
  127. package/src/extensions.ts +45 -0
  128. package/src/facts.ts +46 -0
  129. package/src/file-facts.ts +1 -1
  130. package/src/findings/boundary.ts +2 -0
  131. package/src/findings/completion.ts +43 -4
  132. package/src/findings/emitter.ts +1 -1
  133. package/src/findings/emitters.ts +14 -10
  134. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  135. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  136. package/src/findings/self-check.ts +41 -22
  137. package/src/findings/visibility-narrowing.ts +1 -1
  138. package/src/findings-pass.ts +5 -12
  139. package/src/framework-lifecycle.ts +43 -15
  140. package/src/fs/manifest.ts +25 -21
  141. package/src/generated-file.ts +240 -36
  142. package/src/graph.ts +27 -11
  143. package/src/import-bindings.ts +98 -29
  144. package/src/index/sites.ts +781 -0
  145. package/src/index.ts +1 -1
  146. package/src/indexed-access.ts +570 -527
  147. package/src/injection-container.ts +1 -1
  148. package/src/interface-satisfaction.ts +1 -1
  149. package/src/inventory/commonjs.ts +102 -17
  150. package/src/inventory/enumerate.ts +2 -1
  151. package/src/javascript-file.ts +29 -12
  152. package/src/keyof-comparisons.ts +131 -116
  153. package/src/load/classify.ts +49 -13
  154. package/src/load/diagnostics.ts +11 -1
  155. package/src/load/discover.ts +27 -11
  156. package/src/load/expected-module.ts +8 -2
  157. package/src/load/plan.ts +11 -2
  158. package/src/load/specifiers.ts +8 -5
  159. package/src/load/synthesize.ts +12 -6
  160. package/src/load/test-globals.ts +1 -1
  161. package/src/load/workspace-manifests.ts +1 -1
  162. package/src/load/workspace-resolution.ts +9 -2
  163. package/src/load/workspace-run.ts +1 -1
  164. package/src/matrix.ts +13 -17
  165. package/src/module-declarations.ts +74 -98
  166. package/src/overrides.ts +162 -134
  167. package/src/paths.ts +65 -1
  168. package/src/platform-calls.ts +119 -0
  169. package/src/platform-symbols.ts +189 -0
  170. package/src/references/read-rule.ts +60 -0
  171. package/src/references/read-rules.ts +55 -0
  172. package/src/references/sink.ts +213 -0
  173. package/src/references/sources.ts +632 -0
  174. package/src/references.ts +174 -1492
  175. package/src/report.ts +2 -2
  176. package/src/resolve.ts +1 -1
  177. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +10 -7
  178. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  179. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  180. package/src/roots/published-reach.ts +481 -0
  181. package/src/{roots.ts → roots/root-set.ts} +203 -630
  182. package/src/roots/unseen-loads.ts +345 -0
  183. package/src/serialization-contract.ts +9 -4
  184. package/src/source-files.ts +5 -10
  185. package/src/stores.ts +13 -2
  186. package/src/suppress.ts +48 -33
  187. package/src/sweep.ts +37 -14
  188. package/src/symbol-keys.ts +102 -102
  189. package/src/tests/file-class.ts +118 -0
  190. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  191. package/src/{test-support.ts → tests/support.ts} +15 -21
  192. package/src/type-parameter-reads.ts +187 -158
  193. package/src/unimplemented-callees.ts +297 -280
  194. package/src/verbs/analyze.ts +19 -5
  195. package/src/verbs/describe.ts +5 -2
  196. package/src/verbs/explain.ts +75 -32
  197. package/src/verbs/print-retained.ts +2 -2
  198. package/dist/src/build-scripts.js +0 -418
  199. package/dist/src/conventions.js +0 -907
  200. package/dist/src/entry-points.js +0 -444
  201. package/dist/src/own-body.js +0 -28
  202. package/dist/src/roots.js +0 -865
  203. package/dist/src/unseen-loads.js +0 -189
  204. package/src/build-scripts.ts +0 -536
  205. package/src/entry-points.ts +0 -584
  206. package/src/own-body.ts +0 -42
  207. package/src/unseen-loads.ts +0 -282
@@ -41,12 +41,8 @@ import {
41
41
  type SourceFile,
42
42
  } from "@typescript/native/ast";
43
43
  import type { Type } from "@typescript/native/sync";
44
- import type { AliasChains } from "./alias-chain.ts";
45
- import { nodeKey, type Inventory } from "./inventory.ts";
46
- import { renderPosition } from "./position.ts";
47
44
  import { UNANSWERED } from "./query.ts";
48
- import type { Reference } from "./references.ts";
49
- import type { ProjectView } from "./session.ts";
45
+ import type { Read, ReadRule } from "./references/read-rule.ts";
50
46
  import { parenthesesRemoved } from "./unwrap.ts";
51
47
 
52
48
  type Use = "read" | "write" | "both";
@@ -242,190 +238,200 @@ interface Candidate {
242
238
  }
243
239
 
244
240
  /**
245
- * The references the element accesses of one project's own files make to the members their
246
- * index's literal type names, each made by the declaration holding the access or the call.
241
+ * The reads the element accesses of a side's files make of the members their index's literal
242
+ * type names, each made by the declaration holding the access or the call. A type the checker
243
+ * leaves unanswered holds what its answer could have read.
247
244
  */
248
- export function elementAccessReferences<Brand>(
249
- project: ProjectView<Brand>,
250
- held: Inventory,
251
- chains: AliasChains,
252
- root: string,
253
- testFiles: ReadonlySet<string>,
254
- ): readonly Reference[] {
255
- const queries = project.queries;
256
- const candidates: Candidate[] = [];
257
- const calls = new Map<Node, { readonly file: SourceFile; readonly from: string }>();
258
- const names: Identifier[] = [];
259
-
260
- for (const file of project.ownSourceFiles()) {
261
- const fileId = held.declarations.get(nodeKey(file, file));
262
- if (fileId === undefined) {
263
- continue;
264
- }
265
- const visit = (node: Node, from: string): void => {
266
- const holder = held.declarations.get(nodeKey(file, node)) ?? from;
267
- if (isElementAccessExpression(node) && !isLiteralIndex(node.argumentExpression)) {
268
- candidates.push({ file, from: holder, access: node, use: useOf(node) });
269
- }
270
- if (isCallExpression(node)) {
271
- calls.set(node, { file, from: holder });
272
- }
273
- if (isIdentifier(node)) {
274
- names.push(node);
275
- }
276
- node.forEachChild((child) => {
277
- visit(child, holder);
278
- });
279
- };
280
- file.forEachChild((child) => {
281
- visit(child, fileId);
282
- });
283
- }
284
- if (candidates.length === 0) {
285
- return [];
286
- }
287
-
288
- // A parameter index is decided at the calls only where the function is a callable every
289
- // use of which is a call, so the symbols of the parameters and of every name are read.
290
- const parameterOf = new Map<Candidate, { callable: Callable; at: number }>();
291
- const byCallable = new Map<Identifier, Candidate[]>();
292
- for (const candidate of candidates) {
293
- const index = parenthesesRemoved(candidate.access.argumentExpression);
294
- const fn = isIdentifier(index) ? enclosingFunction(candidate.access) : undefined;
295
- const callable = fn === undefined ? undefined : callableOf(fn);
296
- if (callable === undefined || !isIdentifier(index)) {
297
- continue;
298
- }
299
- const at = callable.parameters.findIndex(
300
- (parameter) => isIdentifier(parameter.name) && parameter.name.text === index.text,
301
- );
302
- if (at < 0 || assigns(callable.body, index.text)) {
303
- continue;
304
- }
305
- parameterOf.set(candidate, { callable, at });
306
- byCallable.set(callable.name, [...(byCallable.get(callable.name) ?? []), candidate]);
307
- }
308
- const callableNames = [...byCallable.keys()];
309
- const spelled = new Set(callableNames.map((name) => name.text));
310
- const sameName = names.filter((name) => spelled.has(name.text));
311
- const symbols = queries.symbolsAt([...callableNames, ...sameName]);
312
- const symbolOf = new Map<Node, unknown>();
313
- [...callableNames, ...sameName].forEach((node, at) => {
314
- const symbol = symbols[at];
315
- if (symbol !== UNANSWERED && symbol !== undefined) {
316
- symbolOf.set(node, symbol);
317
- }
318
- });
319
- const callsOf = new Map<Node, CallExpression[] | undefined>();
320
- for (const name of callableNames) {
321
- const symbol = symbolOf.get(name);
322
- let found: CallExpression[] | undefined = symbol === undefined ? undefined : [];
323
- for (const use of sameName) {
324
- if (found === undefined) {
325
- break;
326
- }
327
- if (use === name || symbolOf.get(use) !== symbol) {
328
- continue;
329
- }
330
- const parent = use.parent;
331
- found =
332
- isCallExpression(parent) && parent.expression === use ? [...found, parent] : undefined;
333
- }
334
- callsOf.set(name, found);
335
- }
336
-
337
- const lookups: Lookup[] = [];
338
- for (const candidate of candidates) {
339
- const deferred = parameterOf.get(candidate);
340
- const sites = deferred === undefined ? undefined : callsOf.get(deferred.callable.name);
341
- if (deferred === undefined || sites === undefined) {
342
- lookups.push({
343
- file: candidate.file,
344
- from: candidate.from,
345
- at: candidate.access.argumentExpression,
346
- object: candidate.access.expression,
347
- index: candidate.access.argumentExpression,
348
- use: candidate.use,
349
- });
350
- continue;
351
- }
352
- // An object that is a parameter too is read as the argument each call passes there.
353
- const object = parenthesesRemoved(candidate.access.expression);
354
- const objectAt = isIdentifier(object)
355
- ? deferred.callable.parameters.findIndex(
356
- (parameter) => isIdentifier(parameter.name) && parameter.name.text === object.text,
357
- )
358
- : -1;
359
- for (const call of sites) {
360
- const parameter = deferred.callable.parameters[deferred.at];
361
- const index = call.arguments[deferred.at] ?? parameter?.initializer;
362
- const site = calls.get(call);
363
- if (index === undefined || site === undefined) {
364
- continue;
365
- }
366
- lookups.push({
367
- file: site.file,
368
- from: site.from,
369
- at: call,
370
- object:
371
- (objectAt < 0 ? undefined : call.arguments[objectAt]) ?? candidate.access.expression,
372
- index,
373
- use: candidate.use,
374
- });
375
- }
376
- }
377
-
378
- const nodes = lookups.flatMap((one) => [one.index, one.object]);
379
- const types = queries.typesAt(nodes);
380
- const typeOf = (node: Node, at: number): Type | undefined => {
381
- const type = types[at];
382
- return nodes[at] === node && type !== UNANSWERED ? type : undefined;
383
- };
384
- // A type parameter's body serves every instantiation, so it reads what its constraint admits.
385
- const keysOf = (type: Type): Type => {
386
- if (!type.isTypeParameter()) {
387
- return type;
388
- }
389
- const constraint = queries.constraintOf(type);
390
- return constraint === UNANSWERED || constraint === undefined ? type : constraint;
391
- };
392
- const partsOf = (type: Type): readonly Type[] => {
393
- const parts = queries.constituents(type);
394
- return parts === UNANSWERED ? [type] : parts;
395
- };
245
+ export const elementAccess: ReadRule = {
246
+ start() {
247
+ const candidates: Candidate[] = [];
248
+ const calls = new Map<Node, { readonly file: SourceFile; readonly from: string }>();
249
+ const names: Identifier[] = [];
250
+ return {
251
+ visit(node, { file, from }) {
252
+ if (isElementAccessExpression(node) && !isLiteralIndex(node.argumentExpression)) {
253
+ candidates.push({ file, from, access: node, use: useOf(node) });
254
+ }
255
+ if (isCallExpression(node)) {
256
+ calls.set(node, { file, from });
257
+ }
258
+ if (isIdentifier(node)) {
259
+ names.push(node);
260
+ }
261
+ },
262
+ reads({ project, chains }) {
263
+ const found: Read[] = [];
264
+ if (candidates.length === 0) {
265
+ return found;
266
+ }
267
+ const queries = project.queries;
268
+ // A parameter index is decided at the calls only where the function is a callable every
269
+ // use of which is a call, so the symbols of the parameters and of every name are read.
270
+ const parameterOf = new Map<Candidate, { callable: Callable; at: number }>();
271
+ const byCallable = new Map<Identifier, Candidate[]>();
272
+ for (const candidate of candidates) {
273
+ const index = parenthesesRemoved(candidate.access.argumentExpression);
274
+ const fn = isIdentifier(index) ? enclosingFunction(candidate.access) : undefined;
275
+ const callable = fn === undefined ? undefined : callableOf(fn);
276
+ if (callable === undefined || !isIdentifier(index)) {
277
+ continue;
278
+ }
279
+ const at = callable.parameters.findIndex(
280
+ (parameter) => isIdentifier(parameter.name) && parameter.name.text === index.text,
281
+ );
282
+ if (at < 0 || assigns(callable.body, index.text)) {
283
+ continue;
284
+ }
285
+ parameterOf.set(candidate, { callable, at });
286
+ byCallable.set(callable.name, [...(byCallable.get(callable.name) ?? []), candidate]);
287
+ }
288
+ const callableNames = [...byCallable.keys()];
289
+ const spelled = new Set(callableNames.map((name) => name.text));
290
+ const sameName = names.filter((name) => spelled.has(name.text));
291
+ const asked = [...callableNames, ...sameName];
292
+ const symbols = queries.symbolsAt(asked);
293
+ const symbolOf = new Map<Node, unknown>();
294
+ asked.forEach((node, at) => {
295
+ const symbol = symbols[at];
296
+ if (symbol !== undefined) {
297
+ symbolOf.set(node, symbol);
298
+ }
299
+ });
300
+ const callsOf = new Map<Node, CallExpression[] | undefined>();
301
+ for (const name of callableNames) {
302
+ const symbol = symbolOf.get(name);
303
+ let sites: CallExpression[] | undefined =
304
+ symbol === undefined || symbol === UNANSWERED ? undefined : [];
305
+ for (const use of sameName) {
306
+ if (sites === undefined) {
307
+ break;
308
+ }
309
+ const other = symbolOf.get(use);
310
+ if (use === name || (other !== symbol && other !== UNANSWERED)) {
311
+ continue;
312
+ }
313
+ // A use the checker could not resolve may be the function's, read where it is not called.
314
+ const parent = use.parent;
315
+ sites =
316
+ other !== UNANSWERED && isCallExpression(parent) && parent.expression === use
317
+ ? [...sites, parent]
318
+ : undefined;
319
+ }
320
+ callsOf.set(name, sites);
321
+ }
396
322
 
397
- const found: Reference[] = [];
398
- lookups.forEach((lookup, at) => {
399
- const indexType = typeOf(lookup.index, 2 * at);
400
- const objectType = typeOf(lookup.object, 2 * at + 1);
401
- const literals = indexType === undefined ? undefined : literalNames(partsOf(keysOf(indexType)));
402
- if (objectType === undefined || literals === undefined) {
403
- return;
404
- }
405
- const position = renderPosition(lookup.file, root, lookup.at.getStart());
406
- for (const part of partsOf(objectType)) {
407
- const properties = queries.propertiesOf(part);
408
- for (const property of properties === UNANSWERED ? [] : properties) {
409
- if (!literals.has(property.escapedName)) {
410
- continue;
323
+ const lookups: Lookup[] = [];
324
+ for (const candidate of candidates) {
325
+ const deferred = parameterOf.get(candidate);
326
+ const sites = deferred === undefined ? undefined : callsOf.get(deferred.callable.name);
327
+ if (deferred === undefined || sites === undefined) {
328
+ lookups.push({
329
+ file: candidate.file,
330
+ from: candidate.from,
331
+ at: candidate.access.argumentExpression,
332
+ object: candidate.access.expression,
333
+ index: candidate.access.argumentExpression,
334
+ use: candidate.use,
335
+ });
336
+ continue;
337
+ }
338
+ // An object that is a parameter too is read as the argument each call passes there.
339
+ const object = parenthesesRemoved(candidate.access.expression);
340
+ const objectAt = isIdentifier(object)
341
+ ? deferred.callable.parameters.findIndex(
342
+ (parameter) => isIdentifier(parameter.name) && parameter.name.text === object.text,
343
+ )
344
+ : -1;
345
+ for (const call of sites) {
346
+ const parameter = deferred.callable.parameters[deferred.at];
347
+ const index = call.arguments[deferred.at] ?? parameter?.initializer;
348
+ const site = calls.get(call);
349
+ if (index === undefined || site === undefined) {
350
+ continue;
351
+ }
352
+ lookups.push({
353
+ file: site.file,
354
+ from: site.from,
355
+ at: call,
356
+ object:
357
+ (objectAt < 0 ? undefined : call.arguments[objectAt]) ??
358
+ candidate.access.expression,
359
+ index,
360
+ use: candidate.use,
361
+ });
362
+ }
411
363
  }
412
- for (const to of chains.declarationsOf(property)) {
413
- for (const use of lookup.use === "both" ? (["read", "write"] as const) : [lookup.use]) {
364
+
365
+ const nodes = lookups.flatMap((one) => [one.index, one.object]);
366
+ const types = queries.typesAt(nodes);
367
+ // A type parameter's body serves every instantiation, so it reads what its constraint admits.
368
+ const keysOf = (type: Type): Type => {
369
+ if (!type.isTypeParameter()) {
370
+ return type;
371
+ }
372
+ const constraint = queries.constraintOf(type);
373
+ return constraint === UNANSWERED || constraint === undefined ? type : constraint;
374
+ };
375
+ const partsOf = (type: Type): readonly Type[] => {
376
+ const parts = queries.constituents(type);
377
+ return parts === UNANSWERED ? [type] : parts;
378
+ };
379
+ lookups.forEach((lookup, at) => {
380
+ const indexType = types[2 * at];
381
+ const objectType = types[2 * at + 1];
382
+ const literals =
383
+ indexType === undefined || indexType === UNANSWERED
384
+ ? undefined
385
+ : literalNames(partsOf(keysOf(indexType)));
386
+ if (indexType === UNANSWERED || objectType === UNANSWERED) {
414
387
  found.push({
388
+ kind: "held",
415
389
  from: lookup.from,
416
- to,
417
- position,
418
- use,
419
- resolution: "keyed",
420
- test: testFiles.has(position.path),
390
+ ...(literals === undefined ? {} : { names: [...literals] }),
391
+ ...(objectType === UNANSWERED || objectType === undefined
392
+ ? literals === undefined
393
+ ? { members: true as const }
394
+ : {}
395
+ : { types: partsOf(objectType) }),
421
396
  });
397
+ return;
422
398
  }
423
- }
424
- }
425
- }
426
- });
427
- return found;
428
- }
399
+ if (objectType === undefined || literals === undefined) {
400
+ return;
401
+ }
402
+ for (const part of partsOf(objectType)) {
403
+ const properties = queries.propertiesOf(part);
404
+ if (properties === UNANSWERED) {
405
+ found.push({ kind: "held", from: lookup.from, names: [...literals] });
406
+ continue;
407
+ }
408
+ for (const property of properties) {
409
+ if (!literals.has(property.escapedName)) {
410
+ continue;
411
+ }
412
+ for (const to of chains.declarationsOf(property)) {
413
+ for (const use of lookup.use === "both"
414
+ ? (["read", "write"] as const)
415
+ : [lookup.use]) {
416
+ found.push({
417
+ kind: "named",
418
+ from: lookup.from,
419
+ to,
420
+ file: lookup.file,
421
+ at: lookup.at,
422
+ use,
423
+ via: "keyed",
424
+ });
425
+ }
426
+ }
427
+ }
428
+ }
429
+ });
430
+ return found;
431
+ },
432
+ };
433
+ },
434
+ };
429
435
 
430
436
  /** The names a union of literal types spells, or none where a constituent is no string or number literal. */
431
437
  function literalNames(parts: readonly Type[]): ReadonlySet<string> | undefined {