@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
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Shell command lines read as text, never run: the commands a line runs, the tokens the
3
+ * Contract splits a command into, and the words POSIX token recognition reads.
4
+ */
5
+ /**
6
+ * The shell's list and pipeline operators, each of which ends one command. An `&` beside a
7
+ * redirection's `<` or `>` (`2>&1`, and bash's `&>log`) is part of the redirection.
8
+ */
9
+ const COMMAND_OPERATORS = /&&|\|\||;|\||(?<![<>])&(?!>)/u;
10
+ /** Where a command splits into tokens: white space and the operators the Contract names. */
11
+ const TOKEN_SEPARATORS = /\s+|&&|\|\||;|\|/u;
12
+ /** A text written inside one pair of quotes. */
13
+ const QUOTED = /^(["'])(.*)\1$/su;
14
+ /** The commands one line runs, the text between two of the shell's list and pipeline operators. */
15
+ export function commandsOf(line) {
16
+ return line.split(COMMAND_OPERATORS);
17
+ }
18
+ /** One text with one pair of enclosing quotes removed. */
19
+ export function unquoted(text) {
20
+ return text.replace(QUOTED, "$2");
21
+ }
22
+ /**
23
+ * Every token one script's command line holds, each with one pair of enclosing quotes
24
+ * removed. A token holding a wildcard is a pattern rather than a path, and names no
25
+ * entry point.
26
+ */
27
+ export function scriptTokens(command) {
28
+ return command
29
+ .split(TOKEN_SEPARATORS)
30
+ .map(unquoted)
31
+ .filter((token) => token !== "" && !token.includes("*"));
32
+ }
33
+ /** One shell line up to its comment: a `#` that starts a word outside quotes. */
34
+ export function withoutComment(line) {
35
+ let quote = "";
36
+ for (let at = 0; at < line.length; at += 1) {
37
+ const char = line[at];
38
+ if (quote !== "") {
39
+ if (char === quote) {
40
+ quote = "";
41
+ }
42
+ else if (char === "\\" && quote === '"') {
43
+ at += 1;
44
+ }
45
+ }
46
+ else if (char === "'" || char === '"') {
47
+ quote = char;
48
+ }
49
+ else if (char === "\\") {
50
+ at += 1;
51
+ }
52
+ else if (char === "#" && (at === 0 || /\s/u.test(line[at - 1] ?? ""))) {
53
+ return line.slice(0, at);
54
+ }
55
+ }
56
+ return line;
57
+ }
58
+ /** The characters that end a word outside quotes and separate nothing: blanks. */
59
+ // Stryker disable next-line Regex: its \v to \V mutant is no valid escape under the u flag, so the whole module fails to parse
60
+ const BLANK = /[ \t\r\f\v]/u;
61
+ /** The characters that end a word outside quotes and begin an operator: newline included. */
62
+ const OPERATOR = /[\n;&|()<>]/u;
63
+ /** The characters a backslash escapes inside double quotes; before any other it stands for itself. */
64
+ const DOUBLE_QUOTE_ESCAPES = new Set(["$", "`", '"', "\\"]);
65
+ /**
66
+ * The words of one command line, as POSIX token recognition reads quotes, backslashes,
67
+ * line continuations, comments and operators. A word an expansion may change (an unquoted
68
+ * `$`, backquote, glob character or leading `~`, or a `$` or backquote in double quotes)
69
+ * is computed, and so is the rest of a line whose quote is never closed. A computed word
70
+ * and each operator are undefined, so a flag's file is never read across them.
71
+ */
72
+ export function shellWords(command) {
73
+ const words = [];
74
+ let word;
75
+ let computed = false;
76
+ const end = () => {
77
+ if (word !== undefined) {
78
+ words.push(computed ? undefined : word);
79
+ }
80
+ word = undefined;
81
+ computed = false;
82
+ };
83
+ for (let at = 0; at < command.length; at += 1) {
84
+ const char = command.charAt(at);
85
+ if (char === "\\" && command.charAt(at + 1) === "\n") {
86
+ at += 1;
87
+ }
88
+ else if (BLANK.test(char)) {
89
+ end();
90
+ }
91
+ else if (OPERATOR.test(char)) {
92
+ end();
93
+ words.push(undefined);
94
+ }
95
+ else if (char === "#" && word === undefined) {
96
+ const newline = command.indexOf("\n", at);
97
+ at = newline < 0 ? command.length : newline - 1;
98
+ }
99
+ else if (char === "\\") {
100
+ at += 1;
101
+ word = (word ?? "") + command.charAt(at);
102
+ }
103
+ else if (char === "'" || char === '"') {
104
+ const close = closingQuote(command, at);
105
+ if (close < 0) {
106
+ word ??= "";
107
+ computed = true;
108
+ break;
109
+ }
110
+ const quoted = char === "'"
111
+ ? { text: command.slice(at + 1, close), computed: false }
112
+ : doubleQuoted(command.slice(at + 1, close));
113
+ word = (word ?? "") + quoted.text;
114
+ computed ||= quoted.computed;
115
+ at = close;
116
+ }
117
+ else {
118
+ computed ||= /[$`*?[]/u.test(char) || (char === "~" && word === undefined);
119
+ word = (word ?? "") + char;
120
+ }
121
+ }
122
+ end();
123
+ return words;
124
+ }
125
+ /** The text between one pair of double quotes, unescaped, and whether an expansion may change it. */
126
+ function doubleQuoted(inner) {
127
+ let text = "";
128
+ let computed = false;
129
+ for (let at = 0; at < inner.length; at += 1) {
130
+ const char = inner.charAt(at);
131
+ const next = inner.charAt(at + 1);
132
+ if (char === "\\" && next === "\n") {
133
+ at += 1;
134
+ }
135
+ else if (char === "\\" && DOUBLE_QUOTE_ESCAPES.has(next)) {
136
+ text += next;
137
+ at += 1;
138
+ }
139
+ else {
140
+ computed ||= char === "$" || char === "`";
141
+ text += char;
142
+ }
143
+ }
144
+ return { text, computed };
145
+ }
146
+ /** The index of the quote that closes the one at `open`, or -1 where none does. */
147
+ function closingQuote(command, open) {
148
+ const quote = command.charAt(open);
149
+ for (let at = open + 1; at < command.length; at += 1) {
150
+ const char = command.charAt(at);
151
+ if (char === quote) {
152
+ return at;
153
+ }
154
+ if (quote === '"' && char === "\\") {
155
+ at += 1;
156
+ }
157
+ }
158
+ return -1;
159
+ }
@@ -5,6 +5,7 @@
5
5
  * alias, a tag, a block scalar, a complex key or a second document, has no value here,
6
6
  * as a JSON document that does not parse has none.
7
7
  */
8
+ import { isRecord } from "../json-document.js";
8
9
  /** A failure to read the document within the subset; it ends the whole reading. */
9
10
  class Unsupported extends Error {
10
11
  }
@@ -353,3 +354,40 @@ export function yamlDocument(text) {
353
354
  throw error;
354
355
  }
355
356
  }
357
+ /** The key one line of the document's top-level mapping opens, or undefined where it opens none. */
358
+ function topLevelKey(raw) {
359
+ const content = withoutComment(raw).trimEnd();
360
+ const end = /^\s/u.test(content) ? -1 : keyEnd(content);
361
+ if (end < 0) {
362
+ return undefined;
363
+ }
364
+ try {
365
+ const key = scalar(content.slice(0, end));
366
+ return typeof key === "object" && key !== null ? undefined : String(key);
367
+ }
368
+ catch {
369
+ return undefined;
370
+ }
371
+ }
372
+ /**
373
+ * The value of one key of a document's top-level block mapping, read from that key's lines
374
+ * alone, so a document whose other keys lie outside the subset still yields it. A key
375
+ * declared twice, or one whose own lines lie outside the subset, is refused.
376
+ */
377
+ export function yamlKey(text, key) {
378
+ const lines = text.replace(/^\uFEFF/u, "").split(/\r?\n/u);
379
+ const starts = lines.flatMap((line, at) => (topLevelKey(line) === undefined ? [] : [at]));
380
+ const named = starts.filter((at) => topLevelKey(lines[at] ?? "") === key);
381
+ const [start] = named;
382
+ if (start === undefined) {
383
+ return { kind: "absent" };
384
+ }
385
+ if (named.length > 1) {
386
+ return { kind: "refused", reason: `the ${key} key is declared more than once` };
387
+ }
388
+ const end = starts.find((at) => at > start) ?? lines.length;
389
+ const value = yamlDocument(lines.slice(start, end).join("\n"));
390
+ return isRecord(value)
391
+ ? { kind: "value", value: value[key] }
392
+ : { kind: "refused", reason: `the value of ${key} is outside the YAML subset read here` };
393
+ }
@@ -8,9 +8,11 @@
8
8
  import { isClassDeclaration, isFunctionLikeDeclaration, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/ast";
9
9
  import { packageOfSpecifier } from "./dependencies.js";
10
10
  import { nodeKey } from "./inventory.js";
11
- import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
11
+ import { ancestors, dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath, } from "./paths.js";
12
12
  import { isAnswered } from "./query.js";
13
13
  import { findWorkspace } from "./workspace.js";
14
+ import { entriesOf } from "./fs/tree.js";
15
+ import { INSTALLED_PACKAGES, MANIFEST, manifestAt } from "./fs/manifest.js";
14
16
  /** The compiler's codes for an import whose module it cannot find. */
15
17
  const MODULE_NOT_FOUND = new Set([2307, 2792]);
16
18
  /**
@@ -19,7 +21,6 @@ const MODULE_NOT_FOUND = new Set([2307, 2792]);
19
21
  * 5056), a file outside `rootDir` (6059), one a composite project does not list (6307).
20
22
  */
21
23
  const CONFIGURATION_LEVEL = new Set([5055, 5056, 6059, 6307]);
22
- const MANIFEST = "package.json";
23
24
  const MANIFEST_SECTIONS = [
24
25
  "dependencies",
25
26
  "devDependencies",
@@ -85,54 +86,29 @@ function provided(host, path, fileOnly) {
85
86
  }
86
87
  const dir = dirnamePath(path);
87
88
  const stem = `${path.slice(dir.length + 1)}.`;
88
- try {
89
- return host
90
- .readDirectory(dir)
91
- .some((entry) => entry.name.startsWith(stem) && counts(joinPath(dir, entry.name)));
92
- }
93
- catch {
94
- return false;
95
- }
89
+ return entriesOf(host, dir).some((entry) => entry.name.startsWith(stem) && counts(joinPath(dir, entry.name)));
96
90
  }
97
91
  /** The manifest at or above `dir`, no higher than the target root, that declares `name`. */
98
92
  function declaringManifest(host, dir, targetRoot, name) {
99
- for (let at = dir;; at = dirnamePath(at)) {
100
- const path = joinPath(at, MANIFEST);
101
- let manifest;
102
- try {
103
- manifest = JSON.parse(host.readFile(path));
104
- }
105
- catch {
106
- manifest = undefined;
107
- }
108
- if (typeof manifest === "object" && manifest !== null) {
109
- const sections = manifest;
110
- for (const section of MANIFEST_SECTIONS) {
111
- const declared = sections[section];
112
- if (typeof declared === "object" && declared !== null && Object.hasOwn(declared, name)) {
113
- return path;
114
- }
93
+ const chain = ancestors(dir, targetRoot);
94
+ // A directory outside the target root reads its own manifest alone.
95
+ for (const at of chain.length > 0 ? chain : [dir]) {
96
+ const manifest = manifestAt(host, at);
97
+ for (const section of MANIFEST_SECTIONS) {
98
+ const declared = manifest?.[section];
99
+ if (typeof declared === "object" && declared !== null && Object.hasOwn(declared, name)) {
100
+ return joinPath(at, MANIFEST);
115
101
  }
116
102
  }
117
- // The target root is not below itself, so the walk stops after reading its manifest.
118
- if (relativePath(targetRoot, at) === undefined) {
119
- return undefined;
120
- }
121
103
  }
104
+ return undefined;
122
105
  }
123
106
  /**
124
107
  * Whether a package is installed for an importer in `dir`: in the `node_modules` of
125
108
  * `dir` or of any ancestor, above the target root too, as Node and the compiler look.
126
109
  */
127
110
  function installed(host, dir, name) {
128
- for (let at = dir;; at = dirnamePath(at)) {
129
- if (host.kindOf(joinPath(at, "node_modules", name)) === "directory") {
130
- return true;
131
- }
132
- if (dirnamePath(at) === at) {
133
- return false;
134
- }
135
- }
111
+ return ancestors(dir).some((at) => host.kindOf(joinPath(at, INSTALLED_PACKAGES, name)) === "directory");
136
112
  }
137
113
  /**
138
114
  * Why one import that the compiler cannot resolve names a module the project expects,
@@ -4,19 +4,12 @@
4
4
  * naming a directory below the calling file's. The files such a load runs are roots the
5
5
  * analysis does not see, so the configuration's roots are never complete.
6
6
  */
7
- import { isAsExpression, isBinaryExpression, isCallExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isParenthesizedExpression, isSatisfiesExpression, isStringLiteral, isTemplateExpression, SyntaxKind, } from "@typescript/native/ast";
7
+ import { isBinaryExpression, isCallExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isStringLiteral, isTemplateExpression, SyntaxKind, } from "@typescript/native/ast";
8
8
  import { dirnamePath, relativePath, resolvePath } from "./paths.js";
9
9
  import { renderPosition } from "./position.js";
10
10
  import { UNANSWERED } from "./query.js";
11
11
  import { sourceFilesOf } from "./source-files.js";
12
- /** The expression a wrapper that changes no value holds: parentheses and type assertions. */
13
- function unwrapped(node) {
14
- let at = node;
15
- while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
16
- at = at.expression;
17
- }
18
- return at;
19
- }
12
+ import { unwrapped } from "./unwrap.js";
20
13
  /** The literal text a computed path starts with: a template's head, or a concatenation's first literal. */
21
14
  export function leadingLiteral(node) {
22
15
  const at = unwrapped(node);
@@ -0,0 +1,23 @@
1
+ import { isAsExpression, isNonNullExpression, isParenthesizedExpression, isSatisfiesExpression, } from "@typescript/native/ast";
2
+ /** The expression inside every pair of parentheses around `node`. */
3
+ export function parenthesesRemoved(node) {
4
+ let at = node;
5
+ while (isParenthesizedExpression(at)) {
6
+ at = at.expression;
7
+ }
8
+ return at;
9
+ }
10
+ /**
11
+ * The expression inside every wrapper around `node` that changes no value it evaluates to:
12
+ * parentheses, `as` and `satisfies` assertions and the non-null assertion.
13
+ */
14
+ export function unwrapped(node) {
15
+ let at = node;
16
+ while (isParenthesizedExpression(at) ||
17
+ isAsExpression(at) ||
18
+ isSatisfiesExpression(at) ||
19
+ isNonNullExpression(at)) {
20
+ at = at.expression;
21
+ }
22
+ return at;
23
+ }
@@ -6,7 +6,7 @@ import { moduleIdentity } from "../consumers.js";
6
6
  import { analyzerProvenance, verdictOf } from "../findings-pass.js";
7
7
  import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "../paths.js";
8
8
  import { partialNotes } from "../partial.js";
9
- import { buildReport, LANGUAGE, capFindings, sortFindings, } from "../report.js";
9
+ import { buildReport, capFindings, sortFindings, } from "../report.js";
10
10
  import { reportRefusals } from "../report-schema.js";
11
11
  import { json } from "../reporters/json.js";
12
12
  import { RENDERINGS } from "../reporters/reporters.js";
@@ -14,6 +14,8 @@ import { parseTemplate, TemplateError } from "../reporters/template.js";
14
14
  import { resolve } from "../resolve.js";
15
15
  import { otherSchemeRows, rowReason, writeBaseline } from "../suppress-file.js";
16
16
  import {} from "./verb.js";
17
+ import { bytewise } from "../text/compare.js";
18
+ import { diagnose, LANGUAGE, message } from "../product.js";
17
19
  /** The options `analyze` takes beside the ones every verb takes. */
18
20
  export const ANALYZE_OPTIONS = {
19
21
  plain: ["report", "baseline-write", "exit-code", "template"],
@@ -31,7 +33,7 @@ class ReportPathError extends Error {
31
33
  }
32
34
  /** The formats this analyzer renders, for a message that names them. */
33
35
  function formatNames() {
34
- return [...RENDERINGS.keys()].sort().join(", ");
36
+ return [...RENDERINGS.keys()].sort(bytewise).join(", ");
35
37
  }
36
38
  /** The renderings one invocation writes: the ones it named, else the configuration's. */
37
39
  function formatsOf(named, configured) {
@@ -137,7 +139,7 @@ function templateOf(host, invoked, named, formats) {
137
139
  source = host.readFile(resolvePath(invoked, named));
138
140
  }
139
141
  catch (error) {
140
- throw new ConfigError("malformed", "", `--template=${named}: ${error instanceof Error ? error.message : String(error)}`);
142
+ throw new ConfigError("malformed", "", `--template=${named}: ${message(error)}`);
141
143
  }
142
144
  try {
143
145
  return parseTemplate(source);
@@ -186,7 +188,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
186
188
  version = host.analyzerVersion();
187
189
  }
188
190
  catch (error) {
189
- err.write(`deadset-ts: ${error instanceof Error ? error.message : String(error)}\n`);
191
+ diagnose(err, message(error));
190
192
  return EXIT_FAILURE;
191
193
  }
192
194
  let report;
@@ -254,7 +256,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
254
256
  if (!(error instanceof ReportPathError)) {
255
257
  throw error;
256
258
  }
257
- err.write(`deadset-ts: ${error.message}\n`);
259
+ diagnose(err, error.message);
258
260
  return EXIT_FAILURE;
259
261
  }
260
262
  try {
@@ -281,30 +283,30 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
281
283
  }
282
284
  }
283
285
  catch (error) {
284
- err.write(`deadset-ts: ${error instanceof Error ? error.message : String(error)}\n`);
286
+ diagnose(err, message(error));
285
287
  return EXIT_FAILURE;
286
288
  }
287
289
  for (const note of partial) {
288
- err.write(`deadset-ts: ${note}\n`);
290
+ diagnose(err, note);
289
291
  }
290
292
  for (const line of [...unread, ...unfound]) {
291
- err.write(`deadset-ts: ${line}\n`);
293
+ diagnose(err, line);
292
294
  }
293
295
  for (const warning of warnings) {
294
- err.write(`deadset-ts: ${warning.path}:${String(warning.line)}: a <script> start tag that begins a line is not read as a block: ${warning.reason}\n`);
296
+ diagnose(err, `${warning.path}:${String(warning.line)}: a <script> start tag that begins a line is not read as a block: ${warning.reason}`);
295
297
  }
296
298
  for (const skip of report.type_error_skips) {
297
- err.write(`deadset-ts: ${skip.path}:${String(skip.line)}: ${skip.message}: the function or statement holding this type error is not evaluated\n`);
299
+ diagnose(err, `${skip.path}:${String(skip.line)}: ${skip.message}: the function or statement holding this type error is not evaluated`);
298
300
  }
299
301
  if (report.totals.pending > 0) {
300
- err.write(`deadset-ts: ${counted(report.totals.pending, "pending finding")}: the report names a declared cross-language edge, and no merge has resolved it\n`);
302
+ diagnose(err, `${counted(report.totals.pending, "pending finding")}: the report names a declared cross-language edge, and no merge has resolved it`);
301
303
  }
302
304
  if (report.totals.stale_suppressions > 0) {
303
- err.write(`deadset-ts: ${counted(report.totals.stale_suppressions, "stale suppression")}\n`);
305
+ diagnose(err, counted(report.totals.stale_suppressions, "stale suppression"));
304
306
  }
305
307
  if (exitCode === EXIT_CODE_OFF) {
306
308
  if (verdict !== EXIT_CLEAN) {
307
- err.write(`deadset-ts: the exit code is configured off: the verdict of this run is ${String(verdict)}\n`);
309
+ diagnose(err, `the exit code is configured off: the verdict of this run is ${String(verdict)}`);
308
310
  }
309
311
  return EXIT_CLEAN;
310
312
  }
@@ -1,7 +1,7 @@
1
1
  import { CONTRACT_VERSION, EXIT_CLEAN, EXIT_FAILURE, EXIT_USAGE, SCHEMA_VERSIONS, } from "../contract/contract.generated.js";
2
2
  import { CONFORMANCE } from "../conformance.js";
3
- import { ANALYZER_NAME, LANGUAGE } from "../report.js";
4
3
  import {} from "./verb.js";
4
+ import { diagnose, LANGUAGE, message, PRODUCT } from "../product.js";
5
5
  /**
6
6
  * The document `describe` writes: the analyzer, the Contract version it implements, the
7
7
  * report schema versions it reads, the languages it claims, and its recorded result over
@@ -9,7 +9,7 @@ import {} from "./verb.js";
9
9
  */
10
10
  export function describeDocument(version, recorded) {
11
11
  const document = {
12
- name: ANALYZER_NAME,
12
+ name: PRODUCT,
13
13
  version,
14
14
  contract_version: CONTRACT_VERSION,
15
15
  schema_versions_accepted: SCHEMA_VERSIONS,
@@ -29,7 +29,7 @@ export function describeDocument(version, recorded) {
29
29
  /** Writes this analyzer's description to the output stream, and nothing to the error stream. */
30
30
  export const describeVerb = ({ out, err, host, args }) => {
31
31
  if (args.length > 0) {
32
- err.write(`deadset-ts: describe takes no argument, got ${JSON.stringify(args[0])}\n`);
32
+ diagnose(err, `describe takes no argument, got ${JSON.stringify(args[0])}`);
33
33
  return EXIT_USAGE;
34
34
  }
35
35
  let version;
@@ -37,7 +37,7 @@ export const describeVerb = ({ out, err, host, args }) => {
37
37
  version = host.analyzerVersion();
38
38
  }
39
39
  catch (error) {
40
- err.write(`deadset-ts: describe: ${error instanceof Error ? error.message : String(error)}\n`);
40
+ diagnose(err, `describe: ${message(error)}`);
41
41
  return EXIT_FAILURE;
42
42
  }
43
43
  out.write(describeDocument(version, CONFORMANCE));
@@ -9,6 +9,8 @@ import { resolvePath } from "../paths.js";
9
9
  import { positionKey } from "../position.js";
10
10
  import { resolve } from "../resolve.js";
11
11
  import {} from "./verb.js";
12
+ import { bytewise } from "../text/compare.js";
13
+ import { diagnose } from "../product.js";
12
14
  /**
13
15
  * The three spellings of an explanation request, each naming the symbol to explain. They are
14
16
  * one request, because exactly one state holds for a symbol and the answer is that state.
@@ -74,7 +76,7 @@ function subjectOf(symbols, named) {
74
76
  const partial = symbols
75
77
  .filter((symbol) => symbol.ref.toLowerCase().includes(folded) || symbol.name.toLowerCase().includes(folded))
76
78
  .map(described)
77
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
79
+ .sort(bytewise);
78
80
  return { candidates: partial.slice(0, MAX_PARTIAL_MATCHES) };
79
81
  }
80
82
  /** Every reference of the run once, a reference several configurations make counted once. */
@@ -318,7 +320,7 @@ export const explainVerb = ({ out, err, host, inputs, scope, openClient, option
318
320
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
319
321
  });
320
322
  if (subject === undefined) {
321
- err.write(`deadset-ts: ${JSON.stringify(named)} names no one symbol of the target\n`);
323
+ diagnose(err, `${JSON.stringify(named)} names no one symbol of the target`);
322
324
  for (const candidate of candidates) {
323
325
  err.write(` ${candidate}\n`);
324
326
  }
@@ -330,7 +332,7 @@ export const explainVerb = ({ out, err, host, inputs, scope, openClient, option
330
332
  out.write(explanation(analysis, subject, config.reporters.cascade));
331
333
  }
332
334
  for (const note of notes) {
333
- err.write(`deadset-ts: ${note}\n`);
335
+ diagnose(err, note);
334
336
  }
335
337
  return subject === undefined ? EXIT_USAGE : EXIT_CLEAN;
336
338
  };
@@ -4,6 +4,7 @@ import { partialNotes } from "../partial.js";
4
4
  import { resolvePath } from "../paths.js";
5
5
  import { resolveUnkinded } from "../resolve.js";
6
6
  import {} from "./verb.js";
7
+ import { diagnose } from "../product.js";
7
8
  /**
8
9
  * Writes the name of every project one run analyzes, one per line: the identifier the
9
10
  * build matrix declares for it, or, where discovery derived the project, its
@@ -23,7 +24,7 @@ export const printProjectsVerb = ({ out, err, host, inputs, scope, openClient })
23
24
  unanswered: answer.unanswered,
24
25
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
25
26
  })) {
26
- err.write(`deadset-ts: ${note}\n`);
27
+ diagnose(err, note);
27
28
  }
28
29
  return EXIT_CLEAN;
29
30
  };
@@ -5,6 +5,7 @@ import { partialNotes } from "../partial.js";
5
5
  import { resolvePath } from "../paths.js";
6
6
  import { resolve } from "../resolve.js";
7
7
  import {} from "./verb.js";
8
+ import { diagnose } from "../product.js";
8
9
  /**
9
10
  * Writes every declaration an exemption held back in the production sweep a report is
10
11
  * built from, one per line, and nothing where no exemption held one back. One fact
@@ -27,7 +28,7 @@ export const printRetainedVerb = ({ out, err, host, inputs, scope, openClient })
27
28
  unanswered: swept.unanswered,
28
29
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
29
30
  })) {
30
- err.write(`deadset-ts: ${note}\n`);
31
+ diagnose(err, note);
31
32
  }
32
33
  return EXIT_CLEAN;
33
34
  };
@@ -4,6 +4,7 @@ import { partialNotes } from "../partial.js";
4
4
  import { resolvePath } from "../paths.js";
5
5
  import { resolve } from "../resolve.js";
6
6
  import {} from "./verb.js";
7
+ import { diagnose } from "../product.js";
7
8
  /**
8
9
  * One root as a line: the declaration's reference, why it is a root, and the string
9
10
  * that named it, separated by tabs. A run of several configurations adds the ones
@@ -40,7 +41,7 @@ export const printRootsVerb = ({ out, err, host, inputs, scope, openClient }) =>
40
41
  unanswered: answer.unanswered,
41
42
  targetRoot: resolvePath(host.workingDirectory(), scoped.target.path),
42
43
  })) {
43
- err.write(`deadset-ts: ${note}\n`);
44
+ diagnose(err, note);
44
45
  }
45
46
  return answer.findings.length > 0 ? EXIT_FINDINGS : EXIT_CLEAN;
46
47
  };
@@ -1,7 +1,8 @@
1
1
  import { CONTRACT_VERSION, EXIT_CLEAN } from "../contract/contract.generated.js";
2
2
  import {} from "./verb.js";
3
+ import { PRODUCT } from "../product.js";
3
4
  /** Writes the version the host reports and the Contract version this analyzer implements. */
4
5
  export const versionVerb = ({ out, host }) => {
5
- out.write(`deadset-ts ${host.analyzerVersion()}\ncontract ${CONTRACT_VERSION}\n`);
6
+ out.write(`${PRODUCT} ${host.analyzerVersion()}\ncontract ${CONTRACT_VERSION}\n`);
6
7
  return EXIT_CLEAN;
7
8
  };