@cplieger/deadset-ts 8.0.0-dev.16 → 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 (131) 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 +58 -22
  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/tools.js +5 -9
  8. package/dist/src/component-files.js +3 -2
  9. package/dist/src/conformance-record.js +1 -1
  10. package/dist/src/consumers.js +6 -9
  11. package/dist/src/contextual-uses.js +1 -17
  12. package/dist/src/contract/config.generated.js +4 -1
  13. package/dist/src/contract/report.generated.js +6 -2
  14. package/dist/src/dependencies.js +25 -29
  15. package/dist/src/destructuring.js +1 -19
  16. package/dist/src/element-access.js +179 -164
  17. package/dist/src/entries/build-scripts.js +2 -2
  18. package/dist/src/entries/configuration-files.js +3 -7
  19. package/dist/src/entries/conventions.js +2 -2
  20. package/dist/src/entries/html-entries.js +7 -10
  21. package/dist/src/entries/mdx-entries.js +2 -2
  22. package/dist/src/entries/static-module.js +2 -1
  23. package/dist/src/facts.js +4 -4
  24. package/dist/src/findings/completion.js +40 -4
  25. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  26. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  27. package/dist/src/framework-lifecycle.js +41 -14
  28. package/dist/src/graph.js +12 -4
  29. package/dist/src/import-bindings.js +73 -28
  30. package/dist/src/index/sites.js +557 -0
  31. package/dist/src/indexed-access.js +523 -478
  32. package/dist/src/interface-satisfaction.js +1 -1
  33. package/dist/src/keyof-comparisons.js +124 -103
  34. package/dist/src/load/expected-module.js +2 -2
  35. package/dist/src/load/specifiers.js +2 -4
  36. package/dist/src/load/synthesize.js +5 -0
  37. package/dist/src/load/workspace-resolution.js +2 -2
  38. package/dist/src/matrix.js +6 -4
  39. package/dist/src/module-declarations.js +70 -85
  40. package/dist/src/overrides.js +134 -112
  41. package/dist/src/paths.js +46 -7
  42. package/dist/src/platform-calls.js +2 -2
  43. package/dist/src/platform-symbols.js +13 -19
  44. package/dist/src/references/read-rule.js +1 -0
  45. package/dist/src/references/read-rules.js +48 -0
  46. package/dist/src/references/sink.js +151 -0
  47. package/dist/src/references/sources.js +450 -0
  48. package/dist/src/references.js +110 -1285
  49. package/dist/src/roots/configured-declarations.js +3 -0
  50. package/dist/src/roots/root-set.js +1 -1
  51. package/dist/src/roots/unseen-loads.js +38 -10
  52. package/dist/src/serialization-contract.js +8 -3
  53. package/dist/src/stores.js +4 -2
  54. package/dist/src/sweep.js +25 -13
  55. package/dist/src/symbol-keys.js +89 -83
  56. package/dist/src/tests/file-class.js +76 -0
  57. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  58. package/dist/src/{test-support.js → tests/support.js} +2 -11
  59. package/dist/src/type-parameter-reads.js +178 -139
  60. package/dist/src/unimplemented-callees.js +262 -242
  61. package/dist/src/verbs/analyze.js +3 -3
  62. package/dist/src/verbs/explain.js +50 -28
  63. package/dist/src/verbs/print-retained.js +2 -2
  64. package/jsr.json +1 -1
  65. package/package.json +2 -2
  66. package/src/alias-chain.ts +0 -7
  67. package/src/amd.ts +130 -0
  68. package/src/analysis.ts +86 -23
  69. package/src/augmentations.ts +41 -27
  70. package/src/bindings.ts +460 -0
  71. package/src/calls.ts +25 -7
  72. package/src/commands/tools.ts +5 -11
  73. package/src/component-files.ts +4 -2
  74. package/src/conformance-record.ts +1 -1
  75. package/src/consumers.ts +8 -11
  76. package/src/contextual-uses.ts +2 -22
  77. package/src/contract/config.generated.ts +4 -1
  78. package/src/contract/report.generated.ts +6 -2
  79. package/src/dependencies.ts +27 -28
  80. package/src/destructuring.ts +1 -31
  81. package/src/element-access.ts +188 -182
  82. package/src/entries/build-scripts.ts +2 -2
  83. package/src/entries/configuration-files.ts +11 -8
  84. package/src/entries/conventions.ts +2 -1
  85. package/src/entries/html-entries.ts +15 -14
  86. package/src/entries/mdx-entries.ts +2 -2
  87. package/src/entries/static-module.ts +2 -1
  88. package/src/facts.ts +5 -3
  89. package/src/findings/boundary.ts +2 -0
  90. package/src/findings/completion.ts +43 -4
  91. package/src/findings/emitter.ts +1 -1
  92. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  93. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  94. package/src/framework-lifecycle.ts +42 -14
  95. package/src/graph.ts +24 -6
  96. package/src/import-bindings.ts +98 -29
  97. package/src/index/sites.ts +781 -0
  98. package/src/indexed-access.ts +570 -527
  99. package/src/interface-satisfaction.ts +1 -1
  100. package/src/keyof-comparisons.ts +131 -116
  101. package/src/load/expected-module.ts +8 -2
  102. package/src/load/specifiers.ts +8 -5
  103. package/src/load/synthesize.ts +6 -0
  104. package/src/load/workspace-resolution.ts +9 -2
  105. package/src/matrix.ts +6 -3
  106. package/src/module-declarations.ts +74 -98
  107. package/src/overrides.ts +162 -134
  108. package/src/paths.ts +54 -8
  109. package/src/platform-calls.ts +3 -3
  110. package/src/platform-symbols.ts +11 -27
  111. package/src/references/read-rule.ts +60 -0
  112. package/src/references/read-rules.ts +55 -0
  113. package/src/references/sink.ts +213 -0
  114. package/src/references/sources.ts +632 -0
  115. package/src/references.ts +168 -1733
  116. package/src/report.ts +1 -1
  117. package/src/roots/configured-declarations.ts +3 -0
  118. package/src/roots/root-set.ts +1 -1
  119. package/src/roots/unseen-loads.ts +49 -11
  120. package/src/serialization-contract.ts +8 -3
  121. package/src/stores.ts +13 -2
  122. package/src/sweep.ts +37 -14
  123. package/src/symbol-keys.ts +102 -102
  124. package/src/tests/file-class.ts +118 -0
  125. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  126. package/src/{test-support.ts → tests/support.ts} +9 -20
  127. package/src/type-parameter-reads.ts +187 -158
  128. package/src/unimplemented-callees.ts +297 -280
  129. package/src/verbs/analyze.ts +3 -2
  130. package/src/verbs/explain.ts +74 -31
  131. package/src/verbs/print-retained.ts +2 -2
package/src/references.ts CHANGED
@@ -1,97 +1,32 @@
1
- import {
2
- isArrayLiteralExpression,
3
- isAsExpression,
4
- isBinaryExpression,
5
- isCallExpression,
6
- isDecorator,
7
- isDeleteExpression,
8
- isElementAccessExpression,
9
- isExportDeclaration,
10
- isExpressionStatement,
11
- isExternalModuleReference,
12
- isForInStatement,
13
- isForOfStatement,
14
- isForStatement,
15
- isIdentifier,
16
- isImportDeclaration,
17
- isImportEqualsDeclaration,
18
- isInterfaceDeclaration,
19
- isMetaProperty,
20
- isModuleBlock,
21
- isModuleDeclaration,
22
- isNewExpression,
23
- isNamespaceExport,
24
- isNoSubstitutionTemplateLiteral,
25
- isObjectLiteralExpression,
26
- isParenthesizedExpression,
27
- isPostfixUnaryExpression,
28
- isPrefixUnaryExpression,
29
- isPropertyAccessExpression,
30
- isPropertyAssignment,
31
- isShorthandPropertyAssignment,
32
- isSpreadAssignment,
33
- isSpreadElement,
34
- isStringLiteral,
35
- isStringLiteralLikeNode,
36
- isVoidExpression,
37
- SyntaxKind,
38
- type Identifier,
39
- type Node,
40
- type ParameterDeclaration,
41
- type SourceFile,
42
- type StringLiteralLikeNode,
43
- } from "@typescript/native/ast";
44
- import { parenthesesRemoved } from "./unwrap.ts";
1
+ /**
2
+ * Every use one side's files make of the target's declarations: one walk and one batched
3
+ * resolution per file, never a reference query per symbol, whose answers would depend on the
4
+ * order the symbols are asked in. A reference carries its use, its position, the declaration
5
+ * enclosing it and whether a test file made it; it carries no mode: which references a run
6
+ * counts is the sweep's reading of this set.
7
+ */
8
+
9
+ import { SyntaxKind, type Node } from "@typescript/native/ast";
45
10
  import type { Symbol as TSSymbol } from "@typescript/native/sync";
46
11
  import { aliasChains, type ChainTarget } from "./alias-chain.ts";
47
- import { contextualUses, type FlowAt, type LiteralAt } from "./contextual-uses.ts";
48
- import { destructuring, propertyReadsOf, type PropertyRead } from "./destructuring.ts";
49
- import { specifierCandidates } from "./entries/configuration-files.ts";
50
- import { namespaceUses } from "./namespace-objects.ts";
51
- import { isParameterProperty, nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
52
- import { ambientGlobalInterfaces, augmentationReferences } from "./augmentations.ts";
53
- import { elementAccessReferences } from "./element-access.ts";
54
- import { indexedAccessReferences } from "./indexed-access.ts";
55
- import { keyofComparisonReferences } from "./keyof-comparisons.ts";
56
- import { unimplementedCalleeReads } from "./unimplemented-callees.ts";
57
- import { moduleDeclarationReferences } from "./module-declarations.ts";
58
- import { overrideReferences } from "./overrides.ts";
59
- import {
60
- dirnamePath,
61
- queriedModulePath,
62
- relativePath,
63
- relativeURLPath,
64
- resolvePath,
65
- } from "./paths.ts";
66
- import { byPosition, isComponentFile, renderPosition, type Position } from "./position.ts";
12
+ import { augmentationReferences, mergedGlobalInterfaces } from "./augmentations.ts";
13
+ import { contextualUses } from "./contextual-uses.ts";
14
+ import { destructuring } from "./destructuring.ts";
15
+ import type { RunDirectories } from "./entries/run-directories.ts";
16
+ import { bindingsOf } from "./import-bindings.ts";
17
+ import { sitesOf } from "./index/sites.ts";
18
+ import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
19
+ import { relativePath } from "./paths.ts";
20
+ import type { Position } from "./position.ts";
67
21
  import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./query.ts";
22
+ import { heldBy, READ_RULES } from "./references/read-rules.ts";
23
+ import { Sink } from "./references/sink.ts";
24
+ import { emitFile, prepared, type Pass } from "./references/sources.ts";
68
25
  import type { ProjectView } from "./session.ts";
69
26
  import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
70
- import { typeParameterReads } from "./type-parameter-reads.ts";
71
- import { typeQueryAliases } from "./type-query-alias.ts";
72
- import { callsPlatform, isPlatformGlobal, startsThread } from "./platform-symbols.ts";
73
- import { writtenName } from "./platform-calls.ts";
74
- import type { RunDirectories } from "./entries/run-directories.ts";
75
- import { computedLoadsOf, isLoadCall, type LoadCall } from "./roots/unseen-loads.ts";
76
- import {
77
- computedKeyNames,
78
- iterationReferences,
79
- keyNameOf,
80
- keyedUses,
81
- type KeyedAccess,
82
- } from "./symbol-keys.ts";
83
- import { handedOn, valuesOf } from "./value-flow.ts";
84
27
  import { bytewise } from "./text/compare.ts";
85
- import { globExpression } from "./text/glob.ts";
86
-
87
- /**
88
- * Every use one project's own files make of the project's own declarations: one local walk
89
- * and one batched resolution per file, never a reference query per symbol, whose answers
90
- * would depend on the order the symbols are asked in. A reference carries its use, its
91
- * position, the declaration enclosing it and whether a test file made it; every import of
92
- * one of the project's modules is an evaluation of that module's file. It carries no mode:
93
- * which references a run counts is the sweep's reading of this set.
94
- */
28
+ import type { TestFile } from "./tests/file-class.ts";
29
+ import { typeQueryAliases } from "./type-query-alias.ts";
95
30
 
96
31
  /**
97
32
  * How one reference uses the declaration it names. An `evaluation` names a file: the
@@ -100,17 +35,17 @@ import { globExpression } from "./text/glob.ts";
100
35
  * `decorator` names the declaration a decorator expression is attached to, which the
101
36
  * decorator receives when the class is defined.
102
37
  */
103
- type Use = "read" | "write" | "evaluation" | "decorator";
38
+ export type Use = "read" | "write" | "evaluation" | "decorator";
104
39
 
105
40
  /**
106
- * Which path answered for one reference: `batch`, a file's batched lookup; `resolved-symbol`, the
107
- * per-node lookup for its residue; `shorthand`, the value `{ a }` reads; `alias`, a step along an
108
- * import or export chain; `syntax`, the tree alone (a decorator's target, a component's markup);
109
- * `destructured`, the property a pattern names; `contextual`, the member a literal property writes
110
- * or a value's position reads; `override`, the override a base call dispatches to; `keyed`, the
111
- * member a symbol key selects; `by-name`, each declaration so spelled, where the checker failed.
41
+ * Which path answered for one reference, provenance only: `batch`, a file's batched lookup;
42
+ * `resolved-symbol`, the per-node lookup for its residue; `shorthand`, the value `{ a }` reads;
43
+ * `alias`, a step along an import or export chain; `syntax`, the tree alone; `destructured`,
44
+ * the property a pattern names; `contextual`, the member a literal property writes or a
45
+ * value's position reads; `override`, the override a base call dispatches to; `keyed`, the
46
+ * member a key selects; `by-name`, each declaration so spelled, where the checker failed.
112
47
  */
113
- type Resolution =
48
+ export type Resolution =
114
49
  | "batch"
115
50
  | "resolved-symbol"
116
51
  | "shorthand"
@@ -137,8 +72,13 @@ export interface Reference {
137
72
  readonly position: Position;
138
73
  readonly use: Use;
139
74
  readonly resolution: Resolution;
140
- /** Whether the file that made the reference is classified as a test file. */
75
+ /** Whether a test file made the reference (`tests/file-class.ts`). */
141
76
  readonly test: boolean;
77
+ /**
78
+ * Present where only the name answered, the checker having failed (program.33): the
79
+ * reference may be a use of any declaration so spelled, so the run holds what it names.
80
+ */
81
+ readonly guessed?: true;
142
82
  /**
143
83
  * The identifier of the consumer whose file made the reference, absent for one the
144
84
  * target made. A consumer's declarations are no declarations of the run, so such a
@@ -164,79 +104,24 @@ export interface ConsumerSide {
164
104
  readonly root: string;
165
105
  }
166
106
 
167
- /** One rule that classified files as test files, and how many files it matched. */
168
- export interface TestFileRule {
169
- /** The rule's name, as a report's own rule list spells one. */
170
- readonly rule: string;
171
- readonly matched: number;
172
- }
173
-
174
- /** What one project's reference pass cost at the client boundary. */
175
- export interface ReferenceCost {
176
- /**
177
- * The name nodes the batches asked about, summed over the files. It is the size of
178
- * the question rather than a cost of its own, and it is reported so that the number
179
- * of batches is readable against it.
180
- */
181
- readonly batched: number;
182
- /**
183
- * Batched lookups: per file, the name-node count divided by the batch cap, rounded
184
- * up. It is what keeps the pass's cost off the identifier count.
185
- */
186
- readonly fileBatches: number;
187
- /** Per-node lookups for the name nodes a batch left unresolved. */
188
- readonly residueFallbacks: number;
189
- /** Per-node lookups for a shorthand property assignment's value. */
190
- readonly shorthandLookups: number;
191
- /** Per-symbol lookups that step from one alias to what it names, one per alias. */
192
- readonly aliasSteps: number;
193
- /**
194
- * Per-node and per-symbol lookups that decide whether a load call is the platform's
195
- * `require`, a construction its `URL`, or a `Worker` Node's, for a callee the pass's own
196
- * chains leave open.
197
- */
198
- readonly platformLookups: number;
199
- /**
200
- * Batched type lookups for the object patterns: per file, the patterns and the values
201
- * assigned to destructuring targets, divided by the batch cap, rounded up.
202
- */
203
- readonly patternBatches: number;
204
- /**
205
- * Per-type lookups for the object patterns: one property table per distinct type a
206
- * pattern destructures, and each step a nested assignment target takes into its value.
207
- */
208
- readonly patternLookups: number;
209
- /**
210
- * Batched lookups for the contextual uses: the contextual types of the object literals
211
- * and of the values that carry a member, and the values' types, each per capped run.
212
- */
213
- readonly contextualBatches: number;
214
- /** Per-type lookups for the contextual uses: each distinct type's constituents, properties and symbol. */
215
- readonly contextualLookups: number;
216
- /**
217
- * The requests the passes after the walk make: the bases overrides are read against,
218
- * element accesses, key comparisons, instantiated signatures, values handed to
219
- * unimplemented callees and the modules a `declare module` names. They are counted
220
- * where the caller measures the client, through `requestCount`, and are 0 otherwise.
221
- */
222
- readonly typedLookups: number;
223
- }
224
-
225
- /** One project's references, in position order, and what reading them cost. */
107
+ /** One side's references, in position order. */
226
108
  export interface References {
227
109
  readonly references: readonly Reference[];
228
110
  /**
229
- * Every file the rules classified as a test file, by its path below the target root,
230
- * ascending. A declaration such a file holds is a test declaration, by the same
231
- * classification that marks the references the file makes.
111
+ * The declarations a read rule's unanswered question could have made live, and those
112
+ * making such a read, each once, ascending: the run holds them (program.33).
232
113
  */
233
- readonly testFilePaths: readonly string[];
114
+ readonly held: readonly string[];
234
115
  /**
235
116
  * Each load by a computed path a production file of the target writes whose files the
236
117
  * analysis cannot name (confidence.26), in file then source order; none for a consumer.
237
118
  */
238
119
  readonly unseenLoads: readonly Position[];
239
- readonly cost: ReferenceCost;
120
+ /**
121
+ * The files, and the declarations an import binds there, that only a bundler's retry of a
122
+ * specifier Node leaves unresolved names (program.38), each once, ascending.
123
+ */
124
+ readonly possiblyLoaded: readonly string[];
240
125
  }
241
126
 
242
127
  /** How one run resolves references. */
@@ -247,731 +132,19 @@ export interface ReferenceOptions {
247
132
  * makes one answer proportional to that file. Absent takes {@link DEFAULT_BATCH_CAP}.
248
133
  */
249
134
  readonly batchCap?: number;
250
- /**
251
- * Glob patterns, relative to the target root, naming the files that are test files.
252
- * Each pattern is a rule of its own, which {@link testFileRulesOf} counts.
253
- */
254
- readonly testFiles: readonly string[];
255
- /** The files, absolute, that are test files beside those the patterns name. */
256
- readonly testFileNames?: ReadonlySet<string>;
135
+ /** Which of the side's files are test files. */
136
+ readonly testFile: TestFile;
257
137
  /**
258
138
  * The consumer whose files the pass walks instead of the target's. Its files are the
259
139
  * program's own files below its root and not below the target root, each position is
260
- * rendered against its root and the test patterns read the path below it, and only a
261
- * reference to a declaration of `held`, the target's, is recorded.
140
+ * rendered against its root, and only a reference to a declaration of `held`, the
141
+ * target's, is recorded.
262
142
  */
263
143
  readonly consumer?: ConsumerSide;
264
- /** The client's running request count, where the caller measures the client. */
265
- readonly requestCount?: () => number;
266
144
  /** The directories a thread's path and a glob's root are read against. */
267
145
  readonly directories: RunDirectories;
268
- }
269
-
270
- /**
271
- * The operators whose left side one assignment stores into. A compound assignment is
272
- * among them: where its value is discarded it reads its target only to write the result
273
- * back, so nothing else receives what it read.
274
- */
275
- const ASSIGNMENTS: ReadonlySet<SyntaxKind> = new Set([
276
- SyntaxKind.EqualsToken,
277
- SyntaxKind.PlusEqualsToken,
278
- SyntaxKind.MinusEqualsToken,
279
- SyntaxKind.AsteriskEqualsToken,
280
- SyntaxKind.AsteriskAsteriskEqualsToken,
281
- SyntaxKind.SlashEqualsToken,
282
- SyntaxKind.PercentEqualsToken,
283
- SyntaxKind.LessThanLessThanEqualsToken,
284
- SyntaxKind.GreaterThanGreaterThanEqualsToken,
285
- SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken,
286
- SyntaxKind.AmpersandEqualsToken,
287
- SyntaxKind.BarEqualsToken,
288
- SyntaxKind.BarBarEqualsToken,
289
- SyntaxKind.AmpersandAmpersandEqualsToken,
290
- SyntaxKind.QuestionQuestionEqualsToken,
291
- SyntaxKind.CaretEqualsToken,
292
- ]);
293
-
294
- /**
295
- * The assignments that read their target to decide whether to store at all, so the read
296
- * carries information out whatever becomes of the result.
297
- */
298
- const LOGICAL_ASSIGNMENTS: ReadonlySet<SyntaxKind> = new Set([
299
- SyntaxKind.BarBarEqualsToken,
300
- SyntaxKind.AmpersandAmpersandEqualsToken,
301
- SyntaxKind.QuestionQuestionEqualsToken,
302
- ]);
303
-
304
- /**
305
- * Whether the value of one store expression is discarded: it is a statement of its own, a
306
- * clause of a `for` loop, the operand of `void`, or the left operand of a comma.
307
- */
308
- function valueDiscarded(node: Node): boolean {
309
- let child = node;
310
- let parent = node.parent;
311
- while (isParenthesizedExpression(parent)) {
312
- child = parent;
313
- parent = parent.parent;
314
- }
315
- if (isExpressionStatement(parent) || isVoidExpression(parent)) {
316
- return true;
317
- }
318
- if (isForStatement(parent)) {
319
- return parent.initializer === child || parent.incrementor === child;
320
- }
321
- return (
322
- isBinaryExpression(parent) &&
323
- parent.operatorToken.kind === SyntaxKind.CommaToken &&
324
- parent.left === child
325
- );
326
- }
327
-
328
- /** Whether one node is an identifier or a private name, the two forms a name takes. */
329
- function isName(node: Node): boolean {
330
- return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PrivateIdentifier;
331
- }
332
-
333
- /**
334
- * Records the name one store writes into.
335
- *
336
- * A property access writes the member it names. An element access writes the collection
337
- * it stores into, since a collection a module only stores into holds nothing anything
338
- * reads, and joins `accesses` for the member a unique-symbol key selects. A destructuring
339
- * target writes each name it binds, through its spreads and nested patterns.
340
- */
341
- function markWrite(target: Node, writes: Set<number>, accesses: Set<Node>): void {
342
- const held = parenthesesRemoved(target);
343
- if (isName(held)) {
344
- writes.add(held.pos);
345
- return;
346
- }
347
- if (isPropertyAccessExpression(held)) {
348
- if (isName(held.name)) {
349
- writes.add(held.name.pos);
350
- }
351
- return;
352
- }
353
- if (isElementAccessExpression(held)) {
354
- accesses.add(held);
355
- // A store through a member's value lands in storage every holder of the value
356
- // reads, so it reads the member; a variable only ever stored into stays written.
357
- if (!isPropertyAccessExpression(parenthesesRemoved(held.expression))) {
358
- markWrite(held.expression, writes, accesses);
359
- }
360
- return;
361
- }
362
- if (isArrayLiteralExpression(held)) {
363
- for (const element of held.elements) {
364
- markWrite(element, writes, accesses);
365
- }
366
- return;
367
- }
368
- if (isObjectLiteralExpression(held)) {
369
- for (const property of held.properties) {
370
- if (isPropertyAssignment(property)) {
371
- markWrite(property.initializer, writes, accesses);
372
- continue;
373
- }
374
- if (isShorthandPropertyAssignment(property)) {
375
- markWrite(property.name, writes, accesses);
376
- continue;
377
- }
378
- if (isSpreadAssignment(property)) {
379
- markWrite(property.expression, writes, accesses);
380
- }
381
- }
382
- return;
383
- }
384
- if (isSpreadElement(held)) {
385
- markWrite(held.expression, writes, accesses);
386
- }
387
- }
388
-
389
- /**
390
- * Records every store one node performs: an assignment, a compound assignment, an
391
- * increment or a decrement, a `delete`, and an iteration that assigns to a name declared
392
- * elsewhere. An iteration that declares its own name stores into nothing the inventory
393
- * holds. A store that reads its target as well records it in `reads`: a logical
394
- * assignment, and a compound assignment, an increment or a decrement whose value is used.
395
- */
396
- function markStores(
397
- node: Node,
398
- writes: Set<number>,
399
- reads: Set<number>,
400
- accesses: { readonly written: Set<Node>; readonly read: Set<Node> },
401
- ): void {
402
- if (isBinaryExpression(node)) {
403
- const operator = node.operatorToken.kind;
404
- if (ASSIGNMENTS.has(operator)) {
405
- markWrite(node.left, writes, accesses.written);
406
- if (
407
- LOGICAL_ASSIGNMENTS.has(operator) ||
408
- (operator !== SyntaxKind.EqualsToken && !valueDiscarded(node))
409
- ) {
410
- markWrite(node.left, reads, accesses.read);
411
- }
412
- }
413
- return;
414
- }
415
- if (isPrefixUnaryExpression(node) || isPostfixUnaryExpression(node)) {
416
- if (
417
- node.operator === SyntaxKind.PlusPlusToken ||
418
- node.operator === SyntaxKind.MinusMinusToken
419
- ) {
420
- markWrite(node.operand, writes, accesses.written);
421
- if (!valueDiscarded(node)) {
422
- markWrite(node.operand, reads, accesses.read);
423
- }
424
- }
425
- return;
426
- }
427
- if (isDeleteExpression(node)) {
428
- markWrite(node.expression, writes, accesses.written);
429
- return;
430
- }
431
- if (isForInStatement(node) || isForOfStatement(node)) {
432
- if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) {
433
- markWrite(node.initializer, writes, accesses.written);
434
- }
435
- }
436
- }
437
-
438
- /** One name node the walk found, and what the walk knows about it. */
439
- interface Site {
440
- /** The name node, whose position the reference carries. */
441
- readonly node: Node;
442
- /**
443
- * The shorthand property assignment this name belongs to, where it belongs to one.
444
- * Its value is the declaration the name reads, while the name itself declares the
445
- * property the literal carries.
446
- */
447
- readonly shorthand: Node | undefined;
448
- readonly from: string;
449
- readonly use: Use;
450
- }
451
-
452
- /**
453
- * One re-export the walk found, which names what it carries forward without a name
454
- * that is a use: an export specifier, a namespace export, or a bare star re-export.
455
- */
456
- interface Link {
457
- /**
458
- * The node whose symbol says what the re-export carries: the alias a specifier or a
459
- * namespace export declares, or the module a bare star re-export names.
460
- */
461
- readonly node: Node;
462
- /** Where the reference is written: the name a specifier re-exports, or the module specifier. */
463
- readonly at: Node;
464
- /** The re-export's own declaration, or for a bare star the declaration enclosing it. */
465
- readonly from: string;
466
- /** Whether the node's symbol is an alias, which is followed one link along. */
467
- readonly alias: boolean;
468
- }
469
-
470
- /** One place the walk found that evaluates a module, and the declaration that holds it. */
471
- interface Evaluation {
472
- /** The module specifier, whose symbol is the module evaluated. */
473
- readonly node: Node;
474
- readonly from: string;
475
- /** The name a `require()` call is written with; {@link callsPlatform} owns which one loads. */
476
- readonly require?: Identifier;
477
- /** Whether the call's value is used, which reads the loaded file's export table. */
478
- readonly whole?: boolean;
479
- }
480
-
481
- /**
482
- * One module URL a source file builds: `new URL(<literal>, import.meta.url)` or
483
- * `import.meta.resolve(<literal>)`, the literal relative. A runtime starts the file it
484
- * names as a module, so it is an import of that file for its effects.
485
- */
486
- interface ModuleURL {
487
- readonly specifier: string;
488
- /** The name the constructor is written with; {@link callsPlatform} owns which one is `URL`. */
489
- readonly constructor: Identifier | undefined;
490
- readonly at: Node;
491
- }
492
-
493
- /** One module URL, and the declaration whose code builds it. */
494
- interface ModuleURLAt extends ModuleURL {
495
- readonly from: string;
496
- }
497
-
498
- /** One worker a call starts by a literal address (references-ts.2), and the declaration whose code starts it. */
499
- interface WorkerSite {
500
- readonly address: StringLiteralLikeNode;
501
- /** The name the constructor or `navigator` is written with. */
502
- readonly constructor: Identifier;
503
- /** The namespace the constructor is read through, as `threads` in `threads.Worker`. */
504
- readonly holder: Identifier | undefined;
505
- /** Whether an options literal writes an `eval` other than `false`, which makes a thread's address code. */
506
- readonly evaluated: boolean;
507
- readonly at: Node;
508
- readonly from: string;
509
- }
510
-
511
- /** One use of a module namespace object that may read every export of the module. */
512
- interface WholeRead {
513
- /** The module specifier, whose symbol is the module read. */
514
- readonly specifier: Node;
515
- /** The use, whose position the reference carries. */
516
- readonly at: Node;
517
- readonly from: string;
518
- }
519
-
520
- /** One decorator expression, and the declaration it is attached to. */
521
- interface Decorated {
522
- readonly decorator: Node;
523
- readonly declaration: string;
524
- }
525
-
526
- /** One parameter property, which its constructor writes, and the declaration that writes it. */
527
- interface ParameterWrite {
528
- readonly name: Node;
529
- readonly from: string;
530
- readonly member: string;
531
- }
532
-
533
- /** One property a pattern reads, and the declaration the pattern is written inside. */
534
- interface Read extends PropertyRead {
535
- readonly from: string;
536
- }
537
-
538
- /** Everything one file's walk found to resolve, and the decorators it needs no resolution for. */
539
- interface FileSites {
540
- readonly uses: readonly Site[];
541
- readonly literals: readonly LiteralAt[];
542
- readonly flows: readonly FlowAt[];
543
- readonly reads: readonly Read[];
544
- readonly links: readonly Link[];
545
- readonly evaluations: readonly Evaluation[];
546
- readonly wholes: readonly WholeRead[];
547
- readonly urls: readonly ModuleURLAt[];
548
- readonly workers: readonly WorkerSite[];
549
- readonly loadCalls: readonly LoadCall[];
550
- readonly decorated: readonly Decorated[];
551
- readonly parameterWrites: readonly ParameterWrite[];
552
- readonly keyed: readonly KeyedAccess[];
553
- }
554
-
555
- /** Whether one node is `import.meta.url`. */
556
- function isImportMetaURL(node: Node): boolean {
557
- const at = parenthesesRemoved(node);
558
- return (
559
- isPropertyAccessExpression(at) &&
560
- isMetaProperty(at.expression) &&
561
- at.expression.keywordToken === SyntaxKind.ImportKeyword &&
562
- at.name.text === "url"
563
- );
564
- }
565
-
566
- /** The module URL one node builds, where it builds one. */
567
- function moduleURLOf(node: Node): ModuleURL | undefined {
568
- const literal = (argument: Node | undefined): string | undefined => {
569
- const at = argument === undefined ? undefined : parenthesesRemoved(argument);
570
- return isLiteralText(at) ? at.text : undefined;
571
- };
572
- const constructor = isNewExpression(node) ? writtenName(node.expression) : undefined;
573
- if (isNewExpression(node) && constructor !== undefined) {
574
- const [reference, base] = node.arguments ?? [];
575
- const specifier = literal(reference);
576
- return specifier !== undefined && base !== undefined && isImportMetaURL(base)
577
- ? { specifier, constructor, at: node }
578
- : undefined;
579
- }
580
- if (isCallExpression(node)) {
581
- const callee = parenthesesRemoved(node.expression);
582
- const specifier = literal(node.arguments[0]);
583
- if (
584
- specifier !== undefined &&
585
- /^\.\.?\//u.test(specifier) &&
586
- isPropertyAccessExpression(callee) &&
587
- isMetaProperty(callee.expression) &&
588
- callee.expression.keywordToken === SyntaxKind.ImportKeyword &&
589
- callee.name.text === "resolve"
590
- ) {
591
- return { specifier, constructor: undefined, at: node };
592
- }
593
- }
594
- return undefined;
595
- }
596
-
597
- /** The constructors whose first argument addresses a worker. */
598
- const WORKER_CONSTRUCTORS: ReadonlySet<string> = new Set(["Worker", "SharedWorker"]);
599
-
600
- /** Whether an options argument is an object literal whose `eval` member is written as anything but `false`. */
601
- function evaluatesCode(options: Node | undefined): boolean {
602
- const at = options === undefined ? undefined : parenthesesRemoved(options);
603
- return (
604
- at !== undefined &&
605
- isObjectLiteralExpression(at) &&
606
- at.properties.some(
607
- (property) =>
608
- (isShorthandPropertyAssignment(property) &&
609
- isIdentifier(property.name) &&
610
- property.name.text === "eval") ||
611
- (isPropertyAssignment(property) &&
612
- (isIdentifier(property.name) || isStringLiteral(property.name)) &&
613
- property.name.text === "eval" &&
614
- parenthesesRemoved(property.initializer).kind !== SyntaxKind.FalseKeyword),
615
- )
616
- );
617
- }
618
-
619
- /** The worker one node starts by a literal address, where it starts one. */
620
- function workerOf(node: Node): Omit<WorkerSite, "from"> | undefined {
621
- if (isNewExpression(node)) {
622
- const constructor = writtenName(node.expression);
623
- const [address, options] = node.arguments ?? [];
624
- const literal = address === undefined ? undefined : parenthesesRemoved(address);
625
- // A thread's constructor may be imported under another name, whose path is relative.
626
- if (
627
- constructor === undefined ||
628
- !isLiteralText(literal) ||
629
- (!WORKER_CONSTRUCTORS.has(constructor.text) && !/^\.\.?\//u.test(literal.text))
630
- ) {
631
- return undefined;
632
- }
633
- const callee = parenthesesRemoved(node.expression);
634
- const holder = isPropertyAccessExpression(callee)
635
- ? parenthesesRemoved(callee.expression)
636
- : undefined;
637
- return {
638
- address: literal,
639
- constructor,
640
- holder: holder !== undefined && isIdentifier(holder) ? holder : undefined,
641
- evaluated: evaluatesCode(options),
642
- at: node,
643
- };
644
- }
645
- if (!isCallExpression(node)) {
646
- return undefined;
647
- }
648
- const callee = parenthesesRemoved(node.expression);
649
- const registry =
650
- isPropertyAccessExpression(callee) && callee.name.text === "register"
651
- ? parenthesesRemoved(callee.expression)
652
- : undefined;
653
- const navigator =
654
- registry !== undefined &&
655
- isPropertyAccessExpression(registry) &&
656
- registry.name.text === "serviceWorker"
657
- ? writtenName(registry.expression)
658
- : undefined;
659
- const [address] = node.arguments;
660
- const literal = address === undefined ? undefined : parenthesesRemoved(address);
661
- return navigator?.text === "navigator" && isLiteralText(literal)
662
- ? { address: literal, constructor: navigator, holder: undefined, evaluated: false, at: node }
663
- : undefined;
664
- }
665
-
666
- /** The file of the target a relative specifier written in `file` names, by its source or output name. */
667
- function fileNamed(
668
- file: SourceFile,
669
- specifier: string,
670
- fileIdByName: ReadonlyMap<string, string>,
671
- ): string | undefined {
672
- return specifierCandidates(dirnamePath(file.fileName), specifier)
673
- .map((path) => fileIdByName.get(path))
674
- .find((id) => id !== undefined);
675
- }
676
-
677
- /** Whether one node is a string literal or a template literal with no substitution. */
678
- function isLiteralText(node: Node | undefined): node is StringLiteralLikeNode {
679
- return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
680
- }
681
-
682
- /**
683
- * The module specifier of the module one node evaluates, if it evaluates one: an import
684
- * declaration, an import-equals declaration of an external module, an export
685
- * declaration that names a module, an `import()` call whose argument is literal text, and
686
- * a call that may be a `require()` of literal text: one written as the name `require`, or
687
- * one of a relative specifier through another name. A type-only form evaluates nothing.
688
- * An `import()` of a computed argument names no module this pass can know.
689
- */
690
- function evaluationOf(node: Node): Omit<Evaluation, "from"> | undefined {
691
- if (isCallExpression(node) && node.expression.kind !== SyntaxKind.ImportKeyword) {
692
- const [argument] = node.arguments;
693
- const require = writtenName(node.expression);
694
- if (
695
- require === undefined ||
696
- !isLiteralText(argument) ||
697
- (require.text !== "require" && !/^\.\.?\//u.test(argument.text))
698
- ) {
699
- return undefined;
700
- }
701
- return { node: argument, require, whole: !discarded(node) };
702
- }
703
- const specifier = evaluatedBy(node);
704
- return specifier === undefined ? undefined : { node: specifier };
705
- }
706
-
707
- /** Whether an expression's value is thrown away: it is a statement of its own, or `void`'s operand. */
708
- function discarded(expression: Node): boolean {
709
- let at = expression.parent;
710
- while (isParenthesizedExpression(at)) {
711
- at = at.parent;
712
- }
713
- return isExpressionStatement(at) || isVoidExpression(at);
714
- }
715
-
716
- function evaluatedBy(node: Node): Node | undefined {
717
- if (isImportDeclaration(node)) {
718
- return node.importClause?.phaseModifier === SyntaxKind.TypeKeyword
719
- ? undefined
720
- : node.moduleSpecifier;
721
- }
722
- if (isExportDeclaration(node)) {
723
- return node.isTypeOnly ? undefined : node.moduleSpecifier;
724
- }
725
- if (isImportEqualsDeclaration(node)) {
726
- const reference = node.moduleReference;
727
- return !node.isTypeOnly && isExternalModuleReference(reference)
728
- ? reference.expression
729
- : undefined;
730
- }
731
- if (isCallExpression(node) && node.expression.kind === SyntaxKind.ImportKeyword) {
732
- const [argument] = node.arguments;
733
- return isLiteralText(argument) ? argument : undefined;
734
- }
735
- return undefined;
736
- }
737
-
738
- /**
739
- * The own file a module specifier with a query evaluates ({@link queriedModulePath}): the
740
- * checker resolves the whole specifier to whatever module declaration matches it.
741
- */
742
- function queriedFile(
743
- file: SourceFile,
744
- specifier: Node,
745
- fileIdByName: ReadonlyMap<string, string>,
746
- ): string | undefined {
747
- if (!isStringLiteral(specifier) && !isNoSubstitutionTemplateLiteral(specifier)) {
748
- return undefined;
749
- }
750
- const path = queriedModulePath(specifier.text);
751
- return path === undefined
752
- ? undefined
753
- : fileIdByName.get(resolvePath(dirnamePath(file.fileName), path));
754
- }
755
-
756
- /**
757
- * The re-exports one node writes, if it is an export declaration. A specifier and a
758
- * namespace export are declarations whose names are where they are written, so no use is
759
- * read from them; each references what it carries forward instead. A bare star declares
760
- * nothing, so the declaration enclosing it, at the top level the file, references the
761
- * module it names.
762
- */
763
- function linksOf(
764
- file: SourceFile,
765
- node: Node,
766
- declarations: ReadonlyMap<string, string>,
767
- enclosing: string,
768
- ): Link[] {
769
- if (!isExportDeclaration(node)) {
770
- return [];
771
- }
772
- const clause = node.exportClause;
773
- const specifier = node.moduleSpecifier;
774
- if (clause === undefined) {
775
- return specifier === undefined
776
- ? []
777
- : [{ node: specifier, at: specifier, from: enclosing, alias: false }];
778
- }
779
- if (isNamespaceExport(clause)) {
780
- const from = declarations.get(nodeKey(file, clause));
781
- return from === undefined
782
- ? []
783
- : [{ node: clause.name, at: specifier ?? clause.name, from, alias: true }];
784
- }
785
- const found: Link[] = [];
786
- for (const element of clause.elements) {
787
- const from = declarations.get(nodeKey(file, element));
788
- if (from !== undefined) {
789
- found.push({
790
- node: element.name,
791
- at: element.propertyName ?? element.name,
792
- from,
793
- alias: true,
794
- });
795
- }
796
- }
797
- return found;
798
- }
799
-
800
- /**
801
- * Whether one object literal is a destructuring target, written on the left of an
802
- * assignment or as a `for` loop's initializer, through the literals and spreads it nests in.
803
- */
804
- function isAssignmentPattern(node: Node): boolean {
805
- let child = node;
806
- let parent = node.parent;
807
- while (
808
- isParenthesizedExpression(parent) ||
809
- isArrayLiteralExpression(parent) ||
810
- isObjectLiteralExpression(parent) ||
811
- isSpreadElement(parent) ||
812
- isSpreadAssignment(parent) ||
813
- (isPropertyAssignment(parent) && parent.initializer === child)
814
- ) {
815
- child = parent;
816
- parent = parent.parent;
817
- }
818
- if (isBinaryExpression(parent)) {
819
- return parent.operatorToken.kind === SyntaxKind.EqualsToken && parent.left === child;
820
- }
821
- return (isForInStatement(parent) || isForOfStatement(parent)) && parent.initializer === child;
822
- }
823
-
824
- /**
825
- * Every name node one file holds that is a use rather than a declaration, in source
826
- * order, each with its enclosing declaration and its use, and every re-export the file
827
- * writes. One walk answers all of it, because a second would have to agree with this one
828
- * about which nodes are names. A declaration's own declared name is left out, which is
829
- * what makes a declaration referenced only from its own site an unreferenced one, and so
830
- * is every node of `unread`, a subtree that is no use.
831
- */
832
- function sitesOf(
833
- file: SourceFile,
834
- declarations: ReadonlyMap<string, string>,
835
- fileId: string,
836
- unread: ReadonlySet<Node>,
837
- ): FileSites {
838
- const found: Site[] = [];
839
- const reads: Read[] = [];
840
- const links: Link[] = [];
841
- const evaluations: Evaluation[] = [];
842
- const wholes: WholeRead[] = [];
843
- const urls: ModuleURLAt[] = [];
844
- const workers: WorkerSite[] = [];
845
- const loadCalls: LoadCall[] = [];
846
- const decorated: Decorated[] = [];
847
- const parameterWrites: ParameterWrite[] = [];
848
- const namespaces = namespaceUses(file);
849
- const literals: LiteralAt[] = [];
850
- const flows: FlowAt[] = [];
851
- const writes = new Set<number>();
852
- const alsoRead = new Set<number>();
853
- const accesses = { written: new Set<Node>(), read: new Set<Node>() };
854
- const keyed: KeyedAccess[] = [];
855
- const declared = new Set<number>();
856
- const shorthands = new Map<number, Node>();
857
- let enclosing = fileId;
858
-
859
- const markNames = (node: Node): void => {
860
- if (isShorthandPropertyAssignment(node)) {
861
- shorthands.set(node.name.pos, node);
862
- return;
863
- }
864
- // A property access carries the member it names where a declaration carries its
865
- // declared name, and it is the one form of the grammar that does.
866
- if (isPropertyAccessExpression(node)) {
867
- return;
868
- }
869
- const named = node as { readonly name?: Node; readonly propertyName?: Node };
870
- for (const name of [named.name, named.propertyName]) {
871
- if (name !== undefined && isName(name)) {
872
- declared.add(name.pos);
873
- }
874
- }
875
- };
876
-
877
- const visit = (node: Node): void => {
878
- if (unread.has(node)) {
879
- return;
880
- }
881
- const namespace = namespaces.get(node);
882
- if (namespace?.kind === "whole") {
883
- wholes.push({ specifier: namespace.specifier, at: node, from: enclosing });
884
- } else if (namespace?.kind === "indexed") {
885
- found.push({ node: namespace.literal, shorthand: undefined, from: enclosing, use: "read" });
886
- }
887
- if (isName(node)) {
888
- if (!declared.has(node.pos)) {
889
- const site = { node, shorthand: shorthands.get(node.pos), from: enclosing };
890
- if (writes.has(node.pos)) {
891
- found.push({ ...site, use: "write" });
892
- }
893
- if (!writes.has(node.pos) || alsoRead.has(node.pos)) {
894
- found.push({ ...site, use: "read" });
895
- }
896
- }
897
- return;
898
- }
899
- markNames(node);
900
- markStores(node, writes, alsoRead, accesses);
901
- const key = isElementAccessExpression(node) ? keyNameOf(node.argumentExpression) : undefined;
902
- if (key !== undefined && isElementAccessExpression(node)) {
903
- const written = accesses.written.has(node);
904
- keyed.push({
905
- access: node,
906
- key,
907
- from: enclosing,
908
- use: written ? (accesses.read.has(node) ? "both" : "write") : "read",
909
- });
910
- }
911
- if (isObjectLiteralExpression(node) && !isAssignmentPattern(node)) {
912
- literals.push({ literal: node, from: enclosing });
913
- }
914
- // An assertion needs only comparability, so no member of its operand is read by it.
915
- for (const written of isAsExpression(node) ? [] : handedOn(node)) {
916
- flows.push(...valuesOf(written).map((value) => ({ value, from: enclosing })));
917
- }
918
- for (const read of propertyReadsOf(node)) {
919
- reads.push({ ...read, from: enclosing });
920
- }
921
- links.push(...linksOf(file, node, declarations, enclosing));
922
- const evaluated = evaluationOf(node);
923
- if (evaluated !== undefined) {
924
- evaluations.push({ ...evaluated, from: enclosing });
925
- }
926
- const url = moduleURLOf(node);
927
- if (url !== undefined) {
928
- urls.push({ ...url, from: enclosing });
929
- }
930
- const worker = workerOf(node);
931
- if (worker !== undefined) {
932
- workers.push({ ...worker, from: enclosing });
933
- }
934
- if (isLoadCall(node)) {
935
- loadCalls.push({ call: node, from: enclosing });
936
- }
937
- const held = declarations.get(nodeKey(file, node));
938
- const outer = enclosing;
939
- // A parameter property's annotation, initializer and decorators stay with the
940
- // parameter when the member goes, so they are written inside the constructor's class.
941
- if (held !== undefined && isParameterProperty(node)) {
942
- parameterWrites.push({
943
- name: (node as ParameterDeclaration).name,
944
- from: outer,
945
- member: held,
946
- });
947
- } else if (held !== undefined) {
948
- enclosing = held;
949
- for (const modifier of (node as { readonly modifiers?: readonly Node[] }).modifiers ?? []) {
950
- if (isDecorator(modifier)) {
951
- decorated.push({ decorator: modifier, declaration: held });
952
- }
953
- }
954
- }
955
- node.forEachChild(visit);
956
- enclosing = outer;
957
- };
958
-
959
- file.forEachChild(visit);
960
- return {
961
- uses: found,
962
- literals,
963
- flows,
964
- reads,
965
- links,
966
- evaluations,
967
- wholes,
968
- urls,
969
- workers,
970
- loadCalls,
971
- decorated,
972
- parameterWrites,
973
- keyed,
974
- };
146
+ /** Whether the program is one the run formed to resolve as Node does (program.36). */
147
+ readonly formedForNode?: boolean;
975
148
  }
976
149
 
977
150
  /** The declaration kinds of a property an object literal declares. */
@@ -983,184 +156,10 @@ const LITERAL_PROPERTY_KINDS: ReadonlySet<SyntaxKind> = new Set([
983
156
  SyntaxKind.SetAccessor,
984
157
  ]);
985
158
 
986
- /** The rule a file the analysis classified as test-support code is counted under. */
987
- const TEST_SUPPORT_RULE = "test-support";
988
-
989
- /**
990
- * The name one test-file rule takes. A rule's name is a word list the report's own
991
- * vocabulary spells, which cannot carry a pattern, so a rule is named by the place of
992
- * its pattern in the configured list.
993
- */
994
- function patternRule(index: number): string {
995
- return `test-file-pattern-${String(index + 1)}`;
996
- }
997
-
998
- /**
999
- * The rules over the test files of several projects at once, ordered by rule: a file two
1000
- * projects hold is one file, so each rule counts the distinct paths it matches.
1001
- */
1002
- export function testFileRulesOf(
1003
- testFiles: readonly string[],
1004
- paths: readonly string[],
1005
- supportFiles: readonly string[] = [],
1006
- ): readonly TestFileRule[] {
1007
- const distinct = [...new Set(paths)];
1008
- const support = new Set(supportFiles).size;
1009
- return [
1010
- ...testFiles.map((pattern, index) => {
1011
- const expression = globExpression(pattern);
1012
- return {
1013
- rule: patternRule(index),
1014
- matched: distinct.filter((path) => expression.test(path)).length,
1015
- };
1016
- }),
1017
- ...(support === 0 ? [] : [{ rule: TEST_SUPPORT_RULE, matched: support }]),
1018
- ].sort((a, b) => bytewise(a.rule, b.rule));
1019
- }
1020
-
1021
- const importedCache = new WeakMap<
1022
- SourceFile,
1023
- ReadonlyMap<string, readonly (string | undefined)[]>
1024
- >();
1025
-
1026
- /**
1027
- * Each local name one file's imports bind, mapped to the names it imports: the
1028
- * exported name of a named import, `default` for a default import, and `undefined` for
1029
- * a namespace import or an import assignment, which binds a whole module.
1030
- */
1031
- function importedNames(file: SourceFile): ReadonlyMap<string, readonly (string | undefined)[]> {
1032
- const known = importedCache.get(file);
1033
- if (known !== undefined) {
1034
- return known;
1035
- }
1036
- const names = new Map<string, (string | undefined)[]>();
1037
- const bind = (local: string, imported: string | undefined): void => {
1038
- names.set(local, [...(names.get(local) ?? []), imported]);
1039
- };
1040
- for (const statement of file.statements) {
1041
- if (isImportEqualsDeclaration(statement)) {
1042
- bind(statement.name.text, undefined);
1043
- continue;
1044
- }
1045
- const clause = isImportDeclaration(statement) ? statement.importClause : undefined;
1046
- if (clause === undefined) {
1047
- continue;
1048
- }
1049
- if (clause.name !== undefined) {
1050
- bind(clause.name.text, "default");
1051
- }
1052
- const bindings = clause.namedBindings;
1053
- if (bindings === undefined) {
1054
- continue;
1055
- }
1056
- if (bindings.kind === SyntaxKind.NamespaceImport) {
1057
- bind(bindings.name.text, undefined);
1058
- } else {
1059
- for (const element of bindings.elements) {
1060
- bind(element.name.text, element.propertyName?.text ?? element.name.text);
1061
- }
1062
- }
1063
- }
1064
- importedCache.set(file, names);
1065
- return names;
1066
- }
1067
-
1068
- /** The name node of every import binding one file's top level declares. */
1069
- function importBindings(file: SourceFile): readonly { node: Node }[] {
1070
- const found: { node: Node }[] = [];
1071
- for (const statement of file.statements) {
1072
- if (isImportEqualsDeclaration(statement)) {
1073
- found.push({ node: statement.name });
1074
- continue;
1075
- }
1076
- const clause = isImportDeclaration(statement) ? statement.importClause : undefined;
1077
- if (clause === undefined) {
1078
- continue;
1079
- }
1080
- if (clause.name !== undefined) {
1081
- found.push({ node: clause.name });
1082
- }
1083
- const bindings = clause.namedBindings;
1084
- if (bindings === undefined) {
1085
- continue;
1086
- }
1087
- if (bindings.kind === SyntaxKind.NamespaceImport) {
1088
- found.push({ node: bindings.name });
1089
- } else {
1090
- found.push(...bindings.elements.map((element) => ({ node: element.name })));
1091
- }
1092
- }
1093
- return found;
1094
- }
1095
-
1096
- /** The value one assignment stores through the name it writes, where it is a plain assignment. */
1097
- function storedValue(name: Node): Node | undefined {
1098
- let target: Node = name;
1099
- if (isPropertyAccessExpression(name.parent) && name.parent.name === name) {
1100
- target = name.parent;
1101
- }
1102
- while (isParenthesizedExpression(target.parent)) {
1103
- target = target.parent;
1104
- }
1105
- const parent = target.parent;
1106
- return isBinaryExpression(parent) &&
1107
- parent.operatorToken.kind === SyntaxKind.EqualsToken &&
1108
- parent.left === target
1109
- ? parent.right
1110
- : undefined;
1111
- }
1112
-
1113
- /**
1114
- * The import bindings of one file that only the values written into one declaration
1115
- * use, for each write into it. A binding is named by an identifier that spells its
1116
- * local name and is not the member a property access names.
1117
- */
1118
- function aloneIn(
1119
- file: SourceFile,
1120
- uses: readonly Site[],
1121
- writes: readonly { index: number; value: Node }[],
1122
- found: readonly Reference[],
1123
- ): readonly { index: number; names: readonly string[] }[] {
1124
- const bindings = new Set(importBindings(file).map((binding) => binding.node.getText()));
1125
- if (bindings.size === 0) {
1126
- return [];
1127
- }
1128
- const occurrences = new Map<string, Node[]>();
1129
- for (const site of uses) {
1130
- const text = site.node.getText();
1131
- const parent = site.node.parent;
1132
- if (!bindings.has(text) || (isPropertyAccessExpression(parent) && parent.name === site.node)) {
1133
- continue;
1134
- }
1135
- if (!occurrences.get(text)?.includes(site.node)) {
1136
- occurrences.set(text, [...(occurrences.get(text) ?? []), site.node]);
1137
- }
1138
- }
1139
- const inside = (node: Node, value: Node): boolean =>
1140
- node.pos >= value.pos && node.end <= value.end;
1141
- const result: { index: number; names: readonly string[] }[] = [];
1142
- for (const write of writes) {
1143
- const to = found[write.index]?.to;
1144
- const sameTarget = writes.filter((one) => found[one.index]?.to === to);
1145
- const names = [...occurrences]
1146
- .filter(
1147
- ([, nodes]) =>
1148
- nodes.some((node) => inside(node, write.value)) &&
1149
- nodes.every((node) => sameTarget.some((one) => inside(node, one.value))),
1150
- )
1151
- .map(([name]) => name)
1152
- .sort(bytewise);
1153
- if (names.length > 0) {
1154
- result.push({ index: write.index, names });
1155
- }
1156
- }
1157
- return result;
1158
- }
1159
-
1160
159
  /**
1161
- * Every reference one project's own files make to its own declarations, and the files the
1162
- * test-file rules classified. `held` is the same project's inventory, where a resolved symbol
1163
- * is looked up; positions render against `targetRoot`, or a consumer's root as
160
+ * Every reference one side's files make to the target's declarations. `held` is the
161
+ * target's inventory over the files the project holds, where a resolved symbol is looked up;
162
+ * positions render against `targetRoot`, or a consumer's root as
1164
163
  * {@link ReferenceOptions.consumer} states. The order is by position, then the declaration
1165
164
  * named, the referencing declaration and the use: an alias chain or a bare star re-export
1166
165
  * names several at one position, and one name references one declaration once.
@@ -1173,23 +172,9 @@ export function references<Brand>(
1173
172
  ): References {
1174
173
  const cap = options.batchCap ?? DEFAULT_BATCH_CAP;
1175
174
  const declarations = held.declarations;
1176
- const patterns = options.testFiles.map(globExpression);
1177
- const found: Reference[] = [];
1178
- const tests: string[] = [];
1179
175
  const chains = aliasChains(project, held);
1180
176
  const tables = propertyTables(project.queries);
1181
- const destructured = destructuring(project, cap, tables);
1182
- let batched = 0;
1183
- let fileBatches = 0;
1184
- let residueFallbacks = 0;
1185
- let platformLookups = 0;
1186
- const asked = (): void => {
1187
- platformLookups += 1;
1188
- };
1189
- let shorthandLookups = 0;
1190
-
1191
177
  const consumer = options.consumer;
1192
- const root = consumer?.root ?? targetRoot;
1193
178
  const files =
1194
179
  consumer === undefined
1195
180
  ? project.ownSourceFiles()
@@ -1200,10 +185,14 @@ export function references<Brand>(
1200
185
  relativePath(consumer.root, file.fileName) !== undefined &&
1201
186
  relativePath(targetRoot, file.fileName) === undefined,
1202
187
  );
188
+ const sink = new Sink(consumer?.root ?? targetRoot, options.testFile, consumer);
1203
189
  const ownPaths = project.ownPaths();
1204
190
  const fileIds = new Set(
1205
191
  held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id),
1206
192
  );
193
+ const declaredAt = (node: Node): string | undefined =>
194
+ declarations.get(nodeKey(node.getSourceFile(), node));
195
+
1207
196
  let moduleBodies: ReadonlyMap<string, readonly InventorySymbol[]> | undefined;
1208
197
  /**
1209
198
  * The declarations one module holds directly: a file's top level, or the body of a
@@ -1213,23 +202,20 @@ export function references<Brand>(
1213
202
  if (moduleBodies === undefined) {
1214
203
  const byModule = new Map<string, InventorySymbol[]>();
1215
204
  for (const symbol of held.symbols) {
1216
- if (!fileIds.has(symbol.parent) && !held.modules.has(symbol.parent)) {
1217
- continue;
1218
- }
1219
- const siblings = byModule.get(symbol.parent);
1220
- if (siblings === undefined) {
1221
- byModule.set(symbol.parent, [symbol]);
1222
- } else {
1223
- siblings.push(symbol);
205
+ if (fileIds.has(symbol.parent) || held.modules.has(symbol.parent)) {
206
+ byModule.set(symbol.parent, [...(byModule.get(symbol.parent) ?? []), symbol]);
1224
207
  }
1225
208
  }
1226
209
  moduleBodies = byModule;
1227
210
  }
1228
211
  return moduleBodies.get(moduleId) ?? [];
1229
212
  };
1230
-
1231
- let ownFiles: SourceFiles | undefined;
1232
- const unseenLoads: Position[] = [];
213
+ const exportedBy = (ids: Iterable<string>): readonly ChainTarget[] =>
214
+ [...ids].flatMap((id) =>
215
+ declaredIn(id)
216
+ .filter((symbol) => symbol.exported)
217
+ .map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })),
218
+ );
1233
219
  const exportsCache = new Map<number, readonly ChainTarget[]>();
1234
220
  /**
1235
221
  * Every declaration one module exports, each re-export walked along its chain. A module
@@ -1237,11 +223,7 @@ export function references<Brand>(
1237
223
  */
1238
224
  const exportsRead = (module: Answer<TSSymbol>): readonly ChainTarget[] => {
1239
225
  if (module === UNANSWERED) {
1240
- return [...fileIds].flatMap((id) =>
1241
- declaredIn(id)
1242
- .filter((symbol) => symbol.exported)
1243
- .map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })),
1244
- );
226
+ return exportedBy(fileIds);
1245
227
  }
1246
228
  const known = exportsCache.get(module.id);
1247
229
  if (known !== undefined) {
@@ -1250,56 +232,37 @@ export function references<Brand>(
1250
232
  const table = project.queries.exportsOfModule(module);
1251
233
  const read =
1252
234
  table === UNANSWERED
1253
- ? chains.declarationsOf(module).flatMap((id) =>
1254
- declaredIn(id)
1255
- .filter((symbol) => symbol.exported)
1256
- .map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })),
1257
- )
235
+ ? exportedBy(chains.declarationsOf(module))
1258
236
  : table.flatMap((exported) => chains.chainOf(exported));
1259
237
  exportsCache.set(module.id, read);
1260
238
  return read;
1261
239
  };
1262
240
 
1263
- /** The identifier of the declaration one node of this program is, where it is one. */
1264
- const declaredAt = (node: Node): string | undefined =>
1265
- declarations.get(nodeKey(node.getSourceFile(), node));
1266
-
1267
241
  /**
1268
- * The declarations one re-export carries forward: for an alias, the first link along
1269
- * its chain that the inventory declares, stepping over the links it does not, such as
1270
- * an import the re-export is written against; for a bare star, the module it names.
1271
- * Each later link is a declaration of its own that references the next, so one link
1272
- * per re-export is what lets a chain be followed one declaration at a time.
242
+ * Every declaration bearing the name one node spells, or the name an import of its file
243
+ * binds that name to, which is what a reference the checker could not resolve is counted
244
+ * as (program.33): it may be a use of any of them. A name a namespace import binds may be
245
+ * a use of any file.
1273
246
  */
1274
- const carriedBy = (link: Link, symbol: TSSymbol): readonly ChainTarget[] =>
1275
- link.alias
1276
- ? chains.nextDeclared(symbol)
1277
- : chains.declarationsOf(symbol).map((id) => ({ id, stepped: false, guessed: false }));
1278
-
1279
- /**
1280
- * Every declaration bearing the name one node spells, or the name an import of its
1281
- * file binds that name to, which is what a reference the checker could not resolve is
1282
- * counted as: it may be a use of any of them, and a declaration it may use is kept
1283
- * live. A name a namespace import binds may be a use of any file.
1284
- */
1285
- const named = (node: Node): readonly string[] => {
1286
- const byName = held.byName;
247
+ const named = (node: Node): readonly ChainTarget[] => {
1287
248
  const text = node.getText();
1288
- const imported = importedNames(node.getSourceFile()).get(text) ?? [];
249
+ const imported = bindingsOf(node.getSourceFile()).filter(
250
+ (binding) => binding.name.text === text,
251
+ );
1289
252
  return [
1290
253
  ...new Set([
1291
- ...(byName.get(text) ?? []),
1292
- ...imported.flatMap((name) =>
1293
- name === undefined ? [...fileIds] : (byName.get(name) ?? []),
254
+ ...(held.byName.get(text) ?? []),
255
+ ...imported.flatMap((binding) =>
256
+ binding.imported === undefined ? [...fileIds] : (held.byName.get(binding.imported) ?? []),
1294
257
  ),
1295
258
  ]),
1296
- ];
259
+ ].map((id) => ({ id, stepped: false, guessed: true }));
1297
260
  };
1298
261
 
1299
262
  // A type-query alias's own type names its export without using it; a use of the
1300
263
  // global is the use, so it names every link of the export's chain as well.
1301
264
  const aliases = project.ownSourceFiles().flatMap(typeQueryAliases);
1302
- const unread = new Set(aliases.map((alias) => alias.type));
265
+ const unread = new Set<Node>(aliases.map((alias) => alias.type));
1303
266
  const aliased = new Map<string, readonly ChainTarget[]>();
1304
267
  if (aliases.length > 0) {
1305
268
  const exported = project.symbolsAt(aliases.map((alias) => project.handle(alias.exported)));
@@ -1339,43 +302,34 @@ export function references<Brand>(
1339
302
  return all;
1340
303
  };
1341
304
 
1342
- /** The symbol one site resolves to, asking only the accessor the site's form names. */
1343
- const symbolFor = (
1344
- site: Site,
1345
- answered: ReadonlyMap<Node, Answer<TSSymbol>>,
1346
- ): Answer<TSSymbol | undefined> => {
1347
- if (site.shorthand !== undefined) {
1348
- shorthandLookups += 1;
1349
- return project.shorthandValueAt(project.handle(site.shorthand));
1350
- }
1351
- const batched = answered.get(site.node);
1352
- if (batched === UNANSWERED || typeof batched === "object") {
1353
- return batched;
1354
- }
1355
- residueFallbacks += 1;
1356
- return project.resolvedSymbolAt(project.handle(site.node));
1357
- };
1358
-
1359
- const walked = files.map((file) => {
305
+ const rules = READ_RULES.map((rule) => rule.start());
306
+ const sites = files.map((file) => {
1360
307
  const fileId = declaredAt(file) ?? "";
1361
- return { file, fileId, ...sitesOf(file, declarations, fileId, unread) };
308
+ return {
309
+ file,
310
+ fileId,
311
+ ...sitesOf(file, declarations, fileId, unread, (node, from) => {
312
+ for (const rule of rules) {
313
+ rule.visit(node, { file, from });
314
+ }
315
+ }),
316
+ };
1362
317
  });
1363
- const fileIdByName = new Map<string, string>(
1364
- walked.filter((one) => fileIds.has(one.fileId)).map((one) => [one.file.fileName, one.fileId]),
1365
- );
1366
- const loadable = walked
318
+ const loadable = sites
1367
319
  .filter((one) => fileIds.has(one.fileId))
1368
320
  .map(({ file, fileId }) => ({ file, fileId }));
1369
- const loadableById = new Map(loadable.map((one) => [one.fileId, one.file]));
321
+ const directories = options.directories;
322
+ const destructured = destructuring(project, cap, tables);
1370
323
  const contextual = contextualUses(
1371
324
  project,
1372
325
  held,
1373
326
  chains,
1374
- walked.flatMap((one) => one.literals),
1375
- walked.flatMap((one) => one.flows),
327
+ sites.flatMap((one) => one.literals),
328
+ sites.flatMap((one) => one.flows),
1376
329
  cap,
1377
330
  tables,
1378
331
  );
332
+
1379
333
  const writtenBy = new Map<string, readonly string[]>();
1380
334
  for (const write of contextual.writes) {
1381
335
  const property = write.at.parent;
@@ -1392,608 +346,89 @@ export function references<Brand>(
1392
346
  return known;
1393
347
  }
1394
348
  const found: ChainTarget[] = [];
1395
- if (writtenBy.size > 0) {
1396
- for (const handle of symbol.declarations) {
1397
- if (!LITERAL_PROPERTY_KINDS.has(handle.kind) || !ownPaths.has(handle.path)) {
1398
- continue;
1399
- }
1400
- const node = project.declarationAt(handle)?.node;
1401
- const ids =
1402
- node === undefined ? undefined : writtenBy.get(nodeKey(node.getSourceFile(), node));
1403
- for (const id of ids ?? []) {
1404
- if (!found.some((target) => target.id === id)) {
1405
- found.push({ id, stepped: true, guessed: false });
1406
- }
349
+ for (const handle of writtenBy.size === 0 ? [] : symbol.declarations) {
350
+ if (!LITERAL_PROPERTY_KINDS.has(handle.kind) || !ownPaths.has(handle.path)) {
351
+ continue;
352
+ }
353
+ const node = project.declarationAt(handle)?.node;
354
+ const ids =
355
+ node === undefined ? undefined : writtenBy.get(nodeKey(node.getSourceFile(), node));
356
+ for (const id of ids ?? []) {
357
+ if (!found.some((target) => target.id === id)) {
358
+ found.push({ id, stepped: true, guessed: false });
1407
359
  }
1408
360
  }
1409
361
  }
1410
362
  forwardedCache.set(symbol.id, found);
1411
363
  return found;
1412
364
  };
1413
- /** One resolved symbol's declarations along its chain, and the members its literal properties write. */
1414
- const targetsOf = (symbol: TSSymbol): readonly ChainTarget[] => {
1415
- const through = forwarded(symbol);
1416
- return through.length === 0 ? chains.chainOf(symbol) : [...chains.chainOf(symbol), ...through];
365
+
366
+ let ownFiles: SourceFiles | undefined;
367
+ const pass: Pass<Brand> = {
368
+ project,
369
+ held,
370
+ chains,
371
+ tables,
372
+ cap,
373
+ sink,
374
+ consumer: consumer !== undefined,
375
+ directories,
376
+ fileIds,
377
+ fileIdByName: new Map(loadable.map((one) => [one.file.fileName, one.fileId])),
378
+ loadableById: new Map(loadable.map((one) => [one.fileId, one.file])),
379
+ destructured,
380
+ unseenLoads: [],
381
+ formedForNode: options.formedForNode === true,
382
+ possiblyLoaded: new Set(),
383
+ declaredIn,
384
+ exportsRead,
385
+ named,
386
+ targetsOf: (symbol) => {
387
+ const through = forwarded(symbol);
388
+ return through.length === 0
389
+ ? chains.chainOf(symbol)
390
+ : [...chains.chainOf(symbol), ...through];
391
+ },
392
+ throughAliases,
393
+ ownFiles: () => (ownFiles ??= sourceFilesOf(project, targetRoot)),
1417
394
  };
1418
- const testFile = new Map<SourceFile, boolean>();
1419
- for (const { file } of walked) {
1420
- const path = renderPosition(file, root, 0).path;
1421
- const test =
1422
- patterns.some((pattern) => pattern.test(path)) ||
1423
- options.testFileNames?.has(file.fileName) === true;
1424
- testFile.set(file, test);
1425
- if (test) {
1426
- tests.push(path);
1427
- }
395
+
396
+ for (const site of contextual.writes) {
397
+ const file = site.at.getSourceFile();
398
+ sink.add(file, site.at, site.from, site.ids, "write", "contextual", site.at.parent);
1428
399
  }
1429
- /** Each write reference's index in `found`, by its file, with the value it stores. */
1430
- const valued = new Map<SourceFile, { index: number; value: Node }[]>();
1431
- const holdsValue = (file: SourceFile, value: Node | undefined): void => {
1432
- if (value !== undefined) {
1433
- valued.set(file, [...(valued.get(file) ?? []), { index: found.length - 1, value }]);
1434
- }
1435
- };
1436
- for (const [use, sites] of [
1437
- ["write", contextual.writes],
1438
- ["read", contextual.reads],
1439
- ] as const) {
1440
- for (const site of sites) {
1441
- const file = site.at.getSourceFile();
1442
- const position = renderPosition(file, root, site.at.getStart());
1443
- for (const id of site.ids) {
1444
- found.push({
1445
- from: site.from,
1446
- to: id,
1447
- position,
1448
- use,
1449
- resolution: "contextual",
1450
- test: testFile.get(file) === true,
1451
- });
1452
- if (use === "write") {
1453
- holdsValue(file, site.at.parent);
1454
- }
1455
- }
1456
- }
400
+ for (const site of contextual.reads) {
401
+ sink.add(site.at.getSourceFile(), site.at, site.from, site.ids, "read", "contextual");
1457
402
  }
1458
-
1459
- const keyNames = computedKeyNames(held.symbols);
1460
- for (const {
1461
- file,
1462
- fileId,
1463
- uses,
1464
- reads,
1465
- links,
1466
- evaluations,
1467
- wholes,
1468
- urls,
1469
- workers,
1470
- loadCalls,
1471
- decorated,
1472
- parameterWrites,
1473
- keyed,
1474
- } of walked) {
1475
- const test = testFile.get(file) === true;
1476
- const loads = computedLoadsOf(file, loadCalls, loadable, (fileName) =>
1477
- options.directories.bundlerRootOf(fileName),
1478
- );
1479
- // A component file's markup may use any binding its blocks declare at the top level.
1480
- const markup = isComponentFile(file);
1481
- const bindings = markup ? importBindings(file) : [];
1482
-
1483
- // A decorator is written inside the declaration it is attached to and receives it,
1484
- // so the declaration is used where the decorator is written, whatever the
1485
- // decorator does with it.
1486
- for (const { decorator, declaration } of decorated) {
1487
- found.push({
1488
- from: declaration,
1489
- to: declaration,
1490
- position: renderPosition(file, root, decorator.getStart()),
1491
- use: "decorator",
1492
- resolution: "syntax",
1493
- test,
1494
- });
1495
- }
1496
-
1497
- // The constructor stores its argument in the member a parameter property declares.
1498
- for (const { name, from, member } of parameterWrites) {
1499
- found.push({
1500
- from,
1501
- to: member,
1502
- position: renderPosition(file, root, name.getStart()),
1503
- use: "write",
1504
- resolution: "syntax",
1505
- test,
1506
- });
1507
- }
1508
-
1509
- // One batch per capped run of the file's name nodes, the re-exports' nodes after
1510
- // the uses and the evaluated modules' specifiers last, each node once: a bare star
1511
- // re-export's specifier is a link and an evaluation both. A shorthand is left out
1512
- // of the batch: its name resolves to the property the literal declares, so the
1513
- // batch's answer for it would name something this project's inventory never holds.
1514
- const batching = [
1515
- ...new Set([
1516
- ...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
1517
- ...links.map((link) => link.node),
1518
- ...evaluations.flatMap((evaluation) =>
1519
- evaluation.require === undefined
1520
- ? [evaluation.node]
1521
- : [
1522
- evaluation.require,
1523
- ...(evaluation.require.text === "require" ? [evaluation.node] : []),
1524
- ],
1525
- ),
1526
- ...wholes.map((whole) => whole.specifier),
1527
- ...urls.flatMap((url) => url.constructor ?? []),
1528
- ...workers.flatMap((worker) =>
1529
- worker.holder === undefined ? [worker.constructor] : [worker.constructor, worker.holder],
1530
- ),
1531
- ...bindings.map((binding) => binding.node),
1532
- ...loads.flatMap((load) => [
1533
- ...(load.require === undefined ? [] : [load.require]),
1534
- ...(load.asset ? [] : load.files.map((loaded) => loaded.file)),
1535
- ]),
1536
- ]),
1537
- ];
1538
- const answered = new Map<Node, Answer<TSSymbol>>();
1539
- batched += batching.length;
1540
- for (let from = 0; from < batching.length; from += cap) {
1541
- const run = batching.slice(from, from + cap);
1542
- const answers = project.symbolsAt(
1543
- run.map((node) => project.handle(node)),
1544
- cap,
1545
- );
1546
- fileBatches += 1;
1547
- run.forEach((node, index) => {
1548
- const symbol = answers[index];
1549
- if (symbol !== undefined) {
1550
- answered.set(node, symbol);
1551
- }
1552
- });
1553
- }
1554
-
1555
- for (const link of links) {
1556
- const symbol = answered.get(link.node);
1557
- if (symbol === undefined) {
1558
- continue;
1559
- }
1560
- const position = renderPosition(file, root, link.at.getStart());
1561
- const carried =
1562
- symbol === UNANSWERED
1563
- ? named(link.at).map((id) => ({ id, stepped: false, guessed: true }))
1564
- : carriedBy(link, symbol);
1565
- for (const target of carried) {
1566
- found.push({
1567
- from: link.from,
1568
- to: target.id,
1569
- position,
1570
- use: "read",
1571
- resolution: target.guessed ? "by-name" : link.alias ? "alias" : "batch",
1572
- test,
1573
- });
1574
- }
1575
- }
1576
-
1577
- const loading = evaluations.filter(
1578
- (one) =>
1579
- one.require === undefined ||
1580
- callsPlatform(project, "require", one.require, answered.get(one.require), asked),
1581
- );
1582
- // A script declares no module, so the checker names nothing for a specifier of one;
1583
- // the compiler's module resolution still names its file.
1584
- const unnamed = loading.flatMap(({ node }) =>
1585
- answered.get(node) === undefined &&
1586
- isStringLiteralLikeNode(node) &&
1587
- queriedFile(file, node, fileIdByName) === undefined
1588
- ? [node]
1589
- : [],
1590
- );
1591
- const resolved = project.resolvedFileNames(unnamed);
1592
- const scripts = new Map<Node, string | undefined>(
1593
- unnamed.map((node, at) => {
1594
- const name = resolved[at];
1595
- return [node, name === undefined ? undefined : fileIdByName.get(name)];
1596
- }),
1597
- );
1598
- const wholeReads: { readonly evaluation: Evaluation; readonly fileId: string }[] = [];
1599
- for (const evaluation of loading) {
1600
- const queried = queriedFile(file, evaluation.node, fileIdByName);
1601
- if (queried !== undefined) {
1602
- found.push({
1603
- from: evaluation.from,
1604
- to: queried,
1605
- position: renderPosition(file, root, evaluation.node.getStart()),
1606
- use: "evaluation",
1607
- resolution: "syntax",
1608
- test,
1609
- });
1610
- }
1611
- const module = answered.get(evaluation.node);
1612
- // Node's require resolves a relative specifier the program records no resolution for,
1613
- // as in a TypeScript file, by its own extension search.
1614
- const script =
1615
- scripts.get(evaluation.node) ??
1616
- (evaluation.require !== undefined &&
1617
- module === undefined &&
1618
- isLiteralText(evaluation.node) &&
1619
- /^\.\.?\//u.test(evaluation.node.text)
1620
- ? fileNamed(file, evaluation.node.text, fileIdByName)
1621
- : undefined);
1622
- if (script !== undefined && evaluation.whole === true && module === undefined) {
1623
- wholeReads.push({ evaluation, fileId: script });
1624
- }
1625
- if (script !== undefined) {
1626
- found.push({
1627
- from: evaluation.from,
1628
- to: script,
1629
- position: renderPosition(file, root, evaluation.node.getStart()),
1630
- use: "evaluation",
1631
- resolution: "syntax",
1632
- test,
1633
- });
1634
- }
1635
- if (module === undefined) {
1636
- continue;
1637
- }
1638
- const position = renderPosition(file, root, evaluation.node.getStart());
1639
- // A module the checker could not resolve may be any file of the target.
1640
- const evaluated = module === UNANSWERED ? [...fileIds] : chains.declarationsOf(module);
1641
- for (const id of evaluated) {
1642
- if (fileIds.has(id)) {
1643
- found.push({
1644
- from: evaluation.from,
1645
- to: id,
1646
- position,
1647
- use: "evaluation",
1648
- resolution: module === UNANSWERED ? "by-name" : "batch",
1649
- test,
1650
- });
1651
- }
1652
- }
1653
- }
1654
-
1655
- // A required file the checker names no module for is read whole where the call's value
1656
- // is used: the value is the file's exports object, which this pass does not follow.
1657
- const required = wholeReads.flatMap((one) => {
1658
- const loaded = loadableById.get(one.fileId);
1659
- return loaded === undefined ? [] : [{ ...one, loaded }];
1660
- });
1661
- const modules = project.symbolsAt(
1662
- required.map((one) => project.handle(one.loaded)),
1663
- cap,
1664
- );
1665
- required.forEach(({ evaluation }, at) => {
1666
- const module = modules[at];
1667
- const position = renderPosition(file, root, evaluation.node.getStart());
1668
- for (const target of module === undefined ? [] : exportsRead(module)) {
1669
- found.push({
1670
- from: evaluation.from,
1671
- to: target.id,
1672
- position,
1673
- use: "read",
1674
- resolution: target.guessed ? "by-name" : "batch",
1675
- test,
1676
- });
1677
- }
1678
- });
1679
-
1680
- for (const url of urls) {
1681
- if (
1682
- url.constructor !== undefined &&
1683
- !callsPlatform(project, "URL", url.constructor, answered.get(url.constructor), asked)
1684
- ) {
1685
- continue;
1686
- }
1687
- const path = relativeURLPath(dirnamePath(file.fileName), url.specifier);
1688
- const loaded = path === undefined ? undefined : fileNamed(file, path, fileIdByName);
1689
- if (loaded !== undefined) {
1690
- found.push({
1691
- from: url.from,
1692
- to: loaded,
1693
- position: renderPosition(file, root, url.at.getStart()),
1694
- use: "evaluation",
1695
- resolution: "syntax",
1696
- test,
1697
- });
1698
- }
1699
- }
1700
-
1701
- for (const worker of workers) {
1702
- const symbol = answered.get(worker.constructor);
1703
- const text = worker.address.text;
1704
- let path: string | undefined;
1705
- if (startsThread(project, symbol, worker.holder && answered.get(worker.holder), asked)) {
1706
- path =
1707
- worker.evaluated || !/^\.\.?\//u.test(text)
1708
- ? undefined
1709
- : resolvePath(options.directories.workingDirectoryOf(file.fileName), text);
1710
- } else if (
1711
- (WORKER_CONSTRUCTORS.has(worker.constructor.text) ||
1712
- worker.constructor.text === "navigator") &&
1713
- isPlatformGlobal(project, symbol)
1714
- ) {
1715
- path = relativeURLPath(dirnamePath(file.fileName), text);
1716
- }
1717
- ownFiles ??= sourceFilesOf(project, targetRoot);
1718
- for (const started of path === undefined ? [] : ownFiles.named(path)) {
1719
- const to = declaredAt(started);
1720
- if (to !== undefined && fileIds.has(to)) {
1721
- found.push({
1722
- from: worker.from,
1723
- to,
1724
- position: renderPosition(file, root, worker.at.getStart()),
1725
- use: "evaluation",
1726
- resolution: "syntax",
1727
- test,
1728
- });
1729
- }
1730
- }
1731
- }
1732
-
1733
- // A computed load imports each file it loads where the call is written, reading what
1734
- // the file exports unless it reads the file as text or a URL; callsPlatform owns which
1735
- // call is a `require`.
1736
- for (const load of loads) {
1737
- if (
1738
- load.require !== undefined &&
1739
- !callsPlatform(project, "require", load.require, answered.get(load.require), asked)
1740
- ) {
1741
- continue;
1742
- }
1743
- const position = renderPosition(file, root, load.call.getStart());
1744
- if (!load.resolved && !test && consumer === undefined && !file.isDeclarationFile) {
1745
- unseenLoads.push(position);
1746
- }
1747
- for (const loaded of load.files) {
1748
- found.push({
1749
- from: load.eager ? fileId : load.from,
1750
- to: loaded.fileId,
1751
- position,
1752
- use: "evaluation",
1753
- resolution: "syntax",
1754
- test,
1755
- });
1756
- const module = load.asset ? undefined : answered.get(loaded.file);
1757
- for (const target of module === undefined ? [] : exportsRead(module)) {
1758
- found.push({
1759
- from: load.from,
1760
- to: target.id,
1761
- position,
1762
- use: "read",
1763
- resolution: target.guessed ? "by-name" : "batch",
1764
- test,
1765
- });
1766
- }
1767
- }
1768
- }
1769
-
1770
- for (const whole of wholes) {
1771
- const module = answered.get(whole.specifier);
1772
- if (module === undefined) {
1773
- continue;
1774
- }
1775
- const position = renderPosition(file, root, whole.at.getStart());
1776
- for (const target of exportsRead(module)) {
1777
- found.push({
1778
- from: whole.from,
1779
- to: target.id,
1780
- position,
1781
- use: "read",
1782
- resolution: target.guessed ? "by-name" : "batch",
1783
- test,
1784
- });
1785
- }
1786
- }
1787
-
1788
- if (markup) {
1789
- for (const symbol of declaredIn(fileId)) {
1790
- found.push({
1791
- from: fileId,
1792
- to: symbol.id,
1793
- position: symbol.position,
1794
- use: "read",
1795
- resolution: "syntax",
1796
- test,
1797
- });
1798
- }
1799
- for (const binding of bindings) {
1800
- const symbol = answered.get(binding.node);
1801
- if (symbol === undefined) {
1802
- continue;
1803
- }
1804
- const position = renderPosition(file, root, binding.node.getStart());
1805
- const chained =
1806
- symbol === UNANSWERED
1807
- ? named(binding.node).map((id) => ({ id, stepped: false, guessed: true }))
1808
- : chains.chainOf(symbol);
1809
- // The markup may name any export of a module a binding's chain reaches, whether a
1810
- // namespace import binds it or a named or default import of a namespace re-export.
1811
- const targets = chained.flatMap((target) => [
1812
- target,
1813
- ...declaredIn(target.id)
1814
- .filter((one) => one.exported)
1815
- .map((one) => ({ ...target, id: one.id })),
1816
- ]);
1817
- for (const target of targets) {
1818
- found.push({
1819
- from: fileId,
1820
- to: target.id,
1821
- position,
1822
- use: "read",
1823
- resolution: target.guessed ? "by-name" : target.stepped ? "alias" : "batch",
1824
- test,
1825
- });
1826
- }
1827
- }
1828
- }
1829
-
1830
- // A pattern names a property of the value it destructures, which no name node of
1831
- // the pattern resolves to: a shorthand's name is the local it binds.
1832
- destructured.resolve(reads).forEach((symbol, index) => {
1833
- const read = reads[index];
1834
- if (symbol === undefined || read === undefined) {
1835
- return;
1836
- }
1837
- const position = renderPosition(file, root, read.key.getStart());
1838
- const targets =
1839
- symbol === UNANSWERED
1840
- ? (held.byName.get(read.name) ?? []).map((id) => ({
1841
- id,
1842
- stepped: false,
1843
- guessed: true,
1844
- }))
1845
- : targetsOf(symbol);
1846
- for (const target of targets) {
1847
- found.push({
1848
- from: read.from,
1849
- to: target.id,
1850
- position,
1851
- use: "read",
1852
- resolution: target.guessed ? "by-name" : "destructured",
1853
- test,
1854
- });
1855
- }
1856
- });
1857
-
1858
- const resolvedKeys = keyed.flatMap((access) => {
1859
- const symbol =
1860
- isIdentifier(access.key) && keyNames.has(access.key.text)
1861
- ? answered.get(access.key)
1862
- : undefined;
1863
- return typeof symbol === "object" ? [{ access, symbol }] : [];
1864
- });
1865
- for (const { access, ids } of keyedUses(project, chains, resolvedKeys, tables, cap)) {
1866
- const position = renderPosition(file, root, access.access.argumentExpression.getStart());
1867
- for (const id of ids) {
1868
- for (const use of access.use === "both" ? (["read", "write"] as const) : [access.use]) {
1869
- found.push({ from: access.from, to: id, position, use, resolution: "keyed", test });
1870
- }
1871
- }
1872
- }
1873
-
1874
- for (const site of uses) {
1875
- const direct: Resolution =
1876
- site.shorthand !== undefined
1877
- ? "shorthand"
1878
- : typeof answered.get(site.node) === "object"
1879
- ? "batch"
1880
- : "resolved-symbol";
1881
- const symbol = symbolFor(site, answered);
1882
- if (symbol === undefined) {
1883
- continue;
1884
- }
1885
- const position = renderPosition(file, root, site.node.getStart());
1886
- // A re-export something imports is used and so is the declaration behind it, so
1887
- // the use names every link of the chain its name stands at the head of.
1888
- const targets = throughAliases(
1889
- symbol === UNANSWERED
1890
- ? named(site.node).map((id) => ({ id, stepped: false, guessed: true }))
1891
- : targetsOf(symbol),
1892
- );
1893
- for (const target of targets) {
1894
- found.push({
1895
- from: site.from,
1896
- to: target.id,
1897
- position,
1898
- use: site.use,
1899
- resolution: target.guessed ? "by-name" : target.stepped ? "alias" : direct,
1900
- test,
1901
- });
1902
- if (site.use === "write") {
1903
- holdsValue(file, storedValue(site.node));
1904
- }
1905
- }
1906
- }
403
+ for (const one of sites) {
404
+ emitFile(prepared(one, pass, loadable), pass);
1907
405
  }
1908
-
1909
- /** The ambient global interfaces of the walked files that merge into a declaration outside the project's own files. */
1910
- const libraryMerged = (scripts: ReadonlySet<string>): ReadonlySet<string> => {
1911
- const names = new Map<string, Node>();
1912
- const visit = (node: Node): void => {
1913
- if (isInterfaceDeclaration(node)) {
1914
- const id = declaredAt(node);
1915
- if (id !== undefined) {
1916
- names.set(id, node.name);
1917
- }
1918
- } else if (isModuleDeclaration(node) || isModuleBlock(node)) {
1919
- node.forEachChild(visit);
406
+ const context = { project, held, chains };
407
+ for (const rule of rules) {
408
+ for (const read of rule.reads(context)) {
409
+ if (read.kind === "named") {
410
+ sink.add(
411
+ read.file,
412
+ read.at,
413
+ read.from,
414
+ [read.to],
415
+ read.use,
416
+ read.via,
417
+ undefined,
418
+ read.production,
419
+ );
420
+ } else {
421
+ sink.hold(heldBy(read, context));
1920
422
  }
1921
- };
1922
- for (const { file } of walked) {
1923
- file.forEachChild(visit);
1924
423
  }
1925
- const global = new Set(ambientGlobalInterfaces(held, [...names.keys()], scripts));
1926
- const asked = [...names].filter(([id]) => global.has(id));
1927
- if (asked.length === 0) {
1928
- return new Set();
1929
- }
1930
- const answers = project.symbolsAt(asked.map(([, name]) => project.handle(name)));
1931
- return new Set(
1932
- asked.flatMap(([id], at) => {
1933
- const symbol = answers[at];
1934
- return typeof symbol === "object" &&
1935
- symbol.declarations.some((handle) => !ownPaths.has(handle.path))
1936
- ? [id]
1937
- : [];
1938
- }),
1939
- );
1940
- };
1941
-
1942
- const usesOf = new Map(walked.map((one) => [one.file, one.uses]));
1943
- for (const [file, writes] of valued) {
1944
- const uses = usesOf.get(file) ?? [];
1945
- for (const { index, names } of aloneIn(file, uses, writes, found)) {
1946
- const reference = found[index];
1947
- if (reference !== undefined) {
1948
- found[index] = { ...reference, alone: names };
1949
- }
1950
- }
1951
- }
1952
-
1953
- let typedLookups = 0;
1954
- if (consumer === undefined) {
1955
- const before = options.requestCount?.() ?? 0;
1956
- found.push(...overrideReferences(project, held, chains, new Set(tests)));
1957
- found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
1958
- found.push(...elementAccessReferences(project, held, chains, root, new Set(tests)));
1959
- found.push(...keyofComparisonReferences(project, held, chains, root, new Set(tests)));
1960
- found.push(...iterationReferences(project, held, chains, root, new Set(tests)));
1961
- found.push(...typeParameterReads(project, held, chains, root, new Set(tests)));
1962
- found.push(...unimplementedCalleeReads(project, held, chains, root, new Set(tests)));
1963
- found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
1964
- typedLookups = (options.requestCount?.() ?? 0) - before;
1965
424
  }
1966
- const scripts = new Set(
1967
- walked.filter((one) => one.file.externalModuleIndicator === undefined).map((one) => one.fileId),
1968
- );
1969
- found.push(...augmentationReferences(held, found, scripts, libraryMerged(scripts)));
1970
- found.sort(
1971
- (a, b) =>
1972
- byPosition(a.position, b.position) ||
1973
- bytewise(a.to, b.to) ||
1974
- bytewise(a.from, b.from) ||
1975
- bytewise(a.use, b.use),
1976
- );
1977
425
 
426
+ const merges = mergedGlobalInterfaces(project, held, pass.ownFiles());
427
+ sink.extend(augmentationReferences(held, sink.made, merges.scripts, merges.interfaces));
428
+ const finished = sink.finish(new Map(sites.map((one) => [one.file, one.uses])));
1978
429
  return {
1979
- references:
1980
- consumer === undefined
1981
- ? found
1982
- : found.map((reference) => ({ ...reference, consumer: consumer.id })),
1983
- testFilePaths: tests.sort(bytewise),
1984
- unseenLoads,
1985
- cost: {
1986
- batched,
1987
- fileBatches,
1988
- residueFallbacks,
1989
- shorthandLookups,
1990
- aliasSteps: chains.steps,
1991
- platformLookups,
1992
- patternBatches: destructured.cost.patternBatches,
1993
- patternLookups: destructured.cost.patternLookups,
1994
- contextualBatches: contextual.batches,
1995
- contextualLookups: contextual.lookups,
1996
- typedLookups,
1997
- },
430
+ ...finished,
431
+ unseenLoads: pass.unseenLoads,
432
+ possiblyLoaded: [...pass.possiblyLoaded].sort(bytewise),
1998
433
  };
1999
434
  }