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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/dist/src/alias-chain.js +0 -5
  2. package/dist/src/amd.js +81 -0
  3. package/dist/src/analysis.js +94 -54
  4. package/dist/src/augmentations.js +23 -24
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +25 -8
  7. package/dist/src/commands/read.js +6 -7
  8. package/dist/src/commands/tools.js +5 -9
  9. package/dist/src/component-files.js +3 -2
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/consumers.js +9 -10
  12. package/dist/src/contextual-uses.js +1 -17
  13. package/dist/src/contract/config.generated.js +5 -2
  14. package/dist/src/contract/contract.generated.js +119 -2
  15. package/dist/src/contract/report.generated.js +6 -2
  16. package/dist/src/dependencies.js +98 -71
  17. package/dist/src/destructuring.js +1 -19
  18. package/dist/src/element-access.js +179 -164
  19. package/dist/src/entries/build-scripts.js +222 -0
  20. package/dist/src/{configuration-files.js → entries/configuration-files.js} +22 -64
  21. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  22. package/dist/src/entries/conventions.js +739 -0
  23. package/dist/src/{html-entries.js → entries/html-entries.js} +9 -12
  24. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +3 -3
  25. package/dist/src/entries/run-directories.js +77 -0
  26. package/dist/src/entries/sources.js +144 -0
  27. package/dist/src/entries/static-module.js +670 -0
  28. package/dist/src/entries/test-runners.js +684 -0
  29. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  30. package/dist/src/extensions.js +37 -0
  31. package/dist/src/facts.js +38 -0
  32. package/dist/src/findings/completion.js +40 -4
  33. package/dist/src/findings/emitters.js +5 -8
  34. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  35. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  36. package/dist/src/findings/self-check.js +36 -20
  37. package/dist/src/findings-pass.js +5 -6
  38. package/dist/src/framework-lifecycle.js +42 -15
  39. package/dist/src/fs/manifest.js +15 -20
  40. package/dist/src/generated-file.js +172 -29
  41. package/dist/src/graph.js +14 -8
  42. package/dist/src/import-bindings.js +73 -28
  43. package/dist/src/index/sites.js +557 -0
  44. package/dist/src/index.js +1 -1
  45. package/dist/src/indexed-access.js +523 -478
  46. package/dist/src/injection-container.js +1 -1
  47. package/dist/src/interface-satisfaction.js +1 -1
  48. package/dist/src/inventory/commonjs.js +62 -17
  49. package/dist/src/inventory/enumerate.js +2 -1
  50. package/dist/src/javascript-file.js +26 -11
  51. package/dist/src/keyof-comparisons.js +124 -103
  52. package/dist/src/load/classify.js +28 -9
  53. package/dist/src/load/diagnostics.js +8 -1
  54. package/dist/src/load/discover.js +15 -7
  55. package/dist/src/load/expected-module.js +2 -2
  56. package/dist/src/load/plan.js +2 -2
  57. package/dist/src/load/specifiers.js +2 -4
  58. package/dist/src/load/synthesize.js +10 -5
  59. package/dist/src/load/workspace-manifests.js +1 -1
  60. package/dist/src/load/workspace-resolution.js +2 -2
  61. package/dist/src/matrix.js +12 -17
  62. package/dist/src/module-declarations.js +70 -85
  63. package/dist/src/overrides.js +134 -112
  64. package/dist/src/paths.js +56 -1
  65. package/dist/src/platform-calls.js +78 -0
  66. package/dist/src/platform-symbols.js +134 -0
  67. package/dist/src/references/read-rule.js +1 -0
  68. package/dist/src/references/read-rules.js +48 -0
  69. package/dist/src/references/sink.js +151 -0
  70. package/dist/src/references/sources.js +450 -0
  71. package/dist/src/references.js +111 -1116
  72. package/dist/src/resolve.js +1 -1
  73. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +7 -4
  74. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  75. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  76. package/dist/src/roots/published-reach.js +412 -0
  77. package/dist/src/roots/root-set.js +489 -0
  78. package/dist/src/roots/unseen-loads.js +236 -0
  79. package/dist/src/serialization-contract.js +9 -4
  80. package/dist/src/source-files.js +5 -9
  81. package/dist/src/stores.js +4 -2
  82. package/dist/src/suppress.js +30 -32
  83. package/dist/src/sweep.js +25 -13
  84. package/dist/src/symbol-keys.js +89 -83
  85. package/dist/src/tests/file-class.js +76 -0
  86. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  87. package/dist/src/{test-support.js → tests/support.js} +5 -12
  88. package/dist/src/type-parameter-reads.js +178 -139
  89. package/dist/src/unimplemented-callees.js +262 -242
  90. package/dist/src/verbs/analyze.js +15 -3
  91. package/dist/src/verbs/describe.js +5 -3
  92. package/dist/src/verbs/explain.js +50 -28
  93. package/dist/src/verbs/print-retained.js +2 -2
  94. package/jsr.json +1 -1
  95. package/package.json +3 -3
  96. package/src/alias-chain.ts +0 -7
  97. package/src/amd.ts +130 -0
  98. package/src/analysis.ts +156 -70
  99. package/src/augmentations.ts +41 -27
  100. package/src/automatic-types.ts +1 -1
  101. package/src/bindings.ts +460 -0
  102. package/src/calls.ts +25 -7
  103. package/src/commands/read.ts +6 -8
  104. package/src/commands/tools.ts +5 -11
  105. package/src/component-files.ts +4 -2
  106. package/src/conformance-record.ts +1 -1
  107. package/src/consumers.ts +12 -11
  108. package/src/contextual-uses.ts +2 -22
  109. package/src/contract/config.generated.ts +5 -2
  110. package/src/contract/contract.generated.ts +138 -2
  111. package/src/contract/report.generated.ts +6 -2
  112. package/src/dependencies.ts +117 -71
  113. package/src/destructuring.ts +1 -31
  114. package/src/element-access.ts +188 -182
  115. package/src/entries/build-scripts.ts +292 -0
  116. package/src/{configuration-files.ts → entries/configuration-files.ts} +33 -82
  117. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  118. package/src/{conventions.ts → entries/conventions.ts} +349 -523
  119. package/src/{html-entries.ts → entries/html-entries.ts} +19 -18
  120. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +5 -5
  121. package/src/entries/run-directories.ts +108 -0
  122. package/src/entries/sources.ts +266 -0
  123. package/src/entries/static-module.ts +858 -0
  124. package/src/entries/test-runners.ts +868 -0
  125. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  126. package/src/exempt.ts +3 -2
  127. package/src/extensions.ts +45 -0
  128. package/src/facts.ts +46 -0
  129. package/src/file-facts.ts +1 -1
  130. package/src/findings/boundary.ts +2 -0
  131. package/src/findings/completion.ts +43 -4
  132. package/src/findings/emitter.ts +1 -1
  133. package/src/findings/emitters.ts +14 -10
  134. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  135. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  136. package/src/findings/self-check.ts +41 -22
  137. package/src/findings/visibility-narrowing.ts +1 -1
  138. package/src/findings-pass.ts +5 -12
  139. package/src/framework-lifecycle.ts +43 -15
  140. package/src/fs/manifest.ts +25 -21
  141. package/src/generated-file.ts +240 -36
  142. package/src/graph.ts +27 -11
  143. package/src/import-bindings.ts +98 -29
  144. package/src/index/sites.ts +781 -0
  145. package/src/index.ts +1 -1
  146. package/src/indexed-access.ts +570 -527
  147. package/src/injection-container.ts +1 -1
  148. package/src/interface-satisfaction.ts +1 -1
  149. package/src/inventory/commonjs.ts +102 -17
  150. package/src/inventory/enumerate.ts +2 -1
  151. package/src/javascript-file.ts +29 -12
  152. package/src/keyof-comparisons.ts +131 -116
  153. package/src/load/classify.ts +49 -13
  154. package/src/load/diagnostics.ts +11 -1
  155. package/src/load/discover.ts +27 -11
  156. package/src/load/expected-module.ts +8 -2
  157. package/src/load/plan.ts +11 -2
  158. package/src/load/specifiers.ts +8 -5
  159. package/src/load/synthesize.ts +12 -6
  160. package/src/load/test-globals.ts +1 -1
  161. package/src/load/workspace-manifests.ts +1 -1
  162. package/src/load/workspace-resolution.ts +9 -2
  163. package/src/load/workspace-run.ts +1 -1
  164. package/src/matrix.ts +13 -17
  165. package/src/module-declarations.ts +74 -98
  166. package/src/overrides.ts +162 -134
  167. package/src/paths.ts +65 -1
  168. package/src/platform-calls.ts +119 -0
  169. package/src/platform-symbols.ts +189 -0
  170. package/src/references/read-rule.ts +60 -0
  171. package/src/references/read-rules.ts +55 -0
  172. package/src/references/sink.ts +213 -0
  173. package/src/references/sources.ts +632 -0
  174. package/src/references.ts +174 -1492
  175. package/src/report.ts +2 -2
  176. package/src/resolve.ts +1 -1
  177. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +10 -7
  178. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  179. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  180. package/src/roots/published-reach.ts +481 -0
  181. package/src/{roots.ts → roots/root-set.ts} +203 -630
  182. package/src/roots/unseen-loads.ts +345 -0
  183. package/src/serialization-contract.ts +9 -4
  184. package/src/source-files.ts +5 -10
  185. package/src/stores.ts +13 -2
  186. package/src/suppress.ts +48 -33
  187. package/src/sweep.ts +37 -14
  188. package/src/symbol-keys.ts +102 -102
  189. package/src/tests/file-class.ts +118 -0
  190. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  191. package/src/{test-support.ts → tests/support.ts} +15 -21
  192. package/src/type-parameter-reads.ts +187 -158
  193. package/src/unimplemented-callees.ts +297 -280
  194. package/src/verbs/analyze.ts +19 -5
  195. package/src/verbs/describe.ts +5 -2
  196. package/src/verbs/explain.ts +75 -32
  197. package/src/verbs/print-retained.ts +2 -2
  198. package/dist/src/build-scripts.js +0 -418
  199. package/dist/src/conventions.js +0 -907
  200. package/dist/src/entry-points.js +0 -444
  201. package/dist/src/own-body.js +0 -28
  202. package/dist/src/roots.js +0 -865
  203. package/dist/src/unseen-loads.js +0 -189
  204. package/src/build-scripts.ts +0 -536
  205. package/src/entry-points.ts +0 -584
  206. package/src/own-body.ts +0 -42
  207. package/src/unseen-loads.ts +0 -282
@@ -156,8 +156,6 @@ function sourceOf(target) {
156
156
  export function destructuring(project, cap, properties = propertyTables(project.queries)) {
157
157
  const queries = project.queries;
158
158
  const tables = new Map();
159
- let patternBatches = 0;
160
- let patternLookups = 0;
161
159
  const tableOf = (type) => {
162
160
  const known = tables.get(type.id);
163
161
  if (known !== undefined) {
@@ -166,7 +164,6 @@ export function destructuring(project, cap, properties = propertyTables(project.
166
164
  let table = new Map();
167
165
  if ((type.flags & TypeFlags.AnyOrUnknown) === 0) {
168
166
  const read = properties.of(type);
169
- patternLookups += read.asked ? 1 : 0;
170
167
  table = isAnswered(read.properties)
171
168
  ? new Map(read.properties.map((property) => [property.name, property]))
172
169
  : UNANSWERED;
@@ -178,7 +175,6 @@ export function destructuring(project, cap, properties = propertyTables(project.
178
175
  if ((type.flags & TypeFlags.Union) === 0) {
179
176
  return type;
180
177
  }
181
- patternLookups += 1;
182
178
  return queries.nonNullable(type);
183
179
  };
184
180
  /**
@@ -193,11 +189,9 @@ export function destructuring(project, cap, properties = propertyTables(project.
193
189
  if (index === undefined) {
194
190
  return undefined;
195
191
  }
196
- patternLookups += 1;
197
192
  const elements = queries.typeArguments(type);
198
193
  return isAnswered(elements) ? elements[index] : UNANSWERED;
199
194
  }
200
- patternLookups += 1;
201
195
  const array = queries.isArray(type);
202
196
  if (!isAnswered(array)) {
203
197
  return UNANSWERED;
@@ -205,7 +199,6 @@ export function destructuring(project, cap, properties = propertyTables(project.
205
199
  if (!array) {
206
200
  return undefined;
207
201
  }
208
- patternLookups += 1;
209
202
  const elements = queries.typeArguments(type);
210
203
  return isAnswered(elements) ? elements[0] : UNANSWERED;
211
204
  };
@@ -222,14 +215,12 @@ export function destructuring(project, cap, properties = propertyTables(project.
222
215
  if (more.length > 0) {
223
216
  return UNANSWERED;
224
217
  }
225
- patternLookups += 1;
226
218
  return queries.typeOfSymbol(property);
227
219
  };
228
220
  const resultOf = (callee) => {
229
221
  if (callee === undefined || callee === UNANSWERED) {
230
222
  return callee;
231
223
  }
232
- patternLookups += 1;
233
224
  const signatures = queries.signaturesOf(callee, SignatureKind.Call);
234
225
  if (!isAnswered(signatures)) {
235
226
  return UNANSWERED;
@@ -238,7 +229,6 @@ export function destructuring(project, cap, properties = propertyTables(project.
238
229
  if (signature === undefined) {
239
230
  return undefined;
240
231
  }
241
- patternLookups += 1;
242
232
  return queries.returnTypeOf(signature);
243
233
  };
244
234
  const iterations = new Map();
@@ -266,7 +256,6 @@ export function destructuring(project, cap, properties = propertyTables(project.
266
256
  if (result !== undefined && result !== UNANSWERED) {
267
257
  let parts = [result];
268
258
  if (result.isUnionType()) {
269
- patternLookups += 1;
270
259
  parts = queries.constituents(result);
271
260
  }
272
261
  const values = isAnswered(parts)
@@ -296,7 +285,6 @@ export function destructuring(project, cap, properties = propertyTables(project.
296
285
  if (property === undefined) {
297
286
  return undefined;
298
287
  }
299
- patternLookups += 1;
300
288
  return queries.typeOfSymbol(property);
301
289
  }
302
290
  return elementOf(defined, next.index);
@@ -317,7 +305,6 @@ export function destructuring(project, cap, properties = propertyTables(project.
317
305
  }
318
306
  }
319
307
  const asked = [...asking];
320
- patternBatches += Math.ceil(asked.length / cap);
321
308
  const answered = new Map();
322
309
  queries.typesAt(asked, cap).forEach((type, index) => {
323
310
  const node = asked[index];
@@ -347,10 +334,5 @@ export function destructuring(project, cap, properties = propertyTables(project.
347
334
  return isAnswered(table) ? table.get(read.name) : UNANSWERED;
348
335
  });
349
336
  };
350
- return {
351
- resolve,
352
- get cost() {
353
- return { patternBatches, patternLookups };
354
- },
355
- };
337
+ return { resolve };
356
338
  }
@@ -5,8 +5,6 @@
5
5
  * target holds, the members are read at each call, by the type of the argument it passes.
6
6
  */
7
7
  import { isArrayLiteralExpression, isArrowFunction, isBinaryExpression, isCallExpression, isElementAccessExpression, isForInStatement, isForOfStatement, isFunctionDeclaration, isFunctionExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVariableDeclaration, isVariableDeclarationList, isVariableStatement, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
8
- import { nodeKey } from "./inventory.js";
9
- import { renderPosition } from "./position.js";
10
8
  import { UNANSWERED } from "./query.js";
11
9
  import { parenthesesRemoved } from "./unwrap.js";
12
10
  /** The assignment operators that read their target before they write it. */
@@ -153,174 +151,191 @@ function assigns(body, name) {
153
151
  return found;
154
152
  }
155
153
  /**
156
- * The references the element accesses of one project's own files make to the members their
157
- * index's literal type names, each made by the declaration holding the access or the call.
154
+ * The reads the element accesses of a side's files make of the members their index's literal
155
+ * type names, each made by the declaration holding the access or the call. A type the checker
156
+ * leaves unanswered holds what its answer could have read.
158
157
  */
159
- export function elementAccessReferences(project, held, chains, root, testFiles) {
160
- const queries = project.queries;
161
- const candidates = [];
162
- const calls = new Map();
163
- const names = [];
164
- for (const file of project.ownSourceFiles()) {
165
- const fileId = held.declarations.get(nodeKey(file, file));
166
- if (fileId === undefined) {
167
- continue;
168
- }
169
- const visit = (node, from) => {
170
- const holder = held.declarations.get(nodeKey(file, node)) ?? from;
171
- if (isElementAccessExpression(node) && !isLiteralIndex(node.argumentExpression)) {
172
- candidates.push({ file, from: holder, access: node, use: useOf(node) });
173
- }
174
- if (isCallExpression(node)) {
175
- calls.set(node, { file, from: holder });
176
- }
177
- if (isIdentifier(node)) {
178
- names.push(node);
179
- }
180
- node.forEachChild((child) => {
181
- visit(child, holder);
182
- });
183
- };
184
- file.forEachChild((child) => {
185
- visit(child, fileId);
186
- });
187
- }
188
- if (candidates.length === 0) {
189
- return [];
190
- }
191
- // A parameter index is decided at the calls only where the function is a callable every
192
- // use of which is a call, so the symbols of the parameters and of every name are read.
193
- const parameterOf = new Map();
194
- const byCallable = new Map();
195
- for (const candidate of candidates) {
196
- const index = parenthesesRemoved(candidate.access.argumentExpression);
197
- const fn = isIdentifier(index) ? enclosingFunction(candidate.access) : undefined;
198
- const callable = fn === undefined ? undefined : callableOf(fn);
199
- if (callable === undefined || !isIdentifier(index)) {
200
- continue;
201
- }
202
- const at = callable.parameters.findIndex((parameter) => isIdentifier(parameter.name) && parameter.name.text === index.text);
203
- if (at < 0 || assigns(callable.body, index.text)) {
204
- continue;
205
- }
206
- parameterOf.set(candidate, { callable, at });
207
- byCallable.set(callable.name, [...(byCallable.get(callable.name) ?? []), candidate]);
208
- }
209
- const callableNames = [...byCallable.keys()];
210
- const spelled = new Set(callableNames.map((name) => name.text));
211
- const sameName = names.filter((name) => spelled.has(name.text));
212
- const symbols = queries.symbolsAt([...callableNames, ...sameName]);
213
- const symbolOf = new Map();
214
- [...callableNames, ...sameName].forEach((node, at) => {
215
- const symbol = symbols[at];
216
- if (symbol !== UNANSWERED && symbol !== undefined) {
217
- symbolOf.set(node, symbol);
218
- }
219
- });
220
- const callsOf = new Map();
221
- for (const name of callableNames) {
222
- const symbol = symbolOf.get(name);
223
- let found = symbol === undefined ? undefined : [];
224
- for (const use of sameName) {
225
- if (found === undefined) {
226
- break;
227
- }
228
- if (use === name || symbolOf.get(use) !== symbol) {
229
- continue;
230
- }
231
- const parent = use.parent;
232
- found =
233
- isCallExpression(parent) && parent.expression === use ? [...found, parent] : undefined;
234
- }
235
- callsOf.set(name, found);
236
- }
237
- const lookups = [];
238
- for (const candidate of candidates) {
239
- const deferred = parameterOf.get(candidate);
240
- const sites = deferred === undefined ? undefined : callsOf.get(deferred.callable.name);
241
- if (deferred === undefined || sites === undefined) {
242
- lookups.push({
243
- file: candidate.file,
244
- from: candidate.from,
245
- at: candidate.access.argumentExpression,
246
- object: candidate.access.expression,
247
- index: candidate.access.argumentExpression,
248
- use: candidate.use,
249
- });
250
- continue;
251
- }
252
- // An object that is a parameter too is read as the argument each call passes there.
253
- const object = parenthesesRemoved(candidate.access.expression);
254
- const objectAt = isIdentifier(object)
255
- ? deferred.callable.parameters.findIndex((parameter) => isIdentifier(parameter.name) && parameter.name.text === object.text)
256
- : -1;
257
- for (const call of sites) {
258
- const parameter = deferred.callable.parameters[deferred.at];
259
- const index = call.arguments[deferred.at] ?? parameter?.initializer;
260
- const site = calls.get(call);
261
- if (index === undefined || site === undefined) {
262
- continue;
263
- }
264
- lookups.push({
265
- file: site.file,
266
- from: site.from,
267
- at: call,
268
- object: (objectAt < 0 ? undefined : call.arguments[objectAt]) ?? candidate.access.expression,
269
- index,
270
- use: candidate.use,
271
- });
272
- }
273
- }
274
- const nodes = lookups.flatMap((one) => [one.index, one.object]);
275
- const types = queries.typesAt(nodes);
276
- const typeOf = (node, at) => {
277
- const type = types[at];
278
- return nodes[at] === node && type !== UNANSWERED ? type : undefined;
279
- };
280
- // A type parameter's body serves every instantiation, so it reads what its constraint admits.
281
- const keysOf = (type) => {
282
- if (!type.isTypeParameter()) {
283
- return type;
284
- }
285
- const constraint = queries.constraintOf(type);
286
- return constraint === UNANSWERED || constraint === undefined ? type : constraint;
287
- };
288
- const partsOf = (type) => {
289
- const parts = queries.constituents(type);
290
- return parts === UNANSWERED ? [type] : parts;
291
- };
292
- const found = [];
293
- lookups.forEach((lookup, at) => {
294
- const indexType = typeOf(lookup.index, 2 * at);
295
- const objectType = typeOf(lookup.object, 2 * at + 1);
296
- const literals = indexType === undefined ? undefined : literalNames(partsOf(keysOf(indexType)));
297
- if (objectType === undefined || literals === undefined) {
298
- return;
299
- }
300
- const position = renderPosition(lookup.file, root, lookup.at.getStart());
301
- for (const part of partsOf(objectType)) {
302
- const properties = queries.propertiesOf(part);
303
- for (const property of properties === UNANSWERED ? [] : properties) {
304
- if (!literals.has(property.escapedName)) {
305
- continue;
158
+ export const elementAccess = {
159
+ start() {
160
+ const candidates = [];
161
+ const calls = new Map();
162
+ const names = [];
163
+ return {
164
+ visit(node, { file, from }) {
165
+ if (isElementAccessExpression(node) && !isLiteralIndex(node.argumentExpression)) {
166
+ candidates.push({ file, from, access: node, use: useOf(node) });
167
+ }
168
+ if (isCallExpression(node)) {
169
+ calls.set(node, { file, from });
170
+ }
171
+ if (isIdentifier(node)) {
172
+ names.push(node);
306
173
  }
307
- for (const to of chains.declarationsOf(property)) {
308
- for (const use of lookup.use === "both" ? ["read", "write"] : [lookup.use]) {
174
+ },
175
+ reads({ project, chains }) {
176
+ const found = [];
177
+ if (candidates.length === 0) {
178
+ return found;
179
+ }
180
+ const queries = project.queries;
181
+ // A parameter index is decided at the calls only where the function is a callable every
182
+ // use of which is a call, so the symbols of the parameters and of every name are read.
183
+ const parameterOf = new Map();
184
+ const byCallable = new Map();
185
+ for (const candidate of candidates) {
186
+ const index = parenthesesRemoved(candidate.access.argumentExpression);
187
+ const fn = isIdentifier(index) ? enclosingFunction(candidate.access) : undefined;
188
+ const callable = fn === undefined ? undefined : callableOf(fn);
189
+ if (callable === undefined || !isIdentifier(index)) {
190
+ continue;
191
+ }
192
+ const at = callable.parameters.findIndex((parameter) => isIdentifier(parameter.name) && parameter.name.text === index.text);
193
+ if (at < 0 || assigns(callable.body, index.text)) {
194
+ continue;
195
+ }
196
+ parameterOf.set(candidate, { callable, at });
197
+ byCallable.set(callable.name, [...(byCallable.get(callable.name) ?? []), candidate]);
198
+ }
199
+ const callableNames = [...byCallable.keys()];
200
+ const spelled = new Set(callableNames.map((name) => name.text));
201
+ const sameName = names.filter((name) => spelled.has(name.text));
202
+ const asked = [...callableNames, ...sameName];
203
+ const symbols = queries.symbolsAt(asked);
204
+ const symbolOf = new Map();
205
+ asked.forEach((node, at) => {
206
+ const symbol = symbols[at];
207
+ if (symbol !== undefined) {
208
+ symbolOf.set(node, symbol);
209
+ }
210
+ });
211
+ const callsOf = new Map();
212
+ for (const name of callableNames) {
213
+ const symbol = symbolOf.get(name);
214
+ let sites = symbol === undefined || symbol === UNANSWERED ? undefined : [];
215
+ for (const use of sameName) {
216
+ if (sites === undefined) {
217
+ break;
218
+ }
219
+ const other = symbolOf.get(use);
220
+ if (use === name || (other !== symbol && other !== UNANSWERED)) {
221
+ continue;
222
+ }
223
+ // A use the checker could not resolve may be the function's, read where it is not called.
224
+ const parent = use.parent;
225
+ sites =
226
+ other !== UNANSWERED && isCallExpression(parent) && parent.expression === use
227
+ ? [...sites, parent]
228
+ : undefined;
229
+ }
230
+ callsOf.set(name, sites);
231
+ }
232
+ const lookups = [];
233
+ for (const candidate of candidates) {
234
+ const deferred = parameterOf.get(candidate);
235
+ const sites = deferred === undefined ? undefined : callsOf.get(deferred.callable.name);
236
+ if (deferred === undefined || sites === undefined) {
237
+ lookups.push({
238
+ file: candidate.file,
239
+ from: candidate.from,
240
+ at: candidate.access.argumentExpression,
241
+ object: candidate.access.expression,
242
+ index: candidate.access.argumentExpression,
243
+ use: candidate.use,
244
+ });
245
+ continue;
246
+ }
247
+ // An object that is a parameter too is read as the argument each call passes there.
248
+ const object = parenthesesRemoved(candidate.access.expression);
249
+ const objectAt = isIdentifier(object)
250
+ ? deferred.callable.parameters.findIndex((parameter) => isIdentifier(parameter.name) && parameter.name.text === object.text)
251
+ : -1;
252
+ for (const call of sites) {
253
+ const parameter = deferred.callable.parameters[deferred.at];
254
+ const index = call.arguments[deferred.at] ?? parameter?.initializer;
255
+ const site = calls.get(call);
256
+ if (index === undefined || site === undefined) {
257
+ continue;
258
+ }
259
+ lookups.push({
260
+ file: site.file,
261
+ from: site.from,
262
+ at: call,
263
+ object: (objectAt < 0 ? undefined : call.arguments[objectAt]) ??
264
+ candidate.access.expression,
265
+ index,
266
+ use: candidate.use,
267
+ });
268
+ }
269
+ }
270
+ const nodes = lookups.flatMap((one) => [one.index, one.object]);
271
+ const types = queries.typesAt(nodes);
272
+ // A type parameter's body serves every instantiation, so it reads what its constraint admits.
273
+ const keysOf = (type) => {
274
+ if (!type.isTypeParameter()) {
275
+ return type;
276
+ }
277
+ const constraint = queries.constraintOf(type);
278
+ return constraint === UNANSWERED || constraint === undefined ? type : constraint;
279
+ };
280
+ const partsOf = (type) => {
281
+ const parts = queries.constituents(type);
282
+ return parts === UNANSWERED ? [type] : parts;
283
+ };
284
+ lookups.forEach((lookup, at) => {
285
+ const indexType = types[2 * at];
286
+ const objectType = types[2 * at + 1];
287
+ const literals = indexType === undefined || indexType === UNANSWERED
288
+ ? undefined
289
+ : literalNames(partsOf(keysOf(indexType)));
290
+ if (indexType === UNANSWERED || objectType === UNANSWERED) {
309
291
  found.push({
292
+ kind: "held",
310
293
  from: lookup.from,
311
- to,
312
- position,
313
- use,
314
- resolution: "keyed",
315
- test: testFiles.has(position.path),
294
+ ...(literals === undefined ? {} : { names: [...literals] }),
295
+ ...(objectType === UNANSWERED || objectType === undefined
296
+ ? literals === undefined
297
+ ? { members: true }
298
+ : {}
299
+ : { types: partsOf(objectType) }),
316
300
  });
301
+ return;
317
302
  }
318
- }
319
- }
320
- }
321
- });
322
- return found;
323
- }
303
+ if (objectType === undefined || literals === undefined) {
304
+ return;
305
+ }
306
+ for (const part of partsOf(objectType)) {
307
+ const properties = queries.propertiesOf(part);
308
+ if (properties === UNANSWERED) {
309
+ found.push({ kind: "held", from: lookup.from, names: [...literals] });
310
+ continue;
311
+ }
312
+ for (const property of properties) {
313
+ if (!literals.has(property.escapedName)) {
314
+ continue;
315
+ }
316
+ for (const to of chains.declarationsOf(property)) {
317
+ for (const use of lookup.use === "both"
318
+ ? ["read", "write"]
319
+ : [lookup.use]) {
320
+ found.push({
321
+ kind: "named",
322
+ from: lookup.from,
323
+ to,
324
+ file: lookup.file,
325
+ at: lookup.at,
326
+ use,
327
+ via: "keyed",
328
+ });
329
+ }
330
+ }
331
+ }
332
+ }
333
+ });
334
+ return found;
335
+ },
336
+ };
337
+ },
338
+ };
324
339
  /** The names a union of literal types spells, or none where a constituent is no string or number literal. */
325
340
  function literalNames(parts) {
326
341
  const names = new Set();
@@ -0,0 +1,222 @@
1
+ /**
2
+ * The files a bundler's JavaScript API builds from, read from the build scripts a
3
+ * manifest's scripts run: the strings an options object passes as the bundler's entries.
4
+ * A script is read without running it, so a value that is not written out, a computed
5
+ * path or an options object built at run time among them, names nothing.
6
+ */
7
+ import { isArrowFunction, isCallExpression, isConstructorDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNewExpression, isSetAccessorDeclaration, isVariableDeclaration, } from "@typescript/native/ast";
8
+ import { declarationOf, membersNamed, staticModule } from "./static-module.js";
9
+ import { filesNamedBy } from "./configuration-files.js";
10
+ import { ancestors, dirnamePath, isExplicitPath, relativePath, resolvePath } from "../paths.js";
11
+ import { bytewise } from "../text/compare.js";
12
+ import { unwrapped } from "../unwrap.js";
13
+ import { holdsManifest, inInstalledPackages, manifestAt } from "../fs/manifest.js";
14
+ import { globExpression } from "../text/glob.js";
15
+ import { pathsNamed, readCommands } from "../commands/read.js";
16
+ import { manifestCommands } from "../roots/manifest.js";
17
+ import { extensionOf, MODULE_EXTENSIONS } from "../extensions.js";
18
+ /**
19
+ * The bundlers whose build call names its entries: esbuild resolves `entryPoints` against
20
+ * `absWorkingDir` (https://esbuild.github.io/api/#working-directory), webpack `entry`
21
+ * against `context` (https://webpack.js.org/configuration/entry-context/), and Rollup
22
+ * `input` against the working directory, each the manifest's directory when its script runs.
23
+ */
24
+ const BUNDLERS = [
25
+ {
26
+ module: "esbuild",
27
+ calls: ["build", "buildSync", "context"],
28
+ option: "entryPoints",
29
+ base: "absWorkingDir",
30
+ paths: true,
31
+ },
32
+ { module: "webpack", calls: ["default"], option: "entry", base: "context", paths: false },
33
+ { module: "rollup", calls: ["rollup", "watch"], option: "input", base: "", paths: true },
34
+ ];
35
+ /** The one value an object writes last for a key, undefined where it writes none or may hide it. */
36
+ function lastMember(object, key) {
37
+ return membersNamed(object, key)?.at(-1);
38
+ }
39
+ /**
40
+ * The path one entry value writes, read against `dir`: a literal, or a join of the
41
+ * script's own directory and literals, read against that directory.
42
+ */
43
+ function pathOf(value, dir, scriptDir) {
44
+ if (value.kind === "text") {
45
+ const { text } = value;
46
+ const request = !isExplicitPath(text);
47
+ return { path: resolvePath(dir, text), text, request };
48
+ }
49
+ if (value.kind === "directory") {
50
+ const joined = value.segments.join("/");
51
+ return { path: resolvePath(scriptDir, joined), text: joined, request: false };
52
+ }
53
+ return undefined;
54
+ }
55
+ /** The entry values one entries option writes: a value, an array of them, an object of them. */
56
+ function entryValues(value) {
57
+ if (value.kind === "array") {
58
+ return value.elements.flatMap(entryValues);
59
+ }
60
+ if (value.kind === "object") {
61
+ const imported = lastMember(value, "import") ?? lastMember(value, "in");
62
+ if (imported !== undefined) {
63
+ return entryValues(imported);
64
+ }
65
+ return value.members.flatMap((member) => entryValues(member.value));
66
+ }
67
+ return [value];
68
+ }
69
+ /** The own files one entry string names, read as `api` reads it. */
70
+ function entriesNamed(files, api, entry) {
71
+ if (!api.paths && entry.request) {
72
+ return [];
73
+ }
74
+ if (api.paths && /[*?[{]/u.test(entry.text)) {
75
+ const expression = globExpression(entry.path);
76
+ return [...files.byName.values()].filter((file) => expression.test(file.fileName));
77
+ }
78
+ const dir = dirnamePath(entry.path);
79
+ return filesNamedBy(files, dir, entry.path.slice(dir.length + 1));
80
+ }
81
+ /** Whether one node is a function, whose body runs only where something calls it. */
82
+ function isFunction(node) {
83
+ return (isFunctionDeclaration(node) ||
84
+ isFunctionExpression(node) ||
85
+ isArrowFunction(node) ||
86
+ isMethodDeclaration(node) ||
87
+ isConstructorDeclaration(node) ||
88
+ isGetAccessorDeclaration(node) ||
89
+ isSetAccessorDeclaration(node));
90
+ }
91
+ /**
92
+ * The calls one build script runs: those of its top level, and those of each function a
93
+ * call it runs calls or passes on, as a callee, an argument or a function called where it
94
+ * is written, a name read through to the function it declares.
95
+ */
96
+ function runCalls(file, each) {
97
+ const ran = new Set();
98
+ const run = (fn) => {
99
+ if (!ran.has(fn)) {
100
+ ran.add(fn);
101
+ fn.forEachChild(visit);
102
+ }
103
+ };
104
+ const runs = (node) => {
105
+ const at = unwrapped(node);
106
+ const declared = isIdentifier(at) ? declarationOf(at) : undefined;
107
+ const fn = declared !== undefined && isVariableDeclaration(declared) && declared.initializer
108
+ ? unwrapped(declared.initializer)
109
+ : (declared ?? at);
110
+ if (isFunction(fn)) {
111
+ run(fn);
112
+ }
113
+ };
114
+ const visit = (node) => {
115
+ if (isFunction(node)) {
116
+ return;
117
+ }
118
+ if (isCallExpression(node) || isNewExpression(node)) {
119
+ if (isCallExpression(node)) {
120
+ each(node);
121
+ }
122
+ runs(node.expression);
123
+ for (const argument of node.arguments ?? []) {
124
+ runs(argument);
125
+ }
126
+ }
127
+ node.forEachChild(visit);
128
+ };
129
+ file.forEachChild(visit);
130
+ }
131
+ /** The entries every bundler call one build script runs names, read against `dir`. */
132
+ function scriptEntries(file, dir, files) {
133
+ const module = staticModule(file);
134
+ const building = new Map();
135
+ for (const api of BUNDLERS) {
136
+ for (const one of module.calls(api.module)) {
137
+ if (api.calls.includes(one.export)) {
138
+ building.set(one.call, api);
139
+ }
140
+ }
141
+ }
142
+ const scriptDir = dirnamePath(file.fileName);
143
+ const found = [];
144
+ runCalls(file, (call) => {
145
+ const api = building.get(call);
146
+ if (api === undefined) {
147
+ return;
148
+ }
149
+ for (const options of module.argument(call)) {
150
+ const entries = lastMember(options, api.option);
151
+ const base = api.base === "" ? undefined : lastMember(options, api.base);
152
+ const against = base === undefined ? { path: dir } : pathOf(base, dir, scriptDir);
153
+ if (entries === undefined || against === undefined) {
154
+ continue;
155
+ }
156
+ for (const value of entryValues(entries)) {
157
+ const named = pathOf(value, against.path, scriptDir);
158
+ if (named === undefined) {
159
+ continue;
160
+ }
161
+ for (const entry of entriesNamed(files, api, named)) {
162
+ found.push({ file: entry, source: named.text });
163
+ }
164
+ }
165
+ }
166
+ });
167
+ return found;
168
+ }
169
+ /** The modules one manifest's command strings run or name, by absolute path, each below the target root. */
170
+ function scriptModules(host, root, dir) {
171
+ const manifest = manifestAt(host, dir);
172
+ const found = new Set();
173
+ for (const { command } of manifest === undefined ? [] : manifestCommands(host, dir, manifest)) {
174
+ for (const { path } of pathsNamed(readCommands(command, dir))) {
175
+ if (extensionOf(path, MODULE_EXTENSIONS) !== undefined &&
176
+ relativePath(root, path) !== undefined &&
177
+ !inInstalledPackages(path) &&
178
+ host.kindOf(path) === "file") {
179
+ found.add(path);
180
+ }
181
+ }
182
+ }
183
+ return [...found].sort(bytewise);
184
+ }
185
+ /**
186
+ * Every own file of one project a build script names as a bundler's entry: a module a
187
+ * script of a manifest at or above an own file runs, below the target root, read from
188
+ * the program where it holds the module and parsed alone otherwise, where a parser is given.
189
+ */
190
+ export function buildScriptEntries(host, targetRoot, files, parse) {
191
+ const root = resolvePath(host.workingDirectory(), targetRoot);
192
+ const dirs = new Set([root]);
193
+ for (const file of files.byName.values()) {
194
+ for (const dir of ancestors(dirnamePath(file.fileName), root)) {
195
+ if (dirs.has(dir)) {
196
+ break;
197
+ }
198
+ dirs.add(dir);
199
+ }
200
+ }
201
+ const found = [];
202
+ for (const dir of [...dirs].sort(bytewise)) {
203
+ if (!holdsManifest(host, dir)) {
204
+ continue;
205
+ }
206
+ for (const path of scriptModules(host, root, dir)) {
207
+ let script = files.byName.get(path);
208
+ if (script === undefined && parse !== undefined) {
209
+ try {
210
+ script = parse(path, host.readFile(path));
211
+ }
212
+ catch {
213
+ continue;
214
+ }
215
+ }
216
+ if (script !== undefined) {
217
+ found.push(...scriptEntries(script, dir, files));
218
+ }
219
+ }
220
+ }
221
+ return found;
222
+ }