@cplieger/deadset-ts 8.0.0-dev.15 → 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 (134) hide show
  1. package/dist/src/analysis.js +41 -37
  2. package/dist/src/commands/read.js +6 -7
  3. package/dist/src/conformance-record.js +1 -1
  4. package/dist/src/consumers.js +3 -1
  5. package/dist/src/contract/config.generated.js +1 -1
  6. package/dist/src/contract/contract.generated.js +119 -2
  7. package/dist/src/dependencies.js +79 -48
  8. package/dist/src/entries/build-scripts.js +222 -0
  9. package/dist/src/{configuration-files.js → entries/configuration-files.js} +20 -58
  10. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  11. package/dist/src/entries/conventions.js +739 -0
  12. package/dist/src/{html-entries.js → entries/html-entries.js} +3 -3
  13. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +2 -2
  14. package/dist/src/entries/run-directories.js +77 -0
  15. package/dist/src/entries/sources.js +144 -0
  16. package/dist/src/entries/static-module.js +669 -0
  17. package/dist/src/entries/test-runners.js +684 -0
  18. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  19. package/dist/src/extensions.js +37 -0
  20. package/dist/src/facts.js +38 -0
  21. package/dist/src/findings/emitters.js +5 -8
  22. package/dist/src/findings/self-check.js +36 -20
  23. package/dist/src/findings-pass.js +5 -6
  24. package/dist/src/framework-lifecycle.js +1 -1
  25. package/dist/src/fs/manifest.js +15 -20
  26. package/dist/src/generated-file.js +172 -29
  27. package/dist/src/graph.js +2 -4
  28. package/dist/src/index.js +1 -1
  29. package/dist/src/injection-container.js +1 -1
  30. package/dist/src/inventory/commonjs.js +62 -17
  31. package/dist/src/inventory/enumerate.js +2 -1
  32. package/dist/src/javascript-file.js +26 -11
  33. package/dist/src/load/classify.js +28 -9
  34. package/dist/src/load/diagnostics.js +8 -1
  35. package/dist/src/load/discover.js +15 -7
  36. package/dist/src/load/plan.js +2 -2
  37. package/dist/src/load/synthesize.js +5 -5
  38. package/dist/src/load/workspace-manifests.js +1 -1
  39. package/dist/src/matrix.js +6 -13
  40. package/dist/src/outside-tests.js +1 -1
  41. package/dist/src/paths.js +16 -0
  42. package/dist/src/platform-calls.js +78 -0
  43. package/dist/src/platform-symbols.js +140 -0
  44. package/dist/src/references.js +202 -32
  45. package/dist/src/resolve.js +1 -1
  46. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +4 -4
  47. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  48. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  49. package/dist/src/roots/published-reach.js +412 -0
  50. package/dist/src/roots/root-set.js +489 -0
  51. package/dist/src/roots/unseen-loads.js +208 -0
  52. package/dist/src/serialization-contract.js +1 -1
  53. package/dist/src/source-files.js +5 -9
  54. package/dist/src/suppress.js +30 -32
  55. package/dist/src/test-support.js +4 -2
  56. package/dist/src/verbs/analyze.js +13 -1
  57. package/dist/src/verbs/describe.js +5 -3
  58. package/jsr.json +1 -1
  59. package/package.json +2 -2
  60. package/src/analysis.ts +75 -52
  61. package/src/automatic-types.ts +1 -1
  62. package/src/commands/read.ts +6 -8
  63. package/src/conformance-record.ts +1 -1
  64. package/src/consumers.ts +4 -0
  65. package/src/contract/config.generated.ts +1 -1
  66. package/src/contract/contract.generated.ts +138 -2
  67. package/src/dependencies.ts +96 -49
  68. package/src/entries/build-scripts.ts +292 -0
  69. package/src/{configuration-files.ts → entries/configuration-files.ts} +23 -75
  70. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  71. package/src/{conventions.ts → entries/conventions.ts} +347 -522
  72. package/src/{html-entries.ts → entries/html-entries.ts} +5 -5
  73. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +4 -4
  74. package/src/entries/run-directories.ts +108 -0
  75. package/src/entries/sources.ts +266 -0
  76. package/src/entries/static-module.ts +857 -0
  77. package/src/entries/test-runners.ts +868 -0
  78. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  79. package/src/exempt.ts +3 -2
  80. package/src/extensions.ts +45 -0
  81. package/src/facts.ts +44 -0
  82. package/src/file-facts.ts +1 -1
  83. package/src/findings/emitters.ts +14 -10
  84. package/src/findings/self-check.ts +41 -22
  85. package/src/findings/visibility-narrowing.ts +1 -1
  86. package/src/findings-pass.ts +5 -12
  87. package/src/framework-lifecycle.ts +1 -1
  88. package/src/fs/manifest.ts +25 -21
  89. package/src/generated-file.ts +240 -36
  90. package/src/graph.ts +3 -5
  91. package/src/index.ts +1 -1
  92. package/src/injection-container.ts +1 -1
  93. package/src/inventory/commonjs.ts +102 -17
  94. package/src/inventory/enumerate.ts +2 -1
  95. package/src/javascript-file.ts +29 -12
  96. package/src/load/classify.ts +49 -13
  97. package/src/load/diagnostics.ts +11 -1
  98. package/src/load/discover.ts +27 -11
  99. package/src/load/plan.ts +11 -2
  100. package/src/load/synthesize.ts +6 -6
  101. package/src/load/test-globals.ts +1 -1
  102. package/src/load/workspace-manifests.ts +1 -1
  103. package/src/load/workspace-run.ts +1 -1
  104. package/src/matrix.ts +7 -14
  105. package/src/outside-tests.ts +1 -1
  106. package/src/paths.ts +18 -0
  107. package/src/platform-calls.ts +119 -0
  108. package/src/platform-symbols.ts +205 -0
  109. package/src/references.ts +281 -34
  110. package/src/report.ts +1 -1
  111. package/src/resolve.ts +1 -1
  112. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +7 -7
  113. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  114. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  115. package/src/roots/published-reach.ts +481 -0
  116. package/src/{roots.ts → roots/root-set.ts} +203 -630
  117. package/src/roots/unseen-loads.ts +307 -0
  118. package/src/serialization-contract.ts +1 -1
  119. package/src/source-files.ts +5 -10
  120. package/src/suppress.ts +48 -33
  121. package/src/test-support.ts +8 -3
  122. package/src/verbs/analyze.ts +17 -4
  123. package/src/verbs/describe.ts +5 -2
  124. package/src/verbs/explain.ts +1 -1
  125. package/dist/src/build-scripts.js +0 -418
  126. package/dist/src/conventions.js +0 -907
  127. package/dist/src/entry-points.js +0 -444
  128. package/dist/src/own-body.js +0 -28
  129. package/dist/src/roots.js +0 -865
  130. package/dist/src/unseen-loads.js +0 -189
  131. package/src/build-scripts.ts +0 -536
  132. package/src/entry-points.ts +0 -584
  133. package/src/own-body.ts +0 -42
  134. package/src/unseen-loads.ts +0 -282
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * The one reader of every command string the analysis reads. Layer 3, the holding rule
3
- * `roots.ts` applies through `text/named-files.ts`, reads all of the string's text whatever
4
- * the other two layers make of it; layer 1 parses the string and layer 2 reads each simple
5
- * command a tool's reader understands, and they only add the files and modules it runs.
3
+ * `roots/root-set.ts` applies through `text/named-files.ts`, reads all of the string's text
4
+ * whatever the other two layers make of it; layer 1 parses the string and layer 2 reads each
5
+ * simple command a tool's reader understands, and they only add the files and modules it runs.
6
6
  */
7
7
 
8
8
  import { joinPath, resolvePath } from "../paths.ts";
@@ -16,6 +16,7 @@ import {
16
16
  type Preload,
17
17
  type Reading,
18
18
  } from "./tools.ts";
19
+ import { APPENDED_EXTENSIONS } from "../extensions.ts";
19
20
 
20
21
  /** One program a read command runs. */
21
22
  interface Run {
@@ -159,9 +160,6 @@ export function readCommands(text: string, dir: string | undefined): CommandRead
159
160
  };
160
161
  }
161
162
 
162
- /** The extensions a runtime or a loader tries after a path written without one. */
163
- const TRIED_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"] as const;
164
-
165
163
  /** One file a run loads, as written, and every absolute path it may be at. */
166
164
  interface Load {
167
165
  readonly written: string;
@@ -188,8 +186,8 @@ export function loadsOf(run: Run): readonly Load[] {
188
186
  written: one,
189
187
  paths: [
190
188
  path,
191
- ...TRIED_EXTENSIONS.map((extension) => `${path}${extension}`),
192
- ...TRIED_EXTENSIONS.map((extension) => joinPath(path, `index${extension}`)),
189
+ ...APPENDED_EXTENSIONS.map((extension) => `${path}${extension}`),
190
+ ...APPENDED_EXTENSIONS.map((extension) => joinPath(path, `index${extension}`)),
193
191
  ],
194
192
  }));
195
193
  }
@@ -4,7 +4,7 @@ export const RECORD = {
4
4
  conformance: {
5
5
  corpusVersion: "6.0.0",
6
6
  result: "pass",
7
- digest: "sha256:4a66f7f01b0f1a23cf843b77ef48d762f2dcf486153e665560f107f10b0ea838",
7
+ digest: "sha256:d8d2a417c5cb9b3829739424d9cfd3bd7b631231d8f420300656f6df86f5a6d1",
8
8
  },
9
9
  gaps: [],
10
10
  } as const;
package/src/consumers.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { PartialConfig } from "./config.ts";
2
+ import type { ParseFile } from "./entries/configuration-files.ts";
3
+ import { runDirectories } from "./entries/run-directories.ts";
2
4
  import type { Host } from "./host.ts";
3
5
  import { inventory } from "./inventory/enumerate.ts";
4
6
  import { relativePath } from "./paths.ts";
@@ -18,6 +20,7 @@ export function consumerReferences<Brand>(
18
20
  targetRoot: string,
19
21
  consumer: ConsumerSide,
20
22
  config: PartialConfig,
23
+ parse: ParseFile,
21
24
  ): readonly Reference[] {
22
25
  const held = inventory(
23
26
  narrowedTo(project, (file) => relativePath(targetRoot, file.fileName) !== undefined),
@@ -27,6 +30,7 @@ export function consumerReferences<Brand>(
27
30
  const found = references(project, held, targetRoot, {
28
31
  testFiles: config.ts.test_files,
29
32
  consumer,
33
+ directories: runDirectories(host, consumer.root, parse),
30
34
  }).references;
31
35
  if (config.analysis.consumer_tests !== "production") {
32
36
  return found;
@@ -291,7 +291,7 @@ export const SETTINGS: readonly SettingRow[] = [
291
291
  role: "setting",
292
292
  read: true,
293
293
  default: [
294
- "**/*.test.{ts,tsx,mts,cts}",
294
+ "**/*.test.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
295
295
  "**/*.test-d.{ts,tsx,mts,cts}",
296
296
  "**/*.spec-d.{ts,tsx,mts,cts}",
297
297
  "**/*.spec.*",
@@ -3,6 +3,7 @@
3
3
  export const CONTRACT_VERSION = "8.0.0";
4
4
  export const SCHEMA_VERSIONS = ["9.0.0"] as const;
5
5
  export const SCHEMA_VERSION = "9.0.0";
6
+ export const FACTS_VERSION = "1.0.0";
6
7
  export const CORPUS_VERSION = "6.0.0";
7
8
  export const CODE_PREFIX = "DS";
8
9
  export const FAMILY_KEY_LENGTH = 4;
@@ -34,16 +35,19 @@ export const LANGUAGE_ROWS = {
34
35
  analyzer: "deadset-go",
35
36
  markers: ["go.mod"],
36
37
  skippedDirectories: ["node_modules", "testdata", "vendor", ".*", "_*"],
38
+ skippedFiles: ["_*", ".*"],
37
39
  },
38
40
  ts: {
39
41
  analyzer: "deadset-ts",
40
42
  markers: ["tsconfig*.json", "jsconfig*.json"],
41
43
  skippedDirectories: ["node_modules", ".git"],
44
+ skippedFiles: ["*.min.js", "*.min.cjs", "*.min.mjs"],
42
45
  },
43
46
  proto: {
44
47
  analyzer: "deadset-proto",
45
48
  markers: ["buf.yaml", "buf.work.yaml"],
46
49
  skippedDirectories: ["node_modules", "vendor", ".*"],
50
+ skippedFiles: [],
47
51
  },
48
52
  } as const satisfies Readonly<
49
53
  Partial<
@@ -53,6 +57,7 @@ export const LANGUAGE_ROWS = {
53
57
  readonly analyzer: string;
54
58
  readonly markers: readonly string[];
55
59
  readonly skippedDirectories: readonly string[];
60
+ readonly skippedFiles: readonly string[];
56
61
  }
57
62
  >
58
63
  >
@@ -494,9 +499,9 @@ export const KIND_ROWS = [
494
499
  languages: ["go", "ts"],
495
500
  subject: "dependency",
496
501
  holdsLive: false,
497
- rule: "A directly declared dependency that no import in the target needs. On the Go side, a direct require whose module provides no package any target package or test variant imports. On the TypeScript side, a manifest dependency, development dependency or peer dependency the project's import closure does not need. A deletion finding whose fix would remove the last use of a dependency names that dependency. On the TypeScript side, a needed dependency whose every use is written inside declarations that are members of the run's dead components, or at the top level of a file reported under DS1602, that no command runs and that no needed dependency used otherwise names as a peer, is reported as well, dead only through that code: at the reachability class possible where one of those declarations belongs to a library's published API, and found under reachability alone where one of those declarations or files is, so at probable unless ts.roots_complete is true. A use the configuration makes, a use an unanswered specifier may make and an import none of whose bindings is used are uses by live code.",
502
+ rule: "A directly declared dependency that no import in the target needs. On the Go side, a direct require whose module provides no package any target package or test variant imports. On the TypeScript side, a manifest dependency, development dependency or peer dependency the project's import closure does not need. A deletion finding whose fix would remove the last use of a dependency names that dependency. On the TypeScript side, a needed dependency whose every use is written inside declarations that are members of the run's dead components, or at the top level of a file reported under DS1602, that no command runs and that no needed dependency used otherwise names as a peer, is reported as well, dead only through that code: at the reachability class possible where one of those declarations belongs to a library's published API, and found under reachability alone where one of those declarations or files is, so at probable unless ts.roots_complete is true. A use the configuration makes, a use an unanswered specifier or callee may make and an import none of whose bindings is used are uses by live code.",
498
503
  precondition:
499
- "On the Go side the rule is the semantics of `go mod tidy -diff` exactly: a requirement marked indirect is never reported, because it exists to pin a transitive version and removing it changes the build list. On the TypeScript side the import closure is the files of the run's projects, and a call of `import.meta.resolve` or of `require.resolve` in them, whatever `require` names, whose first argument is a string literal or a template literal with no substitution needs each dependency an import of that specifier needs. A `@types/<name>` package in a type root of a compiler configuration that lists no `types`, a `node_modules/@types` directory at or above the configuration's directory or a `typeRoots` entry naming one, is needed by a use, in a file of that configuration, of a global name or an ambient module that one of the package's declaration files declares, which resolves to no declaration of the program: a compiler before version 6 includes every package of the type roots where `types` is not listed, and version 6 and later include none, so the program the analysis builds holds none of those declarations. A package's declaration files are the file its `types` or `typings` names, else its `index.d.ts`, and each file a `/// <reference path>` of one of them reaches inside the package. A global is a top-level declaration of such a file that is no module, or a declaration of a `declare global` block of one that is, and an ambient module is a `declare module` with a string name of a file that is no module. A name that names a member, a property or a label is no use of a global. Four more dependencies are needed: one whose command a script entry, a workflow step or a git hook runs, one that a string of a configuration file names, and one that a string names under a manifest key or a package key of an applied convention row, each as grammar/analysis/conventions.md states, and one that a needed dependency names as a peer, as stated below. A command no script entry, workflow step or git hook runs needs nothing. A declared dependency that the installed manifest of a needed declared dependency names in its peerDependencies is needed, and so on until no further dependency joins. A peer dependency is a package the needed dependency expects beside it at a version its manifest states, whether or not it imports the package, and the project's declaration is what keeps that package present at a version the project chooses, so removing the declaration can break the needed dependency where nothing the analysis reads imports the peer. A peer that peerDependenciesMeta marks optional is needed in the same way, because the needed dependency's manifest still names it as a package it works with, and a package manager may leave an optional peer out where the project does not declare it, so the project's declaration is what keeps it present for the needed dependency. A declared dependency that only dependencies nothing needs name as a peer is needed by nothing. A dependency of a manifest whose directory holds source only in configurations the run lists in configurations_not_built is reported at the reachability class possible, because the source that may import it was not analyzed. An installed manifest is the package's manifest in the nearest node_modules directory at or above the directory of the manifest that declares the dependency, and a dependency with none is decided by the import closure alone. On the TypeScript side, the dependencies judged are those of every manifest grammar/analysis/program.md says the analysis reads, a nested package's among them, each read against its own directory: a dependency is needed by a use written in a file at or below the directory of the manifest that declares it where no manifest between them declares a dependency of the same name, so a use below a nested manifest that declares the same dependency needs that nested manifest's declaration only, by a use no one file holds, and as a peer of a needed dependency of the same manifest or of a manifest below it. A dependency a nested package's manifest declares whose installed manifest the analysis cannot find is not judged, because no run requires a nested package to be installed and its commands and peers cannot be read, while a workspace member's and the target's own are judged as above.",
504
+ "On the Go side the rule is the semantics of `go mod tidy -diff` exactly: a requirement marked indirect is never reported, because it exists to pin a transitive version and removing it changes the build list. On the TypeScript side the import closure is the files of the run's projects, and a `require()` call as `roots.23` reads one, or a call of `import.meta.resolve`, of `require.resolve`, whatever `require` names, or of `resolve` on another name that is such a `require`, in them, whose first argument is a string literal or a template literal with no substitution needs each dependency an import of that specifier needs. A `@types/<name>` package in a type root of a compiler configuration that lists no `types`, a `node_modules/@types` directory at or above the configuration's directory or a `typeRoots` entry naming one, is needed by a use, in a file of that configuration, of a global name or an ambient module that one of the package's declaration files declares, which resolves to no declaration of the program: a compiler before version 6 includes every package of the type roots where `types` is not listed, and version 6 and later include none, so the program the analysis builds holds none of those declarations. A package's declaration files are the file its `types` or `typings` names, else its `index.d.ts`, and each file a `/// <reference path>` of one of them reaches inside the package. A global is a top-level declaration of such a file that is no module, or a declaration of a `declare global` block of one that is, and an ambient module is a `declare module` with a string name of a file that is no module. A name that names a member, a property or a label is no use of a global. Four more dependencies are needed: one whose command a script entry, a workflow step or a git hook runs, one that a string of a configuration file names, and one that a string names under a manifest key or a package key of an applied convention row, each as grammar/analysis/conventions.md states, and one that a needed dependency names as a peer, as stated below. A command no script entry, workflow step or git hook runs needs nothing. A declared dependency that the installed manifest of a needed declared dependency names in its peerDependencies is needed, and so on until no further dependency joins. A peer dependency is a package the needed dependency expects beside it at a version its manifest states, whether or not it imports the package, and the project's declaration is what keeps that package present at a version the project chooses, so removing the declaration can break the needed dependency where nothing the analysis reads imports the peer. A peer that peerDependenciesMeta marks optional is needed in the same way, because the needed dependency's manifest still names it as a package it works with, and a package manager may leave an optional peer out where the project does not declare it, so the project's declaration is what keeps it present for the needed dependency. A declared dependency that only dependencies nothing needs name as a peer is needed by nothing. A dependency of a manifest whose directory holds source only in configurations the run lists in configurations_not_built is reported at the reachability class possible, because the source that may import it was not analyzed. An installed manifest is the package's manifest in the nearest node_modules directory at or above the directory of the manifest that declares the dependency, and a dependency with none is decided by the import closure alone. On the TypeScript side, the dependencies judged are those of every manifest grammar/analysis/program.md says the analysis reads, a nested package's among them, each read against its own directory: a dependency is needed by a use written in a file at or below the directory of the manifest that declares it where no manifest between them declares a dependency of the same name, so a use below a nested manifest that declares the same dependency needs that nested manifest's declaration only, by a use no one file holds, and as a peer of a needed dependency of the same manifest or of a manifest below it. A dependency a nested package's manifest declares whose installed manifest the analysis cannot find is not judged, because no run requires a nested package to be installed and its commands and peers cannot be read, while a workspace member's and the target's own are judged as above.",
500
505
  details: { dependency_class: "required" },
501
506
  defaultEnabled: true,
502
507
  defaultSeverity: "deny",
@@ -769,6 +774,49 @@ export const SYMBOL_SUBJECTS = {
769
774
  edge: "record",
770
775
  } as const satisfies Readonly<Record<string, Subject>>;
771
776
 
777
+ export const REMEDIES = {
778
+ declaration: {
779
+ names: "a declaration",
780
+ suppressions: ["inline", "ignore", "baseline"],
781
+ remedy: "delete the symbol, or wire it up so the program uses it",
782
+ },
783
+ binding: {
784
+ names: "an import binding",
785
+ suppressions: ["inline", "ignore", "baseline"],
786
+ remedy: "delete the import, or use what it binds",
787
+ },
788
+ part: {
789
+ names: "a part of a declaration",
790
+ suppressions: ["inline", "ignore", "baseline"],
791
+ remedy: "remove the part, or use it",
792
+ },
793
+ file: {
794
+ names: "a file",
795
+ suppressions: [],
796
+ remedy:
797
+ "delete the file, name it as a root, or build it in a configuration of the build matrix",
798
+ },
799
+ dependency: {
800
+ names: "a dependency row",
801
+ suppressions: ["ignore", "baseline"],
802
+ remedy: "remove the dependency",
803
+ },
804
+ record: {
805
+ names: "a record of deadset's own configuration",
806
+ suppressions: [],
807
+ remedy: "correct or remove the record the finding names",
808
+ },
809
+ } as const satisfies Readonly<
810
+ Record<
811
+ Subject,
812
+ {
813
+ readonly names: string;
814
+ readonly suppressions: readonly ("inline" | "ignore" | "baseline")[];
815
+ readonly remedy: string;
816
+ }
817
+ >
818
+ >;
819
+
772
820
  export const EXEMPTION_CLASSES = [
773
821
  {
774
822
  class: "interface-satisfaction",
@@ -1283,6 +1331,93 @@ export const EVIDENCE = [
1283
1331
  },
1284
1332
  ] as const;
1285
1333
 
1334
+ /** The generators whose TypeScript output a generated file is matched against, in the document's order. */
1335
+ export const GENERATORS = [
1336
+ {
1337
+ id: "protoc-gen-es-2",
1338
+ plugin: "protoc-gen-es",
1339
+ header: "^// @generated by protoc-gen-es v2\\.",
1340
+ versions: { min: "v2.10.0", max: "v2.16.0" },
1341
+ cell: "exact",
1342
+ },
1343
+ {
1344
+ id: "protoc-gen-es-2-fallback",
1345
+ plugin: "protoc-gen-es",
1346
+ header: "^// @generated by protoc-gen-es v2\\.",
1347
+ cell: "fallback",
1348
+ },
1349
+ {
1350
+ id: "protoc-gen-es-1",
1351
+ plugin: "protoc-gen-es",
1352
+ header: "^// @generated by protoc-gen-es v1\\.",
1353
+ cell: "exact",
1354
+ },
1355
+ {
1356
+ id: "protoc-gen-connect-es-1",
1357
+ plugin: "protoc-gen-connect-es",
1358
+ header: "^// @generated by protoc-gen-connect-es v1\\.",
1359
+ cell: "exact",
1360
+ },
1361
+ {
1362
+ id: "ts-proto-2-output-schema",
1363
+ plugin: "ts-proto",
1364
+ header: "^// Code generated by protoc-gen-ts_proto\\. DO NOT EDIT\\.$",
1365
+ versions: { min: "v2.11.4", max: "v2.15.0" },
1366
+ cell: "exact",
1367
+ },
1368
+ {
1369
+ id: "ts-proto-2",
1370
+ plugin: "ts-proto",
1371
+ header: "^// Code generated by protoc-gen-ts_proto\\. DO NOT EDIT\\.$",
1372
+ versions: { min: "v2.11.4", max: "v2.15.0" },
1373
+ cell: "rpc-types-fallback",
1374
+ },
1375
+ {
1376
+ id: "ts-proto-2-types",
1377
+ plugin: "ts-proto",
1378
+ header: "^// Code generated by protoc-gen-ts_proto\\. DO NOT EDIT\\.$",
1379
+ versions: { min: "v2.11.4", max: "v2.15.0" },
1380
+ cell: "fallback",
1381
+ },
1382
+ {
1383
+ id: "ts-proto-2-fallback",
1384
+ plugin: "ts-proto",
1385
+ header: "^// Code generated by protoc-gen-ts_proto\\. DO NOT EDIT\\.$",
1386
+ cell: "fallback",
1387
+ },
1388
+ {
1389
+ id: "protoc-gen-js",
1390
+ plugin: "protoc-gen-js",
1391
+ header: "^// GENERATED CODE -- DO NOT EDIT!$",
1392
+ cell: "fallback",
1393
+ },
1394
+ {
1395
+ id: "protoc-gen-grpc-web",
1396
+ plugin: "protoc-gen-grpc-web",
1397
+ header: "^// Code generated by protoc-gen-grpc-web\\. DO NOT EDIT\\.$",
1398
+ cell: "fallback",
1399
+ },
1400
+ {
1401
+ id: "protobuf-ts",
1402
+ plugin: "protobuf-ts",
1403
+ header: "^// @generated by protobuf-ts ",
1404
+ cell: "fallback",
1405
+ },
1406
+ { id: "ts-protoc-gen", plugin: "ts-protoc-gen", header: "^// package: ", cell: "fallback" },
1407
+ {
1408
+ id: "protoc-gen-es-unsupported",
1409
+ plugin: "protoc-gen-es",
1410
+ header: "^// @generated by protoc-gen-es v(?:0|[3-9]|[1-9][0-9]+)\\.",
1411
+ cell: "unsupported",
1412
+ },
1413
+ ] as const satisfies readonly {
1414
+ readonly id: string;
1415
+ readonly plugin: string;
1416
+ readonly header: string;
1417
+ readonly versions?: { readonly min: string; readonly max: string };
1418
+ readonly cell: string;
1419
+ }[];
1420
+
1286
1421
  /** Each group of failure classes, and the text opening the line that names one of its failures. */
1287
1422
  export const FAILURE_PREFIXES = { setup: "setup failure: ", run: "run failure: " } as const;
1288
1423
 
@@ -1294,6 +1429,7 @@ export const FAILURE_CLASSES = {
1294
1429
  "workspace-member-without-source": "setup",
1295
1430
  "convention-not-literal": "setup",
1296
1431
  "no-program": "setup",
1432
+ "unreadable-configuration": "setup",
1297
1433
  "unreadable-document": "setup",
1298
1434
  "missing-import-path": "setup",
1299
1435
  "binding-undecodable": "setup",
@@ -31,14 +31,14 @@ 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
44
  import { nodeKey, type Inventory } from "./inventory.ts";
@@ -55,8 +55,11 @@ import type { DependencySection, Module } from "./ref.ts";
55
55
  import { UNANSWERED } from "./query.ts";
56
56
  import type { Handle, ProjectView } from "./session.ts";
57
57
  import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
58
- import { manifestCommands, nodemonTexts } from "./manifest.ts";
59
- 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";
60
63
  import {
61
64
  pathsNamed,
62
65
  preloadedPackages,
@@ -355,8 +358,9 @@ function bindingsOf(node: Node | undefined): readonly Texted[] {
355
358
  * Every package one project needs, and which declaration each use is written in.
356
359
  * One local walk and one batched resolution per file: a use of an import binding is
357
360
  * an identifier resolving to the binding's own symbol, so a local declaration that
358
- * shadows the name is not one. A string of a configuration file uses each dependency
359
- * 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.
360
364
  */
361
365
  export function projectNeeds<Brand>(
362
366
  project: ProjectView<Brand>,
@@ -364,6 +368,8 @@ export function projectNeeds<Brand>(
364
368
  host: Host,
365
369
  targetRoot: string,
366
370
  parse: ParseFile,
371
+ configuration: ConfigurationFiles,
372
+ workflows: Workflows,
367
373
  ): ProjectNeeds {
368
374
  const programPackages = new Set<string>();
369
375
  for (const name of project.program.getSourceFileNames()) {
@@ -417,7 +423,6 @@ export function projectNeeds<Brand>(
417
423
  return names;
418
424
  };
419
425
  const files = sourceFilesOf(project, targetRoot);
420
- const configuration = configurationFiles(host, targetRoot, files, parse);
421
426
  for (const { file, manifestDir } of configuration.modules) {
422
427
  const names = declaredAt(joinPath(manifestDir, MANIFEST));
423
428
  for (const { node, text } of moduleStrings(file)) {
@@ -437,7 +442,7 @@ export function projectNeeds<Brand>(
437
442
  targetRoot,
438
443
  files,
439
444
  parse,
440
- scriptRunFiles(host, targetRoot, files),
445
+ scriptRunFiles(host, targetRoot, files, workflows),
441
446
  );
442
447
  for (const { file, manifestDirs } of [...configuration.outside, ...ranFiles]) {
443
448
  for (const manifestDir of manifestDirs) {
@@ -460,6 +465,7 @@ function scriptRunFiles(
460
465
  host: Host,
461
466
  targetRoot: string,
462
467
  files: SourceFiles,
468
+ workflows: Workflows,
463
469
  ): readonly { readonly path: string; readonly manifestDir: string }[] {
464
470
  const root = resolvePath(host.workingDirectory(), targetRoot);
465
471
  const dirs = new Set<string>([root]);
@@ -472,12 +478,7 @@ function scriptRunFiles(
472
478
  }
473
479
  }
474
480
  const named = new Map<string, readonly string[]>([
475
- [
476
- root,
477
- workflowReadings(host, root)
478
- .flatMap(pathsNamed)
479
- .map((one) => one.path),
480
- ],
481
+ [root, workflows.readings.flatMap(pathsNamed).map((one) => one.path)],
481
482
  ]);
482
483
  for (const dir of dirs) {
483
484
  const read = readDocument(host, joinPath(dir, MANIFEST));
@@ -522,20 +523,9 @@ function manifestUses(host: Host, dir: string, manifest: Record<string, unknown>
522
523
  }
523
524
 
524
525
  /** What the target root's workflow steps, git hooks and recipes use, held with their files' whole text. */
525
- function workflowUses(host: Host, targetRoot: string): CommandUses {
526
- const uses = usesOf(workflowReadings(host, targetRoot));
527
- return { ...uses, held: [uses.held, ...commandFileTexts(host, targetRoot)].join("\n") };
528
- }
529
-
530
- /**
531
- * What the target root's workflow steps, git hooks, hook managers and recipes run, each held
532
- * with the directory the file writes for it.
533
- */
534
- function workflowReadings(host: Host, targetRoot: string): readonly CommandReading[] {
535
- return workflowCommands(host, targetRoot).map(({ text, dir, writtenDirectory }) => {
536
- const reading = readCommands(text, dir);
537
- return { ...reading, held: [reading.held, writtenDirectory ?? ""].join("\n") };
538
- });
526
+ function workflowUses(workflows: Workflows): CommandUses {
527
+ const uses = usesOf(workflows.readings);
528
+ return { ...uses, held: [uses.held, ...workflows.texts].join("\n") };
539
529
  }
540
530
 
541
531
  /** What some command strings say about the packages they use. */
@@ -661,8 +651,16 @@ function fileNeeds<Brand>(
661
651
  if (names.size > 0 || automatic.globals.size > 0) {
662
652
  visit(file);
663
653
  }
654
+ const loads = runtimeLoads(file);
655
+ const callees = loads.flatMap((load) => load.callees);
664
656
 
665
- 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
+ ];
666
664
  const handles: Handle<Brand>[] = asked.map((node) => project.handle(node));
667
665
  const resolved = project.symbolsAt(handles);
668
666
 
@@ -738,35 +736,83 @@ function fileNeeds<Brand>(
738
736
  for (const reference of file.typeReferenceDirectives) {
739
737
  use(ownersOf(file, held, file), typePackages(reference.fileName, programPackages));
740
738
  }
741
- for (const specifier of resolvedSpecifiers(file)) {
742
- const named = packageOfSpecifier(specifier.text);
743
- 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))) {
744
765
  use(ownersOf(file, held, specifier), [named]);
766
+ } else if (through.some((one) => unansweredCallees.has(one))) {
767
+ use([PROJECT_OWNER], [named]);
745
768
  }
746
769
  }
747
770
  return resolved.slice(0, specifiers.length).includes(UNANSWERED);
748
771
  }
749
772
 
750
773
  /**
751
- * The literal specifiers one file asks the runtime to resolve, through `import.meta.resolve`
752
- * 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.
753
777
  */
754
- function resolvedSpecifiers(file: SourceFile): readonly Texted[] {
755
- const found: Texted[] = [];
756
- if (!file.text.includes(".resolve(")) {
757
- return found;
758
- }
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[] = [];
759
793
  const visit = (node: Node): void => {
760
- if (isCallExpression(node) && isPropertyAccessExpression(node.expression)) {
761
- const callee = node.expression;
794
+ if (isCallExpression(node)) {
762
795
  const [argument] = node.arguments;
763
- const receiver = callee.expression;
764
- const resolving =
765
- callee.name.text === "resolve" &&
766
- ((isMetaProperty(receiver) && receiver.name.text === "meta") ||
767
- (isIdentifier(receiver) && receiver.text === "require"));
768
- if (resolving && argument !== undefined && isLiteralText(argument)) {
769
- 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
+ }
770
816
  }
771
817
  }
772
818
  node.forEachChild(visit);
@@ -989,6 +1035,7 @@ function addNeededPeers(
989
1035
  export function dependenciesOf(
990
1036
  host: Host,
991
1037
  targetRoot: string,
1038
+ workflows: Workflows,
992
1039
  perProject: readonly ProjectNeeds[],
993
1040
  candidates: readonly string[],
994
1041
  members: readonly string[] = [],
@@ -1011,7 +1058,7 @@ export function dependenciesOf(
1011
1058
  unbuilt.add(path);
1012
1059
  }
1013
1060
  }
1014
- const workflow = workflowUses(host, targetRoot);
1061
+ const workflow = workflowUses(workflows);
1015
1062
  const declared: DeclaredDependency[] = [];
1016
1063
  const ran = new Set<string>();
1017
1064
  const peers = new Map<string, readonly string[]>();