@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
@@ -31,17 +31,18 @@ import {
31
31
  } from "@typescript/native/ast";
32
32
  import { automaticTypes, type AutomaticTypes } from "./automatic-types.ts";
33
33
  import {
34
- configurationFiles,
35
34
  declaredIn as declaredBeside,
36
35
  dependenciesNamed,
37
36
  moduleStrings,
38
37
  outsideChain,
38
+ type ConfigurationFiles,
39
39
  type OutsideModule,
40
40
  type ParseFile,
41
- } from "./configuration-files.ts";
41
+ } from "./entries/configuration-files.ts";
42
42
  import type { FindingPosition } from "./finding.ts";
43
43
  import type { Host } from "./host.ts";
44
- import { nodeKey, packageScope, type Inventory } from "./inventory.ts";
44
+ import { nodeKey, type Inventory } from "./inventory.ts";
45
+ import { packageScope } from "./load/package-scope.ts";
45
46
  import {
46
47
  ancestors,
47
48
  dirnamePath,
@@ -54,8 +55,11 @@ import type { DependencySection, Module } from "./ref.ts";
54
55
  import { UNANSWERED } from "./query.ts";
55
56
  import type { Handle, ProjectView } from "./session.ts";
56
57
  import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
57
- import { manifestCommands, nodemonTexts } from "./manifest.ts";
58
- import { commandFileTexts, workflowCommands } from "./workflow-steps.ts";
58
+ import { manifestCommands, nodemonTexts } from "./roots/manifest.ts";
59
+ import { writtenName } from "./platform-calls.ts";
60
+ import { callsPlatform } from "./platform-symbols.ts";
61
+ import { parenthesesRemoved } from "./unwrap.ts";
62
+ import type { Workflows } from "./entries/workflow-steps.ts";
59
63
  import {
60
64
  pathsNamed,
61
65
  preloadedPackages,
@@ -354,8 +358,9 @@ function bindingsOf(node: Node | undefined): readonly Texted[] {
354
358
  * Every package one project needs, and which declaration each use is written in.
355
359
  * One local walk and one batched resolution per file: a use of an import binding is
356
360
  * an identifier resolving to the binding's own symbol, so a local declaration that
357
- * shadows the name is not one. A string of a configuration file uses each dependency
358
- * the manifest beside the file declares that it spells.
361
+ * shadows the name is not one. A string of a configuration file, as the entry sources
362
+ * read them in `configuration`, uses each dependency the manifest beside the file
363
+ * declares that it spells.
359
364
  */
360
365
  export function projectNeeds<Brand>(
361
366
  project: ProjectView<Brand>,
@@ -363,6 +368,8 @@ export function projectNeeds<Brand>(
363
368
  host: Host,
364
369
  targetRoot: string,
365
370
  parse: ParseFile,
371
+ configuration: ConfigurationFiles,
372
+ workflows: Workflows,
366
373
  ): ProjectNeeds {
367
374
  const programPackages = new Set<string>();
368
375
  for (const name of project.program.getSourceFileNames()) {
@@ -416,7 +423,6 @@ export function projectNeeds<Brand>(
416
423
  return names;
417
424
  };
418
425
  const files = sourceFilesOf(project, targetRoot);
419
- const configuration = configurationFiles(host, targetRoot, files, parse);
420
426
  for (const { file, manifestDir } of configuration.modules) {
421
427
  const names = declaredAt(joinPath(manifestDir, MANIFEST));
422
428
  for (const { node, text } of moduleStrings(file)) {
@@ -436,7 +442,7 @@ export function projectNeeds<Brand>(
436
442
  targetRoot,
437
443
  files,
438
444
  parse,
439
- scriptRunFiles(host, targetRoot, files),
445
+ scriptRunFiles(host, targetRoot, files, workflows),
440
446
  );
441
447
  for (const { file, manifestDirs } of [...configuration.outside, ...ranFiles]) {
442
448
  for (const manifestDir of manifestDirs) {
@@ -459,6 +465,7 @@ function scriptRunFiles(
459
465
  host: Host,
460
466
  targetRoot: string,
461
467
  files: SourceFiles,
468
+ workflows: Workflows,
462
469
  ): readonly { readonly path: string; readonly manifestDir: string }[] {
463
470
  const root = resolvePath(host.workingDirectory(), targetRoot);
464
471
  const dirs = new Set<string>([root]);
@@ -471,12 +478,7 @@ function scriptRunFiles(
471
478
  }
472
479
  }
473
480
  const named = new Map<string, readonly string[]>([
474
- [
475
- root,
476
- workflowReadings(host, root)
477
- .flatMap(pathsNamed)
478
- .map((one) => one.path),
479
- ],
481
+ [root, workflows.readings.flatMap(pathsNamed).map((one) => one.path)],
480
482
  ]);
481
483
  for (const dir of dirs) {
482
484
  const read = readDocument(host, joinPath(dir, MANIFEST));
@@ -521,20 +523,9 @@ function manifestUses(host: Host, dir: string, manifest: Record<string, unknown>
521
523
  }
522
524
 
523
525
  /** What the target root's workflow steps, git hooks and recipes use, held with their files' whole text. */
524
- function workflowUses(host: Host, targetRoot: string): CommandUses {
525
- const uses = usesOf(workflowReadings(host, targetRoot));
526
- return { ...uses, held: [uses.held, ...commandFileTexts(host, targetRoot)].join("\n") };
527
- }
528
-
529
- /**
530
- * What the target root's workflow steps, git hooks, hook managers and recipes run, each held
531
- * with the directory the file writes for it.
532
- */
533
- function workflowReadings(host: Host, targetRoot: string): readonly CommandReading[] {
534
- return workflowCommands(host, targetRoot).map(({ text, dir, writtenDirectory }) => {
535
- const reading = readCommands(text, dir);
536
- return { ...reading, held: [reading.held, writtenDirectory ?? ""].join("\n") };
537
- });
526
+ function workflowUses(workflows: Workflows): CommandUses {
527
+ const uses = usesOf(workflows.readings);
528
+ return { ...uses, held: [uses.held, ...workflows.texts].join("\n") };
538
529
  }
539
530
 
540
531
  /** What some command strings say about the packages they use. */
@@ -660,8 +651,16 @@ function fileNeeds<Brand>(
660
651
  if (names.size > 0 || automatic.globals.size > 0) {
661
652
  visit(file);
662
653
  }
654
+ const loads = runtimeLoads(file);
655
+ const callees = loads.flatMap((load) => load.callees);
663
656
 
664
- const asked = [...specifiers, ...bindings.map(({ node }) => node), ...candidates, ...globals];
657
+ const asked = [
658
+ ...specifiers,
659
+ ...bindings.map(({ node }) => node),
660
+ ...candidates,
661
+ ...globals,
662
+ ...callees,
663
+ ];
665
664
  const handles: Handle<Brand>[] = asked.map((node) => project.handle(node));
666
665
  const resolved = project.symbolsAt(handles);
667
666
 
@@ -737,35 +736,83 @@ function fileNeeds<Brand>(
737
736
  for (const reference of file.typeReferenceDirectives) {
738
737
  use(ownersOf(file, held, file), typePackages(reference.fileName, programPackages));
739
738
  }
740
- for (const specifier of resolvedSpecifiers(file)) {
741
- const named = packageOfSpecifier(specifier.text);
742
- if (named !== undefined) {
739
+ // A callee the checker leaves unanswered may be a require, so its call holds the
740
+ // dependency it names by live code, as program.33 holds one.
741
+ const calleesAt = unresolvedAt + globals.length;
742
+ const decided = new Map<string, boolean>();
743
+ const requires = new Set<Identifier>();
744
+ const unansweredCallees = new Set<Identifier>();
745
+ callees.forEach((callee, index) => {
746
+ const symbol = resolved[calleesAt + index];
747
+ if (symbol === UNANSWERED) {
748
+ unansweredCallees.add(callee);
749
+ return;
750
+ }
751
+ // The decision reads the callee's symbol and its written name, and nothing else of it.
752
+ const key = symbol === undefined ? undefined : `${symbol.id}\u0000${callee.text}`;
753
+ const answer =
754
+ (key === undefined ? undefined : decided.get(key)) ??
755
+ callsPlatform(project, "require", callee, symbol, () => undefined);
756
+ if (key !== undefined) {
757
+ decided.set(key, answer);
758
+ }
759
+ if (answer) {
760
+ requires.add(callee);
761
+ }
762
+ });
763
+ for (const { specifier, named, callees: through } of loads) {
764
+ if (through.length === 0 || through.some((one) => requires.has(one))) {
743
765
  use(ownersOf(file, held, specifier), [named]);
766
+ } else if (through.some((one) => unansweredCallees.has(one))) {
767
+ use([PROJECT_OWNER], [named]);
744
768
  }
745
769
  }
746
770
  return resolved.slice(0, specifiers.length).includes(UNANSWERED);
747
771
  }
748
772
 
749
773
  /**
750
- * The literal specifiers one file asks the runtime to resolve, through `import.meta.resolve`
751
- * or `require.resolve`, each of which needs the package it names installed.
774
+ * A call of one file that may load or resolve a package at run time: the literal specifier
775
+ * naming it, and the names a `require` would be written with, of which one has to be a
776
+ * `require` for the call to need the package; none for a use where it stands.
752
777
  */
753
- function resolvedSpecifiers(file: SourceFile): readonly Texted[] {
754
- const found: Texted[] = [];
755
- if (!file.text.includes(".resolve(")) {
756
- return found;
757
- }
778
+ interface RuntimeLoad {
779
+ readonly specifier: Texted;
780
+ readonly named: string;
781
+ readonly callees: readonly Identifier[];
782
+ }
783
+
784
+ /**
785
+ * The calls one file may load or resolve a package with at run time, each of whose first
786
+ * argument is literal text naming a package: a `require()` call, which the compiler lists among
787
+ * a file's imports only in a JavaScript file and only through the bare name; a `.resolve` of
788
+ * another name, which needs it where that name is a `require`; and `import.meta.resolve` or
789
+ * a `.resolve` of the name `require`, whatever it names, a use where it stands.
790
+ */
791
+ function runtimeLoads(file: SourceFile): readonly RuntimeLoad[] {
792
+ const found: RuntimeLoad[] = [];
758
793
  const visit = (node: Node): void => {
759
- if (isCallExpression(node) && isPropertyAccessExpression(node.expression)) {
760
- const callee = node.expression;
794
+ if (isCallExpression(node)) {
761
795
  const [argument] = node.arguments;
762
- const receiver = callee.expression;
763
- const resolving =
764
- callee.name.text === "resolve" &&
765
- ((isMetaProperty(receiver) && receiver.name.text === "meta") ||
766
- (isIdentifier(receiver) && receiver.text === "require"));
767
- if (resolving && argument !== undefined && isLiteralText(argument)) {
768
- found.push(argument);
796
+ if (argument !== undefined && isLiteralText(argument)) {
797
+ const named = packageOfSpecifier(argument.text);
798
+ const callee = parenthesesRemoved(node.expression);
799
+ const receiver =
800
+ isPropertyAccessExpression(callee) && callee.name.text === "resolve"
801
+ ? parenthesesRemoved(callee.expression)
802
+ : undefined;
803
+ const stands =
804
+ receiver !== undefined &&
805
+ ((isMetaProperty(receiver) && receiver.name.text === "meta") ||
806
+ (isIdentifier(receiver) && receiver.text === "require"));
807
+ const callees = stands
808
+ ? []
809
+ : [
810
+ writtenName(callee),
811
+ receiver === undefined ? undefined : writtenName(receiver),
812
+ ].filter((one) => one !== undefined);
813
+ if (named !== undefined && (stands || callees.length > 0)) {
814
+ found.push({ specifier: argument, named, callees });
815
+ }
769
816
  }
770
817
  }
771
818
  node.forEachChild(visit);
@@ -988,6 +1035,7 @@ function addNeededPeers(
988
1035
  export function dependenciesOf(
989
1036
  host: Host,
990
1037
  targetRoot: string,
1038
+ workflows: Workflows,
991
1039
  perProject: readonly ProjectNeeds[],
992
1040
  candidates: readonly string[],
993
1041
  members: readonly string[] = [],
@@ -1010,7 +1058,7 @@ export function dependenciesOf(
1010
1058
  unbuilt.add(path);
1011
1059
  }
1012
1060
  }
1013
- const workflow = workflowUses(host, targetRoot);
1061
+ const workflow = workflowUses(workflows);
1014
1062
  const declared: DeclaredDependency[] = [];
1015
1063
  const ran = new Set<string>();
1016
1064
  const peers = new Map<string, readonly string[]>();
@@ -0,0 +1,292 @@
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
+
8
+ import {
9
+ isArrowFunction,
10
+ isCallExpression,
11
+ isConstructorDeclaration,
12
+ isFunctionDeclaration,
13
+ isFunctionExpression,
14
+ isGetAccessorDeclaration,
15
+ isIdentifier,
16
+ isMethodDeclaration,
17
+ isNewExpression,
18
+ isSetAccessorDeclaration,
19
+ isVariableDeclaration,
20
+ type CallExpression,
21
+ type Node,
22
+ type SourceFile,
23
+ } from "@typescript/native/ast";
24
+ import { declarationOf, membersNamed, staticModule, type Value } from "./static-module.ts";
25
+ import { filesNamedBy, type ParseFile } from "./configuration-files.ts";
26
+ import type { Host } from "../host.ts";
27
+ import { ancestors, dirnamePath, relativePath, resolvePath } from "../paths.ts";
28
+ import type { SourceFiles } from "../source-files.ts";
29
+ import { bytewise } from "../text/compare.ts";
30
+ import { unwrapped } from "../unwrap.ts";
31
+ import { holdsManifest, inInstalledPackages, manifestAt } from "../fs/manifest.ts";
32
+ import { globExpression } from "../text/glob.ts";
33
+ import { pathsNamed, readCommands } from "../commands/read.ts";
34
+ import { manifestCommands } from "../roots/manifest.ts";
35
+ import { extensionOf, MODULE_EXTENSIONS } from "../extensions.ts";
36
+
37
+ /** One bundler API: the module, the functions it exports that build, and the option naming entries. */
38
+ interface BundlerAPI {
39
+ readonly module: string;
40
+ /** The exported names of the building functions; `default` is the module's own value called. */
41
+ readonly calls: readonly string[];
42
+ /** The option whose strings name the entries, read as `entries` states. */
43
+ readonly option: string;
44
+ /** The option that moves the directory the entries are read against. */
45
+ readonly base: string;
46
+ /** Whether an entry string is a path, glob characters matching, or a module request, only a relative one naming a file. */
47
+ readonly paths: boolean;
48
+ }
49
+
50
+ /**
51
+ * The bundlers whose build call names its entries: esbuild resolves `entryPoints` against
52
+ * `absWorkingDir` (https://esbuild.github.io/api/#working-directory), webpack `entry`
53
+ * against `context` (https://webpack.js.org/configuration/entry-context/), and Rollup
54
+ * `input` against the working directory, each the manifest's directory when its script runs.
55
+ */
56
+ const BUNDLERS: readonly BundlerAPI[] = [
57
+ {
58
+ module: "esbuild",
59
+ calls: ["build", "buildSync", "context"],
60
+ option: "entryPoints",
61
+ base: "absWorkingDir",
62
+ paths: true,
63
+ },
64
+ { module: "webpack", calls: ["default"], option: "entry", base: "context", paths: false },
65
+ { module: "rollup", calls: ["rollup", "watch"], option: "input", base: "", paths: true },
66
+ ];
67
+
68
+ /** One own file a build script's bundler call names as an entry. */
69
+ interface BuildEntry {
70
+ readonly file: SourceFile;
71
+ /** The string that named it. */
72
+ readonly source: string;
73
+ }
74
+
75
+ /** One path an entry value writes. */
76
+ interface EntryPath {
77
+ readonly path: string;
78
+ /** What the script writes for it. */
79
+ readonly text: string;
80
+ /** Whether it is written as neither a relative nor an absolute path. */
81
+ readonly request: boolean;
82
+ }
83
+
84
+ /** The one value an object writes last for a key, undefined where it writes none or may hide it. */
85
+ function lastMember(object: Value, key: string): Value | undefined {
86
+ return membersNamed(object, key)?.at(-1);
87
+ }
88
+
89
+ /**
90
+ * The path one entry value writes, read against `dir`: a literal, or a join of the
91
+ * script's own directory and literals, read against that directory.
92
+ */
93
+ function pathOf(value: Value, dir: string, scriptDir: string): EntryPath | undefined {
94
+ if (value.kind === "text") {
95
+ const { text } = value;
96
+ const request = !text.startsWith("./") && !text.startsWith("../") && !text.startsWith("/");
97
+ return { path: resolvePath(dir, text), text, request };
98
+ }
99
+ if (value.kind === "directory") {
100
+ const joined = value.segments.join("/");
101
+ return { path: resolvePath(scriptDir, joined), text: joined, request: false };
102
+ }
103
+ return undefined;
104
+ }
105
+
106
+ /** The entry values one entries option writes: a value, an array of them, an object of them. */
107
+ function entryValues(value: Value): readonly Value[] {
108
+ if (value.kind === "array") {
109
+ return value.elements.flatMap(entryValues);
110
+ }
111
+ if (value.kind === "object") {
112
+ const imported = lastMember(value, "import") ?? lastMember(value, "in");
113
+ if (imported !== undefined) {
114
+ return entryValues(imported);
115
+ }
116
+ return value.members.flatMap((member) => entryValues(member.value));
117
+ }
118
+ return [value];
119
+ }
120
+
121
+ /** The own files one entry string names, read as `api` reads it. */
122
+ function entriesNamed(
123
+ files: SourceFiles,
124
+ api: BundlerAPI,
125
+ entry: EntryPath,
126
+ ): readonly SourceFile[] {
127
+ if (!api.paths && entry.request) {
128
+ return [];
129
+ }
130
+ if (api.paths && /[*?[{]/u.test(entry.text)) {
131
+ const expression = globExpression(entry.path);
132
+ return [...files.byName.values()].filter((file) => expression.test(file.fileName));
133
+ }
134
+ const dir = dirnamePath(entry.path);
135
+ return filesNamedBy(files, dir, entry.path.slice(dir.length + 1));
136
+ }
137
+
138
+ /** Whether one node is a function, whose body runs only where something calls it. */
139
+ function isFunction(node: Node): boolean {
140
+ return (
141
+ isFunctionDeclaration(node) ||
142
+ isFunctionExpression(node) ||
143
+ isArrowFunction(node) ||
144
+ isMethodDeclaration(node) ||
145
+ isConstructorDeclaration(node) ||
146
+ isGetAccessorDeclaration(node) ||
147
+ isSetAccessorDeclaration(node)
148
+ );
149
+ }
150
+
151
+ /**
152
+ * The calls one build script runs: those of its top level, and those of each function a
153
+ * call it runs calls or passes on, as a callee, an argument or a function called where it
154
+ * is written, a name read through to the function it declares.
155
+ */
156
+ function runCalls(file: SourceFile, each: (call: CallExpression) => void): void {
157
+ const ran = new Set<Node>();
158
+ const run = (fn: Node): void => {
159
+ if (!ran.has(fn)) {
160
+ ran.add(fn);
161
+ fn.forEachChild(visit);
162
+ }
163
+ };
164
+ const runs = (node: Node): void => {
165
+ const at = unwrapped(node);
166
+ const declared = isIdentifier(at) ? declarationOf(at) : undefined;
167
+ const fn =
168
+ declared !== undefined && isVariableDeclaration(declared) && declared.initializer
169
+ ? unwrapped(declared.initializer)
170
+ : (declared ?? at);
171
+ if (isFunction(fn)) {
172
+ run(fn);
173
+ }
174
+ };
175
+ const visit = (node: Node): void => {
176
+ if (isFunction(node)) {
177
+ return;
178
+ }
179
+ if (isCallExpression(node) || isNewExpression(node)) {
180
+ if (isCallExpression(node)) {
181
+ each(node);
182
+ }
183
+ runs(node.expression);
184
+ for (const argument of node.arguments ?? []) {
185
+ runs(argument);
186
+ }
187
+ }
188
+ node.forEachChild(visit);
189
+ };
190
+ file.forEachChild(visit);
191
+ }
192
+
193
+ /** The entries every bundler call one build script runs names, read against `dir`. */
194
+ function scriptEntries(file: SourceFile, dir: string, files: SourceFiles): readonly BuildEntry[] {
195
+ const module = staticModule(file);
196
+ const building = new Map<CallExpression, BundlerAPI>();
197
+ for (const api of BUNDLERS) {
198
+ for (const one of module.calls(api.module)) {
199
+ if (api.calls.includes(one.export)) {
200
+ building.set(one.call, api);
201
+ }
202
+ }
203
+ }
204
+ const scriptDir = dirnamePath(file.fileName);
205
+ const found: BuildEntry[] = [];
206
+ runCalls(file, (call) => {
207
+ const api = building.get(call);
208
+ if (api === undefined) {
209
+ return;
210
+ }
211
+ for (const options of module.argument(call)) {
212
+ const entries = lastMember(options, api.option);
213
+ const base = api.base === "" ? undefined : lastMember(options, api.base);
214
+ const against = base === undefined ? { path: dir } : pathOf(base, dir, scriptDir);
215
+ if (entries === undefined || against === undefined) {
216
+ continue;
217
+ }
218
+ for (const value of entryValues(entries)) {
219
+ const named = pathOf(value, against.path, scriptDir);
220
+ if (named === undefined) {
221
+ continue;
222
+ }
223
+ for (const entry of entriesNamed(files, api, named)) {
224
+ found.push({ file: entry, source: named.text });
225
+ }
226
+ }
227
+ }
228
+ });
229
+ return found;
230
+ }
231
+
232
+ /** The modules one manifest's command strings run or name, by absolute path, each below the target root. */
233
+ function scriptModules(host: Host, root: string, dir: string): readonly string[] {
234
+ const manifest = manifestAt(host, dir);
235
+ const found = new Set<string>();
236
+ for (const { command } of manifest === undefined ? [] : manifestCommands(host, dir, manifest)) {
237
+ for (const { path } of pathsNamed(readCommands(command, dir))) {
238
+ if (
239
+ extensionOf(path, MODULE_EXTENSIONS) !== undefined &&
240
+ relativePath(root, path) !== undefined &&
241
+ !inInstalledPackages(path) &&
242
+ host.kindOf(path) === "file"
243
+ ) {
244
+ found.add(path);
245
+ }
246
+ }
247
+ }
248
+ return [...found].sort(bytewise);
249
+ }
250
+
251
+ /**
252
+ * Every own file of one project a build script names as a bundler's entry: a module a
253
+ * script of a manifest at or above an own file runs, below the target root, read from
254
+ * the program where it holds the module and parsed alone otherwise, where a parser is given.
255
+ */
256
+ export function buildScriptEntries(
257
+ host: Host,
258
+ targetRoot: string,
259
+ files: SourceFiles,
260
+ parse: ParseFile | undefined,
261
+ ): readonly BuildEntry[] {
262
+ const root = resolvePath(host.workingDirectory(), targetRoot);
263
+ const dirs = new Set<string>([root]);
264
+ for (const file of files.byName.values()) {
265
+ for (const dir of ancestors(dirnamePath(file.fileName), root)) {
266
+ if (dirs.has(dir)) {
267
+ break;
268
+ }
269
+ dirs.add(dir);
270
+ }
271
+ }
272
+ const found: BuildEntry[] = [];
273
+ for (const dir of [...dirs].sort(bytewise)) {
274
+ if (!holdsManifest(host, dir)) {
275
+ continue;
276
+ }
277
+ for (const path of scriptModules(host, root, dir)) {
278
+ let script = files.byName.get(path);
279
+ if (script === undefined && parse !== undefined) {
280
+ try {
281
+ script = parse(path, host.readFile(path));
282
+ } catch {
283
+ continue;
284
+ }
285
+ }
286
+ if (script !== undefined) {
287
+ found.push(...scriptEntries(script, dir, files));
288
+ }
289
+ }
290
+ }
291
+ return found;
292
+ }