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

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 (149) hide show
  1. package/dist/src/alias-chain.js +2 -2
  2. package/dist/src/analysis.js +43 -38
  3. package/dist/src/commands/read.js +6 -7
  4. package/dist/src/conformance-record.js +1 -1
  5. package/dist/src/consumers.js +4 -2
  6. package/dist/src/contract/config.generated.js +1 -1
  7. package/dist/src/contract/contract.generated.js +120 -2
  8. package/dist/src/dependencies.js +81 -49
  9. package/dist/src/entries/build-scripts.js +222 -0
  10. package/dist/src/{configuration-files.js → entries/configuration-files.js} +20 -58
  11. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  12. package/dist/src/entries/conventions.js +739 -0
  13. package/dist/src/{html-entries.js → entries/html-entries.js} +3 -3
  14. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +2 -2
  15. package/dist/src/entries/run-directories.js +77 -0
  16. package/dist/src/entries/sources.js +144 -0
  17. package/dist/src/entries/static-module.js +669 -0
  18. package/dist/src/entries/test-runners.js +684 -0
  19. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  20. package/dist/src/extensions.js +37 -0
  21. package/dist/src/facts.js +38 -0
  22. package/dist/src/file-facts.js +1 -1
  23. package/dist/src/findings/emitters.js +5 -8
  24. package/dist/src/findings/self-check.js +36 -20
  25. package/dist/src/findings-pass.js +5 -6
  26. package/dist/src/framework-lifecycle.js +1 -1
  27. package/dist/src/fs/manifest.js +15 -20
  28. package/dist/src/generated-file.js +172 -29
  29. package/dist/src/graph.js +2 -5
  30. package/dist/src/index.js +1 -1
  31. package/dist/src/injection-container.js +1 -1
  32. package/dist/src/inventory/commonjs.js +507 -0
  33. package/dist/src/inventory/enumerate.js +167 -0
  34. package/dist/src/inventory/syntax.js +236 -0
  35. package/dist/src/inventory.js +305 -705
  36. package/dist/src/javascript-file.js +26 -11
  37. package/dist/src/load/classify.js +28 -9
  38. package/dist/src/load/diagnostics.js +8 -1
  39. package/dist/src/load/discover.js +16 -8
  40. package/dist/src/load/package-scope.js +56 -0
  41. package/dist/src/load/plan.js +2 -2
  42. package/dist/src/load/synthesize.js +5 -5
  43. package/dist/src/load/workspace-manifests.js +1 -1
  44. package/dist/src/matrix.js +6 -13
  45. package/dist/src/outside-tests.js +1 -1
  46. package/dist/src/paths.js +16 -0
  47. package/dist/src/platform-calls.js +78 -0
  48. package/dist/src/platform-symbols.js +140 -0
  49. package/dist/src/position.js +41 -0
  50. package/dist/src/ref.js +11 -20
  51. package/dist/src/references.js +281 -50
  52. package/dist/src/resolve.js +1 -1
  53. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +4 -4
  54. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  55. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  56. package/dist/src/roots/published-reach.js +412 -0
  57. package/dist/src/roots/root-set.js +489 -0
  58. package/dist/src/roots/unseen-loads.js +208 -0
  59. package/dist/src/serialization-contract.js +1 -1
  60. package/dist/src/session.js +3 -0
  61. package/dist/src/source-files.js +5 -9
  62. package/dist/src/suppress-inline.js +33 -81
  63. package/dist/src/suppress.js +30 -32
  64. package/dist/src/test-support.js +4 -2
  65. package/dist/src/verbs/analyze.js +13 -1
  66. package/dist/src/verbs/describe.js +5 -3
  67. package/jsr.json +1 -1
  68. package/package.json +3 -3
  69. package/src/alias-chain.ts +2 -2
  70. package/src/analysis.ts +77 -53
  71. package/src/automatic-types.ts +1 -1
  72. package/src/commands/read.ts +6 -8
  73. package/src/conformance-record.ts +1 -1
  74. package/src/consumers.ts +5 -1
  75. package/src/contract/config.generated.ts +1 -1
  76. package/src/contract/contract.generated.ts +139 -2
  77. package/src/dependencies.ts +98 -50
  78. package/src/entries/build-scripts.ts +292 -0
  79. package/src/{configuration-files.ts → entries/configuration-files.ts} +23 -75
  80. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  81. package/src/{conventions.ts → entries/conventions.ts} +347 -522
  82. package/src/{html-entries.ts → entries/html-entries.ts} +5 -5
  83. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +4 -4
  84. package/src/entries/run-directories.ts +108 -0
  85. package/src/entries/sources.ts +266 -0
  86. package/src/entries/static-module.ts +857 -0
  87. package/src/entries/test-runners.ts +868 -0
  88. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  89. package/src/exempt.ts +3 -2
  90. package/src/extensions.ts +45 -0
  91. package/src/facts.ts +44 -0
  92. package/src/file-facts.ts +2 -2
  93. package/src/findings/emitters.ts +14 -10
  94. package/src/findings/self-check.ts +41 -22
  95. package/src/findings/visibility-narrowing.ts +1 -1
  96. package/src/findings-pass.ts +5 -12
  97. package/src/framework-lifecycle.ts +1 -1
  98. package/src/fs/manifest.ts +25 -21
  99. package/src/generated-file.ts +240 -36
  100. package/src/graph.ts +3 -6
  101. package/src/index.ts +1 -1
  102. package/src/injection-container.ts +1 -1
  103. package/src/inventory/commonjs.ts +696 -0
  104. package/src/inventory/enumerate.ts +203 -0
  105. package/src/inventory/syntax.ts +311 -0
  106. package/src/inventory.ts +424 -878
  107. package/src/javascript-file.ts +29 -12
  108. package/src/load/classify.ts +49 -13
  109. package/src/load/diagnostics.ts +11 -1
  110. package/src/load/discover.ts +28 -12
  111. package/src/load/package-scope.ts +74 -0
  112. package/src/load/plan.ts +11 -2
  113. package/src/load/synthesize.ts +6 -6
  114. package/src/load/test-globals.ts +1 -1
  115. package/src/load/workspace-manifests.ts +1 -1
  116. package/src/load/workspace-run.ts +1 -1
  117. package/src/matrix.ts +7 -14
  118. package/src/outside-tests.ts +1 -1
  119. package/src/paths.ts +18 -0
  120. package/src/platform-calls.ts +119 -0
  121. package/src/platform-symbols.ts +205 -0
  122. package/src/position.ts +42 -0
  123. package/src/ref.ts +11 -20
  124. package/src/references.ts +383 -73
  125. package/src/report.ts +1 -1
  126. package/src/resolve.ts +1 -1
  127. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +7 -7
  128. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  129. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  130. package/src/roots/published-reach.ts +481 -0
  131. package/src/{roots.ts → roots/root-set.ts} +203 -630
  132. package/src/roots/unseen-loads.ts +307 -0
  133. package/src/serialization-contract.ts +1 -1
  134. package/src/session.ts +17 -1
  135. package/src/source-files.ts +5 -10
  136. package/src/suppress-inline.ts +53 -89
  137. package/src/suppress.ts +48 -33
  138. package/src/test-support.ts +8 -3
  139. package/src/verbs/analyze.ts +17 -4
  140. package/src/verbs/describe.ts +5 -2
  141. package/src/verbs/explain.ts +1 -1
  142. package/dist/src/build-scripts.js +0 -418
  143. package/dist/src/conventions.js +0 -907
  144. package/dist/src/entry-points.js +0 -513
  145. package/dist/src/roots.js +0 -865
  146. package/dist/src/unseen-loads.js +0 -100
  147. package/src/build-scripts.ts +0 -536
  148. package/src/entry-points.ts +0 -661
  149. package/src/unseen-loads.ts +0 -134
@@ -0,0 +1,669 @@
1
+ /**
2
+ * What a module or a configuration document writes out, read without running it, as one
3
+ * value model every entry source reads: a default export in each of its forms, the options
4
+ * a call of one module's export passes, and the top-level values names stand for. A value
5
+ * the text does not state is opaque, so no reader guesses it.
6
+ */
7
+ import { isArrayLiteralExpression, isBindingElement, isExternalModuleReference, isImportClause, isImportEqualsDeclaration, isImportSpecifier, isStringLiteralLikeNode, isCallExpression, isClassDeclaration, isClassExpression, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isFunctionLikeDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableDeclaration, isVariableStatement, isSourceFile, isBlock, isModuleBlock, isCaseBlock, isCatchClause, isForStatement, isForInStatement, isForOfStatement, isVariableDeclarationList, isMetaProperty, isAssignmentOperator, isBinaryExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isShorthandPropertyAssignment, isSpreadElement, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
8
+ import { commonJSModuleValue } from "../inventory/commonjs.js";
9
+ import { callsPlatformBy, lexicalBinding, writtenName } from "../platform-calls.js";
10
+ import { unwrapped } from "../unwrap.js";
11
+ /** A JSON or YAML document's value, which carries no positions. */
12
+ export function documentValue(value) {
13
+ if (typeof value === "string") {
14
+ return { kind: "text", text: value, at: undefined };
15
+ }
16
+ if (typeof value === "boolean") {
17
+ return { kind: "boolean", value, at: undefined };
18
+ }
19
+ if (Array.isArray(value)) {
20
+ return { kind: "array", elements: value.map(documentValue), at: undefined };
21
+ }
22
+ if (typeof value === "object" && value !== null) {
23
+ return {
24
+ kind: "object",
25
+ members: Object.entries(value).map(([key, held]) => ({
26
+ key,
27
+ value: documentValue(held),
28
+ at: undefined,
29
+ })),
30
+ at: undefined,
31
+ };
32
+ }
33
+ return { kind: "opaque", at: undefined };
34
+ }
35
+ /**
36
+ * The node that declares a name through one binding: the holder itself for a name, or the
37
+ * binding element of an object or array pattern that binds it.
38
+ */
39
+ function boundAt(holder, name, text) {
40
+ if (name === undefined) {
41
+ return undefined;
42
+ }
43
+ if (isIdentifier(name)) {
44
+ return name.text === text ? holder : undefined;
45
+ }
46
+ let found;
47
+ name.forEachChild((element) => {
48
+ found ??= boundAt(element, element.name, text);
49
+ });
50
+ return found;
51
+ }
52
+ /** The declaration of a name one statement makes in the block it is written in. */
53
+ function blockDeclaration(statement, name) {
54
+ if ((isFunctionDeclaration(statement) || isClassDeclaration(statement)) &&
55
+ statement.name?.text === name) {
56
+ return statement;
57
+ }
58
+ if (isVariableStatement(statement) &&
59
+ (statement.declarationList.flags & NodeFlags.BlockScoped) !== 0) {
60
+ return statement.declarationList.declarations
61
+ .map((one) => boundAt(one, one.name, name))
62
+ .find((one) => one !== undefined);
63
+ }
64
+ if (isImportEqualsDeclaration(statement)) {
65
+ return statement.name.text === name ? statement : undefined;
66
+ }
67
+ if (isImportDeclaration(statement)) {
68
+ const clause = statement.importClause;
69
+ if (clause?.name?.text === name) {
70
+ return clause;
71
+ }
72
+ const bindings = clause?.namedBindings;
73
+ if (bindings !== undefined && isNamespaceImport(bindings)) {
74
+ return bindings.name.text === name ? bindings : undefined;
75
+ }
76
+ return bindings?.elements.find((one) => one.name.text === name);
77
+ }
78
+ return undefined;
79
+ }
80
+ /** The `var` declaration of a name anywhere below one scope, outside every function nested in it. */
81
+ function varDeclaration(scope, name) {
82
+ let found;
83
+ const visit = (node) => {
84
+ if (found !== undefined || isFunctionLikeDeclaration(node) || isClassLike(node)) {
85
+ return;
86
+ }
87
+ if (isVariableDeclaration(node) && (node.parent.flags & NodeFlags.BlockScoped) === 0) {
88
+ found = boundAt(node, node.name, name);
89
+ if (found !== undefined) {
90
+ return;
91
+ }
92
+ }
93
+ node.forEachChild(visit);
94
+ };
95
+ scope.forEachChild(visit);
96
+ return found;
97
+ }
98
+ function isClassLike(node) {
99
+ return isClassDeclaration(node) || isClassExpression(node);
100
+ }
101
+ /** The statements a scope holds directly, for a block, a case block or a file. */
102
+ function statementsOf(scope) {
103
+ if (isSourceFile(scope) || isBlock(scope) || isModuleBlock(scope)) {
104
+ return scope.statements;
105
+ }
106
+ if (isCaseBlock(scope)) {
107
+ return scope.clauses.flatMap((clause) => [...clause.statements]);
108
+ }
109
+ return [];
110
+ }
111
+ /** The declaration one scope makes of a name, ECMAScript's lexical and variable environments read from the syntax. */
112
+ function scopeDeclaration(scope, name) {
113
+ if (isFunctionLikeDeclaration(scope)) {
114
+ const own = (isFunctionExpression(scope) || isFunctionDeclaration(scope)) && scope.name?.text === name
115
+ ? isFunctionExpression(scope)
116
+ ? scope
117
+ : undefined
118
+ : undefined;
119
+ const parameter = scope.parameters
120
+ .map((one) => boundAt(one, one.name, name))
121
+ .find((one) => one !== undefined);
122
+ const body = scope.body;
123
+ return (parameter ??
124
+ (body === undefined
125
+ ? undefined
126
+ : (statementsOf(body)
127
+ .map((one) => blockDeclaration(one, name))
128
+ .find(Boolean) ?? varDeclaration(body, name))) ??
129
+ own);
130
+ }
131
+ if (isClassExpression(scope) && scope.name?.text === name) {
132
+ return scope;
133
+ }
134
+ if (isCatchClause(scope)) {
135
+ const caught = scope.variableDeclaration;
136
+ return caught === undefined ? undefined : boundAt(caught, caught.name, name);
137
+ }
138
+ if (isForStatement(scope) || isForInStatement(scope) || isForOfStatement(scope)) {
139
+ const list = scope.initializer;
140
+ return list !== undefined &&
141
+ isVariableDeclarationList(list) &&
142
+ (list.flags & NodeFlags.BlockScoped) !== 0
143
+ ? list.declarations
144
+ .map((one) => boundAt(one, one.name, name))
145
+ .find((one) => one !== undefined)
146
+ : undefined;
147
+ }
148
+ const declared = statementsOf(scope)
149
+ .map((one) => blockDeclaration(one, name))
150
+ .find(Boolean);
151
+ return declared ?? (isSourceFile(scope) ? varDeclaration(scope, name) : undefined);
152
+ }
153
+ /**
154
+ * The declaration an identifier written in a module resolves to by the scopes that enclose
155
+ * it, nearest first, or undefined where none of them, the file included, declares it, as
156
+ * for a global. A module read outside every program has no checker to ask.
157
+ */
158
+ export function declarationOf(occurrence) {
159
+ for (let scope = occurrence.parent;; scope = scope.parent) {
160
+ const found = scopeDeclaration(scope, occurrence.text);
161
+ if (found !== undefined || isSourceFile(scope)) {
162
+ return found;
163
+ }
164
+ }
165
+ }
166
+ /** The text a property name writes: a name, a literal, or a computed literal; undefined where it computes one. */
167
+ function keyOf(name) {
168
+ if (isIdentifier(name) ||
169
+ isStringLiteral(name) ||
170
+ isNoSubstitutionTemplateLiteral(name) ||
171
+ isNumericLiteral(name)) {
172
+ return name.text;
173
+ }
174
+ if (isComputedPropertyName(name)) {
175
+ const key = unwrapped(name.expression);
176
+ if (isStringLiteral(key) || isNoSubstitutionTemplateLiteral(key) || isNumericLiteral(key)) {
177
+ return key.text;
178
+ }
179
+ }
180
+ return undefined;
181
+ }
182
+ /** Whether one expression is the module's own directory: a free `__dirname`, or `import.meta.dirname`. */
183
+ function isOwnDirectory(node) {
184
+ const held = unwrapped(node);
185
+ if (isIdentifier(held)) {
186
+ return held.text === "__dirname" && declarationOf(held) === undefined;
187
+ }
188
+ return (isPropertyAccessExpression(held) &&
189
+ isMetaProperty(held.expression) &&
190
+ held.expression.keywordToken === SyntaxKind.ImportKeyword &&
191
+ held.name.text === "dirname");
192
+ }
193
+ /** The literal segments a `resolve` or `join` call of the module's own directory writes, or undefined. */
194
+ function directorySegments(node) {
195
+ if (!isCallExpression(node)) {
196
+ return undefined;
197
+ }
198
+ const callee = unwrapped(node.expression);
199
+ const name = isPropertyAccessExpression(callee) ? callee.name : callee;
200
+ const [first, ...rest] = node.arguments;
201
+ if (!isIdentifier(name) ||
202
+ (name.text !== "resolve" && name.text !== "join") ||
203
+ first === undefined ||
204
+ !isOwnDirectory(first)) {
205
+ return undefined;
206
+ }
207
+ const segments = rest.map((one) => {
208
+ const part = unwrapped(one);
209
+ return isStringLiteral(part) || isNoSubstitutionTemplateLiteral(part) ? part.text : undefined;
210
+ });
211
+ return segments.every((one) => one !== undefined) ? segments : undefined;
212
+ }
213
+ /** The value one expression writes out, parentheses, `as`, `satisfies` and `!` read through. */
214
+ export function writtenValue(node) {
215
+ const held = unwrapped(node);
216
+ const at = held.getStart();
217
+ const segments = directorySegments(held);
218
+ if (segments !== undefined) {
219
+ return { kind: "directory", segments, at };
220
+ }
221
+ if (isStringLiteral(held) || isNoSubstitutionTemplateLiteral(held)) {
222
+ return { kind: "text", text: held.text, at };
223
+ }
224
+ if (held.kind === SyntaxKind.TrueKeyword || held.kind === SyntaxKind.FalseKeyword) {
225
+ return { kind: "boolean", value: held.kind === SyntaxKind.TrueKeyword, at };
226
+ }
227
+ if (isArrayLiteralExpression(held)) {
228
+ return {
229
+ kind: "array",
230
+ elements: held.elements.map((element) => element.kind === SyntaxKind.SpreadElement
231
+ ? { kind: "opaque", at: element.getStart() }
232
+ : writtenValue(element)),
233
+ at,
234
+ };
235
+ }
236
+ if (isObjectLiteralExpression(held)) {
237
+ return {
238
+ kind: "object",
239
+ members: held.properties.map((member) => {
240
+ const start = member.getStart();
241
+ if (isSpreadAssignment(member)) {
242
+ return { key: undefined, value: { kind: "opaque", at: start }, at: start };
243
+ }
244
+ const key = keyOf(member.name);
245
+ return {
246
+ key,
247
+ value: isPropertyAssignment(member)
248
+ ? writtenValue(member.initializer)
249
+ : { kind: "opaque", at: start },
250
+ at: start,
251
+ };
252
+ }),
253
+ at,
254
+ };
255
+ }
256
+ return { kind: "opaque", at };
257
+ }
258
+ /**
259
+ * The initializer of a top-level `const`, `let` or `var` declaration of the file that
260
+ * declares a name and that the file assigns nowhere else, so the initializer is the value
261
+ * the name holds.
262
+ */
263
+ function topLevelValue(declaration, file) {
264
+ if (declaration === undefined ||
265
+ !isVariableDeclaration(declaration) ||
266
+ !isIdentifier(declaration.name) ||
267
+ declaration.initializer === undefined) {
268
+ return undefined;
269
+ }
270
+ const statement = declaration.parent.parent;
271
+ if (!isVariableStatement(statement) || statement.parent !== file) {
272
+ return undefined;
273
+ }
274
+ const constant = (declaration.parent.flags & NodeFlags.Const) !== 0;
275
+ return constant || !assigned(file, declaration, declaration.name.text)
276
+ ? declaration.initializer
277
+ : undefined;
278
+ }
279
+ /** Whether the file writes a name that resolves to one declaration anywhere but its initializer. */
280
+ function assigned(file, declaration, name) {
281
+ let found = false;
282
+ const visit = (node) => {
283
+ if (found) {
284
+ return;
285
+ }
286
+ if (isIdentifier(node)) {
287
+ found = node.text === name && isWriteTarget(node) && declarationOf(node) === declaration;
288
+ return;
289
+ }
290
+ node.forEachChild(visit);
291
+ };
292
+ file.forEachChild(visit);
293
+ return found;
294
+ }
295
+ /**
296
+ * Whether one identifier is written: the target of an assignment, a destructuring
297
+ * assignment's element included, the operand of `++` or `--`, or a `for`-`in` or
298
+ * `for`-`of` target.
299
+ */
300
+ function isWriteTarget(node) {
301
+ let at = node;
302
+ for (let parent = at.parent;; at = parent, parent = parent.parent) {
303
+ if (isParenthesizedExpression(parent) || isSpreadElement(parent)) {
304
+ continue;
305
+ }
306
+ if (isShorthandPropertyAssignment(parent) && parent.name === at) {
307
+ continue;
308
+ }
309
+ if ((isPropertyAssignment(parent) && parent.initializer === at) ||
310
+ isArrayLiteralExpression(parent) ||
311
+ isObjectLiteralExpression(parent)) {
312
+ continue;
313
+ }
314
+ break;
315
+ }
316
+ const parent = at.parent;
317
+ if (isBinaryExpression(parent)) {
318
+ return parent.left === at && isAssignmentOperator(parent.operatorToken.kind);
319
+ }
320
+ if (isPrefixUnaryExpression(parent) || isPostfixUnaryExpression(parent)) {
321
+ return (at === node &&
322
+ (parent.operator === SyntaxKind.PlusPlusToken ||
323
+ parent.operator === SyntaxKind.MinusMinusToken));
324
+ }
325
+ return (isForInStatement(parent) || isForOfStatement(parent)) && parent.initializer === at;
326
+ }
327
+ /**
328
+ * The values one expression stands for: a name of a top-level binding read through to its
329
+ * initializer, and, where `throughCalls`, a call read through to every argument it passes.
330
+ * A `require` call's argument names a module, which is no value the call returns.
331
+ */
332
+ function readThrough(node, file, throughCalls, seen = new Set()) {
333
+ const held = unwrapped(node);
334
+ if (isIdentifier(held)) {
335
+ const value = topLevelValue(declarationOf(held), file);
336
+ if (value !== undefined && !seen.has(value)) {
337
+ return readThrough(value, file, throughCalls, new Set([...seen, value]));
338
+ }
339
+ }
340
+ else if (throughCalls && isCallExpression(held) && !isRequireCall(held)) {
341
+ return held.arguments.flatMap((argument) => readThrough(argument, file, throughCalls, seen));
342
+ }
343
+ return [writtenValue(held)];
344
+ }
345
+ /** A written name's binding by the scopes that enclose it, {@link declarationOf} answering. */
346
+ const scopeBinding = lexicalBinding(declarationOf);
347
+ /** Whether one call is a `require()` call, read through `bindingOf`, the file's own scopes where no checker answers. */
348
+ function isRequireCall(call, bindingOf = scopeBinding) {
349
+ const callee = writtenName(call.expression);
350
+ return callee !== undefined && callsPlatformBy("require", callee, bindingOf);
351
+ }
352
+ /** The literal one call's one argument writes, where it is a string literal or a template literal with no substitution. */
353
+ function loadedSpecifier(call) {
354
+ const [argument, ...rest] = call.arguments;
355
+ return argument !== undefined &&
356
+ rest.length === 0 &&
357
+ (isStringLiteral(argument) || isNoSubstitutionTemplateLiteral(argument))
358
+ ? argument.text
359
+ : undefined;
360
+ }
361
+ /** Whether one expression is a `require()` call of a module `isModule` accepts, its specifier the one literal argument. */
362
+ function requires(node, isModule, bindingOf = scopeBinding) {
363
+ const held = unwrapped(node);
364
+ if (!isCallExpression(held)) {
365
+ return false;
366
+ }
367
+ const specifier = loadedSpecifier(held);
368
+ return specifier !== undefined && isModule(specifier) && isRequireCall(held, bindingOf);
369
+ }
370
+ /** What {@link boundExports} lists for a name bound to the module itself. */
371
+ export const NAMESPACE = "*";
372
+ /** Whether one import or re-export names a module `isModule` accepts. */
373
+ function fromModule(declaration, isModule) {
374
+ return (isImportDeclaration(declaration) &&
375
+ isStringLiteral(declaration.moduleSpecifier) &&
376
+ isModule(declaration.moduleSpecifier.text));
377
+ }
378
+ /**
379
+ * What one declaration binds of a module `isModule` accepts: the export each name names,
380
+ * `default` for the module's value, and {@link NAMESPACE} for the module itself. A default
381
+ * import, an import assignment and a name a `require()` call initializes bind the value and
382
+ * the module both, as interop reads them; an object binding pattern of such a call binds each
383
+ * export it lists, and a member read of one binds that member.
384
+ */
385
+ export function boundExports(declaration, isModule, bindingOf = scopeBinding) {
386
+ if (isImportClause(declaration)) {
387
+ return declaration.name !== undefined && fromModule(declaration.parent, isModule)
388
+ ? ["default", NAMESPACE]
389
+ : [];
390
+ }
391
+ if (isNamespaceImport(declaration)) {
392
+ return fromModule(declaration.parent.parent, isModule) ? [NAMESPACE] : [];
393
+ }
394
+ if (isImportSpecifier(declaration)) {
395
+ return fromModule(declaration.parent.parent.parent, isModule)
396
+ ? [(declaration.propertyName ?? declaration.name).text]
397
+ : [];
398
+ }
399
+ if (isImportEqualsDeclaration(declaration)) {
400
+ const reference = declaration.moduleReference;
401
+ return isExternalModuleReference(reference) &&
402
+ isStringLiteralLikeNode(reference.expression) &&
403
+ isModule(reference.expression.text)
404
+ ? ["default", NAMESPACE]
405
+ : [];
406
+ }
407
+ if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) {
408
+ const holder = declaration.parent.parent;
409
+ const exported = declaration.propertyName ?? declaration.name;
410
+ const key = exported === undefined ? undefined : keyOf(exported);
411
+ return declaration.dotDotDotToken === undefined &&
412
+ declaration.name !== undefined &&
413
+ isIdentifier(declaration.name) &&
414
+ key !== undefined &&
415
+ isVariableDeclaration(holder) &&
416
+ holder.initializer !== undefined &&
417
+ requires(holder.initializer, isModule, bindingOf)
418
+ ? [key]
419
+ : [];
420
+ }
421
+ if (!isVariableDeclaration(declaration) || declaration.initializer === undefined) {
422
+ return [];
423
+ }
424
+ const value = unwrapped(declaration.initializer);
425
+ if (isIdentifier(declaration.name) && requires(value, isModule, bindingOf)) {
426
+ return ["default", NAMESPACE];
427
+ }
428
+ return isIdentifier(declaration.name) &&
429
+ isPropertyAccessExpression(value) &&
430
+ requires(value.expression, isModule, bindingOf)
431
+ ? [value.name.text]
432
+ : [];
433
+ }
434
+ /**
435
+ * The relative specifiers one module's imports and re-exports name, and those its
436
+ * `require()` calls and `import()` calls name by a literal, in the order the file writes
437
+ * them. A module read outside every program answers it so, with no checker to ask.
438
+ */
439
+ export function relativeSpecifiers(file) {
440
+ const known = specifiersOf.get(file);
441
+ if (known !== undefined) {
442
+ return known;
443
+ }
444
+ const found = [];
445
+ const named = (specifier) => {
446
+ if (specifier?.startsWith("./") === true || specifier?.startsWith("../") === true) {
447
+ found.push(specifier);
448
+ }
449
+ };
450
+ const visit = (node) => {
451
+ if ((isImportDeclaration(node) || isExportDeclaration(node)) &&
452
+ node.moduleSpecifier !== undefined &&
453
+ isStringLiteral(node.moduleSpecifier)) {
454
+ named(node.moduleSpecifier.text);
455
+ }
456
+ else if (isCallExpression(node) &&
457
+ (node.expression.kind === SyntaxKind.ImportKeyword || isRequireCall(node))) {
458
+ named(loadedSpecifier(node));
459
+ }
460
+ node.forEachChild(visit);
461
+ };
462
+ file.forEachChild(visit);
463
+ specifiersOf.set(file, found);
464
+ return found;
465
+ }
466
+ /** What {@link relativeSpecifiers} found in each file, which every project of a run reads alike. */
467
+ const specifiersOf = new WeakMap();
468
+ /** The declarations one top-level statement makes that may bind another module. */
469
+ function bindingDeclarations(statement) {
470
+ if (isImportDeclaration(statement)) {
471
+ const clause = statement.importClause;
472
+ const bindings = clause?.namedBindings;
473
+ return [
474
+ ...(clause === undefined ? [] : [clause]),
475
+ ...(bindings === undefined
476
+ ? []
477
+ : isNamespaceImport(bindings)
478
+ ? [bindings]
479
+ : bindings.elements),
480
+ ];
481
+ }
482
+ if (isImportEqualsDeclaration(statement)) {
483
+ return [statement];
484
+ }
485
+ if (!isVariableStatement(statement)) {
486
+ return [];
487
+ }
488
+ return statement.declarationList.declarations.flatMap((declaration) => isObjectBindingPattern(declaration.name)
489
+ ? [declaration, ...declaration.name.elements]
490
+ : [declaration]);
491
+ }
492
+ /** The top-level bindings one file makes of a module, as {@link boundExports} reads each. */
493
+ function bindingsOf(file, module) {
494
+ const named = new Map();
495
+ const namespaces = new Set();
496
+ const isModule = (specifier) => specifier === module;
497
+ for (const declaration of file.statements.flatMap(bindingDeclarations)) {
498
+ for (const exported of boundExports(declaration, isModule)) {
499
+ if (exported === NAMESPACE) {
500
+ namespaces.add(declaration);
501
+ }
502
+ else {
503
+ named.set(declaration, exported);
504
+ }
505
+ }
506
+ }
507
+ return { named, namespaces };
508
+ }
509
+ /**
510
+ * Every call one file makes of one module's exports: through a name an import or a
511
+ * `require` binding gives it, a namespace's member, a member of a `require` of the
512
+ * module, or that `require`'s value called itself, each where no scope enclosing the
513
+ * call declares the name otherwise.
514
+ */
515
+ function callsOf(file, module) {
516
+ const { named, namespaces } = bindingsOf(file, module);
517
+ const found = [];
518
+ const visit = (node) => {
519
+ if (isCallExpression(node)) {
520
+ const callee = unwrapped(node.expression);
521
+ if (isIdentifier(callee)) {
522
+ const declaration = declarationOf(callee);
523
+ const exported = declaration === undefined ? undefined : named.get(declaration);
524
+ if (exported !== undefined) {
525
+ found.push({ call: node, export: exported });
526
+ }
527
+ }
528
+ else if (requires(callee, (specifier) => specifier === module)) {
529
+ found.push({ call: node, export: "default" });
530
+ }
531
+ else if (isPropertyAccessExpression(callee)) {
532
+ const holder = unwrapped(callee.expression);
533
+ const declaration = isIdentifier(holder) ? declarationOf(holder) : undefined;
534
+ if ((declaration !== undefined && namespaces.has(declaration)) ||
535
+ requires(holder, (specifier) => specifier === module)) {
536
+ found.push({ call: node, export: callee.name.text });
537
+ }
538
+ }
539
+ }
540
+ node.forEachChild(visit);
541
+ };
542
+ file.forEachChild(visit);
543
+ return found;
544
+ }
545
+ /** One parsed module, or a JSON document parsed as one, read as {@link StaticModule}. */
546
+ export function staticModule(file) {
547
+ const argument = (call) => {
548
+ const [first] = call.arguments;
549
+ return first === undefined ? [] : readThrough(first, file, false);
550
+ };
551
+ return {
552
+ defaultExport: defaultExportOf(file),
553
+ options: (module, exported) => callsOf(file, module).flatMap((one) => (one.export === exported ? argument(one.call) : [])),
554
+ calls: (module) => callsOf(file, module),
555
+ argument,
556
+ };
557
+ }
558
+ /** A YAML document's value read as {@link StaticModule}: its default export, and no call. */
559
+ export function documentModule(value) {
560
+ return {
561
+ defaultExport: [documentValue(value)],
562
+ options: () => [],
563
+ calls: () => [],
564
+ argument: () => [],
565
+ };
566
+ }
567
+ /** The value a CommonJS module leaves, its later member writes and any hidden member on it. */
568
+ function moduleValueOf(written, file) {
569
+ const members = written.members.map((member) => {
570
+ const at = member.node.getStart();
571
+ return {
572
+ key: member.key,
573
+ value: member.value === undefined ? { kind: "opaque", at } : writtenValue(member.value),
574
+ at,
575
+ };
576
+ });
577
+ if (written.open) {
578
+ members.push({ key: undefined, value: { kind: "opaque", at: undefined }, at: undefined });
579
+ }
580
+ if (written.whole === undefined) {
581
+ return members.length === 0 ? [] : [{ kind: "object", members, at: members[0]?.at }];
582
+ }
583
+ return readThrough(written.whole, file, true).map((value) => value.kind === "object" ? { ...value, members: [...value.members, ...members] } : value);
584
+ }
585
+ /**
586
+ * The values a module's default export forms write: `export default`, `export =`, a
587
+ * `default` specifier, a JSON document, and, in a file Node runs as CommonJS, the module
588
+ * value as Node leaves it. A default function or class writes no value to read.
589
+ */
590
+ function defaultExportOf(file) {
591
+ if (file.fileName.endsWith(".json")) {
592
+ return file.statements.flatMap((statement) => isExpressionStatement(statement) ? [writtenValue(statement.expression)] : []);
593
+ }
594
+ const commonJS = commonJSModuleValue(file, declarationOf);
595
+ if (commonJS !== undefined) {
596
+ return moduleValueOf(commonJS, file);
597
+ }
598
+ const found = [];
599
+ for (const statement of file.statements) {
600
+ if (isExportAssignment(statement)) {
601
+ found.push(...readThrough(statement.expression, file, true));
602
+ }
603
+ else if ((isFunctionDeclaration(statement) || isClassDeclaration(statement)) &&
604
+ statement.modifiers?.some((one) => one.kind === SyntaxKind.DefaultKeyword) === true) {
605
+ found.push({ kind: "opaque", at: statement.getStart() });
606
+ }
607
+ else if (isExportDeclaration(statement) &&
608
+ statement.moduleSpecifier === undefined &&
609
+ statement.exportClause !== undefined &&
610
+ isNamedExports(statement.exportClause)) {
611
+ for (const element of statement.exportClause.elements) {
612
+ if (element.name.text === "default") {
613
+ found.push(...readThrough(element.propertyName ?? element.name, file, true));
614
+ }
615
+ }
616
+ }
617
+ }
618
+ return found;
619
+ }
620
+ /**
621
+ * The values one object writes for a key after its last spread or computed key, the last
622
+ * written last, or undefined where none follows one, which may write the key unseen. A
623
+ * value that is no object writes none.
624
+ */
625
+ export function membersNamed(value, key) {
626
+ if (value.kind !== "object") {
627
+ return [];
628
+ }
629
+ const hidden = value.members.findLastIndex((member) => member.key === undefined);
630
+ const after = value.members.slice(hidden + 1).filter((member) => member.key === key);
631
+ return hidden >= 0 && after.length === 0 ? undefined : after.map((member) => member.value);
632
+ }
633
+ /**
634
+ * The texts one value writes: itself where it is text, or, where `elements`, each element
635
+ * of an array of texts. Undefined for any other value.
636
+ */
637
+ export function textsOf(value, elements) {
638
+ if (value.kind === "text") {
639
+ return [value.text];
640
+ }
641
+ if (!elements || value.kind !== "array") {
642
+ return undefined;
643
+ }
644
+ const texts = value.elements.map((element) => element.kind === "text" ? element.text : undefined);
645
+ return texts.every((text) => text !== undefined) ? texts : undefined;
646
+ }
647
+ /**
648
+ * The values at one path of keys below each value, whatever else the path holds: a key
649
+ * reads an object's members of that name, and `*` every element of an array and every
650
+ * member of an object.
651
+ */
652
+ export function valuesAt(values, path) {
653
+ const [key, ...rest] = path;
654
+ if (key === undefined) {
655
+ return values;
656
+ }
657
+ const next = values.flatMap((value) => {
658
+ if (key === "*" && value.kind === "array") {
659
+ return value.elements;
660
+ }
661
+ if (value.kind !== "object") {
662
+ return [];
663
+ }
664
+ return value.members
665
+ .filter((member) => key === "*" || member.key === key)
666
+ .map((member) => member.value);
667
+ });
668
+ return valuesAt(next, rest);
669
+ }