@cplieger/deadset-ts 8.0.0-dev.16 → 8.0.0-dev.18

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 (161) hide show
  1. package/dist/src/alias-chain.js +0 -5
  2. package/dist/src/amd.js +81 -0
  3. package/dist/src/analysis.js +82 -91
  4. package/dist/src/augmentations.js +24 -25
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +279 -43
  7. package/dist/src/commands/tools.js +5 -9
  8. package/dist/src/component-files.js +3 -2
  9. package/dist/src/conformance-record.js +1 -1
  10. package/dist/src/consumers.js +6 -9
  11. package/dist/src/contextual-uses.js +1 -17
  12. package/dist/src/contract/config.generated.js +4 -1
  13. package/dist/src/contract/contract.generated.js +2 -2
  14. package/dist/src/contract/report.generated.js +6 -2
  15. package/dist/src/dependencies.js +25 -29
  16. package/dist/src/destructuring.js +1 -19
  17. package/dist/src/element-access.js +179 -164
  18. package/dist/src/entries/build-scripts.js +2 -2
  19. package/dist/src/entries/configuration-files.js +3 -7
  20. package/dist/src/entries/conventions.js +2 -2
  21. package/dist/src/entries/html-entries.js +7 -10
  22. package/dist/src/entries/mdx-entries.js +2 -2
  23. package/dist/src/entries/static-module.js +2 -1
  24. package/dist/src/exempt.js +0 -26
  25. package/dist/src/exposure.js +400 -0
  26. package/dist/src/facts.js +4 -4
  27. package/dist/src/findings/completion.js +44 -21
  28. package/dist/src/findings/emitters.js +22 -33
  29. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  30. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  31. package/dist/src/findings/intra-function.js +3 -3
  32. package/dist/src/findings/reads-and-writes.js +71 -15
  33. package/dist/src/findings/unused-declarations.js +2 -22
  34. package/dist/src/findings/visibility-narrowing.js +14 -20
  35. package/dist/src/framework-lifecycle.js +41 -14
  36. package/dist/src/import-bindings.js +73 -28
  37. package/dist/src/index/sites.js +557 -0
  38. package/dist/src/indexed-access.js +523 -478
  39. package/dist/src/interface-satisfaction.js +1 -1
  40. package/dist/src/inventory.js +18 -0
  41. package/dist/src/keyof-comparisons.js +124 -103
  42. package/dist/src/{components.js → liveness/components.js} +1 -1
  43. package/dist/src/{graph.js → liveness/graph.js} +45 -20
  44. package/dist/src/liveness/matrix.js +99 -0
  45. package/dist/src/liveness/sweep.js +558 -0
  46. package/dist/src/load/expected-module.js +2 -2
  47. package/dist/src/load/specifiers.js +2 -4
  48. package/dist/src/load/synthesize.js +5 -0
  49. package/dist/src/load/workspace-resolution.js +2 -2
  50. package/dist/src/module-declarations.js +70 -85
  51. package/dist/src/overrides.js +134 -112
  52. package/dist/src/paths.js +46 -7
  53. package/dist/src/platform-calls.js +2 -2
  54. package/dist/src/platform-symbols.js +13 -19
  55. package/dist/src/query.js +46 -0
  56. package/dist/src/references/read-rule.js +1 -0
  57. package/dist/src/references/read-rules.js +48 -0
  58. package/dist/src/references/sink.js +237 -0
  59. package/dist/src/references/sources.js +450 -0
  60. package/dist/src/references.js +110 -1285
  61. package/dist/src/roots/configured-declarations.js +3 -0
  62. package/dist/src/roots/published-reach.js +4 -4
  63. package/dist/src/roots/root-set.js +1 -1
  64. package/dist/src/roots/unseen-loads.js +38 -10
  65. package/dist/src/serialization-contract.js +8 -3
  66. package/dist/src/stores.js +4 -2
  67. package/dist/src/symbol-keys.js +89 -83
  68. package/dist/src/tests/file-class.js +76 -0
  69. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  70. package/dist/src/{test-support.js → tests/support.js} +2 -11
  71. package/dist/src/type-parameter-reads.js +178 -139
  72. package/dist/src/unimplemented-callees.js +262 -242
  73. package/dist/src/verbs/analyze.js +3 -3
  74. package/dist/src/verbs/explain.js +75 -132
  75. package/dist/src/verbs/print-retained.js +3 -3
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/alias-chain.ts +0 -7
  79. package/src/amd.ts +130 -0
  80. package/src/analysis.ts +125 -128
  81. package/src/augmentations.ts +42 -28
  82. package/src/bindings.ts +460 -0
  83. package/src/calls.ts +338 -46
  84. package/src/commands/tools.ts +5 -11
  85. package/src/component-files.ts +4 -2
  86. package/src/conformance-record.ts +1 -1
  87. package/src/consumers.ts +8 -11
  88. package/src/contextual-uses.ts +2 -22
  89. package/src/contract/config.generated.ts +4 -1
  90. package/src/contract/contract.generated.ts +2 -2
  91. package/src/contract/report.generated.ts +6 -2
  92. package/src/dependencies.ts +27 -28
  93. package/src/destructuring.ts +1 -31
  94. package/src/element-access.ts +188 -182
  95. package/src/entries/build-scripts.ts +2 -2
  96. package/src/entries/configuration-files.ts +11 -8
  97. package/src/entries/conventions.ts +2 -1
  98. package/src/entries/html-entries.ts +15 -14
  99. package/src/entries/mdx-entries.ts +2 -2
  100. package/src/entries/static-module.ts +2 -1
  101. package/src/exempt.ts +31 -46
  102. package/src/exposure.ts +492 -0
  103. package/src/facts.ts +5 -3
  104. package/src/finding.ts +1 -1
  105. package/src/findings/boundary.ts +9 -0
  106. package/src/findings/completion.ts +48 -23
  107. package/src/findings/emitter.ts +1 -1
  108. package/src/findings/emitters.ts +23 -41
  109. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  110. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  111. package/src/findings/interfaces.ts +3 -3
  112. package/src/findings/intra-function.ts +3 -3
  113. package/src/findings/reads-and-writes.ts +99 -26
  114. package/src/findings/unused-declarations.ts +4 -28
  115. package/src/findings/visibility-narrowing.ts +25 -26
  116. package/src/framework-lifecycle.ts +42 -14
  117. package/src/import-bindings.ts +98 -29
  118. package/src/index/sites.ts +781 -0
  119. package/src/indexed-access.ts +570 -527
  120. package/src/interface-satisfaction.ts +2 -2
  121. package/src/inventory.ts +19 -0
  122. package/src/keyof-comparisons.ts +131 -116
  123. package/src/{components.ts → liveness/components.ts} +3 -3
  124. package/src/{graph.ts → liveness/graph.ts} +93 -27
  125. package/src/liveness/matrix.ts +162 -0
  126. package/src/liveness/sweep.ts +855 -0
  127. package/src/load/expected-module.ts +8 -2
  128. package/src/load/specifiers.ts +8 -5
  129. package/src/load/synthesize.ts +6 -0
  130. package/src/load/workspace-resolution.ts +9 -2
  131. package/src/module-declarations.ts +74 -98
  132. package/src/overrides.ts +162 -134
  133. package/src/paths.ts +54 -8
  134. package/src/platform-calls.ts +3 -3
  135. package/src/platform-symbols.ts +11 -27
  136. package/src/query.ts +81 -0
  137. package/src/references/read-rule.ts +60 -0
  138. package/src/references/read-rules.ts +55 -0
  139. package/src/references/sink.ts +314 -0
  140. package/src/references/sources.ts +632 -0
  141. package/src/references.ts +175 -1733
  142. package/src/report.ts +1 -1
  143. package/src/roots/configured-declarations.ts +3 -0
  144. package/src/roots/published-reach.ts +9 -4
  145. package/src/roots/root-set.ts +1 -1
  146. package/src/roots/unseen-loads.ts +49 -11
  147. package/src/serialization-contract.ts +8 -3
  148. package/src/stores.ts +15 -3
  149. package/src/symbol-keys.ts +102 -102
  150. package/src/tests/file-class.ts +135 -0
  151. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  152. package/src/{test-support.ts → tests/support.ts} +9 -20
  153. package/src/type-parameter-reads.ts +187 -158
  154. package/src/unimplemented-callees.ts +297 -280
  155. package/src/verbs/analyze.ts +3 -2
  156. package/src/verbs/explain.ts +83 -158
  157. package/src/verbs/print-retained.ts +3 -3
  158. package/dist/src/matrix.js +0 -273
  159. package/dist/src/sweep.js +0 -222
  160. package/src/matrix.ts +0 -463
  161. package/src/sweep.ts +0 -361
@@ -1,17 +1,17 @@
1
1
  import { EXIT_CLEAN, EXIT_USAGE } from "../contract/contract.generated.ts";
2
- import { runAnalysis, type RunAnalysis } from "../analysis.ts";
3
- import { listing } from "../components.ts";
2
+ import { productionMode, runAnalysis, type RunAnalysis } from "../analysis.ts";
3
+ import { listing } from "../liveness/components.ts";
4
4
  import type { Cascade } from "../config.ts";
5
5
  import type { CompletedFinding } from "../finding.ts";
6
- import { OUTSIDE, swept as judged } from "../graph.ts";
6
+ import { swept as judged, type Edge, type Graph } from "../liveness/graph.ts";
7
+ import type { Origin } from "../liveness/sweep.ts";
7
8
  import type { InventorySymbol } from "../inventory.ts";
8
- import { referenceKey, type Configured } from "../matrix.ts";
9
9
  import { partialNotes } from "../partial.ts";
10
10
  import { resolvePath } from "../paths.ts";
11
11
  import { positionKey } from "../position.ts";
12
12
  import type { Reference } from "../references.ts";
13
+ import { madeByTestCode } from "../tests/file-class.ts";
13
14
  import { resolve } from "../resolve.ts";
14
- import type { Root } from "../roots/root-set.ts";
15
15
  import { type Verb, type VerbOptions } from "./verb.ts";
16
16
  import { UsageError } from "../usage.ts";
17
17
  import { bytewise } from "../text/compare.ts";
@@ -104,137 +104,28 @@ function subjectOf(
104
104
  return { candidates: partial.slice(0, MAX_PARTIAL_MATCHES) };
105
105
  }
106
106
 
107
- /** Every reference of the run once, a reference several configurations make counted once. */
108
- function referencesOf(configured: readonly Configured[]): readonly Reference[] {
109
- const seen = new Set<string>();
110
- const held: Reference[] = [];
111
- for (const reference of configured.flatMap((one) => one.references)) {
112
- const key = referenceKey(reference);
113
- if (!seen.has(key)) {
114
- seen.add(key);
115
- held.push(reference);
116
- }
117
- }
118
- return held;
119
- }
120
-
121
- /** Every root of the run once per reason, in the order the projects name them. */
122
- function rootsOf(configured: readonly Configured[]): readonly Root[] {
123
- const seen = new Set<string>();
124
- return configured
125
- .flatMap((one) => one.roots)
126
- .filter((root) => {
127
- const key = [root.id, root.kind, root.source].join("\u0000");
128
- if (seen.has(key)) {
129
- return false;
130
- }
131
- seen.add(key);
132
- return true;
133
- });
134
- }
135
-
136
- /**
137
- * One step of a path: a reference, or a reference that reads a module and so reaches a
138
- * declaration the module exports.
139
- */
140
- type Hop =
141
- | { readonly k: "reference"; readonly reference: Reference }
142
- | { readonly k: "export"; readonly reference: Reference; readonly to: string };
143
-
144
- /** Where a path starts: a root of the run, or a reference a loaded consumer makes. */
145
- type Origin =
146
- | { readonly k: "root"; readonly root: Root }
147
- | { readonly k: "consumer"; readonly consumer: string };
148
-
149
- /**
150
- * The shortest path from a root or a loaded consumer to one declaration, walked breadth first
151
- * from every root at once in the order the run names them, a consumer's reference being the
152
- * first hop of a path of its own, and following the references the sweep follows: a
153
- * reference that reads a module reaches what the module exports, and an evaluation or a root
154
- * reaches the module alone. Under `production` no reference a test file made is followed,
155
- * which is the set the report's sweep counts.
156
- */
157
- function shortestPath(
158
- analysis: RunAnalysis,
159
- to: string,
160
- production: boolean,
161
- ): { readonly origin: Origin; readonly path: readonly Hop[] } | undefined {
162
- const graph = analysis.swept.matrix.union;
163
- const out = new Map<string, Reference[]>();
164
- const consumed: (Reference & { readonly consumer: string })[] = [];
165
- for (const reference of referencesOf(analysis.configured)) {
166
- if (production && reference.test) {
167
- continue;
168
- }
169
- const { consumer } = reference;
170
- if (consumer !== undefined) {
171
- consumed.push({ ...reference, consumer });
172
- continue;
173
- }
174
- const held = out.get(reference.from);
175
- if (held === undefined) {
176
- out.set(reference.from, [reference]);
177
- } else {
178
- held.push(reference);
179
- }
180
- }
181
- interface Step {
182
- readonly at: string;
183
- readonly hop: Hop | undefined;
184
- readonly previous: number;
185
- readonly origin: Origin;
186
- }
187
- const steps: Step[] = [];
188
- const seen = new Set<string>();
189
- const expanded = new Set<string>();
190
- const enter = (step: Step): void => {
191
- if (!seen.has(step.at)) {
192
- seen.add(step.at);
193
- steps.push(step);
194
- }
195
- };
196
- const follow = (reference: Reference, previous: number, origin: Origin): void => {
197
- enter({ at: reference.to, hop: { k: "reference", reference }, previous, origin });
198
- const target = graph.at(reference.to);
199
- if (reference.use === "evaluation" || target === OUTSIDE || expanded.has(reference.to)) {
200
- return;
201
- }
202
- expanded.add(reference.to);
203
- for (const exported of graph.exportsOf[target] ?? []) {
204
- const id = graph.symbols[exported]?.id;
205
- if (id !== undefined) {
206
- enter({ at: id, hop: { k: "export", reference, to: id }, previous, origin });
207
- }
208
- }
209
- };
210
- for (const root of rootsOf(analysis.configured)) {
211
- enter({ at: root.id, hop: undefined, previous: -1, origin: { k: "root", root } });
212
- }
213
- // A consumer's reference is one hop long, so it joins the walk where the roots' first
214
- // hops do.
215
- const firstHops = steps.length;
216
- for (let head = 0; ; head += 1) {
217
- if (head === firstHops) {
218
- for (const reference of consumed) {
219
- follow(reference, -1, { k: "consumer", consumer: reference.consumer });
220
- }
221
- }
222
- const step = steps[head];
223
- if (step === undefined) {
224
- break;
225
- }
226
- if (step.at === to) {
227
- const path: Hop[] = [];
228
- for (let at: Step | undefined = step; at?.hop !== undefined; at = steps[at.previous]) {
229
- path.unshift(at.hop);
107
+ /** Where a path starts, as a line names it. */
108
+ function originText(origin: Origin, graph: Graph): string {
109
+ const id = (at: number): string => graph.symbols[at]?.id ?? "";
110
+ switch (origin.k) {
111
+ case "root":
112
+ return `root ${origin.rooted.kind} ${id(origin.rooted.at)}`;
113
+ case "consumed": {
114
+ const reference = graph.references[origin.edge.reference];
115
+ if (reference?.consumer !== undefined) {
116
+ return `consumer ${reference.consumer}`;
230
117
  }
231
- return { origin: step.origin, path };
232
- }
233
- for (const reference of out.get(step.at) ?? []) {
234
- follow(reference, head, step.origin);
118
+ return reference?.unheld === true
119
+ ? `test file no project holds ${reference.from.replace(/#$/u, "")}`
120
+ : `declaration no configuration judges ${reference?.from ?? ""}`;
235
121
  }
122
+ case "exemption":
123
+ return `exemption ${origin.record.class} ${positionKey(origin.record.site)}`;
124
+ case "mark":
125
+ return `suppression of ${id(origin.at)}`;
126
+ case "unanswered":
127
+ return `unanswered question about ${id(origin.at)}`;
236
128
  }
237
- return undefined;
238
129
  }
239
130
 
240
131
  /** What made a reference: a declaration of the target, or a loaded consumer. */
@@ -242,39 +133,74 @@ function madeBy(reference: Reference): string {
242
133
  return reference.consumer === undefined ? reference.from : `consumer ${reference.consumer}`;
243
134
  }
244
135
 
245
- function referenceLine(reference: Reference, tail: string): string {
246
- return ` reference: ${positionKey(reference.position)}\t${reference.use}\t${tail}${reference.test ? "\tfrom a test file" : ""}\n`;
136
+ /**
137
+ * One reference of an answer, marked where test code made it, a test-support file's included,
138
+ * and naming the configurations that make it where they are not every one of the run.
139
+ */
140
+ function referenceLine(
141
+ reference: Reference,
142
+ tail: string,
143
+ support: ReadonlySet<string>,
144
+ configurations: { readonly making: readonly string[]; readonly run: readonly string[] },
145
+ ): string {
146
+ const partial = configurations.making.length < configurations.run.length;
147
+ return ` reference: ${positionKey(reference.position)}\t${reference.use}\t${tail}${madeByTestCode(reference, support) ? "\tfrom a test file" : ""}${partial ? `\tmade in ${configurations.making.join(" ")}` : ""}\n`;
247
148
  }
248
149
 
249
150
  /** Why a declaration no finding names and no exemption held back is live, or how it fell. */
250
151
  function liveness(analysis: RunAnalysis, subject: InventorySymbol, cascade: Cascade): string {
251
- const { sweep } = analysis.swept;
152
+ const { matrix, sweep } = analysis.swept;
153
+ const graph = matrix.union;
252
154
  const candidate = sweep.candidates.find((one) => one.id === subject.id);
253
155
  if (candidate === undefined && !judged(subject.kind)) {
254
156
  return `answer: unjudged\n unjudged: neither liveness relation judges a ${subject.kind}\n`;
255
157
  }
256
158
  let text = `answer: ${candidate === undefined ? "live" : "dead"}\n`;
257
159
  text += ` live under: ${listed(sweep.liveUnder.get(subject.id) ?? [])}\n`;
258
- for (const root of rootsOf(analysis.configured).filter((one) => one.id === subject.id)) {
259
- text += ` root: ${root.kind}${root.source === "" ? "" : `\tnamed by ${root.source}`}\n`;
160
+ const at = graph.at(subject.id);
161
+ const seen = new Set<string>();
162
+ for (const root of graph.rooted.filter((one) => one.at === at)) {
163
+ const key = `${root.kind}\u0000${root.source}`;
164
+ if (!seen.has(key)) {
165
+ seen.add(key);
166
+ text += ` root: ${root.kind}${root.source === "" ? "" : `\tnamed by ${root.source}`}\n`;
167
+ }
260
168
  }
261
- const found =
262
- shortestPath(analysis, subject.id, true) ?? shortestPath(analysis, subject.id, false);
263
- if (found !== undefined && found.path.length > 0) {
264
- const { origin } = found;
265
- text += ` reached from: ${origin.k === "root" ? `root ${origin.root.kind} ${origin.root.id}` : `consumer ${origin.consumer}`}\n`;
266
- for (const hop of found.path) {
267
- text += referenceLine(hop.reference, `${madeBy(hop.reference)} -> ${hop.reference.to}`);
169
+ const support = new Set(analysis.configured.flatMap((one) => one.supportFiles ?? []));
170
+ const line = (edge: Edge, tail: (reference: Reference) => string): string => {
171
+ const reference = graph.references[edge.reference];
172
+ return reference === undefined
173
+ ? ""
174
+ : referenceLine(reference, tail(reference), support, {
175
+ making: graph.configurations[edge.reference] ?? [],
176
+ run: matrix.configurations,
177
+ });
178
+ };
179
+ const found = sweep.path(subject.id);
180
+ if (found !== undefined && found.hops.length > 0) {
181
+ text += ` reached from: ${originText(found.origin, graph)}\n`;
182
+ for (const hop of found.hops) {
183
+ text += line(hop.edge, (reference) => `${madeBy(reference)} -> ${reference.to}`);
268
184
  if (hop.k === "export") {
269
- text += ` export: ${hop.reference.to} -> ${hop.to}\n`;
185
+ const reference = graph.references[hop.edge.reference];
186
+ text += ` export: ${reference?.to ?? ""} -> ${graph.symbols[hop.to]?.id ?? ""}\n`;
270
187
  }
271
188
  }
272
189
  } else if (found === undefined) {
273
- const references = referencesOf(analysis.configured).filter((one) => one.to === subject.id);
274
- text += " unreachable: no path of references reaches it from a root or a loaded consumer\n";
190
+ const references = graph.references.flatMap((one, place) =>
191
+ one.to === subject.id ? [place] : [],
192
+ );
193
+ text +=
194
+ " unreachable: no path of references reaches it from a root, a loaded consumer or a test file no project holds\n";
275
195
  text += ` references: ${String(references.length)}\n`;
276
- for (const reference of references) {
277
- text += referenceLine(reference, `from ${madeBy(reference)}`);
196
+ for (const place of references) {
197
+ const reference = graph.references[place];
198
+ if (reference !== undefined) {
199
+ text += referenceLine(reference, `from ${madeBy(reference)}`, support, {
200
+ making: graph.configurations[place] ?? [],
201
+ run: matrix.configurations,
202
+ });
203
+ }
278
204
  }
279
205
  }
280
206
  if (candidate !== undefined) {
@@ -340,7 +266,7 @@ function explanation(analysis: RunAnalysis, subject: InventorySymbol, cascade: C
340
266
  text += `position: ${subject.id}\n`;
341
267
  text += `configurations: ${listed(analysis.swept.matrix.heldIn[at] ?? [])}\n`;
342
268
  const found = analysis.result.findings.find((one) => positionKey(one.position) === subject.id);
343
- const retained = analysis.swept.retained.filter((one) => one.id === subject.id);
269
+ const retained = analysis.swept.sweep.retained.filter((one) => one.id === subject.id);
344
270
  if (found !== undefined) {
345
271
  text += reported(found);
346
272
  } else if (retained.length > 0) {
@@ -348,7 +274,7 @@ function explanation(analysis: RunAnalysis, subject: InventorySymbol, cascade: C
348
274
  for (const one of retained) {
349
275
  text += ` class: ${one.class}\t${positionKey(one.site)}\t${one.detail}\n`;
350
276
  }
351
- } else if (analysis.swept.heldByUnanswered.includes(subject.id)) {
277
+ } else if (analysis.swept.sweep.held.includes(subject.id)) {
352
278
  text +=
353
279
  "answer: held\n held by: a question the checker did not answer, which could have kept it live, so no finding names it\n";
354
280
  } else {
@@ -360,18 +286,17 @@ function explanation(analysis: RunAnalysis, subject: InventorySymbol, cascade: C
360
286
  /**
361
287
  * Explains one symbol from the analysis the report is built from: why a reported symbol is
362
288
  * reported, which exemption classes held a retained one back, that an unanswered question held
363
- * a held one, and for any other declaration the relations that hold it live and the shortest
364
- * path from a root or a loaded consumer, or the references it has where no path reaches it. A
365
- * request naming no one declaration exits with the usage code and names what partially
289
+ * a held one, and for any other declaration the relations that hold it live and the path the
290
+ * sweep's strongest tier reaching it follows, or the references it has where no path reaches
291
+ * it. A request naming no one declaration exits with the usage code and names what partially
366
292
  * matches it. Where the analysis was partial, the error stream says so.
367
293
  */
368
294
  export const explainVerb: Verb = ({ out, err, host, inputs, scope, openClient, option }) => {
369
295
  const named = namedSymbol(option);
370
296
  const { config, provenance } = resolve(inputs());
371
297
  const scoped = scope();
372
- const analysis = runAnalysis(openClient(config), host, scoped, config, provenance, {
373
- production: true,
374
- });
298
+ const mode = productionMode(config);
299
+ const analysis = runAnalysis(openClient(config), host, scoped, config, provenance, mode);
375
300
  const { subject, candidates } = subjectOf(analysis.swept.matrix.union.symbols, named);
376
301
  const notes = partialNotes({
377
302
  notBuilt: analysis.run.notBuilt,
@@ -1,5 +1,5 @@
1
1
  import { EXIT_CLEAN } from "../contract/contract.generated.ts";
2
- import { runSweep } from "../analysis.ts";
2
+ import { productionMode, runSweep } from "../analysis.ts";
3
3
  import { retainedLines } from "../exempt.ts";
4
4
  import { partialNotes } from "../partial.ts";
5
5
  import { resolvePath } from "../paths.ts";
@@ -19,9 +19,9 @@ export const printRetainedVerb: Verb = ({ out, err, host, inputs, scope, openCli
19
19
  const scoped = scope();
20
20
  const swept = runSweep(openClient(config), host, scoped, config, {
21
21
  marked: [],
22
- mode: { production: true },
22
+ mode: productionMode(config),
23
23
  });
24
- for (const line of retainedLines(swept.matrix.union.symbols, swept.retained)) {
24
+ for (const line of retainedLines(swept.matrix.union.symbols, swept.sweep.retained)) {
25
25
  out.write(`${line}\n`);
26
26
  }
27
27
  for (const note of partialNotes({
@@ -1,273 +0,0 @@
1
- /**
2
- * One run's projects as one matrix: every declaration any project holds, keyed by its
3
- * position and carrying the configurations that hold it, swept once per configuration
4
- * and intersected, with the dead components computed once over every configuration.
5
- */
6
- import { componentsOf } from "./components.js";
7
- import { graphOf, OUTSIDE } from "./graph.js";
8
- import { byPosition } from "./position.js";
9
- import { reachedWithTheTests, sweep, } from "./sweep.js";
10
- /**
11
- * What makes one reference one reference, whichever configuration made it. A consumer's
12
- * position is rendered against its own root, so the consumer is part of what it names.
13
- */
14
- export function referenceKey(reference) {
15
- const { path, line, column } = reference.position;
16
- return [
17
- reference.consumer ?? "",
18
- reference.from,
19
- reference.to,
20
- path,
21
- String(line),
22
- String(column),
23
- reference.use,
24
- ].join("\u0000");
25
- }
26
- /**
27
- * Indexes what the passes answered for each configuration of one run, in the run's order.
28
- * A declaration is keyed by its rendered position, with the fields of the first
29
- * configuration that judges it; one in a file every holder holds only for its references
30
- * is judged by none and is not in the union. One reference at one position of a file
31
- * several configurations compile is one reference; two one configuration makes are two.
32
- */
33
- export function matrixOf(per) {
34
- const symbols = [];
35
- const holders = new Map();
36
- const references = [];
37
- const firstSeen = new Map();
38
- const judged = new Set();
39
- const dependents = new Map();
40
- per.forEach((one, index) => {
41
- for (const [id, after] of one.dependents ?? []) {
42
- if (!dependents.has(id)) {
43
- dependents.set(id, after);
44
- }
45
- }
46
- const referenceOnly = new Set(one.referenceOnly);
47
- for (const symbol of one.symbols) {
48
- const held = holders.get(symbol.id);
49
- if (held === undefined) {
50
- holders.set(symbol.id, [one.configuration]);
51
- }
52
- else if (!held.includes(one.configuration)) {
53
- held.push(one.configuration);
54
- }
55
- if (!referenceOnly.has(symbol.position.path) && !judged.has(symbol.id)) {
56
- judged.add(symbol.id);
57
- symbols.push(symbol);
58
- }
59
- }
60
- for (const reference of one.references) {
61
- const key = referenceKey(reference);
62
- const seen = firstSeen.get(key);
63
- if (seen === undefined) {
64
- firstSeen.set(key, index);
65
- }
66
- else if (seen !== index) {
67
- continue;
68
- }
69
- references.push(reference);
70
- }
71
- });
72
- symbols.sort((a, b) => byPosition(a.position, b.position));
73
- const testUsed = new Set(per.flatMap((one) => one.testUsedFiles ?? []));
74
- return {
75
- configurations: per.map((one) => one.configuration),
76
- graphs: per.map((one) => graphOf(one.symbols, one.references, one.roots, one.testFiles, one.supportFiles, one.ambient)),
77
- union: graphOf(symbols, references, per.flatMap((one) => one.roots), [...new Set(per.flatMap((one) => one.testFiles))], [...new Set(per.flatMap((one) => one.supportFiles ?? []))], new Set(per.flatMap((one) => [...(one.ambient ?? [])]))),
78
- heldIn: symbols.map((symbol) => holders.get(symbol.id) ?? []),
79
- testUsed: symbols.map((symbol) => testUsed.has(symbol.position.path)),
80
- dependents,
81
- };
82
- }
83
- /**
84
- * Which declarations of the run are dead in every configuration that holds them. Each
85
- * configuration is swept on its own graph, since a file it does not compile references nothing
86
- * there and a root it does not hold seeds nothing. A declaration is a candidate when every
87
- * configuration holding it judged it one; its relation is reference counting where every one
88
- * found it so, else reachability, and its counts are the union's. Components are computed once,
89
- * over the union and the intersected dead set, so one dead component has one identifier.
90
- */
91
- export function sweepMatrix(matrix, input) {
92
- const per = matrix.graphs.map((graph) => sweep(graph, input));
93
- const byConfiguration = new Map(matrix.configurations.map((configuration, index) => [
94
- configuration,
95
- new Map((per[index]?.candidates ?? []).map((candidate) => [candidate.id, candidate])),
96
- ]));
97
- const union = matrix.union;
98
- const heldBy = union.symbols.map(() => undefined);
99
- const held = [
100
- ...input.marked,
101
- ...(input.exempt ?? []).map((record) => record.id),
102
- ...(input.unanswered ?? []),
103
- ];
104
- const testReached = reachedWithTheTests(union, held);
105
- // A test uses what it names: a file a test reads through a star re-export or a
106
- // namespace keeps the exports nothing names at their own class.
107
- const testNamed = reachedWithTheTests(union, held, false);
108
- const everywhere = union.symbols.map((symbol, at) => {
109
- const held = matrix.heldIn[at] ?? [];
110
- const found = held.map((configuration) => byConfiguration.get(configuration)?.get(symbol.id));
111
- return held.length === 0 || found.some((candidate) => candidate === undefined)
112
- ? undefined
113
- : found.filter((candidate) => candidate !== undefined);
114
- });
115
- const compiled = compiledReferences(matrix, new Set(union.symbols.flatMap((symbol, at) => (everywhere[at] === undefined ? [] : [symbol.id]))), input.mode);
116
- const judged = everywhere.map((found, at) => compiled.has(union.symbols[at]?.id ?? "") ? undefined : found);
117
- // A configuration that does not hold a test's subject's caller finds the subject dead,
118
- // so a test is of dead code only where every production subject it names is dead in
119
- // every configuration that holds it.
120
- const subjectsDead = (at) => (union.out[at] ?? []).every((edge) => union.test[edge.to] === true ||
121
- union.support[edge.to] === true ||
122
- union.ambient[edge.to] === true ||
123
- union.subject[edge.to] !== true ||
124
- judged[edge.to] !== undefined);
125
- const admittedAt = (at) => judged[at]?.every((candidate) => candidate.testOfDeadCode) === true && subjectsDead(at);
126
- const kept = keptByTests(union, testReached, admittedAt);
127
- const testUsed = testUsedReach(union, union.symbols.map((_symbol, at) => matrix.testUsed[at] === true && testNamed[at] === true), judged.map((one) => one !== undefined));
128
- const candidates = [];
129
- union.symbols.forEach((symbol, at) => {
130
- const all = judged[at];
131
- if (all === undefined) {
132
- return;
133
- }
134
- const held = matrix.heldIn[at] ?? [];
135
- const admitted = admittedAt(at);
136
- const testRefs = union.made[at]?.test ?? 0;
137
- const heldByTests = heldBy[union.parent[at] ?? OUTSIDE] ??
138
- (union.support[at] === true && testRefs > 0 && !admitted ? symbol.id : undefined);
139
- const unreferenced = union.test[at] === true && !admitted && testReached[at] !== true;
140
- heldBy[at] = heldByTests;
141
- const productionRefs = union.made[at]?.production ?? 0;
142
- const counted = all.every((candidate) => candidate.relation === "reference-counting");
143
- candidates.push({
144
- id: symbol.id,
145
- productionRefs,
146
- testRefs,
147
- // The run that judges a test-file declaration counts its test references.
148
- relation: (unreferenced ? productionRefs + testRefs === 0 : counted)
149
- ? "reference-counting"
150
- : "reachability",
151
- testOfDeadCode: admitted,
152
- configurations: held,
153
- heldByTests,
154
- unreferencedTest: unreferenced,
155
- testUsed: testUsed[at] === true,
156
- keptByTests: kept[at] === true,
157
- });
158
- });
159
- const liveUnder = liveAnywhere(union.symbols, per);
160
- return {
161
- liveUnder,
162
- candidates,
163
- components: deadComponents(matrix, { liveUnder, candidates }, new Set()),
164
- };
165
- }
166
- /**
167
- * Every declaration of `dead`, those dead in every configuration that holds them, that a
168
- * configuration's graph references from a judged declaration outside `dead` or already
169
- * kept, until none joins: deleting one would break that configuration's compile. A
170
- * production run counts no reference a test file made.
171
- */
172
- function compiledReferences(matrix, dead, mode) {
173
- const kept = new Set();
174
- for (let changed = true; changed;) {
175
- changed = false;
176
- for (const graph of matrix.graphs) {
177
- graph.out.forEach((edges, from) => {
178
- const id = graph.symbols[from]?.id ?? "";
179
- if (graph.subject[from] !== true || (dead.has(id) && !kept.has(id))) {
180
- return;
181
- }
182
- for (const edge of edges) {
183
- const to = graph.symbols[edge.to]?.id ?? "";
184
- if ((!mode.production || !edge.test) && dead.has(to) && !kept.has(to)) {
185
- kept.add(to);
186
- changed = true;
187
- }
188
- }
189
- });
190
- }
191
- }
192
- return kept;
193
- }
194
- /** Per declaration of the union, whether one of `seeds` is it or reaches it through `dead` declarations. */
195
- function testUsedReach(union, seeds, dead) {
196
- // A file is no candidate, and its top level is what reaches what it alone calls.
197
- const reached = seeds.map((seed, at) => seed && dead[at] === true);
198
- const queue = seeds.flatMap((one, at) => (one ? [at] : []));
199
- for (let at = queue.pop(); at !== undefined; at = queue.pop()) {
200
- for (const edge of union.out[at] ?? []) {
201
- if (dead[edge.to] === true && reached[edge.to] !== true) {
202
- reached[edge.to] = true;
203
- queue.push(edge.to);
204
- }
205
- }
206
- }
207
- return reached;
208
- }
209
- /** Per declaration of the union, whether test code `reached` holds and `admitted` rejects references it. */
210
- function keptByTests(union, reached, admitted) {
211
- const kept = union.symbols.map(() => false);
212
- union.out.forEach((edges, from) => {
213
- const testCode = union.test[from] === true || union.support[from] === true;
214
- if (!testCode || reached[from] !== true || admitted(from)) {
215
- return;
216
- }
217
- for (const edge of edges) {
218
- if (edge.test) {
219
- kept[edge.to] = true;
220
- }
221
- }
222
- });
223
- return kept;
224
- }
225
- /**
226
- * The dead components of one sweep over the union, with the file declarations at `files`
227
- * dead as well: a file that is deleted whole takes the declarations it holds and every
228
- * dead declaration only its top level references with it.
229
- */
230
- export function deadComponents(matrix, result, files) {
231
- const union = matrix.union;
232
- const byId = new Map(result.candidates.map((candidate) => [candidate.id, candidate]));
233
- const candidateAt = (at) => byId.get(union.symbols[at]?.id ?? "");
234
- return componentsOf(union, union.symbols.map((_symbol, at) => {
235
- const candidate = candidateAt(at);
236
- return files.has(at) || (candidate !== undefined && candidate.heldByTests === undefined);
237
- }), union.symbols.map((_symbol, at) => candidateAt(at)?.testOfDeadCode === true), union.symbols.map((_symbol, at) => candidateAt(at)?.unreferencedTest === true), positionsOf(union, matrix.dependents, result.liveUnder));
238
- }
239
- /**
240
- * The file declarations at `paths`, below the target root, that one sweep holds live
241
- * under both relations, the rule a judged declaration is live by.
242
- */
243
- export function liveFilesAt(matrix, result, paths) {
244
- return matrix.union.symbols.filter((symbol) => symbol.kind === "file" &&
245
- paths.has(symbol.position.path) &&
246
- (result.liveUnder.get(symbol.id)?.length ?? 0) === RELATIONS.length);
247
- }
248
- /**
249
- * Each declaration's dependents by its place in the union, and per place whether the
250
- * declaration is live under reachability in some configuration.
251
- */
252
- function positionsOf(union, dependents, liveUnder) {
253
- return {
254
- dependents: union.symbols.map((symbol) => (dependents.get(symbol.id) ?? []).map((id) => union.at(id)).filter((at) => at !== OUTSIDE)),
255
- live: union.symbols.map((symbol) => liveUnder.get(symbol.id)?.includes("reachability") === true),
256
- };
257
- }
258
- /** The relations in the order a declaration's entry lists them. */
259
- const RELATIONS = ["reference-counting", "reachability"];
260
- /**
261
- * Per declaration, in site order, every relation that holds it live in at least one
262
- * configuration.
263
- */
264
- function liveAnywhere(symbols, per) {
265
- const live = new Map();
266
- for (const symbol of symbols) {
267
- const relations = RELATIONS.filter((relation) => per.some((liveness) => (liveness.liveUnder.get(symbol.id) ?? []).includes(relation)));
268
- if (relations.length > 0) {
269
- live.set(symbol.id, relations);
270
- }
271
- }
272
- return live;
273
- }