@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
@@ -1,37 +1,44 @@
1
+ import { CONTRACT_VERSION, EXIT_CLEAN, type Format } from "../contract/contract.generated.ts";
1
2
  import {
2
3
  runAnalysis,
3
4
  runBaselineWrite,
4
5
  unansweredCounts,
5
- type ComponentWarning,
6
6
  type RunAnalysis,
7
7
  type RunProject,
8
8
  } from "../analysis.ts";
9
- import { ConfigError, kindOf, type Format } from "../config.ts";
9
+ import { kindOf } from "../config.ts";
10
10
  import { CONFORMANCE, DECLARED_GAPS } from "../conformance.ts";
11
- import { moduleIdentity } from "../consumers.ts";
12
11
  import { analyzerProvenance, verdictOf } from "../findings-pass.ts";
13
- import { joinPath, normalizePath, relativePath, resolvePath } from "../paths.ts";
12
+ import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "../paths.ts";
14
13
  import type { Host } from "../host.ts";
15
14
  import type { ManifestEntry } from "../manifest.ts";
16
- import type { NotBuilt } from "../discover.ts";
15
+ import type { HeldScripts } from "../roots.ts";
16
+ import { moduleIdentity, type NotBuilt } from "../load/discover.ts";
17
17
  import { partialNotes } from "../partial.ts";
18
18
  import {
19
19
  buildReport,
20
- LANGUAGE,
21
20
  capFindings,
22
21
  sortFindings,
23
- type Report,
24
22
  type ReportConfiguration,
25
23
  type ReportNotBuilt,
26
24
  type ReportNote,
27
25
  } from "../report.ts";
26
+ import { reportRefusals } from "../report-schema.ts";
28
27
  import { json } from "../reporters/json.ts";
29
28
  import { RENDERINGS } from "../reporters/reporters.ts";
30
29
  import { parseTemplate, TemplateError, type Template } from "../reporters/template.ts";
31
30
  import { resolve } from "../resolve.ts";
32
31
  import { otherSchemeRows, rowReason, writeBaseline, type Recorded } from "../suppress-file.ts";
33
- import { CONTRACT_VERSION } from "../version.ts";
34
- import { EXIT_CLEAN, EXIT_FAILURE, type Verb, type VerbOptions } from "./verb.ts";
32
+ import type { UnseenLoad } from "../unseen-loads.ts";
33
+ import type { UnreadInput } from "../load/plan.ts";
34
+ import type { UnresolvedSpecifier } from "../load/specifiers.ts";
35
+
36
+ import { type Verb, type VerbOptions } from "./verb.ts";
37
+ import { UsageError } from "../usage.ts";
38
+ import { bytewise } from "../text/compare.ts";
39
+ import { productFault, failureExit } from "../failure.ts";
40
+ import { diagnose, message } from "../product.ts";
41
+ import { ownVersion } from "./version.ts";
35
42
 
36
43
  /** The options `analyze` takes beside the ones every verb takes. */
37
44
  export const ANALYZE_OPTIONS: VerbOptions = {
@@ -43,17 +50,9 @@ export const ANALYZE_OPTIONS: VerbOptions = {
43
50
  const EXIT_CODE_ON = "on";
44
51
  const EXIT_CODE_OFF = "off";
45
52
 
46
- /** A path of the run no report can name, which ends the run with the failure code. */
47
- class ReportPathError extends Error {
48
- constructor(message: string) {
49
- super(message);
50
- this.name = "ReportPathError";
51
- }
52
- }
53
-
54
53
  /** The formats this analyzer renders, for a message that names them. */
55
54
  function formatNames(): string {
56
- return [...RENDERINGS.keys()].sort().join(", ");
55
+ return [...RENDERINGS.keys()].sort(bytewise).join(", ");
57
56
  }
58
57
 
59
58
  /** The renderings one invocation writes: the ones it named, else the configuration's. */
@@ -62,14 +61,12 @@ function formatsOf(named: readonly string[], configured: readonly Format[]): rea
62
61
  const seen = new Set<string>();
63
62
  for (const format of formats) {
64
63
  if (!RENDERINGS.has(format as Format)) {
65
- throw new ConfigError(
66
- "malformed",
67
- "",
64
+ throw new UsageError(
68
65
  `${JSON.stringify(format)} is not a format this analyzer renders: the formats are ${formatNames()}`,
69
66
  );
70
67
  }
71
68
  if (seen.has(format)) {
72
- throw new ConfigError("malformed", "", `the format ${JSON.stringify(format)} is named twice`);
69
+ throw new UsageError(`the format ${JSON.stringify(format)} is named twice`);
73
70
  }
74
71
  seen.add(format);
75
72
  }
@@ -87,7 +84,8 @@ function runRelative(invoked: string, path: string): string {
87
84
  }
88
85
  const below = relativePath(invoked, path);
89
86
  if (below === undefined) {
90
- throw new ReportPathError(
87
+ throw failureExit(
88
+ "unreportable-path",
91
89
  `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`,
92
90
  );
93
91
  }
@@ -113,13 +111,69 @@ function unmappedNotes(
113
111
  });
114
112
  }
115
113
 
114
+ /**
115
+ * One note per load of a production file the run cannot resolve, each site once: a file
116
+ * it loads is a root the run does not see, so `ts.roots_complete` lifts no cap there.
117
+ */
118
+ function unseenLoadNotes(loads: readonly UnseenLoad[]): readonly ReportNote[] {
119
+ return [
120
+ ...new Map(
121
+ loads.map((load) => {
122
+ const { path, line, column } = load.position;
123
+ const site = `${path}:${String(line)}:${String(column)}`;
124
+ const note: ReportNote = {
125
+ kind: "computed-load",
126
+ path: dirnamePath(path),
127
+ message: `${site} loads a module by a computed path the analysis cannot resolve, so the configurations compiling it report what no root reaches at probable at most, whatever ts.roots_complete says`,
128
+ };
129
+ return [site, note] as const;
130
+ }),
131
+ ).values(),
132
+ ];
133
+ }
134
+
135
+ /** One note per package whose command strings name files a configuration holds, naming every such file. */
136
+ function heldScriptNotes(held: readonly HeldScripts[], targetRoot: string): readonly ReportNote[] {
137
+ const files = new Map<string, Set<string>>();
138
+ for (const one of held) {
139
+ files.set(one.dir, new Set([...(files.get(one.dir) ?? []), ...one.files]));
140
+ }
141
+ return [...files].map(([dir, paths]) => ({
142
+ kind: "unread-command",
143
+ path: relativePath(targetRoot, dir) ?? ".",
144
+ message: `commands the analysis does not read name ${[...paths].sort(bytewise).join(", ")}, so no finding is reported about ${paths.size === 1 ? "that file itself or the names it exports" : "those files themselves or the names they export"}`,
145
+ }));
146
+ }
147
+
148
+ function unresolvedNotes(specifiers: readonly UnresolvedSpecifier[]): readonly ReportNote[] {
149
+ return specifiers.map(({ specifier, position }) => ({
150
+ kind: "unresolved-specifier",
151
+ path: dirnamePath(position.path),
152
+ message: `${position.path}:${String(position.line)}:${String(position.column)} loads ${JSON.stringify(specifier)}, which resolves to no file, so it references nothing`,
153
+ }));
154
+ }
155
+
156
+ function unreadLine(input: UnreadInput, targetRoot: string): string {
157
+ switch (input.kind) {
158
+ case "manifest": {
159
+ const path = relativePath(targetRoot, input.manifest) ?? input.manifest;
160
+ return input.reason === undefined
161
+ ? `${path} does not parse, so the manifest facts of the program formed for its package are unanswered: correct the manifest`
162
+ : `${path} cannot be read, so the manifest facts of the program formed for its package are unanswered: give the run read access to it: ${input.reason}`;
163
+ }
164
+ case "ignore-file":
165
+ return `${relativePath(targetRoot, input.path) ?? input.path} cannot be read, so the JavaScript files it would ignore are analyzed: ${input.reason}`;
166
+ }
167
+ }
168
+
116
169
  /** Each project the run dropped as a report names it, by its configuration file below the target. */
117
170
  function notBuiltOf(dropped: readonly NotBuilt[], targetRoot: string): readonly ReportNotBuilt[] {
118
171
  return dropped.map((one) => {
119
172
  const project = relativePath(targetRoot, one.configFile);
120
173
  if (project === undefined) {
121
- throw new ReportPathError(
122
- `the project ${one.id} would be opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it`,
174
+ throw failureExit(
175
+ "unreportable-path",
176
+ `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`,
123
177
  );
124
178
  }
125
179
  return { id: one.id, project, error: one.error };
@@ -134,14 +188,33 @@ function configurationsOf(
134
188
  return projects.map((one) => {
135
189
  const project = relativePath(targetRoot, one.configFile);
136
190
  if (project === undefined) {
137
- throw new ReportPathError(
138
- `the project ${one.id} is opened from ${one.configFile}, which is not below the target ${targetRoot}, so no report can name it`,
191
+ throw failureExit(
192
+ "unreportable-path",
193
+ `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`,
139
194
  );
140
195
  }
141
196
  return { id: one.id, project };
142
197
  });
143
198
  }
144
199
 
200
+ /**
201
+ * Writes one document the run was asked for. A document that cannot be produced or written
202
+ * ends the run with the run failure that names it and `fix`, the documents written before it
203
+ * kept.
204
+ */
205
+ function writeDocument(
206
+ host: Host,
207
+ path: string,
208
+ fix: string,
209
+ produce: () => Iterable<string>,
210
+ ): void {
211
+ try {
212
+ host.writeDocument(path, produce());
213
+ } catch (error: unknown) {
214
+ throw failureExit("unwritten-output", `${path}: ${message(error)}: ${fix}`);
215
+ }
216
+ }
217
+
145
218
  /** A count with its noun, so a message reads for one record as well as for several. */
146
219
  function counted(n: number, noun: string): string {
147
220
  return `${String(n)} ${noun}${n === 1 ? "" : "s"}`;
@@ -160,9 +233,7 @@ function templateOf(
160
233
  ): Template | undefined {
161
234
  if (named === undefined || named === "") {
162
235
  if (formats.includes("template")) {
163
- throw new ConfigError(
164
- "malformed",
165
- "",
236
+ throw new UsageError(
166
237
  "the template format renders the template --template names, and none was named",
167
238
  );
168
239
  }
@@ -172,11 +243,7 @@ function templateOf(
172
243
  try {
173
244
  source = host.readFile(resolvePath(invoked, named));
174
245
  } catch (error: unknown) {
175
- throw new ConfigError(
176
- "malformed",
177
- "",
178
- `--template=${named}: ${error instanceof Error ? error.message : String(error)}`,
179
- );
246
+ throw new UsageError(`--template=${named}: ${message(error)}`);
180
247
  }
181
248
  try {
182
249
  return parseTemplate(source);
@@ -184,7 +251,7 @@ function templateOf(
184
251
  if (!(error instanceof TemplateError)) {
185
252
  throw error;
186
253
  }
187
- throw new ConfigError("malformed", "", `--template=${named}: ${error.message}`);
254
+ throw new UsageError(`--template=${named}: ${error.message}`);
188
255
  }
189
256
  }
190
257
 
@@ -206,25 +273,17 @@ interface Documents {
206
273
  export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option, repeated }) => {
207
274
  const reportPath = option("report");
208
275
  if (reportPath === undefined || reportPath === "") {
209
- throw new ConfigError(
210
- "malformed",
211
- "",
276
+ throw new UsageError(
212
277
  "analyze writes its report to the path --report names, and no path was named",
213
278
  );
214
279
  }
215
280
  const exitCode = option("exit-code") ?? EXIT_CODE_ON;
216
281
  if (exitCode !== EXIT_CODE_ON && exitCode !== EXIT_CODE_OFF) {
217
- throw new ConfigError(
218
- "malformed",
219
- "",
282
+ throw new UsageError(
220
283
  `--exit-code=${JSON.stringify(exitCode)}: the values are ${EXIT_CODE_ON} and ${EXIT_CODE_OFF}`,
221
284
  );
222
285
  }
223
286
  const { config, provenance } = resolve(inputs());
224
- const kind = config.targetKind;
225
- if (kind === "") {
226
- throw new ConfigError("missing-target-kind", "target.kind", "target.kind is not set");
227
- }
228
287
  const invoked = host.workingDirectory();
229
288
  const baseline = option("baseline-write");
230
289
  const documents: Documents = {
@@ -236,147 +295,150 @@ export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option
236
295
  const scoped = scope();
237
296
  const targetRoot = scoped.target.path;
238
297
 
239
- let version: string;
240
- try {
241
- version = host.analyzerVersion();
242
- } catch (error: unknown) {
243
- err.write(`deadset-ts: ${error instanceof Error ? error.message : String(error)}\n`);
244
- return EXIT_FAILURE;
245
- }
298
+ const version = ownVersion(host);
246
299
 
247
- let report: Report;
248
300
  let recorded: readonly Recorded[];
249
- let verdict: number;
250
- let partial: readonly string[];
251
- let warnings: readonly ComponentWarning[];
252
- let unread: readonly string[];
253
- let unfound: readonly string[];
254
- try {
255
- const root = runRelative(invoked, targetRoot);
256
- const mode = { production: true };
257
- let analysis: RunAnalysis;
258
- if (documents.baseline === undefined) {
259
- analysis = runAnalysis(openClient(config), host, scoped, config, provenance, mode);
260
- recorded = [];
261
- } else {
262
- const reason = rowReason(analyzerProvenance(version));
263
- const write = runBaselineWrite(
264
- openClient(config),
265
- host,
266
- scoped,
267
- config,
268
- provenance,
269
- mode,
270
- reason,
271
- );
272
- analysis = write.first;
273
- recorded = write.rows;
274
- }
275
- const { result, run } = analysis;
276
- warnings = run.componentWarnings;
277
- unread = run.componentsUnread.map(
278
- ({ configFile, reason }) =>
279
- `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`,
301
+ const root = runRelative(invoked, targetRoot);
302
+ const mode = { production: true };
303
+ let analysis: RunAnalysis;
304
+ if (documents.baseline === undefined) {
305
+ analysis = runAnalysis(openClient(config), host, scoped, config, provenance, mode);
306
+ recorded = [];
307
+ } else {
308
+ const reason = rowReason(analyzerProvenance(version));
309
+ const write = runBaselineWrite(
310
+ openClient(config),
311
+ host,
312
+ scoped,
313
+ config,
314
+ provenance,
315
+ mode,
316
+ reason,
280
317
  );
281
- unfound = run.librariesUnfound.map(
282
- ({ configFile, error }) =>
283
- `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: ${error}: the configuration is analyzed without that library`,
284
- );
285
- partial = partialNotes({
286
- notBuilt: run.notBuilt,
287
- unanswered: run.unanswered,
288
- targetRoot: resolvePath(invoked, targetRoot),
289
- });
290
- const built = buildReport({
291
- contractVersion: CONTRACT_VERSION,
292
- version,
293
- conformance: CONFORMANCE,
294
- declaredGaps: DECLARED_GAPS,
295
- target: {
296
- kind: kindOf(kind, LANGUAGE),
297
- root,
298
- identity: moduleIdentity(host, scoped.target),
299
- },
300
- configurations: configurationsOf(run.projects, targetRoot),
301
- notBuilt: notBuiltOf(run.notBuilt, targetRoot),
302
- loaded: run.consumers.map((consumer) => ({
303
- id: consumer.id,
304
- path: runRelative(invoked, consumer.path),
305
- })),
306
- unavailable: [],
307
- result,
308
- testFileRules: run.testFileRules,
309
- typeErrorSkips: run.typeErrorSkips,
310
- unanswered: unansweredCounts(analysis),
311
- conventionsApplied: run.conventionsApplied,
312
- notes: unmappedNotes(run.unmappedEntries, resolvePath(invoked, targetRoot)),
313
- });
314
- report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.maxFindings);
315
- verdict = verdictOf(result, config);
316
- } catch (error: unknown) {
317
- if (!(error instanceof ReportPathError)) {
318
- throw error;
319
- }
320
- err.write(`deadset-ts: ${error.message}\n`);
321
- return EXIT_FAILURE;
318
+ analysis = write.first;
319
+ recorded = write.rows;
322
320
  }
321
+ const { result, run } = analysis;
322
+ const warnings = run.componentWarnings;
323
+ const unread = run.componentsUnread.map(
324
+ ({ configFile, reason }) =>
325
+ `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`,
326
+ );
327
+ const unfound = run.librariesUnfound.map(
328
+ ({ configFile, error }) =>
329
+ `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: ${error}: the configuration is analyzed without that library`,
330
+ );
331
+ const unreadInputs = run.unread.map((one) => unreadLine(one, resolvePath(invoked, targetRoot)));
323
332
 
324
- try {
325
- host.writeDocument(documents.report, json(report));
326
- const options = {
327
- failOn: config.reporters.failOn,
328
- readSource: (path: string) => host.readFile(joinPath(targetRoot, path)),
329
- template,
330
- };
331
- for (const format of documents.formats) {
332
- const rendering = RENDERINGS.get(format);
333
- if (rendering !== undefined) {
334
- host.writeDocument(documents.report + rendering.suffix, rendering.render(report, options));
335
- }
336
- }
337
- if (documents.baseline !== undefined) {
338
- const kept =
339
- host.kindOf(documents.baseline) === "file"
340
- ? otherSchemeRows(host.readFile(documents.baseline))
341
- : [];
342
- host.writeDocument(
343
- documents.baseline,
344
- writeBaseline(recorded, analyzerProvenance(version), kept),
333
+ const partial = partialNotes({
334
+ notBuilt: run.notBuilt,
335
+ unanswered: run.unanswered,
336
+ targetRoot: resolvePath(invoked, targetRoot),
337
+ });
338
+ const built = buildReport({
339
+ contractVersion: CONTRACT_VERSION,
340
+ version,
341
+ conformance: CONFORMANCE,
342
+ declaredGaps: DECLARED_GAPS,
343
+ target: {
344
+ kind: kindOf(config.target.kind),
345
+ root,
346
+ identity: moduleIdentity(host, scoped.target),
347
+ },
348
+ configurations: configurationsOf(run.projects, targetRoot),
349
+ notBuilt: notBuiltOf(run.notBuilt, targetRoot),
350
+ loaded: run.consumers.map((consumer) => ({
351
+ id: consumer.id,
352
+ path: runRelative(invoked, consumer.path),
353
+ })),
354
+ unavailable: [],
355
+ result,
356
+ testFileRules: run.testFileRules,
357
+ typeErrorSkips: run.typeErrorSkips,
358
+ unanswered: unansweredCounts(analysis),
359
+ conventionsApplied: run.conventionsApplied,
360
+ listsComponents: config.reporters.cascade === "full",
361
+ notes: [
362
+ ...unmappedNotes(run.unmappedEntries, resolvePath(invoked, targetRoot)),
363
+ ...unseenLoadNotes(run.unseenLoads),
364
+ ...unresolvedNotes(run.unresolvedSpecifiers),
365
+ ...heldScriptNotes(run.heldScripts, resolvePath(invoked, targetRoot)),
366
+ ],
367
+ });
368
+ const report = capFindings(
369
+ sortFindings(built, config.reporters.sort),
370
+ config.reporters.max_findings,
371
+ );
372
+ const verdict = verdictOf(result, config);
373
+
374
+ const [refused] = reportRefusals(report);
375
+ if (refused !== undefined) {
376
+ throw productFault(
377
+ `built a report that breaks the report schema at ${refused.pointer || "the document"}: ${refused.keyword}: ${refused.reason}`,
378
+ );
379
+ }
380
+ writeDocument(host, documents.report, "name a path the run can write", () => json(report));
381
+ const options = {
382
+ readSource: (path: string) => host.readFile(joinPath(targetRoot, path)),
383
+ template,
384
+ };
385
+ for (const format of documents.formats) {
386
+ const rendering = RENDERINGS.get(format);
387
+ if (rendering !== undefined) {
388
+ writeDocument(
389
+ host,
390
+ documents.report + rendering.suffix,
391
+ "name a path the run can write, or correct the template that renders it",
392
+ () => rendering.render(report, options),
345
393
  );
346
394
  }
347
- } catch (error: unknown) {
348
- err.write(`deadset-ts: ${error instanceof Error ? error.message : String(error)}\n`);
349
- return EXIT_FAILURE;
395
+ }
396
+ const baselinePath = documents.baseline;
397
+ if (baselinePath !== undefined) {
398
+ writeDocument(
399
+ host,
400
+ baselinePath,
401
+ "name a path the run can write, or repair the rows of the baseline the run would keep",
402
+ () => {
403
+ const kept =
404
+ host.kindOf(baselinePath) === "file" ? otherSchemeRows(host.readFile(baselinePath)) : [];
405
+ return writeBaseline(recorded, analyzerProvenance(version), kept);
406
+ },
407
+ );
350
408
  }
351
409
 
352
410
  for (const note of partial) {
353
- err.write(`deadset-ts: ${note}\n`);
411
+ diagnose(err, note);
354
412
  }
355
- for (const line of [...unread, ...unfound]) {
356
- err.write(`deadset-ts: ${line}\n`);
413
+ for (const line of [...unread, ...unfound, ...unreadInputs]) {
414
+ diagnose(err, line);
357
415
  }
358
416
  for (const warning of warnings) {
359
- err.write(
360
- `deadset-ts: ${warning.path}:${String(warning.line)}: a <script> start tag that begins a line is not read as a block: ${warning.reason}\n`,
417
+ diagnose(
418
+ err,
419
+ `${warning.path}:${String(warning.line)}: a <script> start tag that begins a line is not read as a block: ${warning.reason}`,
361
420
  );
362
421
  }
363
422
  for (const skip of report.type_error_skips) {
364
- err.write(
365
- `deadset-ts: ${skip.path}:${String(skip.line)}: ${skip.message}: the function or statement holding this type error is not evaluated\n`,
423
+ diagnose(
424
+ err,
425
+ `${skip.path}:${String(skip.line)}: ${skip.message}: the function or statement holding this type error is not evaluated`,
366
426
  );
367
427
  }
368
428
  if (report.totals.pending > 0) {
369
- err.write(
370
- `deadset-ts: ${counted(report.totals.pending, "pending finding")}: the report names a declared cross-language edge, and no merge has resolved it\n`,
429
+ diagnose(
430
+ err,
431
+ `${counted(report.totals.pending, "pending finding")}: the report names a declared cross-language edge, and no merge has resolved it`,
371
432
  );
372
433
  }
373
434
  if (report.totals.stale_suppressions > 0) {
374
- err.write(`deadset-ts: ${counted(report.totals.stale_suppressions, "stale suppression")}\n`);
435
+ diagnose(err, counted(report.totals.stale_suppressions, "stale suppression"));
375
436
  }
376
437
  if (exitCode === EXIT_CODE_OFF) {
377
438
  if (verdict !== EXIT_CLEAN) {
378
- err.write(
379
- `deadset-ts: the exit code is configured off: the verdict of this run is ${String(verdict)}\n`,
439
+ diagnose(
440
+ err,
441
+ `the exit code is configured off: the verdict of this run is ${String(verdict)}`,
380
442
  );
381
443
  }
382
444
  return EXIT_CLEAN;
@@ -1,7 +1,13 @@
1
+ import {
2
+ CONTRACT_VERSION,
3
+ EXIT_CLEAN,
4
+ EXIT_USAGE,
5
+ SCHEMA_VERSIONS,
6
+ } from "../contract/contract.generated.ts";
1
7
  import { CONFORMANCE, type Conformance } from "../conformance.ts";
2
- import { ANALYZER_NAME, LANGUAGE, SCHEMA_VERSIONS_ACCEPTED } from "../report.ts";
3
- import { CONTRACT_VERSION } from "../version.ts";
4
- import { EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE, type Verb } from "./verb.ts";
8
+ import { type Verb } from "./verb.ts";
9
+ import { diagnose, LANGUAGE, PRODUCT } from "../product.ts";
10
+ import { ownVersion } from "./version.ts";
5
11
 
6
12
  /**
7
13
  * The document `describe` writes: the analyzer, the Contract version it implements, the
@@ -10,10 +16,10 @@ import { EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE, type Verb } from "./verb.ts";
10
16
  */
11
17
  export function describeDocument(version: string, recorded: Conformance | undefined): string {
12
18
  const document = {
13
- name: ANALYZER_NAME,
19
+ name: PRODUCT,
14
20
  version,
15
21
  contract_version: CONTRACT_VERSION,
16
- schema_versions_accepted: SCHEMA_VERSIONS_ACCEPTED,
22
+ schema_versions_accepted: SCHEMA_VERSIONS,
17
23
  languages: [LANGUAGE],
18
24
  ...(recorded === undefined
19
25
  ? {}
@@ -28,19 +34,12 @@ export function describeDocument(version: string, recorded: Conformance | undefi
28
34
  return `${JSON.stringify(document, null, 2)}\n`;
29
35
  }
30
36
 
31
- /** 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. */
32
38
  export const describeVerb: Verb = ({ out, err, host, args }) => {
33
39
  if (args.length > 0) {
34
- err.write(`deadset-ts: describe takes no argument, got ${JSON.stringify(args[0])}\n`);
40
+ diagnose(err, `describe takes no argument, got ${JSON.stringify(args[0])}`);
35
41
  return EXIT_USAGE;
36
42
  }
37
- let version: string;
38
- try {
39
- version = host.analyzerVersion();
40
- } catch (error: unknown) {
41
- err.write(`deadset-ts: describe: ${error instanceof Error ? error.message : String(error)}\n`);
42
- return EXIT_FAILURE;
43
- }
44
- out.write(describeDocument(version, CONFORMANCE));
43
+ out.write(describeDocument(ownVersion(host), CONFORMANCE));
45
44
  return EXIT_CLEAN;
46
45
  };
@@ -1,7 +1,7 @@
1
+ import { EXIT_CLEAN, EXIT_USAGE } from "../contract/contract.generated.ts";
1
2
  import { runAnalysis, type RunAnalysis } from "../analysis.ts";
2
3
  import { listing } from "../components.ts";
3
4
  import type { Cascade } from "../config.ts";
4
- import { ConfigError } from "../config.ts";
5
5
  import type { CompletedFinding } from "../finding.ts";
6
6
  import { OUTSIDE, swept as judged } from "../graph.ts";
7
7
  import type { InventorySymbol } from "../inventory.ts";
@@ -12,7 +12,10 @@ import { positionKey } from "../position.ts";
12
12
  import type { Reference } from "../references.ts";
13
13
  import { resolve } from "../resolve.ts";
14
14
  import type { Root } from "../roots.ts";
15
- import { EXIT_CLEAN, EXIT_USAGE, type Verb, type VerbOptions } from "./verb.ts";
15
+ import { type Verb, type VerbOptions } from "./verb.ts";
16
+ import { UsageError } from "../usage.ts";
17
+ import { bytewise } from "../text/compare.ts";
18
+ import { diagnose } from "../product.ts";
16
19
 
17
20
  /**
18
21
  * The three spellings of an explanation request, each naming the symbol to explain. They are
@@ -44,16 +47,12 @@ function namedSymbol(option: (name: string) => string | undefined): string {
44
47
  });
45
48
  const [only, ...more] = named;
46
49
  if (only === undefined) {
47
- throw new ConfigError(
48
- "malformed",
49
- "",
50
+ throw new UsageError(
50
51
  "explain explains the symbol --why, --why-live or --why-not names, and none was named",
51
52
  );
52
53
  }
53
54
  if (more.length > 0) {
54
- throw new ConfigError(
55
- "malformed",
56
- "",
55
+ throw new UsageError(
57
56
  `explain explains one symbol, and ${named.map((one) => `--${one.name}`).join(", ")} each name one`,
58
57
  );
59
58
  }
@@ -101,7 +100,7 @@ function subjectOf(
101
100
  symbol.ref.toLowerCase().includes(folded) || symbol.name.toLowerCase().includes(folded),
102
101
  )
103
102
  .map(described)
104
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
103
+ .sort(bytewise);
105
104
  return { candidates: partial.slice(0, MAX_PARTIAL_MATCHES) };
106
105
  }
107
106
 
@@ -380,7 +379,7 @@ export const explainVerb: Verb = ({ out, err, host, inputs, scope, openClient, o
380
379
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
381
380
  });
382
381
  if (subject === undefined) {
383
- err.write(`deadset-ts: ${JSON.stringify(named)} names no one symbol of the target\n`);
382
+ diagnose(err, `${JSON.stringify(named)} names no one symbol of the target`);
384
383
  for (const candidate of candidates) {
385
384
  err.write(` ${candidate}\n`);
386
385
  }
@@ -391,7 +390,7 @@ export const explainVerb: Verb = ({ out, err, host, inputs, scope, openClient, o
391
390
  out.write(explanation(analysis, subject, config.reporters.cascade));
392
391
  }
393
392
  for (const note of notes) {
394
- err.write(`deadset-ts: ${note}\n`);
393
+ diagnose(err, note);
395
394
  }
396
395
  return subject === undefined ? EXIT_USAGE : EXIT_CLEAN;
397
396
  };
@@ -1,6 +1,7 @@
1
+ import { EXIT_CLEAN } from "../contract/contract.generated.ts";
1
2
  import { printConfig } from "../print.ts";
2
3
  import { resolve } from "../resolve.ts";
3
- import { EXIT_CLEAN, type Verb } from "./verb.ts";
4
+ import { type Verb } from "./verb.ts";
4
5
 
5
6
  /** Writes the resolved configuration with the source of every setting. */
6
7
  export const printConfigVerb: Verb = ({ out, inputs }) => {