@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.
- package/dist/src/alias-chain.js +0 -5
- package/dist/src/amd.js +81 -0
- package/dist/src/analysis.js +82 -91
- package/dist/src/augmentations.js +24 -25
- package/dist/src/bindings.js +382 -0
- package/dist/src/calls.js +279 -43
- package/dist/src/commands/tools.js +5 -9
- package/dist/src/component-files.js +3 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +6 -9
- package/dist/src/contextual-uses.js +1 -17
- package/dist/src/contract/config.generated.js +4 -1
- package/dist/src/contract/contract.generated.js +2 -2
- package/dist/src/contract/report.generated.js +6 -2
- package/dist/src/dependencies.js +25 -29
- package/dist/src/destructuring.js +1 -19
- package/dist/src/element-access.js +179 -164
- package/dist/src/entries/build-scripts.js +2 -2
- package/dist/src/entries/configuration-files.js +3 -7
- package/dist/src/entries/conventions.js +2 -2
- package/dist/src/entries/html-entries.js +7 -10
- package/dist/src/entries/mdx-entries.js +2 -2
- package/dist/src/entries/static-module.js +2 -1
- package/dist/src/exempt.js +0 -26
- package/dist/src/exposure.js +400 -0
- package/dist/src/facts.js +4 -4
- package/dist/src/findings/completion.js +44 -21
- package/dist/src/findings/emitters.js +22 -33
- package/dist/src/{implementations.js → findings/implementations.js} +3 -3
- package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
- package/dist/src/findings/intra-function.js +3 -3
- package/dist/src/findings/reads-and-writes.js +71 -15
- package/dist/src/findings/unused-declarations.js +2 -22
- package/dist/src/findings/visibility-narrowing.js +14 -20
- package/dist/src/framework-lifecycle.js +41 -14
- package/dist/src/import-bindings.js +73 -28
- package/dist/src/index/sites.js +557 -0
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/interface-satisfaction.js +1 -1
- package/dist/src/inventory.js +18 -0
- package/dist/src/keyof-comparisons.js +124 -103
- package/dist/src/{components.js → liveness/components.js} +1 -1
- package/dist/src/{graph.js → liveness/graph.js} +45 -20
- package/dist/src/liveness/matrix.js +99 -0
- package/dist/src/liveness/sweep.js +558 -0
- package/dist/src/load/expected-module.js +2 -2
- package/dist/src/load/specifiers.js +2 -4
- package/dist/src/load/synthesize.js +5 -0
- package/dist/src/load/workspace-resolution.js +2 -2
- package/dist/src/module-declarations.js +70 -85
- package/dist/src/overrides.js +134 -112
- package/dist/src/paths.js +46 -7
- package/dist/src/platform-calls.js +2 -2
- package/dist/src/platform-symbols.js +13 -19
- package/dist/src/query.js +46 -0
- package/dist/src/references/read-rule.js +1 -0
- package/dist/src/references/read-rules.js +48 -0
- package/dist/src/references/sink.js +237 -0
- package/dist/src/references/sources.js +450 -0
- package/dist/src/references.js +110 -1285
- package/dist/src/roots/configured-declarations.js +3 -0
- package/dist/src/roots/published-reach.js +4 -4
- package/dist/src/roots/root-set.js +1 -1
- package/dist/src/roots/unseen-loads.js +38 -10
- package/dist/src/serialization-contract.js +8 -3
- package/dist/src/stores.js +4 -2
- package/dist/src/symbol-keys.js +89 -83
- package/dist/src/tests/file-class.js +76 -0
- package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
- package/dist/src/{test-support.js → tests/support.js} +2 -11
- package/dist/src/type-parameter-reads.js +178 -139
- package/dist/src/unimplemented-callees.js +262 -242
- package/dist/src/verbs/analyze.js +3 -3
- package/dist/src/verbs/explain.js +75 -132
- package/dist/src/verbs/print-retained.js +3 -3
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +0 -7
- package/src/amd.ts +130 -0
- package/src/analysis.ts +125 -128
- package/src/augmentations.ts +42 -28
- package/src/bindings.ts +460 -0
- package/src/calls.ts +338 -46
- package/src/commands/tools.ts +5 -11
- package/src/component-files.ts +4 -2
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +8 -11
- package/src/contextual-uses.ts +2 -22
- package/src/contract/config.generated.ts +4 -1
- package/src/contract/contract.generated.ts +2 -2
- package/src/contract/report.generated.ts +6 -2
- package/src/dependencies.ts +27 -28
- package/src/destructuring.ts +1 -31
- package/src/element-access.ts +188 -182
- package/src/entries/build-scripts.ts +2 -2
- package/src/entries/configuration-files.ts +11 -8
- package/src/entries/conventions.ts +2 -1
- package/src/entries/html-entries.ts +15 -14
- package/src/entries/mdx-entries.ts +2 -2
- package/src/entries/static-module.ts +2 -1
- package/src/exempt.ts +31 -46
- package/src/exposure.ts +492 -0
- package/src/facts.ts +5 -3
- package/src/finding.ts +1 -1
- package/src/findings/boundary.ts +9 -0
- package/src/findings/completion.ts +48 -23
- package/src/findings/emitter.ts +1 -1
- package/src/findings/emitters.ts +23 -41
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
- package/src/findings/interfaces.ts +3 -3
- package/src/findings/intra-function.ts +3 -3
- package/src/findings/reads-and-writes.ts +99 -26
- package/src/findings/unused-declarations.ts +4 -28
- package/src/findings/visibility-narrowing.ts +25 -26
- package/src/framework-lifecycle.ts +42 -14
- package/src/import-bindings.ts +98 -29
- package/src/index/sites.ts +781 -0
- package/src/indexed-access.ts +570 -527
- package/src/interface-satisfaction.ts +2 -2
- package/src/inventory.ts +19 -0
- package/src/keyof-comparisons.ts +131 -116
- package/src/{components.ts → liveness/components.ts} +3 -3
- package/src/{graph.ts → liveness/graph.ts} +93 -27
- package/src/liveness/matrix.ts +162 -0
- package/src/liveness/sweep.ts +855 -0
- package/src/load/expected-module.ts +8 -2
- package/src/load/specifiers.ts +8 -5
- package/src/load/synthesize.ts +6 -0
- package/src/load/workspace-resolution.ts +9 -2
- package/src/module-declarations.ts +74 -98
- package/src/overrides.ts +162 -134
- package/src/paths.ts +54 -8
- package/src/platform-calls.ts +3 -3
- package/src/platform-symbols.ts +11 -27
- package/src/query.ts +81 -0
- package/src/references/read-rule.ts +60 -0
- package/src/references/read-rules.ts +55 -0
- package/src/references/sink.ts +314 -0
- package/src/references/sources.ts +632 -0
- package/src/references.ts +175 -1733
- package/src/report.ts +1 -1
- package/src/roots/configured-declarations.ts +3 -0
- package/src/roots/published-reach.ts +9 -4
- package/src/roots/root-set.ts +1 -1
- package/src/roots/unseen-loads.ts +49 -11
- package/src/serialization-contract.ts +8 -3
- package/src/stores.ts +15 -3
- package/src/symbol-keys.ts +102 -102
- package/src/tests/file-class.ts +135 -0
- package/src/{outside-tests.ts → tests/outside.ts} +16 -10
- package/src/{test-support.ts → tests/support.ts} +9 -20
- package/src/type-parameter-reads.ts +187 -158
- package/src/unimplemented-callees.ts +297 -280
- package/src/verbs/analyze.ts +3 -2
- package/src/verbs/explain.ts +83 -158
- package/src/verbs/print-retained.ts +3 -3
- package/dist/src/matrix.js +0 -273
- package/dist/src/sweep.js +0 -222
- package/src/matrix.ts +0 -463
- package/src/sweep.ts +0 -361
package/src/verbs/explain.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
/**
|
|
108
|
-
function
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
246
|
-
|
|
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
|
-
|
|
259
|
-
|
|
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
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
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 =
|
|
274
|
-
|
|
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
|
|
277
|
-
|
|
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.
|
|
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
|
|
364
|
-
*
|
|
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
|
|
373
|
-
|
|
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:
|
|
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({
|
package/dist/src/matrix.js
DELETED
|
@@ -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
|
-
}
|