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

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 (201) hide show
  1. package/dist/bin/mapper-connection.js +2 -1
  2. package/dist/bin/node-host.js +3 -2
  3. package/dist/src/analysis.js +28 -46
  4. package/dist/src/automatic-types.js +10 -29
  5. package/dist/src/build-scripts.js +17 -29
  6. package/dist/src/component-layer.js +4 -2
  7. package/dist/src/components.js +2 -3
  8. package/dist/src/config.js +3 -2
  9. package/dist/src/configuration-files.js +27 -168
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/contract/contract.generated.js +1084 -0
  12. package/dist/src/contract/report.generated.js +796 -0
  13. package/dist/src/conventions.js +20 -60
  14. package/dist/src/dependencies.js +25 -43
  15. package/dist/src/discover.js +13 -39
  16. package/dist/src/edges.js +7 -15
  17. package/dist/src/element-access.js +4 -6
  18. package/dist/src/entry-point-gaps.js +2 -8
  19. package/dist/src/entry-points.js +3 -10
  20. package/dist/src/enum-group.js +3 -10
  21. package/dist/src/erased-stores.js +4 -6
  22. package/dist/src/exempt-classes.js +3 -63
  23. package/dist/src/exempt.js +13 -40
  24. package/dist/src/file-facts.js +2 -25
  25. package/dist/src/findings/completion.js +11 -20
  26. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  27. package/dist/src/findings/emitters.js +4 -4
  28. package/dist/src/findings/interfaces.js +4 -5
  29. package/dist/src/findings/intra-function.js +8 -7
  30. package/dist/src/findings/non-code-artifacts.js +5 -6
  31. package/dist/src/findings/reads-and-writes.js +11 -9
  32. package/dist/src/findings/self-check.js +10 -12
  33. package/dist/src/findings/unused-declarations.js +28 -27
  34. package/dist/src/findings/visibility-narrowing.js +9 -11
  35. package/dist/src/findings-pass.js +7 -14
  36. package/dist/src/fs/manifest.js +43 -0
  37. package/dist/src/fs/tree.js +88 -0
  38. package/dist/src/git-ignore.js +34 -25
  39. package/dist/src/html-entries.js +6 -33
  40. package/dist/src/in-operands.js +2 -1
  41. package/dist/src/index.js +1 -1
  42. package/dist/src/intra-function-parts.js +18 -26
  43. package/dist/src/inventory.js +2 -21
  44. package/dist/src/json-document.js +4 -0
  45. package/dist/src/keyof-comparisons.js +4 -6
  46. package/dist/src/kinds.js +4 -360
  47. package/dist/src/manifest.js +5 -43
  48. package/dist/src/mdx-entries.js +3 -26
  49. package/dist/src/partial.js +2 -1
  50. package/dist/src/paths.js +26 -15
  51. package/dist/src/position.js +2 -1
  52. package/dist/src/print.js +3 -2
  53. package/dist/src/product.js +13 -0
  54. package/dist/src/references.js +14 -27
  55. package/dist/src/report-schema.js +272 -0
  56. package/dist/src/report.js +39 -42
  57. package/dist/src/reporters/sarif.js +4 -2
  58. package/dist/src/reporters/template.js +4 -19
  59. package/dist/src/resolve.js +12 -26
  60. package/dist/src/roots.js +7 -17
  61. package/dist/src/run.js +10 -8
  62. package/dist/src/schema.js +4 -3
  63. package/dist/src/scope.js +4 -5
  64. package/dist/src/serialization-contract.js +4 -8
  65. package/dist/src/session.js +2 -1
  66. package/dist/src/setup-failure.js +2 -6
  67. package/dist/src/suppress-file.js +12 -13
  68. package/dist/src/suppress-inline.js +3 -2
  69. package/dist/src/suppress.js +29 -48
  70. package/dist/src/symbol-keys.js +3 -5
  71. package/dist/src/template-field.js +5 -15
  72. package/dist/src/test-globals.js +6 -28
  73. package/dist/src/test-joins.js +6 -6
  74. package/dist/src/text/compare.js +26 -0
  75. package/dist/src/text/glob.js +194 -0
  76. package/dist/src/text/shell.js +159 -0
  77. package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
  78. package/dist/src/type-errors.js +14 -38
  79. package/dist/src/unseen-loads.js +2 -9
  80. package/dist/src/unwrap.js +23 -0
  81. package/dist/src/verbs/analyze.js +22 -15
  82. package/dist/src/verbs/describe.js +7 -7
  83. package/dist/src/verbs/explain.js +7 -4
  84. package/dist/src/verbs/print-config.js +2 -1
  85. package/dist/src/verbs/print-projects.js +4 -2
  86. package/dist/src/verbs/print-retained.js +4 -2
  87. package/dist/src/verbs/print-roots.js +4 -2
  88. package/dist/src/verbs/verb.js +1 -5
  89. package/dist/src/verbs/version.js +4 -3
  90. package/dist/src/workflow-steps.js +9 -46
  91. package/dist/src/workspace-manifests.js +2 -2
  92. package/dist/src/workspace-programs.js +5 -3
  93. package/dist/src/workspace-resolution.js +13 -17
  94. package/dist/src/workspace.js +28 -116
  95. package/jsr.json +1 -1
  96. package/package.json +2 -1
  97. package/src/analysis.ts +29 -54
  98. package/src/automatic-types.ts +11 -29
  99. package/src/build-scripts.ts +17 -38
  100. package/src/component-layer.ts +4 -2
  101. package/src/components.ts +2 -4
  102. package/src/config.ts +11 -15
  103. package/src/configuration-files.ts +27 -170
  104. package/src/conformance-record.ts +1 -1
  105. package/src/contract/contract.generated.ts +1148 -0
  106. package/src/contract/report.generated.ts +1157 -0
  107. package/src/conventions.ts +26 -74
  108. package/src/dependencies.ts +32 -50
  109. package/src/discover.ts +14 -39
  110. package/src/edges.ts +7 -21
  111. package/src/element-access.ts +4 -7
  112. package/src/entry-point-gaps.ts +3 -9
  113. package/src/entry-points.ts +2 -13
  114. package/src/enum-group.ts +2 -11
  115. package/src/erased-stores.ts +3 -7
  116. package/src/exempt-classes.ts +5 -74
  117. package/src/exempt.ts +17 -47
  118. package/src/file-facts.ts +6 -24
  119. package/src/finding.ts +9 -9
  120. package/src/findings/completion.ts +22 -23
  121. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  122. package/src/findings/emitter.ts +6 -7
  123. package/src/findings/emitters.ts +6 -6
  124. package/src/findings/interfaces.ts +4 -6
  125. package/src/findings/intra-function.ts +8 -8
  126. package/src/findings/non-code-artifacts.ts +5 -7
  127. package/src/findings/reads-and-writes.ts +15 -12
  128. package/src/findings/self-check.ts +11 -17
  129. package/src/findings/unused-declarations.ts +28 -27
  130. package/src/findings/visibility-narrowing.ts +9 -12
  131. package/src/findings-pass.ts +7 -16
  132. package/src/fs/manifest.ts +52 -0
  133. package/src/fs/tree.ts +110 -0
  134. package/src/git-ignore.ts +38 -25
  135. package/src/html-entries.ts +6 -33
  136. package/src/in-operands.ts +2 -1
  137. package/src/index.ts +1 -1
  138. package/src/intra-function-parts.ts +18 -28
  139. package/src/inventory.ts +2 -20
  140. package/src/json-document.ts +5 -0
  141. package/src/keyof-comparisons.ts +3 -7
  142. package/src/kinds.ts +6 -406
  143. package/src/manifest.ts +5 -46
  144. package/src/mdx-entries.ts +3 -26
  145. package/src/partial.ts +2 -1
  146. package/src/paths.ts +27 -15
  147. package/src/position.ts +2 -1
  148. package/src/print.ts +3 -2
  149. package/src/product.ts +18 -0
  150. package/src/references.ts +14 -29
  151. package/src/report-schema.ts +364 -0
  152. package/src/report.ts +66 -229
  153. package/src/reporters/annotations.ts +2 -2
  154. package/src/reporters/json.ts +1 -1
  155. package/src/reporters/reporter.ts +2 -2
  156. package/src/reporters/reporters.ts +1 -1
  157. package/src/reporters/sarif.ts +17 -12
  158. package/src/reporters/template.ts +4 -20
  159. package/src/reporters/text.ts +3 -3
  160. package/src/resolve.ts +31 -38
  161. package/src/roots.ts +7 -20
  162. package/src/run.ts +10 -14
  163. package/src/schema.ts +4 -3
  164. package/src/scope.ts +4 -10
  165. package/src/serialization-contract.ts +4 -9
  166. package/src/session.ts +2 -1
  167. package/src/setup-failure.ts +5 -7
  168. package/src/suppress-file.ts +11 -15
  169. package/src/suppress-inline.ts +2 -2
  170. package/src/suppress.ts +39 -55
  171. package/src/symbol-keys.ts +2 -5
  172. package/src/template-field.ts +5 -18
  173. package/src/test-globals.ts +9 -27
  174. package/src/test-joins.ts +6 -8
  175. package/src/text/compare.ts +27 -0
  176. package/src/text/glob.ts +210 -0
  177. package/src/text/shell.ts +161 -0
  178. package/src/{yaml-document.ts → text/yaml.ts} +46 -0
  179. package/src/type-errors.ts +25 -36
  180. package/src/unseen-loads.ts +1 -12
  181. package/src/unwrap.ts +34 -0
  182. package/src/verbs/analyze.ts +38 -25
  183. package/src/verbs/describe.ts +13 -7
  184. package/src/verbs/explain.ts +7 -4
  185. package/src/verbs/print-config.ts +2 -1
  186. package/src/verbs/print-projects.ts +4 -2
  187. package/src/verbs/print-retained.ts +4 -2
  188. package/src/verbs/print-roots.ts +4 -2
  189. package/src/verbs/verb.ts +0 -6
  190. package/src/verbs/version.ts +4 -3
  191. package/src/workflow-steps.ts +9 -44
  192. package/src/workspace-manifests.ts +2 -2
  193. package/src/workspace-programs.ts +5 -3
  194. package/src/workspace-resolution.ts +21 -17
  195. package/src/workspace.ts +35 -121
  196. package/dist/src/glob.js +0 -65
  197. package/dist/src/pnpm-workspace.js +0 -120
  198. package/dist/src/version.js +0 -7
  199. package/src/glob.ts +0 -67
  200. package/src/pnpm-workspace.ts +0 -135
  201. package/src/version.ts +0 -7
package/src/report.ts CHANGED
@@ -4,7 +4,9 @@
4
4
  * renders this document, so no two renderings disagree about which findings exist.
5
5
  */
6
6
 
7
- import type { Severity, Sort, TargetKind } from "./config.ts";
7
+ import { SCHEMA_VERSION, SCHEMA_VERSIONS } from "./contract/contract.generated.ts";
8
+ import type * as wire from "./contract/report.generated.ts";
9
+ import type { Sort, TargetKind } from "./config.ts";
8
10
  import type { Conformance, DeclaredGap } from "./conformance.ts";
9
11
  import type { EdgeEvaluation } from "./edges.ts";
10
12
  import type {
@@ -17,134 +19,8 @@ import type { AppliedConvention } from "./conventions.ts";
17
19
  import type { PassResult } from "./findings-pass.ts";
18
20
  import type { StaleSuppression } from "./findings/self-check.ts";
19
21
  import type { TestFileRule } from "./references.ts";
20
-
21
- /** The version of the report schema this analyzer writes a report to. */
22
- const SCHEMA_VERSION = "9.0.0";
23
-
24
- /** Every report schema version this analyzer reads: the one the Contract it implements admits. */
25
- export const SCHEMA_VERSIONS_ACCEPTED: readonly string[] = [SCHEMA_VERSION];
26
-
27
- /** The name this analyzer writes every document under and mints every identifier with. */
28
- export const ANALYZER_NAME = "deadset-ts";
29
-
30
- /** The language this analyzer claims. */
31
- export const LANGUAGE = "ts";
32
-
33
- interface WirePosition {
34
- readonly path: string;
35
- readonly line: number;
36
- readonly column: number;
37
- readonly end_line: number;
38
- }
39
-
40
- interface WireDetails {
41
- readonly narrower_visibility?: string;
42
- readonly implementations?: readonly {
43
- readonly ref: string;
44
- readonly name: string;
45
- readonly position: WirePosition;
46
- }[];
47
- readonly write_positions?: readonly WirePosition[];
48
- /** Set under the name-literal rule, which no finding of this analyzer meets; SARIF still maps it. */
49
- readonly name_literal?: WirePosition;
50
- readonly dependency_class?: string;
51
- readonly mechanism?: string;
52
- readonly entry?: {
53
- readonly code: string;
54
- readonly symbol?: string;
55
- readonly path?: string;
56
- readonly reason?: string;
57
- };
58
- readonly overlap?: readonly string[];
59
- readonly removes_last_use_of?: readonly string[];
60
- }
61
-
62
- /** One finding as the finding schema spells it. */
63
- export interface WireFinding {
64
- readonly code: string;
65
- readonly kind: string;
66
- readonly language: string;
67
- readonly position: WirePosition;
68
- readonly symbol: {
69
- readonly ref: string;
70
- readonly kind: string;
71
- readonly name: string;
72
- readonly size_lines: number;
73
- };
74
- readonly reachability_class: string;
75
- readonly confidence: string;
76
- readonly liveness_relation?: string;
77
- readonly test_only: boolean;
78
- readonly generated: boolean;
79
- readonly component: {
80
- readonly id: string;
81
- readonly root: boolean;
82
- readonly symbol_count: number;
83
- readonly deletable_lines: number;
84
- };
85
- readonly retained_by: readonly string[];
86
- readonly configurations: readonly string[];
87
- readonly consumers_loaded: readonly string[];
88
- readonly fixability: string;
89
- readonly severity: Severity;
90
- readonly message: string;
91
- readonly details: WireDetails;
92
- }
93
-
94
- /** One dead component a finding names, with its members, as the report lists it once. */
95
- export interface WireComponent {
96
- readonly id: string;
97
- readonly members: readonly {
98
- readonly ref: string;
99
- readonly name: string;
100
- readonly position: WirePosition;
101
- }[];
102
- }
103
-
104
- interface WireEdgeEvaluation {
105
- readonly edge: string;
106
- readonly side: string;
107
- readonly symbol: string;
108
- readonly state: string;
109
- readonly finding?: WireFinding;
110
- }
111
-
112
- export interface WireStaleSuppression {
113
- readonly code: string;
114
- readonly mechanism: string;
115
- readonly entry: {
116
- readonly code: string;
117
- readonly symbol?: string;
118
- readonly path: string;
119
- readonly reason: string;
120
- };
121
- readonly position: { readonly path: string; readonly line: number; readonly column: number };
122
- readonly symbol: string;
123
- readonly message: string;
124
- }
125
-
126
- interface WireDeclaredGap {
127
- readonly fixture: string;
128
- readonly symbol?: string;
129
- readonly capability: string;
130
- readonly reason: string;
131
- }
132
-
133
- export interface WireTotals {
134
- readonly findings: number;
135
- readonly by_severity: { readonly allow: number; readonly warn: number; readonly deny: number };
136
- readonly deletable_lines: number;
137
- readonly suppressions_in_effect: number;
138
- readonly reasons_recorded: number;
139
- readonly stale_suppressions: number;
140
- readonly pending: number;
141
- readonly omitted: number;
142
- readonly withheld: {
143
- readonly certain: number;
144
- readonly probable: number;
145
- readonly possible: number;
146
- };
147
- }
22
+ import { bytewise } from "./text/compare.ts";
23
+ import { LANGUAGE, PRODUCT } from "./product.ts";
148
24
 
149
25
  /** One project configuration of the matrix a run analyzed. */
150
26
  export interface ReportConfiguration {
@@ -191,52 +67,6 @@ interface UnavailableConsumer {
191
67
  readonly reason: string;
192
68
  }
193
69
 
194
- /** The report document. */
195
- export interface Report {
196
- readonly schema_version: string;
197
- readonly contract_version: string;
198
- readonly analyzer: {
199
- readonly name: string;
200
- readonly version: string;
201
- readonly languages: readonly string[];
202
- readonly schema_versions_accepted: readonly string[];
203
- readonly conformance: {
204
- readonly corpus_version: string;
205
- readonly result: string;
206
- readonly digest: string;
207
- };
208
- };
209
- readonly target: { readonly kind: TargetKind; readonly root: string; readonly identity: string };
210
- readonly configurations: readonly ReportConfiguration[];
211
- readonly configurations_not_built: readonly ReportNotBuilt[];
212
- readonly consumers: {
213
- readonly declared: number;
214
- readonly loaded: readonly {
215
- readonly id: string;
216
- readonly role: "consumer";
217
- readonly path: string;
218
- }[];
219
- readonly unavailable: readonly {
220
- readonly id: string;
221
- readonly role: "consumer";
222
- readonly reason: string;
223
- }[];
224
- };
225
- readonly findings: readonly WireFinding[];
226
- /** Present where the run lists each component in full. */
227
- readonly components?: readonly WireComponent[];
228
- readonly edge_evaluations: readonly WireEdgeEvaluation[];
229
- readonly stale_suppressions: readonly WireStaleSuppression[];
230
- readonly declared_gaps: readonly WireDeclaredGap[];
231
- readonly excluded_by_cgo: readonly never[];
232
- readonly test_file_rules: readonly TestFileRule[];
233
- readonly type_error_skips: readonly TypeErrorSkip[];
234
- readonly notes: readonly ReportNote[];
235
- readonly unanswered_questions: readonly UnansweredCount[];
236
- readonly conventions_applied: readonly AppliedConvention[];
237
- readonly totals: WireTotals;
238
- }
239
-
240
70
  /** One hint about the run's setup, as the report schema's `notes` spells it. */
241
71
  export type ReportNote =
242
72
  | {
@@ -282,27 +112,22 @@ export interface ReportInput {
282
112
  * sorted, then the edge evaluations' findings.
283
113
  */
284
114
  function componentsOf(
285
- findings: readonly (readonly [WireFinding, CompletedFinding])[],
286
- ): readonly WireComponent[] {
287
- const listed = new Map<string, WireComponent>();
288
- for (const [wire, finding] of findings) {
115
+ findings: readonly (readonly [wire.Finding, CompletedFinding])[],
116
+ ): readonly wire.ComponentsEntry[] {
117
+ const listed = new Map<string, wire.ComponentsEntry>();
118
+ for (const [written, finding] of findings) {
289
119
  const { members } = finding.component;
290
- if (members !== undefined && !listed.has(wire.component.id)) {
291
- listed.set(wire.component.id, { id: wire.component.id, members: members.map(wireSymbol) });
120
+ if (members !== undefined && !listed.has(written.component.id)) {
121
+ listed.set(written.component.id, {
122
+ id: written.component.id,
123
+ members: members.map(wireSymbol),
124
+ });
292
125
  }
293
126
  }
294
127
  return [...listed.values()];
295
128
  }
296
129
 
297
- /** Two strings ordered bytewise. */
298
- function compare(a: string, b: string): number {
299
- if (a === b) {
300
- return 0;
301
- }
302
- return a < b ? -1 : 1;
303
- }
304
-
305
- function wirePosition(position: FindingPosition): WirePosition {
130
+ function wirePosition(position: FindingPosition): wire.Position {
306
131
  return {
307
132
  path: position.path,
308
133
  line: position.line,
@@ -314,13 +139,13 @@ function wirePosition(position: FindingPosition): WirePosition {
314
139
  function wireSymbol(symbol: PositionedSymbol): {
315
140
  ref: string;
316
141
  name: string;
317
- position: WirePosition;
142
+ position: wire.Position;
318
143
  } {
319
144
  return { ref: symbol.ref, name: symbol.name, position: wirePosition(symbol.position) };
320
145
  }
321
146
 
322
147
  /** The details one code carries, in the order the finding schema lists them. */
323
- function wireDetails(details: FindingDetails): WireDetails {
148
+ function wireDetails(details: FindingDetails): wire.Details {
324
149
  const { entry } = details;
325
150
  return {
326
151
  ...(details.narrowerVisibility === undefined
@@ -352,7 +177,7 @@ function wireDetails(details: FindingDetails): WireDetails {
352
177
  }
353
178
 
354
179
  /** One finding as the finding schema spells it, member for member in its order. */
355
- export function wireFinding(finding: CompletedFinding): WireFinding {
180
+ export function wireFinding(finding: CompletedFinding): wire.Finding {
356
181
  return {
357
182
  code: finding.code,
358
183
  kind: finding.kind,
@@ -387,7 +212,7 @@ export function wireFinding(finding: CompletedFinding): WireFinding {
387
212
  };
388
213
  }
389
214
 
390
- function wireEvaluation(evaluation: EdgeEvaluation<CompletedFinding>): WireEdgeEvaluation {
215
+ function wireEvaluation(evaluation: EdgeEvaluation<CompletedFinding>): wire.EdgeEvaluationsEntry {
391
216
  return {
392
217
  edge: evaluation.edge,
393
218
  side: evaluation.side,
@@ -397,7 +222,7 @@ function wireEvaluation(evaluation: EdgeEvaluation<CompletedFinding>): WireEdgeE
397
222
  };
398
223
  }
399
224
 
400
- function wireStale(stale: StaleSuppression): WireStaleSuppression {
225
+ function wireStale(stale: StaleSuppression): wire.StaleSuppressionsEntry {
401
226
  return {
402
227
  code: stale.code,
403
228
  mechanism: stale.mechanism,
@@ -418,29 +243,29 @@ function wireStale(stale: StaleSuppression): WireStaleSuppression {
418
243
  }
419
244
 
420
245
  /** The canonical key over two findings: path, line, column, code, then the symbol reference. */
421
- function canonical(a: WireFinding, b: WireFinding): number {
246
+ function canonical(a: wire.Finding, b: wire.Finding): number {
422
247
  return (
423
- compare(a.position.path, b.position.path) ||
248
+ bytewise(a.position.path, b.position.path) ||
424
249
  a.position.line - b.position.line ||
425
250
  a.position.column - b.position.column ||
426
- compare(a.code, b.code) ||
427
- compare(a.symbol.ref, b.symbol.ref)
251
+ bytewise(a.code, b.code) ||
252
+ bytewise(a.symbol.ref, b.symbol.ref)
428
253
  );
429
254
  }
430
255
 
431
256
  /** The canonical key over two stale suppressions, each component from the record's own member. */
432
- function canonicalStale(a: WireStaleSuppression, b: WireStaleSuppression): number {
257
+ function canonicalStale(a: wire.StaleSuppressionsEntry, b: wire.StaleSuppressionsEntry): number {
433
258
  return (
434
- compare(a.position.path, b.position.path) ||
259
+ bytewise(a.position.path, b.position.path) ||
435
260
  a.position.line - b.position.line ||
436
261
  a.position.column - b.position.column ||
437
- compare(a.code, b.code) ||
438
- compare(a.symbol, b.symbol)
262
+ bytewise(a.code, b.code) ||
263
+ bytewise(a.symbol, b.symbol)
439
264
  );
440
265
  }
441
266
 
442
267
  /** One declared gap as a report carries it, its members in the schema's order. */
443
- function wireGap(gap: DeclaredGap): WireDeclaredGap {
268
+ function wireGap(gap: DeclaredGap): wire.DeclaredGapsEntry {
444
269
  return {
445
270
  fixture: gap.fixture,
446
271
  ...(gap.symbol === undefined ? {} : { symbol: gap.symbol }),
@@ -450,11 +275,11 @@ function wireGap(gap: DeclaredGap): WireDeclaredGap {
450
275
  }
451
276
 
452
277
  /** Edge evaluations by edge, then side, then their compact encodings. */
453
- function byEdge(a: WireEdgeEvaluation, b: WireEdgeEvaluation): number {
278
+ function byEdge(a: wire.EdgeEvaluationsEntry, b: wire.EdgeEvaluationsEntry): number {
454
279
  return (
455
- compare(a.edge, b.edge) ||
456
- compare(a.side, b.side) ||
457
- compare(JSON.stringify(a), JSON.stringify(b))
280
+ bytewise(a.edge, b.edge) ||
281
+ bytewise(a.side, b.side) ||
282
+ bytewise(JSON.stringify(a), JSON.stringify(b))
458
283
  );
459
284
  }
460
285
 
@@ -462,7 +287,7 @@ function byEdge(a: WireEdgeEvaluation, b: WireEdgeEvaluation): number {
462
287
  * The lines a reader deletes by acting on every finding: each distinct component a root
463
288
  * finding names, counted once however many of its roots the findings hold.
464
289
  */
465
- function deletableLines(findings: readonly WireFinding[]): number {
290
+ function deletableLines(findings: readonly wire.Finding[]): number {
466
291
  const counted = new Map<string, number>();
467
292
  for (const finding of findings) {
468
293
  if (finding.component.root) {
@@ -477,7 +302,7 @@ function deletableLines(findings: readonly WireFinding[]): number {
477
302
  }
478
303
 
479
304
  /** The totals of one finding set, counted over the whole set. */
480
- function totalsOf(findings: readonly WireFinding[], result: PassResult): WireTotals {
305
+ function totalsOf(findings: readonly wire.Finding[], result: PassResult): wire.Totals {
481
306
  const bySeverity = { allow: 0, warn: 0, deny: 0 };
482
307
  for (const finding of findings) {
483
308
  bySeverity[finding.severity] += 1;
@@ -500,7 +325,7 @@ function totalsOf(findings: readonly WireFinding[], result: PassResult): WireTot
500
325
  }
501
326
 
502
327
  /** Builds the report of one run, every array in the order the Contract fixes for it. */
503
- export function buildReport(input: ReportInput): Report {
328
+ export function buildReport(input: ReportInput): wire.Report {
504
329
  const paired = input.result.findings
505
330
  .map((finding) => [wireFinding(finding), finding] as const)
506
331
  .sort(([a], [b]) => canonical(a, b));
@@ -509,19 +334,19 @@ export function buildReport(input: ReportInput): Report {
509
334
  one.finding === undefined ? [] : [[wireFinding(one.finding), one.finding] as const],
510
335
  );
511
336
  const unavailable = [...new Map(input.unavailable.map((one) => [one.id, one])).values()]
512
- .sort((a, b) => compare(a.id, b.id))
337
+ .sort((a, b) => bytewise(a.id, b.id))
513
338
  .map((one) => ({ id: one.id, role: "consumer" as const, reason: one.reason }));
514
339
  const loaded = [...input.loaded]
515
- .sort((a, b) => compare(a.id, b.id) || compare(a.path, b.path))
340
+ .sort((a, b) => bytewise(a.id, b.id) || bytewise(a.path, b.path))
516
341
  .map((one) => ({ id: one.id, role: "consumer" as const, path: one.path }));
517
342
  return {
518
343
  schema_version: SCHEMA_VERSION,
519
344
  contract_version: input.contractVersion,
520
345
  analyzer: {
521
- name: ANALYZER_NAME,
346
+ name: PRODUCT,
522
347
  version: input.version,
523
348
  languages: [LANGUAGE],
524
- schema_versions_accepted: SCHEMA_VERSIONS_ACCEPTED,
349
+ schema_versions_accepted: SCHEMA_VERSIONS,
525
350
  conformance: {
526
351
  corpus_version: input.conformance.corpusVersion,
527
352
  result: input.conformance.result,
@@ -530,10 +355,10 @@ export function buildReport(input: ReportInput): Report {
530
355
  },
531
356
  target: { kind: input.target.kind, root: input.target.root, identity: input.target.identity },
532
357
  configurations: [...input.configurations]
533
- .sort((a, b) => compare(a.id, b.id))
358
+ .sort((a, b) => bytewise(a.id, b.id))
534
359
  .map((one) => ({ id: one.id, project: one.project })),
535
360
  configurations_not_built: [...input.notBuilt]
536
- .sort((a, b) => compare(a.id, b.id))
361
+ .sort((a, b) => bytewise(a.id, b.id))
537
362
  .map((one) => ({ id: one.id, project: one.project, error: one.error })),
538
363
  consumers: { declared: loaded.length + unavailable.length, loaded, unavailable },
539
364
  findings,
@@ -546,10 +371,10 @@ export function buildReport(input: ReportInput): Report {
546
371
  // name, so the compact encodings alone order them.
547
372
  declared_gaps: input.declaredGaps
548
373
  .map(wireGap)
549
- .sort((a, b) => compare(JSON.stringify(a), JSON.stringify(b))),
374
+ .sort((a, b) => bytewise(JSON.stringify(a), JSON.stringify(b))),
550
375
  excluded_by_cgo: [],
551
376
  test_file_rules: [...input.testFileRules]
552
- .sort((a, b) => compare(a.rule, b.rule) || a.matched - b.matched)
377
+ .sort((a, b) => bytewise(a.rule, b.rule) || a.matched - b.matched)
553
378
  .map((one) => ({ rule: one.rule, matched: one.matched })),
554
379
  type_error_skips: [
555
380
  ...new Map(
@@ -559,16 +384,18 @@ export function buildReport(input: ReportInput): Report {
559
384
  }),
560
385
  ),
561
386
  ]
562
- .sort(([a, x], [b, y]) => compare(x.path, y.path) || x.line - y.line || compare(a, b))
387
+ .sort(([a, x], [b, y]) => bytewise(x.path, y.path) || x.line - y.line || bytewise(a, b))
563
388
  .map(([, skip]) => skip),
564
389
  notes: [...new Map((input.notes ?? []).map((one) => [JSON.stringify(one), one])).entries()]
565
- .sort(([a, x], [b, y]) => compare(x.kind, y.kind) || compare(x.path, y.path) || compare(a, b))
390
+ .sort(
391
+ ([a, x], [b, y]) => bytewise(x.kind, y.kind) || bytewise(x.path, y.path) || bytewise(a, b),
392
+ )
566
393
  .map(([, note]) => note),
567
394
  unanswered_questions: [...input.unanswered]
568
395
  .sort(
569
396
  (a, b) =>
570
- compare(a.configuration, b.configuration) ||
571
- compare(JSON.stringify(a), JSON.stringify(b)),
397
+ bytewise(a.configuration, b.configuration) ||
398
+ bytewise(JSON.stringify(a), JSON.stringify(b)),
572
399
  )
573
400
  .map((one) => ({
574
401
  configuration: one.configuration,
@@ -590,8 +417,8 @@ export function buildReport(input: ReportInput): Report {
590
417
  * whose component a deletion shrinks more comes first, then one whose subject is larger,
591
418
  * then the canonical key, so the order stays total; by position it is the canonical key.
592
419
  */
593
- export function sortFindings(report: Report, by: Sort): Report {
594
- const bySize = (a: WireFinding, b: WireFinding): number =>
420
+ export function sortFindings(report: wire.Report, by: Sort): wire.Report {
421
+ const bySize = (a: wire.Finding, b: wire.Finding): number =>
595
422
  b.component.deletable_lines - a.component.deletable_lines ||
596
423
  b.symbol.size_lines - a.symbol.size_lines ||
597
424
  canonical(a, b);
@@ -599,16 +426,26 @@ export function sortFindings(report: Report, by: Sort): Report {
599
426
  }
600
427
 
601
428
  /**
602
- * The report holding the first `most` findings of its order, the rest counted as omitted.
603
- * Every other total keeps describing the whole set. Zero keeps every finding.
429
+ * The report holding the first `most` findings of its order, the rest counted as omitted,
430
+ * and listing only the components those findings name. Every other total keeps describing
431
+ * the whole set. Zero keeps every finding.
604
432
  */
605
- export function capFindings(report: Report, most: number): Report {
433
+ export function capFindings(report: wire.Report, most: number): wire.Report {
606
434
  if (most <= 0 || report.findings.length <= most) {
607
435
  return report;
608
436
  }
437
+ const findings = report.findings.slice(0, most);
438
+ const named = new Set(
439
+ [...findings, ...report.edge_evaluations.flatMap((one) => one.finding ?? [])].map(
440
+ (finding) => finding.component.id,
441
+ ),
442
+ );
609
443
  return {
610
444
  ...report,
611
- findings: report.findings.slice(0, most),
445
+ findings,
446
+ ...(report.components === undefined
447
+ ? {}
448
+ : { components: report.components.filter((listed) => named.has(listed.id)) }),
612
449
  totals: { ...report.totals, omitted: report.findings.length - most },
613
450
  };
614
451
  }
@@ -1,7 +1,7 @@
1
- import type { Severity } from "../config.ts";
1
+ import type { Severity } from "../contract/contract.generated.ts";
2
2
  import { coalesced } from "../json-chunks.ts";
3
3
  import { KINDS } from "../kinds.ts";
4
- import type { Report } from "../report.ts";
4
+ import type { Report } from "../contract/report.generated.ts";
5
5
 
6
6
  /** The escapes a workflow command's message needs, so a line break does not end the command. */
7
7
  const DATA_ESCAPES: Readonly<Record<string, string>> = { "%": "%25", "\r": "%0D", "\n": "%0A" };
@@ -1,5 +1,5 @@
1
1
  import { jsonChunks } from "../json-chunks.ts";
2
- import type { Report } from "../report.ts";
2
+ import type { Report } from "../contract/report.generated.ts";
3
3
 
4
4
  /** The indentation of the report document, which with the schema's member order fixes its bytes. */
5
5
  const INDENT = 2;
@@ -1,5 +1,5 @@
1
- import type { Format } from "../config.ts";
2
- import type { Report } from "../report.ts";
1
+ import type { Format } from "../contract/contract.generated.ts";
2
+ import type { Report } from "../contract/report.generated.ts";
3
3
  import type { Template } from "./template.ts";
4
4
 
5
5
  /** What a rendering reads beside the report. */
@@ -1,4 +1,4 @@
1
- import type { Format } from "../config.ts";
1
+ import type { Format } from "../contract/contract.generated.ts";
2
2
  import { annotations } from "./annotations.ts";
3
3
  import { json } from "./json.ts";
4
4
  import { RenderError, type Rendering, type Renderings } from "./reporter.ts";
@@ -1,12 +1,19 @@
1
- import type { Confidence, Severity } from "../config.ts";
2
1
  import { jsonChunks } from "../json-chunks.ts";
3
- import { KINDS, type KindRow } from "../kinds.ts";
2
+ import type { KindRow, Confidence, Severity } from "../contract/contract.generated.ts";
3
+ import { KINDS } from "../kinds.ts";
4
4
  import { declarationKey } from "../ref.ts";
5
- import type { Report, WireComponent, WireFinding, WireStaleSuppression } from "../report.ts";
5
+ import type {
6
+ ComponentsEntry,
7
+ Finding,
8
+ Report,
9
+ StaleSuppressionsEntry,
10
+ } from "../contract/report.generated.ts";
6
11
  import { lineHashes, symbolFingerprint } from "./fingerprint.ts";
7
12
  import { RenderError, type RenderOptions } from "./reporter.ts";
8
13
  import { utf8Bytes } from "./sha256.ts";
9
14
  import { withheldLine } from "./text.ts";
15
+ import { bytewise } from "../text/compare.ts";
16
+ import { message } from "../product.ts";
10
17
 
11
18
  const SCHEMA =
12
19
  "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";
@@ -128,9 +135,7 @@ function lineHashCache(read: (path: string) => string): (at: Located) => string
128
135
  try {
129
136
  text = read(at.path);
130
137
  } catch (error: unknown) {
131
- throw new RenderError(
132
- `read ${at.path} for its line fingerprint: ${error instanceof Error ? error.message : String(error)}`,
133
- );
138
+ throw new RenderError(`read ${at.path} for its line fingerprint: ${message(error)}`);
134
139
  }
135
140
  hashes = lineHashes(text);
136
141
  held.set(at.path, hashes);
@@ -152,10 +157,10 @@ interface Related {
152
157
  }
153
158
 
154
159
  /** The members the report lists for one component, none where it lists none. */
155
- type MembersOf = (id: string) => WireComponent["members"];
160
+ type MembersOf = (id: string) => ComponentsEntry["members"];
156
161
 
157
162
  /** Every position a finding names beyond its own, in the mapping's order, the first hundred. */
158
- function relatedOf(found: WireFinding, members: MembersOf): readonly Related[] {
163
+ function relatedOf(found: Finding, members: MembersOf): readonly Related[] {
159
164
  const related: Related[] = [];
160
165
  for (const one of found.details.implementations ?? []) {
161
166
  related.push({ label: "implementation", at: one.position, endLine: one.position.end_line });
@@ -192,7 +197,7 @@ function messageWithLinks(message: string, related: readonly Related[]): string
192
197
 
193
198
  /** Every member of the finding the mapping places nowhere else, under its schema name. */
194
199
  /** A result's member bag, its component holding the members the report lists for it once. */
195
- function propertiesOf(found: WireFinding, members: MembersOf): Record<string, unknown> {
200
+ function propertiesOf(found: Finding, members: MembersOf): Record<string, unknown> {
196
201
  const listed = members(found.component.id);
197
202
  return {
198
203
  language: found.language,
@@ -222,7 +227,7 @@ function ruleIndex(index: ReadonlyMap<string, number>, code: string): number {
222
227
  }
223
228
 
224
229
  function findingResult(
225
- found: WireFinding,
230
+ found: Finding,
226
231
  index: ReadonlyMap<string, number>,
227
232
  hashOf: (at: Located) => string,
228
233
  members: MembersOf,
@@ -253,7 +258,7 @@ function findingResult(
253
258
 
254
259
  /** A stale suppression at the failing level, at its own site; it is no finding, so it carries no member bag. */
255
260
  function staleResult(
256
- stale: WireStaleSuppression,
261
+ stale: StaleSuppressionsEntry,
257
262
  index: ReadonlyMap<string, number>,
258
263
  hashOf: (at: Located) => string,
259
264
  ): Record<string, unknown> {
@@ -287,7 +292,7 @@ export function sarif(report: Report, options: RenderOptions): Iterable<string>
287
292
  ...report.findings.map((found) => findingResult(found, index, hashOf, members)),
288
293
  ...report.stale_suppressions.map((stale) => staleResult(stale, index, hashOf)),
289
294
  ];
290
- const languages = [...report.analyzer.languages].sort().join("+");
295
+ const languages = [...report.analyzer.languages].sort(bytewise).join("+");
291
296
  const withheld = withheldLine(report.totals);
292
297
  const log = {
293
298
  $schema: SCHEMA,
@@ -10,6 +10,7 @@
10
10
  import { CHUNK_LENGTH } from "../json-chunks.ts";
11
11
  import { RenderError } from "./reporter.ts";
12
12
  import { utf8Bytes } from "./sha256.ts";
13
+ import { bytewise } from "../text/compare.ts";
13
14
 
14
15
  /** A template that does not parse, which refuses the invocation before any analysis. */
15
16
  export class TemplateError extends Error {
@@ -618,7 +619,7 @@ function formatted(value: Value, nested: boolean): string {
618
619
  }
619
620
  const object: ValueObject = value;
620
621
  const entries = Object.keys(object)
621
- .sort()
622
+ .sort(bytewise)
622
623
  .map((key) => `${key}:${formatted(object[key] ?? null, true)}`);
623
624
  return `map[${entries.join(" ")}]`;
624
625
  }
@@ -647,28 +648,11 @@ function compareBasic(name: string, a: Value, b: Value): number {
647
648
  return Number.NaN;
648
649
  }
649
650
  if (kind === "string") {
650
- return byCodePoints(a as string, b as string);
651
+ return bytewise(a as string, b as string);
651
652
  }
652
653
  return (a as number) < (b as number) ? -1 : 1;
653
654
  }
654
655
 
655
- /**
656
- * Two strings ordered by the bytes of their UTF-8 encodings, which is the order of their
657
- * code points: the order of their UTF-16 code units differs above U+FFFF.
658
- */
659
- function byCodePoints(a: string, b: string): number {
660
- let at = 0;
661
- while (at < a.length && at < b.length) {
662
- const x = a.codePointAt(at) ?? 0;
663
- const y = b.codePointAt(at) ?? 0;
664
- if (x !== y) {
665
- return x < y ? -1 : 1;
666
- }
667
- at += x > 0xffff ? 2 : 1;
668
- }
669
- return a.length - b.length;
670
- }
671
-
672
656
  /** The escapes `%q` writes for the control characters that have one of their own. */
673
657
  const QUOTED_ESCAPES: Readonly<Record<string, string>> = {
674
658
  '"': '\\"',
@@ -1022,7 +1006,7 @@ class Execution {
1022
1006
  entries = value.map((one, i) => [i, one]);
1023
1007
  } else if (isObject(value)) {
1024
1008
  entries = Object.keys(value)
1025
- .sort()
1009
+ .sort(bytewise)
1026
1010
  .map((key) => [key, value[key] ?? null]);
1027
1011
  } else {
1028
1012
  throw new RenderError(`range cannot iterate over a ${kindOf(value)}`);
@@ -1,5 +1,5 @@
1
1
  import { coalesced } from "../json-chunks.ts";
2
- import type { Report, WireTotals } from "../report.ts";
2
+ import type { Report, Totals } from "../contract/report.generated.ts";
3
3
 
4
4
  /** The kind token and the confidence a stale suppression is rendered with, both fixed. */
5
5
  const STALE_KIND = "suppression";
@@ -11,7 +11,7 @@ function counted(n: number, one: string, many: string): string {
11
11
  }
12
12
 
13
13
  /** The report's totals in one line, in the order the report declares them. */
14
- function summary(totals: WireTotals): string {
14
+ function summary(totals: Totals): string {
15
15
  const { allow, warn, deny } = totals.by_severity;
16
16
  return [
17
17
  `summary: ${counted(totals.findings, "finding", "findings")} (${String(allow)} allow, ${String(warn)} warn, ${String(deny)} deny)`,
@@ -31,7 +31,7 @@ const WITHHELD_ORDER = ["probable", "possible"] as const;
31
31
  * The line naming what the minimum confidence withheld and the setting that shows it all,
32
32
  * or undefined where it withheld nothing.
33
33
  */
34
- export function withheldLine(totals: WireTotals): string | undefined {
34
+ export function withheldLine(totals: Totals): string | undefined {
35
35
  const named = WITHHELD_ORDER.filter((confidence) => totals.withheld[confidence] > 0);
36
36
  const lowest = named.at(-1);
37
37
  if (lowest === undefined) {