@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10

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 (288) 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 +245 -309
  5. package/dist/src/augmentations.js +124 -18
  6. package/dist/src/automatic-types.js +170 -0
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +9 -9
  10. package/dist/src/config.js +6 -81
  11. package/dist/src/configuration-files.js +78 -188
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +2 -2
  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 +146 -1
  20. package/dist/src/conventions.js +136 -70
  21. package/dist/src/decorator.js +21 -18
  22. package/dist/src/dependencies.js +280 -62
  23. package/dist/src/edges.js +38 -122
  24. package/dist/src/element-access.js +4 -6
  25. package/dist/src/entry-point-gaps.js +9 -18
  26. package/dist/src/entry-points.js +106 -21
  27. package/dist/src/enum-group.js +3 -10
  28. package/dist/src/erased-stores.js +4 -6
  29. package/dist/src/exempt-classes.js +1 -63
  30. package/dist/src/exempt.js +21 -40
  31. package/dist/src/failure.js +36 -0
  32. package/dist/src/file-facts.js +2 -25
  33. package/dist/src/findings/completion.js +225 -63
  34. package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
  35. package/dist/src/findings/emitters.js +6 -6
  36. package/dist/src/findings/interfaces.js +4 -5
  37. package/dist/src/findings/intra-function.js +53 -19
  38. package/dist/src/findings/non-code-artifacts.js +71 -39
  39. package/dist/src/findings/reads-and-writes.js +16 -13
  40. package/dist/src/findings/self-check.js +10 -12
  41. package/dist/src/findings/unused-declarations.js +57 -28
  42. package/dist/src/findings/visibility-narrowing.js +40 -18
  43. package/dist/src/findings-pass.js +8 -15
  44. package/dist/src/framework-lifecycle.js +7 -7
  45. package/dist/src/fs/document.js +52 -0
  46. package/dist/src/fs/manifest.js +72 -0
  47. package/dist/src/fs/tree.js +88 -0
  48. package/dist/src/generated-file.js +18 -4
  49. package/dist/src/git-ignore.js +99 -0
  50. package/dist/src/graph.js +9 -4
  51. package/dist/src/html-entries.js +6 -33
  52. package/dist/src/import-bindings.js +97 -0
  53. package/dist/src/in-operands.js +2 -1
  54. package/dist/src/index.js +1 -1
  55. package/dist/src/indexed-access.js +130 -28
  56. package/dist/src/injection-container.js +1 -1
  57. package/dist/src/interface-satisfaction.js +30 -12
  58. package/dist/src/intra-function-parts.js +341 -69
  59. package/dist/src/inventory.js +54 -29
  60. package/dist/src/javascript-file.js +33 -0
  61. package/dist/src/json-document.js +198 -67
  62. package/dist/src/keyof-comparisons.js +4 -6
  63. package/dist/src/kinds.js +3 -366
  64. package/dist/src/load/classify.js +270 -0
  65. package/dist/src/load/diagnostics.js +80 -0
  66. package/dist/src/load/discover.js +277 -0
  67. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  68. package/dist/src/load/expected-module.js +136 -0
  69. package/dist/src/load/load.js +27 -0
  70. package/dist/src/load/module-lookup.js +37 -0
  71. package/dist/src/load/plan.js +313 -0
  72. package/dist/src/load/specifiers.js +115 -0
  73. package/dist/src/load/synthesize.js +140 -0
  74. package/dist/src/load/test-globals.js +207 -0
  75. package/dist/src/load/test-joins.js +82 -0
  76. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  77. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
  78. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
  79. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  80. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  81. package/dist/src/manifest.js +49 -54
  82. package/dist/src/matrix.js +94 -13
  83. package/dist/src/mdx-entries.js +174 -0
  84. package/dist/src/module-declarations.js +9 -13
  85. package/dist/src/outside-tests.js +1 -1
  86. package/dist/src/overrides.js +41 -10
  87. package/dist/src/partial.js +2 -1
  88. package/dist/src/paths.js +26 -15
  89. package/dist/src/position.js +2 -1
  90. package/dist/src/print.js +11 -78
  91. package/dist/src/product.js +13 -0
  92. package/dist/src/query.js +1 -0
  93. package/dist/src/ref.js +11 -0
  94. package/dist/src/references.js +52 -31
  95. package/dist/src/report-schema.js +51 -0
  96. package/dist/src/report.js +59 -41
  97. package/dist/src/reporters/annotations.js +15 -8
  98. package/dist/src/reporters/sarif.js +16 -10
  99. package/dist/src/reporters/template.js +4 -19
  100. package/dist/src/resolve.js +245 -727
  101. package/dist/src/roots.js +374 -116
  102. package/dist/src/run.js +66 -77
  103. package/dist/src/schema.js +320 -342
  104. package/dist/src/scope.js +38 -115
  105. package/dist/src/serialization-contract.js +649 -29
  106. package/dist/src/session.js +41 -29
  107. package/dist/src/source-files.js +40 -15
  108. package/dist/src/suppress-file.js +34 -29
  109. package/dist/src/suppress-inline.js +21 -4
  110. package/dist/src/suppress.js +40 -56
  111. package/dist/src/sweep.js +32 -17
  112. package/dist/src/symbol-keys.js +105 -6
  113. package/dist/src/template-field.js +9 -19
  114. package/dist/src/test-support.js +119 -18
  115. package/dist/src/text/compare.js +26 -0
  116. package/dist/src/text/glob.js +194 -0
  117. package/dist/src/text/named-files.js +54 -0
  118. package/dist/src/text/shell.js +159 -0
  119. package/dist/src/text/yaml.js +393 -0
  120. package/dist/src/type-errors.js +27 -221
  121. package/dist/src/type-parameter-reads.js +172 -0
  122. package/dist/src/unseen-loads.js +100 -0
  123. package/dist/src/unwrap.js +23 -0
  124. package/dist/src/usage.js +10 -0
  125. package/dist/src/verbs/analyze.js +181 -125
  126. package/dist/src/verbs/describe.js +9 -16
  127. package/dist/src/verbs/explain.js +14 -9
  128. package/dist/src/verbs/print-config.js +2 -1
  129. package/dist/src/verbs/print-projects.js +6 -4
  130. package/dist/src/verbs/print-retained.js +4 -2
  131. package/dist/src/verbs/print-roots.js +4 -2
  132. package/dist/src/verbs/verb.js +1 -5
  133. package/dist/src/verbs/version.js +14 -3
  134. package/dist/src/workflow-steps.js +128 -33
  135. package/jsr.json +1 -1
  136. package/package.json +5 -4
  137. package/src/analysis.ts +333 -457
  138. package/src/augmentations.ts +152 -23
  139. package/src/automatic-types.ts +228 -0
  140. package/src/build-scripts.ts +543 -0
  141. package/src/component-layer.ts +4 -2
  142. package/src/components.ts +9 -10
  143. package/src/config.ts +31 -299
  144. package/src/configuration-files.ts +100 -191
  145. package/src/configured-declarations.ts +18 -23
  146. package/src/conformance-record.ts +2 -2
  147. package/src/consumers.ts +6 -57
  148. package/src/contextual-uses.ts +64 -12
  149. package/src/contract/config.generated.ts +818 -0
  150. package/src/contract/contract.generated.ts +1308 -0
  151. package/src/contract/report.generated.ts +1237 -0
  152. package/src/convention-rows.ts +186 -1
  153. package/src/conventions.ts +166 -90
  154. package/src/decorator.ts +28 -18
  155. package/src/dependencies.ts +356 -78
  156. package/src/edges.ts +41 -140
  157. package/src/element-access.ts +4 -7
  158. package/src/entry-point-gaps.ts +11 -20
  159. package/src/entry-points.ts +124 -28
  160. package/src/enum-group.ts +2 -11
  161. package/src/erased-stores.ts +3 -7
  162. package/src/exempt-classes.ts +1 -75
  163. package/src/exempt.ts +36 -50
  164. package/src/failure.ts +63 -0
  165. package/src/file-facts.ts +9 -24
  166. package/src/finding.ts +24 -9
  167. package/src/findings/boundary.ts +2 -0
  168. package/src/findings/completion.ts +297 -77
  169. package/src/findings/dependencies-and-module-machinery.ts +90 -31
  170. package/src/findings/emitter.ts +8 -9
  171. package/src/findings/emitters.ts +8 -8
  172. package/src/findings/interfaces.ts +4 -6
  173. package/src/findings/intra-function.ts +64 -20
  174. package/src/findings/non-code-artifacts.ts +88 -50
  175. package/src/findings/reads-and-writes.ts +20 -16
  176. package/src/findings/self-check.ts +11 -17
  177. package/src/findings/unused-declarations.ts +65 -28
  178. package/src/findings/visibility-narrowing.ts +51 -19
  179. package/src/findings-pass.ts +10 -19
  180. package/src/framework-lifecycle.ts +7 -7
  181. package/src/fs/document.ts +70 -0
  182. package/src/fs/manifest.ts +92 -0
  183. package/src/fs/tree.ts +110 -0
  184. package/src/generated-file.ts +23 -4
  185. package/src/git-ignore.ts +122 -0
  186. package/src/graph.ts +11 -3
  187. package/src/host.ts +17 -14
  188. package/src/html-entries.ts +6 -33
  189. package/src/import-bindings.ts +128 -0
  190. package/src/in-operands.ts +2 -1
  191. package/src/index.ts +1 -1
  192. package/src/indexed-access.ts +152 -30
  193. package/src/injection-container.ts +1 -1
  194. package/src/interface-satisfaction.ts +33 -13
  195. package/src/intra-function-parts.ts +449 -85
  196. package/src/inventory.ts +73 -27
  197. package/src/javascript-file.ts +37 -0
  198. package/src/json-document.ts +241 -76
  199. package/src/keyof-comparisons.ts +3 -7
  200. package/src/kinds.ts +5 -413
  201. package/src/load/classify.ts +397 -0
  202. package/src/load/diagnostics.ts +144 -0
  203. package/src/load/discover.ts +455 -0
  204. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  205. package/src/load/expected-module.ts +188 -0
  206. package/src/load/load.ts +73 -0
  207. package/src/load/module-lookup.ts +50 -0
  208. package/src/load/plan.ts +504 -0
  209. package/src/load/specifiers.ts +168 -0
  210. package/src/load/synthesize.ts +211 -0
  211. package/src/load/test-globals.ts +251 -0
  212. package/src/load/test-joins.ts +98 -0
  213. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  214. package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
  215. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
  216. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  217. package/src/{workspace.ts → load/workspace.ts} +90 -134
  218. package/src/manifest.ts +81 -75
  219. package/src/matrix.ts +154 -18
  220. package/src/mdx-entries.ts +218 -0
  221. package/src/module-declarations.ts +11 -13
  222. package/src/outside-tests.ts +1 -1
  223. package/src/overrides.ts +51 -12
  224. package/src/partial.ts +3 -2
  225. package/src/paths.ts +27 -15
  226. package/src/position.ts +2 -1
  227. package/src/print.ts +12 -91
  228. package/src/product.ts +18 -0
  229. package/src/query.ts +11 -0
  230. package/src/ref.ts +13 -0
  231. package/src/references.ts +62 -32
  232. package/src/report-schema.ts +68 -0
  233. package/src/report.ts +105 -215
  234. package/src/reporters/annotations.ts +22 -11
  235. package/src/reporters/json.ts +1 -1
  236. package/src/reporters/reporter.ts +2 -4
  237. package/src/reporters/reporters.ts +1 -1
  238. package/src/reporters/sarif.ts +29 -16
  239. package/src/reporters/template.ts +4 -20
  240. package/src/reporters/text.ts +3 -3
  241. package/src/resolve.ts +283 -1111
  242. package/src/roots.ts +490 -137
  243. package/src/run.ts +75 -91
  244. package/src/schema.ts +391 -387
  245. package/src/scope.ts +46 -138
  246. package/src/serialization-contract.ts +818 -26
  247. package/src/session.ts +77 -54
  248. package/src/source-files.ts +67 -17
  249. package/src/suppress-file.ts +36 -34
  250. package/src/suppress-inline.ts +20 -3
  251. package/src/suppress.ts +52 -63
  252. package/src/sweep.ts +53 -18
  253. package/src/symbol-keys.ts +131 -7
  254. package/src/template-field.ts +15 -25
  255. package/src/test-support.ts +155 -18
  256. package/src/text/compare.ts +27 -0
  257. package/src/text/glob.ts +210 -0
  258. package/src/text/named-files.inputs.json +1522 -0
  259. package/src/text/named-files.ts +68 -0
  260. package/src/text/shell.ts +161 -0
  261. package/src/text/yaml.ts +423 -0
  262. package/src/type-errors.ts +35 -273
  263. package/src/type-parameter-reads.ts +227 -0
  264. package/src/unseen-loads.ts +134 -0
  265. package/src/unwrap.ts +34 -0
  266. package/src/usage.ts +10 -0
  267. package/src/verbs/analyze.ts +245 -161
  268. package/src/verbs/describe.ts +14 -15
  269. package/src/verbs/explain.ts +15 -13
  270. package/src/verbs/print-config.ts +2 -1
  271. package/src/verbs/print-projects.ts +6 -4
  272. package/src/verbs/print-retained.ts +4 -2
  273. package/src/verbs/print-roots.ts +4 -2
  274. package/src/verbs/verb.ts +2 -8
  275. package/src/verbs/version.ts +15 -3
  276. package/src/workflow-steps.ts +149 -34
  277. package/dist/src/discover.js +0 -347
  278. package/dist/src/glob.js +0 -65
  279. package/dist/src/pnpm-workspace.js +0 -120
  280. package/dist/src/setup-failure.js +0 -23
  281. package/dist/src/test-joins.js +0 -101
  282. package/dist/src/version.js +0 -7
  283. package/src/discover.ts +0 -510
  284. package/src/glob.ts +0 -67
  285. package/src/pnpm-workspace.ts +0 -135
  286. package/src/setup-failure.ts +0 -39
  287. package/src/test-joins.ts +0 -120
  288. package/src/version.ts +0 -7
package/src/session.ts CHANGED
@@ -15,9 +15,7 @@ import type {
15
15
  } from "@typescript/native/sync";
16
16
  import type { Node, SourceFile } from "@typescript/native/ast";
17
17
  import { componentLayer, mapperManifest } from "./component-layer.ts";
18
- import { DiscoveryError } from "./discover.ts";
19
18
  import type { Host, TemporaryDirectory } from "./host.ts";
20
- import type { WorkspaceProgram } from "./workspace-programs.ts";
21
19
  import { joinPath } from "./paths.ts";
22
20
  import {
23
21
  queriesOf,
@@ -26,6 +24,7 @@ import {
26
24
  type Queries,
27
25
  type UnansweredQuestion,
28
26
  } from "./query.ts";
27
+ import { MANIFEST } from "./fs/manifest.ts";
29
28
 
30
29
  /**
31
30
  * The compiler the analysis reads through: a client over a protocol to a separate
@@ -36,8 +35,8 @@ import {
36
35
  export interface Engine {
37
36
  /**
38
37
  * Resolves one compiler configuration file, or throws with the reason it cannot
39
- * be read. The result is spelled through the client's own signature: the type
40
- * the compiler package returns here is not one of its exported names.
38
+ * be read. The result is spelled through the client's own signature: the type the
39
+ * compiler package returns here is not one of its exported names.
41
40
  */
42
41
  parseConfigFile(file: DocumentIdentifier): ReturnType<API["parseConfigFile"]>;
43
42
  /** Resolves one configuration's JSON as if `configFile` held it, which no file need. */
@@ -82,6 +81,22 @@ export interface Engine {
82
81
  close(): void;
83
82
  }
84
83
 
84
+ /** The most questions one round trip carries. */
85
+ const BATCH = 2048;
86
+
87
+ /** Asks one question of every item, in round trips of at most {@link BATCH}, answers in order. */
88
+ export function batched<T, A>(
89
+ engine: Engine,
90
+ items: readonly T[],
91
+ question: (item: T) => APIRequestGenerator<A>,
92
+ ): A[] {
93
+ const answers: A[] = [];
94
+ for (let at = 0; at < items.length; at += BATCH) {
95
+ answers.push(...engine.batch(items.slice(at, at + BATCH).map(question)));
96
+ }
97
+ return answers;
98
+ }
99
+
85
100
  /** How a run opens its compiler client. */
86
101
  export interface EngineOptions {
87
102
  /** Collect per-request timing, which the calibration record reads. */
@@ -115,7 +130,7 @@ export function openEngine(options: EngineOptions): Engine {
115
130
  if (components !== undefined && components.extensions.length > 0) {
116
131
  const made = components.host.temporaryDirectory();
117
132
  try {
118
- components.host.writeDocument(joinPath(made.path, "package.json"), [
133
+ components.host.writeDocument(joinPath(made.path, MANIFEST), [
119
134
  mapperManifest(components.host.componentMapperCommand()),
120
135
  ]);
121
136
  } catch (error: unknown) {
@@ -129,6 +144,7 @@ export function openEngine(options: EngineOptions): Engine {
129
144
  ...(mapper === undefined ? {} : { runExternalCode: true }),
130
145
  });
131
146
  let closed = false;
147
+ const parsed = new Map<string, { readonly text: string; readonly file: SourceFile }>();
132
148
  return {
133
149
  parseConfigFile: (file) => api.parseConfigFile(file),
134
150
  parseConfigJson: (json, configFile) =>
@@ -160,8 +176,14 @@ export function openEngine(options: EngineOptions): Engine {
160
176
  moduleResolutions: { fallback: "resolve", entries: [...entries] },
161
177
  }),
162
178
  parseSourceFile: (fileName, text) => {
179
+ // Every project of a run reads the same files outside its program, so one parse serves all.
180
+ const known = parsed.get(fileName);
181
+ if (known?.text === text) {
182
+ return known.file;
183
+ }
163
184
  const retained = api.createSourceFile(fileName, text);
164
185
  try {
186
+ parsed.set(fileName, { text, file: retained.sourceFile });
165
187
  return retained.sourceFile;
166
188
  } finally {
167
189
  retained.dispose();
@@ -264,13 +286,6 @@ export interface ProjectView<Brand> {
264
286
  */
265
287
  export type ProjectVisitor<Result> = <Brand>(project: ProjectView<Brand>) => Result;
266
288
 
267
- /** One project a session opened: its configuration and the files of its own program. */
268
- export interface OpenedFiles {
269
- readonly configFile: string;
270
- /** The program's source files no default library and no external library holds. */
271
- readonly fileNames: () => readonly string[];
272
- }
273
-
274
289
  /**
275
290
  * The source files of one program that `own` admits, in the program's order, none of
276
291
  * them one of the compiler's default libraries.
@@ -464,19 +479,30 @@ export function diagnosticsOf<Brand>(project: ProjectView<Brand>): ProjectDiagno
464
479
  };
465
480
  }
466
481
 
482
+ /** How a snapshot builds one configuration other than by opening the project it names. */
483
+ export type ProgramBuild =
484
+ | { readonly kind: "program"; readonly params: CreateSnapshotProgramParams }
485
+ /** A configuration only the snapshot holds, at a path no file has. */
486
+ | { readonly kind: "configuration"; readonly file: string; readonly text: string };
487
+
467
488
  /** One run's result: what the visitor answered for each project, and what it cost. */
468
489
  export interface SessionResult<Result> {
469
- /** One answer per configuration the snapshot opened a project for, in the order named. */
490
+ /** One answer per configuration, in the order named. */
470
491
  readonly projects: readonly Result[];
471
- /** The configurations the snapshot opened no project for, in the order named. */
472
- readonly unopened: readonly string[];
473
492
  readonly timing: TimingInfo;
474
- /** Every question the checker could not answer, each once, in the order first asked. */
475
- readonly unanswered: readonly UnansweredQuestion[];
476
493
  }
477
494
 
478
- /** What a run does about a configuration the snapshot opened no project for. */
479
- type Unopened = "refuse" | "record";
495
+ /** One open snapshot's projects, valid only while the session that handed it out runs. */
496
+ export interface OpenedSession {
497
+ /** One view per configuration the snapshot opened a project for, in the order named. */
498
+ readonly views: readonly ProjectView<unknown>[];
499
+ }
500
+
501
+ /** How one session opens its configurations. */
502
+ export interface SessionOptions {
503
+ /** How the snapshot builds each configuration it does not open as its project. */
504
+ readonly programs?: ReadonlyMap<string, ProgramBuild> | undefined;
505
+ }
480
506
 
481
507
  /**
482
508
  * The projects one snapshot holds for the configurations named, by configuration file. A
@@ -505,22 +531,18 @@ function projectsOf(
505
531
  }
506
532
 
507
533
  /**
508
- * Runs `visit` over every project the compiler configurations name, inside one never updated
509
- * snapshot of one client, and answers in their order. A configuration `programs` describes is
510
- * built as that program, or opened from the configuration it writes. One the snapshot holds
511
- * nothing for ends the run under `"refuse"` and is named in the result under `"record"`.
512
- * `first` runs over every opened project before the first visit and `last` after the last,
513
- * both while the client answers; the snapshot and the client are then released.
534
+ * Opens every project the compiler configurations name in one never updated snapshot of one
535
+ * client, and hands them to `use` while the client answers; the snapshot and the client are
536
+ * then released. A configuration `options.programs` describes is built as that program, or
537
+ * opened from the configuration it writes. One the snapshot holds nothing for has no view.
514
538
  */
515
- export function runSession<Result>(
539
+ export function openSession<Result>(
516
540
  engine: Engine,
517
541
  configFiles: readonly string[],
518
- visit: ProjectVisitor<Result>,
519
- unopened: Unopened = "refuse",
520
- programs: ReadonlyMap<string, WorkspaceProgram> = new Map(),
521
- last: () => void = () => undefined,
522
- first: (opened: readonly OpenedFiles[]) => void = () => undefined,
523
- ): SessionResult<Result> {
542
+ options: SessionOptions,
543
+ use: (opened: OpenedSession, unanswered: () => readonly UnansweredQuestion[]) => Result,
544
+ ): Result {
545
+ const programs = options.programs ?? new Map<string, ProgramBuild>();
524
546
  try {
525
547
  const built: string[] = [];
526
548
  const params: CreateSnapshotProgramParams[] = [];
@@ -545,32 +567,13 @@ export function runSession<Result>(
545
567
  const opened = projectsOf(snapshot, built, written);
546
568
  const log = new UnansweredLog();
547
569
  const views: ProjectView<unknown>[] = [];
548
- const missing: string[] = [];
549
570
  for (const configFile of configFiles) {
550
571
  const project = opened.get(configFile);
551
- if (project === undefined) {
552
- if (unopened === "refuse") {
553
- throw new DiscoveryError(`${configFile} opened no project`, []);
554
- }
555
- missing.push(configFile);
556
- continue;
572
+ if (project !== undefined) {
573
+ views.push(viewOf(project, configFile, engine, log));
557
574
  }
558
- views.push(viewOf(project, configFile, engine, log));
559
575
  }
560
- first(
561
- views.map((view) => ({
562
- configFile: view.configFile,
563
- fileNames: () => view.ownSourceFiles().map((file) => file.fileName),
564
- })),
565
- );
566
- const projects = views.map((view) => visit(view));
567
- last();
568
- return {
569
- projects,
570
- unopened: missing,
571
- timing: engine.getTimingInfo(),
572
- unanswered: log.questions,
573
- };
576
+ return use({ views }, () => log.questions);
574
577
  } finally {
575
578
  snapshot.dispose();
576
579
  }
@@ -578,3 +581,23 @@ export function runSession<Result>(
578
581
  engine.close();
579
582
  }
580
583
  }
584
+
585
+ /**
586
+ * Runs `visit` over the project {@link openSession} opens for each configuration, and answers
587
+ * in their order; a configuration the snapshot opens nothing for throws before any visit.
588
+ */
589
+ export function runSession<Result>(
590
+ engine: Engine,
591
+ configFiles: readonly string[],
592
+ visit: ProjectVisitor<Result>,
593
+ ): SessionResult<Result> {
594
+ return openSession(engine, configFiles, {}, (opened) => {
595
+ const unopened = configFiles.find(
596
+ (configFile) => !opened.views.some((view) => view.configFile === configFile),
597
+ );
598
+ if (unopened !== undefined) {
599
+ throw new Error(`${unopened} opened no project`);
600
+ }
601
+ return { projects: opened.views.map((view) => visit(view)), timing: engine.getTimingInfo() };
602
+ });
603
+ }
@@ -5,13 +5,19 @@
5
5
  */
6
6
 
7
7
  import type { SourceFile } from "@typescript/native/ast";
8
- import { commonDirectory, emittedFrom } from "./emit-map.ts";
9
- import { dirnamePath, relativePath, resolvePath } from "./paths.ts";
8
+ import { commonDirectory, emittedFrom } from "./load/emit-map.ts";
9
+ import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
10
10
  import type { ProjectView } from "./session.ts";
11
11
 
12
12
  /** The one character a subpath pattern of a manifest's `exports` holds. */
13
13
  const SUBPATH_WILDCARD = "*";
14
14
 
15
+ /** The extension a source file's name drops when the project writes it out. */
16
+ const SOURCE_EXTENSION = /\.[cm]?[jt]sx?$/u;
17
+
18
+ /** The extensions the project may write a source file's output under. */
19
+ const OUTPUT_EXTENSIONS = [".js", ".mjs", ".cjs", ".jsx", ".d.ts", ".d.mts", ".d.cts"] as const;
20
+
15
21
  /**
16
22
  * Whether one path is what a subpath pattern stands for: the text before the
17
23
  * wildcard, any run of characters including the solidus, then the text after it.
@@ -40,6 +46,47 @@ export interface SourceFiles {
40
46
  named(path: string): readonly SourceFile[];
41
47
  /** The directories the project writes its output below, absolute: its output and declaration directories. */
42
48
  readonly outputDirs: readonly string[];
49
+ /** The output paths, absolute, the project writes one own file to, each of which {@link named} reads back to it. */
50
+ writtenAs(file: SourceFile): readonly string[];
51
+ /**
52
+ * The own files one absolute path names read as an output of the project written below
53
+ * `outputDir`: a manifest that ships from its build directory names its entries there.
54
+ */
55
+ namedAsOutput(outputDir: string, path: string): readonly SourceFile[];
56
+ }
57
+
58
+ /** The first own file of the paths given that the project holds, or none. */
59
+ function heldAt(
60
+ byName: ReadonlyMap<string, SourceFile>,
61
+ paths: readonly string[],
62
+ ): readonly SourceFile[] {
63
+ for (const candidate of paths) {
64
+ const file = byName.get(candidate);
65
+ if (file !== undefined) {
66
+ return [file];
67
+ }
68
+ }
69
+ return [];
70
+ }
71
+
72
+ /** Where one project writes its output, and the directory it writes it from, absolute. */
73
+ function layoutOf<Brand>(
74
+ project: ProjectView<Brand>,
75
+ fileNames: readonly string[],
76
+ ): {
77
+ readonly outDir: string | undefined;
78
+ readonly declarationDir: string | undefined;
79
+ readonly sourceDir: string;
80
+ } {
81
+ const options = project.program.getCompilerOptions();
82
+ const configDir = dirnamePath(project.configFile);
83
+ const at = (option: string | undefined): string | undefined =>
84
+ option === undefined ? undefined : resolvePath(configDir, option);
85
+ return {
86
+ outDir: at(options.outDir),
87
+ declarationDir: at(options.declarationDir),
88
+ sourceDir: at(options.rootDir) ?? commonDirectory(fileNames),
89
+ };
43
90
  }
44
91
 
45
92
  /**
@@ -62,14 +109,7 @@ export function sourceFilesOf<Brand>(project: ProjectView<Brand>, targetRoot: st
62
109
  }
63
110
  }
64
111
 
65
- const options = project.program.getCompilerOptions();
66
- const configDir = dirnamePath(project.configFile);
67
- const at = (option: string | undefined): string | undefined =>
68
- option === undefined ? undefined : resolvePath(configDir, option);
69
- const outDir = at(options.outDir);
70
- const declarationDir = at(options.declarationDir);
71
- const sourceDir = at(options.rootDir) ?? commonDirectory([...byName.keys()]);
72
-
112
+ const { outDir, declarationDir, sourceDir } = layoutOf(project, [...byName.keys()]);
73
113
  const layout = { outDir, declarationDir, sourceDirs: [sourceDir] };
74
114
 
75
115
  /** The source paths one written path may stand for, the path itself first. */
@@ -79,15 +119,25 @@ export function sourceFilesOf<Brand>(project: ProjectView<Brand>, targetRoot: st
79
119
  byPath,
80
120
  byName,
81
121
  outputDirs: [...new Set([outDir, declarationDir])].filter((dir) => dir !== undefined),
122
+ writtenAs: (file) => {
123
+ const below = relativePath(sourceDir, file.fileName);
124
+ if (below === undefined) {
125
+ return [];
126
+ }
127
+ const written = [outDir, declarationDir].flatMap((dir) =>
128
+ dir === undefined
129
+ ? []
130
+ : OUTPUT_EXTENSIONS.map((extension) =>
131
+ joinPath(dir, below.replace(SOURCE_EXTENSION, extension)),
132
+ ),
133
+ );
134
+ return [...new Set(written)].filter((path) => heldAt(byName, candidates(path))[0] === file);
135
+ },
136
+ namedAsOutput: (outputDir, path) =>
137
+ heldAt(byName, emittedFrom(path, { outDir: outputDir, sourceDirs: [sourceDir] })),
82
138
  named: (path) => {
83
139
  if (!path.includes(SUBPATH_WILDCARD)) {
84
- for (const candidate of candidates(path)) {
85
- const held = byName.get(candidate);
86
- if (held !== undefined) {
87
- return [held];
88
- }
89
- }
90
- return [];
140
+ return heldAt(byName, candidates(path));
91
141
  }
92
142
  const patterns = candidates(path).filter((candidate) => candidate.includes(SUBPATH_WILDCARD));
93
143
  return [...byName.values()].filter((file) =>
@@ -5,20 +5,20 @@
5
5
  * ignore file by gaining a maintainer's reason.
6
6
  */
7
7
 
8
+ import { CODE } from "./contract/contract.generated.ts";
8
9
  import type { Host } from "./host.ts";
9
10
  import type { InventorySymbol } from "./inventory.ts";
11
+ import { readTargetDocument } from "./fs/document.ts";
10
12
  import { jsonChunks } from "./json-chunks.ts";
11
- import { joinPath } from "./paths.ts";
12
- import { positionAt, walkDocument } from "./json-document.ts";
13
+ import { isRecord, positionAt, readStrict, StrictJSONError } from "./json-document.ts";
13
14
  import type { Position } from "./position.ts";
14
15
  import { declarationKey } from "./ref.ts";
15
16
  import {
16
17
  CODE_FORM,
17
- SUPPRESSION_WITHOUT_REASON,
18
18
  SuppressionError,
19
- UNSCOPED_ENTRY,
20
19
  blankReason,
21
20
  isEntryPath,
21
+ isPartKind,
22
22
  isSymbolRef,
23
23
  siteText,
24
24
  type Mechanism,
@@ -26,6 +26,7 @@ import {
26
26
  type SuppressionRecord,
27
27
  type Suppressions,
28
28
  } from "./suppress.ts";
29
+ import { message } from "./product.ts";
29
30
 
30
31
  /** The name and the location the grammar fixes for the ignore file. */
31
32
  export const IGNORE_FILE = "deadset-ignore.json";
@@ -79,10 +80,6 @@ function documentSite(shape: Shape): Position {
79
80
  return { path: shape.file, line: 0, column: 0 };
80
81
  }
81
82
 
82
- function isObject(value: unknown): value is Record<string, unknown> {
83
- return typeof value === "object" && value !== null && !Array.isArray(value);
84
- }
85
-
86
83
  /** What one entry or row resolves to. */
87
84
  interface Read {
88
85
  readonly records: readonly SuppressionRecord[];
@@ -100,7 +97,7 @@ function readRecord(value: unknown, site: Position, byReference: ByReference, sh
100
97
  const refuse = (text: string, want: string): never => {
101
98
  throw new SuppressionError(shape.mechanism, siteText(site), text, want);
102
99
  };
103
- if (!isObject(value)) {
100
+ if (!isRecord(value)) {
104
101
  return refuse(JSON.stringify(value), shape.recordWant);
105
102
  }
106
103
  for (const [key, held] of Object.entries(value)) {
@@ -147,21 +144,31 @@ function readRecord(value: unknown, site: Position, byReference: ByReference, sh
147
144
  };
148
145
  const refusals: Refusal[] = [];
149
146
  if (blankReason(reason)) {
150
- refusals.push({ ...written, reported: SUPPRESSION_WITHOUT_REASON });
147
+ refusals.push({ ...written, reported: CODE.suppressionWithoutReason });
151
148
  }
152
149
  if (written.path === "") {
153
- refusals.push({ ...written, reported: UNSCOPED_ENTRY });
150
+ refusals.push({ ...written, reported: CODE.unscopedIgnoreEntry });
154
151
  }
155
152
  if (refusals.length > 0) {
156
153
  return { records: [], refusals };
157
154
  }
158
- const named = byReference.get(declarationKey(symbol, written.path)) ?? [];
155
+ const named =
156
+ byReference.get(declarationKey(symbol, written.path)) ??
157
+ (isPartKind(code)
158
+ ? byReference.get(declarationKey(parameterHolder(symbol), written.path))
159
+ : undefined) ??
160
+ [];
159
161
  if (named.length === 0) {
160
162
  return { records: [{ ...written, bound: "" }], refusals: [] };
161
163
  }
162
164
  return { records: named.map((one) => ({ ...written, bound: one.id })), refusals: [] };
163
165
  }
164
166
 
167
+ /** The reference of the declaration a parameter's reference names it under, or `ref` itself. */
168
+ function parameterHolder(ref: string): string {
169
+ return ref.endsWith(")") ? ref.slice(0, ref.lastIndexOf("(")) : ref;
170
+ }
171
+
165
172
  function byReferenceOf(symbols: readonly InventorySymbol[]): ByReference {
166
173
  const byReference = new Map<string, InventorySymbol[]>();
167
174
  for (const symbol of symbols) {
@@ -178,30 +185,26 @@ function readDocument(
178
185
  symbols: readonly InventorySymbol[],
179
186
  shape: Shape,
180
187
  ): Suppressions {
181
- const file = joinPath(targetRoot, shape.file);
182
- if (host.kindOf(file) === "absent") {
188
+ const text = readTargetDocument(host, targetRoot, shape.file);
189
+ if (text === undefined) {
183
190
  return { records: [], refusals: [] };
184
191
  }
185
- const text = host.readFile(file);
186
192
  const malformed = (detail: string, site = documentSite(shape)): never => {
187
193
  throw new SuppressionError(shape.mechanism, siteText(site), detail, shape.documentWant);
188
194
  };
189
- let parsed: unknown;
195
+ let document;
190
196
  try {
191
- parsed = JSON.parse(text);
197
+ document = readStrict(text, shape.array);
192
198
  } catch (error: unknown) {
193
- return malformed(`${shape.file}: ${error instanceof Error ? error.message : String(error)}`);
199
+ if (!(error instanceof StrictJSONError)) {
200
+ throw error;
201
+ }
202
+ return malformed(error.message, positionAt(text, shape.file, error.offset));
194
203
  }
195
- if (!isObject(parsed)) {
204
+ const parsed = document.value;
205
+ if (!isRecord(parsed)) {
196
206
  return malformed(shape.file);
197
207
  }
198
- const walk = walkDocument(text, shape.array);
199
- if (walk.twice !== undefined) {
200
- malformed(
201
- `the member ${JSON.stringify(walk.twice.name)} is written twice, so neither value is chosen`,
202
- positionAt(text, shape.file, walk.twice.offset),
203
- );
204
- }
205
208
  for (const key of Object.keys(parsed)) {
206
209
  if (key !== "description" && key !== shape.array) {
207
210
  malformed(`the member ${JSON.stringify(key)} is not one the grammar declares`);
@@ -217,7 +220,7 @@ function readDocument(
217
220
  const records: SuppressionRecord[] = [];
218
221
  const refusals: Refusal[] = [];
219
222
  (held as readonly unknown[]).forEach((value, index) => {
220
- const offset = walk.starts[index];
223
+ const offset = document.starts.get(index);
221
224
  const site = offset === undefined ? documentSite(shape) : positionAt(text, shape.file, offset);
222
225
  const read = readRecord(value, site, byReference, shape);
223
226
  records.push(...read.records);
@@ -318,9 +321,9 @@ export function otherSchemeRows(text: string, own: string = OWN_SCHEME): readonl
318
321
  try {
319
322
  parsed = JSON.parse(text);
320
323
  } catch (error: unknown) {
321
- return refuse(error instanceof Error ? error.message : String(error));
324
+ return refuse(message(error));
322
325
  }
323
- const held = isObject(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
326
+ const held = isRecord(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
324
327
  if (!Array.isArray(held)) {
325
328
  return refuse(BASELINE_SHAPE.documentWant);
326
329
  }
@@ -328,7 +331,7 @@ export function otherSchemeRows(text: string, own: string = OWN_SCHEME): readonl
328
331
  try {
329
332
  readRecord(value, documentSite(BASELINE_SHAPE), new Map(), BASELINE_SHAPE);
330
333
  } catch (error: unknown) {
331
- return refuse(error instanceof Error ? error.message : String(error));
334
+ return refuse(message(error));
332
335
  }
333
336
  // readRecord has checked every member is a string and the code and symbol are present.
334
337
  const { code, symbol, path, reason } = value as Readonly<Record<string, string>>;
@@ -375,10 +378,9 @@ function* separatorsEscaped(pieces: Iterable<string>): Generator<string, void, u
375
378
  * A baseline holding one row per finding, in the order given, which is the report's, and
376
379
  * the other language's rows `kept`, Go's rows ahead of TypeScript's and each scheme's in
377
380
  * the order given, so two analyzers writing one document in either order write the same
378
- * bytes, in the form grammar/suppression.md fixes. Every recorded row carries the
379
- * provenance as its reason. A finding
380
- * missing a value a row needs, and an identity missing its name or version, are refused
381
- * rather than written, because the grammar requires a reason on every row.
381
+ * bytes, in the form grammar/suppression.md fixes. Every row carries the provenance as
382
+ * its reason, so a finding missing a value a row needs, or an identity missing its name
383
+ * or version, is refused rather than written.
382
384
  */
383
385
  export function writeBaseline(
384
386
  findings: readonly Recorded[],
@@ -4,19 +4,21 @@
4
4
  * below it, or on its own line where it trails a token there.
5
5
  */
6
6
 
7
+ import { CODE } from "./contract/contract.generated.ts";
7
8
  import {
8
9
  isConstructorDeclaration,
9
10
  SyntaxKind,
10
11
  type Node,
11
12
  type SourceFile,
12
13
  } from "@typescript/native/ast";
14
+ import { importBindings } from "./import-bindings.ts";
13
15
  import { isParameterProperty, nodeKey, type Inventory } from "./inventory.ts";
14
16
  import { byPosition, positionKey, renderPosition, type Position } from "./position.ts";
17
+ import { importRef } from "./ref.ts";
15
18
  import type { ProjectView } from "./session.ts";
16
19
  import { signatureOf } from "./signatures.ts";
17
20
  import {
18
21
  isPartKind,
19
- SUPPRESSION_WITHOUT_REASON,
20
22
  SuppressionError,
21
23
  siteText,
22
24
  type Refusal,
@@ -200,6 +202,7 @@ function declarationLines(
200
202
  file: SourceFile,
201
203
  held: Inventory,
202
204
  symbols: ReadonlyMap<string, Declared>,
205
+ targetRoot: string,
203
206
  ): ReadonlyMap<number, readonly Binding[]> {
204
207
  const below = new Map<number, Binding[]>();
205
208
  const add = (offset: number, one: Binding): void => {
@@ -245,6 +248,20 @@ function declarationLines(
245
248
  node.forEachChild(visit);
246
249
  };
247
250
  file.forEachChild(visit);
251
+ // An import binding is a subject of its own, bound on its line as a declaration is.
252
+ const fileId = held.declarations.get(nodeKey(file, file));
253
+ const moduleRef = held.symbols.find((symbol) => symbol.id === fileId)?.ref ?? "";
254
+ for (const name of moduleRef === "" ? [] : importBindings(file)) {
255
+ const position = renderPosition(file, targetRoot, name.getStart());
256
+ const one = { id: positionKey(position), ref: importRef(moduleRef, name.text), position };
257
+ let statement: Node = name;
258
+ while (statement.parent.kind !== SyntaxKind.SourceFile) {
259
+ statement = statement.parent;
260
+ }
261
+ for (const at of [statement.pos, name.getStart()]) {
262
+ add(at, { declared: one, part: one });
263
+ }
264
+ }
248
265
  return below;
249
266
  }
250
267
 
@@ -285,7 +302,7 @@ export function inlineDirectives<Brand>(
285
302
  if (comments.length === 0) {
286
303
  continue;
287
304
  }
288
- const below = declarationLines(file, held, symbols);
305
+ const below = declarationLines(file, held, symbols, targetRoot);
289
306
  for (const comment of comments) {
290
307
  const site = renderPosition(file, targetRoot, comment.offset);
291
308
  const target = trails(file, comment.offset) ? site.line : site.line + 1;
@@ -337,7 +354,7 @@ export function inlineSuppressions(
337
354
  case "no-reason":
338
355
  for (const code of directive.codes) {
339
356
  refusals.push({
340
- reported: SUPPRESSION_WITHOUT_REASON,
357
+ reported: CODE.suppressionWithoutReason,
341
358
  code,
342
359
  symbol: "",
343
360
  path: site.path,