@cplieger/deadset-ts 8.0.0-dev.6 → 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 (158) 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 +25 -43
  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 +2 -1
  9. package/dist/src/configuration-files.js +27 -168
  10. package/dist/src/contract/contract.generated.js +13 -0
  11. package/dist/src/conventions.js +20 -60
  12. package/dist/src/dependencies.js +25 -43
  13. package/dist/src/discover.js +13 -39
  14. package/dist/src/edges.js +7 -15
  15. package/dist/src/element-access.js +4 -6
  16. package/dist/src/entry-point-gaps.js +2 -8
  17. package/dist/src/entry-points.js +3 -10
  18. package/dist/src/enum-group.js +3 -10
  19. package/dist/src/erased-stores.js +4 -6
  20. package/dist/src/exempt-classes.js +2 -4
  21. package/dist/src/exempt.js +6 -22
  22. package/dist/src/file-facts.js +2 -25
  23. package/dist/src/findings/reads-and-writes.js +2 -1
  24. package/dist/src/findings/self-check.js +3 -2
  25. package/dist/src/findings-pass.js +6 -11
  26. package/dist/src/fs/manifest.js +43 -0
  27. package/dist/src/fs/tree.js +88 -0
  28. package/dist/src/git-ignore.js +34 -25
  29. package/dist/src/html-entries.js +6 -33
  30. package/dist/src/in-operands.js +2 -1
  31. package/dist/src/intra-function-parts.js +10 -13
  32. package/dist/src/inventory.js +2 -21
  33. package/dist/src/json-document.js +4 -0
  34. package/dist/src/keyof-comparisons.js +4 -6
  35. package/dist/src/manifest.js +5 -43
  36. package/dist/src/mdx-entries.js +3 -26
  37. package/dist/src/partial.js +2 -1
  38. package/dist/src/paths.js +26 -15
  39. package/dist/src/position.js +2 -1
  40. package/dist/src/print.js +3 -2
  41. package/dist/src/product.js +13 -0
  42. package/dist/src/references.js +14 -27
  43. package/dist/src/report-schema.js +15 -16
  44. package/dist/src/report.js +22 -31
  45. package/dist/src/reporters/sarif.js +4 -2
  46. package/dist/src/reporters/template.js +4 -19
  47. package/dist/src/resolve.js +11 -20
  48. package/dist/src/roots.js +7 -17
  49. package/dist/src/run.js +8 -7
  50. package/dist/src/schema.js +4 -3
  51. package/dist/src/scope.js +4 -5
  52. package/dist/src/serialization-contract.js +4 -8
  53. package/dist/src/session.js +2 -1
  54. package/dist/src/suppress-file.js +8 -10
  55. package/dist/src/symbol-keys.js +3 -5
  56. package/dist/src/template-field.js +5 -15
  57. package/dist/src/test-globals.js +6 -28
  58. package/dist/src/test-joins.js +6 -6
  59. package/dist/src/text/compare.js +26 -0
  60. package/dist/src/text/glob.js +194 -0
  61. package/dist/src/text/shell.js +159 -0
  62. package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
  63. package/dist/src/type-errors.js +14 -38
  64. package/dist/src/unseen-loads.js +2 -9
  65. package/dist/src/unwrap.js +23 -0
  66. package/dist/src/verbs/analyze.js +15 -13
  67. package/dist/src/verbs/describe.js +4 -4
  68. package/dist/src/verbs/explain.js +5 -3
  69. package/dist/src/verbs/print-projects.js +2 -1
  70. package/dist/src/verbs/print-retained.js +2 -1
  71. package/dist/src/verbs/print-roots.js +2 -1
  72. package/dist/src/verbs/version.js +2 -1
  73. package/dist/src/workflow-steps.js +9 -46
  74. package/dist/src/workspace-manifests.js +2 -2
  75. package/dist/src/workspace-programs.js +5 -3
  76. package/dist/src/workspace-resolution.js +13 -17
  77. package/dist/src/workspace.js +28 -116
  78. package/jsr.json +1 -1
  79. package/package.json +1 -1
  80. package/src/analysis.ts +26 -51
  81. package/src/automatic-types.ts +11 -29
  82. package/src/build-scripts.ts +17 -38
  83. package/src/component-layer.ts +4 -2
  84. package/src/components.ts +2 -4
  85. package/src/config.ts +4 -2
  86. package/src/configuration-files.ts +27 -170
  87. package/src/contract/contract.generated.ts +25 -0
  88. package/src/conventions.ts +26 -74
  89. package/src/dependencies.ts +32 -50
  90. package/src/discover.ts +14 -39
  91. package/src/edges.ts +7 -21
  92. package/src/element-access.ts +4 -7
  93. package/src/entry-point-gaps.ts +3 -9
  94. package/src/entry-points.ts +2 -13
  95. package/src/enum-group.ts +2 -11
  96. package/src/erased-stores.ts +3 -7
  97. package/src/exempt-classes.ts +4 -4
  98. package/src/exempt.ts +5 -27
  99. package/src/file-facts.ts +6 -24
  100. package/src/findings/reads-and-writes.ts +2 -1
  101. package/src/findings/self-check.ts +3 -2
  102. package/src/findings-pass.ts +6 -12
  103. package/src/fs/manifest.ts +52 -0
  104. package/src/fs/tree.ts +110 -0
  105. package/src/git-ignore.ts +38 -25
  106. package/src/html-entries.ts +6 -33
  107. package/src/in-operands.ts +2 -1
  108. package/src/intra-function-parts.ts +10 -14
  109. package/src/inventory.ts +2 -20
  110. package/src/json-document.ts +5 -0
  111. package/src/keyof-comparisons.ts +3 -7
  112. package/src/manifest.ts +5 -46
  113. package/src/mdx-entries.ts +3 -26
  114. package/src/partial.ts +2 -1
  115. package/src/paths.ts +27 -15
  116. package/src/position.ts +2 -1
  117. package/src/print.ts +3 -2
  118. package/src/product.ts +18 -0
  119. package/src/references.ts +14 -29
  120. package/src/report-schema.ts +15 -17
  121. package/src/report.ts +24 -34
  122. package/src/reporters/sarif.ts +4 -4
  123. package/src/reporters/template.ts +4 -20
  124. package/src/resolve.ts +20 -27
  125. package/src/roots.ts +7 -20
  126. package/src/run.ts +8 -13
  127. package/src/schema.ts +4 -3
  128. package/src/scope.ts +4 -10
  129. package/src/serialization-contract.ts +4 -9
  130. package/src/session.ts +2 -1
  131. package/src/suppress-file.ts +8 -11
  132. package/src/symbol-keys.ts +2 -5
  133. package/src/template-field.ts +5 -18
  134. package/src/test-globals.ts +9 -27
  135. package/src/test-joins.ts +6 -8
  136. package/src/text/compare.ts +27 -0
  137. package/src/text/glob.ts +210 -0
  138. package/src/text/shell.ts +161 -0
  139. package/src/{yaml-document.ts → text/yaml.ts} +46 -0
  140. package/src/type-errors.ts +25 -36
  141. package/src/unseen-loads.ts +1 -12
  142. package/src/unwrap.ts +34 -0
  143. package/src/verbs/analyze.ts +22 -21
  144. package/src/verbs/describe.ts +4 -4
  145. package/src/verbs/explain.ts +5 -3
  146. package/src/verbs/print-projects.ts +2 -1
  147. package/src/verbs/print-retained.ts +2 -1
  148. package/src/verbs/print-roots.ts +2 -1
  149. package/src/verbs/version.ts +2 -1
  150. package/src/workflow-steps.ts +9 -44
  151. package/src/workspace-manifests.ts +2 -2
  152. package/src/workspace-programs.ts +5 -3
  153. package/src/workspace-resolution.ts +21 -17
  154. package/src/workspace.ts +35 -121
  155. package/dist/src/glob.js +0 -65
  156. package/dist/src/pnpm-workspace.js +0 -120
  157. package/src/glob.ts +0 -67
  158. package/src/pnpm-workspace.ts +0 -135
@@ -1,10 +1,10 @@
1
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
9
  import { ambientGlobalInterfaces, augmentationReferences } from "./augmentations.js";
10
10
  import { elementAccessReferences } from "./element-access.js";
@@ -20,6 +20,8 @@ import { typeParameterReads } from "./type-parameter-reads.js";
20
20
  import { typeQueryAliases } from "./type-query-alias.js";
21
21
  import { computedKeyNames, iterationReferences, keyNameOf, keyedUses, } from "./symbol-keys.js";
22
22
  import { handedOn, valuesOf } from "./value-flow.js";
23
+ import { bytewise } from "./text/compare.js";
24
+ import { globExpression } from "./text/glob.js";
23
25
  /**
24
26
  * The operators whose left side one assignment stores into. A compound assignment is
25
27
  * among them: where its value is discarded it reads its target only to write the result
@@ -77,14 +79,6 @@ function valueDiscarded(node) {
77
79
  function isName(node) {
78
80
  return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PrivateIdentifier;
79
81
  }
80
- /** The expression one node is, through however many parentheses are written around it. */
81
- function unparenthesized(node) {
82
- let held = node;
83
- while (isParenthesizedExpression(held)) {
84
- held = held.expression;
85
- }
86
- return held;
87
- }
88
82
  /**
89
83
  * Records the name one store writes into.
90
84
  *
@@ -94,7 +88,7 @@ function unparenthesized(node) {
94
88
  * target writes each name it binds, through its spreads and nested patterns.
95
89
  */
96
90
  function markWrite(target, writes, accesses) {
97
- const held = unparenthesized(target);
91
+ const held = parenthesesRemoved(target);
98
92
  if (isName(held)) {
99
93
  writes.add(held.pos);
100
94
  return;
@@ -109,7 +103,7 @@ function markWrite(target, writes, accesses) {
109
103
  accesses.add(held);
110
104
  // A store through a member's value lands in storage every holder of the value
111
105
  // reads, so it reads the member; a variable only ever stored into stays written.
112
- if (!isPropertyAccessExpression(unparenthesized(held.expression))) {
106
+ if (!isPropertyAccessExpression(parenthesesRemoved(held.expression))) {
113
107
  markWrite(held.expression, writes, accesses);
114
108
  }
115
109
  return;
@@ -181,7 +175,7 @@ function markStores(node, writes, reads, accesses) {
181
175
  }
182
176
  /** Whether one node is `import.meta.url`. */
183
177
  function isImportMetaURL(node) {
184
- const at = unparenthesized(node);
178
+ const at = parenthesesRemoved(node);
185
179
  return (isPropertyAccessExpression(at) &&
186
180
  isMetaProperty(at.expression) &&
187
181
  at.expression.keywordToken === SyntaxKind.ImportKeyword &&
@@ -190,7 +184,7 @@ function isImportMetaURL(node) {
190
184
  /** The module URL one node builds, where it builds one. */
191
185
  function moduleURLOf(node) {
192
186
  const relative = (argument) => {
193
- const at = argument === undefined ? undefined : unparenthesized(argument);
187
+ const at = argument === undefined ? undefined : parenthesesRemoved(argument);
194
188
  return at !== undefined &&
195
189
  (isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at)) &&
196
190
  /^\.\.?\//u.test(at.text)
@@ -205,7 +199,7 @@ function moduleURLOf(node) {
205
199
  : undefined;
206
200
  }
207
201
  if (isCallExpression(node)) {
208
- const callee = unparenthesized(node.expression);
202
+ const callee = parenthesesRemoved(node.expression);
209
203
  const specifier = relative(node.arguments[0]);
210
204
  if (specifier !== undefined &&
211
205
  isPropertyAccessExpression(callee) &&
@@ -490,7 +484,7 @@ export function testFileRulesOf(testFiles, paths, supportFiles = []) {
490
484
  };
491
485
  }),
492
486
  ...(support === 0 ? [] : [{ rule: TEST_SUPPORT_RULE, matched: support }]),
493
- ].sort((a, b) => compare(a.rule, b.rule));
487
+ ].sort((a, b) => bytewise(a.rule, b.rule));
494
488
  }
495
489
  const importedCache = new WeakMap();
496
490
  /**
@@ -609,20 +603,13 @@ function aloneIn(file, uses, writes, found) {
609
603
  .filter(([, nodes]) => nodes.some((node) => inside(node, write.value)) &&
610
604
  nodes.every((node) => sameTarget.some((one) => inside(node, one.value))))
611
605
  .map(([name]) => name)
612
- .sort(compare);
606
+ .sort(bytewise);
613
607
  if (names.length > 0) {
614
608
  result.push({ index: write.index, names });
615
609
  }
616
610
  }
617
611
  return result;
618
612
  }
619
- /** Two strings ordered bytewise, which is the order every set of a run is read in. */
620
- function compare(a, b) {
621
- if (a === b) {
622
- return 0;
623
- }
624
- return a < b ? -1 : 1;
625
- }
626
613
  /**
627
614
  * Every reference one project's own files make to the project's own declarations, in
628
615
  * position order, and the files the test-file rules classified.
@@ -1192,14 +1179,14 @@ export function references(project, held, targetRoot, options) {
1192
1179
  const scripts = new Set(walked.filter((one) => one.file.externalModuleIndicator === undefined).map((one) => one.fileId));
1193
1180
  found.push(...augmentationReferences(held, found, scripts, libraryMerged(scripts)));
1194
1181
  found.sort((a, b) => byPosition(a.position, b.position) ||
1195
- compare(a.to, b.to) ||
1196
- compare(a.from, b.from) ||
1197
- compare(a.use, b.use));
1182
+ bytewise(a.to, b.to) ||
1183
+ bytewise(a.from, b.from) ||
1184
+ bytewise(a.use, b.use));
1198
1185
  return {
1199
1186
  references: consumer === undefined
1200
1187
  ? found
1201
1188
  : found.map((reference) => ({ ...reference, consumer: consumer.id })),
1202
- testFilePaths: tests.sort(compare),
1189
+ testFilePaths: tests.sort(bytewise),
1203
1190
  cost: {
1204
1191
  batched,
1205
1192
  fileBatches,
@@ -6,6 +6,8 @@
6
6
  * ECMAScript.
7
7
  */
8
8
  import { REPORT_SCHEMAS } from "./contract/report.generated.js";
9
+ import { bytewise } from "./text/compare.js";
10
+ import { isRecord } from "./json-document.js";
9
11
  /**
10
12
  * Every keyword the validator evaluates or knows to carry no assertion. A schema naming
11
13
  * another fails every check rather than passing values the keyword would refuse.
@@ -26,9 +28,6 @@ function pattern(source) {
26
28
  }
27
29
  return held;
28
30
  }
29
- function isObject(value) {
30
- return value !== null && typeof value === "object" && !Array.isArray(value);
31
- }
32
31
  function typeOf(value) {
33
32
  if (value === null) {
34
33
  return "null";
@@ -50,9 +49,9 @@ function canonical(value) {
50
49
  if (Array.isArray(value)) {
51
50
  return `[${value.map(canonical).join(",")}]`;
52
51
  }
53
- if (isObject(value)) {
52
+ if (isRecord(value)) {
54
53
  return `{${Object.keys(value)
55
- .sort()
54
+ .sort(bytewise)
56
55
  .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
57
56
  .join(",")}}`;
58
57
  }
@@ -78,11 +77,11 @@ class Checker {
78
77
  const target = file === "" ? doc : file;
79
78
  let schema = this.documents[target];
80
79
  for (const token of pointer.split("/").filter((one) => one !== "")) {
81
- schema = isObject(schema)
80
+ schema = isRecord(schema)
82
81
  ? schema[token.replaceAll("~1", "/").replaceAll("~0", "~")]
83
82
  : undefined;
84
83
  }
85
- if (!isObject(schema)) {
84
+ if (!isRecord(schema)) {
86
85
  throw new Error(`report schema: ${ref} names no schema`);
87
86
  }
88
87
  return [target, schema];
@@ -115,7 +114,7 @@ class Checker {
115
114
  if (Array.isArray(value)) {
116
115
  this.checkArray(doc, schema, value, at, found, refuse);
117
116
  }
118
- else if (isObject(value)) {
117
+ else if (isRecord(value)) {
119
118
  this.checkObject(doc, schema, value, at, found, refuse);
120
119
  }
121
120
  this.checkApplicators(doc, schema, value, at, found, refuse);
@@ -146,18 +145,18 @@ class Checker {
146
145
  }
147
146
  }
148
147
  checkObject(doc, schema, value, at, found, refuse) {
149
- const properties = isObject(schema["properties"]) ? schema["properties"] : {};
148
+ const properties = isRecord(schema["properties"]) ? schema["properties"] : {};
150
149
  const additional = schema["additionalProperties"];
151
- for (const name of Object.keys(value).sort()) {
150
+ for (const name of Object.keys(value).sort(bytewise)) {
152
151
  const member = `${at}/${escape(name)}`;
153
152
  const declared = properties[name];
154
- if (isObject(declared)) {
153
+ if (isRecord(declared)) {
155
154
  this.check(doc, declared, value[name], member, found);
156
155
  }
157
156
  else if (additional === false) {
158
157
  refuse("additionalProperties", `the member ${JSON.stringify(name)} is not one the schema declares`);
159
158
  }
160
- else if (isObject(additional)) {
159
+ else if (isRecord(additional)) {
161
160
  this.check(doc, additional, value[name], member, found);
162
161
  }
163
162
  }
@@ -173,7 +172,7 @@ class Checker {
173
172
  }
174
173
  checkArray(doc, schema, value, at, found, refuse) {
175
174
  const items = schema["items"];
176
- if (isObject(items)) {
175
+ if (isRecord(items)) {
177
176
  value.forEach((item, i) => {
178
177
  this.check(doc, items, item, `${at}/${String(i)}`, found);
179
178
  });
@@ -200,12 +199,12 @@ class Checker {
200
199
  if (schema["oneOf"] !== undefined && matching("oneOf") !== 1) {
201
200
  refuse("oneOf", `the value matches ${String(matching("oneOf"))} of the alternatives, want exactly one`);
202
201
  }
203
- if (isObject(schema["not"]) && this.matches(doc, schema["not"], value, at)) {
202
+ if (isRecord(schema["not"]) && this.matches(doc, schema["not"], value, at)) {
204
203
  refuse("not", "the value matches a schema it must not match");
205
204
  }
206
- if (isObject(schema["if"])) {
205
+ if (isRecord(schema["if"])) {
207
206
  const branch = this.matches(doc, schema["if"], value, at) ? schema["then"] : schema["else"];
208
- if (isObject(branch)) {
207
+ if (isRecord(branch)) {
209
208
  this.check(doc, branch, value, at, found);
210
209
  }
211
210
  }
@@ -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 {
@@ -1,12 +1,15 @@
1
1
  import { SEVERITIES, LANGUAGES, CONFIDENCES, FORMATS, FAMILY_KEY_LENGTH, } from "./contract/contract.generated.js";
2
2
  import { ConfigError, defaultConfig, } from "./config.js";
3
3
  import { CONVENTION_ROWS } from "./convention-rows.js";
4
+ import { EXEMPTION_CLASS_NAMES } from "./exempt-classes.js";
4
5
  import { FIXED_SEVERITY_CODES, LIVE_CODES } from "./kinds.js";
5
6
  import { checkDocument, malformed, nestFlags, SCHEMA_ROOT, SEVERITY_SECTION, } from "./schema.js";
7
+ import { bytewise } from "./text/compare.js";
8
+ import { isRecord } from "./json-document.js";
9
+ import { message } from "./product.js";
6
10
  /** Names the command-line flags in a refusal, where a document is named by its path. */
7
11
  const FLAGS_LABEL = "the command-line flags";
8
12
  const SEVERITY_KEY = /^DS[0-9]{2}([0-9]{2})?$/u;
9
- const EXEMPTION_CLASS = /^[a-z][a-z0-9-]*$/u;
10
13
  const CONTRACT_VERSION_PATTERN = /^[0-9]+\.[0-9]+\.[0-9]+$/u;
11
14
  const ANALYZER_NAME = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/u;
12
15
  const PROVIDER_SOURCE = /^(go|npm):[^ \t\r\n]+$/u;
@@ -43,9 +46,6 @@ function originOf(source, path) {
43
46
  }
44
47
  return { source: source.kind, label: source.label };
45
48
  }
46
- function isRecord(value) {
47
- return typeof value === "object" && value !== null && !Array.isArray(value);
48
- }
49
49
  function member(parent, name) {
50
50
  if (parent === undefined || !Object.hasOwn(parent, name)) {
51
51
  return undefined;
@@ -292,7 +292,7 @@ function unimplementedSeverityKey(label, path, reason) {
292
292
  * them all rather than the first one found.
293
293
  */
294
294
  function fixedByContract(code) {
295
- return FIXED_SEVERITY_CODES.filter((candidate) => code === candidate || (code.length === FAMILY_KEY_LENGTH && candidate.startsWith(code))).sort();
295
+ return FIXED_SEVERITY_CODES.filter((candidate) => code === candidate || (code.length === FAMILY_KEY_LENGTH && candidate.startsWith(code))).sort(bytewise);
296
296
  }
297
297
  /** The codes a refusal names: one on its own, and several with the last joined by and. */
298
298
  function spellCodes(codes) {
@@ -321,7 +321,7 @@ function readSeverity(document, label) {
321
321
  return undefined;
322
322
  }
323
323
  const severity = new Map();
324
- for (const code of Object.keys(object).sort()) {
324
+ for (const code of Object.keys(object).sort(bytewise)) {
325
325
  const path = `${SEVERITY_SECTION}.${code}`;
326
326
  if (!SEVERITY_KEY.test(code)) {
327
327
  throw unimplementedSeverityKey(label, path, "a severity key is one issue-kind code or one two-digit family prefix");
@@ -342,19 +342,10 @@ function readSeverity(document, label) {
342
342
  }
343
343
  return severity;
344
344
  }
345
- /** The exemption classes named for switching off. */
345
+ /** The exemption classes named for switching off, each one the vocabulary holds. */
346
346
  function readExemptions(document, label) {
347
347
  const exemptions = section(document, "exemptions", "exemptions", label);
348
- const disabled = readStrings(exemptions, "disabled", "exemptions.disabled", label, 0);
349
- if (disabled === undefined) {
350
- return undefined;
351
- }
352
- for (const entry of disabled) {
353
- if (!EXEMPTION_CLASS.test(entry)) {
354
- throw malformed(label, "exemptions.disabled", `${JSON.stringify(entry)} is not an exemption class name`);
355
- }
356
- }
357
- return disabled;
348
+ return readStrings(exemptions, "disabled", "exemptions.disabled", label, 0, EXEMPTION_CLASS_NAMES);
358
349
  }
359
350
  /** The members that name a declaration, each belonging to exactly one shape. */
360
351
  const SHAPE_MEMBERS = ["symbol", "module", "name", "global"];
@@ -572,7 +563,7 @@ function parseDocument(text, node, label) {
572
563
  value = JSON.parse(text);
573
564
  }
574
565
  catch (error) {
575
- throw malformed(label, "", error instanceof Error ? error.message : String(error));
566
+ throw malformed(label, "", message(error));
576
567
  }
577
568
  checkDocument(text, node, label);
578
569
  return value;
@@ -656,7 +647,7 @@ function settleSeverity(provenance, sources) {
656
647
  }
657
648
  }
658
649
  const severity = new Map();
659
- for (const code of [...codes].sort()) {
650
+ for (const code of [...codes].sort(bytewise)) {
660
651
  const path = `${SEVERITY_SECTION}.${code}`;
661
652
  for (const source of sources) {
662
653
  const value = source.doc.severity?.get(code);
@@ -692,7 +683,7 @@ function describeSource(named, text, label) {
692
683
  * print a provenance value the closed key list refuses to read back.
693
684
  */
694
685
  function checkLabels(provenance) {
695
- for (const path of [...provenance.keys()].sort()) {
686
+ for (const path of [...provenance.keys()].sort(bytewise)) {
696
687
  const origin = provenance.get(path);
697
688
  if (origin !== undefined && origin.source !== "default" && origin.label === "") {
698
689
  throw malformed(origin.source === "flag" ? FLAGS_LABEL : origin.source, path, `came from ${origin.source}, which names no file or flag`);
package/dist/src/roots.js CHANGED
@@ -13,13 +13,15 @@ import { isGlobalBlock } from "./augmentations.js";
13
13
  import { filesNamedBy, outsideChain, relativeSpecifiers, } from "./configuration-files.js";
14
14
  import { conventionEntries } from "./conventions.js";
15
15
  import { entryPoints } from "./entry-points.js";
16
- import { globExpression } from "./glob.js";
17
16
  import { nodeKey } from "./inventory.js";
18
- import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
17
+ import { ancestors, dirnamePath, relativePath, resolvePath } from "./paths.js";
19
18
  import { byPosition } from "./position.js";
20
19
  import { UNANSWERED } from "./query.js";
21
20
  import { sourceFilesOf } from "./source-files.js";
22
21
  import { typeQueryAliases } from "./type-query-alias.js";
22
+ import { bytewise } from "./text/compare.js";
23
+ import { holdsManifest } from "./fs/manifest.js";
24
+ import { globExpression } from "./text/glob.js";
23
25
  /** Whether one declaration is written below the directory of a published workspace member. */
24
26
  function inPublishedMember(held, id, root, members) {
25
27
  const path = held.symbols.find((symbol) => symbol.id === id)?.position.path;
@@ -47,13 +49,8 @@ const OUTPUT_EXTENSION = /\.(?:d\.ts|d\.mts|d\.cts|js|mjs|cjs|jsx)$/u;
47
49
  const SOURCE_EXTENSION = /\.(?:d\.)?[cm]?[jt]sx?$/u;
48
50
  /** Whether no `package.json` sits between one file and the manifest's directory, which holds it. */
49
51
  function ofPackage(host, dir, file) {
50
- for (let at = dirnamePath(file); at !== dir; at = dirnamePath(at)) {
51
- if (relativePath(dir, at) === undefined ||
52
- host.kindOf(joinPath(at, "package.json")) === "file") {
53
- return false;
54
- }
55
- }
56
- return true;
52
+ const between = ancestors(dirnamePath(file), dir);
53
+ return between.length > 0 && !between.slice(0, -1).some((at) => holdsManifest(host, at));
57
54
  }
58
55
  /**
59
56
  * The rooted files one manifest target reads back to by name: its path below the
@@ -163,13 +160,6 @@ export function matchRef(pattern, ref) {
163
160
  }
164
161
  return matchGlob(Array.from(pattern), Array.from(ref));
165
162
  }
166
- /** Two strings ordered bytewise, which is the order every set of a run is read in. */
167
- function compare(a, b) {
168
- if (a === b) {
169
- return 0;
170
- }
171
- return a < b ? -1 : 1;
172
- }
173
163
  /** The members of one declaration, indexed by the container that holds them. */
174
164
  function childrenOf(held) {
175
165
  const byParent = new Map();
@@ -344,7 +334,7 @@ export function roots(project, held, targetRoot, options) {
344
334
  const left = sites.get(a.id);
345
335
  const right = sites.get(b.id);
346
336
  const site = left === undefined || right === undefined ? 0 : byPosition(left, right);
347
- return site || compare(a.kind, b.kind) || compare(a.source, b.source);
337
+ return site || bytewise(a.kind, b.kind) || bytewise(a.source, b.source);
348
338
  });
349
339
  return {
350
340
  liveUnderReachability: ordered,
package/dist/src/run.js CHANGED
@@ -14,6 +14,7 @@ import { describeVerb } from "./verbs/describe.js";
14
14
  import { EXPLAIN_OPTIONS, explainVerb } from "./verbs/explain.js";
15
15
  import {} from "./verbs/verb.js";
16
16
  import { versionVerb } from "./verbs/version.js";
17
+ import { diagnose, message, PRODUCT } from "./product.js";
17
18
  /** Every verb this command answers, in the order the usage text lists them. */
18
19
  const VERBS = [
19
20
  { name: "analyze", run: analyzeVerb, options: ANALYZE_OPTIONS },
@@ -25,7 +26,7 @@ const VERBS = [
25
26
  { name: "describe", run: describeVerb },
26
27
  { name: "version", run: versionVerb },
27
28
  ];
28
- const USAGE = `usage: deadset-ts <verb> [options]\nverbs: ${VERBS.map((verb) => verb.name).join(", ")}\n`;
29
+ const USAGE = `usage: ${PRODUCT} <verb> [options]\nverbs: ${VERBS.map((verb) => verb.name).join(", ")}\n`;
29
30
  /**
30
31
  * The tokens whose presence in an option's name makes that option a request to
31
32
  * edit a source file. The set is closed, and a token is matched in any position of
@@ -109,7 +110,7 @@ function readFileOr(host, path, orUndefined) {
109
110
  if (orUndefined) {
110
111
  return undefined;
111
112
  }
112
- throw new ConfigError("malformed", "", `${path}: ${error instanceof Error ? error.message : String(error)}`);
113
+ throw new ConfigError("malformed", "", `${path}: ${message(error)}`);
113
114
  }
114
115
  }
115
116
  /**
@@ -162,7 +163,7 @@ function scopeOf(host, options) {
162
163
  export function run(args, out, err, host, openClient = openEngine) {
163
164
  const editing = sourceEditOption(args);
164
165
  if (editing !== undefined) {
165
- err.write(`deadset-ts: ${editing} is not supported: deadset-ts reports and never edits a source file\n`);
166
+ diagnose(err, `${editing} is not supported: ${PRODUCT} reports and never edits a source file`);
166
167
  err.write(USAGE);
167
168
  return EXIT_USAGE;
168
169
  }
@@ -172,7 +173,7 @@ export function run(args, out, err, host, openClient = openEngine) {
172
173
  return invoke(verb.run, verb.options ?? NO_OPTIONS, args.slice(1), { out, err, host }, openClient);
173
174
  }
174
175
  if (name !== undefined) {
175
- err.write(`deadset-ts: unknown verb ${JSON.stringify(name)}\n`);
176
+ diagnose(err, `unknown verb ${JSON.stringify(name)}`);
176
177
  }
177
178
  err.write(USAGE);
178
179
  return EXIT_USAGE;
@@ -211,7 +212,7 @@ function invoke(verb, own, args, streams, openClient) {
211
212
  /** Turns one refusal into the exit code the Contract's table gives it. */
212
213
  function refuse(error, err) {
213
214
  if (error instanceof ConfigError) {
214
- err.write(`deadset-ts: ${error.message}\n`);
215
+ diagnose(err, error.message);
215
216
  err.write(USAGE);
216
217
  return EXIT_USAGE;
217
218
  }
@@ -219,7 +220,7 @@ function refuse(error, err) {
219
220
  for (const diagnostic of error.diagnostics) {
220
221
  err.write(`${renderDiagnostic(diagnostic)}\n`);
221
222
  }
222
- err.write(`deadset-ts: ${error.message}\n`);
223
+ diagnose(err, error.message);
223
224
  return EXIT_FAILURE;
224
225
  }
225
226
  if (error instanceof SetupError) {
@@ -229,7 +230,7 @@ function refuse(error, err) {
229
230
  return EXIT_FAILURE;
230
231
  }
231
232
  if (error instanceof ScopeError) {
232
- err.write(`deadset-ts: ${error.message}\n`);
233
+ diagnose(err, error.message);
233
234
  return EXIT_FAILURE;
234
235
  }
235
236
  throw error;