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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/mapper-connection.js +2 -1
  3. package/dist/bin/node-host.js +24 -5
  4. package/dist/src/analysis.js +245 -309
  5. package/dist/src/augmentations.js +124 -18
  6. package/dist/src/automatic-types.js +170 -0
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +9 -9
  10. package/dist/src/config.js +6 -81
  11. package/dist/src/configuration-files.js +78 -188
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +2 -2
  14. package/dist/src/consumers.js +3 -30
  15. package/dist/src/contextual-uses.js +56 -9
  16. package/dist/src/contract/config.generated.js +558 -0
  17. package/dist/src/contract/contract.generated.js +1244 -0
  18. package/dist/src/contract/report.generated.js +862 -0
  19. package/dist/src/convention-rows.js +146 -1
  20. package/dist/src/conventions.js +136 -70
  21. package/dist/src/decorator.js +21 -18
  22. package/dist/src/dependencies.js +280 -62
  23. package/dist/src/edges.js +38 -122
  24. package/dist/src/element-access.js +4 -6
  25. package/dist/src/entry-point-gaps.js +9 -18
  26. package/dist/src/entry-points.js +106 -21
  27. package/dist/src/enum-group.js +3 -10
  28. package/dist/src/erased-stores.js +4 -6
  29. package/dist/src/exempt-classes.js +1 -63
  30. package/dist/src/exempt.js +21 -40
  31. package/dist/src/failure.js +36 -0
  32. package/dist/src/file-facts.js +2 -25
  33. package/dist/src/findings/completion.js +225 -63
  34. package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
  35. package/dist/src/findings/emitters.js +6 -6
  36. package/dist/src/findings/interfaces.js +4 -5
  37. package/dist/src/findings/intra-function.js +53 -19
  38. package/dist/src/findings/non-code-artifacts.js +71 -39
  39. package/dist/src/findings/reads-and-writes.js +16 -13
  40. package/dist/src/findings/self-check.js +10 -12
  41. package/dist/src/findings/unused-declarations.js +57 -28
  42. package/dist/src/findings/visibility-narrowing.js +40 -18
  43. package/dist/src/findings-pass.js +8 -15
  44. package/dist/src/framework-lifecycle.js +7 -7
  45. package/dist/src/fs/document.js +52 -0
  46. package/dist/src/fs/manifest.js +72 -0
  47. package/dist/src/fs/tree.js +88 -0
  48. package/dist/src/generated-file.js +18 -4
  49. package/dist/src/git-ignore.js +99 -0
  50. package/dist/src/graph.js +9 -4
  51. package/dist/src/html-entries.js +6 -33
  52. package/dist/src/import-bindings.js +97 -0
  53. package/dist/src/in-operands.js +2 -1
  54. package/dist/src/index.js +1 -1
  55. package/dist/src/indexed-access.js +130 -28
  56. package/dist/src/injection-container.js +1 -1
  57. package/dist/src/interface-satisfaction.js +30 -12
  58. package/dist/src/intra-function-parts.js +341 -69
  59. package/dist/src/inventory.js +54 -29
  60. package/dist/src/javascript-file.js +33 -0
  61. package/dist/src/json-document.js +198 -67
  62. package/dist/src/keyof-comparisons.js +4 -6
  63. package/dist/src/kinds.js +3 -366
  64. package/dist/src/load/classify.js +270 -0
  65. package/dist/src/load/diagnostics.js +80 -0
  66. package/dist/src/load/discover.js +277 -0
  67. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  68. package/dist/src/load/expected-module.js +136 -0
  69. package/dist/src/load/load.js +27 -0
  70. package/dist/src/load/module-lookup.js +37 -0
  71. package/dist/src/load/plan.js +313 -0
  72. package/dist/src/load/specifiers.js +115 -0
  73. package/dist/src/load/synthesize.js +140 -0
  74. package/dist/src/load/test-globals.js +207 -0
  75. package/dist/src/load/test-joins.js +82 -0
  76. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  77. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
  78. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
  79. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  80. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  81. package/dist/src/manifest.js +49 -54
  82. package/dist/src/matrix.js +94 -13
  83. package/dist/src/mdx-entries.js +174 -0
  84. package/dist/src/module-declarations.js +9 -13
  85. package/dist/src/outside-tests.js +1 -1
  86. package/dist/src/overrides.js +41 -10
  87. package/dist/src/partial.js +2 -1
  88. package/dist/src/paths.js +26 -15
  89. package/dist/src/position.js +2 -1
  90. package/dist/src/print.js +11 -78
  91. package/dist/src/product.js +13 -0
  92. package/dist/src/query.js +1 -0
  93. package/dist/src/ref.js +11 -0
  94. package/dist/src/references.js +52 -31
  95. package/dist/src/report-schema.js +51 -0
  96. package/dist/src/report.js +59 -41
  97. package/dist/src/reporters/annotations.js +15 -8
  98. package/dist/src/reporters/sarif.js +16 -10
  99. package/dist/src/reporters/template.js +4 -19
  100. package/dist/src/resolve.js +245 -727
  101. package/dist/src/roots.js +374 -116
  102. package/dist/src/run.js +66 -77
  103. package/dist/src/schema.js +320 -342
  104. package/dist/src/scope.js +38 -115
  105. package/dist/src/serialization-contract.js +649 -29
  106. package/dist/src/session.js +41 -29
  107. package/dist/src/source-files.js +40 -15
  108. package/dist/src/suppress-file.js +34 -29
  109. package/dist/src/suppress-inline.js +21 -4
  110. package/dist/src/suppress.js +40 -56
  111. package/dist/src/sweep.js +32 -17
  112. package/dist/src/symbol-keys.js +105 -6
  113. package/dist/src/template-field.js +9 -19
  114. package/dist/src/test-support.js +119 -18
  115. package/dist/src/text/compare.js +26 -0
  116. package/dist/src/text/glob.js +194 -0
  117. package/dist/src/text/named-files.js +54 -0
  118. package/dist/src/text/shell.js +159 -0
  119. package/dist/src/text/yaml.js +393 -0
  120. package/dist/src/type-errors.js +27 -221
  121. package/dist/src/type-parameter-reads.js +172 -0
  122. package/dist/src/unseen-loads.js +100 -0
  123. package/dist/src/unwrap.js +23 -0
  124. package/dist/src/usage.js +10 -0
  125. package/dist/src/verbs/analyze.js +181 -125
  126. package/dist/src/verbs/describe.js +9 -16
  127. package/dist/src/verbs/explain.js +14 -9
  128. package/dist/src/verbs/print-config.js +2 -1
  129. package/dist/src/verbs/print-projects.js +6 -4
  130. package/dist/src/verbs/print-retained.js +4 -2
  131. package/dist/src/verbs/print-roots.js +4 -2
  132. package/dist/src/verbs/verb.js +1 -5
  133. package/dist/src/verbs/version.js +14 -3
  134. package/dist/src/workflow-steps.js +128 -33
  135. package/jsr.json +1 -1
  136. package/package.json +5 -4
  137. package/src/analysis.ts +333 -457
  138. package/src/augmentations.ts +152 -23
  139. package/src/automatic-types.ts +228 -0
  140. package/src/build-scripts.ts +543 -0
  141. package/src/component-layer.ts +4 -2
  142. package/src/components.ts +9 -10
  143. package/src/config.ts +31 -299
  144. package/src/configuration-files.ts +100 -191
  145. package/src/configured-declarations.ts +18 -23
  146. package/src/conformance-record.ts +2 -2
  147. package/src/consumers.ts +6 -57
  148. package/src/contextual-uses.ts +64 -12
  149. package/src/contract/config.generated.ts +818 -0
  150. package/src/contract/contract.generated.ts +1308 -0
  151. package/src/contract/report.generated.ts +1237 -0
  152. package/src/convention-rows.ts +186 -1
  153. package/src/conventions.ts +166 -90
  154. package/src/decorator.ts +28 -18
  155. package/src/dependencies.ts +356 -78
  156. package/src/edges.ts +41 -140
  157. package/src/element-access.ts +4 -7
  158. package/src/entry-point-gaps.ts +11 -20
  159. package/src/entry-points.ts +124 -28
  160. package/src/enum-group.ts +2 -11
  161. package/src/erased-stores.ts +3 -7
  162. package/src/exempt-classes.ts +1 -75
  163. package/src/exempt.ts +36 -50
  164. package/src/failure.ts +63 -0
  165. package/src/file-facts.ts +9 -24
  166. package/src/finding.ts +24 -9
  167. package/src/findings/boundary.ts +2 -0
  168. package/src/findings/completion.ts +297 -77
  169. package/src/findings/dependencies-and-module-machinery.ts +90 -31
  170. package/src/findings/emitter.ts +8 -9
  171. package/src/findings/emitters.ts +8 -8
  172. package/src/findings/interfaces.ts +4 -6
  173. package/src/findings/intra-function.ts +64 -20
  174. package/src/findings/non-code-artifacts.ts +88 -50
  175. package/src/findings/reads-and-writes.ts +20 -16
  176. package/src/findings/self-check.ts +11 -17
  177. package/src/findings/unused-declarations.ts +65 -28
  178. package/src/findings/visibility-narrowing.ts +51 -19
  179. package/src/findings-pass.ts +10 -19
  180. package/src/framework-lifecycle.ts +7 -7
  181. package/src/fs/document.ts +70 -0
  182. package/src/fs/manifest.ts +92 -0
  183. package/src/fs/tree.ts +110 -0
  184. package/src/generated-file.ts +23 -4
  185. package/src/git-ignore.ts +122 -0
  186. package/src/graph.ts +11 -3
  187. package/src/host.ts +17 -14
  188. package/src/html-entries.ts +6 -33
  189. package/src/import-bindings.ts +128 -0
  190. package/src/in-operands.ts +2 -1
  191. package/src/index.ts +1 -1
  192. package/src/indexed-access.ts +152 -30
  193. package/src/injection-container.ts +1 -1
  194. package/src/interface-satisfaction.ts +33 -13
  195. package/src/intra-function-parts.ts +449 -85
  196. package/src/inventory.ts +73 -27
  197. package/src/javascript-file.ts +37 -0
  198. package/src/json-document.ts +241 -76
  199. package/src/keyof-comparisons.ts +3 -7
  200. package/src/kinds.ts +5 -413
  201. package/src/load/classify.ts +397 -0
  202. package/src/load/diagnostics.ts +144 -0
  203. package/src/load/discover.ts +455 -0
  204. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  205. package/src/load/expected-module.ts +188 -0
  206. package/src/load/load.ts +73 -0
  207. package/src/load/module-lookup.ts +50 -0
  208. package/src/load/plan.ts +504 -0
  209. package/src/load/specifiers.ts +168 -0
  210. package/src/load/synthesize.ts +211 -0
  211. package/src/load/test-globals.ts +251 -0
  212. package/src/load/test-joins.ts +98 -0
  213. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  214. package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
  215. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
  216. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  217. package/src/{workspace.ts → load/workspace.ts} +90 -134
  218. package/src/manifest.ts +81 -75
  219. package/src/matrix.ts +154 -18
  220. package/src/mdx-entries.ts +218 -0
  221. package/src/module-declarations.ts +11 -13
  222. package/src/outside-tests.ts +1 -1
  223. package/src/overrides.ts +51 -12
  224. package/src/partial.ts +3 -2
  225. package/src/paths.ts +27 -15
  226. package/src/position.ts +2 -1
  227. package/src/print.ts +12 -91
  228. package/src/product.ts +18 -0
  229. package/src/query.ts +11 -0
  230. package/src/ref.ts +13 -0
  231. package/src/references.ts +62 -32
  232. package/src/report-schema.ts +68 -0
  233. package/src/report.ts +105 -215
  234. package/src/reporters/annotations.ts +22 -11
  235. package/src/reporters/json.ts +1 -1
  236. package/src/reporters/reporter.ts +2 -4
  237. package/src/reporters/reporters.ts +1 -1
  238. package/src/reporters/sarif.ts +29 -16
  239. package/src/reporters/template.ts +4 -20
  240. package/src/reporters/text.ts +3 -3
  241. package/src/resolve.ts +283 -1111
  242. package/src/roots.ts +490 -137
  243. package/src/run.ts +75 -91
  244. package/src/schema.ts +391 -387
  245. package/src/scope.ts +46 -138
  246. package/src/serialization-contract.ts +818 -26
  247. package/src/session.ts +77 -54
  248. package/src/source-files.ts +67 -17
  249. package/src/suppress-file.ts +36 -34
  250. package/src/suppress-inline.ts +20 -3
  251. package/src/suppress.ts +52 -63
  252. package/src/sweep.ts +53 -18
  253. package/src/symbol-keys.ts +131 -7
  254. package/src/template-field.ts +15 -25
  255. package/src/test-support.ts +155 -18
  256. package/src/text/compare.ts +27 -0
  257. package/src/text/glob.ts +210 -0
  258. package/src/text/named-files.inputs.json +1522 -0
  259. package/src/text/named-files.ts +68 -0
  260. package/src/text/shell.ts +161 -0
  261. package/src/text/yaml.ts +423 -0
  262. package/src/type-errors.ts +35 -273
  263. package/src/type-parameter-reads.ts +227 -0
  264. package/src/unseen-loads.ts +134 -0
  265. package/src/unwrap.ts +34 -0
  266. package/src/usage.ts +10 -0
  267. package/src/verbs/analyze.ts +245 -161
  268. package/src/verbs/describe.ts +14 -15
  269. package/src/verbs/explain.ts +15 -13
  270. package/src/verbs/print-config.ts +2 -1
  271. package/src/verbs/print-projects.ts +6 -4
  272. package/src/verbs/print-retained.ts +4 -2
  273. package/src/verbs/print-roots.ts +4 -2
  274. package/src/verbs/verb.ts +2 -8
  275. package/src/verbs/version.ts +15 -3
  276. package/src/workflow-steps.ts +149 -34
  277. package/dist/src/discover.js +0 -347
  278. package/dist/src/glob.js +0 -65
  279. package/dist/src/pnpm-workspace.js +0 -120
  280. package/dist/src/setup-failure.js +0 -23
  281. package/dist/src/test-joins.js +0 -101
  282. package/dist/src/version.js +0 -7
  283. package/src/discover.ts +0 -510
  284. package/src/glob.ts +0 -67
  285. package/src/pnpm-workspace.ts +0 -135
  286. package/src/setup-failure.ts +0 -39
  287. package/src/test-joins.ts +0 -120
  288. package/src/version.ts +0 -7
@@ -1,5 +1,5 @@
1
1
  import type { RunSweep } from "../analysis.ts";
2
- import type { Config } from "../config.ts";
2
+ import type { ResolvedConfig } from "../config.ts";
3
3
  import type { Dependencies } from "../dependencies.ts";
4
4
  import type { FileFacts } from "../file-facts.ts";
5
5
  import type { Finding } from "../finding.ts";
@@ -11,10 +11,10 @@ import type { SelfCheckFacts } from "./self-check.ts";
11
11
 
12
12
  /**
13
13
  * What every emitter reads: the configuration the run resolved, the run's sweep, and
14
- * the facts about the run each kind family reads beside the sweep.
14
+ * the facts about the run each emitter reads beside the sweep.
15
15
  */
16
16
  export interface EmitterInput {
17
- readonly config: Config;
17
+ readonly config: ResolvedConfig;
18
18
  /** The production sweep a report is built from, beside the matrix it judged. */
19
19
  readonly swept: RunSweep;
20
20
  /** The declarations carrying the deprecation marker in any configuration of the run. */
@@ -29,18 +29,17 @@ export interface EmitterInput {
29
29
  readonly files: FileFacts;
30
30
  /** What can reach the target from outside it: consumers, declared edges, and `exports`. */
31
31
  readonly boundary: Boundary;
32
- /** The run's suppression outcomes and configured roots, which the self-check family reports. */
32
+ /** The run's suppression outcomes and configured roots, which the configuration family reports. */
33
33
  readonly selfCheck: SelfCheckFacts;
34
- /** The parts of the run's declarations, which the intra-function family reports. */
34
+ /** The parts of the run's declarations, which the inside-functions family reports. */
35
35
  readonly intraFunction: IntraFunctionFacts;
36
36
  }
37
37
 
38
38
  /**
39
- * One kind family's rule over one run: every finding of the codes in its family's range
40
- * of the Contract's code space, whatever severity and minimum confidence the
41
- * configuration sets, in no particular order.
39
+ * One emitter's rule over one run: every finding of the codes it reports, whatever
40
+ * severity and minimum confidence the configuration sets, in no particular order.
42
41
  */
43
42
  export type Emitter = (input: EmitterInput) => readonly Finding[];
44
43
 
45
- /** The emitter of each kind family, by the family's name in the Contract, in code order. */
44
+ /** Every emitter this analyzer runs, by the name its tests select it by. */
46
45
  export type Emitters = ReadonlyMap<string, Emitter>;
@@ -1,4 +1,4 @@
1
- import type { Confidence } from "../config.ts";
1
+ import type { Confidence } from "../contract/contract.generated.ts";
2
2
  import { evaluateEdges, type EdgeEvaluation } from "../edges.ts";
3
3
  import type { CompletedFinding } from "../finding.ts";
4
4
  import { liveFilesAt } from "../matrix.ts";
@@ -16,7 +16,7 @@ import { selfCheck } from "./self-check.ts";
16
16
  import { fallenMembers, unusedDeclarations } from "./unused-declarations.ts";
17
17
  import { visibilityNarrowing } from "./visibility-narrowing.ts";
18
18
 
19
- /** Every kind family this analyzer reports, each by its emitter, in code order. */
19
+ /** Every emitter this analyzer runs, by the name its tests select it by. */
20
20
  export const EMITTERS: Emitters = new Map<string, Emitter>([
21
21
  ["unused-declarations", unusedDeclarations],
22
22
  ["visibility-narrowing", visibilityNarrowing],
@@ -76,7 +76,7 @@ interface Suppressed {
76
76
 
77
77
  /** What the emitter table answers about one run. */
78
78
  interface DecidedFindings {
79
- /** Every finding the run reports, each family's in table order. */
79
+ /** Every finding the run reports, each emitter's in table order. */
80
80
  readonly findings: readonly CompletedFinding[];
81
81
  /** One evaluation per declared edge side of this language, ordered by edge, then by side. */
82
82
  readonly evaluations: readonly EdgeEvaluation<CompletedFinding>[];
@@ -91,7 +91,7 @@ interface DecidedFindings {
91
91
  /** How many findings the minimum confidence withheld at each confidence. */
92
92
  export type Withheld = Readonly<Record<Confidence, number>>;
93
93
 
94
- /** Every family's findings over one sweep, and those of any further emitter, completed by one step. */
94
+ /** Every emitter's findings over one sweep, and those of any further emitter, completed by one step. */
95
95
  function completedOver(
96
96
  input: EmitterInput,
97
97
  ...more: readonly Emitter[]
@@ -124,8 +124,8 @@ function dialsOver(
124
124
 
125
125
  /**
126
126
  * Every finding of one run swept under its suppressions' marks, decided in one pass: each
127
- * family's findings completed by one step, so every component number is minted once, the
128
- * finding each record claims withheld, the rest withheld by the dials, so a root one family
127
+ * emitter's findings completed by one step, so every component number is minted once, the
128
+ * finding each record claims withheld, the rest withheld by the dials, so a root one emitter
129
129
  * reports withholds what another reports in its component, and every declared edge
130
130
  * evaluated over what remains, so a pending finding is in its evaluation alone.
131
131
  */
@@ -145,7 +145,7 @@ function atPossible(input: EmitterInput): EmitterInput {
145
145
  const { config } = input;
146
146
  return {
147
147
  ...input,
148
- config: { ...config, analysis: { ...config.analysis, minConfidence: "possible" } },
148
+ config: { ...config, analysis: { ...config.analysis, min_confidence: "possible" } },
149
149
  };
150
150
  }
151
151
 
@@ -165,7 +165,7 @@ function dialledOver(
165
165
  const unanswered = heldKeys(input);
166
166
  const markup = markupKeys(input);
167
167
  const candidates = found.filter((finding) => {
168
- const key = declarationKey(finding.symbol.ref, finding.position.path);
168
+ const key = declarationKey(finding.symbol.holder ?? finding.symbol.ref, finding.position.path);
169
169
  return (
170
170
  !ledger.withheld(finding) &&
171
171
  !unanswered.has(key) &&
@@ -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,6 +1,8 @@
1
+ import { CODE, type Confidence } from "../contract/contract.generated.ts";
1
2
  import type { Finding } from "../finding.ts";
2
3
  import { KINDS } from "../kinds.ts";
3
- import { UNUSED_PARAMETER, UNUSED_RESULT, type PartFact } from "../intra-function-parts.ts";
4
+ import { parameterRef } from "../ref.ts";
5
+ import { type PartFact } from "../intra-function-parts.ts";
4
6
  import { reachedWithTheTests } from "../sweep.ts";
5
7
  import type { Emitter, EmitterInput } from "./emitter.ts";
6
8
 
@@ -12,29 +14,58 @@ function spelled(rules: readonly string[]): string {
12
14
  return `${rules.slice(0, -1).join(", ")} and ${rules.at(-1) ?? ""}`;
13
15
  }
14
16
 
15
- /** The clause a message ends with: the other rules that report the kind, or that none is known. */
16
- function overlapClause(overlap: readonly string[]): string {
17
- return overlap.length === 1 && overlap[0] === "none known"
17
+ /** The compiler's rule of the parameter kind's overlap list, which reports only in a file it checks. */
18
+ const COMPILER_PARAMETER_RULE = "tsc --noUnusedParameters";
19
+
20
+ /**
21
+ * The clause a message ends with: the other rules that report the part, or that none is
22
+ * known. A parameter of a file the compiler does not check is no report of the compiler's.
23
+ */
24
+ function overlapClause(overlap: readonly string[], part: PartFact): string {
25
+ const rules =
26
+ part.parameter?.checked === false
27
+ ? overlap.filter((rule) => rule !== COMPILER_PARAMETER_RULE)
28
+ : overlap;
29
+ return rules.length === 1 && rules[0] === "none known"
18
30
  ? "no other rule is known to report it"
19
- : `also reported by ${spelled(overlap)}`;
31
+ : `also reported by ${spelled(rules)}`;
20
32
  }
21
33
 
34
+ /** What a parameter's finding asks: its deletion, or, where a read parameter follows it, its rename. */
35
+ type ParameterVerdict =
36
+ | { readonly fix: "delete"; readonly confidence: Confidence }
37
+ | { readonly fix: "rename" }
38
+ | undefined;
39
+
22
40
  /**
23
- * Whether one parameter's name is reported: every unread one of a function whose
24
- * signature is free, and of a function used as a value only one no read parameter follows,
25
- * because a caller may pass such a function more arguments than it declares.
41
+ * Whether one unread parameter is reported, and how. Of a free signature it is deleted. A
42
+ * caller may pass a function used as a value more arguments than it declares, so there one
43
+ * a read parameter follows is renamed to an underscore name, which the compilers read as
44
+ * declared unread, and one no read parameter follows is deleted: kept where the call's
45
+ * inference may read its annotation, at `probable` where nothing reads the annotation or
46
+ * where it is a name a contextually typed object pattern binds.
26
47
  */
27
- function parameterReported(
48
+ function parameterVerdict(
28
49
  part: PartFact,
29
50
  free: ReadonlySet<string>,
30
51
  freeButValued: ReadonlySet<string>,
31
- ): boolean {
52
+ ): ParameterVerdict {
32
53
  const place = part.parameter;
33
54
  if (place?.inValue !== true && free.has(part.declaration)) {
34
- return true;
55
+ return place?.annotated === undefined ? { fix: "delete", confidence: "certain" } : undefined;
35
56
  }
36
57
  const valued = place?.inValue === true || freeButValued.has(part.declaration);
37
- return valued && place?.beforeARead !== true;
58
+ if (!valued) {
59
+ return undefined;
60
+ }
61
+ if (place?.beforeARead === true) {
62
+ return { fix: "rename" };
63
+ }
64
+ if (place?.annotated === "steers") {
65
+ return undefined;
66
+ }
67
+ const unseen = place?.annotated === "free" || place?.contextualPattern === true;
68
+ return { fix: "delete", confidence: unseen ? "probable" : "certain" };
38
69
  }
39
70
 
40
71
  /**
@@ -54,9 +85,9 @@ function reported(
54
85
  }
55
86
  const { free, freeButValued, discardedEverywhere } = input.intraFunction;
56
87
  switch (part.code) {
57
- case UNUSED_PARAMETER:
58
- return parameterReported(part, free, freeButValued);
59
- case UNUSED_RESULT:
88
+ case CODE.unusedParameter:
89
+ return parameterVerdict(part, free, freeButValued) !== undefined;
90
+ case CODE.unusedResult:
60
91
  return (
61
92
  free.has(part.declaration) &&
62
93
  discardedEverywhere.has(part.declaration) &&
@@ -68,7 +99,7 @@ function reported(
68
99
  }
69
100
 
70
101
  /**
71
- * 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
72
103
  * the declaration its reference names, at the part's own position, whatever the sweep
73
104
  * decided about a production declaration. A part of test code is reported only where the
74
105
  * run that counts test references reaches its declaration and that is no test of dead
@@ -96,7 +127,7 @@ export const intraFunction: Emitter = (input) => {
96
127
  }),
97
128
  );
98
129
  const { declared, loaded } = input.boundary.consumers;
99
- const closed = input.config.consumersComplete && declared.every((id) => loaded.includes(id));
130
+ const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
100
131
  const published = (id: string): boolean => {
101
132
  const symbol = byId.get(id);
102
133
  return (
@@ -115,24 +146,37 @@ export const intraFunction: Emitter = (input) => {
115
146
  const found: Finding[] = [];
116
147
  for (const part of input.intraFunction.parts) {
117
148
  const declaration = byId.get(part.declaration);
118
- const overlap = KINDS.get(part.code)?.overlap ?? [];
149
+ const overlap = KINDS.get(part.code)?.overlap?.ts ?? [];
119
150
  if (declaration === undefined || !judged(declaration.id) || !reported(input, part, unknown)) {
120
151
  continue;
121
152
  }
153
+ const verdict =
154
+ part.code === CODE.unusedParameter
155
+ ? parameterVerdict(part, input.intraFunction.free, input.intraFunction.freeButValued)
156
+ : undefined;
157
+ const message =
158
+ verdict?.fix === "rename"
159
+ ? `${part.message}, and a read parameter follows it, so rename it to _${part.name}`
160
+ : part.message;
122
161
  found.push({
123
162
  code: part.code,
124
163
  position: part.position,
125
164
  symbol: {
126
- ref: declaration.ref,
165
+ ...(part.code === CODE.unusedParameter
166
+ ? { ref: parameterRef(declaration.ref, part.name), holder: declaration.ref }
167
+ : { ref: declaration.ref }),
127
168
  kind: part.kind,
128
169
  name: part.name,
129
170
  sizeLines: part.position.endLine - part.position.line + 1,
130
171
  },
131
- message: `${part.message}; ${overlapClause(overlap)}`,
172
+ message: `${message}; ${overlapClause(overlap, part)}`,
132
173
  details: {
133
174
  ...(part.kind === "store" ? { writePositions: [part.position] } : {}),
134
175
  overlap,
135
176
  },
177
+ ...(verdict?.fix === "delete" && verdict.confidence !== "certain"
178
+ ? { reachabilityClass: verdict.confidence }
179
+ : {}),
136
180
  });
137
181
  }
138
182
  return found;
@@ -1,11 +1,8 @@
1
- import type { Config } from "../config.ts";
1
+ import { CODE } from "../contract/contract.generated.ts";
2
+ import type { ResolvedConfig } from "../config.ts";
2
3
  import type { Finding } from "../finding.ts";
3
- import type { Graph } from "../graph.ts";
4
4
  import type { Emitter, EmitterInput } from "./emitter.ts";
5
5
 
6
- const FILE_NEVER_BUILT = "DS1501";
7
- const FILE_NEVER_IMPORTED = "DS1502";
8
-
9
6
  /** The subject kind of a finding about a source file, from the finding schema's vocabulary. */
10
7
  const FILE_SUBJECT = "file";
11
8
 
@@ -18,10 +15,10 @@ const FILE_START = 1;
18
15
  * completeness and listing no project declares it of a matrix the run derived, and a
19
16
  * derived matrix is never complete.
20
17
  */
21
- function completeMatrix(config: Config): boolean {
18
+ function completeMatrix(config: ResolvedConfig): boolean {
22
19
  return (
23
- config.analysis.matrixComplete &&
24
- config.analysis.configurations.some((entry) => entry.shape === "project")
20
+ config.analysis.matrix.complete &&
21
+ config.analysis.configurations.some((entry) => "project" in entry)
25
22
  );
26
23
  }
27
24
 
@@ -58,46 +55,37 @@ function fileNeverBuilt({ config, swept, files }: EmitterInput): readonly Findin
58
55
  const message = `no project of the build matrix includes this file (${swept.matrix.configurations.join(", ")})`;
59
56
  return files.tree
60
57
  .filter((file) => !built.has(file.path))
61
- .map((file) => fileFinding(FILE_NEVER_BUILT, file, message));
58
+ .map((file) => fileFinding(CODE.fileNeverBuilt, file, message));
62
59
  }
63
60
 
64
- /**
65
- * The paths of every file a reference written in another file reaches, naming the
66
- * file or a declaration in it, a loaded consumer's file included. An import is such a
67
- * reference, so an import from any file reaches what it names, whatever reaches the
68
- * importing file.
69
- */
70
- function reachedFromOutside(union: Graph): ReadonlySet<string> {
71
- const reached = new Set<string>();
72
- for (const edge of union.consumed) {
73
- const to = union.symbols[edge.to];
74
- if (to !== undefined) {
75
- reached.add(to.position.path);
76
- }
77
- }
78
- union.out.forEach((edges, from) => {
79
- const fromPath = union.symbols[from]?.position.path;
80
- for (const edge of edges) {
81
- const to = union.symbols[edge.to];
82
- if (to !== undefined && to.position.path !== fromPath) {
83
- reached.add(to.position.path);
84
- }
85
- }
86
- });
87
- return reached;
61
+ /** The files of the run, each with whether a reference written in another file reaches it. */
62
+ interface KeptFiles {
63
+ /** The files kept: reached from a live file, or named by a root, an edge or a program. */
64
+ readonly kept: ReadonlySet<string>;
65
+ /** The files a reference written in another file reaches, whatever holds that file. */
66
+ readonly referenced: ReadonlySet<string>;
88
67
  }
89
68
 
90
69
  /**
91
- * The paths of the files an import reaches, a root names, or a declared edge names a
92
- * declaration in. A file holding a declaration the sweep judged live is kept whatever
93
- * held it live, and so is a file a program holds by inclusion, which acts on every
94
- * file of the program without an import naming it.
70
+ * The paths of the files a reference from a live declaration of another file reaches, a
71
+ * loaded consumer's file included, a root names, or a declared edge names a declaration in,
72
+ * and of every file a reference written anywhere reaches. A file holding a declaration the
73
+ * sweep judged live is kept whatever held it live, and so is a file a program holds by
74
+ * inclusion, which acts on every file of the program without an import naming it. A
75
+ * reference from a file's top level counts once the file is kept.
95
76
  */
96
- function keptFiles({ swept, files, boundary }: EmitterInput): ReadonlySet<string> {
77
+ function keptFiles({ swept, files, boundary }: EmitterInput): KeptFiles {
97
78
  const union = swept.matrix.union;
98
79
  const edgeNamed = new Set(boundary.edges.map((side) => side.symbol));
99
80
  const dead = new Set(swept.sweep.candidates.map((candidate) => candidate.id));
100
- const kept = new Set([...reachedFromOutside(union), ...files.heldByInclusion]);
81
+ const kept = new Set(files.heldByInclusion);
82
+ const referenced = new Set<string>();
83
+ for (const edge of union.consumed) {
84
+ const to = union.symbols[edge.to];
85
+ if (to !== undefined) {
86
+ kept.add(to.position.path);
87
+ }
88
+ }
101
89
  for (const root of union.rooted) {
102
90
  const named = union.symbols[root.at];
103
91
  if (named !== undefined) {
@@ -109,24 +97,74 @@ function keptFiles({ swept, files, boundary }: EmitterInput): ReadonlySet<string
109
97
  kept.add(symbol.position.path);
110
98
  }
111
99
  }
112
- return kept;
100
+ const fromTopLevel = new Map<string, string[]>();
101
+ const pending: string[] = [];
102
+ const keep = (path: string): void => {
103
+ if (!kept.has(path)) {
104
+ kept.add(path);
105
+ pending.push(path);
106
+ }
107
+ };
108
+ union.out.forEach((edges, from) => {
109
+ const source = union.symbols[from];
110
+ if (source === undefined) {
111
+ return;
112
+ }
113
+ for (const edge of edges) {
114
+ const to = union.symbols[edge.to]?.position.path;
115
+ if (to === undefined || to === source.position.path) {
116
+ continue;
117
+ }
118
+ referenced.add(to);
119
+ if (source.kind === FILE_SUBJECT) {
120
+ fromTopLevel.set(source.position.path, [
121
+ ...(fromTopLevel.get(source.position.path) ?? []),
122
+ to,
123
+ ]);
124
+ } else if (!dead.has(source.id)) {
125
+ pending.push(to);
126
+ }
127
+ }
128
+ });
129
+ pending.push(...kept);
130
+ for (let path = pending.pop(); path !== undefined; path = pending.pop()) {
131
+ kept.add(path);
132
+ for (const to of fromTopLevel.get(path) ?? []) {
133
+ keep(to);
134
+ }
135
+ fromTopLevel.delete(path);
136
+ }
137
+ return { kept, referenced };
113
138
  }
114
139
 
115
- /** Every file of the run that no import reaches and no root or edge names. */
140
+ /**
141
+ * Every file of the run that no reference from live code reaches and no root or edge
142
+ * names. A file only dead code imports is found dead under reachability alone.
143
+ */
116
144
  function fileNeverImported(input: EmitterInput): readonly Finding[] {
117
- const kept = keptFiles(input);
145
+ const { kept, referenced } = keptFiles(input);
118
146
  return input.swept.matrix.union.symbols
119
147
  .filter((symbol) => symbol.kind === FILE_SUBJECT && !kept.has(symbol.position.path))
120
- .map((symbol) =>
121
- fileFinding(
122
- FILE_NEVER_IMPORTED,
123
- { path: symbol.position.path, ref: symbol.ref, endLine: symbol.endLine },
124
- "no import reaches this file and no root names it or a declaration in it",
125
- ),
126
- );
148
+ .map((symbol) => {
149
+ const file = { path: symbol.position.path, ref: symbol.ref, endLine: symbol.endLine };
150
+ return referenced.has(symbol.position.path)
151
+ ? {
152
+ ...fileFinding(
153
+ CODE.fileNeverImported,
154
+ file,
155
+ "only code that is itself dead imports this file, and no root names it or a declaration in it",
156
+ ),
157
+ unreachableOnly: true as const,
158
+ }
159
+ : fileFinding(
160
+ CODE.fileNeverImported,
161
+ file,
162
+ "no import reaches this file and no root names it or a declaration in it",
163
+ );
164
+ });
127
165
  }
128
166
 
129
- /** The findings of the non-code-artifacts family, `DS1500` to `DS1599`. */
167
+ /** The findings of the files family, `DS1600` to `DS1699`. */
130
168
  export const nonCodeArtifacts: Emitter = (input) => [
131
169
  ...fileNeverBuilt(input),
132
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 and a class method. */
12
- const SIGNATURES: ReadonlySet<string> = new Set(["function", "method"]);
14
+ /** The containers whose type parameters `DS1006` reports: a function, a class method and a type alias. */
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,21 +217,22 @@ 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 or a class method that neither the
229
- * declaration's signature nor its body names. A type parameter of a class, an interface,
230
- * a type alias or a method signature belongs to a type declaration and is never
231
- * reported, because a phantom parameter makes two instantiations distinct types.
231
+ * `DS1006`: a type parameter of a function, a class method or a type alias that neither
232
+ * the declaration's signature nor its body names. An alias names no type of its own, so
233
+ * a parameter its type never names makes no two instantiations distinct. One of a class,
234
+ * an interface or a method signature is never reported: a class or an interface merges
235
+ * only with declarations of the same parameters.
232
236
  */
233
237
  function unusedTypeParameters(run: Run): readonly Finding[] {
234
238
  return unnamed(run, "type-parameter")
@@ -236,7 +240,7 @@ function unusedTypeParameters(run: Run): readonly Finding[] {
236
240
  .map((symbol) =>
237
241
  findingAbout(
238
242
  symbol,
239
- "DS1303",
243
+ CODE.unusedTypeParameter,
240
244
  `type parameter ${symbol.name} is named in neither its signature nor its body`,
241
245
  ),
242
246
  );
@@ -256,8 +260,8 @@ export function writeOnlyDeclarations(input: EmitterInput): ReadonlySet<string>
256
260
  }
257
261
 
258
262
  /**
259
- * The findings of the reads-and-writes family, `DS1300` to `DS1399`, in code order and
260
- * 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.
261
265
  */
262
266
  export const readsAndWrites: Emitter = (input) => {
263
267
  const run = indexed(input);