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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/dist/bin/mapper-connection.js +2 -1
  2. package/dist/bin/node-host.js +3 -2
  3. package/dist/src/analysis.js +28 -46
  4. package/dist/src/automatic-types.js +10 -29
  5. package/dist/src/build-scripts.js +17 -29
  6. package/dist/src/component-layer.js +4 -2
  7. package/dist/src/components.js +2 -3
  8. package/dist/src/config.js +3 -2
  9. package/dist/src/configuration-files.js +27 -168
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/contract/contract.generated.js +1084 -0
  12. package/dist/src/contract/report.generated.js +796 -0
  13. package/dist/src/conventions.js +20 -60
  14. package/dist/src/dependencies.js +25 -43
  15. package/dist/src/discover.js +13 -39
  16. package/dist/src/edges.js +7 -15
  17. package/dist/src/element-access.js +4 -6
  18. package/dist/src/entry-point-gaps.js +2 -8
  19. package/dist/src/entry-points.js +3 -10
  20. package/dist/src/enum-group.js +3 -10
  21. package/dist/src/erased-stores.js +4 -6
  22. package/dist/src/exempt-classes.js +3 -63
  23. package/dist/src/exempt.js +13 -40
  24. package/dist/src/file-facts.js +2 -25
  25. package/dist/src/findings/completion.js +11 -20
  26. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  27. package/dist/src/findings/emitters.js +4 -4
  28. package/dist/src/findings/interfaces.js +4 -5
  29. package/dist/src/findings/intra-function.js +8 -7
  30. package/dist/src/findings/non-code-artifacts.js +5 -6
  31. package/dist/src/findings/reads-and-writes.js +11 -9
  32. package/dist/src/findings/self-check.js +10 -12
  33. package/dist/src/findings/unused-declarations.js +28 -27
  34. package/dist/src/findings/visibility-narrowing.js +9 -11
  35. package/dist/src/findings-pass.js +7 -14
  36. package/dist/src/fs/manifest.js +43 -0
  37. package/dist/src/fs/tree.js +88 -0
  38. package/dist/src/git-ignore.js +34 -25
  39. package/dist/src/html-entries.js +6 -33
  40. package/dist/src/in-operands.js +2 -1
  41. package/dist/src/index.js +1 -1
  42. package/dist/src/intra-function-parts.js +18 -26
  43. package/dist/src/inventory.js +2 -21
  44. package/dist/src/json-document.js +4 -0
  45. package/dist/src/keyof-comparisons.js +4 -6
  46. package/dist/src/kinds.js +4 -360
  47. package/dist/src/manifest.js +5 -43
  48. package/dist/src/mdx-entries.js +3 -26
  49. package/dist/src/partial.js +2 -1
  50. package/dist/src/paths.js +26 -15
  51. package/dist/src/position.js +2 -1
  52. package/dist/src/print.js +3 -2
  53. package/dist/src/product.js +13 -0
  54. package/dist/src/references.js +14 -27
  55. package/dist/src/report-schema.js +272 -0
  56. package/dist/src/report.js +39 -42
  57. package/dist/src/reporters/sarif.js +4 -2
  58. package/dist/src/reporters/template.js +4 -19
  59. package/dist/src/resolve.js +12 -26
  60. package/dist/src/roots.js +7 -17
  61. package/dist/src/run.js +10 -8
  62. package/dist/src/schema.js +4 -3
  63. package/dist/src/scope.js +4 -5
  64. package/dist/src/serialization-contract.js +4 -8
  65. package/dist/src/session.js +2 -1
  66. package/dist/src/setup-failure.js +2 -6
  67. package/dist/src/suppress-file.js +12 -13
  68. package/dist/src/suppress-inline.js +3 -2
  69. package/dist/src/suppress.js +29 -48
  70. package/dist/src/symbol-keys.js +3 -5
  71. package/dist/src/template-field.js +5 -15
  72. package/dist/src/test-globals.js +6 -28
  73. package/dist/src/test-joins.js +6 -6
  74. package/dist/src/text/compare.js +26 -0
  75. package/dist/src/text/glob.js +194 -0
  76. package/dist/src/text/shell.js +159 -0
  77. package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
  78. package/dist/src/type-errors.js +14 -38
  79. package/dist/src/unseen-loads.js +2 -9
  80. package/dist/src/unwrap.js +23 -0
  81. package/dist/src/verbs/analyze.js +22 -15
  82. package/dist/src/verbs/describe.js +7 -7
  83. package/dist/src/verbs/explain.js +7 -4
  84. package/dist/src/verbs/print-config.js +2 -1
  85. package/dist/src/verbs/print-projects.js +4 -2
  86. package/dist/src/verbs/print-retained.js +4 -2
  87. package/dist/src/verbs/print-roots.js +4 -2
  88. package/dist/src/verbs/verb.js +1 -5
  89. package/dist/src/verbs/version.js +4 -3
  90. package/dist/src/workflow-steps.js +9 -46
  91. package/dist/src/workspace-manifests.js +2 -2
  92. package/dist/src/workspace-programs.js +5 -3
  93. package/dist/src/workspace-resolution.js +13 -17
  94. package/dist/src/workspace.js +28 -116
  95. package/jsr.json +1 -1
  96. package/package.json +2 -1
  97. package/src/analysis.ts +29 -54
  98. package/src/automatic-types.ts +11 -29
  99. package/src/build-scripts.ts +17 -38
  100. package/src/component-layer.ts +4 -2
  101. package/src/components.ts +2 -4
  102. package/src/config.ts +11 -15
  103. package/src/configuration-files.ts +27 -170
  104. package/src/conformance-record.ts +1 -1
  105. package/src/contract/contract.generated.ts +1148 -0
  106. package/src/contract/report.generated.ts +1157 -0
  107. package/src/conventions.ts +26 -74
  108. package/src/dependencies.ts +32 -50
  109. package/src/discover.ts +14 -39
  110. package/src/edges.ts +7 -21
  111. package/src/element-access.ts +4 -7
  112. package/src/entry-point-gaps.ts +3 -9
  113. package/src/entry-points.ts +2 -13
  114. package/src/enum-group.ts +2 -11
  115. package/src/erased-stores.ts +3 -7
  116. package/src/exempt-classes.ts +5 -74
  117. package/src/exempt.ts +17 -47
  118. package/src/file-facts.ts +6 -24
  119. package/src/finding.ts +9 -9
  120. package/src/findings/completion.ts +22 -23
  121. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  122. package/src/findings/emitter.ts +6 -7
  123. package/src/findings/emitters.ts +6 -6
  124. package/src/findings/interfaces.ts +4 -6
  125. package/src/findings/intra-function.ts +8 -8
  126. package/src/findings/non-code-artifacts.ts +5 -7
  127. package/src/findings/reads-and-writes.ts +15 -12
  128. package/src/findings/self-check.ts +11 -17
  129. package/src/findings/unused-declarations.ts +28 -27
  130. package/src/findings/visibility-narrowing.ts +9 -12
  131. package/src/findings-pass.ts +7 -16
  132. package/src/fs/manifest.ts +52 -0
  133. package/src/fs/tree.ts +110 -0
  134. package/src/git-ignore.ts +38 -25
  135. package/src/html-entries.ts +6 -33
  136. package/src/in-operands.ts +2 -1
  137. package/src/index.ts +1 -1
  138. package/src/intra-function-parts.ts +18 -28
  139. package/src/inventory.ts +2 -20
  140. package/src/json-document.ts +5 -0
  141. package/src/keyof-comparisons.ts +3 -7
  142. package/src/kinds.ts +6 -406
  143. package/src/manifest.ts +5 -46
  144. package/src/mdx-entries.ts +3 -26
  145. package/src/partial.ts +2 -1
  146. package/src/paths.ts +27 -15
  147. package/src/position.ts +2 -1
  148. package/src/print.ts +3 -2
  149. package/src/product.ts +18 -0
  150. package/src/references.ts +14 -29
  151. package/src/report-schema.ts +364 -0
  152. package/src/report.ts +66 -229
  153. package/src/reporters/annotations.ts +2 -2
  154. package/src/reporters/json.ts +1 -1
  155. package/src/reporters/reporter.ts +2 -2
  156. package/src/reporters/reporters.ts +1 -1
  157. package/src/reporters/sarif.ts +17 -12
  158. package/src/reporters/template.ts +4 -20
  159. package/src/reporters/text.ts +3 -3
  160. package/src/resolve.ts +31 -38
  161. package/src/roots.ts +7 -20
  162. package/src/run.ts +10 -14
  163. package/src/schema.ts +4 -3
  164. package/src/scope.ts +4 -10
  165. package/src/serialization-contract.ts +4 -9
  166. package/src/session.ts +2 -1
  167. package/src/setup-failure.ts +5 -7
  168. package/src/suppress-file.ts +11 -15
  169. package/src/suppress-inline.ts +2 -2
  170. package/src/suppress.ts +39 -55
  171. package/src/symbol-keys.ts +2 -5
  172. package/src/template-field.ts +5 -18
  173. package/src/test-globals.ts +9 -27
  174. package/src/test-joins.ts +6 -8
  175. package/src/text/compare.ts +27 -0
  176. package/src/text/glob.ts +210 -0
  177. package/src/text/shell.ts +161 -0
  178. package/src/{yaml-document.ts → text/yaml.ts} +46 -0
  179. package/src/type-errors.ts +25 -36
  180. package/src/unseen-loads.ts +1 -12
  181. package/src/unwrap.ts +34 -0
  182. package/src/verbs/analyze.ts +38 -25
  183. package/src/verbs/describe.ts +13 -7
  184. package/src/verbs/explain.ts +7 -4
  185. package/src/verbs/print-config.ts +2 -1
  186. package/src/verbs/print-projects.ts +4 -2
  187. package/src/verbs/print-retained.ts +4 -2
  188. package/src/verbs/print-roots.ts +4 -2
  189. package/src/verbs/verb.ts +0 -6
  190. package/src/verbs/version.ts +4 -3
  191. package/src/workflow-steps.ts +9 -44
  192. package/src/workspace-manifests.ts +2 -2
  193. package/src/workspace-programs.ts +5 -3
  194. package/src/workspace-resolution.ts +21 -17
  195. package/src/workspace.ts +35 -121
  196. package/dist/src/glob.js +0 -65
  197. package/dist/src/pnpm-workspace.js +0 -120
  198. package/dist/src/version.js +0 -7
  199. package/src/glob.ts +0 -67
  200. package/src/pnpm-workspace.ts +0 -135
  201. package/src/version.ts +0 -7
@@ -1,62 +1,4 @@
1
- /**
2
- * The exemption classes of the Contract's vocabulary, in its order: each class's name, its
3
- * languages and, for a TypeScript class, which private members it may retain. The rows are
4
- * written out, as the fixed severity codes are, and a test holds them equal to the Contract.
5
- * A class name is a type, so a table or a record naming a class outside the vocabulary, or
6
- * one of another language only, does not compile.
7
- */
8
- /** Every class of the vocabulary, in the order the Contract lists them. */
9
- export const EXEMPTION_CLASSES = [
10
- {
11
- class: "interface-satisfaction",
12
- languages: ["go", "ts"],
13
- typescriptVisibility: { private: false, privateName: false },
14
- },
15
- { class: "encoding-reflection", languages: ["go"] },
16
- { class: "format-verb-contract", languages: ["go"] },
17
- { class: "errors-duck-typing", languages: ["go"] },
18
- {
19
- class: "enum-group",
20
- languages: ["go", "ts"],
21
- typescriptVisibility: { private: false, privateName: false },
22
- },
23
- {
24
- class: "generated-file",
25
- languages: ["go", "ts"],
26
- typescriptVisibility: { private: true, privateName: false },
27
- },
28
- { class: "linkname-cgo-asm-plugin", languages: ["go"] },
29
- {
30
- class: "template-field",
31
- languages: ["go", "ts"],
32
- typescriptVisibility: { private: true, privateName: false },
33
- },
34
- {
35
- class: "reflective-lookup",
36
- languages: ["go", "ts"],
37
- typescriptVisibility: { private: true, privateName: false },
38
- },
39
- {
40
- class: "decorator",
41
- languages: ["ts"],
42
- typescriptVisibility: { private: true, privateName: false },
43
- },
44
- {
45
- class: "injection-container",
46
- languages: ["ts"],
47
- typescriptVisibility: { private: true, privateName: false },
48
- },
49
- {
50
- class: "framework-lifecycle",
51
- languages: ["ts"],
52
- typescriptVisibility: { private: true, privateName: false },
53
- },
54
- {
55
- class: "serialization-contract",
56
- languages: ["ts"],
57
- typescriptVisibility: { private: true, privateName: false },
58
- },
59
- ];
1
+ import { EXEMPTION_CLASSES } from "./contract/contract.generated.js";
60
2
  function runsOnTS(row) {
61
3
  const languages = row.languages;
62
4
  return languages.includes("ts");
@@ -69,7 +11,5 @@ const VISIBILITY = new Map(TS_ROWS.map((row) => [row.class, row.typescriptVisibi
69
11
  export function typescriptVisibilityOf(exemptionClass) {
70
12
  return VISIBILITY.get(exemptionClass) ?? { private: false, privateName: false };
71
13
  }
72
- /** Whether one name is a class of the vocabulary, whichever language it runs on. */
73
- export function isExemptionClass(name) {
74
- return EXEMPTION_CLASSES.some((row) => row.class === name);
75
- }
14
+ /** Every class of the vocabulary, whichever language it runs on, in the vocabulary's order. */
15
+ export const EXEMPTION_CLASS_NAMES = EXEMPTION_CLASSES.map((row) => row.class);
@@ -6,26 +6,18 @@
6
6
  * which records held something back. It names no class's detector: the composition
7
7
  * hands it the table of the classes it has.
8
8
  */
9
- import { ConfigError } from "./config.js";
10
- import { EXEMPTION_CLASSES, isExemptionClass, TS_EXEMPTION_CLASSES, typescriptVisibilityOf, } from "./exempt-classes.js";
9
+ import { EXEMPTION_CLASSES } from "./contract/contract.generated.js";
10
+ import { TS_EXEMPTION_CLASSES, typescriptVisibilityOf, } from "./exempt-classes.js";
11
11
  import { byPosition, positionKey } from "./position.js";
12
12
  import { Unanswerable } from "./query.js";
13
13
  import { sweep } from "./sweep.js";
14
+ import { bytewise } from "./text/compare.js";
14
15
  /**
15
- * The classes `exemptions.disabled` switches off. A name is refused unless the
16
- * vocabulary holds it; a class that runs on another language only is admitted and
17
- * switches nothing off here, so one configuration serves every analyzer.
16
+ * The classes `exemptions.disabled` switches off. A class that runs on another language
17
+ * only switches nothing off here, so one configuration serves every analyzer.
18
18
  */
19
19
  export function disabledClasses(config) {
20
- const disabled = new Set();
21
- for (const name of config.exemptionsDisabled) {
22
- if (!isExemptionClass(name)) {
23
- const known = EXEMPTION_CLASSES.map((row) => row.class).join(", ");
24
- throw new ConfigError("malformed", "exemptions.disabled", `exemptions.disabled: ${JSON.stringify(name)} is not an exemption class: the classes are ${known}`);
25
- }
26
- disabled.add(name);
27
- }
28
- return disabled;
20
+ return new Set(config.exemptionsDisabled);
29
21
  }
30
22
  /**
31
23
  * Whether a class whose row states `allowed` may retain a member of one visibility. A
@@ -40,20 +32,8 @@ function retainable(allowed, visibility) {
40
32
  }
41
33
  return true;
42
34
  }
43
- /**
44
- * The kinds of declaration each class can hold back, which a detector that stops holds
45
- * back whole. A class with no row here holds back every declaration but a file.
46
- */
47
- const RETAINABLE = new Map([
48
- ["interface-satisfaction", new Set(["method", "class-member"])],
49
- ["enum-group", new Set(["enum-member"])],
50
- ["injection-container", new Set(["method", "class-member"])],
51
- ["framework-lifecycle", new Set(["method", "class-member"])],
52
- [
53
- "serialization-contract",
54
- new Set(["method", "class-member", "interface-method", "type-member"]),
55
- ],
56
- ]);
35
+ /** The kinds of declaration each class can hold back, which a detector that stops holds back whole. */
36
+ const RETAINABLE = new Map(EXEMPTION_CLASSES.flatMap((row) => "ts" in row.retainsKinds ? [[row.class, new Set(row.retainsKinds.ts)]] : []));
57
37
  /** The detail of a record a detector that could not finish holds. */
58
38
  const UNANSWERED_DETAIL = "kept live by a question the checker did not answer";
59
39
  /** Whether one record is held by a detector that could not finish. */
@@ -76,7 +56,7 @@ function detected(exemptionClass, detect, input) {
76
56
  }
77
57
  const kinds = RETAINABLE.get(exemptionClass);
78
58
  return input.held.symbols
79
- .filter((symbol) => (kinds === undefined ? symbol.kind !== "file" : kinds.has(symbol.kind)))
59
+ .filter((symbol) => kinds?.has(symbol.kind) === true)
80
60
  .map((symbol) => ({ id: symbol.id, detail: UNANSWERED_DETAIL, site: symbol.position }));
81
61
  }
82
62
  }
@@ -133,20 +113,13 @@ export function heldRecords(records, holding) {
133
113
  }
134
114
  return { records: exemptionsOf(found), inTestFiles };
135
115
  }
136
- /** Two strings ordered bytewise. */
137
- function compare(a, b) {
138
- if (a === b) {
139
- return 0;
140
- }
141
- return a < b ? -1 : 1;
142
- }
143
116
  /**
144
117
  * Records as a run holds them: ordered by site, then class, then declaration, and one
145
118
  * per declaration, class, detail and file it holds while live, the one at the first
146
119
  * site. A fact found at many sites, or by several projects, is one record.
147
120
  */
148
121
  export function exemptionsOf(found) {
149
- const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) || compare(a.class, b.class) || compare(a.id, b.id));
122
+ const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) || bytewise(a.class, b.class) || bytewise(a.id, b.id));
150
123
  const seen = new Set();
151
124
  return ordered.filter((record) => {
152
125
  const fact = [record.id, record.class, record.detail, record.whileLive ?? ""].join("\u0000");
@@ -197,8 +170,8 @@ export function retainedIn(matrix, input, writeOnly = new Set()) {
197
170
  return matrix.union.symbols.flatMap((symbol) => held.get(symbol.id) ?? []);
198
171
  }
199
172
  /**
200
- * One line per held-back declaration, in site order: its reference, then each record's
201
- * class, site and detail, all separated by tabs.
173
+ * One line per held-back declaration, in site order: its reference and position, then
174
+ * each record's class, site and detail, all separated by tabs.
202
175
  */
203
176
  export function retainedLines(symbols, retained) {
204
177
  const held = byDeclaration(retained);
@@ -212,6 +185,6 @@ export function retainedLines(symbols, retained) {
212
185
  positionKey(record.site),
213
186
  record.detail,
214
187
  ]);
215
- return [[symbol.ref, ...fields].join("\t")];
188
+ return [[symbol.ref, positionKey(symbol.position), ...fields].join("\t")];
216
189
  });
217
190
  }
@@ -3,10 +3,10 @@
3
3
  * file below the target root, whether or not a project includes it, and the files a
4
4
  * program holds by inclusion rather than by an import.
5
5
  */
6
- import { IGNORED_DIRECTORIES } from "./discover.js";
7
6
  import { packageScope } from "./inventory.js";
8
7
  import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
9
8
  import { renderRef } from "./ref.js";
9
+ import { filesBelow } from "./fs/tree.js";
10
10
  /** A file name the compiler reads as a source file. */
11
11
  const SOURCE_FILE = /\.(?:ts|tsx|mts|cts)$/u;
12
12
  /** A file name the compiler reads as a declaration file, which no build emits code from. */
@@ -30,30 +30,7 @@ function lastLine(text) {
30
30
  * that cannot be read is skipped, as discovery skips it.
31
31
  */
32
32
  export function sourcePaths(host, targetRoot) {
33
- const found = [];
34
- const pending = [""];
35
- for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
36
- let entries;
37
- try {
38
- entries = host.readDirectory(joinPath(targetRoot, dir));
39
- }
40
- catch {
41
- continue;
42
- }
43
- for (const entry of entries) {
44
- const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
45
- if (entry.directory) {
46
- if (!IGNORED_DIRECTORIES.has(entry.name)) {
47
- pending.push(path);
48
- }
49
- continue;
50
- }
51
- if (SOURCE_FILE.test(entry.name) && !DECLARATION_FILE.test(entry.name)) {
52
- found.push(path);
53
- }
54
- }
55
- }
56
- return found.sort((a, b) => (a < b ? -1 : 1));
33
+ return filesBelow(host, targetRoot, (name) => SOURCE_FILE.test(name) && !DECLARATION_FILE.test(name));
57
34
  }
58
35
  /**
59
36
  * Every source file below the target root, in path order. A file that cannot be read is
@@ -3,29 +3,20 @@
3
3
  * the code's row of the issue-kind vocabulary, and the dials that withhold a finding
4
4
  * together with every finding that falls with a root they withhold.
5
5
  */
6
+ import { CODE, FAMILY_KEY_LENGTH, KIND_ROWS, } from "../contract/contract.generated.js";
6
7
  import { componentId } from "../components.js";
7
8
  import { deadComponents } from "../matrix.js";
8
9
  import { OUTSIDE } from "../graph.js";
9
10
  import { KINDS } from "../kinds.js";
10
11
  import { byPosition, positionKey } from "../position.js";
11
12
  import { declarationKey } from "../ref.js";
12
- import { FAMILY_KEY_LENGTH } from "../resolve.js";
13
13
  import { isPartSubject, isRowSubject } from "../suppress.js";
14
- /** The unused-file code, whose deletion removes every use its file writes. */
15
- const FILE_NEVER_IMPORTED = "DS1502";
16
14
  /**
17
- * The codes whose subject the run that counts test references holds live, as
18
- * finding.schema.json lists them, so a test file's candidate reported under one carries
19
- * none of a dead declaration's sweep facts.
15
+ * The codes whose subject the run that counts test references holds live, so a test
16
+ * file's candidate reported under one carries none of a dead declaration's sweep facts.
20
17
  */
21
- const LIVE_SUBJECT = new Set([
22
- "DS1101",
23
- "DS1102",
24
- "DS1104",
25
- "DS1204",
26
- "DS1301",
27
- ]);
28
- /** The fixability grammar/analysis.md "Fixability" gives a finding about a held declaration. */
18
+ const LIVE_SUBJECT = new Set(KIND_ROWS.filter((row) => row.holdsLive).map((row) => row.code));
19
+ /** The fixability grammar/analysis/fixability.md gives a finding about a held declaration. */
29
20
  const MANUAL = "manual";
30
21
  /** The member kinds an object property can declare, which an `in` expression tests by name. */
31
22
  const IN_TESTED = new Set([
@@ -147,7 +138,7 @@ function testCeilings(input, classOf) {
147
138
  }
148
139
  /**
149
140
  * The subjects dead only because production code only tests use is: each declaration the
150
- * sweep marks test-used, each `DS1502` file of such code, and each test of dead code every
141
+ * sweep marks test-used, each `DS1602` file of such code, and each test of dead code every
151
142
  * target of which is one. Nothing they hold references a dead declaration outside them, so
152
143
  * the rest of a component they share is reported without them.
153
144
  */
@@ -224,13 +215,13 @@ function membersListed(swept) {
224
215
  .sort((a, b) => byPosition(a.position, b.position));
225
216
  }
226
217
  /**
227
- * The run's dead components with each file a `DS1502` finding reports dead among them, so
218
+ * The run's dead components with each file a `DS1602` finding reports dead among them, so
228
219
  * the file's declarations fall with it, and with the components each subject that is no
229
220
  * declaration falls with joined into one.
230
221
  */
231
222
  function runComponents(swept, findings) {
232
223
  const union = swept.matrix.union;
233
- const reported = new Set(findings.filter((one) => one.code === FILE_NEVER_IMPORTED).map((one) => one.position.path));
224
+ const reported = new Set(findings.filter((one) => one.code === CODE.fileNeverImported).map((one) => one.position.path));
234
225
  const files = new Map(union.symbols.flatMap((symbol, at) => symbol.kind === "file" && reported.has(symbol.position.path) ? [[at, symbol]] : []));
235
226
  const computed = files.size === 0
236
227
  ? swept.sweep.components
@@ -330,7 +321,7 @@ export function completed(input, findings, kinds = KINDS) {
330
321
  : candidates.get(judged.id);
331
322
  const fallsWith = finding.fallsWith?.[0];
332
323
  const joined = fallsWith === undefined ? undefined : componentOf(fallsWith);
333
- const file = finding.code === FILE_NEVER_IMPORTED ? files.get(finding.position.path) : undefined;
324
+ const file = finding.code === CODE.fileNeverImported ? files.get(finding.position.path) : undefined;
334
325
  const found = joined !== undefined
335
326
  ? { ...joined, root: false }
336
327
  : file !== undefined
@@ -376,7 +367,7 @@ export function completed(input, findings, kinds = KINDS) {
376
367
  const ownClass = judged === undefined ? (finding.reachabilityClass ?? "certain") : classOf(judged.id);
377
368
  const used = judged !== undefined
378
369
  ? candidate !== undefined && testUsed.declarations.has(judged.id)
379
- : finding.code === FILE_NEVER_IMPORTED && testUsed.files.has(finding.position.path);
370
+ : finding.code === CODE.fileNeverImported && testUsed.files.has(finding.position.path);
380
371
  const reachabilityClass = used && candidate?.testOfDeadCode !== true ? "possible" : ownClass;
381
372
  const configurations = candidate?.configurations ??
382
373
  (judged === undefined && !part
@@ -396,7 +387,7 @@ export function completed(input, findings, kinds = KINDS) {
396
387
  ? undefined
397
388
  : candidate !== undefined
398
389
  ? input.dependencies.lastUses.get(candidate.id)
399
- : finding.code === FILE_NEVER_IMPORTED
390
+ : finding.code === CODE.fileNeverImported
400
391
  ? input.dependencies.fileLastUses.get(finding.position.path)
401
392
  : undefined;
402
393
  return {
@@ -1,8 +1,7 @@
1
+ import { CODE } from "../contract/contract.generated.js";
1
2
  import { declarationKey } from "../dependencies.js";
2
3
  import { renderRef } from "../ref.js";
3
4
  import { nonCodeArtifacts } from "./non-code-artifacts.js";
4
- const UNUSED_DEPENDENCY = "DS1601";
5
- const FILE_NEVER_IMPORTED = "DS1502";
6
5
  const MESSAGES = {
7
6
  dependency: "no import in the target needs the declared dependency",
8
7
  "dev-dependency": "no import in the target needs the declared development dependency",
@@ -25,7 +24,7 @@ function deadCode(input) {
25
24
  const reachedOnly = new Set(swept.sweep.candidates.filter((one) => one.relation === "reachability").map((one) => one.id));
26
25
  for (const finding of nonCodeArtifacts(input)) {
27
26
  const id = files.get(finding.position.path);
28
- if (finding.code === FILE_NEVER_IMPORTED && id !== undefined) {
27
+ if (finding.code === CODE.fileNeverImported && id !== undefined) {
29
28
  dead.add(id);
30
29
  if (finding.unreachableOnly === true) {
31
30
  reachedOnly.add(id);
@@ -57,7 +56,7 @@ function unusedDependencies(input, dependencies) {
57
56
  const atPossible = unbuilt || users?.some((id) => published.has(id)) === true;
58
57
  return [
59
58
  {
60
- code: UNUSED_DEPENDENCY,
59
+ code: CODE.unusedDependency,
61
60
  position: dependency.position,
62
61
  symbol: {
63
62
  ref: renderRef(dependency.manifest, {
@@ -80,5 +79,5 @@ function unusedDependencies(input, dependencies) {
80
79
  ];
81
80
  });
82
81
  }
83
- /** The findings of the dependencies-and-module-machinery family, `DS1600` to `DS1699`. */
82
+ /** The findings of the dependencies family, `DS1700` to `DS1799`. */
84
83
  export const dependenciesAndModuleMachinery = (input) => unusedDependencies(input, input.dependencies);
@@ -12,7 +12,7 @@ import { readsAndWrites } from "./reads-and-writes.js";
12
12
  import { selfCheck } from "./self-check.js";
13
13
  import { fallenMembers, unusedDeclarations } from "./unused-declarations.js";
14
14
  import { visibilityNarrowing } from "./visibility-narrowing.js";
15
- /** Every kind family this analyzer reports, each by its emitter, in code order. */
15
+ /** Every emitter this analyzer runs, by the name its tests select it by. */
16
16
  export const EMITTERS = new Map([
17
17
  ["unused-declarations", unusedDeclarations],
18
18
  ["visibility-narrowing", visibilityNarrowing],
@@ -51,7 +51,7 @@ function markupKeys(input) {
51
51
  .filter((symbol) => live.has(symbol.parent))
52
52
  .map((symbol) => declarationKey(symbol.ref, symbol.position.path)));
53
53
  }
54
- /** Every family's findings over one sweep, and those of any further emitter, completed by one step. */
54
+ /** Every emitter's findings over one sweep, and those of any further emitter, completed by one step. */
55
55
  function completedOver(input, ...more) {
56
56
  return completed(input, [...EMITTERS.values(), ...more].flatMap((emit) => emit(input)));
57
57
  }
@@ -70,8 +70,8 @@ function dialsOver(input, found, suppressed, unmarked) {
70
70
  }
71
71
  /**
72
72
  * Every finding of one run swept under its suppressions' marks, decided in one pass: each
73
- * family's findings completed by one step, so every component number is minted once, the
74
- * finding each record claims withheld, the rest withheld by the dials, so a root one family
73
+ * emitter's findings completed by one step, so every component number is minted once, the
74
+ * finding each record claims withheld, the rest withheld by the dials, so a root one emitter
75
75
  * reports withholds what another reports in its component, and every declared edge
76
76
  * evaluated over what remains, so a pending finding is in its evaluation alone.
77
77
  */
@@ -1,6 +1,5 @@
1
+ import { CODE } from "../contract/contract.generated.js";
1
2
  import { byPosition } from "../position.js";
2
- const UNUSED_INTERFACE = "DS1201";
3
- const UNCALLED_INTERFACE_METHOD = "DS1203";
4
3
  /** What each finding says, by what the sweep found about the subject's references. */
5
4
  const UNUSED_MESSAGE = "nothing in the target names the interface as a type";
6
5
  const UNUSED_DEAD_MESSAGE = "the interface is named as a type only from declarations that are themselves dead";
@@ -45,7 +44,7 @@ function findingOf(code, symbol, message, implementations) {
45
44
  }
46
45
  /**
47
46
  * The findings of the interfaces family, `DS1200` to `DS1299`: `DS1201`, a dead
48
- * interface, whose members fall with it unreported; and `DS1203`, a method of a live
47
+ * interface, whose members fall with it unreported; and `DS1202`, a method of a live
49
48
  * interface no reference names, unless it is a marker, a method whose every
50
49
  * implementation has an empty body. A test file's declaration is neither kind's subject,
51
50
  * because a production sweep counts none of the references it can have, save an interface
@@ -60,7 +59,7 @@ export const interfaces = (input) => {
60
59
  continue;
61
60
  }
62
61
  if (symbol.kind === "interface") {
63
- found.push(findingOf(UNUSED_INTERFACE, symbol, unusedMessage(candidate), run.implementationsOf(candidate.id)));
62
+ found.push(findingOf(CODE.unusedInterface, symbol, unusedMessage(candidate), run.implementationsOf(candidate.id)));
64
63
  continue;
65
64
  }
66
65
  if (symbol.kind !== "interface-method" ||
@@ -69,7 +68,7 @@ export const interfaces = (input) => {
69
68
  marker(input, candidate.id)) {
70
69
  continue;
71
70
  }
72
- found.push(findingOf(UNCALLED_INTERFACE_METHOD, symbol, UNCALLED_MESSAGE, run.implementationsOf(symbol.parent)));
71
+ found.push(findingOf(CODE.uncalledInterfaceMethod, symbol, UNCALLED_MESSAGE, run.implementationsOf(symbol.parent)));
73
72
  }
74
73
  return found;
75
74
  };
@@ -1,6 +1,7 @@
1
+ import { CODE } from "../contract/contract.generated.js";
1
2
  import { KINDS } from "../kinds.js";
2
3
  import { parameterRef } from "../ref.js";
3
- import { UNUSED_PARAMETER, UNUSED_RESULT } from "../intra-function-parts.js";
4
+ import {} from "../intra-function-parts.js";
4
5
  import { reachedWithTheTests } from "../sweep.js";
5
6
  /** A list of rules as a sentence names them. */
6
7
  function spelled(rules) {
@@ -62,9 +63,9 @@ function reported(input, part, callersUnknown) {
62
63
  }
63
64
  const { free, freeButValued, discardedEverywhere } = input.intraFunction;
64
65
  switch (part.code) {
65
- case UNUSED_PARAMETER:
66
+ case CODE.unusedParameter:
66
67
  return parameterVerdict(part, free, freeButValued) !== undefined;
67
- case UNUSED_RESULT:
68
+ case CODE.unusedResult:
68
69
  return (free.has(part.declaration) &&
69
70
  discardedEverywhere.has(part.declaration) &&
70
71
  !callersUnknown.has(part.declaration));
@@ -73,7 +74,7 @@ function reported(input, part, callersUnknown) {
73
74
  }
74
75
  }
75
76
  /**
76
- * The findings of the intra-function family, `DS1800` to `DS1899`, each about a part of
77
+ * The findings of the inside-functions family, `DS1401` to `DS1406`, each about a part of
77
78
  * the declaration its reference names, at the part's own position, whatever the sweep
78
79
  * decided about a production declaration. A part of test code is reported only where the
79
80
  * run that counts test references reaches its declaration and that is no test of dead
@@ -114,11 +115,11 @@ export const intraFunction = (input) => {
114
115
  const found = [];
115
116
  for (const part of input.intraFunction.parts) {
116
117
  const declaration = byId.get(part.declaration);
117
- const overlap = KINDS.get(part.code)?.overlap ?? [];
118
+ const overlap = KINDS.get(part.code)?.overlap?.ts ?? [];
118
119
  if (declaration === undefined || !judged(declaration.id) || !reported(input, part, unknown)) {
119
120
  continue;
120
121
  }
121
- const verdict = part.code === UNUSED_PARAMETER
122
+ const verdict = part.code === CODE.unusedParameter
122
123
  ? parameterVerdict(part, input.intraFunction.free, input.intraFunction.freeButValued)
123
124
  : undefined;
124
125
  const message = verdict?.fix === "rename"
@@ -128,7 +129,7 @@ export const intraFunction = (input) => {
128
129
  code: part.code,
129
130
  position: part.position,
130
131
  symbol: {
131
- ...(part.code === UNUSED_PARAMETER
132
+ ...(part.code === CODE.unusedParameter
132
133
  ? { ref: parameterRef(declaration.ref, part.name), holder: declaration.ref }
133
134
  : { ref: declaration.ref }),
134
135
  kind: part.kind,
@@ -1,5 +1,4 @@
1
- const FILE_NEVER_BUILT = "DS1501";
2
- const FILE_NEVER_IMPORTED = "DS1502";
1
+ import { CODE } from "../contract/contract.generated.js";
3
2
  /** The subject kind of a finding about a source file, from the finding schema's vocabulary. */
4
3
  const FILE_SUBJECT = "file";
5
4
  /** A file's subject starts at its first character. */
@@ -40,7 +39,7 @@ function fileNeverBuilt({ config, swept, files }) {
40
39
  const message = `no project of the build matrix includes this file (${swept.matrix.configurations.join(", ")})`;
41
40
  return files.tree
42
41
  .filter((file) => !built.has(file.path))
43
- .map((file) => fileFinding(FILE_NEVER_BUILT, file, message));
42
+ .map((file) => fileFinding(CODE.fileNeverBuilt, file, message));
44
43
  }
45
44
  /**
46
45
  * The paths of the files a reference from a live declaration of another file reaches, a
@@ -125,13 +124,13 @@ function fileNeverImported(input) {
125
124
  const file = { path: symbol.position.path, ref: symbol.ref, endLine: symbol.endLine };
126
125
  return referenced.has(symbol.position.path)
127
126
  ? {
128
- ...fileFinding(FILE_NEVER_IMPORTED, file, "only code that is itself dead imports this file, and no root names it or a declaration in it"),
127
+ ...fileFinding(CODE.fileNeverImported, file, "only code that is itself dead imports this file, and no root names it or a declaration in it"),
129
128
  unreachableOnly: true,
130
129
  }
131
- : fileFinding(FILE_NEVER_IMPORTED, file, "no import reaches this file and no root names it or a declaration in it");
130
+ : fileFinding(CODE.fileNeverImported, file, "no import reaches this file and no root names it or a declaration in it");
132
131
  });
133
132
  }
134
- /** The findings of the non-code-artifacts family, `DS1500` to `DS1599`. */
133
+ /** The findings of the files family, `DS1600` to `DS1699`. */
135
134
  export const nonCodeArtifacts = (input) => [
136
135
  ...fileNeverBuilt(input),
137
136
  ...fileNeverImported(input),
@@ -1,6 +1,8 @@
1
+ import { CODE } from "../contract/contract.generated.js";
1
2
  import { positionKey } from "../position.js";
2
3
  import { declarationKey } from "../ref.js";
3
- /** The containers whose type parameters `DS1303` reports: a function, a class method and a type alias. */
4
+ import { bytewise } from "../text/compare.js";
5
+ /** The containers whose type parameters `DS1006` reports: a function, a class method and a type alias. */
4
6
  const SIGNATURES = new Set(["function", "method", "type"]);
5
7
  function indexed(input) {
6
8
  const { matrix, sweep } = input.swept;
@@ -75,7 +77,7 @@ function writeOnlyMessage(run, namedBy, symbol, held) {
75
77
  : "never read";
76
78
  const cascade = [
77
79
  ...parametersOnlyNamedBy(run, namedBy, symbol).map((name) => `type parameter ${name}`),
78
- ...[...held.alone].sort().map((name) => `import ${name}`),
80
+ ...[...held.alone].sort(bytewise).map((name) => `import ${name}`),
79
81
  ];
80
82
  const word = SUBJECT_WORDS[symbol.kind] ?? "member";
81
83
  const message = `${word} ${symbol.name} is written ${writes} and ${read}`;
@@ -133,7 +135,7 @@ function writeOnlySymbols(run) {
133
135
  run.candidates.has(symbol.id)) {
134
136
  continue;
135
137
  }
136
- found.push(findingAbout(symbol, "DS1301", writeOnlyMessage(run, namedBy, symbol, held), {
138
+ found.push(findingAbout(symbol, CODE.writeOnlySymbol, writeOnlyMessage(run, namedBy, symbol, held), {
137
139
  writePositions: held.writes,
138
140
  }));
139
141
  }
@@ -157,15 +159,15 @@ function unnamed(run, kind) {
157
159
  });
158
160
  }
159
161
  /**
160
- * `DS1302`: a member of an enum declaration that nothing names. A member an enum-group
162
+ * `DS1005`: a member of an enum declaration that nothing names. A member an enum-group
161
163
  * record holds back is no candidate, so a member a conversion can produce never reaches
162
164
  * this kind.
163
165
  */
164
166
  function unusedEnumMembers(run) {
165
- return unnamed(run, "enum-member").map((symbol) => findingAbout(symbol, "DS1302", `enum member ${symbol.name} is named nowhere`));
167
+ return unnamed(run, "enum-member").map((symbol) => findingAbout(symbol, CODE.unusedEnumMember, `enum member ${symbol.name} is named nowhere`));
166
168
  }
167
169
  /**
168
- * `DS1303`: a type parameter of a function, a class method or a type alias that neither
170
+ * `DS1006`: a type parameter of a function, a class method or a type alias that neither
169
171
  * the declaration's signature nor its body names. An alias names no type of its own, so
170
172
  * a parameter its type never names makes no two instantiations distinct. One of a class,
171
173
  * an interface or a method signature is never reported: a class or an interface merges
@@ -174,7 +176,7 @@ function unusedEnumMembers(run) {
174
176
  function unusedTypeParameters(run) {
175
177
  return unnamed(run, "type-parameter")
176
178
  .filter((symbol) => SIGNATURES.has(run.symbols.get(symbol.parent)?.kind ?? ""))
177
- .map((symbol) => findingAbout(symbol, "DS1303", `type parameter ${symbol.name} is named in neither its signature nor its body`));
179
+ .map((symbol) => findingAbout(symbol, CODE.unusedTypeParameter, `type parameter ${symbol.name} is named in neither its signature nor its body`));
178
180
  }
179
181
  /**
180
182
  * The keys of the declarations the write-only kind reports, whatever the
@@ -185,8 +187,8 @@ export function writeOnlyDeclarations(input) {
185
187
  return new Set(writeOnlySymbols(indexed(input)).map((finding) => declarationKey(finding.symbol.ref, finding.position.path)));
186
188
  }
187
189
  /**
188
- * The findings of the reads-and-writes family, `DS1300` to `DS1399`, in code order and
189
- * each code's in site order.
190
+ * The findings of the reads-and-writes kinds, `DS1301`, `DS1005` and `DS1006`, in that
191
+ * order and each code's in site order.
190
192
  */
191
193
  export const readsAndWrites = (input) => {
192
194
  const run = indexed(input);
@@ -1,10 +1,8 @@
1
+ import { CODE } from "../contract/contract.generated.js";
1
2
  import { REPOSITORY_DOCUMENT } from "../config.js";
2
3
  import { FIXED_SEVERITY_CODES } from "../kinds.js";
3
- import { isDependencyCode, isUnsuppressibleCode, STALE_SUPPRESSION, SUPPRESSION_WITHOUT_REASON, } from "../suppress.js";
4
- /** The code a configured root that matches nothing is reported under. */
5
- const UNMATCHED_ROOT = "DS1704";
6
- /** The code a configured declaration that names nothing is reported under. */
7
- const UNMATCHED_DECLARATION = "DS1706";
4
+ import { isDependencyCode, isUnsuppressibleCode, } from "../suppress.js";
5
+ import { bytewise } from "../text/compare.js";
8
6
  /** The subject kind of a finding about a configured declaration. */
9
7
  const DECLARATION_SUBJECT = "configured-declaration";
10
8
  /** The subject kind of a finding about a suppression record. */
@@ -63,7 +61,7 @@ function suppressionRef(symbol, path, files) {
63
61
  }
64
62
  /** One finding per refused suppression: the grammar's reason refusal and its scope refusal. */
65
63
  function refusedSuppressions(refusals, files) {
66
- return refusals.map((refused) => aboutSuppression(refused.reported, suppressionRef(refused.symbol, refused.path, files), refused.site, `${WRITTEN[refused.mechanism]} for ${refused.code} ${refused.reported === SUPPRESSION_WITHOUT_REASON
64
+ return refusals.map((refused) => aboutSuppression(refused.reported, suppressionRef(refused.symbol, refused.path, files), refused.site, `${WRITTEN[refused.mechanism]} for ${refused.code} ${refused.reported === CODE.suppressionWithoutReason
67
65
  ? "carries no reason"
68
66
  : "names a symbol and no path"}`, { mechanism: refused.mechanism, entry: writtenEntry(refused) }));
69
67
  }
@@ -83,10 +81,10 @@ export function staleSuppressions(stale, files) {
83
81
  bySite.set(key, held);
84
82
  }
85
83
  return [...bySite.values()].map(({ first, records }) => {
86
- const codes = [...new Set(records.map((one) => one.record.code))].sort();
84
+ const codes = [...new Set(records.map((one) => one.record.code))].sort(bytewise);
87
85
  const reports = [...new Set(records.flatMap((one) => one.reports))]
88
86
  .filter((code) => !codes.includes(code))
89
- .sort();
87
+ .sort(bytewise);
90
88
  const rows = codes.filter(isUnsuppressibleCode);
91
89
  const dependencies = first.mechanism === "inline" ? codes.filter(isDependencyCode) : [];
92
90
  const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
@@ -112,7 +110,7 @@ export function staleSuppressions(stale, files) {
112
110
  ? ""
113
111
  : `, and ${declared}`;
114
112
  return {
115
- code: STALE_SUPPRESSION,
113
+ code: CODE.staleSuppression,
116
114
  mechanism: first.mechanism,
117
115
  entry: {
118
116
  code: first.code,
@@ -141,7 +139,7 @@ function documentPosition(document) {
141
139
  */
142
140
  function unmatchedDeclarationFindings(unmatched) {
143
141
  return unmatched.map((one) => ({
144
- code: UNMATCHED_DECLARATION,
142
+ code: CODE.unmatchedConfiguredDeclaration,
145
143
  position: documentPosition(one.document),
146
144
  symbol: { ref: one.ref, kind: DECLARATION_SUBJECT, name: one.ref, sizeLines: 1 },
147
145
  message: `${one.key} entry names no declaration in any project`,
@@ -157,7 +155,7 @@ function unmatchedDeclarationFindings(unmatched) {
157
155
  export function unmatchedRootFindings(sources, document) {
158
156
  const position = documentPosition(document);
159
157
  return sources.map((source) => ({
160
- code: UNMATCHED_ROOT,
158
+ code: CODE.unmatchedRoot,
161
159
  position,
162
160
  symbol: { ref: source, kind: ROOT_SUBJECT, name: source, sizeLines: 1 },
163
161
  message: source.includes("*") || source.includes("?")
@@ -166,7 +164,7 @@ export function unmatchedRootFindings(sources, document) {
166
164
  }));
167
165
  }
168
166
  /**
169
- * The findings of the self-check family, `DS1700` to `DS1799`: a suppression refused for
167
+ * The findings of the configuration family, `DS9000` to `DS9099`: a suppression refused for
170
168
  * carrying no reason or naming no path, and a configured root or declaration that named
171
169
  * nothing. A
172
170
  * suppression that matched nothing is a record of the report's stale suppressions, which