@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,12 +1,10 @@
1
+ import { CODE } from "../contract/contract.generated.ts";
1
2
  import type { Finding, FindingPosition, PositionedSymbol } from "../finding.ts";
2
3
  import type { InventorySymbol } from "../inventory.ts";
3
4
  import { byPosition } from "../position.ts";
4
5
  import type { MatrixCandidate } from "../matrix.ts";
5
6
  import type { Emitter, EmitterInput } from "./emitter.ts";
6
7
 
7
- const UNUSED_INTERFACE = "DS1201";
8
- const UNCALLED_INTERFACE_METHOD = "DS1203";
9
-
10
8
  /** What each finding says, by what the sweep found about the subject's references. */
11
9
  const UNUSED_MESSAGE = "nothing in the target names the interface as a type";
12
10
  const UNUSED_DEAD_MESSAGE =
@@ -69,7 +67,7 @@ function findingOf(
69
67
 
70
68
  /**
71
69
  * The findings of the interfaces family, `DS1200` to `DS1299`: `DS1201`, a dead
72
- * interface, whose members fall with it unreported; and `DS1203`, a method of a live
70
+ * interface, whose members fall with it unreported; and `DS1202`, a method of a live
73
71
  * interface no reference names, unless it is a marker, a method whose every
74
72
  * implementation has an empty body. A test file's declaration is neither kind's subject,
75
73
  * because a production sweep counts none of the references it can have, save an interface
@@ -86,7 +84,7 @@ export const interfaces: Emitter = (input) => {
86
84
  if (symbol.kind === "interface") {
87
85
  found.push(
88
86
  findingOf(
89
- UNUSED_INTERFACE,
87
+ CODE.unusedInterface,
90
88
  symbol,
91
89
  unusedMessage(candidate),
92
90
  run.implementationsOf(candidate.id),
@@ -104,7 +102,7 @@ export const interfaces: Emitter = (input) => {
104
102
  }
105
103
  found.push(
106
104
  findingOf(
107
- UNCALLED_INTERFACE_METHOD,
105
+ CODE.uncalledInterfaceMethod,
108
106
  symbol,
109
107
  UNCALLED_MESSAGE,
110
108
  run.implementationsOf(symbol.parent),
@@ -1,8 +1,8 @@
1
- import type { Confidence } from "../config.ts";
1
+ import { CODE, type Confidence } from "../contract/contract.generated.ts";
2
2
  import type { Finding } from "../finding.ts";
3
3
  import { KINDS } from "../kinds.ts";
4
4
  import { parameterRef } from "../ref.ts";
5
- import { UNUSED_PARAMETER, UNUSED_RESULT, type PartFact } from "../intra-function-parts.ts";
5
+ import { type PartFact } from "../intra-function-parts.ts";
6
6
  import { reachedWithTheTests } from "../sweep.ts";
7
7
  import type { Emitter, EmitterInput } from "./emitter.ts";
8
8
 
@@ -85,9 +85,9 @@ function reported(
85
85
  }
86
86
  const { free, freeButValued, discardedEverywhere } = input.intraFunction;
87
87
  switch (part.code) {
88
- case UNUSED_PARAMETER:
88
+ case CODE.unusedParameter:
89
89
  return parameterVerdict(part, free, freeButValued) !== undefined;
90
- case UNUSED_RESULT:
90
+ case CODE.unusedResult:
91
91
  return (
92
92
  free.has(part.declaration) &&
93
93
  discardedEverywhere.has(part.declaration) &&
@@ -99,7 +99,7 @@ function reported(
99
99
  }
100
100
 
101
101
  /**
102
- * The findings of the intra-function family, `DS1800` to `DS1899`, each about a part of
102
+ * The findings of the inside-functions family, `DS1401` to `DS1406`, each about a part of
103
103
  * the declaration its reference names, at the part's own position, whatever the sweep
104
104
  * decided about a production declaration. A part of test code is reported only where the
105
105
  * run that counts test references reaches its declaration and that is no test of dead
@@ -146,12 +146,12 @@ export const intraFunction: Emitter = (input) => {
146
146
  const found: Finding[] = [];
147
147
  for (const part of input.intraFunction.parts) {
148
148
  const declaration = byId.get(part.declaration);
149
- const overlap = KINDS.get(part.code)?.overlap ?? [];
149
+ const overlap = KINDS.get(part.code)?.overlap?.ts ?? [];
150
150
  if (declaration === undefined || !judged(declaration.id) || !reported(input, part, unknown)) {
151
151
  continue;
152
152
  }
153
153
  const verdict =
154
- part.code === UNUSED_PARAMETER
154
+ part.code === CODE.unusedParameter
155
155
  ? parameterVerdict(part, input.intraFunction.free, input.intraFunction.freeButValued)
156
156
  : undefined;
157
157
  const message =
@@ -162,7 +162,7 @@ export const intraFunction: Emitter = (input) => {
162
162
  code: part.code,
163
163
  position: part.position,
164
164
  symbol: {
165
- ...(part.code === UNUSED_PARAMETER
165
+ ...(part.code === CODE.unusedParameter
166
166
  ? { ref: parameterRef(declaration.ref, part.name), holder: declaration.ref }
167
167
  : { ref: declaration.ref }),
168
168
  kind: part.kind,
@@ -1,10 +1,8 @@
1
+ import { CODE } from "../contract/contract.generated.ts";
1
2
  import type { Config } from "../config.ts";
2
3
  import type { Finding } from "../finding.ts";
3
4
  import type { Emitter, EmitterInput } from "./emitter.ts";
4
5
 
5
- const FILE_NEVER_BUILT = "DS1501";
6
- const FILE_NEVER_IMPORTED = "DS1502";
7
-
8
6
  /** The subject kind of a finding about a source file, from the finding schema's vocabulary. */
9
7
  const FILE_SUBJECT = "file";
10
8
 
@@ -57,7 +55,7 @@ function fileNeverBuilt({ config, swept, files }: EmitterInput): readonly Findin
57
55
  const message = `no project of the build matrix includes this file (${swept.matrix.configurations.join(", ")})`;
58
56
  return files.tree
59
57
  .filter((file) => !built.has(file.path))
60
- .map((file) => fileFinding(FILE_NEVER_BUILT, file, message));
58
+ .map((file) => fileFinding(CODE.fileNeverBuilt, file, message));
61
59
  }
62
60
 
63
61
  /** The files of the run, each with whether a reference written in another file reaches it. */
@@ -152,21 +150,21 @@ function fileNeverImported(input: EmitterInput): readonly Finding[] {
152
150
  return referenced.has(symbol.position.path)
153
151
  ? {
154
152
  ...fileFinding(
155
- FILE_NEVER_IMPORTED,
153
+ CODE.fileNeverImported,
156
154
  file,
157
155
  "only code that is itself dead imports this file, and no root names it or a declaration in it",
158
156
  ),
159
157
  unreachableOnly: true as const,
160
158
  }
161
159
  : fileFinding(
162
- FILE_NEVER_IMPORTED,
160
+ CODE.fileNeverImported,
163
161
  file,
164
162
  "no import reaches this file and no root names it or a declaration in it",
165
163
  );
166
164
  });
167
165
  }
168
166
 
169
- /** The findings of the non-code-artifacts family, `DS1500` to `DS1599`. */
167
+ /** The findings of the files family, `DS1600` to `DS1699`. */
170
168
  export const nonCodeArtifacts: Emitter = (input) => [
171
169
  ...fileNeverBuilt(input),
172
170
  ...fileNeverImported(input),
@@ -1,17 +1,20 @@
1
+ import { CODE } from "../contract/contract.generated.ts";
1
2
  import type { Finding, FindingDetails, FindingPosition } from "../finding.ts";
2
3
  import type { InventorySymbol } from "../inventory.ts";
3
4
  import type { MatrixCandidate } from "../matrix.ts";
4
5
  import { positionKey } from "../position.ts";
5
6
  import { declarationKey } from "../ref.ts";
6
7
  import type { Emitter, EmitterInput } from "./emitter.ts";
8
+ import { bytewise } from "../text/compare.ts";
7
9
 
8
- /** The codes of this family. */
9
- type ReadsAndWritesCode = "DS1301" | "DS1302" | "DS1303";
10
+ /** The codes this emitter reports. */
11
+ type ReadsAndWritesCode =
12
+ typeof CODE.writeOnlySymbol | typeof CODE.unusedEnumMember | typeof CODE.unusedTypeParameter;
10
13
 
11
- /** The containers whose type parameters `DS1303` reports: a function, a class method and a type alias. */
14
+ /** The containers whose type parameters `DS1006` reports: a function, a class method and a type alias. */
12
15
  const SIGNATURES: ReadonlySet<string> = new Set(["function", "method", "type"]);
13
16
 
14
- /** What the family reads of one run, indexed by declaration. */
17
+ /** What this emitter reads of one run, indexed by declaration. */
15
18
  interface Run {
16
19
  readonly input: EmitterInput;
17
20
  readonly symbols: ReadonlyMap<string, InventorySymbol>;
@@ -121,7 +124,7 @@ function writeOnlyMessage(
121
124
  : "never read";
122
125
  const cascade = [
123
126
  ...parametersOnlyNamedBy(run, namedBy, symbol).map((name) => `type parameter ${name}`),
124
- ...[...held.alone].sort().map((name) => `import ${name}`),
127
+ ...[...held.alone].sort(bytewise).map((name) => `import ${name}`),
125
128
  ];
126
129
  const word = SUBJECT_WORDS[symbol.kind] ?? "member";
127
130
  const message = `${word} ${symbol.name} is written ${writes} and ${read}`;
@@ -185,7 +188,7 @@ function writeOnlySymbols(run: Run): readonly Finding[] {
185
188
  continue;
186
189
  }
187
190
  found.push(
188
- findingAbout(symbol, "DS1301", writeOnlyMessage(run, namedBy, symbol, held), {
191
+ findingAbout(symbol, CODE.writeOnlySymbol, writeOnlyMessage(run, namedBy, symbol, held), {
189
192
  writePositions: held.writes,
190
193
  }),
191
194
  );
@@ -214,18 +217,18 @@ function unnamed(run: Run, kind: InventorySymbol["kind"]): readonly InventorySym
214
217
  }
215
218
 
216
219
  /**
217
- * `DS1302`: a member of an enum declaration that nothing names. A member an enum-group
220
+ * `DS1005`: a member of an enum declaration that nothing names. A member an enum-group
218
221
  * record holds back is no candidate, so a member a conversion can produce never reaches
219
222
  * this kind.
220
223
  */
221
224
  function unusedEnumMembers(run: Run): readonly Finding[] {
222
225
  return unnamed(run, "enum-member").map((symbol) =>
223
- findingAbout(symbol, "DS1302", `enum member ${symbol.name} is named nowhere`),
226
+ findingAbout(symbol, CODE.unusedEnumMember, `enum member ${symbol.name} is named nowhere`),
224
227
  );
225
228
  }
226
229
 
227
230
  /**
228
- * `DS1303`: a type parameter of a function, a class method or a type alias that neither
231
+ * `DS1006`: a type parameter of a function, a class method or a type alias that neither
229
232
  * the declaration's signature nor its body names. An alias names no type of its own, so
230
233
  * a parameter its type never names makes no two instantiations distinct. One of a class,
231
234
  * an interface or a method signature is never reported: a class or an interface merges
@@ -237,7 +240,7 @@ function unusedTypeParameters(run: Run): readonly Finding[] {
237
240
  .map((symbol) =>
238
241
  findingAbout(
239
242
  symbol,
240
- "DS1303",
243
+ CODE.unusedTypeParameter,
241
244
  `type parameter ${symbol.name} is named in neither its signature nor its body`,
242
245
  ),
243
246
  );
@@ -257,8 +260,8 @@ export function writeOnlyDeclarations(input: EmitterInput): ReadonlySet<string>
257
260
  }
258
261
 
259
262
  /**
260
- * The findings of the reads-and-writes family, `DS1300` to `DS1399`, in code order and
261
- * each code's in site order.
263
+ * The findings of the reads-and-writes kinds, `DS1301`, `DS1005` and `DS1006`, in that
264
+ * order and each code's in site order.
262
265
  */
263
266
  export const readsAndWrites: Emitter = (input) => {
264
267
  const run = indexed(input);
@@ -1,3 +1,4 @@
1
+ import { CODE } from "../contract/contract.generated.ts";
1
2
  import { REPOSITORY_DOCUMENT } from "../config.ts";
2
3
  import type { Finding, FindingDetails, SuppressionEntry } from "../finding.ts";
3
4
  import type { InventorySymbol } from "../inventory.ts";
@@ -6,19 +7,12 @@ import type { Position } from "../position.ts";
6
7
  import {
7
8
  isDependencyCode,
8
9
  isUnsuppressibleCode,
9
- STALE_SUPPRESSION,
10
- SUPPRESSION_WITHOUT_REASON,
11
10
  type Mechanism,
12
11
  type Refusal,
13
12
  type SuppressionRecord,
14
13
  } from "../suppress.ts";
15
14
  import type { Emitter } from "./emitter.ts";
16
-
17
- /** The code a configured root that matches nothing is reported under. */
18
- const UNMATCHED_ROOT = "DS1704";
19
-
20
- /** The code a configured declaration that names nothing is reported under. */
21
- const UNMATCHED_DECLARATION = "DS1706";
15
+ import { bytewise } from "../text/compare.ts";
22
16
 
23
17
  /** The subject kind of a finding about a configured declaration. */
24
18
  const DECLARATION_SUBJECT = "configured-declaration";
@@ -39,7 +33,7 @@ export interface StaleRecord {
39
33
  readonly reports: readonly string[];
40
34
  }
41
35
 
42
- /** What the self-check family reads: the run's refused suppressions and its configured roots. */
36
+ /** What the configuration family reads: the run's refused suppressions and its configured roots. */
43
37
  export interface SelfCheckFacts {
44
38
  /** The suppressions the grammar refused with a finding, in reading order. */
45
39
  readonly refusals: readonly Refusal[];
@@ -135,7 +129,7 @@ function refusedSuppressions(refusals: readonly Refusal[], files: FileRefs): rea
135
129
  suppressionRef(refused.symbol, refused.path, files),
136
130
  refused.site,
137
131
  `${WRITTEN[refused.mechanism]} for ${refused.code} ${
138
- refused.reported === SUPPRESSION_WITHOUT_REASON
132
+ refused.reported === CODE.suppressionWithoutReason
139
133
  ? "carries no reason"
140
134
  : "names a symbol and no path"
141
135
  }`,
@@ -146,7 +140,7 @@ function refusedSuppressions(refusals: readonly Refusal[], files: FileRefs): rea
146
140
 
147
141
  /** One stale suppression as the report's stale-suppression record carries it. */
148
142
  export interface StaleSuppression {
149
- readonly code: typeof STALE_SUPPRESSION;
143
+ readonly code: typeof CODE.staleSuppression;
150
144
  readonly mechanism: Mechanism;
151
145
  readonly entry: {
152
146
  readonly code: string;
@@ -178,10 +172,10 @@ export function staleSuppressions(
178
172
  bySite.set(key, held);
179
173
  }
180
174
  return [...bySite.values()].map(({ first, records }) => {
181
- const codes = [...new Set(records.map((one) => one.record.code))].sort();
175
+ const codes = [...new Set(records.map((one) => one.record.code))].sort(bytewise);
182
176
  const reports = [...new Set(records.flatMap((one) => one.reports))]
183
177
  .filter((code) => !codes.includes(code))
184
- .sort();
178
+ .sort(bytewise);
185
179
  const rows = codes.filter(isUnsuppressibleCode);
186
180
  const dependencies = first.mechanism === "inline" ? codes.filter(isDependencyCode) : [];
187
181
  const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
@@ -209,7 +203,7 @@ export function staleSuppressions(
209
203
  ? ""
210
204
  : `, and ${declared}`;
211
205
  return {
212
- code: STALE_SUPPRESSION,
206
+ code: CODE.staleSuppression,
213
207
  mechanism: first.mechanism,
214
208
  entry: {
215
209
  code: first.code,
@@ -242,7 +236,7 @@ function unmatchedDeclarationFindings(
242
236
  unmatched: readonly UnmatchedDeclaration[],
243
237
  ): readonly Finding[] {
244
238
  return unmatched.map((one) => ({
245
- code: UNMATCHED_DECLARATION,
239
+ code: CODE.unmatchedConfiguredDeclaration,
246
240
  position: documentPosition(one.document),
247
241
  symbol: { ref: one.ref, kind: DECLARATION_SUBJECT, name: one.ref, sizeLines: 1 },
248
242
  message: `${one.key} entry names no declaration in any project`,
@@ -262,7 +256,7 @@ export function unmatchedRootFindings(
262
256
  ): readonly Finding[] {
263
257
  const position = documentPosition(document);
264
258
  return sources.map((source) => ({
265
- code: UNMATCHED_ROOT,
259
+ code: CODE.unmatchedRoot,
266
260
  position,
267
261
  symbol: { ref: source, kind: ROOT_SUBJECT, name: source, sizeLines: 1 },
268
262
  message:
@@ -273,7 +267,7 @@ export function unmatchedRootFindings(
273
267
  }
274
268
 
275
269
  /**
276
- * The findings of the self-check family, `DS1700` to `DS1799`: a suppression refused for
270
+ * The findings of the configuration family, `DS9000` to `DS9099`: a suppression refused for
277
271
  * carrying no reason or naming no path, and a configured root or declaration that named
278
272
  * nothing. A
279
273
  * suppression that matched nothing is a record of the report's stale suppressions, which
@@ -1,3 +1,4 @@
1
+ import { CODE } from "../contract/contract.generated.ts";
1
2
  import type { Finding } from "../finding.ts";
2
3
  import type { InventorySymbol, SymbolKind } from "../inventory.ts";
3
4
  import type { MatrixCandidate } from "../matrix.ts";
@@ -14,18 +15,18 @@ const MEMBERS: ReadonlySet<SymbolKind> = new Set([
14
15
  ]);
15
16
 
16
17
  /**
17
- * The kinds of declaration no code of this family reports: an interface and a method an
18
+ * The kinds of declaration no code of this emitter reports: an interface and a method an
18
19
  * interface declares are the interface kinds' subjects, and a type parameter is the
19
20
  * subject of the unused-type-parameter kind or of none.
20
21
  */
21
- const NOT_THIS_FAMILY: ReadonlySet<SymbolKind> = new Set([
22
+ const NOT_REPORTED_HERE: ReadonlySet<SymbolKind> = new Set([
22
23
  "file",
23
24
  "interface",
24
25
  "interface-method",
25
26
  "type-parameter",
26
27
  ]);
27
28
 
28
- /** The reader's word for each kind of declaration a finding of the family names. */
29
+ /** The reader's word for each kind of declaration a finding of this emitter names. */
29
30
  const WORDS: Readonly<Record<SymbolKind, string>> = {
30
31
  file: "file",
31
32
  function: "function",
@@ -71,9 +72,9 @@ interface Judged {
71
72
  function codeOf(judged: Judged): string | undefined {
72
73
  const { candidate, symbol } = judged;
73
74
  if (candidate.testOfDeadCode) {
74
- return judged.testsUnseenAPI ? undefined : "DS1005";
75
+ return judged.testsUnseenAPI ? undefined : CODE.testOfDeadCode;
75
76
  }
76
- if ((judged.test && !candidate.unreferencedTest) || NOT_THIS_FAMILY.has(symbol.kind)) {
77
+ if ((judged.test && !candidate.unreferencedTest) || NOT_REPORTED_HERE.has(symbol.kind)) {
77
78
  return undefined;
78
79
  }
79
80
  const referenced = candidate.productionRefs + candidate.testRefs > 0;
@@ -84,33 +85,33 @@ function codeOf(judged: Judged): string | undefined {
84
85
  return unreferencedCode(symbol);
85
86
  }
86
87
  if (candidate.heldByTests === candidate.id) {
87
- return "DS1004";
88
+ return CODE.testOnlyUse;
88
89
  }
89
90
  if (candidate.productionRefs === 0 && judged.deprecated) {
90
- return "DS1006";
91
+ return CODE.deprecatedUnused;
91
92
  }
92
93
  if (
93
94
  candidate.testRefs > 0 &&
94
95
  (candidate.productionRefs === 0 ||
95
96
  (candidate.relation === "reachability" && candidate.keptByTests))
96
97
  ) {
97
- return "DS1004";
98
+ return CODE.testOnlyUse;
98
99
  }
99
100
  if (MEMBERS.has(symbol.kind)) {
100
- return "DS1003";
101
+ return CODE.unusedMember;
101
102
  }
102
103
  if (!symbol.exported) {
103
- return "DS1002";
104
+ return CODE.unusedUnexported;
104
105
  }
105
- return judged.unreachable ? undefined : "DS1001";
106
+ return judged.unreachable ? undefined : CODE.unusedExported;
106
107
  }
107
108
 
108
109
  /** The code a declaration nothing references carries, by its shape and its visibility. */
109
110
  function unreferencedCode(symbol: InventorySymbol): string {
110
111
  if (MEMBERS.has(symbol.kind)) {
111
- return "DS1003";
112
+ return CODE.unusedMember;
112
113
  }
113
- return symbol.exported ? "DS1001" : "DS1002";
114
+ return symbol.exported ? CODE.unusedExported : CODE.unusedUnexported;
114
115
  }
115
116
 
116
117
  /** The visibility a member's message names, where it narrows the member's reach. */
@@ -135,17 +136,17 @@ function messageOf(code: string, judged: Judged): string {
135
136
  const { candidate, symbol } = judged;
136
137
  const word = WORDS[symbol.kind];
137
138
  switch (code) {
138
- case "DS1005":
139
+ case CODE.testOfDeadCode:
139
140
  return TEST_OF_DEAD_CODE;
140
- case "DS1006":
141
+ case CODE.deprecatedUnused:
141
142
  return `deprecated ${word} has no production reference`;
142
- case "DS1004":
143
+ case CODE.testOnlyUse:
143
144
  return candidate.productionRefs === 0
144
145
  ? `${word} is referenced only from test files and never from production code`
145
146
  : `${word} is referenced only from test files and from declarations that are themselves dead`;
146
147
  }
147
148
  const subject =
148
- code === "DS1003"
149
+ code === CODE.unusedMember
149
150
  ? `${visibilityWord(symbol)}${word}`
150
151
  : symbol.kind === "export-alias"
151
152
  ? word
@@ -153,7 +154,7 @@ function messageOf(code: string, judged: Judged): string {
153
154
  if (candidate.relation === "reachability") {
154
155
  return `${subject} is referenced only from declarations that are themselves dead`;
155
156
  }
156
- if (code === "DS1001") {
157
+ if (code === CODE.unusedExported) {
157
158
  return `${subject} has no reference in the target and none from any loaded consumer`;
158
159
  }
159
160
  return `${subject} has no reference in the target`;
@@ -205,15 +206,15 @@ function brandMembers(input: EmitterInput): ReadonlySet<string> {
205
206
  /** The kinds of member a brand is: a data member of a type or of a class. */
206
207
  const BRANDS: ReadonlySet<SymbolKind> = new Set(["type-member", "class-member"]);
207
208
 
208
- /** The findings of the unused-declarations family, `DS1000` to `DS1099`. */
209
+ /** The findings of the unused-declaration kinds, `DS1001` to `DS1004` and `DS1102` to `DS1104`. */
209
210
  export const unusedDeclarations: Emitter = (input) => [
210
- ...familyFindings(input, false),
211
+ ...emitterFindings(input, false),
211
212
  ...unusedImports(input),
212
213
  ];
213
214
 
214
215
  /**
215
- * The import bindings their file never reads, under the unexported code. A generated
216
- * file's are the generator's to remove unless the run includes generated files.
216
+ * The import bindings their file never reads. A generated file's are the generator's to
217
+ * remove unless the run includes generated files.
217
218
  */
218
219
  function unusedImports(input: EmitterInput): Finding[] {
219
220
  const generated =
@@ -223,7 +224,7 @@ function unusedImports(input: EmitterInput): Finding[] {
223
224
  ? []
224
225
  : [
225
226
  {
226
- code: "DS1002",
227
+ code: CODE.unusedImport,
227
228
  position: binding.position,
228
229
  symbol: { ref: binding.ref, kind: "import", name: binding.name, sizeLines: 1 },
229
230
  message: `import binding ${binding.name} is never read in its file`,
@@ -237,10 +238,10 @@ function unusedImports(input: EmitterInput): Finding[] {
237
238
  * a record on such a member is matched against, since the report carries the container's
238
239
  * finding rather than one of the member's own.
239
240
  */
240
- export const fallenMembers: Emitter = (input) => familyFindings(input, true);
241
+ export const fallenMembers: Emitter = (input) => emitterFindings(input, true);
241
242
 
242
- /** The family's findings, or, where `fallen` is set, its dead containers' members' alone. */
243
- function familyFindings(input: EmitterInput, fallen: boolean): Finding[] {
243
+ /** This emitter's findings, or, where `fallen` is set, its dead containers' members' alone. */
244
+ function emitterFindings(input: EmitterInput, fallen: boolean): Finding[] {
244
245
  const union = input.swept.matrix.union;
245
246
  const dead = new Set(input.swept.sweep.candidates.map((candidate) => candidate.id));
246
247
  const unreachable = unreachableExports(input);
@@ -269,7 +270,7 @@ function familyFindings(input: EmitterInput, fallen: boolean): Finding[] {
269
270
  const code = codeOf(judged);
270
271
  if (
271
272
  code === undefined ||
272
- (code === "DS1003" && (brands ??= brandMembers(input)).has(symbol.id))
273
+ (code === CODE.unusedMember && (brands ??= brandMembers(input)).has(symbol.id))
273
274
  ) {
274
275
  continue;
275
276
  }
@@ -1,3 +1,4 @@
1
+ import { CODE } from "../contract/contract.generated.ts";
1
2
  import type { Finding } from "../finding.ts";
2
3
  import { OUTSIDE, type Graph } from "../graph.ts";
3
4
  import type { InventorySymbol, SymbolKind } from "../inventory.ts";
@@ -8,10 +9,6 @@ import { runnerCalls } from "../sweep.ts";
8
9
  import type { Emitter, EmitterInput } from "./emitter.ts";
9
10
  import { writeOnlyDeclarations } from "./reads-and-writes.ts";
10
11
 
11
- const UNNECESSARY_EXPORT = "DS1101";
12
- const UNREACHABLE_EXPORT = "DS1103";
13
- const REDUNDANT_EXPORT_KEYWORD = "DS1104";
14
-
15
12
  /** How far the references to one declaration reach, from the narrowest. */
16
13
  type Reach = "file" | "package" | "outside";
17
14
 
@@ -22,7 +19,7 @@ type Reach = "file" | "package" | "outside";
22
19
  */
23
20
  const PUBLISHING: ReadonlySet<RootKind> = new Set<RootKind>(["manifest-entry", "published-api"]);
24
21
 
25
- /** The reader's word for each kind of declaration this family reports. */
22
+ /** The reader's word for each kind of declaration this emitter reports. */
26
23
  const WORDS: Partial<Readonly<Record<SymbolKind, string>>> = {
27
24
  function: "function",
28
25
  class: "class",
@@ -151,8 +148,8 @@ function wordOf(symbol: InventorySymbol): string {
151
148
 
152
149
  /** The visibility each narrowing code names, which only the narrowing codes carry. */
153
150
  const NARROWER: Readonly<Record<string, "file" | "package">> = {
154
- [REDUNDANT_EXPORT_KEYWORD]: "file",
155
- [UNNECESSARY_EXPORT]: "package",
151
+ [CODE.redundantExportKeyword]: "file",
152
+ [CODE.unnecessaryExport]: "package",
156
153
  };
157
154
 
158
155
  function findingOf(symbol: InventorySymbol, code: string, message: string): Finding {
@@ -206,7 +203,7 @@ function narrowing(
206
203
  }
207
204
  return findingOf(
208
205
  symbol,
209
- REDUNDANT_EXPORT_KEYWORD,
206
+ CODE.redundantExportKeyword,
210
207
  `exported ${word} is referenced only inside the file that declares it`,
211
208
  );
212
209
  case "package":
@@ -215,7 +212,7 @@ function narrowing(
215
212
  }
216
213
  return findingOf(
217
214
  symbol,
218
- UNNECESSARY_EXPORT,
215
+ CODE.unnecessaryExport,
219
216
  `exported ${word} is referenced only inside the package that declares it`,
220
217
  );
221
218
  default:
@@ -254,13 +251,13 @@ function unreachable(
254
251
  : "has no reference in the target";
255
252
  return findingOf(
256
253
  symbol,
257
- UNREACHABLE_EXPORT,
254
+ CODE.unreachableExport,
258
255
  `exported ${wordOf(symbol)} ${found}, and nothing outside can import the file that declares it`,
259
256
  );
260
257
  }
261
258
 
262
259
  /**
263
- * The visibility-narrowing family over one run: `DS1101`, `DS1103` and `DS1104`. A finding
260
+ * The visibility-narrowing kinds over one run: `DS1501`, `DS1101` and `DS1503`. A finding
264
261
  * about a symbol a declared cross-language edge names is reported here like any other, and
265
262
  * the edge's evaluation then holds it pending, because the edge stands for a reference from
266
263
  * outside the file and the package whose use is decided in the other language. A
@@ -316,5 +313,5 @@ export function unreachableExports(input: EmitterInput): ReadonlySet<string> {
316
313
  return found;
317
314
  }
318
315
 
319
- /** The findings of the visibility-narrowing family, `DS1100` to `DS1199`. */
316
+ /** The findings of the visibility-narrowing kinds, `DS1501`, `DS1101` and `DS1503`. */
320
317
  export const visibilityNarrowing: Emitter = narrowings;
@@ -4,6 +4,7 @@
4
4
  * verdict a report carries beside them.
5
5
  */
6
6
 
7
+ import { EXIT_CLEAN, EXIT_FINDINGS, EXIT_PENDING } from "./contract/contract.generated.ts";
7
8
  import type { AnalysisInputs } from "./analysis.ts";
8
9
  import { fails, type Config } from "./config.ts";
9
10
  import type { EdgeEvaluation } from "./edges.ts";
@@ -23,7 +24,8 @@ import {
23
24
  type Ledger,
24
25
  } from "./suppress.ts";
25
26
  import type { Provenance, Recorded } from "./suppress-file.ts";
26
- import { EXIT_CLEAN, EXIT_FINDINGS } from "./verbs/verb.ts";
27
+ import { bytewise } from "./text/compare.ts";
28
+ import { PRODUCT } from "./product.ts";
27
29
 
28
30
  /** The counts a report's totals carry beside its findings. */
29
31
  interface PassTotals {
@@ -49,25 +51,14 @@ export interface PassResult {
49
51
  readonly ledger: Ledger;
50
52
  }
51
53
 
52
- /** The exit code of a report holding at least one pending finding. */
53
- export const EXIT_PENDING = 4;
54
-
55
- /** Two strings ordered bytewise. */
56
- function compare(a: string, b: string): number {
57
- if (a === b) {
58
- return 0;
59
- }
60
- return a < b ? -1 : 1;
61
- }
62
-
63
54
  /** The canonical order: path, line, column, code, then the symbol reference. */
64
55
  function canonical(a: Finding, b: Finding): number {
65
56
  return (
66
- compare(a.position.path, b.position.path) ||
57
+ bytewise(a.position.path, b.position.path) ||
67
58
  a.position.line - b.position.line ||
68
59
  a.position.column - b.position.column ||
69
- compare(a.code, b.code) ||
70
- compare(a.symbol.ref, b.symbol.ref)
60
+ bytewise(a.code, b.code) ||
61
+ bytewise(a.symbol.ref, b.symbol.ref)
71
62
  );
72
63
  }
73
64
 
@@ -164,5 +155,5 @@ export function rowKey(row: Recorded): string {
164
155
 
165
156
  /** The analyzer identity a baseline this analyzer writes names as its rows' provenance. */
166
157
  export function analyzerProvenance(version: string): Provenance {
167
- return { analyzer: "deadset-ts", version };
158
+ return { analyzer: PRODUCT, version };
168
159
  }
@@ -0,0 +1,52 @@
1
+ import type { Host } from "../host.ts";
2
+ import { isRecord } from "../json-document.ts";
3
+ import { joinPath } from "../paths.ts";
4
+
5
+ /** The file a package's manifest is read from. */
6
+ export const MANIFEST = "package.json";
7
+
8
+ /** The directory a package manager installs packages into, whose files are no project's own. */
9
+ export const INSTALLED_PACKAGES = "node_modules";
10
+
11
+ /** Whether one path passes through an installed-packages directory. */
12
+ export function inInstalledPackages(path: string): boolean {
13
+ return path.split("/").includes(INSTALLED_PACKAGES);
14
+ }
15
+
16
+ /** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
17
+ export function readDocument(
18
+ host: Host,
19
+ path: string,
20
+ ): { text: string; value: unknown } | undefined {
21
+ let text: string;
22
+ try {
23
+ text = host.readFile(path);
24
+ } catch {
25
+ return undefined;
26
+ }
27
+ try {
28
+ return { text, value: JSON.parse(text) as unknown };
29
+ } catch {
30
+ return undefined;
31
+ }
32
+ }
33
+
34
+ /** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
35
+ export function readJSON(host: Host, path: string): unknown {
36
+ return readDocument(host, path)?.value;
37
+ }
38
+
39
+ /**
40
+ * The manifest in one directory, decoded, or undefined where none reads as an object. A
41
+ * manifest this program cannot read declares nothing, the same answer as an absent one:
42
+ * the package manager owns the refusal of a manifest it rejects.
43
+ */
44
+ export function manifestAt(host: Host, dir: string): Record<string, unknown> | undefined {
45
+ const value = readJSON(host, joinPath(dir, MANIFEST));
46
+ return isRecord(value) ? value : undefined;
47
+ }
48
+
49
+ /** Whether one directory holds a manifest file. */
50
+ export function holdsManifest(host: Host, dir: string): boolean {
51
+ return host.kindOf(joinPath(dir, MANIFEST)) === "file";
52
+ }