@cplieger/deadset-ts 6.1.1-dev.1 → 7.0.0-dev.2

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 (98) hide show
  1. package/dist/src/analysis.js +45 -32
  2. package/dist/src/component-layer.js +1 -1
  3. package/dist/src/configuration-files.js +227 -27
  4. package/dist/src/conformance-record.js +2 -2
  5. package/dist/src/contextual-uses.js +68 -10
  6. package/dist/src/convention-rows.js +48 -0
  7. package/dist/src/conventions.js +80 -0
  8. package/dist/src/dependencies.js +121 -46
  9. package/dist/src/discover.js +1 -1
  10. package/dist/src/edges.js +1 -1
  11. package/dist/src/exempt-classes.js +9 -22
  12. package/dist/src/exempt.js +3 -15
  13. package/dist/src/findings/completion.js +41 -11
  14. package/dist/src/findings/dependencies-and-module-machinery.js +6 -26
  15. package/dist/src/findings/emitters.js +0 -4
  16. package/dist/src/findings/self-check.js +14 -10
  17. package/dist/src/html-entries.js +29 -14
  18. package/dist/src/indexed-access.js +377 -81
  19. package/dist/src/intra-function-parts.js +57 -11
  20. package/dist/src/inventory.js +3 -12
  21. package/dist/src/kinds.js +6 -6
  22. package/dist/src/references.js +62 -23
  23. package/dist/src/report.js +3 -0
  24. package/dist/src/roots.js +96 -7
  25. package/dist/src/run.js +0 -2
  26. package/dist/src/schema.js +1 -1
  27. package/dist/src/scope.js +2 -2
  28. package/dist/src/session.js +6 -2
  29. package/dist/src/source-files.js +1 -0
  30. package/dist/src/suppress-inline.js +1 -1
  31. package/dist/src/suppress.js +15 -16
  32. package/dist/src/sweep.js +29 -17
  33. package/dist/src/symbol-keys.js +114 -0
  34. package/dist/src/test-joins.js +101 -0
  35. package/dist/src/version.js +1 -1
  36. package/dist/src/workflow-steps.js +21 -15
  37. package/dist/src/workspace-programs.js +1 -4
  38. package/dist/src/workspace-resolution.js +0 -2
  39. package/dist/src/workspace.js +0 -3
  40. package/jsr.json +1 -1
  41. package/package.json +2 -1
  42. package/src/analysis.ts +55 -47
  43. package/src/calls.ts +3 -3
  44. package/src/component-files.ts +7 -7
  45. package/src/component-layer.ts +2 -2
  46. package/src/components.ts +1 -1
  47. package/src/config.ts +4 -4
  48. package/src/configuration-files.ts +246 -28
  49. package/src/conformance-record.ts +2 -2
  50. package/src/contextual-uses.ts +99 -13
  51. package/src/convention-rows.ts +76 -0
  52. package/src/conventions.ts +102 -0
  53. package/src/dependencies.ts +148 -56
  54. package/src/discover.ts +3 -3
  55. package/src/edges.ts +3 -3
  56. package/src/entry-points.ts +1 -1
  57. package/src/exempt-classes.ts +10 -24
  58. package/src/exempt.ts +5 -29
  59. package/src/file-facts.ts +1 -1
  60. package/src/finding.ts +5 -1
  61. package/src/findings/completion.ts +53 -11
  62. package/src/findings/dependencies-and-module-machinery.ts +9 -28
  63. package/src/findings/emitters.ts +3 -8
  64. package/src/findings/self-check.ts +17 -14
  65. package/src/findings-pass.ts +2 -2
  66. package/src/graph.ts +2 -2
  67. package/src/html-entries.ts +34 -16
  68. package/src/indexed-access.ts +476 -102
  69. package/src/intra-function-parts.ts +84 -12
  70. package/src/inventory.ts +5 -33
  71. package/src/json-document.ts +1 -1
  72. package/src/kinds.ts +7 -6
  73. package/src/manifest.ts +1 -1
  74. package/src/namespace-objects.ts +1 -1
  75. package/src/pnpm-workspace.ts +1 -1
  76. package/src/references.ts +93 -33
  77. package/src/report.ts +8 -4
  78. package/src/roots.ts +129 -9
  79. package/src/run.ts +0 -2
  80. package/src/schema.ts +2 -2
  81. package/src/scope.ts +2 -2
  82. package/src/session.ts +7 -3
  83. package/src/source-files.ts +3 -0
  84. package/src/stores.ts +1 -1
  85. package/src/suppress-inline.ts +3 -3
  86. package/src/suppress.ts +21 -24
  87. package/src/sweep.ts +29 -17
  88. package/src/symbol-keys.ts +172 -0
  89. package/src/template-field.ts +1 -1
  90. package/src/test-joins.ts +120 -0
  91. package/src/value-reach.ts +1 -1
  92. package/src/verbs/verb.ts +1 -4
  93. package/src/version.ts +1 -1
  94. package/src/workflow-steps.ts +23 -16
  95. package/src/workspace-programs.ts +3 -10
  96. package/src/workspace-resolution.ts +0 -3
  97. package/src/workspace-run.ts +1 -1
  98. package/src/workspace.ts +1 -9
@@ -1,11 +1,11 @@
1
1
  /**
2
- * The members an indexed-access type names. `T["a" | "b"]` reads the members `a` and `b`
3
- * of `T` as a property access by either name would. Where the index is a type parameter
4
- * a call fixes, the call names the members the literal type argument names, written or
5
- * inferred, which is read off the parameter the type parameter annotates whole. The type
6
- * query of a type-query alias is no reference.
2
+ * The members an indexed-access type names: `T["a" | "b"]` reads `a` and `b` of `T`. An
3
+ * index naming a type parameter is read once the reference or the call holding it fixes
4
+ * that parameter, and a call of a method of a generic receiver fixes the receiver's by its
5
+ * type arguments. The type query of a type-query alias is no reference.
7
6
  */
8
- import { isArrowFunction, isCallExpression, isFunctionDeclaration, isFunctionExpression, isIdentifier, isIndexedAccessTypeNode, isLiteralTypeNode, isMethodDeclaration, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isParenthesizedTypeNode, isPropertyAccessExpression, isStringLiteral, isTypeReferenceNode, isUnionTypeNode, SyntaxKind, } from "@typescript/native/unstable/ast";
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";
9
9
  import { nodeKey } from "./inventory.js";
10
10
  import { renderPosition } from "./position.js";
11
11
  import { UNANSWERED } from "./query.js";
@@ -13,10 +13,19 @@ import { typeQueryAliases } from "./type-query-alias.js";
13
13
  function isGeneric(node) {
14
14
  return ((isFunctionDeclaration(node) ||
15
15
  isMethodDeclaration(node) ||
16
+ isMethodSignatureDeclaration(node) ||
16
17
  isArrowFunction(node) ||
17
- isFunctionExpression(node)) &&
18
+ isFunctionExpression(node) ||
19
+ isTypeAliasDeclaration(node) ||
20
+ isInterfaceDeclaration(node) ||
21
+ isClassDeclaration(node) ||
22
+ isClassExpression(node)) &&
18
23
  node.typeParameters !== undefined);
19
24
  }
25
+ /** Whether one generic is a receiver's: a class or an interface whose methods it parameterizes. */
26
+ function isReceiver(node) {
27
+ return isInterfaceDeclaration(node) || isClassDeclaration(node) || isClassExpression(node);
28
+ }
20
29
  function unwrappedType(node) {
21
30
  return isParenthesizedTypeNode(node) ? unwrappedType(node.type) : node;
22
31
  }
@@ -52,35 +61,141 @@ function literalTypeNames(type, parts) {
52
61
  }
53
62
  return names;
54
63
  }
64
+ /** The type a fixed side stands for, or none where the site passes it on. */
65
+ function concrete(fixed) {
66
+ return fixed.kind === "node" ? fixed.node : fixed.kind === "type" ? fixed.type : undefined;
67
+ }
68
+ /** The position of the type parameter `name` among the ones a generic declares, or -1. */
69
+ function parameterAt(generic, name) {
70
+ return (generic.typeParameters ?? []).findIndex((parameter) => isIdentifier(parameter.name) && parameter.name.text === name);
71
+ }
72
+ /** The type parameter one type node names whole, among the enclosing generics, innermost first. */
73
+ function namedParameter(node, enclosing) {
74
+ const type = unwrappedType(node);
75
+ if (!isTypeReferenceNode(type) ||
76
+ !isIdentifier(type.typeName) ||
77
+ type.typeArguments !== undefined) {
78
+ return undefined;
79
+ }
80
+ const spelled = type.typeName.text;
81
+ for (const generic of [...enclosing].reverse()) {
82
+ const index = parameterAt(generic, spelled);
83
+ if (index >= 0) {
84
+ return { generic, index };
85
+ }
86
+ }
87
+ return undefined;
88
+ }
89
+ /** The name a site writes to reach one generic: its own, or its variable's. */
90
+ function calledName(generic) {
91
+ const own = generic.name;
92
+ if (own !== undefined && isIdentifier(own)) {
93
+ return own.text;
94
+ }
95
+ const parent = generic.parent;
96
+ return parent !== undefined && isVariableDeclaration(parent) && isIdentifier(parent.name)
97
+ ? parent.name.text
98
+ : undefined;
99
+ }
100
+ /** The generic a declaration a symbol names is: the function a variable is initialized with included. */
101
+ function genericOf(node) {
102
+ if (isGeneric(node)) {
103
+ return node;
104
+ }
105
+ if (isVariableDeclaration(node) &&
106
+ node.initializer !== undefined &&
107
+ isGeneric(node.initializer)) {
108
+ return node.initializer;
109
+ }
110
+ return undefined;
111
+ }
112
+ /**
113
+ * Each indexed access inside one generic whose index names a type parameter of it, read
114
+ * 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.
116
+ */
117
+ function carriedIn(generic) {
118
+ const found = [];
119
+ const receiver = generic.parent;
120
+ const outer = receiver !== undefined && isReceiver(receiver) && isGeneric(receiver) ? receiver : undefined;
121
+ const visit = (node) => {
122
+ if (isIndexedAccessTypeNode(node)) {
123
+ const index = namedParameter(node.indexType, [generic]);
124
+ 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 } });
135
+ }
136
+ }
137
+ if (node !== generic && isGeneric(node)) {
138
+ return;
139
+ }
140
+ node.forEachChild(visit);
141
+ };
142
+ visit(generic);
143
+ return found;
144
+ }
55
145
  /**
56
146
  * The references the indexed-access types of one project's own files make to the members
57
- * they name, each made by the declaration that holds the type or the call, or by the file.
147
+ * they name, each made by the declaration that holds the type, the reference or the call,
148
+ * or by the file.
58
149
  */
59
150
  export function indexedAccessReferences(project, held, chains, root, testFiles) {
60
151
  const queries = project.queries;
61
152
  const accesses = [];
62
- const generics = new Map();
63
- const calls = [];
153
+ const carried = new Map();
64
154
  const names = new Set();
155
+ 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 = [];
161
+ const carry = (generic, one) => {
162
+ const known = carried.get(generic) ?? [];
163
+ const same = known.some((other) => JSON.stringify(sideKey(other)) === JSON.stringify(sideKey(one)));
164
+ if (same) {
165
+ return false;
166
+ }
167
+ carried.set(generic, [...known, one]);
168
+ const name = calledName(generic);
169
+ if (name !== undefined) {
170
+ names.add(name);
171
+ }
172
+ return true;
173
+ };
65
174
  for (const file of project.ownSourceFiles()) {
66
175
  const fileId = held.declarations.get(nodeKey(file, file));
67
176
  if (fileId === undefined) {
68
177
  continue;
69
178
  }
70
179
  const aliasTypes = new Set(typeQueryAliases(file).map((alias) => alias.type));
180
+ const imported = packageBindings(file);
71
181
  const visit = (node, from, enclosing) => {
72
182
  const own = held.declarations.get(nodeKey(file, node));
73
183
  const holder = own ?? from;
74
184
  const inside = isGeneric(node) ? [...enclosing, node] : enclosing;
185
+ if (isGeneric(node)) {
186
+ carriedIn(node).forEach((one) => carry(node, one));
187
+ }
75
188
  if (isIndexedAccessTypeNode(node) && !aliasTypes.has(node)) {
76
189
  const literal = literalNames(node.indexType);
77
190
  if (literal !== undefined) {
78
- accesses.push({ file, from: holder, at: node, object: node.objectType, names: literal });
79
- }
80
- else {
81
- parameterIndex(node, inside, file, generics, names);
191
+ accesses.push({ file, from: holder, at: node, object: node.objectType, index: literal });
82
192
  }
83
193
  }
194
+ if (isTypeReferenceNode(node) &&
195
+ node.typeArguments !== undefined &&
196
+ isIdentifier(node.typeName)) {
197
+ sites.push({ file, from: holder, node, name: node.typeName.text, enclosing: inside });
198
+ }
84
199
  if (isCallExpression(node) || isNewExpression(node)) {
85
200
  const callee = node.expression;
86
201
  const name = isIdentifier(callee)
@@ -89,7 +204,16 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
89
204
  ? callee.name.text
90
205
  : undefined;
91
206
  if (name !== undefined) {
92
- calls.push({ file, from: holder, call: node, name });
207
+ const site = { file, from: holder, node, name, enclosing: inside };
208
+ 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);
216
+ }
93
217
  }
94
218
  }
95
219
  node.forEachChild((child) => {
@@ -100,45 +224,198 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
100
224
  visit(child, fileId, []);
101
225
  });
102
226
  }
103
- for (const { file, from, call, name } of calls) {
104
- if (!names.has(name)) {
105
- continue;
106
- }
107
- const resolved = queries.resolvedSignature(call);
108
- if (resolved === UNANSWERED || resolved?.declaration === undefined) {
109
- continue;
227
+ function partsOf(type) {
228
+ const parts = queries.constituents(type);
229
+ return parts === UNANSWERED ? [type] : parts;
230
+ }
231
+ /** The generics one symbol declares, through the alias it may be. */
232
+ const genericsOf = (symbol) => {
233
+ const target = (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : queries.aliased(symbol);
234
+ if (target === UNANSWERED) {
235
+ return [];
110
236
  }
111
- const declaration = project.declarationAt(resolved.declaration)?.node;
112
- const byParameter = declaration === undefined
113
- ? undefined
114
- : generics.get(nodeKey(declaration.getSourceFile(), declaration));
115
- if (byParameter === undefined) {
116
- continue;
237
+ return target.declarations.flatMap((handle) => {
238
+ const node = project.declarationAt(handle)?.node;
239
+ const generic = node === undefined ? undefined : genericOf(node);
240
+ return generic === undefined ? [] : [generic];
241
+ });
242
+ };
243
+ /** What one site fixes for one side of a carried access. */
244
+ const fixedAt = (site, side, generic) => {
245
+ if (side.kind === "written") {
246
+ return { kind: "node", node: side.node };
117
247
  }
118
- for (const { object, parameter } of byParameter) {
119
- const fixed = queries.typeAtPosition(resolved, parameter);
120
- const literal = fixed === UNANSWERED ? undefined : literalTypeNames(fixed, (type) => partsOf(type));
121
- if (literal !== undefined) {
122
- accesses.push({ file, from, at: call, object, names: literal });
248
+ const { node } = site;
249
+ if (side.kind === "receiver") {
250
+ 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) {
256
+ return undefined;
123
257
  }
258
+ const args = queries.typeArguments(receiver);
259
+ const type = args === UNANSWERED ? undefined : args[side.index];
260
+ return type === undefined ? undefined : { kind: "type", type };
124
261
  }
262
+ const written = node.typeArguments?.[side.index];
263
+ if (written !== undefined) {
264
+ const passed = namedParameter(written, site.enclosing);
265
+ return passed === undefined
266
+ ? { kind: "node", node: written }
267
+ : { kind: "passed", generic: passed.generic, index: passed.index };
268
+ }
269
+ if (isTypeReferenceNode(node)) {
270
+ return undefined;
271
+ }
272
+ const at = (generic.parameters ?? []).findIndex((parameter) => {
273
+ const type = parameter.type === undefined ? undefined : unwrappedType(parameter.type);
274
+ return (type !== undefined &&
275
+ isTypeReferenceNode(type) &&
276
+ isIdentifier(type.typeName) &&
277
+ parameterAt(generic, type.typeName.text) === side.index);
278
+ });
279
+ if (at < 0) {
280
+ return undefined;
281
+ }
282
+ const resolved = queries.resolvedSignature(node);
283
+ if (resolved === UNANSWERED || resolved === undefined) {
284
+ return undefined;
285
+ }
286
+ const type = queries.typeAtPosition(resolved, at);
287
+ return type === UNANSWERED ? undefined : { kind: "type", type };
288
+ };
289
+ /** Reads one carried access at one site: an access where both sides are fixed, a carry where one is passed on. */
290
+ const readAt = (site, generic, one) => {
291
+ const object = fixedAt(site, one.object, generic);
292
+ const index = object === undefined ? undefined : fixedAt(site, one.index, generic);
293
+ if (object === undefined || index === undefined) {
294
+ return false;
295
+ }
296
+ const passed = [object, index].find((fixed) => fixed.kind === "passed");
297
+ if (passed !== undefined) {
298
+ const side = (fixed) => fixed.kind === "passed"
299
+ ? fixed.generic === passed.generic
300
+ ? { kind: "parameter", index: fixed.index }
301
+ : undefined
302
+ : fixed.kind === "node"
303
+ ? { kind: "written", node: fixed.node }
304
+ : undefined;
305
+ const objectSide = side(object);
306
+ const indexSide = side(index);
307
+ return objectSide !== undefined && indexSide !== undefined
308
+ ? carry(passed.generic, { object: objectSide, index: indexSide })
309
+ : false;
310
+ }
311
+ const objectAt = concrete(object);
312
+ const indexAt = concrete(index);
313
+ if (objectAt === undefined || indexAt === undefined) {
314
+ return false;
315
+ }
316
+ accesses.push({
317
+ file: site.file,
318
+ from: site.from,
319
+ at: site.node,
320
+ object: objectAt,
321
+ index: "kind" in indexAt ? (literalNames(indexAt) ?? indexAt) : indexAt,
322
+ });
323
+ return false;
324
+ };
325
+ // A site passing a type parameter on carries the access to its own generic, so the
326
+ // sites are read again until no generic gains an access.
327
+ const resolvedGenerics = new Map();
328
+ const read = new Set();
329
+ for (let changed = true; changed;) {
330
+ changed = false;
331
+ const pending = sites.filter((site) => names.has(site.name) && !resolvedGenerics.has(site));
332
+ const typeSites = pending.filter((site) => isTypeReferenceNode(site.node));
333
+ const symbols = typeSites.length === 0
334
+ ? []
335
+ : queries.symbolsAt(typeSites.map((site) => site.node.typeName));
336
+ typeSites.forEach((site, at) => {
337
+ const symbol = symbols[at];
338
+ resolvedGenerics.set(site, symbol === UNANSWERED || symbol === undefined ? [] : genericsOf(symbol));
339
+ });
340
+ for (const site of pending.filter((one) => !isTypeReferenceNode(one.node))) {
341
+ const resolved = queries.resolvedSignature(site.node);
342
+ const declaration = resolved === UNANSWERED || resolved?.declaration === undefined
343
+ ? undefined
344
+ : project.declarationAt(resolved.declaration)?.node;
345
+ const generic = declaration === undefined ? undefined : genericOf(declaration);
346
+ resolvedGenerics.set(site, generic === undefined ? [] : [generic]);
347
+ }
348
+ sites.forEach((site, siteAt) => {
349
+ for (const generic of resolvedGenerics.get(site) ?? []) {
350
+ (carried.get(generic) ?? []).forEach((one, at) => {
351
+ const key = `${String(siteAt)}:${String(at)}:${nodeKey(generic.getSourceFile(), generic)}`;
352
+ if (read.has(key)) {
353
+ return;
354
+ }
355
+ read.add(key);
356
+ changed = readAt(site, generic, one) || changed;
357
+ });
358
+ }
359
+ });
125
360
  }
126
- function partsOf(type) {
127
- const parts = queries.constituents(type);
128
- return parts === UNANSWERED ? [type] : parts;
129
- }
130
- const objectNodes = accesses.flatMap((one) => ("kind" in one.object ? [one.object] : []));
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
+ }));
370
+ 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;
380
+ }
381
+ for (const handle of symbol.declarations) {
382
+ if (own.has(handle.path)) {
383
+ continue;
384
+ }
385
+ const node = project.declarationAt(handle)?.node;
386
+ const generic = node === undefined ? undefined : genericOf(node);
387
+ if (generic === undefined) {
388
+ continue;
389
+ }
390
+ for (const one of carriedIn(generic)) {
391
+ readAt(site, generic, one);
392
+ }
393
+ }
394
+ });
395
+ const typeNodes = accesses.flatMap((one) => [
396
+ ...("kind" in one.object ? [one.object] : []),
397
+ ...(!Array.isArray(one.index) && "kind" in one.index ? [one.index] : []),
398
+ ]);
131
399
  const typeOf = new Map();
132
- queries.typesAt(objectNodes).forEach((answer, index) => {
133
- const node = objectNodes[index];
400
+ queries.typesAt(typeNodes).forEach((answer, index) => {
401
+ const node = typeNodes[index];
134
402
  if (answer !== UNANSWERED && answer !== undefined && node !== undefined) {
135
403
  typeOf.set(node, answer);
136
404
  }
137
405
  });
406
+ const asType = (side) => "kind" in side ? typeOf.get(side) : side;
138
407
  const found = [];
139
408
  for (const access of accesses) {
140
- const type = "kind" in access.object ? typeOf.get(access.object) : access.object;
141
- if (type === undefined) {
409
+ const type = asType(access.object);
410
+ const indexType = Array.isArray(access.index)
411
+ ? undefined
412
+ : asType(access.index);
413
+ const accessed = Array.isArray(access.index)
414
+ ? access.index
415
+ : indexType === undefined
416
+ ? undefined
417
+ : literalTypeNames(indexType, partsOf);
418
+ if (type === undefined || accessed === undefined) {
142
419
  continue;
143
420
  }
144
421
  const position = renderPosition(access.file, root, access.at.getStart());
@@ -148,7 +425,7 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
148
425
  continue;
149
426
  }
150
427
  for (const property of properties) {
151
- if (!access.names.includes(property.escapedName)) {
428
+ if (!accessed.includes(property.escapedName)) {
152
429
  continue;
153
430
  }
154
431
  for (const to of chains.declarationsOf(property)) {
@@ -167,49 +444,68 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
167
444
  return found;
168
445
  }
169
446
  /**
170
- * Records an indexed-access type whose index names a type parameter of an enclosing
171
- * function that a parameter of that function is annotated with whole, so a call's
172
- * argument for that parameter fixes the index.
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.
173
450
  */
174
- function parameterIndex(node, enclosing, file, generics, names) {
175
- const index = unwrappedType(node.indexType);
176
- if (!isTypeReferenceNode(index) || !isIdentifier(index.typeName)) {
177
- return;
178
- }
179
- const spelled = index.typeName.text;
180
- for (const generic of [...enclosing].reverse()) {
181
- const declares = (generic.typeParameters ?? []).some((parameter) => isIdentifier(parameter.name) && parameter.name.text === spelled);
182
- if (!declares) {
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)) {
183
457
  continue;
184
458
  }
185
- const at = generic.parameters.findIndex((parameter) => {
186
- const type = parameter.type === undefined ? undefined : unwrappedType(parameter.type);
187
- return (type !== undefined &&
188
- isTypeReferenceNode(type) &&
189
- isIdentifier(type.typeName) &&
190
- type.typeName.text === spelled);
191
- });
192
- if (at >= 0) {
193
- const key = nodeKey(file, generic);
194
- generics.set(key, [...(generics.get(key) ?? []), { object: node.objectType, parameter: at }]);
195
- const name = calledName(generic);
196
- if (name !== undefined) {
197
- names.add(name);
198
- }
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);
199
466
  }
200
- return;
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;
201
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;
202
488
  }
203
- /** The name a call writes to call one generic function: its own, or its variable's. */
204
- function calledName(generic) {
205
- const own = generic.name;
206
- if (own !== undefined && isIdentifier(own)) {
207
- return own.text;
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;
208
505
  }
209
- const parent = generic.parent;
210
- return parent.kind === SyntaxKind.VariableDeclaration &&
211
- parent.name !== undefined &&
212
- isIdentifier(parent.name)
213
- ? parent.name.text
214
- : undefined;
506
+ }
507
+ /** What makes two carried accesses one: the kind and position of each side, or its written node. */
508
+ function sideKey(one) {
509
+ const key = (side) => side.kind === "written" ? ["written", side.node.pos, side.node.end] : [side.kind, side.index];
510
+ return [key(one.object), key(one.index)];
215
511
  }