@cplieger/deadset-ts 8.0.0-dev.16 → 8.0.0-dev.18

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 (161) 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 +82 -91
  4. package/dist/src/augmentations.js +24 -25
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +279 -43
  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/contract.generated.js +2 -2
  14. package/dist/src/contract/report.generated.js +6 -2
  15. package/dist/src/dependencies.js +25 -29
  16. package/dist/src/destructuring.js +1 -19
  17. package/dist/src/element-access.js +179 -164
  18. package/dist/src/entries/build-scripts.js +2 -2
  19. package/dist/src/entries/configuration-files.js +3 -7
  20. package/dist/src/entries/conventions.js +2 -2
  21. package/dist/src/entries/html-entries.js +7 -10
  22. package/dist/src/entries/mdx-entries.js +2 -2
  23. package/dist/src/entries/static-module.js +2 -1
  24. package/dist/src/exempt.js +0 -26
  25. package/dist/src/exposure.js +400 -0
  26. package/dist/src/facts.js +4 -4
  27. package/dist/src/findings/completion.js +44 -21
  28. package/dist/src/findings/emitters.js +22 -33
  29. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  30. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  31. package/dist/src/findings/intra-function.js +3 -3
  32. package/dist/src/findings/reads-and-writes.js +71 -15
  33. package/dist/src/findings/unused-declarations.js +2 -22
  34. package/dist/src/findings/visibility-narrowing.js +14 -20
  35. package/dist/src/framework-lifecycle.js +41 -14
  36. package/dist/src/import-bindings.js +73 -28
  37. package/dist/src/index/sites.js +557 -0
  38. package/dist/src/indexed-access.js +523 -478
  39. package/dist/src/interface-satisfaction.js +1 -1
  40. package/dist/src/inventory.js +18 -0
  41. package/dist/src/keyof-comparisons.js +124 -103
  42. package/dist/src/{components.js → liveness/components.js} +1 -1
  43. package/dist/src/{graph.js → liveness/graph.js} +45 -20
  44. package/dist/src/liveness/matrix.js +99 -0
  45. package/dist/src/liveness/sweep.js +558 -0
  46. package/dist/src/load/expected-module.js +2 -2
  47. package/dist/src/load/specifiers.js +2 -4
  48. package/dist/src/load/synthesize.js +5 -0
  49. package/dist/src/load/workspace-resolution.js +2 -2
  50. package/dist/src/module-declarations.js +70 -85
  51. package/dist/src/overrides.js +134 -112
  52. package/dist/src/paths.js +46 -7
  53. package/dist/src/platform-calls.js +2 -2
  54. package/dist/src/platform-symbols.js +13 -19
  55. package/dist/src/query.js +46 -0
  56. package/dist/src/references/read-rule.js +1 -0
  57. package/dist/src/references/read-rules.js +48 -0
  58. package/dist/src/references/sink.js +237 -0
  59. package/dist/src/references/sources.js +450 -0
  60. package/dist/src/references.js +110 -1285
  61. package/dist/src/roots/configured-declarations.js +3 -0
  62. package/dist/src/roots/published-reach.js +4 -4
  63. package/dist/src/roots/root-set.js +1 -1
  64. package/dist/src/roots/unseen-loads.js +38 -10
  65. package/dist/src/serialization-contract.js +8 -3
  66. package/dist/src/stores.js +4 -2
  67. package/dist/src/symbol-keys.js +89 -83
  68. package/dist/src/tests/file-class.js +76 -0
  69. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  70. package/dist/src/{test-support.js → tests/support.js} +2 -11
  71. package/dist/src/type-parameter-reads.js +178 -139
  72. package/dist/src/unimplemented-callees.js +262 -242
  73. package/dist/src/verbs/analyze.js +3 -3
  74. package/dist/src/verbs/explain.js +75 -132
  75. package/dist/src/verbs/print-retained.js +3 -3
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/alias-chain.ts +0 -7
  79. package/src/amd.ts +130 -0
  80. package/src/analysis.ts +125 -128
  81. package/src/augmentations.ts +42 -28
  82. package/src/bindings.ts +460 -0
  83. package/src/calls.ts +338 -46
  84. package/src/commands/tools.ts +5 -11
  85. package/src/component-files.ts +4 -2
  86. package/src/conformance-record.ts +1 -1
  87. package/src/consumers.ts +8 -11
  88. package/src/contextual-uses.ts +2 -22
  89. package/src/contract/config.generated.ts +4 -1
  90. package/src/contract/contract.generated.ts +2 -2
  91. package/src/contract/report.generated.ts +6 -2
  92. package/src/dependencies.ts +27 -28
  93. package/src/destructuring.ts +1 -31
  94. package/src/element-access.ts +188 -182
  95. package/src/entries/build-scripts.ts +2 -2
  96. package/src/entries/configuration-files.ts +11 -8
  97. package/src/entries/conventions.ts +2 -1
  98. package/src/entries/html-entries.ts +15 -14
  99. package/src/entries/mdx-entries.ts +2 -2
  100. package/src/entries/static-module.ts +2 -1
  101. package/src/exempt.ts +31 -46
  102. package/src/exposure.ts +492 -0
  103. package/src/facts.ts +5 -3
  104. package/src/finding.ts +1 -1
  105. package/src/findings/boundary.ts +9 -0
  106. package/src/findings/completion.ts +48 -23
  107. package/src/findings/emitter.ts +1 -1
  108. package/src/findings/emitters.ts +23 -41
  109. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  110. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  111. package/src/findings/interfaces.ts +3 -3
  112. package/src/findings/intra-function.ts +3 -3
  113. package/src/findings/reads-and-writes.ts +99 -26
  114. package/src/findings/unused-declarations.ts +4 -28
  115. package/src/findings/visibility-narrowing.ts +25 -26
  116. package/src/framework-lifecycle.ts +42 -14
  117. package/src/import-bindings.ts +98 -29
  118. package/src/index/sites.ts +781 -0
  119. package/src/indexed-access.ts +570 -527
  120. package/src/interface-satisfaction.ts +2 -2
  121. package/src/inventory.ts +19 -0
  122. package/src/keyof-comparisons.ts +131 -116
  123. package/src/{components.ts → liveness/components.ts} +3 -3
  124. package/src/{graph.ts → liveness/graph.ts} +93 -27
  125. package/src/liveness/matrix.ts +162 -0
  126. package/src/liveness/sweep.ts +855 -0
  127. package/src/load/expected-module.ts +8 -2
  128. package/src/load/specifiers.ts +8 -5
  129. package/src/load/synthesize.ts +6 -0
  130. package/src/load/workspace-resolution.ts +9 -2
  131. package/src/module-declarations.ts +74 -98
  132. package/src/overrides.ts +162 -134
  133. package/src/paths.ts +54 -8
  134. package/src/platform-calls.ts +3 -3
  135. package/src/platform-symbols.ts +11 -27
  136. package/src/query.ts +81 -0
  137. package/src/references/read-rule.ts +60 -0
  138. package/src/references/read-rules.ts +55 -0
  139. package/src/references/sink.ts +314 -0
  140. package/src/references/sources.ts +632 -0
  141. package/src/references.ts +175 -1733
  142. package/src/report.ts +1 -1
  143. package/src/roots/configured-declarations.ts +3 -0
  144. package/src/roots/published-reach.ts +9 -4
  145. package/src/roots/root-set.ts +1 -1
  146. package/src/roots/unseen-loads.ts +49 -11
  147. package/src/serialization-contract.ts +8 -3
  148. package/src/stores.ts +15 -3
  149. package/src/symbol-keys.ts +102 -102
  150. package/src/tests/file-class.ts +135 -0
  151. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  152. package/src/{test-support.ts → tests/support.ts} +9 -20
  153. package/src/type-parameter-reads.ts +187 -158
  154. package/src/unimplemented-callees.ts +297 -280
  155. package/src/verbs/analyze.ts +3 -2
  156. package/src/verbs/explain.ts +83 -158
  157. package/src/verbs/print-retained.ts +3 -3
  158. package/dist/src/matrix.js +0 -273
  159. package/dist/src/sweep.js +0 -222
  160. package/src/matrix.ts +0 -463
  161. package/src/sweep.ts +0 -361
@@ -1,554 +1,26 @@
1
- import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isInterfaceDeclaration, isMetaProperty, isModuleBlock, isModuleDeclaration, isNewExpression, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isStringLiteralLikeNode, isVoidExpression, SyntaxKind, } from "@typescript/native/ast";
2
- import { parenthesesRemoved } from "./unwrap.js";
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
+ import { SyntaxKind } from "@typescript/native/ast";
3
9
  import { aliasChains } from "./alias-chain.js";
10
+ import { augmentationReferences, mergedGlobalInterfaces } from "./augmentations.js";
4
11
  import { contextualUses } from "./contextual-uses.js";
5
- import { destructuring, propertyReadsOf } from "./destructuring.js";
6
- import { specifierCandidates } from "./entries/configuration-files.js";
7
- import { namespaceUses } from "./namespace-objects.js";
8
- import { isParameterProperty, nodeKey } from "./inventory.js";
9
- import { ambientGlobalInterfaces, augmentationReferences } from "./augmentations.js";
10
- import { elementAccessReferences } from "./element-access.js";
11
- import { indexedAccessReferences } from "./indexed-access.js";
12
- import { keyofComparisonReferences } from "./keyof-comparisons.js";
13
- import { unimplementedCalleeReads } from "./unimplemented-callees.js";
14
- import { moduleDeclarationReferences } from "./module-declarations.js";
15
- import { overrideReferences } from "./overrides.js";
16
- import { dirnamePath, queriedModulePath, relativePath, relativeURLPath, resolvePath, } from "./paths.js";
17
- import { byPosition, isComponentFile, renderPosition } from "./position.js";
12
+ import { destructuring } from "./destructuring.js";
13
+ import { bindingsOf } from "./import-bindings.js";
14
+ import { sitesOf } from "./index/sites.js";
15
+ import { nodeKey } from "./inventory.js";
16
+ import { relativePath } from "./paths.js";
18
17
  import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED } from "./query.js";
18
+ import { heldBy, READ_RULES } from "./references/read-rules.js";
19
+ import { Sink } from "./references/sink.js";
20
+ import { emitFile, prepared } from "./references/sources.js";
19
21
  import { sourceFilesOf } from "./source-files.js";
20
- import { typeParameterReads } from "./type-parameter-reads.js";
21
- import { typeQueryAliases } from "./type-query-alias.js";
22
- import { callsPlatform, isPlatformGlobal, startsThread } from "./platform-symbols.js";
23
- import { writtenName } from "./platform-calls.js";
24
- import { computedLoadsOf, isLoadCall } from "./roots/unseen-loads.js";
25
- import { computedKeyNames, iterationReferences, keyNameOf, keyedUses, } from "./symbol-keys.js";
26
- import { handedOn, valuesOf } from "./value-flow.js";
27
22
  import { bytewise } from "./text/compare.js";
28
- import { globExpression } from "./text/glob.js";
29
- /**
30
- * The operators whose left side one assignment stores into. A compound assignment is
31
- * among them: where its value is discarded it reads its target only to write the result
32
- * back, so nothing else receives what it read.
33
- */
34
- const ASSIGNMENTS = new Set([
35
- SyntaxKind.EqualsToken,
36
- SyntaxKind.PlusEqualsToken,
37
- SyntaxKind.MinusEqualsToken,
38
- SyntaxKind.AsteriskEqualsToken,
39
- SyntaxKind.AsteriskAsteriskEqualsToken,
40
- SyntaxKind.SlashEqualsToken,
41
- SyntaxKind.PercentEqualsToken,
42
- SyntaxKind.LessThanLessThanEqualsToken,
43
- SyntaxKind.GreaterThanGreaterThanEqualsToken,
44
- SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken,
45
- SyntaxKind.AmpersandEqualsToken,
46
- SyntaxKind.BarEqualsToken,
47
- SyntaxKind.BarBarEqualsToken,
48
- SyntaxKind.AmpersandAmpersandEqualsToken,
49
- SyntaxKind.QuestionQuestionEqualsToken,
50
- SyntaxKind.CaretEqualsToken,
51
- ]);
52
- /**
53
- * The assignments that read their target to decide whether to store at all, so the read
54
- * carries information out whatever becomes of the result.
55
- */
56
- const LOGICAL_ASSIGNMENTS = new Set([
57
- SyntaxKind.BarBarEqualsToken,
58
- SyntaxKind.AmpersandAmpersandEqualsToken,
59
- SyntaxKind.QuestionQuestionEqualsToken,
60
- ]);
61
- /**
62
- * Whether the value of one store expression is discarded: it is a statement of its own, a
63
- * clause of a `for` loop, the operand of `void`, or the left operand of a comma.
64
- */
65
- function valueDiscarded(node) {
66
- let child = node;
67
- let parent = node.parent;
68
- while (isParenthesizedExpression(parent)) {
69
- child = parent;
70
- parent = parent.parent;
71
- }
72
- if (isExpressionStatement(parent) || isVoidExpression(parent)) {
73
- return true;
74
- }
75
- if (isForStatement(parent)) {
76
- return parent.initializer === child || parent.incrementor === child;
77
- }
78
- return (isBinaryExpression(parent) &&
79
- parent.operatorToken.kind === SyntaxKind.CommaToken &&
80
- parent.left === child);
81
- }
82
- /** Whether one node is an identifier or a private name, the two forms a name takes. */
83
- function isName(node) {
84
- return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PrivateIdentifier;
85
- }
86
- /**
87
- * Records the name one store writes into.
88
- *
89
- * A property access writes the member it names. An element access writes the collection
90
- * it stores into, since a collection a module only stores into holds nothing anything
91
- * reads, and joins `accesses` for the member a unique-symbol key selects. A destructuring
92
- * target writes each name it binds, through its spreads and nested patterns.
93
- */
94
- function markWrite(target, writes, accesses) {
95
- const held = parenthesesRemoved(target);
96
- if (isName(held)) {
97
- writes.add(held.pos);
98
- return;
99
- }
100
- if (isPropertyAccessExpression(held)) {
101
- if (isName(held.name)) {
102
- writes.add(held.name.pos);
103
- }
104
- return;
105
- }
106
- if (isElementAccessExpression(held)) {
107
- accesses.add(held);
108
- // A store through a member's value lands in storage every holder of the value
109
- // reads, so it reads the member; a variable only ever stored into stays written.
110
- if (!isPropertyAccessExpression(parenthesesRemoved(held.expression))) {
111
- markWrite(held.expression, writes, accesses);
112
- }
113
- return;
114
- }
115
- if (isArrayLiteralExpression(held)) {
116
- for (const element of held.elements) {
117
- markWrite(element, writes, accesses);
118
- }
119
- return;
120
- }
121
- if (isObjectLiteralExpression(held)) {
122
- for (const property of held.properties) {
123
- if (isPropertyAssignment(property)) {
124
- markWrite(property.initializer, writes, accesses);
125
- continue;
126
- }
127
- if (isShorthandPropertyAssignment(property)) {
128
- markWrite(property.name, writes, accesses);
129
- continue;
130
- }
131
- if (isSpreadAssignment(property)) {
132
- markWrite(property.expression, writes, accesses);
133
- }
134
- }
135
- return;
136
- }
137
- if (isSpreadElement(held)) {
138
- markWrite(held.expression, writes, accesses);
139
- }
140
- }
141
- /**
142
- * Records every store one node performs: an assignment, a compound assignment, an
143
- * increment or a decrement, a `delete`, and an iteration that assigns to a name declared
144
- * elsewhere. An iteration that declares its own name stores into nothing the inventory
145
- * holds. A store that reads its target as well records it in `reads`: a logical
146
- * assignment, and a compound assignment, an increment or a decrement whose value is used.
147
- */
148
- function markStores(node, writes, reads, accesses) {
149
- if (isBinaryExpression(node)) {
150
- const operator = node.operatorToken.kind;
151
- if (ASSIGNMENTS.has(operator)) {
152
- markWrite(node.left, writes, accesses.written);
153
- if (LOGICAL_ASSIGNMENTS.has(operator) ||
154
- (operator !== SyntaxKind.EqualsToken && !valueDiscarded(node))) {
155
- markWrite(node.left, reads, accesses.read);
156
- }
157
- }
158
- return;
159
- }
160
- if (isPrefixUnaryExpression(node) || isPostfixUnaryExpression(node)) {
161
- if (node.operator === SyntaxKind.PlusPlusToken ||
162
- node.operator === SyntaxKind.MinusMinusToken) {
163
- markWrite(node.operand, writes, accesses.written);
164
- if (!valueDiscarded(node)) {
165
- markWrite(node.operand, reads, accesses.read);
166
- }
167
- }
168
- return;
169
- }
170
- if (isDeleteExpression(node)) {
171
- markWrite(node.expression, writes, accesses.written);
172
- return;
173
- }
174
- if (isForInStatement(node) || isForOfStatement(node)) {
175
- if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) {
176
- markWrite(node.initializer, writes, accesses.written);
177
- }
178
- }
179
- }
180
- /** Whether one node is `import.meta.url`. */
181
- function isImportMetaURL(node) {
182
- const at = parenthesesRemoved(node);
183
- return (isPropertyAccessExpression(at) &&
184
- isMetaProperty(at.expression) &&
185
- at.expression.keywordToken === SyntaxKind.ImportKeyword &&
186
- at.name.text === "url");
187
- }
188
- /** The module URL one node builds, where it builds one. */
189
- function moduleURLOf(node) {
190
- const literal = (argument) => {
191
- const at = argument === undefined ? undefined : parenthesesRemoved(argument);
192
- return isLiteralText(at) ? at.text : undefined;
193
- };
194
- const constructor = isNewExpression(node) ? writtenName(node.expression) : undefined;
195
- if (isNewExpression(node) && constructor !== undefined) {
196
- const [reference, base] = node.arguments ?? [];
197
- const specifier = literal(reference);
198
- return specifier !== undefined && base !== undefined && isImportMetaURL(base)
199
- ? { specifier, constructor, at: node }
200
- : undefined;
201
- }
202
- if (isCallExpression(node)) {
203
- const callee = parenthesesRemoved(node.expression);
204
- const specifier = literal(node.arguments[0]);
205
- if (specifier !== undefined &&
206
- /^\.\.?\//u.test(specifier) &&
207
- isPropertyAccessExpression(callee) &&
208
- isMetaProperty(callee.expression) &&
209
- callee.expression.keywordToken === SyntaxKind.ImportKeyword &&
210
- callee.name.text === "resolve") {
211
- return { specifier, constructor: undefined, at: node };
212
- }
213
- }
214
- return undefined;
215
- }
216
- /** The constructors whose first argument addresses a worker. */
217
- const WORKER_CONSTRUCTORS = new Set(["Worker", "SharedWorker"]);
218
- /** Whether an options argument is an object literal whose `eval` member is written as anything but `false`. */
219
- function evaluatesCode(options) {
220
- const at = options === undefined ? undefined : parenthesesRemoved(options);
221
- return (at !== undefined &&
222
- isObjectLiteralExpression(at) &&
223
- at.properties.some((property) => (isShorthandPropertyAssignment(property) &&
224
- isIdentifier(property.name) &&
225
- property.name.text === "eval") ||
226
- (isPropertyAssignment(property) &&
227
- (isIdentifier(property.name) || isStringLiteral(property.name)) &&
228
- property.name.text === "eval" &&
229
- parenthesesRemoved(property.initializer).kind !== SyntaxKind.FalseKeyword)));
230
- }
231
- /** The worker one node starts by a literal address, where it starts one. */
232
- function workerOf(node) {
233
- if (isNewExpression(node)) {
234
- const constructor = writtenName(node.expression);
235
- const [address, options] = node.arguments ?? [];
236
- const literal = address === undefined ? undefined : parenthesesRemoved(address);
237
- // A thread's constructor may be imported under another name, whose path is relative.
238
- if (constructor === undefined ||
239
- !isLiteralText(literal) ||
240
- (!WORKER_CONSTRUCTORS.has(constructor.text) && !/^\.\.?\//u.test(literal.text))) {
241
- return undefined;
242
- }
243
- const callee = parenthesesRemoved(node.expression);
244
- const holder = isPropertyAccessExpression(callee)
245
- ? parenthesesRemoved(callee.expression)
246
- : undefined;
247
- return {
248
- address: literal,
249
- constructor,
250
- holder: holder !== undefined && isIdentifier(holder) ? holder : undefined,
251
- evaluated: evaluatesCode(options),
252
- at: node,
253
- };
254
- }
255
- if (!isCallExpression(node)) {
256
- return undefined;
257
- }
258
- const callee = parenthesesRemoved(node.expression);
259
- const registry = isPropertyAccessExpression(callee) && callee.name.text === "register"
260
- ? parenthesesRemoved(callee.expression)
261
- : undefined;
262
- const navigator = registry !== undefined &&
263
- isPropertyAccessExpression(registry) &&
264
- registry.name.text === "serviceWorker"
265
- ? writtenName(registry.expression)
266
- : undefined;
267
- const [address] = node.arguments;
268
- const literal = address === undefined ? undefined : parenthesesRemoved(address);
269
- return navigator?.text === "navigator" && isLiteralText(literal)
270
- ? { address: literal, constructor: navigator, holder: undefined, evaluated: false, at: node }
271
- : undefined;
272
- }
273
- /** The file of the target a relative specifier written in `file` names, by its source or output name. */
274
- function fileNamed(file, specifier, fileIdByName) {
275
- return specifierCandidates(dirnamePath(file.fileName), specifier)
276
- .map((path) => fileIdByName.get(path))
277
- .find((id) => id !== undefined);
278
- }
279
- /** Whether one node is a string literal or a template literal with no substitution. */
280
- function isLiteralText(node) {
281
- return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
282
- }
283
- /**
284
- * The module specifier of the module one node evaluates, if it evaluates one: an import
285
- * declaration, an import-equals declaration of an external module, an export
286
- * declaration that names a module, an `import()` call whose argument is literal text, and
287
- * a call that may be a `require()` of literal text: one written as the name `require`, or
288
- * one of a relative specifier through another name. A type-only form evaluates nothing.
289
- * An `import()` of a computed argument names no module this pass can know.
290
- */
291
- function evaluationOf(node) {
292
- if (isCallExpression(node) && node.expression.kind !== SyntaxKind.ImportKeyword) {
293
- const [argument] = node.arguments;
294
- const require = writtenName(node.expression);
295
- if (require === undefined ||
296
- !isLiteralText(argument) ||
297
- (require.text !== "require" && !/^\.\.?\//u.test(argument.text))) {
298
- return undefined;
299
- }
300
- return { node: argument, require, whole: !discarded(node) };
301
- }
302
- const specifier = evaluatedBy(node);
303
- return specifier === undefined ? undefined : { node: specifier };
304
- }
305
- /** Whether an expression's value is thrown away: it is a statement of its own, or `void`'s operand. */
306
- function discarded(expression) {
307
- let at = expression.parent;
308
- while (isParenthesizedExpression(at)) {
309
- at = at.parent;
310
- }
311
- return isExpressionStatement(at) || isVoidExpression(at);
312
- }
313
- function evaluatedBy(node) {
314
- if (isImportDeclaration(node)) {
315
- return node.importClause?.phaseModifier === SyntaxKind.TypeKeyword
316
- ? undefined
317
- : node.moduleSpecifier;
318
- }
319
- if (isExportDeclaration(node)) {
320
- return node.isTypeOnly ? undefined : node.moduleSpecifier;
321
- }
322
- if (isImportEqualsDeclaration(node)) {
323
- const reference = node.moduleReference;
324
- return !node.isTypeOnly && isExternalModuleReference(reference)
325
- ? reference.expression
326
- : undefined;
327
- }
328
- if (isCallExpression(node) && node.expression.kind === SyntaxKind.ImportKeyword) {
329
- const [argument] = node.arguments;
330
- return isLiteralText(argument) ? argument : undefined;
331
- }
332
- return undefined;
333
- }
334
- /**
335
- * The own file a module specifier with a query evaluates ({@link queriedModulePath}): the
336
- * checker resolves the whole specifier to whatever module declaration matches it.
337
- */
338
- function queriedFile(file, specifier, fileIdByName) {
339
- if (!isStringLiteral(specifier) && !isNoSubstitutionTemplateLiteral(specifier)) {
340
- return undefined;
341
- }
342
- const path = queriedModulePath(specifier.text);
343
- return path === undefined
344
- ? undefined
345
- : fileIdByName.get(resolvePath(dirnamePath(file.fileName), path));
346
- }
347
- /**
348
- * The re-exports one node writes, if it is an export declaration. A specifier and a
349
- * namespace export are declarations whose names are where they are written, so no use is
350
- * read from them; each references what it carries forward instead. A bare star declares
351
- * nothing, so the declaration enclosing it, at the top level the file, references the
352
- * module it names.
353
- */
354
- function linksOf(file, node, declarations, enclosing) {
355
- if (!isExportDeclaration(node)) {
356
- return [];
357
- }
358
- const clause = node.exportClause;
359
- const specifier = node.moduleSpecifier;
360
- if (clause === undefined) {
361
- return specifier === undefined
362
- ? []
363
- : [{ node: specifier, at: specifier, from: enclosing, alias: false }];
364
- }
365
- if (isNamespaceExport(clause)) {
366
- const from = declarations.get(nodeKey(file, clause));
367
- return from === undefined
368
- ? []
369
- : [{ node: clause.name, at: specifier ?? clause.name, from, alias: true }];
370
- }
371
- const found = [];
372
- for (const element of clause.elements) {
373
- const from = declarations.get(nodeKey(file, element));
374
- if (from !== undefined) {
375
- found.push({
376
- node: element.name,
377
- at: element.propertyName ?? element.name,
378
- from,
379
- alias: true,
380
- });
381
- }
382
- }
383
- return found;
384
- }
385
- /**
386
- * Whether one object literal is a destructuring target, written on the left of an
387
- * assignment or as a `for` loop's initializer, through the literals and spreads it nests in.
388
- */
389
- function isAssignmentPattern(node) {
390
- let child = node;
391
- let parent = node.parent;
392
- while (isParenthesizedExpression(parent) ||
393
- isArrayLiteralExpression(parent) ||
394
- isObjectLiteralExpression(parent) ||
395
- isSpreadElement(parent) ||
396
- isSpreadAssignment(parent) ||
397
- (isPropertyAssignment(parent) && parent.initializer === child)) {
398
- child = parent;
399
- parent = parent.parent;
400
- }
401
- if (isBinaryExpression(parent)) {
402
- return parent.operatorToken.kind === SyntaxKind.EqualsToken && parent.left === child;
403
- }
404
- return (isForInStatement(parent) || isForOfStatement(parent)) && parent.initializer === child;
405
- }
406
- /**
407
- * Every name node one file holds that is a use rather than a declaration, in source
408
- * order, each with its enclosing declaration and its use, and every re-export the file
409
- * writes. One walk answers all of it, because a second would have to agree with this one
410
- * about which nodes are names. A declaration's own declared name is left out, which is
411
- * what makes a declaration referenced only from its own site an unreferenced one, and so
412
- * is every node of `unread`, a subtree that is no use.
413
- */
414
- function sitesOf(file, declarations, fileId, unread) {
415
- const found = [];
416
- const reads = [];
417
- const links = [];
418
- const evaluations = [];
419
- const wholes = [];
420
- const urls = [];
421
- const workers = [];
422
- const loadCalls = [];
423
- const decorated = [];
424
- const parameterWrites = [];
425
- const namespaces = namespaceUses(file);
426
- const literals = [];
427
- const flows = [];
428
- const writes = new Set();
429
- const alsoRead = new Set();
430
- const accesses = { written: new Set(), read: new Set() };
431
- const keyed = [];
432
- const declared = new Set();
433
- const shorthands = new Map();
434
- let enclosing = fileId;
435
- const markNames = (node) => {
436
- if (isShorthandPropertyAssignment(node)) {
437
- shorthands.set(node.name.pos, node);
438
- return;
439
- }
440
- // A property access carries the member it names where a declaration carries its
441
- // declared name, and it is the one form of the grammar that does.
442
- if (isPropertyAccessExpression(node)) {
443
- return;
444
- }
445
- const named = node;
446
- for (const name of [named.name, named.propertyName]) {
447
- if (name !== undefined && isName(name)) {
448
- declared.add(name.pos);
449
- }
450
- }
451
- };
452
- const visit = (node) => {
453
- if (unread.has(node)) {
454
- return;
455
- }
456
- const namespace = namespaces.get(node);
457
- if (namespace?.kind === "whole") {
458
- wholes.push({ specifier: namespace.specifier, at: node, from: enclosing });
459
- }
460
- else if (namespace?.kind === "indexed") {
461
- found.push({ node: namespace.literal, shorthand: undefined, from: enclosing, use: "read" });
462
- }
463
- if (isName(node)) {
464
- if (!declared.has(node.pos)) {
465
- const site = { node, shorthand: shorthands.get(node.pos), from: enclosing };
466
- if (writes.has(node.pos)) {
467
- found.push({ ...site, use: "write" });
468
- }
469
- if (!writes.has(node.pos) || alsoRead.has(node.pos)) {
470
- found.push({ ...site, use: "read" });
471
- }
472
- }
473
- return;
474
- }
475
- markNames(node);
476
- markStores(node, writes, alsoRead, accesses);
477
- const key = isElementAccessExpression(node) ? keyNameOf(node.argumentExpression) : undefined;
478
- if (key !== undefined && isElementAccessExpression(node)) {
479
- const written = accesses.written.has(node);
480
- keyed.push({
481
- access: node,
482
- key,
483
- from: enclosing,
484
- use: written ? (accesses.read.has(node) ? "both" : "write") : "read",
485
- });
486
- }
487
- if (isObjectLiteralExpression(node) && !isAssignmentPattern(node)) {
488
- literals.push({ literal: node, from: enclosing });
489
- }
490
- // An assertion needs only comparability, so no member of its operand is read by it.
491
- for (const written of isAsExpression(node) ? [] : handedOn(node)) {
492
- flows.push(...valuesOf(written).map((value) => ({ value, from: enclosing })));
493
- }
494
- for (const read of propertyReadsOf(node)) {
495
- reads.push({ ...read, from: enclosing });
496
- }
497
- links.push(...linksOf(file, node, declarations, enclosing));
498
- const evaluated = evaluationOf(node);
499
- if (evaluated !== undefined) {
500
- evaluations.push({ ...evaluated, from: enclosing });
501
- }
502
- const url = moduleURLOf(node);
503
- if (url !== undefined) {
504
- urls.push({ ...url, from: enclosing });
505
- }
506
- const worker = workerOf(node);
507
- if (worker !== undefined) {
508
- workers.push({ ...worker, from: enclosing });
509
- }
510
- if (isLoadCall(node)) {
511
- loadCalls.push({ call: node, from: enclosing });
512
- }
513
- const held = declarations.get(nodeKey(file, node));
514
- const outer = enclosing;
515
- // A parameter property's annotation, initializer and decorators stay with the
516
- // parameter when the member goes, so they are written inside the constructor's class.
517
- if (held !== undefined && isParameterProperty(node)) {
518
- parameterWrites.push({
519
- name: node.name,
520
- from: outer,
521
- member: held,
522
- });
523
- }
524
- else if (held !== undefined) {
525
- enclosing = held;
526
- for (const modifier of node.modifiers ?? []) {
527
- if (isDecorator(modifier)) {
528
- decorated.push({ decorator: modifier, declaration: held });
529
- }
530
- }
531
- }
532
- node.forEachChild(visit);
533
- enclosing = outer;
534
- };
535
- file.forEachChild(visit);
536
- return {
537
- uses: found,
538
- literals,
539
- flows,
540
- reads,
541
- links,
542
- evaluations,
543
- wholes,
544
- urls,
545
- workers,
546
- loadCalls,
547
- decorated,
548
- parameterWrites,
549
- keyed,
550
- };
551
- }
23
+ import { typeQueryAliases } from "./type-query-alias.js";
552
24
  /** The declaration kinds of a property an object literal declares. */
553
25
  const LITERAL_PROPERTY_KINDS = new Set([
554
26
  SyntaxKind.PropertyAssignment,
@@ -557,162 +29,10 @@ const LITERAL_PROPERTY_KINDS = new Set([
557
29
  SyntaxKind.GetAccessor,
558
30
  SyntaxKind.SetAccessor,
559
31
  ]);
560
- /** The rule a file the analysis classified as test-support code is counted under. */
561
- const TEST_SUPPORT_RULE = "test-support";
562
- /**
563
- * The name one test-file rule takes. A rule's name is a word list the report's own
564
- * vocabulary spells, which cannot carry a pattern, so a rule is named by the place of
565
- * its pattern in the configured list.
566
- */
567
- function patternRule(index) {
568
- return `test-file-pattern-${String(index + 1)}`;
569
- }
570
32
  /**
571
- * The rules over the test files of several projects at once, ordered by rule: a file two
572
- * projects hold is one file, so each rule counts the distinct paths it matches.
573
- */
574
- export function testFileRulesOf(testFiles, paths, supportFiles = []) {
575
- const distinct = [...new Set(paths)];
576
- const support = new Set(supportFiles).size;
577
- return [
578
- ...testFiles.map((pattern, index) => {
579
- const expression = globExpression(pattern);
580
- return {
581
- rule: patternRule(index),
582
- matched: distinct.filter((path) => expression.test(path)).length,
583
- };
584
- }),
585
- ...(support === 0 ? [] : [{ rule: TEST_SUPPORT_RULE, matched: support }]),
586
- ].sort((a, b) => bytewise(a.rule, b.rule));
587
- }
588
- const importedCache = new WeakMap();
589
- /**
590
- * Each local name one file's imports bind, mapped to the names it imports: the
591
- * exported name of a named import, `default` for a default import, and `undefined` for
592
- * a namespace import or an import assignment, which binds a whole module.
593
- */
594
- function importedNames(file) {
595
- const known = importedCache.get(file);
596
- if (known !== undefined) {
597
- return known;
598
- }
599
- const names = new Map();
600
- const bind = (local, imported) => {
601
- names.set(local, [...(names.get(local) ?? []), imported]);
602
- };
603
- for (const statement of file.statements) {
604
- if (isImportEqualsDeclaration(statement)) {
605
- bind(statement.name.text, undefined);
606
- continue;
607
- }
608
- const clause = isImportDeclaration(statement) ? statement.importClause : undefined;
609
- if (clause === undefined) {
610
- continue;
611
- }
612
- if (clause.name !== undefined) {
613
- bind(clause.name.text, "default");
614
- }
615
- const bindings = clause.namedBindings;
616
- if (bindings === undefined) {
617
- continue;
618
- }
619
- if (bindings.kind === SyntaxKind.NamespaceImport) {
620
- bind(bindings.name.text, undefined);
621
- }
622
- else {
623
- for (const element of bindings.elements) {
624
- bind(element.name.text, element.propertyName?.text ?? element.name.text);
625
- }
626
- }
627
- }
628
- importedCache.set(file, names);
629
- return names;
630
- }
631
- /** The name node of every import binding one file's top level declares. */
632
- function importBindings(file) {
633
- const found = [];
634
- for (const statement of file.statements) {
635
- if (isImportEqualsDeclaration(statement)) {
636
- found.push({ node: statement.name });
637
- continue;
638
- }
639
- const clause = isImportDeclaration(statement) ? statement.importClause : undefined;
640
- if (clause === undefined) {
641
- continue;
642
- }
643
- if (clause.name !== undefined) {
644
- found.push({ node: clause.name });
645
- }
646
- const bindings = clause.namedBindings;
647
- if (bindings === undefined) {
648
- continue;
649
- }
650
- if (bindings.kind === SyntaxKind.NamespaceImport) {
651
- found.push({ node: bindings.name });
652
- }
653
- else {
654
- found.push(...bindings.elements.map((element) => ({ node: element.name })));
655
- }
656
- }
657
- return found;
658
- }
659
- /** The value one assignment stores through the name it writes, where it is a plain assignment. */
660
- function storedValue(name) {
661
- let target = name;
662
- if (isPropertyAccessExpression(name.parent) && name.parent.name === name) {
663
- target = name.parent;
664
- }
665
- while (isParenthesizedExpression(target.parent)) {
666
- target = target.parent;
667
- }
668
- const parent = target.parent;
669
- return isBinaryExpression(parent) &&
670
- parent.operatorToken.kind === SyntaxKind.EqualsToken &&
671
- parent.left === target
672
- ? parent.right
673
- : undefined;
674
- }
675
- /**
676
- * The import bindings of one file that only the values written into one declaration
677
- * use, for each write into it. A binding is named by an identifier that spells its
678
- * local name and is not the member a property access names.
679
- */
680
- function aloneIn(file, uses, writes, found) {
681
- const bindings = new Set(importBindings(file).map((binding) => binding.node.getText()));
682
- if (bindings.size === 0) {
683
- return [];
684
- }
685
- const occurrences = new Map();
686
- for (const site of uses) {
687
- const text = site.node.getText();
688
- const parent = site.node.parent;
689
- if (!bindings.has(text) || (isPropertyAccessExpression(parent) && parent.name === site.node)) {
690
- continue;
691
- }
692
- if (!occurrences.get(text)?.includes(site.node)) {
693
- occurrences.set(text, [...(occurrences.get(text) ?? []), site.node]);
694
- }
695
- }
696
- const inside = (node, value) => node.pos >= value.pos && node.end <= value.end;
697
- const result = [];
698
- for (const write of writes) {
699
- const to = found[write.index]?.to;
700
- const sameTarget = writes.filter((one) => found[one.index]?.to === to);
701
- const names = [...occurrences]
702
- .filter(([, nodes]) => nodes.some((node) => inside(node, write.value)) &&
703
- nodes.every((node) => sameTarget.some((one) => inside(node, one.value))))
704
- .map(([name]) => name)
705
- .sort(bytewise);
706
- if (names.length > 0) {
707
- result.push({ index: write.index, names });
708
- }
709
- }
710
- return result;
711
- }
712
- /**
713
- * Every reference one project's own files make to its own declarations, and the files the
714
- * test-file rules classified. `held` is the same project's inventory, where a resolved symbol
715
- * is looked up; positions render against `targetRoot`, or a consumer's root as
33
+ * Every reference one side's files make to the target's declarations. `held` is the
34
+ * target's inventory over the files the project holds, where a resolved symbol is looked up;
35
+ * positions render against `targetRoot`, or a consumer's root as
716
36
  * {@link ReferenceOptions.consumer} states. The order is by position, then the declaration
717
37
  * named, the referencing declaration and the use: an alias chain or a bare star re-export
718
38
  * names several at one position, and one name references one declaration once.
@@ -720,22 +40,9 @@ function aloneIn(file, uses, writes, found) {
720
40
  export function references(project, held, targetRoot, options) {
721
41
  const cap = options.batchCap ?? DEFAULT_BATCH_CAP;
722
42
  const declarations = held.declarations;
723
- const patterns = options.testFiles.map(globExpression);
724
- const found = [];
725
- const tests = [];
726
43
  const chains = aliasChains(project, held);
727
44
  const tables = propertyTables(project.queries);
728
- const destructured = destructuring(project, cap, tables);
729
- let batched = 0;
730
- let fileBatches = 0;
731
- let residueFallbacks = 0;
732
- let platformLookups = 0;
733
- const asked = () => {
734
- platformLookups += 1;
735
- };
736
- let shorthandLookups = 0;
737
45
  const consumer = options.consumer;
738
- const root = consumer?.root ?? targetRoot;
739
46
  const files = consumer === undefined
740
47
  ? project.ownSourceFiles()
741
48
  : project
@@ -744,6 +51,8 @@ export function references(project, held, targetRoot, options) {
744
51
  relativePath(targetRoot, file.fileName) === undefined);
745
52
  const ownPaths = project.ownPaths();
746
53
  const fileIds = new Set(held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id));
54
+ const declaredAt = (node) => declarations.get(nodeKey(node.getSourceFile(), node));
55
+ const sink = new Sink(consumer?.root ?? targetRoot, options.testFile, consumer, declaredAt);
747
56
  let moduleBodies;
748
57
  /**
749
58
  * The declarations one module holds directly: a file's top level, or the body of a
@@ -753,23 +62,17 @@ export function references(project, held, targetRoot, options) {
753
62
  if (moduleBodies === undefined) {
754
63
  const byModule = new Map();
755
64
  for (const symbol of held.symbols) {
756
- if (!fileIds.has(symbol.parent) && !held.modules.has(symbol.parent)) {
757
- continue;
758
- }
759
- const siblings = byModule.get(symbol.parent);
760
- if (siblings === undefined) {
761
- byModule.set(symbol.parent, [symbol]);
762
- }
763
- else {
764
- siblings.push(symbol);
65
+ if (fileIds.has(symbol.parent) || held.modules.has(symbol.parent)) {
66
+ byModule.set(symbol.parent, [...(byModule.get(symbol.parent) ?? []), symbol]);
765
67
  }
766
68
  }
767
69
  moduleBodies = byModule;
768
70
  }
769
71
  return moduleBodies.get(moduleId) ?? [];
770
72
  };
771
- let ownFiles;
772
- const unseenLoads = [];
73
+ const exportedBy = (ids) => [...ids].flatMap((id) => declaredIn(id)
74
+ .filter((symbol) => symbol.exported)
75
+ .map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })));
773
76
  const exportsCache = new Map();
774
77
  /**
775
78
  * Every declaration one module exports, each re-export walked along its chain. A module
@@ -777,9 +80,7 @@ export function references(project, held, targetRoot, options) {
777
80
  */
778
81
  const exportsRead = (module) => {
779
82
  if (module === UNANSWERED) {
780
- return [...fileIds].flatMap((id) => declaredIn(id)
781
- .filter((symbol) => symbol.exported)
782
- .map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })));
83
+ return exportedBy(fileIds);
783
84
  }
784
85
  const known = exportsCache.get(module.id);
785
86
  if (known !== undefined) {
@@ -787,41 +88,26 @@ export function references(project, held, targetRoot, options) {
787
88
  }
788
89
  const table = project.queries.exportsOfModule(module);
789
90
  const read = table === UNANSWERED
790
- ? chains.declarationsOf(module).flatMap((id) => declaredIn(id)
791
- .filter((symbol) => symbol.exported)
792
- .map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })))
91
+ ? exportedBy(chains.declarationsOf(module))
793
92
  : table.flatMap((exported) => chains.chainOf(exported));
794
93
  exportsCache.set(module.id, read);
795
94
  return read;
796
95
  };
797
- /** The identifier of the declaration one node of this program is, where it is one. */
798
- const declaredAt = (node) => declarations.get(nodeKey(node.getSourceFile(), node));
799
- /**
800
- * The declarations one re-export carries forward: for an alias, the first link along
801
- * its chain that the inventory declares, stepping over the links it does not, such as
802
- * an import the re-export is written against; for a bare star, the module it names.
803
- * Each later link is a declaration of its own that references the next, so one link
804
- * per re-export is what lets a chain be followed one declaration at a time.
805
- */
806
- const carriedBy = (link, symbol) => link.alias
807
- ? chains.nextDeclared(symbol)
808
- : chains.declarationsOf(symbol).map((id) => ({ id, stepped: false, guessed: false }));
809
96
  /**
810
- * Every declaration bearing the name one node spells, or the name an import of its
811
- * file binds that name to, which is what a reference the checker could not resolve is
812
- * counted as: it may be a use of any of them, and a declaration it may use is kept
813
- * live. A name a namespace import binds may be a use of any file.
97
+ * Every declaration bearing the name one node spells, or the name an import of its file
98
+ * binds that name to, which is what a reference the checker could not resolve is counted
99
+ * as (program.33): it may be a use of any of them. A name a namespace import binds may be
100
+ * a use of any file.
814
101
  */
815
102
  const named = (node) => {
816
- const byName = held.byName;
817
103
  const text = node.getText();
818
- const imported = importedNames(node.getSourceFile()).get(text) ?? [];
104
+ const imported = bindingsOf(node.getSourceFile()).filter((binding) => binding.name.text === text);
819
105
  return [
820
106
  ...new Set([
821
- ...(byName.get(text) ?? []),
822
- ...imported.flatMap((name) => name === undefined ? [...fileIds] : (byName.get(name) ?? [])),
107
+ ...(held.byName.get(text) ?? []),
108
+ ...imported.flatMap((binding) => binding.imported === undefined ? [...fileIds] : (held.byName.get(binding.imported) ?? [])),
823
109
  ]),
824
- ];
110
+ ].map((id) => ({ id, stepped: false, guessed: true }));
825
111
  };
826
112
  // A type-query alias's own type names its export without using it; a use of the
827
113
  // global is the use, so it names every link of the export's chain as well.
@@ -862,29 +148,25 @@ export function references(project, held, targetRoot, options) {
862
148
  }
863
149
  return all;
864
150
  };
865
- /** The symbol one site resolves to, asking only the accessor the site's form names. */
866
- const symbolFor = (site, answered) => {
867
- if (site.shorthand !== undefined) {
868
- shorthandLookups += 1;
869
- return project.shorthandValueAt(project.handle(site.shorthand));
870
- }
871
- const batched = answered.get(site.node);
872
- if (batched === UNANSWERED || typeof batched === "object") {
873
- return batched;
874
- }
875
- residueFallbacks += 1;
876
- return project.resolvedSymbolAt(project.handle(site.node));
877
- };
878
- const walked = files.map((file) => {
151
+ const rules = READ_RULES.map((rule) => rule.start());
152
+ const sites = files.map((file) => {
879
153
  const fileId = declaredAt(file) ?? "";
880
- return { file, fileId, ...sitesOf(file, declarations, fileId, unread) };
154
+ return {
155
+ file,
156
+ fileId,
157
+ ...sitesOf(file, declarations, fileId, unread, (node, from) => {
158
+ for (const rule of rules) {
159
+ rule.visit(node, { file, from });
160
+ }
161
+ }),
162
+ };
881
163
  });
882
- const fileIdByName = new Map(walked.filter((one) => fileIds.has(one.fileId)).map((one) => [one.file.fileName, one.fileId]));
883
- const loadable = walked
164
+ const loadable = sites
884
165
  .filter((one) => fileIds.has(one.fileId))
885
166
  .map(({ file, fileId }) => ({ file, fileId }));
886
- const loadableById = new Map(loadable.map((one) => [one.fileId, one.file]));
887
- const contextual = contextualUses(project, held, chains, walked.flatMap((one) => one.literals), walked.flatMap((one) => one.flows), cap, tables);
167
+ const directories = options.directories;
168
+ const destructured = destructuring(project, cap, tables);
169
+ const contextual = contextualUses(project, held, chains, sites.flatMap((one) => one.literals), sites.flatMap((one) => one.flows), cap, tables);
888
170
  const writtenBy = new Map();
889
171
  for (const write of contextual.writes) {
890
172
  const property = write.at.parent;
@@ -901,534 +183,77 @@ export function references(project, held, targetRoot, options) {
901
183
  return known;
902
184
  }
903
185
  const found = [];
904
- if (writtenBy.size > 0) {
905
- for (const handle of symbol.declarations) {
906
- if (!LITERAL_PROPERTY_KINDS.has(handle.kind) || !ownPaths.has(handle.path)) {
907
- continue;
908
- }
909
- const node = project.declarationAt(handle)?.node;
910
- const ids = node === undefined ? undefined : writtenBy.get(nodeKey(node.getSourceFile(), node));
911
- for (const id of ids ?? []) {
912
- if (!found.some((target) => target.id === id)) {
913
- found.push({ id, stepped: true, guessed: false });
914
- }
186
+ for (const handle of writtenBy.size === 0 ? [] : symbol.declarations) {
187
+ if (!LITERAL_PROPERTY_KINDS.has(handle.kind) || !ownPaths.has(handle.path)) {
188
+ continue;
189
+ }
190
+ const node = project.declarationAt(handle)?.node;
191
+ const ids = node === undefined ? undefined : writtenBy.get(nodeKey(node.getSourceFile(), node));
192
+ for (const id of ids ?? []) {
193
+ if (!found.some((target) => target.id === id)) {
194
+ found.push({ id, stepped: true, guessed: false });
915
195
  }
916
196
  }
917
197
  }
918
198
  forwardedCache.set(symbol.id, found);
919
199
  return found;
920
200
  };
921
- /** One resolved symbol's declarations along its chain, and the members its literal properties write. */
922
- const targetsOf = (symbol) => {
923
- const through = forwarded(symbol);
924
- return through.length === 0 ? chains.chainOf(symbol) : [...chains.chainOf(symbol), ...through];
201
+ let ownFiles;
202
+ const pass = {
203
+ project,
204
+ held,
205
+ chains,
206
+ tables,
207
+ cap,
208
+ sink,
209
+ consumer: consumer !== undefined,
210
+ directories,
211
+ fileIds,
212
+ fileIdByName: new Map(loadable.map((one) => [one.file.fileName, one.fileId])),
213
+ loadableById: new Map(loadable.map((one) => [one.fileId, one.file])),
214
+ destructured,
215
+ unseenLoads: [],
216
+ formedForNode: options.formedForNode === true,
217
+ possiblyLoaded: new Set(),
218
+ declaredIn,
219
+ exportsRead,
220
+ named,
221
+ targetsOf: (symbol) => {
222
+ const through = forwarded(symbol);
223
+ return through.length === 0
224
+ ? chains.chainOf(symbol)
225
+ : [...chains.chainOf(symbol), ...through];
226
+ },
227
+ throughAliases,
228
+ ownFiles: () => (ownFiles ??= sourceFilesOf(project, targetRoot)),
925
229
  };
926
- const testFile = new Map();
927
- for (const { file } of walked) {
928
- const path = renderPosition(file, root, 0).path;
929
- const test = patterns.some((pattern) => pattern.test(path)) ||
930
- options.testFileNames?.has(file.fileName) === true;
931
- testFile.set(file, test);
932
- if (test) {
933
- tests.push(path);
934
- }
230
+ for (const site of contextual.writes) {
231
+ const file = site.at.getSourceFile();
232
+ sink.add(file, site.at, site.from, site.ids, "write", "contextual", site.at.parent);
935
233
  }
936
- /** Each write reference's index in `found`, by its file, with the value it stores. */
937
- const valued = new Map();
938
- const holdsValue = (file, value) => {
939
- if (value !== undefined) {
940
- valued.set(file, [...(valued.get(file) ?? []), { index: found.length - 1, value }]);
941
- }
942
- };
943
- for (const [use, sites] of [
944
- ["write", contextual.writes],
945
- ["read", contextual.reads],
946
- ]) {
947
- for (const site of sites) {
948
- const file = site.at.getSourceFile();
949
- const position = renderPosition(file, root, site.at.getStart());
950
- for (const id of site.ids) {
951
- found.push({
952
- from: site.from,
953
- to: id,
954
- position,
955
- use,
956
- resolution: "contextual",
957
- test: testFile.get(file) === true,
958
- });
959
- if (use === "write") {
960
- holdsValue(file, site.at.parent);
961
- }
962
- }
963
- }
234
+ for (const site of contextual.reads) {
235
+ sink.add(site.at.getSourceFile(), site.at, site.from, site.ids, "read", "contextual");
964
236
  }
965
- const keyNames = computedKeyNames(held.symbols);
966
- for (const { file, fileId, uses, reads, links, evaluations, wholes, urls, workers, loadCalls, decorated, parameterWrites, keyed, } of walked) {
967
- const test = testFile.get(file) === true;
968
- const loads = computedLoadsOf(file, loadCalls, loadable, (fileName) => options.directories.bundlerRootOf(fileName));
969
- // A component file's markup may use any binding its blocks declare at the top level.
970
- const markup = isComponentFile(file);
971
- const bindings = markup ? importBindings(file) : [];
972
- // A decorator is written inside the declaration it is attached to and receives it,
973
- // so the declaration is used where the decorator is written, whatever the
974
- // decorator does with it.
975
- for (const { decorator, declaration } of decorated) {
976
- found.push({
977
- from: declaration,
978
- to: declaration,
979
- position: renderPosition(file, root, decorator.getStart()),
980
- use: "decorator",
981
- resolution: "syntax",
982
- test,
983
- });
984
- }
985
- // The constructor stores its argument in the member a parameter property declares.
986
- for (const { name, from, member } of parameterWrites) {
987
- found.push({
988
- from,
989
- to: member,
990
- position: renderPosition(file, root, name.getStart()),
991
- use: "write",
992
- resolution: "syntax",
993
- test,
994
- });
995
- }
996
- // One batch per capped run of the file's name nodes, the re-exports' nodes after
997
- // the uses and the evaluated modules' specifiers last, each node once: a bare star
998
- // re-export's specifier is a link and an evaluation both. A shorthand is left out
999
- // of the batch: its name resolves to the property the literal declares, so the
1000
- // batch's answer for it would name something this project's inventory never holds.
1001
- const batching = [
1002
- ...new Set([
1003
- ...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
1004
- ...links.map((link) => link.node),
1005
- ...evaluations.flatMap((evaluation) => evaluation.require === undefined
1006
- ? [evaluation.node]
1007
- : [
1008
- evaluation.require,
1009
- ...(evaluation.require.text === "require" ? [evaluation.node] : []),
1010
- ]),
1011
- ...wholes.map((whole) => whole.specifier),
1012
- ...urls.flatMap((url) => url.constructor ?? []),
1013
- ...workers.flatMap((worker) => worker.holder === undefined ? [worker.constructor] : [worker.constructor, worker.holder]),
1014
- ...bindings.map((binding) => binding.node),
1015
- ...loads.flatMap((load) => [
1016
- ...(load.require === undefined ? [] : [load.require]),
1017
- ...(load.asset ? [] : load.files.map((loaded) => loaded.file)),
1018
- ]),
1019
- ]),
1020
- ];
1021
- const answered = new Map();
1022
- batched += batching.length;
1023
- for (let from = 0; from < batching.length; from += cap) {
1024
- const run = batching.slice(from, from + cap);
1025
- const answers = project.symbolsAt(run.map((node) => project.handle(node)), cap);
1026
- fileBatches += 1;
1027
- run.forEach((node, index) => {
1028
- const symbol = answers[index];
1029
- if (symbol !== undefined) {
1030
- answered.set(node, symbol);
1031
- }
1032
- });
1033
- }
1034
- for (const link of links) {
1035
- const symbol = answered.get(link.node);
1036
- if (symbol === undefined) {
1037
- continue;
1038
- }
1039
- const position = renderPosition(file, root, link.at.getStart());
1040
- const carried = symbol === UNANSWERED
1041
- ? named(link.at).map((id) => ({ id, stepped: false, guessed: true }))
1042
- : carriedBy(link, symbol);
1043
- for (const target of carried) {
1044
- found.push({
1045
- from: link.from,
1046
- to: target.id,
1047
- position,
1048
- use: "read",
1049
- resolution: target.guessed ? "by-name" : link.alias ? "alias" : "batch",
1050
- test,
1051
- });
1052
- }
1053
- }
1054
- const loading = evaluations.filter((one) => one.require === undefined ||
1055
- callsPlatform(project, "require", one.require, answered.get(one.require), asked));
1056
- // A script declares no module, so the checker names nothing for a specifier of one;
1057
- // the compiler's module resolution still names its file.
1058
- const unnamed = loading.flatMap(({ node }) => answered.get(node) === undefined &&
1059
- isStringLiteralLikeNode(node) &&
1060
- queriedFile(file, node, fileIdByName) === undefined
1061
- ? [node]
1062
- : []);
1063
- const resolved = project.resolvedFileNames(unnamed);
1064
- const scripts = new Map(unnamed.map((node, at) => {
1065
- const name = resolved[at];
1066
- return [node, name === undefined ? undefined : fileIdByName.get(name)];
1067
- }));
1068
- const wholeReads = [];
1069
- for (const evaluation of loading) {
1070
- const queried = queriedFile(file, evaluation.node, fileIdByName);
1071
- if (queried !== undefined) {
1072
- found.push({
1073
- from: evaluation.from,
1074
- to: queried,
1075
- position: renderPosition(file, root, evaluation.node.getStart()),
1076
- use: "evaluation",
1077
- resolution: "syntax",
1078
- test,
1079
- });
1080
- }
1081
- const module = answered.get(evaluation.node);
1082
- // Node's require resolves a relative specifier the program records no resolution for,
1083
- // as in a TypeScript file, by its own extension search.
1084
- const script = scripts.get(evaluation.node) ??
1085
- (evaluation.require !== undefined &&
1086
- module === undefined &&
1087
- isLiteralText(evaluation.node) &&
1088
- /^\.\.?\//u.test(evaluation.node.text)
1089
- ? fileNamed(file, evaluation.node.text, fileIdByName)
1090
- : undefined);
1091
- if (script !== undefined && evaluation.whole === true && module === undefined) {
1092
- wholeReads.push({ evaluation, fileId: script });
1093
- }
1094
- if (script !== undefined) {
1095
- found.push({
1096
- from: evaluation.from,
1097
- to: script,
1098
- position: renderPosition(file, root, evaluation.node.getStart()),
1099
- use: "evaluation",
1100
- resolution: "syntax",
1101
- test,
1102
- });
1103
- }
1104
- if (module === undefined) {
1105
- continue;
1106
- }
1107
- const position = renderPosition(file, root, evaluation.node.getStart());
1108
- // A module the checker could not resolve may be any file of the target.
1109
- const evaluated = module === UNANSWERED ? [...fileIds] : chains.declarationsOf(module);
1110
- for (const id of evaluated) {
1111
- if (fileIds.has(id)) {
1112
- found.push({
1113
- from: evaluation.from,
1114
- to: id,
1115
- position,
1116
- use: "evaluation",
1117
- resolution: module === UNANSWERED ? "by-name" : "batch",
1118
- test,
1119
- });
1120
- }
1121
- }
1122
- }
1123
- // A required file the checker names no module for is read whole where the call's value
1124
- // is used: the value is the file's exports object, which this pass does not follow.
1125
- const required = wholeReads.flatMap((one) => {
1126
- const loaded = loadableById.get(one.fileId);
1127
- return loaded === undefined ? [] : [{ ...one, loaded }];
1128
- });
1129
- const modules = project.symbolsAt(required.map((one) => project.handle(one.loaded)), cap);
1130
- required.forEach(({ evaluation }, at) => {
1131
- const module = modules[at];
1132
- const position = renderPosition(file, root, evaluation.node.getStart());
1133
- for (const target of module === undefined ? [] : exportsRead(module)) {
1134
- found.push({
1135
- from: evaluation.from,
1136
- to: target.id,
1137
- position,
1138
- use: "read",
1139
- resolution: target.guessed ? "by-name" : "batch",
1140
- test,
1141
- });
1142
- }
1143
- });
1144
- for (const url of urls) {
1145
- if (url.constructor !== undefined &&
1146
- !callsPlatform(project, "URL", url.constructor, answered.get(url.constructor), asked)) {
1147
- continue;
1148
- }
1149
- const path = relativeURLPath(dirnamePath(file.fileName), url.specifier);
1150
- const loaded = path === undefined ? undefined : fileNamed(file, path, fileIdByName);
1151
- if (loaded !== undefined) {
1152
- found.push({
1153
- from: url.from,
1154
- to: loaded,
1155
- position: renderPosition(file, root, url.at.getStart()),
1156
- use: "evaluation",
1157
- resolution: "syntax",
1158
- test,
1159
- });
1160
- }
1161
- }
1162
- for (const worker of workers) {
1163
- const symbol = answered.get(worker.constructor);
1164
- const text = worker.address.text;
1165
- let path;
1166
- if (startsThread(project, symbol, worker.holder && answered.get(worker.holder), asked)) {
1167
- path =
1168
- worker.evaluated || !/^\.\.?\//u.test(text)
1169
- ? undefined
1170
- : resolvePath(options.directories.workingDirectoryOf(file.fileName), text);
1171
- }
1172
- else if ((WORKER_CONSTRUCTORS.has(worker.constructor.text) ||
1173
- worker.constructor.text === "navigator") &&
1174
- isPlatformGlobal(project, symbol)) {
1175
- path = relativeURLPath(dirnamePath(file.fileName), text);
1176
- }
1177
- ownFiles ??= sourceFilesOf(project, targetRoot);
1178
- for (const started of path === undefined ? [] : ownFiles.named(path)) {
1179
- const to = declaredAt(started);
1180
- if (to !== undefined && fileIds.has(to)) {
1181
- found.push({
1182
- from: worker.from,
1183
- to,
1184
- position: renderPosition(file, root, worker.at.getStart()),
1185
- use: "evaluation",
1186
- resolution: "syntax",
1187
- test,
1188
- });
1189
- }
1190
- }
1191
- }
1192
- // A computed load imports each file it loads where the call is written, reading what
1193
- // the file exports unless it reads the file as text or a URL; callsPlatform owns which
1194
- // call is a `require`.
1195
- for (const load of loads) {
1196
- if (load.require !== undefined &&
1197
- !callsPlatform(project, "require", load.require, answered.get(load.require), asked)) {
1198
- continue;
1199
- }
1200
- const position = renderPosition(file, root, load.call.getStart());
1201
- if (!load.resolved && !test && consumer === undefined && !file.isDeclarationFile) {
1202
- unseenLoads.push(position);
1203
- }
1204
- for (const loaded of load.files) {
1205
- found.push({
1206
- from: load.eager ? fileId : load.from,
1207
- to: loaded.fileId,
1208
- position,
1209
- use: "evaluation",
1210
- resolution: "syntax",
1211
- test,
1212
- });
1213
- const module = load.asset ? undefined : answered.get(loaded.file);
1214
- for (const target of module === undefined ? [] : exportsRead(module)) {
1215
- found.push({
1216
- from: load.from,
1217
- to: target.id,
1218
- position,
1219
- use: "read",
1220
- resolution: target.guessed ? "by-name" : "batch",
1221
- test,
1222
- });
1223
- }
1224
- }
1225
- }
1226
- for (const whole of wholes) {
1227
- const module = answered.get(whole.specifier);
1228
- if (module === undefined) {
1229
- continue;
1230
- }
1231
- const position = renderPosition(file, root, whole.at.getStart());
1232
- for (const target of exportsRead(module)) {
1233
- found.push({
1234
- from: whole.from,
1235
- to: target.id,
1236
- position,
1237
- use: "read",
1238
- resolution: target.guessed ? "by-name" : "batch",
1239
- test,
1240
- });
1241
- }
1242
- }
1243
- if (markup) {
1244
- for (const symbol of declaredIn(fileId)) {
1245
- found.push({
1246
- from: fileId,
1247
- to: symbol.id,
1248
- position: symbol.position,
1249
- use: "read",
1250
- resolution: "syntax",
1251
- test,
1252
- });
1253
- }
1254
- for (const binding of bindings) {
1255
- const symbol = answered.get(binding.node);
1256
- if (symbol === undefined) {
1257
- continue;
1258
- }
1259
- const position = renderPosition(file, root, binding.node.getStart());
1260
- const chained = symbol === UNANSWERED
1261
- ? named(binding.node).map((id) => ({ id, stepped: false, guessed: true }))
1262
- : chains.chainOf(symbol);
1263
- // The markup may name any export of a module a binding's chain reaches, whether a
1264
- // namespace import binds it or a named or default import of a namespace re-export.
1265
- const targets = chained.flatMap((target) => [
1266
- target,
1267
- ...declaredIn(target.id)
1268
- .filter((one) => one.exported)
1269
- .map((one) => ({ ...target, id: one.id })),
1270
- ]);
1271
- for (const target of targets) {
1272
- found.push({
1273
- from: fileId,
1274
- to: target.id,
1275
- position,
1276
- use: "read",
1277
- resolution: target.guessed ? "by-name" : target.stepped ? "alias" : "batch",
1278
- test,
1279
- });
1280
- }
1281
- }
1282
- }
1283
- // A pattern names a property of the value it destructures, which no name node of
1284
- // the pattern resolves to: a shorthand's name is the local it binds.
1285
- destructured.resolve(reads).forEach((symbol, index) => {
1286
- const read = reads[index];
1287
- if (symbol === undefined || read === undefined) {
1288
- return;
1289
- }
1290
- const position = renderPosition(file, root, read.key.getStart());
1291
- const targets = symbol === UNANSWERED
1292
- ? (held.byName.get(read.name) ?? []).map((id) => ({
1293
- id,
1294
- stepped: false,
1295
- guessed: true,
1296
- }))
1297
- : targetsOf(symbol);
1298
- for (const target of targets) {
1299
- found.push({
1300
- from: read.from,
1301
- to: target.id,
1302
- position,
1303
- use: "read",
1304
- resolution: target.guessed ? "by-name" : "destructured",
1305
- test,
1306
- });
1307
- }
1308
- });
1309
- const resolvedKeys = keyed.flatMap((access) => {
1310
- const symbol = isIdentifier(access.key) && keyNames.has(access.key.text)
1311
- ? answered.get(access.key)
1312
- : undefined;
1313
- return typeof symbol === "object" ? [{ access, symbol }] : [];
1314
- });
1315
- for (const { access, ids } of keyedUses(project, chains, resolvedKeys, tables, cap)) {
1316
- const position = renderPosition(file, root, access.access.argumentExpression.getStart());
1317
- for (const id of ids) {
1318
- for (const use of access.use === "both" ? ["read", "write"] : [access.use]) {
1319
- found.push({ from: access.from, to: id, position, use, resolution: "keyed", test });
1320
- }
1321
- }
1322
- }
1323
- for (const site of uses) {
1324
- const direct = site.shorthand !== undefined
1325
- ? "shorthand"
1326
- : typeof answered.get(site.node) === "object"
1327
- ? "batch"
1328
- : "resolved-symbol";
1329
- const symbol = symbolFor(site, answered);
1330
- if (symbol === undefined) {
1331
- continue;
1332
- }
1333
- const position = renderPosition(file, root, site.node.getStart());
1334
- // A re-export something imports is used and so is the declaration behind it, so
1335
- // the use names every link of the chain its name stands at the head of.
1336
- const targets = throughAliases(symbol === UNANSWERED
1337
- ? named(site.node).map((id) => ({ id, stepped: false, guessed: true }))
1338
- : targetsOf(symbol));
1339
- for (const target of targets) {
1340
- found.push({
1341
- from: site.from,
1342
- to: target.id,
1343
- position,
1344
- use: site.use,
1345
- resolution: target.guessed ? "by-name" : target.stepped ? "alias" : direct,
1346
- test,
1347
- });
1348
- if (site.use === "write") {
1349
- holdsValue(file, storedValue(site.node));
1350
- }
1351
- }
1352
- }
237
+ for (const one of sites) {
238
+ emitFile(prepared(one, pass, loadable), pass);
1353
239
  }
1354
- /** The ambient global interfaces of the walked files that merge into a declaration outside the project's own files. */
1355
- const libraryMerged = (scripts) => {
1356
- const names = new Map();
1357
- const visit = (node) => {
1358
- if (isInterfaceDeclaration(node)) {
1359
- const id = declaredAt(node);
1360
- if (id !== undefined) {
1361
- names.set(id, node.name);
1362
- }
240
+ const context = { project, held, chains };
241
+ for (const rule of rules) {
242
+ for (const read of rule.reads(context)) {
243
+ if (read.kind === "named") {
244
+ sink.add(read.file, read.at, read.from, [read.to], read.use, read.via, undefined, read.production);
1363
245
  }
1364
- else if (isModuleDeclaration(node) || isModuleBlock(node)) {
1365
- node.forEachChild(visit);
246
+ else {
247
+ sink.hold(heldBy(read, context));
1366
248
  }
1367
- };
1368
- for (const { file } of walked) {
1369
- file.forEachChild(visit);
1370
- }
1371
- const global = new Set(ambientGlobalInterfaces(held, [...names.keys()], scripts));
1372
- const asked = [...names].filter(([id]) => global.has(id));
1373
- if (asked.length === 0) {
1374
- return new Set();
1375
249
  }
1376
- const answers = project.symbolsAt(asked.map(([, name]) => project.handle(name)));
1377
- return new Set(asked.flatMap(([id], at) => {
1378
- const symbol = answers[at];
1379
- return typeof symbol === "object" &&
1380
- symbol.declarations.some((handle) => !ownPaths.has(handle.path))
1381
- ? [id]
1382
- : [];
1383
- }));
1384
- };
1385
- const usesOf = new Map(walked.map((one) => [one.file, one.uses]));
1386
- for (const [file, writes] of valued) {
1387
- const uses = usesOf.get(file) ?? [];
1388
- for (const { index, names } of aloneIn(file, uses, writes, found)) {
1389
- const reference = found[index];
1390
- if (reference !== undefined) {
1391
- found[index] = { ...reference, alone: names };
1392
- }
1393
- }
1394
- }
1395
- let typedLookups = 0;
1396
- if (consumer === undefined) {
1397
- const before = options.requestCount?.() ?? 0;
1398
- found.push(...overrideReferences(project, held, chains, new Set(tests)));
1399
- found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
1400
- found.push(...elementAccessReferences(project, held, chains, root, new Set(tests)));
1401
- found.push(...keyofComparisonReferences(project, held, chains, root, new Set(tests)));
1402
- found.push(...iterationReferences(project, held, chains, root, new Set(tests)));
1403
- found.push(...typeParameterReads(project, held, chains, root, new Set(tests)));
1404
- found.push(...unimplementedCalleeReads(project, held, chains, root, new Set(tests)));
1405
- found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
1406
- typedLookups = (options.requestCount?.() ?? 0) - before;
1407
250
  }
1408
- const scripts = new Set(walked.filter((one) => one.file.externalModuleIndicator === undefined).map((one) => one.fileId));
1409
- found.push(...augmentationReferences(held, found, scripts, libraryMerged(scripts)));
1410
- found.sort((a, b) => byPosition(a.position, b.position) ||
1411
- bytewise(a.to, b.to) ||
1412
- bytewise(a.from, b.from) ||
1413
- bytewise(a.use, b.use));
251
+ const merges = mergedGlobalInterfaces(project, held, pass.ownFiles());
252
+ sink.extend(augmentationReferences(held, sink.made, merges.scripts, merges.interfaces));
253
+ const finished = sink.finish(new Map(sites.map((one) => [one.file, one.uses])));
1414
254
  return {
1415
- references: consumer === undefined
1416
- ? found
1417
- : found.map((reference) => ({ ...reference, consumer: consumer.id })),
1418
- testFilePaths: tests.sort(bytewise),
1419
- unseenLoads,
1420
- cost: {
1421
- batched,
1422
- fileBatches,
1423
- residueFallbacks,
1424
- shorthandLookups,
1425
- aliasSteps: chains.steps,
1426
- platformLookups,
1427
- patternBatches: destructured.cost.patternBatches,
1428
- patternLookups: destructured.cost.patternLookups,
1429
- contextualBatches: contextual.batches,
1430
- contextualLookups: contextual.lookups,
1431
- typedLookups,
1432
- },
255
+ ...finished,
256
+ unseenLoads: pass.unseenLoads,
257
+ possiblyLoaded: [...pass.possiblyLoaded].sort(bytewise),
1433
258
  };
1434
259
  }