@cplieger/deadset-ts 8.0.0-dev.1 → 8.0.0-dev.11

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 (276) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/mapper-connection.js +2 -1
  3. package/dist/bin/node-host.js +24 -5
  4. package/dist/src/analysis.js +181 -341
  5. package/dist/src/augmentations.js +106 -13
  6. package/dist/src/automatic-types.js +10 -29
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +2 -3
  10. package/dist/src/config.js +6 -82
  11. package/dist/src/configuration-files.js +28 -176
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +1 -1
  14. package/dist/src/consumers.js +3 -30
  15. package/dist/src/contextual-uses.js +56 -9
  16. package/dist/src/contract/config.generated.js +558 -0
  17. package/dist/src/contract/contract.generated.js +1244 -0
  18. package/dist/src/contract/report.generated.js +862 -0
  19. package/dist/src/convention-rows.js +73 -3
  20. package/dist/src/conventions.js +36 -66
  21. package/dist/src/dependencies.js +103 -40
  22. package/dist/src/edges.js +38 -122
  23. package/dist/src/element-access.js +4 -6
  24. package/dist/src/entry-point-gaps.js +2 -8
  25. package/dist/src/entry-points.js +11 -27
  26. package/dist/src/enum-group.js +3 -10
  27. package/dist/src/erased-stores.js +4 -6
  28. package/dist/src/exempt-classes.js +1 -63
  29. package/dist/src/exempt.js +21 -40
  30. package/dist/src/failure.js +36 -0
  31. package/dist/src/file-facts.js +2 -25
  32. package/dist/src/findings/completion.js +139 -72
  33. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  34. package/dist/src/findings/emitters.js +5 -5
  35. package/dist/src/findings/interfaces.js +4 -5
  36. package/dist/src/findings/intra-function.js +13 -10
  37. package/dist/src/findings/non-code-artifacts.js +7 -8
  38. package/dist/src/findings/reads-and-writes.js +16 -13
  39. package/dist/src/findings/self-check.js +10 -12
  40. package/dist/src/findings/unused-declarations.js +41 -32
  41. package/dist/src/findings/visibility-narrowing.js +19 -16
  42. package/dist/src/findings-pass.js +8 -15
  43. package/dist/src/framework-lifecycle.js +7 -7
  44. package/dist/src/fs/document.js +52 -0
  45. package/dist/src/fs/manifest.js +72 -0
  46. package/dist/src/fs/tree.js +88 -0
  47. package/dist/src/generated-file.js +7 -4
  48. package/dist/src/git-ignore.js +39 -26
  49. package/dist/src/graph.js +5 -3
  50. package/dist/src/html-entries.js +6 -33
  51. package/dist/src/in-operands.js +2 -1
  52. package/dist/src/index.js +1 -1
  53. package/dist/src/injection-container.js +1 -1
  54. package/dist/src/intra-function-parts.js +63 -43
  55. package/dist/src/inventory.js +2 -21
  56. package/dist/src/javascript-file.js +33 -0
  57. package/dist/src/json-document.js +198 -67
  58. package/dist/src/keyof-comparisons.js +4 -6
  59. package/dist/src/kinds.js +3 -366
  60. package/dist/src/load/classify.js +270 -0
  61. package/dist/src/load/diagnostics.js +80 -0
  62. package/dist/src/load/discover.js +277 -0
  63. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  64. package/dist/src/load/expected-module.js +136 -0
  65. package/dist/src/load/load.js +27 -0
  66. package/dist/src/load/module-lookup.js +37 -0
  67. package/dist/src/load/plan.js +313 -0
  68. package/dist/src/load/specifiers.js +115 -0
  69. package/dist/src/load/synthesize.js +140 -0
  70. package/dist/src/load/test-globals.js +207 -0
  71. package/dist/src/load/test-joins.js +82 -0
  72. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  73. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +89 -66
  74. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +23 -65
  75. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  76. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  77. package/dist/src/manifest.js +49 -54
  78. package/dist/src/matrix.js +76 -4
  79. package/dist/src/mdx-entries.js +3 -26
  80. package/dist/src/outside-tests.js +1 -1
  81. package/dist/src/partial.js +2 -1
  82. package/dist/src/paths.js +26 -15
  83. package/dist/src/position.js +2 -1
  84. package/dist/src/print.js +11 -79
  85. package/dist/src/product.js +13 -0
  86. package/dist/src/query.js +1 -0
  87. package/dist/src/ref.js +1 -0
  88. package/dist/src/references.js +48 -30
  89. package/dist/src/report-schema.js +51 -0
  90. package/dist/src/report.js +57 -41
  91. package/dist/src/reporters/annotations.js +15 -8
  92. package/dist/src/reporters/sarif.js +16 -10
  93. package/dist/src/reporters/template.js +4 -19
  94. package/dist/src/resolve.js +245 -729
  95. package/dist/src/roots.js +273 -87
  96. package/dist/src/run.js +66 -77
  97. package/dist/src/schema.js +320 -343
  98. package/dist/src/scope.js +38 -115
  99. package/dist/src/serialization-contract.js +595 -21
  100. package/dist/src/session.js +34 -29
  101. package/dist/src/source-files.js +1 -1
  102. package/dist/src/suppress-file.js +25 -28
  103. package/dist/src/suppress-inline.js +3 -2
  104. package/dist/src/suppress.js +33 -51
  105. package/dist/src/sweep.js +7 -9
  106. package/dist/src/symbol-keys.js +3 -5
  107. package/dist/src/template-field.js +9 -19
  108. package/dist/src/test-support.js +8 -11
  109. package/dist/src/text/compare.js +26 -0
  110. package/dist/src/text/glob.js +194 -0
  111. package/dist/src/text/named-files.js +54 -0
  112. package/dist/src/text/shell.js +159 -0
  113. package/dist/src/text/yaml.js +393 -0
  114. package/dist/src/type-errors.js +11 -232
  115. package/dist/src/unseen-loads.js +100 -0
  116. package/dist/src/unwrap.js +23 -0
  117. package/dist/src/usage.js +10 -0
  118. package/dist/src/verbs/analyze.js +166 -126
  119. package/dist/src/verbs/describe.js +9 -16
  120. package/dist/src/verbs/explain.js +10 -7
  121. package/dist/src/verbs/print-config.js +2 -1
  122. package/dist/src/verbs/print-projects.js +6 -4
  123. package/dist/src/verbs/print-retained.js +4 -2
  124. package/dist/src/verbs/print-roots.js +4 -2
  125. package/dist/src/verbs/verb.js +1 -5
  126. package/dist/src/verbs/version.js +14 -3
  127. package/dist/src/workflow-steps.js +62 -64
  128. package/jsr.json +1 -1
  129. package/package.json +5 -4
  130. package/src/analysis.ts +245 -502
  131. package/src/augmentations.ts +135 -17
  132. package/src/automatic-types.ts +11 -29
  133. package/src/build-scripts.ts +543 -0
  134. package/src/component-layer.ts +4 -2
  135. package/src/components.ts +2 -4
  136. package/src/config.ts +31 -302
  137. package/src/configuration-files.ts +34 -179
  138. package/src/configured-declarations.ts +18 -23
  139. package/src/conformance-record.ts +1 -1
  140. package/src/consumers.ts +6 -57
  141. package/src/contextual-uses.ts +64 -12
  142. package/src/contract/config.generated.ts +818 -0
  143. package/src/contract/contract.generated.ts +1308 -0
  144. package/src/contract/report.generated.ts +1237 -0
  145. package/src/convention-rows.ts +85 -3
  146. package/src/conventions.ts +46 -84
  147. package/src/dependencies.ts +118 -46
  148. package/src/edges.ts +41 -140
  149. package/src/element-access.ts +4 -7
  150. package/src/entry-point-gaps.ts +3 -9
  151. package/src/entry-points.ts +12 -38
  152. package/src/enum-group.ts +2 -11
  153. package/src/erased-stores.ts +3 -7
  154. package/src/exempt-classes.ts +1 -75
  155. package/src/exempt.ts +36 -50
  156. package/src/failure.ts +63 -0
  157. package/src/file-facts.ts +9 -24
  158. package/src/finding.ts +14 -9
  159. package/src/findings/boundary.ts +2 -0
  160. package/src/findings/completion.ts +181 -84
  161. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  162. package/src/findings/emitter.ts +8 -9
  163. package/src/findings/emitters.ts +7 -7
  164. package/src/findings/interfaces.ts +4 -6
  165. package/src/findings/intra-function.ts +13 -11
  166. package/src/findings/non-code-artifacts.ts +9 -11
  167. package/src/findings/reads-and-writes.ts +20 -16
  168. package/src/findings/self-check.ts +11 -17
  169. package/src/findings/unused-declarations.ts +45 -32
  170. package/src/findings/visibility-narrowing.ts +20 -17
  171. package/src/findings-pass.ts +10 -19
  172. package/src/framework-lifecycle.ts +7 -7
  173. package/src/fs/document.ts +70 -0
  174. package/src/fs/manifest.ts +92 -0
  175. package/src/fs/tree.ts +110 -0
  176. package/src/generated-file.ts +7 -4
  177. package/src/git-ignore.ts +50 -26
  178. package/src/graph.ts +5 -3
  179. package/src/host.ts +12 -5
  180. package/src/html-entries.ts +6 -33
  181. package/src/in-operands.ts +2 -1
  182. package/src/index.ts +1 -1
  183. package/src/injection-container.ts +1 -1
  184. package/src/intra-function-parts.ts +73 -44
  185. package/src/inventory.ts +2 -20
  186. package/src/javascript-file.ts +37 -0
  187. package/src/json-document.ts +241 -76
  188. package/src/keyof-comparisons.ts +3 -7
  189. package/src/kinds.ts +5 -413
  190. package/src/load/classify.ts +397 -0
  191. package/src/load/diagnostics.ts +144 -0
  192. package/src/load/discover.ts +455 -0
  193. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  194. package/src/load/expected-module.ts +188 -0
  195. package/src/load/load.ts +73 -0
  196. package/src/load/module-lookup.ts +50 -0
  197. package/src/load/plan.ts +504 -0
  198. package/src/load/specifiers.ts +168 -0
  199. package/src/load/synthesize.ts +211 -0
  200. package/src/load/test-globals.ts +251 -0
  201. package/src/load/test-joins.ts +98 -0
  202. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  203. package/src/{workspace-programs.ts → load/workspace-programs.ts} +128 -85
  204. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +33 -76
  205. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  206. package/src/{workspace.ts → load/workspace.ts} +90 -134
  207. package/src/manifest.ts +81 -75
  208. package/src/matrix.ts +122 -4
  209. package/src/mdx-entries.ts +3 -26
  210. package/src/outside-tests.ts +1 -1
  211. package/src/partial.ts +3 -2
  212. package/src/paths.ts +27 -15
  213. package/src/position.ts +2 -1
  214. package/src/print.ts +12 -92
  215. package/src/product.ts +18 -0
  216. package/src/query.ts +11 -0
  217. package/src/ref.ts +1 -0
  218. package/src/references.ts +52 -31
  219. package/src/report-schema.ts +68 -0
  220. package/src/report.ts +101 -224
  221. package/src/reporters/annotations.ts +22 -11
  222. package/src/reporters/json.ts +1 -1
  223. package/src/reporters/reporter.ts +2 -4
  224. package/src/reporters/reporters.ts +1 -1
  225. package/src/reporters/sarif.ts +29 -16
  226. package/src/reporters/template.ts +4 -20
  227. package/src/reporters/text.ts +3 -3
  228. package/src/resolve.ts +283 -1114
  229. package/src/roots.ts +339 -104
  230. package/src/run.ts +75 -91
  231. package/src/schema.ts +391 -388
  232. package/src/scope.ts +46 -138
  233. package/src/serialization-contract.ts +763 -22
  234. package/src/session.ts +70 -54
  235. package/src/source-files.ts +1 -1
  236. package/src/suppress-file.ts +24 -33
  237. package/src/suppress-inline.ts +2 -2
  238. package/src/suppress.ts +43 -58
  239. package/src/sweep.ts +23 -9
  240. package/src/symbol-keys.ts +2 -5
  241. package/src/template-field.ts +15 -25
  242. package/src/test-support.ts +17 -13
  243. package/src/text/compare.ts +27 -0
  244. package/src/text/glob.ts +210 -0
  245. package/src/text/named-files.inputs.json +1522 -0
  246. package/src/text/named-files.ts +68 -0
  247. package/src/text/shell.ts +161 -0
  248. package/src/text/yaml.ts +423 -0
  249. package/src/type-errors.ts +18 -288
  250. package/src/unseen-loads.ts +134 -0
  251. package/src/unwrap.ts +34 -0
  252. package/src/usage.ts +10 -0
  253. package/src/verbs/analyze.ts +224 -162
  254. package/src/verbs/describe.ts +14 -15
  255. package/src/verbs/explain.ts +10 -11
  256. package/src/verbs/print-config.ts +2 -1
  257. package/src/verbs/print-projects.ts +6 -4
  258. package/src/verbs/print-retained.ts +4 -2
  259. package/src/verbs/print-roots.ts +4 -2
  260. package/src/verbs/verb.ts +2 -8
  261. package/src/verbs/version.ts +15 -3
  262. package/src/workflow-steps.ts +74 -64
  263. package/dist/src/base-url.js +0 -18
  264. package/dist/src/discover.js +0 -468
  265. package/dist/src/glob.js +0 -65
  266. package/dist/src/pnpm-workspace.js +0 -120
  267. package/dist/src/setup-failure.js +0 -23
  268. package/dist/src/test-joins.js +0 -101
  269. package/dist/src/version.js +0 -7
  270. package/src/base-url.ts +0 -24
  271. package/src/discover.ts +0 -653
  272. package/src/glob.ts +0 -67
  273. package/src/pnpm-workspace.ts +0 -135
  274. package/src/setup-failure.ts +0 -39
  275. package/src/test-joins.ts +0 -120
  276. package/src/version.ts +0 -7
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The test files a derived matrix holds although no configuration's file list names them.
3
+ * The test runner compiles such a file with the options of the nearest compiler
4
+ * configuration at or above it, so the file joins that configuration's program as a file
5
+ * its list names, unless a `package.json` below that configuration's directory makes the
6
+ * file another package's, which its own test runner compiles.
7
+ */
8
+
9
+ import { sourcePaths } from "../file-facts.ts";
10
+ import type { Host } from "../host.ts";
11
+ import { ancestors, dirnamePath, joinPath } from "../paths.ts";
12
+ import { bytewise } from "../text/compare.ts";
13
+ import { holdsManifest } from "../fs/manifest.ts";
14
+ import type { Configuration } from "./discover.ts";
15
+ import type { ProgramPlan } from "./plan.ts";
16
+
17
+ const PREFERRED = "/tsconfig.json";
18
+
19
+ /** The configurations of one directory in the order a file joins them: `tsconfig.json` first. */
20
+ function byPreference(a: string, b: string): number {
21
+ const first = a.endsWith(PREFERRED) ? 0 : 1;
22
+ const second = b.endsWith(PREFERRED) ? 0 : 1;
23
+ return first - second || bytewise(a, b);
24
+ }
25
+
26
+ /**
27
+ * Per configuration of `configurations`, the absolute paths of the test files that join it:
28
+ * each file below `root` a pattern of `patterns` names, that no configuration's file list
29
+ * holds, joins the configuration nearest at or above its directory when no `package.json`
30
+ * sits between them.
31
+ */
32
+ export function joinedTestFiles(
33
+ host: Host,
34
+ root: string,
35
+ configurations: readonly Configuration[],
36
+ patterns: readonly RegExp[],
37
+ ): ReadonlyMap<string, readonly string[]> {
38
+ const listed = new Set<string>();
39
+ const byDir = new Map<string, string[]>();
40
+ for (const { configFile, parsed } of configurations) {
41
+ parsed.fileNames.forEach((name) => listed.add(name));
42
+ const dir = dirnamePath(configFile);
43
+ byDir.set(dir, [...(byDir.get(dir) ?? []), configFile].sort(byPreference));
44
+ }
45
+ const joins = new Map<string, string[]>();
46
+ for (const path of sourcePaths(host, root)) {
47
+ const file = joinPath(root, path);
48
+ if (listed.has(file) || !patterns.some((pattern) => pattern.test(path))) {
49
+ continue;
50
+ }
51
+ for (const dir of ancestors(dirnamePath(file), root)) {
52
+ const [nearest] = byDir.get(dir) ?? [];
53
+ if (nearest !== undefined) {
54
+ joins.set(nearest, [...(joins.get(nearest) ?? []), file]);
55
+ break;
56
+ }
57
+ if (holdsManifest(host, dir)) {
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ return joins;
63
+ }
64
+
65
+ /**
66
+ * The plans with each joined test file added to the configuration it joins: to a described
67
+ * program's root files, or to the file list of a configuration only the snapshot holds, which
68
+ * lists every file the configuration lists and the joined ones.
69
+ */
70
+ export function joinedTests(
71
+ plans: readonly ProgramPlan[],
72
+ joins: ReadonlyMap<string, readonly string[]>,
73
+ ): readonly ProgramPlan[] {
74
+ return plans.map((plan): ProgramPlan => {
75
+ const files = joins.get(plan.configFile);
76
+ const { build } = plan;
77
+ if (files === undefined) {
78
+ return plan;
79
+ }
80
+ if (build.kind === "program") {
81
+ return {
82
+ ...plan,
83
+ build: {
84
+ kind: "program",
85
+ params: { ...build.params, rootFiles: [...(build.params.rootFiles ?? []), ...files] },
86
+ },
87
+ };
88
+ }
89
+ const written = build.kind === "configuration" ? build.value : { extends: plan.configFile };
90
+ return {
91
+ ...plan,
92
+ build: {
93
+ kind: "configuration",
94
+ value: { ...written, files: [...plan.parsed.fileNames, ...files], include: [] },
95
+ },
96
+ };
97
+ });
98
+ }
@@ -7,11 +7,12 @@
7
7
  */
8
8
 
9
9
  import { emittedFrom } from "./emit-map.ts";
10
- import type { Host } from "./host.ts";
11
- import { readManifest, type Manifest, type ManifestEntry } from "./manifest.ts";
12
- import { joinPath, relativePath } from "./paths.ts";
10
+ import type { Host } from "../host.ts";
11
+ import { readManifest, type Manifest, type ManifestEntry } from "../manifest.ts";
12
+ import { joinPath, relativePath } from "../paths.ts";
13
13
  import type { WorkspaceMember } from "./workspace.ts";
14
14
  import type { WorkspaceResolver } from "./workspace-resolution.ts";
15
+ import { MANIFEST } from "../fs/manifest.ts";
15
16
 
16
17
  /** The one character a subpath pattern of a manifest's `exports` holds. */
17
18
  const SUBPATH_WILDCARD = "*";
@@ -84,8 +85,7 @@ export function memberEntries(
84
85
  }
85
86
  const manifest = readManifest(host, member.dir);
86
87
  const where =
87
- relativePath(targetRoot, joinPath(member.dir, "package.json")) ??
88
- joinPath(member.dir, "package.json");
88
+ relativePath(targetRoot, joinPath(member.dir, MANIFEST)) ?? joinPath(member.dir, MANIFEST);
89
89
  const kept = manifest.entries.flatMap((entry): ManifestEntry[] => {
90
90
  const exported = entry.member.startsWith("exports");
91
91
  if (member.private && exported) {
@@ -16,11 +16,16 @@ import type {
16
16
  } from "@typescript/native/sync";
17
17
  import { isStringLiteralLikeNode } from "@typescript/native/ast";
18
18
  import type { StringLiteralLikeNode } from "@typescript/native/ast";
19
- import { baseUrlPaths } from "./base-url.ts";
20
- import type { Host } from "./host.ts";
21
- import { dirnamePath, resolvePath } from "./paths.ts";
22
- import type { Engine } from "./session.ts";
19
+ import type { Host } from "../host.ts";
20
+ import { JAVASCRIPT_FILE } from "../javascript-file.ts";
21
+ import { isRecord } from "../json-document.ts";
22
+ import { dirnamePath, resolvePath } from "../paths.ts";
23
+ import { batched, type Engine, type ProgramBuild } from "../session.ts";
23
24
  import type { WorkspaceResolver } from "./workspace-resolution.ts";
25
+ import { bytewise } from "../text/compare.ts";
26
+ import { PRODUCT } from "../product.ts";
27
+ import type { Parsed } from "./discover.ts";
28
+ import type { Build, CompilerOptions } from "./plan.ts";
24
29
 
25
30
  /** One member import no member source answers for. */
26
31
  interface UnresolvedImport {
@@ -43,32 +48,35 @@ export interface MemberReading {
43
48
  readonly componentsUnread: string | undefined;
44
49
  }
45
50
 
46
- /** How a snapshot reads one configuration that imports a member. */
47
- export type WorkspaceProgram =
48
- | { readonly kind: "program"; readonly params: CreateSnapshotProgramParams }
49
- /** A configuration only the snapshot holds, at a path no file has. */
50
- | { readonly kind: "configuration"; readonly file: string; readonly text: string };
51
+ /** One configuration as a written configuration or a described program. */
52
+ type Written = Exclude<Build, { readonly kind: "project" }>;
51
53
 
52
54
  /** The programs one run builds instead of opening projects, with what reading them did. */
53
55
  interface WorkspacePrograms {
54
56
  /** Per configuration that imports a member, how the snapshot reads it. */
55
- readonly programs: ReadonlyMap<string, WorkspaceProgram>;
57
+ readonly programs: ReadonlyMap<string, Written>;
56
58
  readonly readings: readonly MemberReading[];
57
59
  }
58
60
 
59
- type Parsed = ReturnType<Engine["parseConfigFile"]>;
60
-
61
- /** The most questions one round trip carries. */
62
- const BATCH = 2048;
61
+ /** One configuration the rounds read: its parse and the options the plan builds it with. */
62
+ export interface PlannedConfiguration {
63
+ readonly configFile: string;
64
+ readonly parsed: Parsed;
65
+ readonly options: CompilerOptions;
66
+ /** The configuration of a program the run forms, which only the snapshot holds, at `configFile`. */
67
+ readonly written?: Readonly<Record<string, unknown>> | undefined;
68
+ }
63
69
 
64
70
  /**
65
- * The options a configuration's program is built with: its own, less the ones that
66
- * lay out an emitted program's inputs and outputs, and with `paths` read against the
67
- * effective `baseUrl` as {@link baseUrlPaths} states. The analysis emits nothing, and a
68
- * member's source is outside the importer's root directory by construction, which
69
- * the compiler refuses under such a layout.
71
+ * The options a configuration's program is built with: the plan's, less the ones that lay
72
+ * out an emitted program's inputs and outputs. The analysis emits nothing, and a member's
73
+ * source is outside the importer's root directory by construction, which the compiler
74
+ * refuses under such a layout.
70
75
  */
71
- function analysisOptions(parsed: Parsed): CreateSnapshotProgramParams["compilerOptions"] {
76
+ function analysisOptions(
77
+ options: CompilerOptions,
78
+ readsJavaScript: boolean,
79
+ ): CreateSnapshotProgramParams["compilerOptions"] {
72
80
  const {
73
81
  composite,
74
82
  rootDir: _rootDir,
@@ -76,10 +84,24 @@ function analysisOptions(parsed: Parsed): CreateSnapshotProgramParams["compilerO
76
84
  declarationDir: _declarationDir,
77
85
  tsBuildInfoFile: _tsBuildInfoFile,
78
86
  ...kept
79
- } = parsed.options;
80
- const paths = baseUrlPaths(parsed.options);
81
- const options = paths === undefined ? kept : { ...kept, paths };
82
- return composite === true ? { ...options, declaration: true } : options;
87
+ } = options;
88
+ const read = readsJavaScript ? { ...kept, allowJs: true } : kept;
89
+ return composite === true ? { ...read, declaration: true } : read;
90
+ }
91
+
92
+ /**
93
+ * Whether one reading answers a member import with a JavaScript file, which its program reads
94
+ * as source only where it allows JavaScript, a program formed for a JavaScript member being the
95
+ * member's source.
96
+ */
97
+ function readsJavaScript(reading: Reading): boolean {
98
+ return (
99
+ reading.options.allowJs !== true &&
100
+ reading.entries.some((entry) => {
101
+ const file = entry.result.resolvedFileName;
102
+ return typeof file === "string" && JAVASCRIPT_FILE.test(file);
103
+ })
104
+ );
83
105
  }
84
106
 
85
107
  /** The conditions an `exports` lookup under one configuration and one resolution mode reads. */
@@ -101,9 +123,7 @@ function resolutionMode(kind: ModuleKind): ResolutionMode | undefined {
101
123
  }
102
124
 
103
125
  /** One configuration's state across the rounds. */
104
- interface Reading {
105
- readonly configFile: string;
106
- readonly parsed: Parsed;
126
+ interface Reading extends PlannedConfiguration {
107
127
  readonly entries: ModuleResolutionEntry[];
108
128
  readonly seen: Set<string>;
109
129
  /** Per member specifier, every file an import of it is read as. */
@@ -125,38 +145,60 @@ function unstatable(reading: Reading): string | undefined {
125
145
  return undefined;
126
146
  }
127
147
 
128
- /** The path beside `configFile` its written configuration takes, which no file has. */
129
- export function writtenPath(host: Host, configFile: string): string {
148
+ /** The path beside `configFile` its written configuration takes, which no file and none of `taken` has. */
149
+ export function writtenPath(
150
+ host: Host,
151
+ configFile: string,
152
+ taken: ReadonlySet<string> = new Set(),
153
+ ): string {
130
154
  const stem = configFile.replace(/\.json$/u, "");
131
155
  for (let counter = 0; ; counter += 1) {
132
- const path = `${stem}.deadset-ts${counter === 0 ? "" : `-${String(counter)}`}.json`;
133
- if (host.kindOf(path) === "absent") {
156
+ const path = `${stem}.${PRODUCT}${counter === 0 ? "" : `-${String(counter)}`}.json`;
157
+ if (host.kindOf(path) === "absent" && !taken.has(path)) {
134
158
  return path;
135
159
  }
136
160
  }
137
161
  }
138
162
 
139
163
  /**
140
- * The configuration that reads as one configuration's program: it extends it, lays out
141
- * no output for the reason {@link analysisOptions} gives, maps each answered specifier
142
- * to its file, and carries the references the program does. The compiler's default
143
- * `exclude` names the output directories this drops, so where the file list moves it is
144
- * restated; a list that still differs, or answers `paths` cannot state, are a reason.
164
+ * The path the snapshot holds one configuration's written form at: a formed configuration's
165
+ * own path, else a free one beside it that no configuration of `formed` holds, because a formed
166
+ * program in a directory with a `jsconfig.json` takes the name that file's written form would.
167
+ */
168
+ export function writtenFileOf(
169
+ host: Host,
170
+ configuration: Pick<PlannedConfiguration, "configFile" | "written">,
171
+ formed: ReadonlySet<string>,
172
+ ): string {
173
+ return configuration.written === undefined
174
+ ? writtenPath(host, configuration.configFile, formed)
175
+ : configuration.configFile;
176
+ }
177
+
178
+ /**
179
+ * The configuration that reads as one configuration's program: it extends it, lays out no output
180
+ * ({@link analysisOptions}), maps each answered specifier to its file and keeps its references.
181
+ * Where the file list moves, the default `exclude` the dropped output directories are named in is
182
+ * restated, and one that {@link readsJavaScript} lists its files outright, as `allowJs` widens its
183
+ * `include` to JavaScript; a list that still differs, or answers `paths` cannot state, are a
184
+ * reason. A formed configuration, which no file holds to extend, is its own value with `paths`.
145
185
  */
146
186
  function configurationOf(
147
187
  engine: Engine,
148
188
  host: Host,
149
189
  reading: Reading,
150
- ): WorkspaceProgram | { readonly kind: "unread"; readonly reason: string } {
190
+ formed: ReadonlySet<string>,
191
+ ): Written | { readonly kind: "unread"; readonly reason: string } {
151
192
  const reason = unstatable(reading);
152
193
  if (reason !== undefined) {
153
194
  return { kind: "unread", reason };
154
195
  }
155
- const { options, projectReferences: references = [] } = reading.parsed;
196
+ const { options } = reading;
197
+ const references = reading.parsed.projectReferences ?? [];
156
198
  const raw: unknown = (options as Record<string, unknown>)["pathsBasePath"];
157
199
  const base = typeof raw === "string" ? raw : dirnamePath(reading.configFile);
158
200
  const paths: Record<string, string[]> = {};
159
- for (const [key, targets] of Object.entries(baseUrlPaths(options) ?? options.paths ?? {})) {
201
+ for (const [key, targets] of Object.entries(options.paths ?? {})) {
160
202
  paths[key] = targets.map((target) => resolvePath(base, target));
161
203
  }
162
204
  for (const entry of reading.entries) {
@@ -165,6 +207,17 @@ function configurationOf(
165
207
  paths[entry.moduleName] = [file];
166
208
  }
167
209
  }
210
+ if (reading.written !== undefined) {
211
+ const held = reading.written["compilerOptions"];
212
+ return {
213
+ kind: "configuration",
214
+ value: {
215
+ ...reading.written,
216
+ compilerOptions: { ...(isRecord(held) ? held : {}), paths },
217
+ },
218
+ };
219
+ }
220
+ const javascript = readsJavaScript(reading);
168
221
  const json = (exclude: readonly string[] | undefined): Record<string, unknown> => ({
169
222
  extends: reading.configFile,
170
223
  compilerOptions: {
@@ -173,18 +226,20 @@ function configurationOf(
173
226
  declarationDir: null,
174
227
  tsBuildInfoFile: null,
175
228
  ...(options.composite === true ? { composite: false, declaration: true } : {}),
229
+ ...(javascript ? { allowJs: true } : {}),
176
230
  paths,
177
231
  },
178
232
  ...(references.length > 0
179
233
  ? { references: references.map((reference) => ({ path: reference.path })) }
180
234
  : {}),
235
+ ...(javascript ? { files: [...reading.parsed.fileNames], include: [] } : {}),
181
236
  ...(exclude === undefined ? {} : { exclude }),
182
237
  });
183
- const file = writtenPath(host, reading.configFile);
184
- const expected = [...reading.parsed.fileNames].sort().join("\u0000");
238
+ const file = writtenFileOf(host, reading, formed);
239
+ const expected = [...reading.parsed.fileNames].sort(bytewise).join("\u0000");
185
240
  const listed = (value: Record<string, unknown>): string | undefined => {
186
241
  try {
187
- return [...engine.parseConfigJson(value, file).fileNames].sort().join("\u0000");
242
+ return [...engine.parseConfigJson(value, file).fileNames].sort(bytewise).join("\u0000");
188
243
  } catch {
189
244
  return undefined;
190
245
  }
@@ -202,7 +257,7 @@ function configurationOf(
202
257
  };
203
258
  }
204
259
  }
205
- return { kind: "configuration", file, text: `${JSON.stringify(value, null, 2)}\n` };
260
+ return { kind: "configuration", value };
206
261
  }
207
262
 
208
263
  /** The program one configuration is built as, under the answers it holds so far. */
@@ -210,7 +265,7 @@ function programOf(
210
265
  engine: Engine,
211
266
  reading: Reading,
212
267
  ): { readonly params: CreateSnapshotProgramParams; readonly resolver: ModuleResolver } {
213
- const compilerOptions = analysisOptions(reading.parsed);
268
+ const compilerOptions = analysisOptions(reading.options, readsJavaScript(reading));
214
269
  const references = reading.parsed.projectReferences ?? [];
215
270
  const resolver = engine.createModuleResolver(compilerOptions, reading.entries);
216
271
  return {
@@ -227,23 +282,6 @@ function programOf(
227
282
  };
228
283
  }
229
284
 
230
- function chunks<T>(items: readonly T[]): readonly (readonly T[])[] {
231
- const found: T[][] = [];
232
- for (let at = 0; at < items.length; at += BATCH) {
233
- found.push(items.slice(at, at + BATCH));
234
- }
235
- return found;
236
- }
237
-
238
- /** Asks one question of every item, in round trips of at most {@link BATCH}. */
239
- function batched<T, A>(
240
- engine: Engine,
241
- items: readonly T[],
242
- question: (item: T) => Parameters<Engine["batch"]>[0][number],
243
- ): A[] {
244
- return chunks(items).flatMap((run) => engine.batch(run.map(question)) as A[]);
245
- }
246
-
247
285
  /** One round over one program: answers the member imports no earlier round asked about. */
248
286
  function scan(
249
287
  engine: Engine,
@@ -336,6 +374,18 @@ type Built =
336
374
  }
337
375
  | { readonly kind: "configuration"; readonly file: string; readonly text: string };
338
376
 
377
+ function configurationText(value: Readonly<Record<string, unknown>>): string {
378
+ return `${JSON.stringify(value, null, 2)}\n`;
379
+ }
380
+
381
+ /** One configuration value as the snapshot holds it, at `file`, which no file on disk holds. */
382
+ export function writtenConfiguration(
383
+ file: string,
384
+ value: Readonly<Record<string, unknown>>,
385
+ ): Extract<ProgramBuild, { readonly kind: "configuration" }> {
386
+ return { kind: "configuration", file, text: configurationText(value) };
387
+ }
388
+
339
389
  /**
340
390
  * Answers the member imports of every reading `build` describes, round by round, all
341
391
  * the readings still adding answers built in one snapshot per round. A reading `build`
@@ -391,45 +441,38 @@ function readRounds(
391
441
  export function workspacePrograms(
392
442
  engine: Engine,
393
443
  host: Host,
394
- configFiles: readonly string[],
444
+ configurations: readonly PlannedConfiguration[],
395
445
  resolver: WorkspaceResolver,
396
446
  ): WorkspacePrograms {
397
- const readings: Reading[] = configFiles.flatMap((configFile) => {
398
- let parsed: Parsed;
399
- try {
400
- parsed = engine.parseConfigFile(configFile);
401
- } catch {
402
- // A configuration that does not read is opened as a project, which states why.
403
- return [];
404
- }
405
- return [
406
- {
407
- configFile,
408
- parsed,
409
- entries: [],
410
- seen: new Set<string>(),
411
- bound: new Map<string, Set<string>>(),
412
- unresolved: [],
413
- },
414
- ];
415
- });
447
+ const readings: Reading[] = configurations.map((configuration) => ({
448
+ ...configuration,
449
+ entries: [],
450
+ seen: new Set<string>(),
451
+ bound: new Map<string, Set<string>>(),
452
+ unresolved: [],
453
+ }));
454
+ const formed = new Set(
455
+ configurations.filter((one) => one.written !== undefined).map((one) => one.configFile),
456
+ );
416
457
  readRounds(engine, readings, resolver, (reading) => ({
417
458
  kind: "program",
418
459
  ...programOf(engine, reading),
419
460
  }));
420
461
  if (engine.readsComponents) {
421
462
  readRounds(engine, readings, resolver, (reading) => {
422
- const one = configurationOf(engine, host, reading);
423
- return one.kind === "configuration" ? one : undefined;
463
+ const one = configurationOf(engine, host, reading, formed);
464
+ return one.kind === "configuration"
465
+ ? writtenConfiguration(writtenFileOf(host, reading, formed), one.value)
466
+ : undefined;
424
467
  });
425
468
  }
426
- const programs = new Map<string, WorkspaceProgram>();
469
+ const programs = new Map<string, Written>();
427
470
  const unread = new Map<string, string>();
428
471
  for (const reading of readings) {
429
472
  if (reading.entries.length === 0) {
430
473
  continue;
431
474
  }
432
- const one = engine.readsComponents ? configurationOf(engine, host, reading) : undefined;
475
+ const one = engine.readsComponents ? configurationOf(engine, host, reading, formed) : undefined;
433
476
  if (one !== undefined && one.kind !== "unread") {
434
477
  programs.set(reading.configFile, one);
435
478
  continue;
@@ -7,11 +7,20 @@
7
7
  */
8
8
 
9
9
  import { emittedFrom } from "./emit-map.ts";
10
- import { gitIgnored } from "./git-ignore.ts";
11
- import type { Host } from "./host.ts";
12
- import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.ts";
13
- import { satisfies } from "./version-range.ts";
10
+ import { packageOfSpecifier } from "../dependencies.ts";
11
+ import { gitIgnored } from "../git-ignore.ts";
12
+ import type { Host } from "../host.ts";
13
+ import { ancestors, dirnamePath, joinPath, relativePath, resolvePath } from "../paths.ts";
14
+ import { satisfies } from "../version-range.ts";
14
15
  import type { Workspace, WorkspaceMember } from "./workspace.ts";
16
+ import { isRecord } from "../json-document.ts";
17
+ import { MANIFEST } from "../fs/manifest.ts";
18
+ import {
19
+ dependencySpecifiers,
20
+ installedPackage,
21
+ manifestsAbove,
22
+ type ManifestAt,
23
+ } from "./module-lookup.ts";
15
24
 
16
25
  /** What the compiler's own resolution answered for one import. */
17
26
  export interface DefaultResolution {
@@ -36,39 +45,9 @@ export type MemberResolution =
36
45
  /** The manifest members that name an entry point for the subpath `.` without `exports`. */
37
46
  const LEGACY_MEMBERS = ["types", "typings", "module", "main"] as const;
38
47
 
39
- /** The dependency members a manifest declares other packages under. */
40
- const DEPENDENCY_MEMBERS = [
41
- "dependencies",
42
- "devDependencies",
43
- "peerDependencies",
44
- "optionalDependencies",
45
- ] as const;
46
-
47
48
  const SOURCE = /(?<!\.d)\.(?:[mc]?ts|tsx)$/u;
48
49
  const OUTPUT = /(?:\.d\.[mc]?ts|\.[mc]?js|\.jsx)$/u;
49
50
 
50
- function isRecord(value: unknown): value is Record<string, unknown> {
51
- return typeof value === "object" && value !== null && !Array.isArray(value);
52
- }
53
-
54
- /** The package name a bare specifier names, or `undefined` for any other specifier. */
55
- export function packageNameOf(specifier: string): string | undefined {
56
- if (
57
- specifier === "" ||
58
- specifier.startsWith(".") ||
59
- specifier.startsWith("#") ||
60
- isAbsolutePath(specifier) ||
61
- specifier.includes(":")
62
- ) {
63
- return undefined;
64
- }
65
- const [first, second] = specifier.split("/");
66
- if (specifier.startsWith("@")) {
67
- return second === undefined || second === "" ? undefined : `${first ?? ""}/${second}`;
68
- }
69
- return first;
70
- }
71
-
72
51
  function inside(dir: string, path: string): boolean {
73
52
  return relativePath(dir, path) !== undefined;
74
53
  }
@@ -210,52 +189,32 @@ type Linked =
210
189
  /** The member imports of one workspace, read from the members' sources. */
211
190
  export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceResolver {
212
191
  const links = new Map<string, string | undefined>();
213
- /** The directory `node_modules/<name>` names from one directory, links followed. */
192
+ /** The directory the package `name` installed for one directory is, links followed. */
214
193
  const linkedFrom = (name: string, dir: string): string | undefined => {
215
194
  const key = `${name}\u0000${dir}`;
216
- if (links.has(key)) {
217
- return links.get(key);
218
- }
219
- const candidate = joinPath(dir, "node_modules", name);
220
- let found: string | undefined;
221
- if (host.kindOf(candidate) === "absent") {
222
- const up = dirnamePath(dir);
223
- found = up === dir ? undefined : linkedFrom(name, up);
224
- } else {
195
+ if (!links.has(key)) {
196
+ const installed = installedPackage(host, dir, name);
197
+ let found: string | undefined;
225
198
  try {
226
- found = host.realPath(candidate);
199
+ found = installed === undefined ? undefined : host.realPath(installed);
227
200
  } catch {
228
201
  found = undefined;
229
202
  }
203
+ links.set(key, found);
230
204
  }
231
- links.set(key, found);
232
- return found;
205
+ return links.get(key);
233
206
  };
234
207
 
235
208
  const root = workspace.members[0]?.dir;
236
209
  const ignored = root === undefined ? () => false : gitIgnored(host, root);
237
- const manifests = new Map<
238
- string,
239
- { dir: string; manifest: Record<string, unknown> } | undefined
240
- >();
210
+ const manifests = new Map<string, ManifestAt | undefined>();
241
211
  /** The nearest manifest at or above one directory, up to the workspace's root. */
242
- const nearestManifest = (
243
- dir: string,
244
- ): { dir: string; manifest: Record<string, unknown> } | undefined => {
245
- if (manifests.has(dir)) {
246
- return manifests.get(dir);
212
+ const nearestManifest = (dir: string): ManifestAt | undefined => {
213
+ if (!manifests.has(dir)) {
214
+ const [nearest] = manifestsAbove(host, dir, root ?? dir);
215
+ manifests.set(dir, nearest);
247
216
  }
248
- let found: { dir: string; manifest: Record<string, unknown> } | undefined;
249
- try {
250
- const value: unknown = JSON.parse(host.readFile(joinPath(dir, "package.json")));
251
- found = isRecord(value) ? { dir, manifest: value } : undefined;
252
- } catch {
253
- const up = dirnamePath(dir);
254
- found =
255
- up === dir || root === undefined || !inside(root, up) ? undefined : nearestManifest(up);
256
- }
257
- manifests.set(dir, found);
258
- return found;
217
+ return manifests.get(dir);
259
218
  };
260
219
 
261
220
  const linkOf = (candidates: readonly WorkspaceMember[], fromDir: string): Linked => {
@@ -265,18 +224,16 @@ export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceRe
265
224
  const member = candidates.find((one) => one.dir === linked);
266
225
  return member === undefined ? { kind: "none" } : { kind: "member", member };
267
226
  }
268
- const scope = workspace.memberOf(joinPath(fromDir, "package.json"));
227
+ const scope = workspace.memberOf(joinPath(fromDir, MANIFEST));
269
228
  if (scope?.name === name) {
270
229
  return { kind: "member", member: scope };
271
230
  }
272
231
  // A package below the root that no workspace lists depends on a member as a member does.
273
232
  const owner = scope ?? nearestManifest(fromDir);
274
233
  const manifest = owner?.manifest;
275
- const declared = DEPENDENCY_MEMBERS.flatMap((field) => {
276
- const held = manifest?.[field];
277
- const specifier = isRecord(held) ? held[name] : undefined;
278
- return typeof specifier === "string" ? [specifier] : [];
279
- });
234
+ const declared = (manifest === undefined ? [] : dependencySpecifiers(manifest, name)).filter(
235
+ (specifier) => typeof specifier === "string",
236
+ );
280
237
  const matched = candidates.filter((member) =>
281
238
  declared.some((specifier) => dependsOn(specifier, member, owner?.dir ?? fromDir)),
282
239
  );
@@ -300,7 +257,7 @@ export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceRe
300
257
  * directory however its configuration writes it.
301
258
  */
302
259
  const mirrored = (member: WorkspaceMember, path: string): string | undefined => {
303
- for (let dir = dirnamePath(path); inside(member.dir, dir); dir = dirnamePath(dir)) {
260
+ for (const dir of ancestors(dirnamePath(path), member.dir).filter((at) => at !== member.dir)) {
304
261
  const found = emittedFrom(path, { outDir: dir, sourceDirs: [member.dir] }).find(
305
262
  (candidate) =>
306
263
  SOURCE.test(candidate) && !ignored(candidate) && host.kindOf(candidate) === "file",
@@ -379,11 +336,11 @@ export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceRe
379
336
  workspace,
380
337
  sourceOf,
381
338
  names: (specifier) => {
382
- const name = packageNameOf(specifier);
339
+ const name = packageOfSpecifier(specifier);
383
340
  return name !== undefined && workspace.named.has(name);
384
341
  },
385
342
  resolve(specifier, fromDir, conditions, answered) {
386
- const name = packageNameOf(specifier);
343
+ const name = packageOfSpecifier(specifier);
387
344
  const candidates = name === undefined ? undefined : workspace.named.get(name);
388
345
  if (name === undefined || candidates === undefined) {
389
346
  return { kind: "default" };