@cplieger/deadset-ts 8.0.0-dev.7 → 8.0.0-dev.9

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 (137) hide show
  1. package/dist/bin/node-host.js +21 -3
  2. package/dist/src/analysis.js +94 -299
  3. package/dist/src/config.js +6 -83
  4. package/dist/src/configuration-files.js +2 -9
  5. package/dist/src/configured-declarations.js +15 -20
  6. package/dist/src/consumers.js +3 -30
  7. package/dist/src/contract/config.generated.js +544 -0
  8. package/dist/src/contract/contract.generated.js +20 -10
  9. package/dist/src/contract/report.generated.js +3 -2
  10. package/dist/src/conventions.js +3 -8
  11. package/dist/src/edges.js +36 -112
  12. package/dist/src/exempt-classes.js +0 -2
  13. package/dist/src/exempt.js +1 -1
  14. package/dist/src/failure.js +36 -0
  15. package/dist/src/findings/completion.js +5 -4
  16. package/dist/src/findings/emitters.js +1 -1
  17. package/dist/src/findings/intra-function.js +1 -1
  18. package/dist/src/findings/non-code-artifacts.js +2 -2
  19. package/dist/src/findings/unused-declarations.js +1 -1
  20. package/dist/src/findings/visibility-narrowing.js +2 -2
  21. package/dist/src/findings-pass.js +1 -1
  22. package/dist/src/framework-lifecycle.js +7 -7
  23. package/dist/src/fs/document.js +52 -0
  24. package/dist/src/fs/manifest.js +7 -0
  25. package/dist/src/injection-container.js +1 -1
  26. package/dist/src/json-document.js +194 -67
  27. package/dist/src/kinds.js +0 -7
  28. package/dist/src/load/classify.js +236 -0
  29. package/dist/src/load/diagnostics.js +79 -0
  30. package/dist/src/load/discover.js +230 -0
  31. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  32. package/dist/src/load/expected-module.js +136 -0
  33. package/dist/src/load/load.js +22 -0
  34. package/dist/src/load/module-lookup.js +37 -0
  35. package/dist/src/load/plan.js +155 -0
  36. package/dist/src/{test-globals.js → load/test-globals.js} +51 -56
  37. package/dist/src/load/test-joins.js +82 -0
  38. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +3 -3
  39. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +37 -40
  40. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +21 -59
  41. package/dist/src/{workspace-run.js → load/workspace-run.js} +4 -3
  42. package/dist/src/{workspace.js → load/workspace.js} +21 -17
  43. package/dist/src/outside-tests.js +1 -1
  44. package/dist/src/print.js +10 -79
  45. package/dist/src/report-schema.js +3 -224
  46. package/dist/src/resolve.js +245 -715
  47. package/dist/src/run.js +59 -72
  48. package/dist/src/schema.js +320 -344
  49. package/dist/src/scope.js +37 -113
  50. package/dist/src/serialization-contract.js +1 -1
  51. package/dist/src/session.js +22 -28
  52. package/dist/src/source-files.js +1 -1
  53. package/dist/src/suppress-file.js +15 -17
  54. package/dist/src/suppress.js +3 -3
  55. package/dist/src/template-field.js +4 -4
  56. package/dist/src/type-errors.js +11 -216
  57. package/dist/src/usage.js +10 -0
  58. package/dist/src/verbs/analyze.js +100 -122
  59. package/dist/src/verbs/describe.js +5 -12
  60. package/dist/src/verbs/explain.js +3 -3
  61. package/dist/src/verbs/print-projects.js +2 -2
  62. package/dist/src/verbs/version.js +12 -2
  63. package/jsr.json +1 -1
  64. package/package.json +1 -1
  65. package/src/analysis.ts +95 -460
  66. package/src/config.ts +31 -298
  67. package/src/configuration-files.ts +8 -10
  68. package/src/configured-declarations.ts +18 -23
  69. package/src/consumers.ts +6 -57
  70. package/src/contract/config.generated.ts +798 -0
  71. package/src/contract/contract.generated.ts +20 -10
  72. package/src/contract/report.generated.ts +3 -2
  73. package/src/conventions.ts +6 -12
  74. package/src/edges.ts +39 -124
  75. package/src/exempt-classes.ts +0 -5
  76. package/src/exempt.ts +4 -4
  77. package/src/failure.ts +63 -0
  78. package/src/findings/completion.ts +5 -6
  79. package/src/findings/emitter.ts +2 -2
  80. package/src/findings/emitters.ts +1 -1
  81. package/src/findings/intra-function.ts +1 -1
  82. package/src/findings/non-code-artifacts.ts +4 -4
  83. package/src/findings/unused-declarations.ts +1 -1
  84. package/src/findings/visibility-narrowing.ts +2 -2
  85. package/src/findings-pass.ts +3 -3
  86. package/src/framework-lifecycle.ts +7 -7
  87. package/src/fs/document.ts +70 -0
  88. package/src/fs/manifest.ts +8 -0
  89. package/src/host.ts +12 -5
  90. package/src/injection-container.ts +1 -1
  91. package/src/json-document.ts +236 -76
  92. package/src/kinds.ts +0 -8
  93. package/src/load/classify.ts +352 -0
  94. package/src/load/diagnostics.ts +141 -0
  95. package/src/load/discover.ts +378 -0
  96. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  97. package/src/load/expected-module.ts +188 -0
  98. package/src/load/load.ts +61 -0
  99. package/src/load/module-lookup.ts +50 -0
  100. package/src/load/plan.ts +258 -0
  101. package/src/{test-globals.ts → load/test-globals.ts} +59 -65
  102. package/src/load/test-joins.ts +98 -0
  103. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +4 -4
  104. package/src/{workspace-programs.ts → load/workspace-programs.ts} +59 -53
  105. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +31 -78
  106. package/src/{workspace-run.ts → load/workspace-run.ts} +14 -11
  107. package/src/{workspace.ts → load/workspace.ts} +28 -21
  108. package/src/outside-tests.ts +1 -1
  109. package/src/partial.ts +1 -1
  110. package/src/print.ts +11 -92
  111. package/src/report-schema.ts +3 -299
  112. package/src/resolve.ts +281 -1105
  113. package/src/run.ts +68 -80
  114. package/src/schema.ts +391 -389
  115. package/src/scope.ts +45 -131
  116. package/src/serialization-contract.ts +1 -1
  117. package/src/session.ts +52 -53
  118. package/src/source-files.ts +1 -1
  119. package/src/suppress-file.ts +15 -20
  120. package/src/suppress.ts +3 -3
  121. package/src/template-field.ts +10 -7
  122. package/src/type-errors.ts +18 -288
  123. package/src/usage.ts +10 -0
  124. package/src/verbs/analyze.ts +139 -157
  125. package/src/verbs/describe.ts +4 -11
  126. package/src/verbs/explain.ts +3 -7
  127. package/src/verbs/print-projects.ts +2 -2
  128. package/src/verbs/verb.ts +2 -2
  129. package/src/verbs/version.ts +13 -2
  130. package/dist/src/base-url.js +0 -18
  131. package/dist/src/discover.js +0 -374
  132. package/dist/src/setup-failure.js +0 -19
  133. package/dist/src/test-joins.js +0 -101
  134. package/src/base-url.ts +0 -24
  135. package/src/discover.ts +0 -548
  136. package/src/setup-failure.ts +0 -37
  137. package/src/test-joins.ts +0 -118
@@ -1,25 +1,18 @@
1
- import {
2
- CONTRACT_VERSION,
3
- EXIT_CLEAN,
4
- EXIT_FAILURE,
5
- type Format,
6
- } from "../contract/contract.generated.ts";
1
+ import { CONTRACT_VERSION, EXIT_CLEAN, type Format } from "../contract/contract.generated.ts";
7
2
  import {
8
3
  runAnalysis,
9
4
  runBaselineWrite,
10
5
  unansweredCounts,
11
- type ComponentWarning,
12
6
  type RunAnalysis,
13
7
  type RunProject,
14
8
  } from "../analysis.ts";
15
- import { ConfigError, kindOf } from "../config.ts";
9
+ import { kindOf } from "../config.ts";
16
10
  import { CONFORMANCE, DECLARED_GAPS } from "../conformance.ts";
17
- import { moduleIdentity } from "../consumers.ts";
18
11
  import { analyzerProvenance, verdictOf } from "../findings-pass.ts";
19
12
  import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "../paths.ts";
20
13
  import type { Host } from "../host.ts";
21
14
  import type { ManifestEntry } from "../manifest.ts";
22
- import type { NotBuilt } from "../discover.ts";
15
+ import { moduleIdentity, type NotBuilt } from "../load/discover.ts";
23
16
  import { partialNotes } from "../partial.ts";
24
17
  import {
25
18
  buildReport,
@@ -29,7 +22,6 @@ import {
29
22
  type ReportNotBuilt,
30
23
  type ReportNote,
31
24
  } from "../report.ts";
32
- import type { Report } from "../contract/report.generated.ts";
33
25
  import { reportRefusals } from "../report-schema.ts";
34
26
  import { json } from "../reporters/json.ts";
35
27
  import { RENDERINGS } from "../reporters/reporters.ts";
@@ -38,8 +30,11 @@ import { resolve } from "../resolve.ts";
38
30
  import { otherSchemeRows, rowReason, writeBaseline, type Recorded } from "../suppress-file.ts";
39
31
  import type { UnseenLoad } from "../unseen-loads.ts";
40
32
  import { type Verb, type VerbOptions } from "./verb.ts";
33
+ import { UsageError } from "../usage.ts";
41
34
  import { bytewise } from "../text/compare.ts";
42
- import { diagnose, LANGUAGE, message } from "../product.ts";
35
+ import { productFault, failureExit } from "../failure.ts";
36
+ import { diagnose, message } from "../product.ts";
37
+ import { ownVersion } from "./version.ts";
43
38
 
44
39
  /** The options `analyze` takes beside the ones every verb takes. */
45
40
  export const ANALYZE_OPTIONS: VerbOptions = {
@@ -51,14 +46,6 @@ export const ANALYZE_OPTIONS: VerbOptions = {
51
46
  const EXIT_CODE_ON = "on";
52
47
  const EXIT_CODE_OFF = "off";
53
48
 
54
- /** A path of the run no report can name, which ends the run with the failure code. */
55
- class ReportPathError extends Error {
56
- constructor(message: string) {
57
- super(message);
58
- this.name = "ReportPathError";
59
- }
60
- }
61
-
62
49
  /** The formats this analyzer renders, for a message that names them. */
63
50
  function formatNames(): string {
64
51
  return [...RENDERINGS.keys()].sort(bytewise).join(", ");
@@ -70,14 +57,12 @@ function formatsOf(named: readonly string[], configured: readonly Format[]): rea
70
57
  const seen = new Set<string>();
71
58
  for (const format of formats) {
72
59
  if (!RENDERINGS.has(format as Format)) {
73
- throw new ConfigError(
74
- "malformed",
75
- "",
60
+ throw new UsageError(
76
61
  `${JSON.stringify(format)} is not a format this analyzer renders: the formats are ${formatNames()}`,
77
62
  );
78
63
  }
79
64
  if (seen.has(format)) {
80
- throw new ConfigError("malformed", "", `the format ${JSON.stringify(format)} is named twice`);
65
+ throw new UsageError(`the format ${JSON.stringify(format)} is named twice`);
81
66
  }
82
67
  seen.add(format);
83
68
  }
@@ -95,7 +80,8 @@ function runRelative(invoked: string, path: string): string {
95
80
  }
96
81
  const below = relativePath(invoked, path);
97
82
  if (below === undefined) {
98
- throw new ReportPathError(
83
+ throw failureExit(
84
+ "unreportable-path",
99
85
  `a report names its target relative to the directory the run was invoked from, and ${invoked} does not contain ${path}: run from a directory that does`,
100
86
  );
101
87
  }
@@ -147,8 +133,9 @@ function notBuiltOf(dropped: readonly NotBuilt[], targetRoot: string): readonly
147
133
  return dropped.map((one) => {
148
134
  const project = relativePath(targetRoot, one.configFile);
149
135
  if (project === undefined) {
150
- throw new ReportPathError(
151
- `the project ${one.id} would be opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it`,
136
+ throw failureExit(
137
+ "unreportable-path",
138
+ `the project ${one.id} would be opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it: analyze a target that holds it`,
152
139
  );
153
140
  }
154
141
  return { id: one.id, project, error: one.error };
@@ -163,14 +150,33 @@ function configurationsOf(
163
150
  return projects.map((one) => {
164
151
  const project = relativePath(targetRoot, one.configFile);
165
152
  if (project === undefined) {
166
- throw new ReportPathError(
167
- `the project ${one.id} is opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it`,
153
+ throw failureExit(
154
+ "unreportable-path",
155
+ `the project ${one.id} is opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it: analyze a target that holds it`,
168
156
  );
169
157
  }
170
158
  return { id: one.id, project };
171
159
  });
172
160
  }
173
161
 
162
+ /**
163
+ * Writes one document the run was asked for. A document that cannot be produced or written
164
+ * ends the run with the run failure that names it and `fix`, the documents written before it
165
+ * kept.
166
+ */
167
+ function writeDocument(
168
+ host: Host,
169
+ path: string,
170
+ fix: string,
171
+ produce: () => Iterable<string>,
172
+ ): void {
173
+ try {
174
+ host.writeDocument(path, produce());
175
+ } catch (error: unknown) {
176
+ throw failureExit("unwritten-output", `${path}: ${message(error)}: ${fix}`);
177
+ }
178
+ }
179
+
174
180
  /** A count with its noun, so a message reads for one record as well as for several. */
175
181
  function counted(n: number, noun: string): string {
176
182
  return `${String(n)} ${noun}${n === 1 ? "" : "s"}`;
@@ -189,9 +195,7 @@ function templateOf(
189
195
  ): Template | undefined {
190
196
  if (named === undefined || named === "") {
191
197
  if (formats.includes("template")) {
192
- throw new ConfigError(
193
- "malformed",
194
- "",
198
+ throw new UsageError(
195
199
  "the template format renders the template --template names, and none was named",
196
200
  );
197
201
  }
@@ -201,7 +205,7 @@ function templateOf(
201
205
  try {
202
206
  source = host.readFile(resolvePath(invoked, named));
203
207
  } catch (error: unknown) {
204
- throw new ConfigError("malformed", "", `--template=${named}: ${message(error)}`);
208
+ throw new UsageError(`--template=${named}: ${message(error)}`);
205
209
  }
206
210
  try {
207
211
  return parseTemplate(source);
@@ -209,7 +213,7 @@ function templateOf(
209
213
  if (!(error instanceof TemplateError)) {
210
214
  throw error;
211
215
  }
212
- throw new ConfigError("malformed", "", `--template=${named}: ${error.message}`);
216
+ throw new UsageError(`--template=${named}: ${error.message}`);
213
217
  }
214
218
  }
215
219
 
@@ -231,25 +235,17 @@ interface Documents {
231
235
  export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option, repeated }) => {
232
236
  const reportPath = option("report");
233
237
  if (reportPath === undefined || reportPath === "") {
234
- throw new ConfigError(
235
- "malformed",
236
- "",
238
+ throw new UsageError(
237
239
  "analyze writes its report to the path --report names, and no path was named",
238
240
  );
239
241
  }
240
242
  const exitCode = option("exit-code") ?? EXIT_CODE_ON;
241
243
  if (exitCode !== EXIT_CODE_ON && exitCode !== EXIT_CODE_OFF) {
242
- throw new ConfigError(
243
- "malformed",
244
- "",
244
+ throw new UsageError(
245
245
  `--exit-code=${JSON.stringify(exitCode)}: the values are ${EXIT_CODE_ON} and ${EXIT_CODE_OFF}`,
246
246
  );
247
247
  }
248
248
  const { config, provenance } = resolve(inputs());
249
- const kind = config.targetKind;
250
- if (kind === "") {
251
- throw new ConfigError("missing-target-kind", "target.kind", "target.kind is not set");
252
- }
253
249
  const invoked = host.workingDirectory();
254
250
  const baseline = option("baseline-write");
255
251
  const documents: Documents = {
@@ -261,126 +257,112 @@ export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option
261
257
  const scoped = scope();
262
258
  const targetRoot = scoped.target.path;
263
259
 
264
- let version: string;
265
- try {
266
- version = host.analyzerVersion();
267
- } catch (error: unknown) {
268
- diagnose(err, message(error));
269
- return EXIT_FAILURE;
270
- }
260
+ const version = ownVersion(host);
271
261
 
272
- let report: Report;
273
262
  let recorded: readonly Recorded[];
274
- let verdict: number;
275
- let partial: readonly string[];
276
- let warnings: readonly ComponentWarning[];
277
- let unread: readonly string[];
278
- let unfound: readonly string[];
279
- try {
280
- const root = runRelative(invoked, targetRoot);
281
- const mode = { production: true };
282
- let analysis: RunAnalysis;
283
- if (documents.baseline === undefined) {
284
- analysis = runAnalysis(openClient(config), host, scoped, config, provenance, mode);
285
- recorded = [];
286
- } else {
287
- const reason = rowReason(analyzerProvenance(version));
288
- const write = runBaselineWrite(
289
- openClient(config),
290
- host,
291
- scoped,
292
- config,
293
- provenance,
294
- mode,
295
- reason,
296
- );
297
- analysis = write.first;
298
- recorded = write.rows;
299
- }
300
- const { result, run } = analysis;
301
- warnings = run.componentWarnings;
302
- unread = run.componentsUnread.map(
303
- ({ configFile, reason }) =>
304
- `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`,
263
+ const root = runRelative(invoked, targetRoot);
264
+ const mode = { production: true };
265
+ let analysis: RunAnalysis;
266
+ if (documents.baseline === undefined) {
267
+ analysis = runAnalysis(openClient(config), host, scoped, config, provenance, mode);
268
+ recorded = [];
269
+ } else {
270
+ const reason = rowReason(analyzerProvenance(version));
271
+ const write = runBaselineWrite(
272
+ openClient(config),
273
+ host,
274
+ scoped,
275
+ config,
276
+ provenance,
277
+ mode,
278
+ reason,
305
279
  );
306
- unfound = run.librariesUnfound.map(
307
- ({ configFile, error }) =>
308
- `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: ${error}: the configuration is analyzed without that library`,
309
- );
310
- partial = partialNotes({
311
- notBuilt: run.notBuilt,
312
- unanswered: run.unanswered,
313
- targetRoot: resolvePath(invoked, targetRoot),
314
- });
315
- const built = buildReport({
316
- contractVersion: CONTRACT_VERSION,
317
- version,
318
- conformance: CONFORMANCE,
319
- declaredGaps: DECLARED_GAPS,
320
- target: {
321
- kind: kindOf(kind, LANGUAGE),
322
- root,
323
- identity: moduleIdentity(host, scoped.target),
324
- },
325
- configurations: configurationsOf(run.projects, targetRoot),
326
- notBuilt: notBuiltOf(run.notBuilt, targetRoot),
327
- loaded: run.consumers.map((consumer) => ({
328
- id: consumer.id,
329
- path: runRelative(invoked, consumer.path),
330
- })),
331
- unavailable: [],
332
- result,
333
- testFileRules: run.testFileRules,
334
- typeErrorSkips: run.typeErrorSkips,
335
- unanswered: unansweredCounts(analysis),
336
- conventionsApplied: run.conventionsApplied,
337
- listsComponents: config.reporters.cascade === "full",
338
- notes: [
339
- ...unmappedNotes(run.unmappedEntries, resolvePath(invoked, targetRoot)),
340
- ...unseenLoadNotes(run.unseenLoads),
341
- ],
342
- });
343
- report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.maxFindings);
344
- verdict = verdictOf(result, config);
345
- } catch (error: unknown) {
346
- if (!(error instanceof ReportPathError)) {
347
- throw error;
348
- }
349
- diagnose(err, error.message);
350
- return EXIT_FAILURE;
280
+ analysis = write.first;
281
+ recorded = write.rows;
351
282
  }
283
+ const { result, run } = analysis;
284
+ const warnings = run.componentWarnings;
285
+ const unread = run.componentsUnread.map(
286
+ ({ configFile, reason }) =>
287
+ `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`,
288
+ );
289
+ const unfound = run.librariesUnfound.map(
290
+ ({ configFile, error }) =>
291
+ `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: ${error}: the configuration is analyzed without that library`,
292
+ );
293
+ const partial = partialNotes({
294
+ notBuilt: run.notBuilt,
295
+ unanswered: run.unanswered,
296
+ targetRoot: resolvePath(invoked, targetRoot),
297
+ });
298
+ const built = buildReport({
299
+ contractVersion: CONTRACT_VERSION,
300
+ version,
301
+ conformance: CONFORMANCE,
302
+ declaredGaps: DECLARED_GAPS,
303
+ target: {
304
+ kind: kindOf(config.target.kind),
305
+ root,
306
+ identity: moduleIdentity(host, scoped.target),
307
+ },
308
+ configurations: configurationsOf(run.projects, targetRoot),
309
+ notBuilt: notBuiltOf(run.notBuilt, targetRoot),
310
+ loaded: run.consumers.map((consumer) => ({
311
+ id: consumer.id,
312
+ path: runRelative(invoked, consumer.path),
313
+ })),
314
+ unavailable: [],
315
+ result,
316
+ testFileRules: run.testFileRules,
317
+ typeErrorSkips: run.typeErrorSkips,
318
+ unanswered: unansweredCounts(analysis),
319
+ conventionsApplied: run.conventionsApplied,
320
+ listsComponents: config.reporters.cascade === "full",
321
+ notes: [
322
+ ...unmappedNotes(run.unmappedEntries, resolvePath(invoked, targetRoot)),
323
+ ...unseenLoadNotes(run.unseenLoads),
324
+ ],
325
+ });
326
+ const report = capFindings(
327
+ sortFindings(built, config.reporters.sort),
328
+ config.reporters.max_findings,
329
+ );
330
+ const verdict = verdictOf(result, config);
352
331
 
353
- try {
354
- const [refused] = reportRefusals(report);
355
- if (refused !== undefined) {
356
- throw new Error(
357
- `the report breaks the report schema at ${refused.pointer || "the document"}: ${refused.keyword}: ${refused.reason}`,
358
- );
359
- }
360
- host.writeDocument(documents.report, json(report));
361
- const options = {
362
- readSource: (path: string) => host.readFile(joinPath(targetRoot, path)),
363
- template,
364
- };
365
- for (const format of documents.formats) {
366
- const rendering = RENDERINGS.get(format);
367
- if (rendering !== undefined) {
368
- host.writeDocument(documents.report + rendering.suffix, rendering.render(report, options));
369
- }
370
- }
371
- if (documents.baseline !== undefined) {
372
- const kept =
373
- host.kindOf(documents.baseline) === "file"
374
- ? otherSchemeRows(host.readFile(documents.baseline))
375
- : [];
376
- host.writeDocument(
377
- documents.baseline,
378
- writeBaseline(recorded, analyzerProvenance(version), kept),
332
+ const [refused] = reportRefusals(report);
333
+ if (refused !== undefined) {
334
+ throw productFault(
335
+ `built a report that breaks the report schema at ${refused.pointer || "the document"}: ${refused.keyword}: ${refused.reason}`,
336
+ );
337
+ }
338
+ writeDocument(host, documents.report, "name a path the run can write", () => json(report));
339
+ const options = {
340
+ readSource: (path: string) => host.readFile(joinPath(targetRoot, path)),
341
+ template,
342
+ };
343
+ for (const format of documents.formats) {
344
+ const rendering = RENDERINGS.get(format);
345
+ if (rendering !== undefined) {
346
+ writeDocument(
347
+ host,
348
+ documents.report + rendering.suffix,
349
+ "name a path the run can write, or correct the template that renders it",
350
+ () => rendering.render(report, options),
379
351
  );
380
352
  }
381
- } catch (error: unknown) {
382
- diagnose(err, message(error));
383
- return EXIT_FAILURE;
353
+ }
354
+ const baselinePath = documents.baseline;
355
+ if (baselinePath !== undefined) {
356
+ writeDocument(
357
+ host,
358
+ baselinePath,
359
+ "name a path the run can write, or repair the rows of the baseline the run would keep",
360
+ () => {
361
+ const kept =
362
+ host.kindOf(baselinePath) === "file" ? otherSchemeRows(host.readFile(baselinePath)) : [];
363
+ return writeBaseline(recorded, analyzerProvenance(version), kept);
364
+ },
365
+ );
384
366
  }
385
367
 
386
368
  for (const note of partial) {
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  CONTRACT_VERSION,
3
3
  EXIT_CLEAN,
4
- EXIT_FAILURE,
5
4
  EXIT_USAGE,
6
5
  SCHEMA_VERSIONS,
7
6
  } from "../contract/contract.generated.ts";
8
7
  import { CONFORMANCE, type Conformance } from "../conformance.ts";
9
8
  import { type Verb } from "./verb.ts";
10
- import { diagnose, LANGUAGE, message, PRODUCT } from "../product.ts";
9
+ import { diagnose, LANGUAGE, PRODUCT } from "../product.ts";
10
+ import { ownVersion } from "./version.ts";
11
11
 
12
12
  /**
13
13
  * The document `describe` writes: the analyzer, the Contract version it implements, the
@@ -34,19 +34,12 @@ export function describeDocument(version: string, recorded: Conformance | undefi
34
34
  return `${JSON.stringify(document, null, 2)}\n`;
35
35
  }
36
36
 
37
- /** Writes this analyzer's description to the output stream, and nothing to the error stream. */
37
+ /** Writes this analyzer's description to the output stream. */
38
38
  export const describeVerb: Verb = ({ out, err, host, args }) => {
39
39
  if (args.length > 0) {
40
40
  diagnose(err, `describe takes no argument, got ${JSON.stringify(args[0])}`);
41
41
  return EXIT_USAGE;
42
42
  }
43
- let version: string;
44
- try {
45
- version = host.analyzerVersion();
46
- } catch (error: unknown) {
47
- diagnose(err, `describe: ${message(error)}`);
48
- return EXIT_FAILURE;
49
- }
50
- out.write(describeDocument(version, CONFORMANCE));
43
+ out.write(describeDocument(ownVersion(host), CONFORMANCE));
51
44
  return EXIT_CLEAN;
52
45
  };
@@ -2,7 +2,6 @@ import { EXIT_CLEAN, EXIT_USAGE } from "../contract/contract.generated.ts";
2
2
  import { runAnalysis, type RunAnalysis } from "../analysis.ts";
3
3
  import { listing } from "../components.ts";
4
4
  import type { Cascade } from "../config.ts";
5
- import { ConfigError } from "../config.ts";
6
5
  import type { CompletedFinding } from "../finding.ts";
7
6
  import { OUTSIDE, swept as judged } from "../graph.ts";
8
7
  import type { InventorySymbol } from "../inventory.ts";
@@ -14,6 +13,7 @@ import type { Reference } from "../references.ts";
14
13
  import { resolve } from "../resolve.ts";
15
14
  import type { Root } from "../roots.ts";
16
15
  import { type Verb, type VerbOptions } from "./verb.ts";
16
+ import { UsageError } from "../usage.ts";
17
17
  import { bytewise } from "../text/compare.ts";
18
18
  import { diagnose } from "../product.ts";
19
19
 
@@ -47,16 +47,12 @@ function namedSymbol(option: (name: string) => string | undefined): string {
47
47
  });
48
48
  const [only, ...more] = named;
49
49
  if (only === undefined) {
50
- throw new ConfigError(
51
- "malformed",
52
- "",
50
+ throw new UsageError(
53
51
  "explain explains the symbol --why, --why-live or --why-not names, and none was named",
54
52
  );
55
53
  }
56
54
  if (more.length > 0) {
57
- throw new ConfigError(
58
- "malformed",
59
- "",
55
+ throw new UsageError(
60
56
  `explain explains one symbol, and ${named.map((one) => `--${one.name}`).join(", ")} each name one`,
61
57
  );
62
58
  }
@@ -2,7 +2,7 @@ import { EXIT_CLEAN } from "../contract/contract.generated.ts";
2
2
  import { runProjects } from "../analysis.ts";
3
3
  import { partialNotes } from "../partial.ts";
4
4
  import { resolvePath } from "../paths.ts";
5
- import { resolveUnkinded } from "../resolve.ts";
5
+ import { resolvePartial } from "../resolve.ts";
6
6
  import { type Verb } from "./verb.ts";
7
7
  import { diagnose } from "../product.ts";
8
8
 
@@ -14,7 +14,7 @@ import { diagnose } from "../product.ts";
14
14
  * instead, and one that fails the analysis fails this command the same way.
15
15
  */
16
16
  export const printProjectsVerb: Verb = ({ out, err, host, inputs, scope, openClient }) => {
17
- const config = resolveUnkinded(inputs());
17
+ const config = resolvePartial(inputs());
18
18
  const scoped = scope();
19
19
  const answer = runProjects(openClient(config), host, scoped, config);
20
20
  for (const id of answer.configurations) {
package/src/verbs/verb.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Config, Inputs } from "../config.ts";
1
+ import type { Inputs, PartialConfig } from "../config.ts";
2
2
  import type { Host } from "../host.ts";
3
3
  import type { Writer } from "../run.ts";
4
4
  import type { Scope } from "../scope.ts";
@@ -21,7 +21,7 @@ interface Invocation {
21
21
  * Opens the compiler client for a run under `config`. A client the verb opened is
22
22
  * closed if the verb throws.
23
23
  */
24
- readonly openClient: (config: Config) => Engine;
24
+ readonly openClient: (config: PartialConfig) => Engine;
25
25
  /** The arguments after the verb's name, as written. */
26
26
  readonly args: readonly string[];
27
27
  /** The last value one of the verb's own options was given, undefined where none was. */
@@ -1,9 +1,20 @@
1
1
  import { CONTRACT_VERSION, EXIT_CLEAN } from "../contract/contract.generated.ts";
2
+ import { productFault } from "../failure.ts";
3
+ import type { Host } from "../host.ts";
2
4
  import { type Verb } from "./verb.ts";
3
- import { PRODUCT } from "../product.ts";
5
+ import { message, PRODUCT } from "../product.ts";
6
+
7
+ /** The version the host reports for this analyzer; one it cannot read is a fault of the analyzer. */
8
+ export function ownVersion(host: Host): string {
9
+ try {
10
+ return host.analyzerVersion();
11
+ } catch (error: unknown) {
12
+ throw productFault(`cannot read its own version: ${message(error)}`);
13
+ }
14
+ }
4
15
 
5
16
  /** Writes the version the host reports and the Contract version this analyzer implements. */
6
17
  export const versionVerb: Verb = ({ out, host }) => {
7
- out.write(`${PRODUCT} ${host.analyzerVersion()}\ncontract ${CONTRACT_VERSION}\n`);
18
+ out.write(`${PRODUCT} ${ownVersion(host)}\ncontract ${CONTRACT_VERSION}\n`);
8
19
  return EXIT_CLEAN;
9
20
  };
@@ -1,18 +0,0 @@
1
- import { resolvePath } from "./paths.js";
2
- /**
3
- * The `paths` one configuration's resolved options map under the compilers before version
4
- * 7, each target absolute: a target is read against the effective `baseUrl`, and a specifier
5
- * no other pattern maps is looked up below it. Undefined where no `baseUrl` is set, so the
6
- * options' own `paths` stand. Version 7 reads no `baseUrl`:
7
- * https://github.com/microsoft/TypeScript/issues/62207
8
- */
9
- export function baseUrlPaths(options) {
10
- const baseUrl = Reflect.get(options, "baseUrl");
11
- if (typeof baseUrl !== "string") {
12
- return undefined;
13
- }
14
- return Object.fromEntries(Object.entries({ "*": ["*"], ...options.paths }).map(([key, targets]) => [
15
- key,
16
- targets.map((target) => resolvePath(baseUrl, target)),
17
- ]));
18
- }