@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
@@ -8,13 +8,16 @@
8
8
  */
9
9
  import { isArrayLiteralExpression, isCallExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isMetaProperty, isNamespaceExport, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isStringLiteral, isNoSubstitutionTemplateLiteral, SyntaxKind, } from "@typescript/native/ast";
10
10
  import { automaticTypes } from "./automatic-types.js";
11
- import { configurationFiles, declaredIn as declaredBeside, dependenciesNamed, moduleStrings, outsideChain, } from "./configuration-files.js";
12
- import { nodeKey, packageScope } from "./inventory.js";
11
+ import { declaredIn as declaredBeside, dependenciesNamed, moduleStrings, outsideChain, } from "./entries/configuration-files.js";
12
+ import { nodeKey } from "./inventory.js";
13
+ import { packageScope } from "./load/package-scope.js";
13
14
  import { ancestors, dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath, } from "./paths.js";
14
15
  import { UNANSWERED } from "./query.js";
15
16
  import { sourceFilesOf } from "./source-files.js";
16
- import { manifestCommands, nodemonTexts } from "./manifest.js";
17
- import { commandFileTexts, workflowCommands } from "./workflow-steps.js";
17
+ import { manifestCommands, nodemonTexts } from "./roots/manifest.js";
18
+ import { writtenName } from "./platform-calls.js";
19
+ import { callsPlatform } from "./platform-symbols.js";
20
+ import { parenthesesRemoved } from "./unwrap.js";
18
21
  import { pathsNamed, preloadedPackages, readCommands, } from "./commands/read.js";
19
22
  import { heldNames } from "./text/named-files.js";
20
23
  import { bytewise } from "./text/compare.js";
@@ -201,10 +204,11 @@ function bindingsOf(node) {
201
204
  * Every package one project needs, and which declaration each use is written in.
202
205
  * One local walk and one batched resolution per file: a use of an import binding is
203
206
  * an identifier resolving to the binding's own symbol, so a local declaration that
204
- * shadows the name is not one. A string of a configuration file uses each dependency
205
- * the manifest beside the file declares that it spells.
207
+ * shadows the name is not one. A string of a configuration file, as the entry sources
208
+ * read them in `configuration`, uses each dependency the manifest beside the file
209
+ * declares that it spells.
206
210
  */
207
- export function projectNeeds(project, held, host, targetRoot, parse) {
211
+ export function projectNeeds(project, held, host, targetRoot, parse, configuration, workflows) {
208
212
  const programPackages = new Set();
209
213
  for (const name of project.program.getSourceFileNames()) {
210
214
  const named = packageOfPath(name);
@@ -255,7 +259,6 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
255
259
  return names;
256
260
  };
257
261
  const files = sourceFilesOf(project, targetRoot);
258
- const configuration = configurationFiles(host, targetRoot, files, parse);
259
262
  for (const { file, manifestDir } of configuration.modules) {
260
263
  const names = declaredAt(joinPath(manifestDir, MANIFEST));
261
264
  for (const { node, text } of moduleStrings(file)) {
@@ -270,7 +273,7 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
270
273
  }
271
274
  }
272
275
  }
273
- const ranFiles = outsideChain(host, targetRoot, files, parse, scriptRunFiles(host, targetRoot, files));
276
+ const ranFiles = outsideChain(host, targetRoot, files, parse, scriptRunFiles(host, targetRoot, files, workflows));
274
277
  for (const { file, manifestDirs } of [...configuration.outside, ...ranFiles]) {
275
278
  for (const manifestDir of manifestDirs) {
276
279
  const names = declaredAt(joinPath(manifestDir, MANIFEST));
@@ -287,7 +290,7 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
287
290
  * workflow step or git hook names, with the target root's: a path `pathsNamed` reads, below the
288
291
  * target root and in no `node_modules`.
289
292
  */
290
- function scriptRunFiles(host, targetRoot, files) {
293
+ function scriptRunFiles(host, targetRoot, files, workflows) {
291
294
  const root = resolvePath(host.workingDirectory(), targetRoot);
292
295
  const dirs = new Set([root]);
293
296
  for (const name of files.byName.keys()) {
@@ -299,12 +302,7 @@ function scriptRunFiles(host, targetRoot, files) {
299
302
  }
300
303
  }
301
304
  const named = new Map([
302
- [
303
- root,
304
- workflowReadings(host, root)
305
- .flatMap(pathsNamed)
306
- .map((one) => one.path),
307
- ],
305
+ [root, workflows.readings.flatMap(pathsNamed).map((one) => one.path)],
308
306
  ]);
309
307
  for (const dir of dirs) {
310
308
  const read = readDocument(host, joinPath(dir, MANIFEST));
@@ -340,19 +338,9 @@ function manifestUses(host, dir, manifest) {
340
338
  return { ...uses, held: [uses.held, ...texts].join("\n") };
341
339
  }
342
340
  /** What the target root's workflow steps, git hooks and recipes use, held with their files' whole text. */
343
- function workflowUses(host, targetRoot) {
344
- const uses = usesOf(workflowReadings(host, targetRoot));
345
- return { ...uses, held: [uses.held, ...commandFileTexts(host, targetRoot)].join("\n") };
346
- }
347
- /**
348
- * What the target root's workflow steps, git hooks, hook managers and recipes run, each held
349
- * with the directory the file writes for it.
350
- */
351
- function workflowReadings(host, targetRoot) {
352
- return workflowCommands(host, targetRoot).map(({ text, dir, writtenDirectory }) => {
353
- const reading = readCommands(text, dir);
354
- return { ...reading, held: [reading.held, writtenDirectory ?? ""].join("\n") };
355
- });
341
+ function workflowUses(workflows) {
342
+ const uses = usesOf(workflows.readings);
343
+ return { ...uses, held: [uses.held, ...workflows.texts].join("\n") };
356
344
  }
357
345
  function usesOf(readings) {
358
346
  return {
@@ -438,7 +426,15 @@ function fileNeeds(project, held, file, { programPackages, automatic }, use) {
438
426
  if (names.size > 0 || automatic.globals.size > 0) {
439
427
  visit(file);
440
428
  }
441
- const asked = [...specifiers, ...bindings.map(({ node }) => node), ...candidates, ...globals];
429
+ const loads = runtimeLoads(file);
430
+ const callees = loads.flatMap((load) => load.callees);
431
+ const asked = [
432
+ ...specifiers,
433
+ ...bindings.map(({ node }) => node),
434
+ ...candidates,
435
+ ...globals,
436
+ ...callees,
437
+ ];
442
438
  const handles = asked.map((node) => project.handle(node));
443
439
  const resolved = project.symbolsAt(handles);
444
440
  const packagesOf = new Map();
@@ -509,33 +505,69 @@ function fileNeeds(project, held, file, { programPackages, automatic }, use) {
509
505
  for (const reference of file.typeReferenceDirectives) {
510
506
  use(ownersOf(file, held, file), typePackages(reference.fileName, programPackages));
511
507
  }
512
- for (const specifier of resolvedSpecifiers(file)) {
513
- const named = packageOfSpecifier(specifier.text);
514
- if (named !== undefined) {
508
+ // A callee the checker leaves unanswered may be a require, so its call holds the
509
+ // dependency it names by live code, as program.33 holds one.
510
+ const calleesAt = unresolvedAt + globals.length;
511
+ const decided = new Map();
512
+ const requires = new Set();
513
+ const unansweredCallees = new Set();
514
+ callees.forEach((callee, index) => {
515
+ const symbol = resolved[calleesAt + index];
516
+ if (symbol === UNANSWERED) {
517
+ unansweredCallees.add(callee);
518
+ return;
519
+ }
520
+ // The decision reads the callee's symbol and its written name, and nothing else of it.
521
+ const key = symbol === undefined ? undefined : `${symbol.id}\u0000${callee.text}`;
522
+ const answer = (key === undefined ? undefined : decided.get(key)) ??
523
+ callsPlatform(project, "require", callee, symbol, () => undefined);
524
+ if (key !== undefined) {
525
+ decided.set(key, answer);
526
+ }
527
+ if (answer) {
528
+ requires.add(callee);
529
+ }
530
+ });
531
+ for (const { specifier, named, callees: through } of loads) {
532
+ if (through.length === 0 || through.some((one) => requires.has(one))) {
515
533
  use(ownersOf(file, held, specifier), [named]);
516
534
  }
535
+ else if (through.some((one) => unansweredCallees.has(one))) {
536
+ use([PROJECT_OWNER], [named]);
537
+ }
517
538
  }
518
539
  return resolved.slice(0, specifiers.length).includes(UNANSWERED);
519
540
  }
520
541
  /**
521
- * The literal specifiers one file asks the runtime to resolve, through `import.meta.resolve`
522
- * or `require.resolve`, each of which needs the package it names installed.
542
+ * The calls one file may load or resolve a package with at run time, each of whose first
543
+ * argument is literal text naming a package: a `require()` call, which the compiler lists among
544
+ * a file's imports only in a JavaScript file and only through the bare name; a `.resolve` of
545
+ * another name, which needs it where that name is a `require`; and `import.meta.resolve` or
546
+ * a `.resolve` of the name `require`, whatever it names, a use where it stands.
523
547
  */
524
- function resolvedSpecifiers(file) {
548
+ function runtimeLoads(file) {
525
549
  const found = [];
526
- if (!file.text.includes(".resolve(")) {
527
- return found;
528
- }
529
550
  const visit = (node) => {
530
- if (isCallExpression(node) && isPropertyAccessExpression(node.expression)) {
531
- const callee = node.expression;
551
+ if (isCallExpression(node)) {
532
552
  const [argument] = node.arguments;
533
- const receiver = callee.expression;
534
- const resolving = callee.name.text === "resolve" &&
535
- ((isMetaProperty(receiver) && receiver.name.text === "meta") ||
536
- (isIdentifier(receiver) && receiver.text === "require"));
537
- if (resolving && argument !== undefined && isLiteralText(argument)) {
538
- found.push(argument);
553
+ if (argument !== undefined && isLiteralText(argument)) {
554
+ const named = packageOfSpecifier(argument.text);
555
+ const callee = parenthesesRemoved(node.expression);
556
+ const receiver = isPropertyAccessExpression(callee) && callee.name.text === "resolve"
557
+ ? parenthesesRemoved(callee.expression)
558
+ : undefined;
559
+ const stands = receiver !== undefined &&
560
+ ((isMetaProperty(receiver) && receiver.name.text === "meta") ||
561
+ (isIdentifier(receiver) && receiver.text === "require"));
562
+ const callees = stands
563
+ ? []
564
+ : [
565
+ writtenName(callee),
566
+ receiver === undefined ? undefined : writtenName(receiver),
567
+ ].filter((one) => one !== undefined);
568
+ if (named !== undefined && (stands || callees.length > 0)) {
569
+ found.push({ specifier: argument, named, callees });
570
+ }
539
571
  }
540
572
  }
541
573
  node.forEachChild(visit);
@@ -726,7 +758,7 @@ function addNeededPeers(declared, peers, needed, ran) {
726
758
  * manifest declares nothing, nor does a nested package's uninstalled dependency, because
727
759
  * no run requires a nested package to be installed.
728
760
  */
729
- export function dependenciesOf(host, targetRoot, perProject, candidates, members = [], unbuiltMembers = [], nested = []) {
761
+ export function dependenciesOf(host, targetRoot, workflows, perProject, candidates, members = [], unbuiltMembers = [], nested = []) {
730
762
  const root = resolvePath(host.workingDirectory(), targetRoot);
731
763
  const scopeOf = packageScope(host, targetRoot);
732
764
  const manifests = new Map([[root, MANIFEST]]);
@@ -743,7 +775,7 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
743
775
  unbuilt.add(path);
744
776
  }
745
777
  }
746
- const workflow = workflowUses(host, targetRoot);
778
+ const workflow = workflowUses(workflows);
747
779
  const declared = [];
748
780
  const ran = new Set();
749
781
  const peers = new Map();
@@ -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, 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 = !text.startsWith("./") && !text.startsWith("../") && !text.startsWith("/");
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
+ }
@@ -6,19 +6,26 @@
6
6
  * every string either holds as it likes, so each one that spells a declared dependency
7
7
  * uses it and each relative one that names a source file of the program roots the file.
8
8
  */
9
- import { isCallExpression, isExportDeclaration, isIdentifier, isImportDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
10
- import { ancestors, dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
11
- import { bytewise } from "./text/compare.js";
12
- import { isRecord } from "./json-document.js";
13
- import { filesIn } from "./fs/tree.js";
14
- import { DEPENDENCY_SECTIONS, holdsManifest, inInstalledPackages, manifestAt, readJSON, } from "./fs/manifest.js";
15
- import { readCommands } from "./commands/read.js";
16
- import { manifestCommands } from "./manifest.js";
9
+ import { isNoSubstitutionTemplateLiteral, isStringLiteral, } from "@typescript/native/ast";
10
+ import { relativeSpecifiers } from "./static-module.js";
11
+ import { ancestors, dirnamePath, joinPath, relativePath, resolvePath } from "../paths.js";
12
+ import { bytewise } from "../text/compare.js";
13
+ import { isRecord } from "../json-document.js";
14
+ import { filesIn } from "../fs/tree.js";
15
+ import { DEPENDENCY_SECTIONS, holdsManifest, inInstalledPackages, manifestAt, readJSON, } from "../fs/manifest.js";
16
+ import { readCommands } from "../commands/read.js";
17
+ import { manifestCommands } from "../roots/manifest.js";
18
+ import { APPENDED_EXTENSIONS, extensionOf, MODULE_EXTENSIONS } from "../extensions.js";
19
+ /** The extensions of a configuration module, as an expression's alternatives. */
20
+ const MODULE_NAME = MODULE_EXTENSIONS.map((one) => one.slice(1)).join("|");
17
21
  /** A configuration module's name, each form with the spelling a root reports it by. */
18
22
  const MODULE_FORMS = [
19
- { name: /^[\w-]+\.config\.(?:js|mjs|cjs|ts|mts|cts)$/u, form: "<stem>.config.<ext>" },
20
23
  {
21
- name: /^[\w-]+\.[\w-]+\.config\.(?:js|mjs|cjs|ts|mts|cts)$/u,
24
+ name: new RegExp(String.raw `^[\w-]+\.config\.(?:${MODULE_NAME})$`, "u"),
25
+ form: "<stem>.config.<ext>",
26
+ },
27
+ {
28
+ name: new RegExp(String.raw `^[\w-]+\.[\w-]+\.config\.(?:${MODULE_NAME})$`, "u"),
22
29
  form: "<stem>.<qualifier>.config.<ext>",
23
30
  },
24
31
  { name: /^\.[\w-]+rc\.(?:js|mjs|cjs)$/u, form: ".<stem>rc.<ext>" },
@@ -34,8 +41,6 @@ const COUNTERPARTS = [
34
41
  [".mjs", [".mts"]],
35
42
  [".cjs", [".cts"]],
36
43
  ];
37
- /** The extensions appended to a relative specifier from a configuration file, in order. */
38
- const APPENDED = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
39
44
  /** The extensions the program's module resolution appends to a specifier written without one. */
40
45
  export const RESOLVED_EXTENSIONS = [
41
46
  ".ts",
@@ -65,8 +70,6 @@ export function configurationModuleForm(name) {
65
70
  export function isConfigurationDocumentName(name) {
66
71
  return DOCUMENT_NAME.test(name);
67
72
  }
68
- /** The extensions of a module a configuration flag may name. */
69
- const MODULE_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"];
70
73
  /**
71
74
  * The files the command strings of the manifest in one directory name after a configuration
72
75
  * flag, as `commands/read.ts` reads them against the directory each command runs in, each a
@@ -200,47 +203,6 @@ export function configurationFiles(host, targetRoot, files, parse) {
200
203
  : outsideModules(host, root, [...dirs], files, manifestHeld, parse, named),
201
204
  };
202
205
  }
203
- /** The literal module specifier one `require(...)` or `import(...)` call names, if any. */
204
- function calledSpecifier(node) {
205
- if (!isCallExpression(node) || node.arguments.length !== 1) {
206
- return undefined;
207
- }
208
- const callee = node.expression;
209
- const loads = callee.kind === SyntaxKind.ImportKeyword || (isIdentifier(callee) && callee.text === "require");
210
- return loads ? node.arguments[0] : undefined;
211
- }
212
- /**
213
- * The relative specifiers one file's imports and re-exports name, and those its
214
- * `require(...)` and `import(...)` calls name by a literal.
215
- */
216
- export function relativeSpecifiers(file) {
217
- const known = specifiersOf.get(file);
218
- if (known !== undefined) {
219
- return known;
220
- }
221
- const found = [];
222
- const named = (specifier) => {
223
- if (specifier !== undefined &&
224
- (isStringLiteral(specifier) || isNoSubstitutionTemplateLiteral(specifier)) &&
225
- RELATIVE_PREFIXES.some((prefix) => specifier.text.startsWith(prefix))) {
226
- found.push(specifier.text);
227
- }
228
- };
229
- const visit = (node) => {
230
- named(calledSpecifier(node));
231
- node.forEachChild(visit);
232
- };
233
- for (const statement of file.statements) {
234
- named(isImportDeclaration(statement) || isExportDeclaration(statement)
235
- ? statement.moduleSpecifier
236
- : undefined);
237
- visit(statement);
238
- }
239
- specifiersOf.set(file, found);
240
- return found;
241
- }
242
- /** What {@link relativeSpecifiers} found in each file, which every project of a run reads alike. */
243
- const specifiersOf = new WeakMap();
244
206
  /**
245
207
  * The files no program holds that a chain from `seeds` reads: each seed, and each file
246
208
  * below the target root one imports, re-exports or loads by a relative specifier that no
@@ -261,7 +223,7 @@ export function outsideChain(host, targetRoot, files, parse, seeds) {
261
223
  export function specifierCandidates(dir, specifier) {
262
224
  const path = resolvePath(dir, specifier);
263
225
  const candidates = [];
264
- if (APPENDED.some((extension) => path.endsWith(extension))) {
226
+ if (extensionOf(path, APPENDED_EXTENSIONS) !== undefined) {
265
227
  candidates.push(path);
266
228
  }
267
229
  for (const [extension, counterparts] of COUNTERPARTS) {
@@ -270,8 +232,8 @@ export function specifierCandidates(dir, specifier) {
270
232
  candidates.push(...counterparts.map((one) => stem + one));
271
233
  }
272
234
  }
273
- candidates.push(...APPENDED.map((extension) => path + extension));
274
- candidates.push(...APPENDED.map((extension) => joinPath(path, `index${extension}`)));
235
+ candidates.push(...APPENDED_EXTENSIONS.map((extension) => path + extension));
236
+ candidates.push(...APPENDED_EXTENSIONS.map((extension) => joinPath(path, `index${extension}`)));
275
237
  return candidates;
276
238
  }
277
239
  /** The file on disk one relative specifier from a configuration file reaches, or undefined. */