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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/mapper-connection.js +2 -1
  3. package/dist/bin/node-host.js +24 -5
  4. package/dist/src/analysis.js +245 -309
  5. package/dist/src/augmentations.js +124 -18
  6. package/dist/src/automatic-types.js +170 -0
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +9 -9
  10. package/dist/src/config.js +6 -81
  11. package/dist/src/configuration-files.js +78 -188
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +2 -2
  14. package/dist/src/consumers.js +3 -30
  15. package/dist/src/contextual-uses.js +56 -9
  16. package/dist/src/contract/config.generated.js +558 -0
  17. package/dist/src/contract/contract.generated.js +1244 -0
  18. package/dist/src/contract/report.generated.js +862 -0
  19. package/dist/src/convention-rows.js +146 -1
  20. package/dist/src/conventions.js +136 -70
  21. package/dist/src/decorator.js +21 -18
  22. package/dist/src/dependencies.js +280 -62
  23. package/dist/src/edges.js +38 -122
  24. package/dist/src/element-access.js +4 -6
  25. package/dist/src/entry-point-gaps.js +9 -18
  26. package/dist/src/entry-points.js +106 -21
  27. package/dist/src/enum-group.js +3 -10
  28. package/dist/src/erased-stores.js +4 -6
  29. package/dist/src/exempt-classes.js +1 -63
  30. package/dist/src/exempt.js +21 -40
  31. package/dist/src/failure.js +36 -0
  32. package/dist/src/file-facts.js +2 -25
  33. package/dist/src/findings/completion.js +225 -63
  34. package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
  35. package/dist/src/findings/emitters.js +6 -6
  36. package/dist/src/findings/interfaces.js +4 -5
  37. package/dist/src/findings/intra-function.js +53 -19
  38. package/dist/src/findings/non-code-artifacts.js +71 -39
  39. package/dist/src/findings/reads-and-writes.js +16 -13
  40. package/dist/src/findings/self-check.js +10 -12
  41. package/dist/src/findings/unused-declarations.js +57 -28
  42. package/dist/src/findings/visibility-narrowing.js +40 -18
  43. package/dist/src/findings-pass.js +8 -15
  44. package/dist/src/framework-lifecycle.js +7 -7
  45. package/dist/src/fs/document.js +52 -0
  46. package/dist/src/fs/manifest.js +72 -0
  47. package/dist/src/fs/tree.js +88 -0
  48. package/dist/src/generated-file.js +18 -4
  49. package/dist/src/git-ignore.js +99 -0
  50. package/dist/src/graph.js +9 -4
  51. package/dist/src/html-entries.js +6 -33
  52. package/dist/src/import-bindings.js +97 -0
  53. package/dist/src/in-operands.js +2 -1
  54. package/dist/src/index.js +1 -1
  55. package/dist/src/indexed-access.js +130 -28
  56. package/dist/src/injection-container.js +1 -1
  57. package/dist/src/interface-satisfaction.js +30 -12
  58. package/dist/src/intra-function-parts.js +341 -69
  59. package/dist/src/inventory.js +54 -29
  60. package/dist/src/javascript-file.js +33 -0
  61. package/dist/src/json-document.js +198 -67
  62. package/dist/src/keyof-comparisons.js +4 -6
  63. package/dist/src/kinds.js +3 -366
  64. package/dist/src/load/classify.js +270 -0
  65. package/dist/src/load/diagnostics.js +80 -0
  66. package/dist/src/load/discover.js +277 -0
  67. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  68. package/dist/src/load/expected-module.js +136 -0
  69. package/dist/src/load/load.js +27 -0
  70. package/dist/src/load/module-lookup.js +37 -0
  71. package/dist/src/load/plan.js +313 -0
  72. package/dist/src/load/specifiers.js +115 -0
  73. package/dist/src/load/synthesize.js +140 -0
  74. package/dist/src/load/test-globals.js +207 -0
  75. package/dist/src/load/test-joins.js +82 -0
  76. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  77. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
  78. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
  79. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  80. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  81. package/dist/src/manifest.js +49 -54
  82. package/dist/src/matrix.js +94 -13
  83. package/dist/src/mdx-entries.js +174 -0
  84. package/dist/src/module-declarations.js +9 -13
  85. package/dist/src/outside-tests.js +1 -1
  86. package/dist/src/overrides.js +41 -10
  87. package/dist/src/partial.js +2 -1
  88. package/dist/src/paths.js +26 -15
  89. package/dist/src/position.js +2 -1
  90. package/dist/src/print.js +11 -78
  91. package/dist/src/product.js +13 -0
  92. package/dist/src/query.js +1 -0
  93. package/dist/src/ref.js +11 -0
  94. package/dist/src/references.js +52 -31
  95. package/dist/src/report-schema.js +51 -0
  96. package/dist/src/report.js +59 -41
  97. package/dist/src/reporters/annotations.js +15 -8
  98. package/dist/src/reporters/sarif.js +16 -10
  99. package/dist/src/reporters/template.js +4 -19
  100. package/dist/src/resolve.js +245 -727
  101. package/dist/src/roots.js +374 -116
  102. package/dist/src/run.js +66 -77
  103. package/dist/src/schema.js +320 -342
  104. package/dist/src/scope.js +38 -115
  105. package/dist/src/serialization-contract.js +649 -29
  106. package/dist/src/session.js +41 -29
  107. package/dist/src/source-files.js +40 -15
  108. package/dist/src/suppress-file.js +34 -29
  109. package/dist/src/suppress-inline.js +21 -4
  110. package/dist/src/suppress.js +40 -56
  111. package/dist/src/sweep.js +32 -17
  112. package/dist/src/symbol-keys.js +105 -6
  113. package/dist/src/template-field.js +9 -19
  114. package/dist/src/test-support.js +119 -18
  115. package/dist/src/text/compare.js +26 -0
  116. package/dist/src/text/glob.js +194 -0
  117. package/dist/src/text/named-files.js +54 -0
  118. package/dist/src/text/shell.js +159 -0
  119. package/dist/src/text/yaml.js +393 -0
  120. package/dist/src/type-errors.js +27 -221
  121. package/dist/src/type-parameter-reads.js +172 -0
  122. package/dist/src/unseen-loads.js +100 -0
  123. package/dist/src/unwrap.js +23 -0
  124. package/dist/src/usage.js +10 -0
  125. package/dist/src/verbs/analyze.js +181 -125
  126. package/dist/src/verbs/describe.js +9 -16
  127. package/dist/src/verbs/explain.js +14 -9
  128. package/dist/src/verbs/print-config.js +2 -1
  129. package/dist/src/verbs/print-projects.js +6 -4
  130. package/dist/src/verbs/print-retained.js +4 -2
  131. package/dist/src/verbs/print-roots.js +4 -2
  132. package/dist/src/verbs/verb.js +1 -5
  133. package/dist/src/verbs/version.js +14 -3
  134. package/dist/src/workflow-steps.js +128 -33
  135. package/jsr.json +1 -1
  136. package/package.json +5 -4
  137. package/src/analysis.ts +333 -457
  138. package/src/augmentations.ts +152 -23
  139. package/src/automatic-types.ts +228 -0
  140. package/src/build-scripts.ts +543 -0
  141. package/src/component-layer.ts +4 -2
  142. package/src/components.ts +9 -10
  143. package/src/config.ts +31 -299
  144. package/src/configuration-files.ts +100 -191
  145. package/src/configured-declarations.ts +18 -23
  146. package/src/conformance-record.ts +2 -2
  147. package/src/consumers.ts +6 -57
  148. package/src/contextual-uses.ts +64 -12
  149. package/src/contract/config.generated.ts +818 -0
  150. package/src/contract/contract.generated.ts +1308 -0
  151. package/src/contract/report.generated.ts +1237 -0
  152. package/src/convention-rows.ts +186 -1
  153. package/src/conventions.ts +166 -90
  154. package/src/decorator.ts +28 -18
  155. package/src/dependencies.ts +356 -78
  156. package/src/edges.ts +41 -140
  157. package/src/element-access.ts +4 -7
  158. package/src/entry-point-gaps.ts +11 -20
  159. package/src/entry-points.ts +124 -28
  160. package/src/enum-group.ts +2 -11
  161. package/src/erased-stores.ts +3 -7
  162. package/src/exempt-classes.ts +1 -75
  163. package/src/exempt.ts +36 -50
  164. package/src/failure.ts +63 -0
  165. package/src/file-facts.ts +9 -24
  166. package/src/finding.ts +24 -9
  167. package/src/findings/boundary.ts +2 -0
  168. package/src/findings/completion.ts +297 -77
  169. package/src/findings/dependencies-and-module-machinery.ts +90 -31
  170. package/src/findings/emitter.ts +8 -9
  171. package/src/findings/emitters.ts +8 -8
  172. package/src/findings/interfaces.ts +4 -6
  173. package/src/findings/intra-function.ts +64 -20
  174. package/src/findings/non-code-artifacts.ts +88 -50
  175. package/src/findings/reads-and-writes.ts +20 -16
  176. package/src/findings/self-check.ts +11 -17
  177. package/src/findings/unused-declarations.ts +65 -28
  178. package/src/findings/visibility-narrowing.ts +51 -19
  179. package/src/findings-pass.ts +10 -19
  180. package/src/framework-lifecycle.ts +7 -7
  181. package/src/fs/document.ts +70 -0
  182. package/src/fs/manifest.ts +92 -0
  183. package/src/fs/tree.ts +110 -0
  184. package/src/generated-file.ts +23 -4
  185. package/src/git-ignore.ts +122 -0
  186. package/src/graph.ts +11 -3
  187. package/src/host.ts +17 -14
  188. package/src/html-entries.ts +6 -33
  189. package/src/import-bindings.ts +128 -0
  190. package/src/in-operands.ts +2 -1
  191. package/src/index.ts +1 -1
  192. package/src/indexed-access.ts +152 -30
  193. package/src/injection-container.ts +1 -1
  194. package/src/interface-satisfaction.ts +33 -13
  195. package/src/intra-function-parts.ts +449 -85
  196. package/src/inventory.ts +73 -27
  197. package/src/javascript-file.ts +37 -0
  198. package/src/json-document.ts +241 -76
  199. package/src/keyof-comparisons.ts +3 -7
  200. package/src/kinds.ts +5 -413
  201. package/src/load/classify.ts +397 -0
  202. package/src/load/diagnostics.ts +144 -0
  203. package/src/load/discover.ts +455 -0
  204. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  205. package/src/load/expected-module.ts +188 -0
  206. package/src/load/load.ts +73 -0
  207. package/src/load/module-lookup.ts +50 -0
  208. package/src/load/plan.ts +504 -0
  209. package/src/load/specifiers.ts +168 -0
  210. package/src/load/synthesize.ts +211 -0
  211. package/src/load/test-globals.ts +251 -0
  212. package/src/load/test-joins.ts +98 -0
  213. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  214. package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
  215. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
  216. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  217. package/src/{workspace.ts → load/workspace.ts} +90 -134
  218. package/src/manifest.ts +81 -75
  219. package/src/matrix.ts +154 -18
  220. package/src/mdx-entries.ts +218 -0
  221. package/src/module-declarations.ts +11 -13
  222. package/src/outside-tests.ts +1 -1
  223. package/src/overrides.ts +51 -12
  224. package/src/partial.ts +3 -2
  225. package/src/paths.ts +27 -15
  226. package/src/position.ts +2 -1
  227. package/src/print.ts +12 -91
  228. package/src/product.ts +18 -0
  229. package/src/query.ts +11 -0
  230. package/src/ref.ts +13 -0
  231. package/src/references.ts +62 -32
  232. package/src/report-schema.ts +68 -0
  233. package/src/report.ts +105 -215
  234. package/src/reporters/annotations.ts +22 -11
  235. package/src/reporters/json.ts +1 -1
  236. package/src/reporters/reporter.ts +2 -4
  237. package/src/reporters/reporters.ts +1 -1
  238. package/src/reporters/sarif.ts +29 -16
  239. package/src/reporters/template.ts +4 -20
  240. package/src/reporters/text.ts +3 -3
  241. package/src/resolve.ts +283 -1111
  242. package/src/roots.ts +490 -137
  243. package/src/run.ts +75 -91
  244. package/src/schema.ts +391 -387
  245. package/src/scope.ts +46 -138
  246. package/src/serialization-contract.ts +818 -26
  247. package/src/session.ts +77 -54
  248. package/src/source-files.ts +67 -17
  249. package/src/suppress-file.ts +36 -34
  250. package/src/suppress-inline.ts +20 -3
  251. package/src/suppress.ts +52 -63
  252. package/src/sweep.ts +53 -18
  253. package/src/symbol-keys.ts +131 -7
  254. package/src/template-field.ts +15 -25
  255. package/src/test-support.ts +155 -18
  256. package/src/text/compare.ts +27 -0
  257. package/src/text/glob.ts +210 -0
  258. package/src/text/named-files.inputs.json +1522 -0
  259. package/src/text/named-files.ts +68 -0
  260. package/src/text/shell.ts +161 -0
  261. package/src/text/yaml.ts +423 -0
  262. package/src/type-errors.ts +35 -273
  263. package/src/type-parameter-reads.ts +227 -0
  264. package/src/unseen-loads.ts +134 -0
  265. package/src/unwrap.ts +34 -0
  266. package/src/usage.ts +10 -0
  267. package/src/verbs/analyze.ts +245 -161
  268. package/src/verbs/describe.ts +14 -15
  269. package/src/verbs/explain.ts +15 -13
  270. package/src/verbs/print-config.ts +2 -1
  271. package/src/verbs/print-projects.ts +6 -4
  272. package/src/verbs/print-retained.ts +4 -2
  273. package/src/verbs/print-roots.ts +4 -2
  274. package/src/verbs/verb.ts +2 -8
  275. package/src/verbs/version.ts +15 -3
  276. package/src/workflow-steps.ts +149 -34
  277. package/dist/src/discover.js +0 -347
  278. package/dist/src/glob.js +0 -65
  279. package/dist/src/pnpm-workspace.js +0 -120
  280. package/dist/src/setup-failure.js +0 -23
  281. package/dist/src/test-joins.js +0 -101
  282. package/dist/src/version.js +0 -7
  283. package/src/discover.ts +0 -510
  284. package/src/glob.ts +0 -67
  285. package/src/pnpm-workspace.ts +0 -135
  286. package/src/setup-failure.ts +0 -39
  287. package/src/test-joins.ts +0 -120
  288. package/src/version.ts +0 -7
@@ -1,12 +1,12 @@
1
- import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isMetaProperty, isNewExpression, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/ast";
1
+ import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isInterfaceDeclaration, isMetaProperty, isModuleBlock, isModuleDeclaration, isNewExpression, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/ast";
2
+ import { parenthesesRemoved } from "./unwrap.js";
2
3
  import { aliasChains } from "./alias-chain.js";
3
4
  import { contextualUses } from "./contextual-uses.js";
4
5
  import { destructuring, propertyReadsOf } from "./destructuring.js";
5
6
  import { specifierCandidates } from "./configuration-files.js";
6
7
  import { namespaceUses } from "./namespace-objects.js";
7
- import { globExpression } from "./glob.js";
8
8
  import { declarationsByName, isParameterProperty, nodeKey, } from "./inventory.js";
9
- import { augmentationReferences } from "./augmentations.js";
9
+ import { ambientGlobalInterfaces, augmentationReferences } from "./augmentations.js";
10
10
  import { elementAccessReferences } from "./element-access.js";
11
11
  import { indexedAccessReferences } from "./indexed-access.js";
12
12
  import { keyofComparisonReferences } from "./keyof-comparisons.js";
@@ -16,9 +16,12 @@ import { overrideReferences } from "./overrides.js";
16
16
  import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.js";
17
17
  import { byPosition, isComponentFile, renderPosition } from "./position.js";
18
18
  import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED } from "./query.js";
19
+ import { typeParameterReads } from "./type-parameter-reads.js";
19
20
  import { typeQueryAliases } from "./type-query-alias.js";
20
- import { computedKeyNames, keyNameOf, keyedUses } from "./symbol-keys.js";
21
+ import { computedKeyNames, iterationReferences, keyNameOf, keyedUses, } from "./symbol-keys.js";
21
22
  import { handedOn, valuesOf } from "./value-flow.js";
23
+ import { bytewise } from "./text/compare.js";
24
+ import { globExpression } from "./text/glob.js";
22
25
  /**
23
26
  * The operators whose left side one assignment stores into. A compound assignment is
24
27
  * among them: where its value is discarded it reads its target only to write the result
@@ -76,14 +79,6 @@ function valueDiscarded(node) {
76
79
  function isName(node) {
77
80
  return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PrivateIdentifier;
78
81
  }
79
- /** The expression one node is, through however many parentheses are written around it. */
80
- function unparenthesized(node) {
81
- let held = node;
82
- while (isParenthesizedExpression(held)) {
83
- held = held.expression;
84
- }
85
- return held;
86
- }
87
82
  /**
88
83
  * Records the name one store writes into.
89
84
  *
@@ -93,7 +88,7 @@ function unparenthesized(node) {
93
88
  * target writes each name it binds, through its spreads and nested patterns.
94
89
  */
95
90
  function markWrite(target, writes, accesses) {
96
- const held = unparenthesized(target);
91
+ const held = parenthesesRemoved(target);
97
92
  if (isName(held)) {
98
93
  writes.add(held.pos);
99
94
  return;
@@ -108,7 +103,7 @@ function markWrite(target, writes, accesses) {
108
103
  accesses.add(held);
109
104
  // A store through a member's value lands in storage every holder of the value
110
105
  // reads, so it reads the member; a variable only ever stored into stays written.
111
- if (!isPropertyAccessExpression(unparenthesized(held.expression))) {
106
+ if (!isPropertyAccessExpression(parenthesesRemoved(held.expression))) {
112
107
  markWrite(held.expression, writes, accesses);
113
108
  }
114
109
  return;
@@ -180,7 +175,7 @@ function markStores(node, writes, reads, accesses) {
180
175
  }
181
176
  /** Whether one node is `import.meta.url`. */
182
177
  function isImportMetaURL(node) {
183
- const at = unparenthesized(node);
178
+ const at = parenthesesRemoved(node);
184
179
  return (isPropertyAccessExpression(at) &&
185
180
  isMetaProperty(at.expression) &&
186
181
  at.expression.keywordToken === SyntaxKind.ImportKeyword &&
@@ -189,7 +184,7 @@ function isImportMetaURL(node) {
189
184
  /** The module URL one node builds, where it builds one. */
190
185
  function moduleURLOf(node) {
191
186
  const relative = (argument) => {
192
- const at = argument === undefined ? undefined : unparenthesized(argument);
187
+ const at = argument === undefined ? undefined : parenthesesRemoved(argument);
193
188
  return at !== undefined &&
194
189
  (isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at)) &&
195
190
  /^\.\.?\//u.test(at.text)
@@ -204,7 +199,7 @@ function moduleURLOf(node) {
204
199
  : undefined;
205
200
  }
206
201
  if (isCallExpression(node)) {
207
- const callee = unparenthesized(node.expression);
202
+ const callee = parenthesesRemoved(node.expression);
208
203
  const specifier = relative(node.arguments[0]);
209
204
  if (specifier !== undefined &&
210
205
  isPropertyAccessExpression(callee) &&
@@ -489,7 +484,7 @@ export function testFileRulesOf(testFiles, paths, supportFiles = []) {
489
484
  };
490
485
  }),
491
486
  ...(support === 0 ? [] : [{ rule: TEST_SUPPORT_RULE, matched: support }]),
492
- ].sort((a, b) => compare(a.rule, b.rule));
487
+ ].sort((a, b) => bytewise(a.rule, b.rule));
493
488
  }
494
489
  const importedCache = new WeakMap();
495
490
  /**
@@ -608,20 +603,13 @@ function aloneIn(file, uses, writes, found) {
608
603
  .filter(([, nodes]) => nodes.some((node) => inside(node, write.value)) &&
609
604
  nodes.every((node) => sameTarget.some((one) => inside(node, one.value))))
610
605
  .map(([name]) => name)
611
- .sort(compare);
606
+ .sort(bytewise);
612
607
  if (names.length > 0) {
613
608
  result.push({ index: write.index, names });
614
609
  }
615
610
  }
616
611
  return result;
617
612
  }
618
- /** Two strings ordered bytewise, which is the order every set of a run is read in. */
619
- function compare(a, b) {
620
- if (a === b) {
621
- return 0;
622
- }
623
- return a < b ? -1 : 1;
624
- }
625
613
  /**
626
614
  * Every reference one project's own files make to the project's own declarations, in
627
615
  * position order, and the files the test-file rules classified.
@@ -1134,6 +1122,37 @@ export function references(project, held, targetRoot, options) {
1134
1122
  }
1135
1123
  }
1136
1124
  }
1125
+ /** The ambient global interfaces of the walked files that merge into a declaration outside the project's own files. */
1126
+ const libraryMerged = (scripts) => {
1127
+ const names = new Map();
1128
+ const visit = (node) => {
1129
+ if (isInterfaceDeclaration(node)) {
1130
+ const id = declaredAt(node);
1131
+ if (id !== undefined) {
1132
+ names.set(id, node.name);
1133
+ }
1134
+ }
1135
+ else if (isModuleDeclaration(node) || isModuleBlock(node)) {
1136
+ node.forEachChild(visit);
1137
+ }
1138
+ };
1139
+ for (const { file } of walked) {
1140
+ file.forEachChild(visit);
1141
+ }
1142
+ const global = new Set(ambientGlobalInterfaces(held, [...names.keys()], scripts));
1143
+ const asked = [...names].filter(([id]) => global.has(id));
1144
+ if (asked.length === 0) {
1145
+ return new Set();
1146
+ }
1147
+ const answers = project.symbolsAt(asked.map(([, name]) => project.handle(name)));
1148
+ return new Set(asked.flatMap(([id], at) => {
1149
+ const symbol = answers[at];
1150
+ return typeof symbol === "object" &&
1151
+ symbol.declarations.some((handle) => !ownPaths.has(handle.path))
1152
+ ? [id]
1153
+ : [];
1154
+ }));
1155
+ };
1137
1156
  const usesOf = new Map(walked.map((one) => [one.file, one.uses]));
1138
1157
  for (const [file, writes] of valued) {
1139
1158
  const uses = usesOf.get(file) ?? [];
@@ -1151,21 +1170,23 @@ export function references(project, held, targetRoot, options) {
1151
1170
  found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
1152
1171
  found.push(...elementAccessReferences(project, held, chains, root, new Set(tests)));
1153
1172
  found.push(...keyofComparisonReferences(project, held, chains, root, new Set(tests)));
1173
+ found.push(...iterationReferences(project, held, chains, root, new Set(tests)));
1174
+ found.push(...typeParameterReads(project, held, chains, root, new Set(tests)));
1154
1175
  found.push(...unimplementedCalleeReads(project, held, chains, root, new Set(tests)));
1155
1176
  found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
1156
1177
  typedLookups = (options.requestCount?.() ?? 0) - before;
1157
1178
  }
1158
1179
  const scripts = new Set(walked.filter((one) => one.file.externalModuleIndicator === undefined).map((one) => one.fileId));
1159
- found.push(...augmentationReferences(held, found, scripts));
1180
+ found.push(...augmentationReferences(held, found, scripts, libraryMerged(scripts)));
1160
1181
  found.sort((a, b) => byPosition(a.position, b.position) ||
1161
- compare(a.to, b.to) ||
1162
- compare(a.from, b.from) ||
1163
- compare(a.use, b.use));
1182
+ bytewise(a.to, b.to) ||
1183
+ bytewise(a.from, b.from) ||
1184
+ bytewise(a.use, b.use));
1164
1185
  return {
1165
1186
  references: consumer === undefined
1166
1187
  ? found
1167
1188
  : found.map((reference) => ({ ...reference, consumer: consumer.id })),
1168
- testFilePaths: tests.sort(compare),
1189
+ testFilePaths: tests.sort(bytewise),
1169
1190
  cost: {
1170
1191
  batched,
1171
1192
  fileBatches,
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Holds a report to `contract/report.schema.json` and `contract/finding.schema.json`
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.
5
+ */
6
+ import { REPORT_SCHEMAS } from "./contract/report.generated.js";
7
+ import { schemaRefusals } from "./schema.js";
8
+ const reportSchema = schemaRefusals(REPORT_SCHEMAS, "report.schema.json");
9
+ /**
10
+ * What the report schema refuses in one report document, none for an instance, and, as
11
+ * no schema keyword can state it, a components listing that is not one entry per
12
+ * component a finding or an edge evaluation's finding names.
13
+ */
14
+ export function reportRefusals(document) {
15
+ const found = reportSchema(document);
16
+ return found.length > 0 ? found : listingRefusals(document);
17
+ }
18
+ /** The components listing held to the components the findings name, once each. */
19
+ function listingRefusals(report) {
20
+ if (report.components === undefined) {
21
+ return [];
22
+ }
23
+ const refuse = (pointer, reason) => [
24
+ { pointer, keyword: "components", reason, member: "" },
25
+ ];
26
+ const listed = new Set();
27
+ for (const [i, entry] of report.components.entries()) {
28
+ if (listed.has(entry.id)) {
29
+ return refuse(`/components/${String(i)}`, `the entry lists component ${entry.id} a second time`);
30
+ }
31
+ listed.add(entry.id);
32
+ }
33
+ const named = [
34
+ ...report.findings.map((finding, i) => [`/findings/${String(i)}`, finding.component.id]),
35
+ ...report.edge_evaluations.flatMap((one, i) => one.finding === undefined
36
+ ? []
37
+ : [[`/edge_evaluations/${String(i)}/finding`, one.finding.component.id]]),
38
+ ];
39
+ for (const [pointer, id] of named) {
40
+ if (!listed.has(id)) {
41
+ return refuse(pointer, `the finding names component ${id}, which the components listing lacks`);
42
+ }
43
+ }
44
+ const ids = new Set(named.map(([, id]) => id));
45
+ for (const [i, entry] of report.components.entries()) {
46
+ if (!ids.has(entry.id)) {
47
+ return refuse(`/components/${String(i)}`, `the entry lists component ${entry.id}, which no finding names`);
48
+ }
49
+ }
50
+ return [];
51
+ }
@@ -3,20 +3,26 @@
3
3
  * in the order its schema lists them, built from what one run answered. Every reporter
4
4
  * renders this document, so no two renderings disagree about which findings exist.
5
5
  */
6
- /** The version of the report schema this analyzer writes a report to. */
7
- const SCHEMA_VERSION = "8.2.0";
8
- /** Every report schema version this analyzer reads: the one the Contract it implements admits. */
9
- export const SCHEMA_VERSIONS_ACCEPTED = [SCHEMA_VERSION];
10
- /** The name this analyzer writes every document under and mints every identifier with. */
11
- export const ANALYZER_NAME = "deadset-ts";
12
- /** The language this analyzer claims. */
13
- export const LANGUAGE = "ts";
14
- /** Two strings ordered bytewise. */
15
- function compare(a, b) {
16
- if (a === b) {
17
- return 0;
6
+ import { SCHEMA_VERSION, SCHEMA_VERSIONS } from "./contract/contract.generated.js";
7
+ import { bytewise } from "./text/compare.js";
8
+ import { LANGUAGE, PRODUCT } from "./product.js";
9
+ /**
10
+ * Each component the findings name, once, with the members the first finding naming it
11
+ * carries, in the order the findings are: `findings` paired with their wire forms and
12
+ * sorted, then the edge evaluations' findings.
13
+ */
14
+ function componentsOf(findings) {
15
+ const listed = new Map();
16
+ for (const [written, finding] of findings) {
17
+ const { members } = finding.component;
18
+ if (members !== undefined && !listed.has(written.component.id)) {
19
+ listed.set(written.component.id, {
20
+ id: written.component.id,
21
+ members: members.map(wireSymbol),
22
+ });
23
+ }
18
24
  }
19
- return a < b ? -1 : 1;
25
+ return [...listed.values()];
20
26
  }
21
27
  function wirePosition(position) {
22
28
  return {
@@ -85,9 +91,6 @@ export function wireFinding(finding) {
85
91
  root: finding.component.root,
86
92
  symbol_count: finding.component.symbolCount,
87
93
  deletable_lines: finding.component.deletableLines,
88
- ...(finding.component.members === undefined
89
- ? {}
90
- : { members: finding.component.members.map(wireSymbol) }),
91
94
  },
92
95
  retained_by: finding.retainedBy,
93
96
  configurations: finding.configurations,
@@ -128,19 +131,19 @@ function wireStale(stale) {
128
131
  }
129
132
  /** The canonical key over two findings: path, line, column, code, then the symbol reference. */
130
133
  function canonical(a, b) {
131
- return (compare(a.position.path, b.position.path) ||
134
+ return (bytewise(a.position.path, b.position.path) ||
132
135
  a.position.line - b.position.line ||
133
136
  a.position.column - b.position.column ||
134
- compare(a.code, b.code) ||
135
- compare(a.symbol.ref, b.symbol.ref));
137
+ bytewise(a.code, b.code) ||
138
+ bytewise(a.symbol.ref, b.symbol.ref));
136
139
  }
137
140
  /** The canonical key over two stale suppressions, each component from the record's own member. */
138
141
  function canonicalStale(a, b) {
139
- return (compare(a.position.path, b.position.path) ||
142
+ return (bytewise(a.position.path, b.position.path) ||
140
143
  a.position.line - b.position.line ||
141
144
  a.position.column - b.position.column ||
142
- compare(a.code, b.code) ||
143
- compare(a.symbol, b.symbol));
145
+ bytewise(a.code, b.code) ||
146
+ bytewise(a.symbol, b.symbol));
144
147
  }
145
148
  /** One declared gap as a report carries it, its members in the schema's order. */
146
149
  function wireGap(gap) {
@@ -153,9 +156,9 @@ function wireGap(gap) {
153
156
  }
154
157
  /** Edge evaluations by edge, then side, then their compact encodings. */
155
158
  function byEdge(a, b) {
156
- return (compare(a.edge, b.edge) ||
157
- compare(a.side, b.side) ||
158
- 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)));
159
162
  }
160
163
  /**
161
164
  * The lines a reader deletes by acting on every finding: each distinct component a root
@@ -198,21 +201,25 @@ function totalsOf(findings, result) {
198
201
  }
199
202
  /** Builds the report of one run, every array in the order the Contract fixes for it. */
200
203
  export function buildReport(input) {
201
- const findings = input.result.findings.map(wireFinding).sort(canonical);
204
+ const paired = input.result.findings
205
+ .map((finding) => [wireFinding(finding), finding])
206
+ .sort(([a], [b]) => canonical(a, b));
207
+ const findings = paired.map(([wire]) => wire);
208
+ const evaluated = input.result.edgeEvaluations.flatMap((one) => one.finding === undefined ? [] : [[wireFinding(one.finding), one.finding]]);
202
209
  const unavailable = [...new Map(input.unavailable.map((one) => [one.id, one])).values()]
203
- .sort((a, b) => compare(a.id, b.id))
210
+ .sort((a, b) => bytewise(a.id, b.id))
204
211
  .map((one) => ({ id: one.id, role: "consumer", reason: one.reason }));
205
212
  const loaded = [...input.loaded]
206
- .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))
207
214
  .map((one) => ({ id: one.id, role: "consumer", path: one.path }));
208
215
  return {
209
216
  schema_version: SCHEMA_VERSION,
210
217
  contract_version: input.contractVersion,
211
218
  analyzer: {
212
- name: ANALYZER_NAME,
219
+ name: PRODUCT,
213
220
  version: input.version,
214
221
  languages: [LANGUAGE],
215
- schema_versions_accepted: SCHEMA_VERSIONS_ACCEPTED,
222
+ schema_versions_accepted: SCHEMA_VERSIONS,
216
223
  conformance: {
217
224
  corpus_version: input.conformance.corpusVersion,
218
225
  result: input.conformance.result,
@@ -221,23 +228,26 @@ export function buildReport(input) {
221
228
  },
222
229
  target: { kind: input.target.kind, root: input.target.root, identity: input.target.identity },
223
230
  configurations: [...input.configurations]
224
- .sort((a, b) => compare(a.id, b.id))
231
+ .sort((a, b) => bytewise(a.id, b.id))
225
232
  .map((one) => ({ id: one.id, project: one.project })),
226
233
  configurations_not_built: [...input.notBuilt]
227
- .sort((a, b) => compare(a.id, b.id))
234
+ .sort((a, b) => bytewise(a.id, b.id))
228
235
  .map((one) => ({ id: one.id, project: one.project, error: one.error })),
229
236
  consumers: { declared: loaded.length + unavailable.length, loaded, unavailable },
230
237
  findings,
238
+ ...(input.listsComponents === true
239
+ ? { components: componentsOf([...paired, ...evaluated]) }
240
+ : {}),
231
241
  edge_evaluations: input.result.edgeEvaluations.map(wireEvaluation).sort(byEdge),
232
242
  stale_suppressions: input.result.staleSuppressions.map(wireStale).sort(canonicalStale),
233
243
  // Every gap shares the one component of the canonical key a gap supplies, the analyzer's
234
244
  // name, so the compact encodings alone order them.
235
245
  declared_gaps: input.declaredGaps
236
246
  .map(wireGap)
237
- .sort((a, b) => compare(JSON.stringify(a), JSON.stringify(b))),
247
+ .sort((a, b) => bytewise(JSON.stringify(a), JSON.stringify(b))),
238
248
  excluded_by_cgo: [],
239
249
  test_file_rules: [...input.testFileRules]
240
- .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)
241
251
  .map((one) => ({ rule: one.rule, matched: one.matched })),
242
252
  type_error_skips: [
243
253
  ...new Map(input.typeErrorSkips.map((one) => {
@@ -245,12 +255,14 @@ export function buildReport(input) {
245
255
  return [JSON.stringify(skip), skip];
246
256
  })),
247
257
  ]
248
- .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))
249
259
  .map(([, skip]) => skip),
250
- notes: [],
260
+ notes: [...new Map((input.notes ?? []).map((one) => [JSON.stringify(one), one])).entries()]
261
+ .sort(([a, x], [b, y]) => bytewise(x.kind, y.kind) || bytewise(x.path, y.path) || bytewise(a, b))
262
+ .map(([, note]) => note),
251
263
  unanswered_questions: [...input.unanswered]
252
- .sort((a, b) => compare(a.configuration, b.configuration) ||
253
- compare(JSON.stringify(a), JSON.stringify(b)))
264
+ .sort((a, b) => bytewise(a.configuration, b.configuration) ||
265
+ bytewise(JSON.stringify(a), JSON.stringify(b)))
254
266
  .map((one) => ({
255
267
  configuration: one.configuration,
256
268
  questions: one.questions,
@@ -277,16 +289,22 @@ export function sortFindings(report, by) {
277
289
  return { ...report, findings: [...report.findings].sort(by === "size" ? bySize : canonical) };
278
290
  }
279
291
  /**
280
- * The report holding the first `most` findings of its order, the rest counted as omitted.
281
- * Every other total keeps describing the whole set. Zero keeps every finding.
292
+ * The report holding the first `most` findings of its order, the rest counted as omitted,
293
+ * and listing only the components those findings name. Every other total keeps describing
294
+ * the whole set. Zero keeps every finding.
282
295
  */
283
296
  export function capFindings(report, most) {
284
297
  if (most <= 0 || report.findings.length <= most) {
285
298
  return report;
286
299
  }
300
+ const findings = report.findings.slice(0, most);
301
+ const named = new Set([...findings, ...report.edge_evaluations.flatMap((one) => one.finding ?? [])].map((finding) => finding.component.id));
287
302
  return {
288
303
  ...report,
289
- findings: report.findings.slice(0, most),
304
+ findings,
305
+ ...(report.components === undefined
306
+ ? {}
307
+ : { components: report.components.filter((listed) => named.has(listed.id)) }),
290
308
  totals: { ...report.totals, omitted: report.findings.length - most },
291
309
  };
292
310
  }
@@ -1,4 +1,3 @@
1
- import { fails } from "../config.js";
2
1
  import { coalesced } from "../json-chunks.js";
3
2
  import { KINDS } from "../kinds.js";
4
3
  /** The escapes a workflow command's message needs, so a line break does not end the command. */
@@ -19,6 +18,12 @@ function escapeProperty(value) {
19
18
  function kindName(code) {
20
19
  return KINDS.get(code)?.name ?? code;
21
20
  }
21
+ /** The command each finding severity is annotated with, whatever the failing severity. */
22
+ const LEVELS = {
23
+ deny: "error",
24
+ warn: "warning",
25
+ allow: "notice",
26
+ };
22
27
  function command(level, at, endLine, title, message) {
23
28
  return `::${level} file=${escapeProperty(at.path)},line=${String(at.line)},col=${String(at.column)},endLine=${String(endLine)},title=${escapeProperty(title)}::${escapeData(message)}`;
24
29
  }
@@ -26,16 +31,15 @@ function command(level, at, endLine, title, message) {
26
31
  const SKIP_TITLE = "type error skipped";
27
32
  const SKIP_MESSAGE = "the analysis did not evaluate the function or statement holding this type error: ";
28
33
  /**
29
- * One workflow annotation per finding, then one per stale suppression, each naming its
30
- * file, line, column and last line, titled by its code and kind, then one warning per
31
- * type-error skip naming its file and line. A finding at or above the failing severity is
32
- * an error and one below it a warning; a stale suppression is an error, because its kind
33
- * is fixed on at the failing severity.
34
+ * The annotations `contract/grammar/annotations.md` states: one per finding, at the
35
+ * command its severity names, and one error per stale suppression, each naming its file,
36
+ * line, column and last line and titled by its code and kind; then one warning per
37
+ * type-error skip naming its file and line, and one per note naming its directory.
34
38
  */
35
- export function annotations(report, options) {
39
+ export function annotations(report) {
36
40
  return coalesced((function* lines() {
37
41
  for (const found of report.findings) {
38
- yield `${command(fails(found.severity, options.failOn) ? "error" : "warning", found.position, found.position.end_line, `${found.code} ${found.kind}`, found.message)}\n`;
42
+ yield `${command(LEVELS[found.severity], found.position, found.position.end_line, `${found.code} ${found.kind}`, found.message)}\n`;
39
43
  }
40
44
  for (const stale of report.stale_suppressions) {
41
45
  yield `${command("error", stale.position, stale.position.line, `${stale.code} ${kindName(stale.code)}`, stale.message)}\n`;
@@ -43,5 +47,8 @@ export function annotations(report, options) {
43
47
  for (const skip of report.type_error_skips) {
44
48
  yield `::warning file=${escapeProperty(skip.path)},line=${String(skip.line)},title=${escapeProperty(SKIP_TITLE)}::${escapeData(SKIP_MESSAGE + skip.message)}\n`;
45
49
  }
50
+ for (const note of report.notes) {
51
+ yield `::warning file=${escapeProperty(note.path)},title=${escapeProperty(note.kind)}::${escapeData(note.message)}\n`;
52
+ }
46
53
  })());
47
54
  }
@@ -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);
@@ -114,7 +116,7 @@ function lineHashCache(read) {
114
116
  };
115
117
  }
116
118
  /** Every position a finding names beyond its own, in the mapping's order, the first hundred. */
117
- function relatedOf(found) {
119
+ function relatedOf(found, members) {
118
120
  const related = [];
119
121
  for (const one of found.details.implementations ?? []) {
120
122
  related.push({ label: "implementation", at: one.position, endLine: one.position.end_line });
@@ -126,7 +128,7 @@ function relatedOf(found) {
126
128
  const at = found.details.name_literal;
127
129
  related.push({ label: "name", at, endLine: at.end_line });
128
130
  }
129
- for (const one of found.component.members ?? []) {
131
+ for (const one of members(found.component.id)) {
130
132
  if (declarationKey(one.ref, one.position.path) !==
131
133
  declarationKey(found.symbol.ref, found.position.path)) {
132
134
  related.push({ label: "member", at: one.position, endLine: one.position.end_line });
@@ -143,7 +145,9 @@ function messageWithLinks(message, related) {
143
145
  return `${message} (see ${links.join(", ")})`;
144
146
  }
145
147
  /** Every member of the finding the mapping places nowhere else, under its schema name. */
146
- function propertiesOf(found) {
148
+ /** A result's member bag, its component holding the members the report lists for it once. */
149
+ function propertiesOf(found, members) {
150
+ const listed = members(found.component.id);
147
151
  return {
148
152
  language: found.language,
149
153
  symbol: found.symbol,
@@ -154,7 +158,7 @@ function propertiesOf(found) {
154
158
  : { liveness_relation: found.liveness_relation }),
155
159
  test_only: found.test_only,
156
160
  generated: found.generated,
157
- component: found.component,
161
+ component: listed.length === 0 ? found.component : { ...found.component, members: listed },
158
162
  retained_by: found.retained_by,
159
163
  configurations: found.configurations,
160
164
  consumers_loaded: found.consumers_loaded,
@@ -169,8 +173,8 @@ function ruleIndex(index, code) {
169
173
  }
170
174
  return at;
171
175
  }
172
- function findingResult(found, index, hashOf) {
173
- const related = relatedOf(found);
176
+ function findingResult(found, index, hashOf, members) {
177
+ const related = relatedOf(found, members);
174
178
  return {
175
179
  ruleId: found.code,
176
180
  ruleIndex: ruleIndex(index, found.code),
@@ -190,7 +194,7 @@ function findingResult(found, index, hashOf) {
190
194
  primaryLocationLineHash: hashOf(found.position),
191
195
  "deadsetSymbolRef/v1": symbolFingerprint(found.code, found.symbol.ref),
192
196
  },
193
- properties: propertiesOf(found),
197
+ properties: propertiesOf(found, members),
194
198
  };
195
199
  }
196
200
  /** A stale suppression at the failing level, at its own site; it is no finding, so it carries no member bag. */
@@ -218,11 +222,13 @@ export function sarif(report, options) {
218
222
  const ruleList = rules(kinds);
219
223
  const index = new Map(kinds.map((kind, at) => [kind.code, at]));
220
224
  const hashOf = lineHashCache(options.readSource);
225
+ const listed = new Map((report.components ?? []).map((one) => [one.id, one.members]));
226
+ const members = (id) => listed.get(id) ?? [];
221
227
  const results = [
222
- ...report.findings.map((found) => findingResult(found, index, hashOf)),
228
+ ...report.findings.map((found) => findingResult(found, index, hashOf, members)),
223
229
  ...report.stale_suppressions.map((stale) => staleResult(stale, index, hashOf)),
224
230
  ];
225
- const languages = [...report.analyzer.languages].sort().join("+");
231
+ const languages = [...report.analyzer.languages].sort(bytewise).join("+");
226
232
  const withheld = withheldLine(report.totals);
227
233
  const log = {
228
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 {