@cplieger/deadset-ts 8.0.0-dev.6 → 8.0.0-dev.8

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 (193) hide show
  1. package/dist/bin/mapper-connection.js +2 -1
  2. package/dist/bin/node-host.js +24 -5
  3. package/dist/src/analysis.js +37 -56
  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 +6 -82
  9. package/dist/src/configuration-files.js +27 -168
  10. package/dist/src/configured-declarations.js +15 -20
  11. package/dist/src/consumers.js +2 -2
  12. package/dist/src/contract/config.generated.js +544 -0
  13. package/dist/src/contract/contract.generated.js +13 -0
  14. package/dist/src/contract/report.generated.js +3 -2
  15. package/dist/src/conventions.js +20 -60
  16. package/dist/src/dependencies.js +25 -43
  17. package/dist/src/discover.js +14 -40
  18. package/dist/src/edges.js +38 -122
  19. package/dist/src/element-access.js +4 -6
  20. package/dist/src/entry-point-gaps.js +2 -8
  21. package/dist/src/entry-points.js +3 -10
  22. package/dist/src/enum-group.js +3 -10
  23. package/dist/src/erased-stores.js +4 -6
  24. package/dist/src/exempt-classes.js +0 -4
  25. package/dist/src/exempt.js +6 -22
  26. package/dist/src/file-facts.js +2 -25
  27. package/dist/src/findings/completion.js +5 -4
  28. package/dist/src/findings/emitters.js +1 -1
  29. package/dist/src/findings/intra-function.js +1 -1
  30. package/dist/src/findings/non-code-artifacts.js +2 -2
  31. package/dist/src/findings/reads-and-writes.js +2 -1
  32. package/dist/src/findings/self-check.js +3 -2
  33. package/dist/src/findings/unused-declarations.js +1 -1
  34. package/dist/src/findings/visibility-narrowing.js +2 -2
  35. package/dist/src/findings-pass.js +7 -12
  36. package/dist/src/framework-lifecycle.js +7 -7
  37. package/dist/src/fs/document.js +59 -0
  38. package/dist/src/fs/manifest.js +43 -0
  39. package/dist/src/fs/tree.js +88 -0
  40. package/dist/src/git-ignore.js +34 -25
  41. package/dist/src/html-entries.js +6 -33
  42. package/dist/src/in-operands.js +2 -1
  43. package/dist/src/injection-container.js +1 -1
  44. package/dist/src/intra-function-parts.js +10 -13
  45. package/dist/src/inventory.js +2 -21
  46. package/dist/src/json-document.js +198 -67
  47. package/dist/src/keyof-comparisons.js +4 -6
  48. package/dist/src/kinds.js +0 -7
  49. package/dist/src/manifest.js +5 -43
  50. package/dist/src/mdx-entries.js +3 -26
  51. package/dist/src/partial.js +2 -1
  52. package/dist/src/paths.js +26 -15
  53. package/dist/src/position.js +2 -1
  54. package/dist/src/print.js +11 -79
  55. package/dist/src/product.js +13 -0
  56. package/dist/src/references.js +14 -27
  57. package/dist/src/report-schema.js +3 -225
  58. package/dist/src/report.js +22 -31
  59. package/dist/src/reporters/sarif.js +4 -2
  60. package/dist/src/reporters/template.js +4 -19
  61. package/dist/src/resolve.js +245 -724
  62. package/dist/src/roots.js +7 -17
  63. package/dist/src/run.js +55 -61
  64. package/dist/src/schema.js +320 -343
  65. package/dist/src/scope.js +34 -103
  66. package/dist/src/serialization-contract.js +5 -9
  67. package/dist/src/session.js +2 -1
  68. package/dist/src/suppress-file.js +21 -25
  69. package/dist/src/suppress.js +3 -3
  70. package/dist/src/symbol-keys.js +3 -5
  71. package/dist/src/template-field.js +9 -19
  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/usage.js +10 -0
  82. package/dist/src/verbs/analyze.js +25 -26
  83. package/dist/src/verbs/describe.js +4 -4
  84. package/dist/src/verbs/explain.js +8 -6
  85. package/dist/src/verbs/print-projects.js +4 -3
  86. package/dist/src/verbs/print-retained.js +2 -1
  87. package/dist/src/verbs/print-roots.js +2 -1
  88. package/dist/src/verbs/version.js +2 -1
  89. package/dist/src/workflow-steps.js +9 -46
  90. package/dist/src/workspace-manifests.js +2 -2
  91. package/dist/src/workspace-programs.js +5 -3
  92. package/dist/src/workspace-resolution.js +13 -17
  93. package/dist/src/workspace.js +28 -116
  94. package/jsr.json +1 -1
  95. package/package.json +1 -1
  96. package/src/analysis.ts +55 -76
  97. package/src/automatic-types.ts +11 -29
  98. package/src/build-scripts.ts +17 -38
  99. package/src/component-layer.ts +4 -2
  100. package/src/components.ts +2 -4
  101. package/src/config.ts +31 -296
  102. package/src/configuration-files.ts +27 -170
  103. package/src/configured-declarations.ts +18 -23
  104. package/src/consumers.ts +4 -4
  105. package/src/contract/config.generated.ts +798 -0
  106. package/src/contract/contract.generated.ts +25 -0
  107. package/src/contract/report.generated.ts +3 -2
  108. package/src/conventions.ts +26 -74
  109. package/src/dependencies.ts +32 -50
  110. package/src/discover.ts +15 -40
  111. package/src/edges.ts +41 -140
  112. package/src/element-access.ts +4 -7
  113. package/src/entry-point-gaps.ts +3 -9
  114. package/src/entry-points.ts +2 -13
  115. package/src/enum-group.ts +2 -11
  116. package/src/erased-stores.ts +3 -7
  117. package/src/exempt-classes.ts +0 -5
  118. package/src/exempt.ts +8 -30
  119. package/src/file-facts.ts +6 -24
  120. package/src/findings/completion.ts +5 -6
  121. package/src/findings/emitter.ts +2 -2
  122. package/src/findings/emitters.ts +1 -1
  123. package/src/findings/intra-function.ts +1 -1
  124. package/src/findings/non-code-artifacts.ts +4 -4
  125. package/src/findings/reads-and-writes.ts +2 -1
  126. package/src/findings/self-check.ts +3 -2
  127. package/src/findings/unused-declarations.ts +1 -1
  128. package/src/findings/visibility-narrowing.ts +2 -2
  129. package/src/findings-pass.ts +9 -15
  130. package/src/framework-lifecycle.ts +7 -7
  131. package/src/fs/document.ts +74 -0
  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/host.ts +12 -5
  136. package/src/html-entries.ts +6 -33
  137. package/src/in-operands.ts +2 -1
  138. package/src/injection-container.ts +1 -1
  139. package/src/intra-function-parts.ts +10 -14
  140. package/src/inventory.ts +2 -20
  141. package/src/json-document.ts +241 -76
  142. package/src/keyof-comparisons.ts +3 -7
  143. package/src/kinds.ts +0 -8
  144. package/src/manifest.ts +5 -46
  145. package/src/mdx-entries.ts +3 -26
  146. package/src/partial.ts +2 -1
  147. package/src/paths.ts +27 -15
  148. package/src/position.ts +2 -1
  149. package/src/print.ts +12 -92
  150. package/src/product.ts +18 -0
  151. package/src/references.ts +14 -29
  152. package/src/report-schema.ts +3 -301
  153. package/src/report.ts +24 -34
  154. package/src/reporters/sarif.ts +4 -4
  155. package/src/reporters/template.ts +4 -20
  156. package/src/resolve.ts +283 -1114
  157. package/src/roots.ts +7 -20
  158. package/src/run.ts +57 -75
  159. package/src/schema.ts +391 -388
  160. package/src/scope.ts +40 -126
  161. package/src/serialization-contract.ts +5 -10
  162. package/src/session.ts +2 -1
  163. package/src/suppress-file.ts +21 -29
  164. package/src/suppress.ts +3 -3
  165. package/src/symbol-keys.ts +2 -5
  166. package/src/template-field.ts +15 -25
  167. package/src/test-globals.ts +9 -27
  168. package/src/test-joins.ts +6 -8
  169. package/src/text/compare.ts +27 -0
  170. package/src/text/glob.ts +210 -0
  171. package/src/text/shell.ts +161 -0
  172. package/src/{yaml-document.ts → text/yaml.ts} +46 -0
  173. package/src/type-errors.ts +25 -36
  174. package/src/unseen-loads.ts +1 -12
  175. package/src/unwrap.ts +34 -0
  176. package/src/usage.ts +10 -0
  177. package/src/verbs/analyze.ts +32 -42
  178. package/src/verbs/describe.ts +4 -4
  179. package/src/verbs/explain.ts +8 -10
  180. package/src/verbs/print-projects.ts +4 -3
  181. package/src/verbs/print-retained.ts +2 -1
  182. package/src/verbs/print-roots.ts +2 -1
  183. package/src/verbs/verb.ts +2 -2
  184. package/src/verbs/version.ts +2 -1
  185. package/src/workflow-steps.ts +9 -44
  186. package/src/workspace-manifests.ts +2 -2
  187. package/src/workspace-programs.ts +5 -3
  188. package/src/workspace-resolution.ts +21 -17
  189. package/src/workspace.ts +35 -121
  190. package/dist/src/glob.js +0 -65
  191. package/dist/src/pnpm-workspace.js +0 -120
  192. package/src/glob.ts +0 -67
  193. package/src/pnpm-workspace.ts +0 -135
@@ -1,232 +1,10 @@
1
1
  /**
2
2
  * Holds a report to `contract/report.schema.json` and `contract/finding.schema.json`
3
3
  * before it is written, reading the schemas the generator copies from the Contract, so
4
- * this analyzer never writes a document a reader refuses. It evaluates the keywords the
5
- * Contract's schemas use; the schemas' patterns are written to match alike in RE2 and in
6
- * ECMAScript.
4
+ * this analyzer never writes a document a reader refuses.
7
5
  */
8
6
  import { REPORT_SCHEMAS } from "./contract/report.generated.js";
9
- /**
10
- * Every keyword the validator evaluates or knows to carry no assertion. A schema naming
11
- * another fails every check rather than passing values the keyword would refuse.
12
- */
13
- const KEYWORDS = new Set([
14
- ...["$schema", "$id", "$comment", "$defs", "title", "description", "default", "examples"],
15
- ...["$ref", "type", "enum", "const", "pattern", "minLength", "minimum", "maximum"],
16
- ...["properties", "additionalProperties", "required", "minProperties"],
17
- ...["items", "minItems", "maxItems", "uniqueItems"],
18
- ...["allOf", "anyOf", "oneOf", "not", "if", "then", "else"],
19
- ]);
20
- const patterns = new Map();
21
- function pattern(source) {
22
- let held = patterns.get(source);
23
- if (held === undefined) {
24
- held = new RegExp(source, "u");
25
- patterns.set(source, held);
26
- }
27
- return held;
28
- }
29
- function isObject(value) {
30
- return value !== null && typeof value === "object" && !Array.isArray(value);
31
- }
32
- function typeOf(value) {
33
- if (value === null) {
34
- return "null";
35
- }
36
- if (Array.isArray(value)) {
37
- return "array";
38
- }
39
- if (typeof value === "number") {
40
- return Number.isInteger(value) ? "integer" : "number";
41
- }
42
- return typeof value;
43
- }
44
- function hasType(value, name) {
45
- const held = typeOf(value);
46
- return held === name || (name === "number" && held === "integer");
47
- }
48
- /** One value's encoding with object members sorted, so two equal values encode alike. */
49
- function canonical(value) {
50
- if (Array.isArray(value)) {
51
- return `[${value.map(canonical).join(",")}]`;
52
- }
53
- if (isObject(value)) {
54
- return `{${Object.keys(value)
55
- .sort()
56
- .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
57
- .join(",")}}`;
58
- }
59
- return JSON.stringify(value);
60
- }
61
- /** A string's length in code points, which JSON Schema's minLength counts. */
62
- function codePoints(value) {
63
- let counted = 0;
64
- for (const _point of value) {
65
- counted += 1;
66
- }
67
- return counted;
68
- }
69
- const escape = (name) => name.replaceAll("~", "~0").replaceAll("/", "~1");
70
- /** A schema evaluated in the document it is read from, so a relative `$ref` resolves there. */
71
- class Checker {
72
- documents;
73
- constructor(documents) {
74
- this.documents = documents;
75
- }
76
- resolve(doc, ref) {
77
- const [file = "", pointer = ""] = ref.split("#");
78
- const target = file === "" ? doc : file;
79
- let schema = this.documents[target];
80
- for (const token of pointer.split("/").filter((one) => one !== "")) {
81
- schema = isObject(schema)
82
- ? schema[token.replaceAll("~1", "/").replaceAll("~0", "~")]
83
- : undefined;
84
- }
85
- if (!isObject(schema)) {
86
- throw new Error(`report schema: ${ref} names no schema`);
87
- }
88
- return [target, schema];
89
- }
90
- matches(doc, schema, value, at) {
91
- const found = [];
92
- this.check(doc, schema, value, at, found);
93
- return found.length === 0;
94
- }
95
- check(doc, schema, value, at, found) {
96
- for (const keyword of Object.keys(schema)) {
97
- if (!KEYWORDS.has(keyword) && !keyword.startsWith("x-")) {
98
- throw new Error(`report schema: the keyword ${keyword} is not one this validator evaluates`);
99
- }
100
- }
101
- const refuse = (keyword, reason, pointer = at) => {
102
- found.push({ pointer, keyword, reason });
103
- };
104
- if (typeof schema["$ref"] === "string") {
105
- const [target, referenced] = this.resolve(doc, schema["$ref"]);
106
- this.check(target, referenced, value, at, found);
107
- }
108
- const types = schema["type"];
109
- const named = types === undefined ? [] : Array.isArray(types) ? types : [types];
110
- if (named.length > 0 && !named.some((one) => hasType(value, String(one)))) {
111
- refuse("type", `the value is ${typeOf(value)}, want ${named.join(", ")}`);
112
- return;
113
- }
114
- this.checkValue(schema, value, refuse);
115
- if (Array.isArray(value)) {
116
- this.checkArray(doc, schema, value, at, found, refuse);
117
- }
118
- else if (isObject(value)) {
119
- this.checkObject(doc, schema, value, at, found, refuse);
120
- }
121
- this.checkApplicators(doc, schema, value, at, found, refuse);
122
- }
123
- checkValue(schema, value, refuse) {
124
- const values = schema["enum"];
125
- if (Array.isArray(values) && !values.some((one) => canonical(one) === canonical(value))) {
126
- refuse("enum", `${canonical(value)} is not one of the values the schema lists`);
127
- }
128
- if (schema["const"] !== undefined && canonical(schema["const"]) !== canonical(value)) {
129
- refuse("const", `${canonical(value)} is not ${canonical(schema["const"])}`);
130
- }
131
- if (typeof value === "string") {
132
- if (typeof schema["pattern"] === "string" && !pattern(schema["pattern"]).test(value)) {
133
- refuse("pattern", `${JSON.stringify(value)} does not match ${schema["pattern"]}`);
134
- }
135
- if (typeof schema["minLength"] === "number" && codePoints(value) < schema["minLength"]) {
136
- refuse("minLength", `${JSON.stringify(value)} is shorter than ${String(schema["minLength"])} characters`);
137
- }
138
- }
139
- if (typeof value === "number") {
140
- if (typeof schema["minimum"] === "number" && value < schema["minimum"]) {
141
- refuse("minimum", `${String(value)} is below ${String(schema["minimum"])}`);
142
- }
143
- if (typeof schema["maximum"] === "number" && value > schema["maximum"]) {
144
- refuse("maximum", `${String(value)} is above ${String(schema["maximum"])}`);
145
- }
146
- }
147
- }
148
- checkObject(doc, schema, value, at, found, refuse) {
149
- const properties = isObject(schema["properties"]) ? schema["properties"] : {};
150
- const additional = schema["additionalProperties"];
151
- for (const name of Object.keys(value).sort()) {
152
- const member = `${at}/${escape(name)}`;
153
- const declared = properties[name];
154
- if (isObject(declared)) {
155
- this.check(doc, declared, value[name], member, found);
156
- }
157
- else if (additional === false) {
158
- refuse("additionalProperties", `the member ${JSON.stringify(name)} is not one the schema declares`);
159
- }
160
- else if (isObject(additional)) {
161
- this.check(doc, additional, value[name], member, found);
162
- }
163
- }
164
- for (const name of Array.isArray(schema["required"]) ? schema["required"] : []) {
165
- if (!Object.hasOwn(value, name)) {
166
- refuse("required", `the member ${JSON.stringify(name)} is absent`);
167
- }
168
- }
169
- const fewest = schema["minProperties"];
170
- if (typeof fewest === "number" && Object.keys(value).length < fewest) {
171
- refuse("minProperties", `the object holds ${String(Object.keys(value).length)} members, fewer than ${String(fewest)}`);
172
- }
173
- }
174
- checkArray(doc, schema, value, at, found, refuse) {
175
- const items = schema["items"];
176
- if (isObject(items)) {
177
- value.forEach((item, i) => {
178
- this.check(doc, items, item, `${at}/${String(i)}`, found);
179
- });
180
- }
181
- if (typeof schema["minItems"] === "number" && value.length < schema["minItems"]) {
182
- refuse("minItems", `the array holds ${String(value.length)} items, fewer than ${String(schema["minItems"])}`);
183
- }
184
- if (typeof schema["maxItems"] === "number" && value.length > schema["maxItems"]) {
185
- refuse("maxItems", `the array holds ${String(value.length)} items, more than ${String(schema["maxItems"])}`);
186
- }
187
- if (schema["uniqueItems"] === true && new Set(value.map(canonical)).size !== value.length) {
188
- refuse("uniqueItems", "the array lists one value twice");
189
- }
190
- }
191
- checkApplicators(doc, schema, value, at, found, refuse) {
192
- const list = (keyword) => Array.isArray(schema[keyword]) ? schema[keyword] : [];
193
- for (const each of list("allOf")) {
194
- this.check(doc, each, value, at, found);
195
- }
196
- const matching = (keyword) => list(keyword).filter((each) => this.matches(doc, each, value, at)).length;
197
- if (schema["anyOf"] !== undefined && matching("anyOf") === 0) {
198
- refuse("anyOf", "the value matches none of the alternatives");
199
- }
200
- if (schema["oneOf"] !== undefined && matching("oneOf") !== 1) {
201
- refuse("oneOf", `the value matches ${String(matching("oneOf"))} of the alternatives, want exactly one`);
202
- }
203
- if (isObject(schema["not"]) && this.matches(doc, schema["not"], value, at)) {
204
- refuse("not", "the value matches a schema it must not match");
205
- }
206
- if (isObject(schema["if"])) {
207
- const branch = this.matches(doc, schema["if"], value, at) ? schema["then"] : schema["else"];
208
- if (isObject(branch)) {
209
- this.check(doc, branch, value, at, found);
210
- }
211
- }
212
- }
213
- }
214
- /**
215
- * A validator over one set of schema documents, each named as a `$ref` names it, rooted
216
- * at one of them: what it refuses in a value, none for an instance.
217
- */
218
- export function schemaRefusals(documents, root) {
219
- const checker = new Checker(documents);
220
- const top = documents[root];
221
- if (top === undefined) {
222
- throw new Error(`the schema set holds no document ${root}`);
223
- }
224
- return (value) => {
225
- const found = [];
226
- checker.check(root, top, value, "", found);
227
- return found;
228
- };
229
- }
7
+ import { schemaRefusals } from "./schema.js";
230
8
  const reportSchema = schemaRefusals(REPORT_SCHEMAS, "report.schema.json");
231
9
  /**
232
10
  * What the report schema refuses in one report document, none for an instance, and, as
@@ -243,7 +21,7 @@ function listingRefusals(report) {
243
21
  return [];
244
22
  }
245
23
  const refuse = (pointer, reason) => [
246
- { pointer, keyword: "components", reason },
24
+ { pointer, keyword: "components", reason, member: "" },
247
25
  ];
248
26
  const listed = new Set();
249
27
  for (const [i, entry] of report.components.entries()) {
@@ -4,10 +4,8 @@
4
4
  * renders this document, so no two renderings disagree about which findings exist.
5
5
  */
6
6
  import { SCHEMA_VERSION, SCHEMA_VERSIONS } from "./contract/contract.generated.js";
7
- /** The name this analyzer writes every document under and mints every identifier with. */
8
- export const ANALYZER_NAME = "deadset-ts";
9
- /** The language this analyzer claims. */
10
- export const LANGUAGE = "ts";
7
+ import { bytewise } from "./text/compare.js";
8
+ import { LANGUAGE, PRODUCT } from "./product.js";
11
9
  /**
12
10
  * Each component the findings name, once, with the members the first finding naming it
13
11
  * carries, in the order the findings are: `findings` paired with their wire forms and
@@ -26,13 +24,6 @@ function componentsOf(findings) {
26
24
  }
27
25
  return [...listed.values()];
28
26
  }
29
- /** Two strings ordered bytewise. */
30
- function compare(a, b) {
31
- if (a === b) {
32
- return 0;
33
- }
34
- return a < b ? -1 : 1;
35
- }
36
27
  function wirePosition(position) {
37
28
  return {
38
29
  path: position.path,
@@ -140,19 +131,19 @@ function wireStale(stale) {
140
131
  }
141
132
  /** The canonical key over two findings: path, line, column, code, then the symbol reference. */
142
133
  function canonical(a, b) {
143
- return (compare(a.position.path, b.position.path) ||
134
+ return (bytewise(a.position.path, b.position.path) ||
144
135
  a.position.line - b.position.line ||
145
136
  a.position.column - b.position.column ||
146
- compare(a.code, b.code) ||
147
- compare(a.symbol.ref, b.symbol.ref));
137
+ bytewise(a.code, b.code) ||
138
+ bytewise(a.symbol.ref, b.symbol.ref));
148
139
  }
149
140
  /** The canonical key over two stale suppressions, each component from the record's own member. */
150
141
  function canonicalStale(a, b) {
151
- return (compare(a.position.path, b.position.path) ||
142
+ return (bytewise(a.position.path, b.position.path) ||
152
143
  a.position.line - b.position.line ||
153
144
  a.position.column - b.position.column ||
154
- compare(a.code, b.code) ||
155
- compare(a.symbol, b.symbol));
145
+ bytewise(a.code, b.code) ||
146
+ bytewise(a.symbol, b.symbol));
156
147
  }
157
148
  /** One declared gap as a report carries it, its members in the schema's order. */
158
149
  function wireGap(gap) {
@@ -165,9 +156,9 @@ function wireGap(gap) {
165
156
  }
166
157
  /** Edge evaluations by edge, then side, then their compact encodings. */
167
158
  function byEdge(a, b) {
168
- return (compare(a.edge, b.edge) ||
169
- compare(a.side, b.side) ||
170
- compare(JSON.stringify(a), JSON.stringify(b)));
159
+ return (bytewise(a.edge, b.edge) ||
160
+ bytewise(a.side, b.side) ||
161
+ bytewise(JSON.stringify(a), JSON.stringify(b)));
171
162
  }
172
163
  /**
173
164
  * The lines a reader deletes by acting on every finding: each distinct component a root
@@ -216,16 +207,16 @@ export function buildReport(input) {
216
207
  const findings = paired.map(([wire]) => wire);
217
208
  const evaluated = input.result.edgeEvaluations.flatMap((one) => one.finding === undefined ? [] : [[wireFinding(one.finding), one.finding]]);
218
209
  const unavailable = [...new Map(input.unavailable.map((one) => [one.id, one])).values()]
219
- .sort((a, b) => compare(a.id, b.id))
210
+ .sort((a, b) => bytewise(a.id, b.id))
220
211
  .map((one) => ({ id: one.id, role: "consumer", reason: one.reason }));
221
212
  const loaded = [...input.loaded]
222
- .sort((a, b) => compare(a.id, b.id) || compare(a.path, b.path))
213
+ .sort((a, b) => bytewise(a.id, b.id) || bytewise(a.path, b.path))
223
214
  .map((one) => ({ id: one.id, role: "consumer", path: one.path }));
224
215
  return {
225
216
  schema_version: SCHEMA_VERSION,
226
217
  contract_version: input.contractVersion,
227
218
  analyzer: {
228
- name: ANALYZER_NAME,
219
+ name: PRODUCT,
229
220
  version: input.version,
230
221
  languages: [LANGUAGE],
231
222
  schema_versions_accepted: SCHEMA_VERSIONS,
@@ -237,10 +228,10 @@ export function buildReport(input) {
237
228
  },
238
229
  target: { kind: input.target.kind, root: input.target.root, identity: input.target.identity },
239
230
  configurations: [...input.configurations]
240
- .sort((a, b) => compare(a.id, b.id))
231
+ .sort((a, b) => bytewise(a.id, b.id))
241
232
  .map((one) => ({ id: one.id, project: one.project })),
242
233
  configurations_not_built: [...input.notBuilt]
243
- .sort((a, b) => compare(a.id, b.id))
234
+ .sort((a, b) => bytewise(a.id, b.id))
244
235
  .map((one) => ({ id: one.id, project: one.project, error: one.error })),
245
236
  consumers: { declared: loaded.length + unavailable.length, loaded, unavailable },
246
237
  findings,
@@ -253,10 +244,10 @@ export function buildReport(input) {
253
244
  // name, so the compact encodings alone order them.
254
245
  declared_gaps: input.declaredGaps
255
246
  .map(wireGap)
256
- .sort((a, b) => compare(JSON.stringify(a), JSON.stringify(b))),
247
+ .sort((a, b) => bytewise(JSON.stringify(a), JSON.stringify(b))),
257
248
  excluded_by_cgo: [],
258
249
  test_file_rules: [...input.testFileRules]
259
- .sort((a, b) => compare(a.rule, b.rule) || a.matched - b.matched)
250
+ .sort((a, b) => bytewise(a.rule, b.rule) || a.matched - b.matched)
260
251
  .map((one) => ({ rule: one.rule, matched: one.matched })),
261
252
  type_error_skips: [
262
253
  ...new Map(input.typeErrorSkips.map((one) => {
@@ -264,14 +255,14 @@ export function buildReport(input) {
264
255
  return [JSON.stringify(skip), skip];
265
256
  })),
266
257
  ]
267
- .sort(([a, x], [b, y]) => compare(x.path, y.path) || x.line - y.line || compare(a, b))
258
+ .sort(([a, x], [b, y]) => bytewise(x.path, y.path) || x.line - y.line || bytewise(a, b))
268
259
  .map(([, skip]) => skip),
269
260
  notes: [...new Map((input.notes ?? []).map((one) => [JSON.stringify(one), one])).entries()]
270
- .sort(([a, x], [b, y]) => compare(x.kind, y.kind) || compare(x.path, y.path) || compare(a, b))
261
+ .sort(([a, x], [b, y]) => bytewise(x.kind, y.kind) || bytewise(x.path, y.path) || bytewise(a, b))
271
262
  .map(([, note]) => note),
272
263
  unanswered_questions: [...input.unanswered]
273
- .sort((a, b) => compare(a.configuration, b.configuration) ||
274
- compare(JSON.stringify(a), JSON.stringify(b)))
264
+ .sort((a, b) => bytewise(a.configuration, b.configuration) ||
265
+ bytewise(JSON.stringify(a), JSON.stringify(b)))
275
266
  .map((one) => ({
276
267
  configuration: one.configuration,
277
268
  questions: one.questions,
@@ -5,6 +5,8 @@ import { lineHashes, symbolFingerprint } from "./fingerprint.js";
5
5
  import { RenderError } from "./reporter.js";
6
6
  import { utf8Bytes } from "./sha256.js";
7
7
  import { withheldLine } from "./text.js";
8
+ import { bytewise } from "../text/compare.js";
9
+ import { message } from "../product.js";
8
10
  const SCHEMA = "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";
9
11
  const VERSION = "2.1.0";
10
12
  /** The unit a finding's column counts. */
@@ -101,7 +103,7 @@ function lineHashCache(read) {
101
103
  text = read(at.path);
102
104
  }
103
105
  catch (error) {
104
- throw new RenderError(`read ${at.path} for its line fingerprint: ${error instanceof Error ? error.message : String(error)}`);
106
+ throw new RenderError(`read ${at.path} for its line fingerprint: ${message(error)}`);
105
107
  }
106
108
  hashes = lineHashes(text);
107
109
  held.set(at.path, hashes);
@@ -226,7 +228,7 @@ export function sarif(report, options) {
226
228
  ...report.findings.map((found) => findingResult(found, index, hashOf, members)),
227
229
  ...report.stale_suppressions.map((stale) => staleResult(stale, index, hashOf)),
228
230
  ];
229
- const languages = [...report.analyzer.languages].sort().join("+");
231
+ const languages = [...report.analyzer.languages].sort(bytewise).join("+");
230
232
  const withheld = withheldLine(report.totals);
231
233
  const log = {
232
234
  $schema: SCHEMA,
@@ -9,6 +9,7 @@
9
9
  import { CHUNK_LENGTH } from "../json-chunks.js";
10
10
  import { RenderError } from "./reporter.js";
11
11
  import { utf8Bytes } from "./sha256.js";
12
+ import { bytewise } from "../text/compare.js";
12
13
  /** A template that does not parse, which refuses the invocation before any analysis. */
13
14
  export class TemplateError extends Error {
14
15
  constructor(message) {
@@ -513,7 +514,7 @@ function formatted(value, nested) {
513
514
  }
514
515
  const object = value;
515
516
  const entries = Object.keys(object)
516
- .sort()
517
+ .sort(bytewise)
517
518
  .map((key) => `${key}:${formatted(object[key] ?? null, true)}`);
518
519
  return `map[${entries.join(" ")}]`;
519
520
  }
@@ -538,26 +539,10 @@ function compareBasic(name, a, b) {
538
539
  return Number.NaN;
539
540
  }
540
541
  if (kind === "string") {
541
- return byCodePoints(a, b);
542
+ return bytewise(a, b);
542
543
  }
543
544
  return a < b ? -1 : 1;
544
545
  }
545
- /**
546
- * Two strings ordered by the bytes of their UTF-8 encodings, which is the order of their
547
- * code points: the order of their UTF-16 code units differs above U+FFFF.
548
- */
549
- function byCodePoints(a, b) {
550
- let at = 0;
551
- while (at < a.length && at < b.length) {
552
- const x = a.codePointAt(at) ?? 0;
553
- const y = b.codePointAt(at) ?? 0;
554
- if (x !== y) {
555
- return x < y ? -1 : 1;
556
- }
557
- at += x > 0xffff ? 2 : 1;
558
- }
559
- return a.length - b.length;
560
- }
561
546
  /** The escapes `%q` writes for the control characters that have one of their own. */
562
547
  const QUOTED_ESCAPES = {
563
548
  '"': '\\"',
@@ -903,7 +888,7 @@ class Execution {
903
888
  }
904
889
  else if (isObject(value)) {
905
890
  entries = Object.keys(value)
906
- .sort()
891
+ .sort(bytewise)
907
892
  .map((key) => [key, value[key] ?? null]);
908
893
  }
909
894
  else {