@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,38 +1,83 @@
1
+ import { CODE } from "../contract/contract.generated.js";
1
2
  import { declarationKey } from "../dependencies.js";
2
3
  import { renderRef } from "../ref.js";
3
- const UNUSED_DEPENDENCY = "DS1601";
4
+ import { nonCodeArtifacts } from "./non-code-artifacts.js";
4
5
  const MESSAGES = {
5
6
  dependency: "no import in the target needs the declared dependency",
6
7
  "dev-dependency": "no import in the target needs the declared development dependency",
7
8
  "peer-dependency": "no import in the target needs the declared peer dependency",
8
9
  };
10
+ const DEAD_USERS_MESSAGES = {
11
+ dependency: "only code that is itself dead uses the declared dependency",
12
+ "dev-dependency": "only code that is itself dead uses the declared development dependency",
13
+ "peer-dependency": "only code that is itself dead uses the declared peer dependency",
14
+ };
15
+ /**
16
+ * The run's dead code: test code keeps the code it references out of every dead component
17
+ * unless that code is dead as well, and a file's top level is dead where the file is.
18
+ */
19
+ function deadCode(input) {
20
+ const { swept } = input;
21
+ const union = swept.matrix.union;
22
+ const files = new Map(union.symbols.filter((one) => one.kind === "file").map((one) => [one.position.path, one.id]));
23
+ const dead = new Set(swept.sweep.components.flatMap((component) => component.members));
24
+ const reachedOnly = new Set(swept.sweep.candidates.filter((one) => one.relation === "reachability").map((one) => one.id));
25
+ for (const finding of nonCodeArtifacts(input)) {
26
+ const id = files.get(finding.position.path);
27
+ if (finding.code === CODE.fileNeverImported && id !== undefined) {
28
+ dead.add(id);
29
+ if (finding.unreachableOnly === true) {
30
+ reachedOnly.add(id);
31
+ }
32
+ }
33
+ }
34
+ const published = new Set(union.rooted
35
+ .filter((root) => root.kind === "published-api")
36
+ .flatMap((root) => union.symbols[root.at]?.id ?? []));
37
+ return { dead, reachedOnly, published };
38
+ }
9
39
  /**
10
40
  * A dependency, development dependency or peer dependency of a manifest the run reads
11
- * that the target does not need, at the position of its key.
41
+ * that the target does not need, at the position of its key, and one whose every use is
42
+ * held by dead code, which is dead only through that code and falls with its component:
43
+ * at `possible` where one of its holders is published, and found under reachability alone
44
+ * where one of them is.
12
45
  */
13
- function unusedDependencies(dependencies) {
14
- return dependencies.declared
15
- .filter((dependency) => !dependencies.needed.has(declarationKey(dependency)) &&
16
- !dependencies.ran.has(declarationKey(dependency)))
17
- .map((dependency) => ({
18
- code: UNUSED_DEPENDENCY,
19
- position: dependency.position,
20
- symbol: {
21
- ref: renderRef(dependency.manifest, {
22
- of: "dependency",
23
- name: dependency.name,
24
- section: dependency.section,
25
- }),
26
- kind: "dependency",
27
- name: dependency.name,
28
- sizeLines: 1,
29
- },
30
- message: MESSAGES[dependency.section],
31
- details: { dependencyClass: dependency.section },
32
- ...(dependencies.unbuilt.has(dependency.position.path)
33
- ? { reachabilityClass: "possible" }
34
- : {}),
35
- }));
46
+ function unusedDependencies(input, dependencies) {
47
+ const { dead, reachedOnly, published } = deadCode(input);
48
+ return dependencies.declared.flatMap((dependency) => {
49
+ const key = declarationKey(dependency);
50
+ const held = dependencies.heldBy.get(key);
51
+ const users = held?.every((id) => dead.has(id)) === true ? held : undefined;
52
+ if (dependencies.ran.has(key) || (dependencies.needed.has(key) && users === undefined)) {
53
+ return [];
54
+ }
55
+ const unbuilt = dependencies.unbuilt.has(dependency.position.path);
56
+ const atPossible = unbuilt || users?.some((id) => published.has(id)) === true;
57
+ return [
58
+ {
59
+ code: CODE.unusedDependency,
60
+ position: dependency.position,
61
+ symbol: {
62
+ ref: renderRef(dependency.manifest, {
63
+ of: "dependency",
64
+ name: dependency.name,
65
+ section: dependency.section,
66
+ }),
67
+ kind: "dependency",
68
+ name: dependency.name,
69
+ sizeLines: 1,
70
+ },
71
+ message: (users === undefined ? MESSAGES : DEAD_USERS_MESSAGES)[dependency.section],
72
+ details: { dependencyClass: dependency.section },
73
+ ...(atPossible ? { reachabilityClass: "possible" } : {}),
74
+ ...(users?.some((id) => reachedOnly.has(id)) === true
75
+ ? { unreachableOnly: true }
76
+ : {}),
77
+ ...(users === undefined ? {} : { fallsWith: users }),
78
+ },
79
+ ];
80
+ });
36
81
  }
37
- /** The findings of the dependencies-and-module-machinery family, `DS1600` to `DS1699`. */
38
- export const dependenciesAndModuleMachinery = ({ dependencies }) => unusedDependencies(dependencies);
82
+ /** The findings of the dependencies family, `DS1700` to `DS1799`. */
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
  */
@@ -86,7 +86,7 @@ function atPossible(input) {
86
86
  const { config } = input;
87
87
  return {
88
88
  ...input,
89
- config: { ...config, analysis: { ...config.analysis, minConfidence: "possible" } },
89
+ config: { ...config, analysis: { ...config.analysis, min_confidence: "possible" } },
90
90
  };
91
91
  }
92
92
  /**
@@ -100,7 +100,7 @@ function dialledOver(input, suppressed, found, unmarked) {
100
100
  const unanswered = heldKeys(input);
101
101
  const markup = markupKeys(input);
102
102
  const candidates = found.filter((finding) => {
103
- const key = declarationKey(finding.symbol.ref, finding.position.path);
103
+ const key = declarationKey(finding.symbol.holder ?? finding.symbol.ref, finding.position.path);
104
104
  return (!ledger.withheld(finding) &&
105
105
  !unanswered.has(key) &&
106
106
  !markup.has(key) &&
@@ -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,5 +1,7 @@
1
+ import { CODE } from "../contract/contract.generated.js";
1
2
  import { KINDS } from "../kinds.js";
2
- import { UNUSED_PARAMETER, UNUSED_RESULT } from "../intra-function-parts.js";
3
+ import { parameterRef } from "../ref.js";
4
+ import {} from "../intra-function-parts.js";
3
5
  import { reachedWithTheTests } from "../sweep.js";
4
6
  /** A list of rules as a sentence names them. */
5
7
  function spelled(rules) {
@@ -8,24 +10,45 @@ function spelled(rules) {
8
10
  }
9
11
  return `${rules.slice(0, -1).join(", ")} and ${rules.at(-1) ?? ""}`;
10
12
  }
11
- /** The clause a message ends with: the other rules that report the kind, or that none is known. */
12
- function overlapClause(overlap) {
13
- return overlap.length === 1 && overlap[0] === "none known"
13
+ /** The compiler's rule of the parameter kind's overlap list, which reports only in a file it checks. */
14
+ const COMPILER_PARAMETER_RULE = "tsc --noUnusedParameters";
15
+ /**
16
+ * The clause a message ends with: the other rules that report the part, or that none is
17
+ * known. A parameter of a file the compiler does not check is no report of the compiler's.
18
+ */
19
+ function overlapClause(overlap, part) {
20
+ const rules = part.parameter?.checked === false
21
+ ? overlap.filter((rule) => rule !== COMPILER_PARAMETER_RULE)
22
+ : overlap;
23
+ return rules.length === 1 && rules[0] === "none known"
14
24
  ? "no other rule is known to report it"
15
- : `also reported by ${spelled(overlap)}`;
25
+ : `also reported by ${spelled(rules)}`;
16
26
  }
17
27
  /**
18
- * Whether one parameter's name is reported: every unread one of a function whose
19
- * signature is free, and of a function used as a value only one no read parameter follows,
20
- * because a caller may pass such a function more arguments than it declares.
28
+ * Whether one unread parameter is reported, and how. Of a free signature it is deleted. A
29
+ * caller may pass a function used as a value more arguments than it declares, so there one
30
+ * a read parameter follows is renamed to an underscore name, which the compilers read as
31
+ * declared unread, and one no read parameter follows is deleted: kept where the call's
32
+ * inference may read its annotation, at `probable` where nothing reads the annotation or
33
+ * where it is a name a contextually typed object pattern binds.
21
34
  */
22
- function parameterReported(part, free, freeButValued) {
35
+ function parameterVerdict(part, free, freeButValued) {
23
36
  const place = part.parameter;
24
37
  if (place?.inValue !== true && free.has(part.declaration)) {
25
- return true;
38
+ return place?.annotated === undefined ? { fix: "delete", confidence: "certain" } : undefined;
26
39
  }
27
40
  const valued = place?.inValue === true || freeButValued.has(part.declaration);
28
- return valued && place?.beforeARead !== true;
41
+ if (!valued) {
42
+ return undefined;
43
+ }
44
+ if (place?.beforeARead === true) {
45
+ return { fix: "rename" };
46
+ }
47
+ if (place?.annotated === "steers") {
48
+ return undefined;
49
+ }
50
+ const unseen = place?.annotated === "free" || place?.contextualPattern === true;
51
+ return { fix: "delete", confidence: unseen ? "probable" : "certain" };
29
52
  }
30
53
  /**
31
54
  * Whether one part is reported. A parameter needs its function's signature free to
@@ -40,9 +63,9 @@ function reported(input, part, callersUnknown) {
40
63
  }
41
64
  const { free, freeButValued, discardedEverywhere } = input.intraFunction;
42
65
  switch (part.code) {
43
- case UNUSED_PARAMETER:
44
- return parameterReported(part, free, freeButValued);
45
- case UNUSED_RESULT:
66
+ case CODE.unusedParameter:
67
+ return parameterVerdict(part, free, freeButValued) !== undefined;
68
+ case CODE.unusedResult:
46
69
  return (free.has(part.declaration) &&
47
70
  discardedEverywhere.has(part.declaration) &&
48
71
  !callersUnknown.has(part.declaration));
@@ -51,7 +74,7 @@ function reported(input, part, callersUnknown) {
51
74
  }
52
75
  }
53
76
  /**
54
- * 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
55
78
  * the declaration its reference names, at the part's own position, whatever the sweep
56
79
  * decided about a production declaration. A part of test code is reported only where the
57
80
  * run that counts test references reaches its declaration and that is no test of dead
@@ -75,7 +98,7 @@ export const intraFunction = (input) => {
75
98
  return symbol === undefined ? [] : [symbol.id];
76
99
  }));
77
100
  const { declared, loaded } = input.boundary.consumers;
78
- const closed = input.config.consumersComplete && declared.every((id) => loaded.includes(id));
101
+ const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
79
102
  const published = (id) => {
80
103
  const symbol = byId.get(id);
81
104
  return (symbol !== undefined &&
@@ -92,24 +115,35 @@ export const intraFunction = (input) => {
92
115
  const found = [];
93
116
  for (const part of input.intraFunction.parts) {
94
117
  const declaration = byId.get(part.declaration);
95
- const overlap = KINDS.get(part.code)?.overlap ?? [];
118
+ const overlap = KINDS.get(part.code)?.overlap?.ts ?? [];
96
119
  if (declaration === undefined || !judged(declaration.id) || !reported(input, part, unknown)) {
97
120
  continue;
98
121
  }
122
+ const verdict = part.code === CODE.unusedParameter
123
+ ? parameterVerdict(part, input.intraFunction.free, input.intraFunction.freeButValued)
124
+ : undefined;
125
+ const message = verdict?.fix === "rename"
126
+ ? `${part.message}, and a read parameter follows it, so rename it to _${part.name}`
127
+ : part.message;
99
128
  found.push({
100
129
  code: part.code,
101
130
  position: part.position,
102
131
  symbol: {
103
- ref: declaration.ref,
132
+ ...(part.code === CODE.unusedParameter
133
+ ? { ref: parameterRef(declaration.ref, part.name), holder: declaration.ref }
134
+ : { ref: declaration.ref }),
104
135
  kind: part.kind,
105
136
  name: part.name,
106
137
  sizeLines: part.position.endLine - part.position.line + 1,
107
138
  },
108
- message: `${part.message}; ${overlapClause(overlap)}`,
139
+ message: `${message}; ${overlapClause(overlap, part)}`,
109
140
  details: {
110
141
  ...(part.kind === "store" ? { writePositions: [part.position] } : {}),
111
142
  overlap,
112
143
  },
144
+ ...(verdict?.fix === "delete" && verdict.confidence !== "certain"
145
+ ? { reachabilityClass: verdict.confidence }
146
+ : {}),
113
147
  });
114
148
  }
115
149
  return found;
@@ -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. */
@@ -11,8 +10,8 @@ const FILE_START = 1;
11
10
  * derived matrix is never complete.
12
11
  */
13
12
  function completeMatrix(config) {
14
- return (config.analysis.matrixComplete &&
15
- config.analysis.configurations.some((entry) => entry.shape === "project"));
13
+ return (config.analysis.matrix.complete &&
14
+ config.analysis.configurations.some((entry) => "project" in entry));
16
15
  }
17
16
  /** One finding about a whole file. */
18
17
  function fileFinding(code, file, message) {
@@ -40,44 +39,28 @@ 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
- * The paths of every file a reference written in another file reaches, naming the
47
- * file or a declaration in it, a loaded consumer's file included. An import is such a
48
- * reference, so an import from any file reaches what it names, whatever reaches the
49
- * importing file.
45
+ * The paths of the files a reference from a live declaration of another file reaches, a
46
+ * loaded consumer's file included, a root names, or a declared edge names a declaration in,
47
+ * and of every file a reference written anywhere reaches. A file holding a declaration the
48
+ * sweep judged live is kept whatever held it live, and so is a file a program holds by
49
+ * inclusion, which acts on every file of the program without an import naming it. A
50
+ * reference from a file's top level counts once the file is kept.
50
51
  */
51
- function reachedFromOutside(union) {
52
- const reached = new Set();
52
+ function keptFiles({ swept, files, boundary }) {
53
+ const union = swept.matrix.union;
54
+ const edgeNamed = new Set(boundary.edges.map((side) => side.symbol));
55
+ const dead = new Set(swept.sweep.candidates.map((candidate) => candidate.id));
56
+ const kept = new Set(files.heldByInclusion);
57
+ const referenced = new Set();
53
58
  for (const edge of union.consumed) {
54
59
  const to = union.symbols[edge.to];
55
60
  if (to !== undefined) {
56
- reached.add(to.position.path);
61
+ kept.add(to.position.path);
57
62
  }
58
63
  }
59
- union.out.forEach((edges, from) => {
60
- const fromPath = union.symbols[from]?.position.path;
61
- for (const edge of edges) {
62
- const to = union.symbols[edge.to];
63
- if (to !== undefined && to.position.path !== fromPath) {
64
- reached.add(to.position.path);
65
- }
66
- }
67
- });
68
- return reached;
69
- }
70
- /**
71
- * The paths of the files an import reaches, a root names, or a declared edge names a
72
- * declaration in. A file holding a declaration the sweep judged live is kept whatever
73
- * held it live, and so is a file a program holds by inclusion, which acts on every
74
- * file of the program without an import naming it.
75
- */
76
- function keptFiles({ swept, files, boundary }) {
77
- const union = swept.matrix.union;
78
- const edgeNamed = new Set(boundary.edges.map((side) => side.symbol));
79
- const dead = new Set(swept.sweep.candidates.map((candidate) => candidate.id));
80
- const kept = new Set([...reachedFromOutside(union), ...files.heldByInclusion]);
81
64
  for (const root of union.rooted) {
82
65
  const named = union.symbols[root.at];
83
66
  if (named !== undefined) {
@@ -89,16 +72,65 @@ function keptFiles({ swept, files, boundary }) {
89
72
  kept.add(symbol.position.path);
90
73
  }
91
74
  }
92
- return kept;
75
+ const fromTopLevel = new Map();
76
+ const pending = [];
77
+ const keep = (path) => {
78
+ if (!kept.has(path)) {
79
+ kept.add(path);
80
+ pending.push(path);
81
+ }
82
+ };
83
+ union.out.forEach((edges, from) => {
84
+ const source = union.symbols[from];
85
+ if (source === undefined) {
86
+ return;
87
+ }
88
+ for (const edge of edges) {
89
+ const to = union.symbols[edge.to]?.position.path;
90
+ if (to === undefined || to === source.position.path) {
91
+ continue;
92
+ }
93
+ referenced.add(to);
94
+ if (source.kind === FILE_SUBJECT) {
95
+ fromTopLevel.set(source.position.path, [
96
+ ...(fromTopLevel.get(source.position.path) ?? []),
97
+ to,
98
+ ]);
99
+ }
100
+ else if (!dead.has(source.id)) {
101
+ pending.push(to);
102
+ }
103
+ }
104
+ });
105
+ pending.push(...kept);
106
+ for (let path = pending.pop(); path !== undefined; path = pending.pop()) {
107
+ kept.add(path);
108
+ for (const to of fromTopLevel.get(path) ?? []) {
109
+ keep(to);
110
+ }
111
+ fromTopLevel.delete(path);
112
+ }
113
+ return { kept, referenced };
93
114
  }
94
- /** Every file of the run that no import reaches and no root or edge names. */
115
+ /**
116
+ * Every file of the run that no reference from live code reaches and no root or edge
117
+ * names. A file only dead code imports is found dead under reachability alone.
118
+ */
95
119
  function fileNeverImported(input) {
96
- const kept = keptFiles(input);
120
+ const { kept, referenced } = keptFiles(input);
97
121
  return input.swept.matrix.union.symbols
98
122
  .filter((symbol) => symbol.kind === FILE_SUBJECT && !kept.has(symbol.position.path))
99
- .map((symbol) => fileFinding(FILE_NEVER_IMPORTED, { path: symbol.position.path, ref: symbol.ref, endLine: symbol.endLine }, "no import reaches this file and no root names it or a declaration in it"));
123
+ .map((symbol) => {
124
+ const file = { path: symbol.position.path, ref: symbol.ref, endLine: symbol.endLine };
125
+ return referenced.has(symbol.position.path)
126
+ ? {
127
+ ...fileFinding(CODE.fileNeverImported, file, "only code that is itself dead imports this file, and no root names it or a declaration in it"),
128
+ unreachableOnly: true,
129
+ }
130
+ : fileFinding(CODE.fileNeverImported, file, "no import reaches this file and no root names it or a declaration in it");
131
+ });
100
132
  }
101
- /** The findings of the non-code-artifacts family, `DS1500` to `DS1599`. */
133
+ /** The findings of the files family, `DS1600` to `DS1699`. */
102
134
  export const nonCodeArtifacts = (input) => [
103
135
  ...fileNeverBuilt(input),
104
136
  ...fileNeverImported(input),
@@ -1,7 +1,9 @@
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 and a class method. */
4
- const SIGNATURES = new Set(["function", "method"]);
4
+ import { bytewise } from "../text/compare.js";
5
+ /** The containers whose type parameters `DS1006` reports: a function, a class method and a type alias. */
6
+ const SIGNATURES = new Set(["function", "method", "type"]);
5
7
  function indexed(input) {
6
8
  const { matrix, sweep } = input.swept;
7
9
  return {
@@ -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,23 +159,24 @@ 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 or a class method that neither the
169
- * declaration's signature nor its body names. A type parameter of a class, an interface,
170
- * a type alias or a method signature belongs to a type declaration and is never
171
- * reported, because a phantom parameter makes two instantiations distinct types.
170
+ * `DS1006`: a type parameter of a function, a class method or a type alias that neither
171
+ * the declaration's signature nor its body names. An alias names no type of its own, so
172
+ * a parameter its type never names makes no two instantiations distinct. One of a class,
173
+ * an interface or a method signature is never reported: a class or an interface merges
174
+ * only with declarations of the same parameters.
172
175
  */
173
176
  function unusedTypeParameters(run) {
174
177
  return unnamed(run, "type-parameter")
175
178
  .filter((symbol) => SIGNATURES.has(run.symbols.get(symbol.parent)?.kind ?? ""))
176
- .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`));
177
180
  }
178
181
  /**
179
182
  * The keys of the declarations the write-only kind reports, whatever the
@@ -184,8 +187,8 @@ export function writeOnlyDeclarations(input) {
184
187
  return new Set(writeOnlySymbols(indexed(input)).map((finding) => declarationKey(finding.symbol.ref, finding.position.path)));
185
188
  }
186
189
  /**
187
- * The findings of the reads-and-writes family, `DS1300` to `DS1399`, in code order and
188
- * 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.
189
192
  */
190
193
  export const readsAndWrites = (input) => {
191
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