@cplieger/deadset-ts 8.0.0-dev.17 → 8.0.0-dev.19
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/analysis.js +30 -72
- package/dist/src/augmentations.js +1 -1
- package/dist/src/calls.js +267 -48
- package/dist/src/commands/held.js +499 -0
- package/dist/src/commands/read.js +16 -10
- package/dist/src/commands/shell.js +109 -15
- package/dist/src/commands/tools.js +1540 -149
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contract/contract.generated.js +9 -2
- package/dist/src/entries/conventions.js +7 -6
- package/dist/src/entries/test-runners.js +6 -40
- package/dist/src/entries/workflow-steps.js +202 -28
- package/dist/src/exempt.js +0 -26
- package/dist/src/exposure.js +400 -0
- package/dist/src/findings/completion.js +4 -17
- package/dist/src/findings/emitters.js +22 -33
- package/dist/src/findings/intra-function.js +3 -3
- package/dist/src/findings/non-code-artifacts.js +11 -5
- 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/inventory.js +18 -0
- package/dist/src/{components.js → liveness/components.js} +1 -1
- package/dist/src/{graph.js → liveness/graph.js} +38 -21
- package/dist/src/liveness/matrix.js +99 -0
- package/dist/src/liveness/sweep.js +558 -0
- package/dist/src/query.js +46 -0
- package/dist/src/references/sink.js +87 -1
- package/dist/src/references.js +1 -1
- package/dist/src/roots/manifest.js +47 -4
- package/dist/src/roots/published-reach.js +4 -4
- package/dist/src/roots/root-set.js +14 -16
- package/dist/src/text/named-files.js +257 -34
- package/dist/src/verbs/analyze.js +16 -1
- package/dist/src/verbs/explain.js +73 -152
- package/dist/src/verbs/print-retained.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +54 -108
- package/src/augmentations.ts +1 -1
- package/src/calls.ts +326 -52
- package/src/commands/held.ts +632 -0
- package/src/commands/read.ts +26 -12
- package/src/commands/shell.ts +117 -15
- package/src/commands/tools.ts +1972 -202
- package/src/conformance-record.ts +1 -1
- package/src/contract/contract.generated.ts +9 -2
- package/src/dependencies.ts +1 -1
- package/src/entries/conventions.ts +8 -12
- package/src/entries/test-runners.ts +12 -45
- package/src/entries/workflow-steps.ts +227 -38
- package/src/exempt.ts +31 -46
- package/src/exposure.ts +492 -0
- package/src/file-facts.ts +5 -0
- package/src/finding.ts +1 -1
- package/src/findings/boundary.ts +7 -0
- package/src/findings/completion.ts +5 -19
- package/src/findings/emitters.ts +23 -41
- package/src/findings/interfaces.ts +3 -3
- package/src/findings/intra-function.ts +3 -3
- package/src/findings/non-code-artifacts.ts +16 -5
- 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/interface-satisfaction.ts +1 -1
- package/src/inventory.ts +19 -0
- package/src/{components.ts → liveness/components.ts} +3 -3
- package/src/{graph.ts → liveness/graph.ts} +74 -26
- package/src/liveness/matrix.ts +162 -0
- package/src/liveness/sweep.ts +855 -0
- package/src/query.ts +81 -0
- package/src/references/sink.ts +101 -0
- package/src/references.ts +8 -1
- package/src/roots/manifest.ts +53 -8
- package/src/roots/published-reach.ts +9 -4
- package/src/roots/root-set.ts +18 -16
- package/src/stores.ts +2 -1
- package/src/tests/file-class.ts +19 -2
- package/src/text/named-files.ts +333 -36
- package/src/verbs/analyze.ts +16 -1
- package/src/verbs/explain.ts +81 -199
- package/src/verbs/print-retained.ts +1 -1
- package/dist/src/matrix.js +0 -275
- package/dist/src/sweep.js +0 -234
- package/src/matrix.ts +0 -466
- package/src/sweep.ts +0 -384
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { EXIT_CLEAN, EXIT_USAGE } from "../contract/contract.generated.js";
|
|
2
2
|
import { productionMode, runAnalysis } from "../analysis.js";
|
|
3
|
-
import { listing } from "../components.js";
|
|
4
|
-
import {
|
|
5
|
-
import { referenceKey } from "../matrix.js";
|
|
3
|
+
import { listing } from "../liveness/components.js";
|
|
4
|
+
import { swept as judged } from "../liveness/graph.js";
|
|
6
5
|
import { partialNotes } from "../partial.js";
|
|
7
6
|
import { resolvePath } from "../paths.js";
|
|
8
7
|
import { positionKey } from "../position.js";
|
|
9
|
-
import { madeByTestCode
|
|
8
|
+
import { madeByTestCode } from "../tests/file-class.js";
|
|
10
9
|
import { resolve } from "../resolve.js";
|
|
11
10
|
import {} from "./verb.js";
|
|
12
11
|
import { UsageError } from "../usage.js";
|
|
@@ -80,172 +79,94 @@ function subjectOf(symbols, named) {
|
|
|
80
79
|
.sort(bytewise);
|
|
81
80
|
return { candidates: partial.slice(0, MAX_PARTIAL_MATCHES) };
|
|
82
81
|
}
|
|
83
|
-
/** Every reference of the run once, a reference several configurations make counted once. */
|
|
84
|
-
function referencesOf(configured) {
|
|
85
|
-
const seen = new Set();
|
|
86
|
-
const held = [];
|
|
87
|
-
for (const reference of configured.flatMap((one) => one.references)) {
|
|
88
|
-
const key = referenceKey(reference);
|
|
89
|
-
if (!seen.has(key)) {
|
|
90
|
-
seen.add(key);
|
|
91
|
-
held.push(reference);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return held;
|
|
95
|
-
}
|
|
96
|
-
/** Every root of the run once per reason, in the order the projects name them. */
|
|
97
|
-
function rootsOf(configured) {
|
|
98
|
-
const seen = new Set();
|
|
99
|
-
return configured
|
|
100
|
-
.flatMap((one) => one.roots)
|
|
101
|
-
.filter((root) => {
|
|
102
|
-
const key = [root.id, root.kind, root.source].join("\u0000");
|
|
103
|
-
if (seen.has(key)) {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
seen.add(key);
|
|
107
|
-
return true;
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* The shortest path from a root, a loaded consumer or a test file no project holds to one
|
|
112
|
-
* declaration, walked breadth first from every root at once in the order the run names them,
|
|
113
|
-
* a reference of a consumer or of such a file being the first hop of a path of its own, as
|
|
114
|
-
* the sweep seeds them, and following the references the sweep follows: a
|
|
115
|
-
* reference that reads a module reaches what the module exports, and an evaluation or a root
|
|
116
|
-
* reaches the module alone. Under `production` no reference a test file made is followed,
|
|
117
|
-
* which is the set the report's sweep counts.
|
|
118
|
-
*/
|
|
119
|
-
function shortestPath(analysis, to, mode, support) {
|
|
120
|
-
const graph = analysis.swept.matrix.union;
|
|
121
|
-
const out = new Map();
|
|
122
|
-
const consumed = [];
|
|
123
|
-
for (const reference of referencesOf(analysis.configured)) {
|
|
124
|
-
if (withheldBy(mode, reference, support)) {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
const { consumer } = reference;
|
|
128
|
-
if (consumer !== undefined) {
|
|
129
|
-
consumed.push({ reference, origin: { k: "consumer", consumer } });
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
if (reference.unheld === true) {
|
|
133
|
-
consumed.push({
|
|
134
|
-
reference,
|
|
135
|
-
origin: { k: "unheld", file: reference.from.replace(/#$/u, "") },
|
|
136
|
-
});
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
const held = out.get(reference.from);
|
|
140
|
-
if (held === undefined) {
|
|
141
|
-
out.set(reference.from, [reference]);
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
held.push(reference);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
const steps = [];
|
|
148
|
-
const seen = new Set();
|
|
149
|
-
const expanded = new Set();
|
|
150
|
-
const enter = (step) => {
|
|
151
|
-
if (!seen.has(step.at)) {
|
|
152
|
-
seen.add(step.at);
|
|
153
|
-
steps.push(step);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
const follow = (reference, previous, origin) => {
|
|
157
|
-
enter({ at: reference.to, hop: { k: "reference", reference }, previous, origin });
|
|
158
|
-
const target = graph.at(reference.to);
|
|
159
|
-
if (reference.use === "evaluation" || target === OUTSIDE || expanded.has(reference.to)) {
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
expanded.add(reference.to);
|
|
163
|
-
for (const exported of graph.exportsOf[target] ?? []) {
|
|
164
|
-
const id = graph.symbols[exported]?.id;
|
|
165
|
-
if (id !== undefined) {
|
|
166
|
-
enter({ at: id, hop: { k: "export", reference, to: id }, previous, origin });
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
for (const root of rootsOf(analysis.configured)) {
|
|
171
|
-
enter({ at: root.id, hop: undefined, previous: -1, origin: { k: "root", root } });
|
|
172
|
-
}
|
|
173
|
-
// A consumed reference is one hop long, so it joins the walk where the roots' first hops do.
|
|
174
|
-
const firstHops = steps.length;
|
|
175
|
-
for (let head = 0;; head += 1) {
|
|
176
|
-
if (head === firstHops) {
|
|
177
|
-
for (const { reference, origin } of consumed) {
|
|
178
|
-
follow(reference, -1, origin);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const step = steps[head];
|
|
182
|
-
if (step === undefined) {
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
if (step.at === to) {
|
|
186
|
-
const path = [];
|
|
187
|
-
for (let at = step; at?.hop !== undefined; at = steps[at.previous]) {
|
|
188
|
-
path.unshift(at.hop);
|
|
189
|
-
}
|
|
190
|
-
return { origin: step.origin, path };
|
|
191
|
-
}
|
|
192
|
-
for (const reference of out.get(step.at) ?? []) {
|
|
193
|
-
follow(reference, head, step.origin);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return undefined;
|
|
197
|
-
}
|
|
198
82
|
/** Where a path starts, as a line names it. */
|
|
199
|
-
function originText(origin) {
|
|
83
|
+
function originText(origin, graph) {
|
|
84
|
+
const id = (at) => graph.symbols[at]?.id ?? "";
|
|
200
85
|
switch (origin.k) {
|
|
201
86
|
case "root":
|
|
202
|
-
return `root ${origin.
|
|
203
|
-
case "
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
87
|
+
return `root ${origin.rooted.kind} ${id(origin.rooted.at)}`;
|
|
88
|
+
case "consumed": {
|
|
89
|
+
const reference = graph.references[origin.edge.reference];
|
|
90
|
+
if (reference?.consumer !== undefined) {
|
|
91
|
+
return `consumer ${reference.consumer}`;
|
|
92
|
+
}
|
|
93
|
+
return reference?.unheld === true
|
|
94
|
+
? `test file no project holds ${reference.from.replace(/#$/u, "")}`
|
|
95
|
+
: `declaration no configuration judges ${reference?.from ?? ""}`;
|
|
96
|
+
}
|
|
97
|
+
case "exemption":
|
|
98
|
+
return `exemption ${origin.record.class} ${positionKey(origin.record.site)}`;
|
|
99
|
+
case "mark":
|
|
100
|
+
return `suppression of ${id(origin.at)}`;
|
|
101
|
+
case "unanswered":
|
|
102
|
+
return `unanswered question about ${id(origin.at)}`;
|
|
207
103
|
}
|
|
208
104
|
}
|
|
209
105
|
/** What made a reference: a declaration of the target, or a loaded consumer. */
|
|
210
106
|
function madeBy(reference) {
|
|
211
107
|
return reference.consumer === undefined ? reference.from : `consumer ${reference.consumer}`;
|
|
212
108
|
}
|
|
213
|
-
/**
|
|
214
|
-
|
|
215
|
-
|
|
109
|
+
/**
|
|
110
|
+
* One reference of an answer, marked where test code made it, a test-support file's included,
|
|
111
|
+
* and naming the configurations that make it where they are not every one of the run.
|
|
112
|
+
*/
|
|
113
|
+
function referenceLine(reference, tail, support, configurations) {
|
|
114
|
+
const partial = configurations.making.length < configurations.run.length;
|
|
115
|
+
return ` reference: ${positionKey(reference.position)}\t${reference.use}\t${tail}${madeByTestCode(reference, support) ? "\tfrom a test file" : ""}${partial ? `\tmade in ${configurations.making.join(" ")}` : ""}\n`;
|
|
216
116
|
}
|
|
217
117
|
/** Why a declaration no finding names and no exemption held back is live, or how it fell. */
|
|
218
|
-
function liveness(analysis, subject, cascade
|
|
219
|
-
const { sweep } = analysis.swept;
|
|
118
|
+
function liveness(analysis, subject, cascade) {
|
|
119
|
+
const { matrix, sweep } = analysis.swept;
|
|
120
|
+
const graph = matrix.union;
|
|
220
121
|
const candidate = sweep.candidates.find((one) => one.id === subject.id);
|
|
221
122
|
if (candidate === undefined && !judged(subject.kind)) {
|
|
222
123
|
return `answer: unjudged\n unjudged: neither liveness relation judges a ${subject.kind}\n`;
|
|
223
124
|
}
|
|
224
125
|
let text = `answer: ${candidate === undefined ? "live" : "dead"}\n`;
|
|
225
126
|
text += ` live under: ${listed(sweep.liveUnder.get(subject.id) ?? [])}\n`;
|
|
226
|
-
|
|
227
|
-
|
|
127
|
+
const at = graph.at(subject.id);
|
|
128
|
+
const seen = new Set();
|
|
129
|
+
for (const root of graph.rooted.filter((one) => one.at === at)) {
|
|
130
|
+
const key = `${root.kind}\u0000${root.source}`;
|
|
131
|
+
if (!seen.has(key)) {
|
|
132
|
+
seen.add(key);
|
|
133
|
+
text += ` root: ${root.kind}${root.source === "" ? "" : `\tnamed by ${root.source}`}\n`;
|
|
134
|
+
}
|
|
228
135
|
}
|
|
229
136
|
const support = new Set(analysis.configured.flatMap((one) => one.supportFiles ?? []));
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
137
|
+
const line = (edge, tail) => {
|
|
138
|
+
const reference = graph.references[edge.reference];
|
|
139
|
+
return reference === undefined
|
|
140
|
+
? ""
|
|
141
|
+
: referenceLine(reference, tail(reference), support, {
|
|
142
|
+
making: graph.configurations[edge.reference] ?? [],
|
|
143
|
+
run: matrix.configurations,
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
const found = sweep.path(subject.id);
|
|
147
|
+
if (found !== undefined && found.hops.length > 0) {
|
|
148
|
+
text += ` reached from: ${originText(found.origin, graph)}\n`;
|
|
149
|
+
for (const hop of found.hops) {
|
|
150
|
+
text += line(hop.edge, (reference) => `${madeBy(reference)} -> ${reference.to}`);
|
|
237
151
|
if (hop.k === "export") {
|
|
238
|
-
|
|
152
|
+
const reference = graph.references[hop.edge.reference];
|
|
153
|
+
text += ` export: ${reference?.to ?? ""} -> ${graph.symbols[hop.to]?.id ?? ""}\n`;
|
|
239
154
|
}
|
|
240
155
|
}
|
|
241
156
|
}
|
|
242
157
|
else if (found === undefined) {
|
|
243
|
-
const references =
|
|
158
|
+
const references = graph.references.flatMap((one, place) => one.to === subject.id ? [place] : []);
|
|
244
159
|
text +=
|
|
245
160
|
" unreachable: no path of references reaches it from a root, a loaded consumer or a test file no project holds\n";
|
|
246
161
|
text += ` references: ${String(references.length)}\n`;
|
|
247
|
-
for (const
|
|
248
|
-
|
|
162
|
+
for (const place of references) {
|
|
163
|
+
const reference = graph.references[place];
|
|
164
|
+
if (reference !== undefined) {
|
|
165
|
+
text += referenceLine(reference, `from ${madeBy(reference)}`, support, {
|
|
166
|
+
making: graph.configurations[place] ?? [],
|
|
167
|
+
run: matrix.configurations,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
249
170
|
}
|
|
250
171
|
}
|
|
251
172
|
if (candidate !== undefined) {
|
|
@@ -294,7 +215,7 @@ function namedElsewhere(findings, subject) {
|
|
|
294
215
|
.join("");
|
|
295
216
|
}
|
|
296
217
|
/** The one explanation that applies to a declaration, built whole before it is written. */
|
|
297
|
-
function explanation(analysis, subject, cascade
|
|
218
|
+
function explanation(analysis, subject, cascade) {
|
|
298
219
|
const graph = analysis.swept.matrix.union;
|
|
299
220
|
const at = graph.at(subject.id);
|
|
300
221
|
let text = `symbol: ${subject.ref}\n`;
|
|
@@ -302,7 +223,7 @@ function explanation(analysis, subject, cascade, mode) {
|
|
|
302
223
|
text += `position: ${subject.id}\n`;
|
|
303
224
|
text += `configurations: ${listed(analysis.swept.matrix.heldIn[at] ?? [])}\n`;
|
|
304
225
|
const found = analysis.result.findings.find((one) => positionKey(one.position) === subject.id);
|
|
305
|
-
const retained = analysis.swept.retained.filter((one) => one.id === subject.id);
|
|
226
|
+
const retained = analysis.swept.sweep.retained.filter((one) => one.id === subject.id);
|
|
306
227
|
if (found !== undefined) {
|
|
307
228
|
text += reported(found);
|
|
308
229
|
}
|
|
@@ -312,22 +233,22 @@ function explanation(analysis, subject, cascade, mode) {
|
|
|
312
233
|
text += ` class: ${one.class}\t${positionKey(one.site)}\t${one.detail}\n`;
|
|
313
234
|
}
|
|
314
235
|
}
|
|
315
|
-
else if (analysis.swept.
|
|
236
|
+
else if (analysis.swept.sweep.held.includes(subject.id)) {
|
|
316
237
|
text +=
|
|
317
238
|
"answer: held\n held by: a question the checker did not answer, which could have kept it live, so no finding names it\n";
|
|
318
239
|
}
|
|
319
240
|
else {
|
|
320
|
-
text += liveness(analysis, subject, cascade
|
|
241
|
+
text += liveness(analysis, subject, cascade);
|
|
321
242
|
}
|
|
322
243
|
return text + namedElsewhere(analysis.result.findings, subject);
|
|
323
244
|
}
|
|
324
245
|
/**
|
|
325
246
|
* Explains one symbol from the analysis the report is built from: why a reported symbol is
|
|
326
247
|
* reported, which exemption classes held a retained one back, that an unanswered question held
|
|
327
|
-
* a held one, and for any other declaration the relations that hold it live and the
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
248
|
+
* a held one, and for any other declaration the relations that hold it live and the path the
|
|
249
|
+
* sweep's strongest tier reaching it follows, or the references it has where no path reaches
|
|
250
|
+
* it. A request naming no one declaration exits with the usage code and names what partially
|
|
251
|
+
* matches it. Where the analysis was partial, the error stream says so.
|
|
331
252
|
*/
|
|
332
253
|
export const explainVerb = ({ out, err, host, inputs, scope, openClient, option }) => {
|
|
333
254
|
const named = namedSymbol(option);
|
|
@@ -351,7 +272,7 @@ export const explainVerb = ({ out, err, host, inputs, scope, openClient, option
|
|
|
351
272
|
}
|
|
352
273
|
}
|
|
353
274
|
else {
|
|
354
|
-
out.write(explanation(analysis, subject, config.reporters.cascade
|
|
275
|
+
out.write(explanation(analysis, subject, config.reporters.cascade));
|
|
355
276
|
}
|
|
356
277
|
for (const note of notes) {
|
|
357
278
|
diagnose(err, note);
|
|
@@ -20,7 +20,7 @@ export const printRetainedVerb = ({ out, err, host, inputs, scope, openClient })
|
|
|
20
20
|
marked: [],
|
|
21
21
|
mode: productionMode(config),
|
|
22
22
|
});
|
|
23
|
-
for (const line of retainedLines(swept.matrix.union.symbols, swept.retained)) {
|
|
23
|
+
for (const line of retainedLines(swept.matrix.union.symbols, swept.sweep.retained)) {
|
|
24
24
|
out.write(`${line}\n`);
|
|
25
25
|
}
|
|
26
26
|
for (const note of partialNotes({
|
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/analysis.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from "./roots/configured-declarations.ts";
|
|
18
18
|
import { decorator } from "./decorator.ts";
|
|
19
19
|
import { configurationNeeds, dependenciesOf, projectNeeds } from "./dependencies.ts";
|
|
20
|
+
import { carriersOf, NO_CARRIERS, type Carriers } from "./exposure.ts";
|
|
20
21
|
import { deprecatedDeclarations, type Deprecation } from "./deprecation.ts";
|
|
21
22
|
import { moduleIdentity, type LoadedConsumer, type NotBuilt } from "./load/discover.ts";
|
|
22
23
|
import type { LibraryUnfound } from "./load/classify.ts";
|
|
@@ -35,9 +36,7 @@ import {
|
|
|
35
36
|
heldRecords,
|
|
36
37
|
disabledClasses,
|
|
37
38
|
exemptionsOf,
|
|
38
|
-
holdingWhile,
|
|
39
39
|
isUnansweredRecord,
|
|
40
|
-
retainedIn,
|
|
41
40
|
writtenRecords,
|
|
42
41
|
type Detector,
|
|
43
42
|
type Detectors,
|
|
@@ -57,15 +56,7 @@ import { intraFunctionFacts, projectParts, type ProjectParts } from "./intra-fun
|
|
|
57
56
|
import { type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
58
57
|
import { inventory } from "./inventory/enumerate.ts";
|
|
59
58
|
import { readManifest, type ManifestEntry } from "./roots/manifest.ts";
|
|
60
|
-
import {
|
|
61
|
-
liveFilesAt,
|
|
62
|
-
matrixOf,
|
|
63
|
-
referenceKey,
|
|
64
|
-
sweepMatrix,
|
|
65
|
-
type Configured,
|
|
66
|
-
type Matrix,
|
|
67
|
-
type SweepResult,
|
|
68
|
-
} from "./matrix.ts";
|
|
59
|
+
import { matrixOf, referenceKey, type Configured, type Matrix } from "./liveness/matrix.ts";
|
|
69
60
|
import {
|
|
70
61
|
ancestors,
|
|
71
62
|
dirnamePath,
|
|
@@ -108,7 +99,9 @@ import { accessorsOf, storesOf } from "./stores.ts";
|
|
|
108
99
|
import { isPartKind, type SuppressionRecord, type Suppressions, type Verdict } from "./suppress.ts";
|
|
109
100
|
import { bindRows, readBaseline, readIgnoreFile, type Recorded } from "./suppress-file.ts";
|
|
110
101
|
import { inlineDirectives, inlineSuppressions, type InlineDirective } from "./suppress-inline.ts";
|
|
111
|
-
import
|
|
102
|
+
import { sweep, type Sweep, type SweepPolicy } from "./liveness/sweep.ts";
|
|
103
|
+
import type { Exemption } from "./exempt.ts";
|
|
104
|
+
import type { Mode } from "./tests/file-class.ts";
|
|
112
105
|
import { readTemplates, templateField } from "./template-field.ts";
|
|
113
106
|
import { bytewise } from "./text/compare.ts";
|
|
114
107
|
import { holdsManifest, manifestAt } from "./fs/manifest.ts";
|
|
@@ -434,27 +427,13 @@ export function productionMode(config: PartialConfig): Mode {
|
|
|
434
427
|
/** The run's sweep, beside the matrix it judged. */
|
|
435
428
|
export interface RunSweep {
|
|
436
429
|
readonly matrix: Matrix;
|
|
437
|
-
readonly sweep:
|
|
438
|
-
/** The exemption records that hold under the sweep. */
|
|
439
|
-
readonly exempt: readonly Exemption[];
|
|
440
|
-
/**
|
|
441
|
-
* The exemption records that held back a declaration some configuration of the run
|
|
442
|
-
* would otherwise judge dead, in the declarations' site order.
|
|
443
|
-
*/
|
|
444
|
-
readonly retained: readonly Exemption[];
|
|
430
|
+
readonly sweep: Sweep;
|
|
445
431
|
/** The derived configurations the run dropped. */
|
|
446
432
|
readonly notBuilt: readonly NotBuilt[];
|
|
447
433
|
/** Every question the checker could not answer, each once. */
|
|
448
434
|
readonly unanswered: readonly UnansweredQuestion[];
|
|
449
|
-
/**
|
|
450
|
-
* Every declaration an unanswered question could have kept live, and every one only
|
|
451
|
-
* such a declaration keeps live. The run reports none of them.
|
|
452
|
-
*/
|
|
453
|
-
readonly heldByUnanswered: readonly string[];
|
|
454
435
|
/** The lines of the units type errors skipped, on which the run reports nothing. */
|
|
455
436
|
readonly skipped: readonly SkippedUnit[];
|
|
456
|
-
/** The component files the run read, by path below the target root, each once. */
|
|
457
|
-
readonly componentFiles: readonly string[];
|
|
458
437
|
}
|
|
459
438
|
|
|
460
439
|
/** One `<script` start tag of a component file the run read no block for. */
|
|
@@ -527,6 +506,8 @@ interface ReadRun<Extra> {
|
|
|
527
506
|
readonly projects: readonly SweptProject<Extra>[];
|
|
528
507
|
/** Every declared consumer, each loaded, in the scope's order. */
|
|
529
508
|
readonly consumers: readonly LoadedConsumer[];
|
|
509
|
+
/** Whether the scope declares a consumer, loaded or not. */
|
|
510
|
+
readonly declaresConsumers: boolean;
|
|
530
511
|
/** The derived configurations the run dropped. */
|
|
531
512
|
readonly notBuilt: readonly NotBuilt[];
|
|
532
513
|
/** Every question the checker could not answer, each once. */
|
|
@@ -826,6 +807,7 @@ function readRun<Extra>(
|
|
|
826
807
|
],
|
|
827
808
|
projects,
|
|
828
809
|
consumers: read.plan.discovery.consumers,
|
|
810
|
+
declaresConsumers: scope.consumers.length > 0,
|
|
829
811
|
notBuilt: read.classified.notBuilt,
|
|
830
812
|
unanswered: read.unanswered,
|
|
831
813
|
componentsUnread: read.plan.workspace?.componentsUnread ?? [],
|
|
@@ -840,33 +822,6 @@ function readRun<Extra>(
|
|
|
840
822
|
};
|
|
841
823
|
}
|
|
842
824
|
|
|
843
|
-
/**
|
|
844
|
-
* The run swept under one set of marks with the records that hold. A record a component
|
|
845
|
-
* file's markup holds counts only while the file is live, and what it holds may be what
|
|
846
|
-
* keeps the file live, so each file's markup joins only once a sweep without it finds the
|
|
847
|
-
* file live, until a sweep finds no further file live.
|
|
848
|
-
*/
|
|
849
|
-
function holdingSweep<Extra>(
|
|
850
|
-
read: ReadRun<Extra>,
|
|
851
|
-
input: Pick<SweepInput, "marked" | "mode">,
|
|
852
|
-
): { readonly swept: SweepInput; readonly result: SweepResult } {
|
|
853
|
-
const paths = new Set(read.exempt.flatMap((record) => record.whileLive ?? []));
|
|
854
|
-
let live = new Set<string>();
|
|
855
|
-
for (;;) {
|
|
856
|
-
const swept: SweepInput = {
|
|
857
|
-
...input,
|
|
858
|
-
exempt: holdingWhile(read.exempt, live),
|
|
859
|
-
unanswered: read.unansweredHeld,
|
|
860
|
-
};
|
|
861
|
-
const result = sweepMatrix(read.matrix, swept);
|
|
862
|
-
const next = new Set(liveFilesAt(read.matrix, result, paths).map((one) => one.position.path));
|
|
863
|
-
if (next.size === live.size) {
|
|
864
|
-
return { swept, result };
|
|
865
|
-
}
|
|
866
|
-
live = next;
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
|
|
870
825
|
/**
|
|
871
826
|
* The test references exemption evidence written in a test file makes: each is a read of
|
|
872
827
|
* the declaration it would retain, made by the test file at the evidence's site.
|
|
@@ -939,62 +894,27 @@ function writtenOnly<Extra>(read: ReadRun<Extra>, mode: Mode): ReadonlySet<strin
|
|
|
939
894
|
return new Set([...written].filter((id) => !readSome.has(id)));
|
|
940
895
|
}
|
|
941
896
|
|
|
942
|
-
/**
|
|
943
|
-
* The sweep of a run read once, under one set of marks. Where a question went unanswered,
|
|
944
|
-
* the run is swept once more without what that question could have kept live and without
|
|
945
|
-
* the references that stood in for its answer, and every declaration that sweep judges
|
|
946
|
-
* dead and this one judges live, or dead by another rule, is held by the gap too, as is
|
|
947
|
-
* every declaration that makes such a reference.
|
|
948
|
-
*/
|
|
897
|
+
/** The sweep of a run read once, under one set of marks. */
|
|
949
898
|
function sweptOf<Extra>(
|
|
950
899
|
read: ReadRun<Extra>,
|
|
951
|
-
input: Pick<
|
|
900
|
+
input: Pick<SweepPolicy, "marked" | "mode">,
|
|
952
901
|
): RunSweep {
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
if (held.size > 0) {
|
|
957
|
-
// What a declaration reads where the checker did not answer is unknown, so a rule
|
|
958
|
-
// over what it reads, the test of dead code among them, cannot judge it.
|
|
959
|
-
for (const { configured } of read.projects) {
|
|
960
|
-
for (const reference of configured.references) {
|
|
961
|
-
if (reference.guessed === true) {
|
|
962
|
-
held.add(reference.from);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
const dead = new Map(result.candidates.map((candidate) => [candidate.id, candidate]));
|
|
967
|
-
const answered = matrixOf(
|
|
968
|
-
read.projects.map(({ configured }) => ({
|
|
969
|
-
...configured,
|
|
970
|
-
references: configured.references.filter((one) => one.guessed !== true),
|
|
971
|
-
})),
|
|
972
|
-
);
|
|
973
|
-
const bare: SweepInput = {
|
|
974
|
-
...swept,
|
|
975
|
-
exempt: exempt.filter((record) => !isUnansweredRecord(record)),
|
|
976
|
-
unanswered: [],
|
|
977
|
-
};
|
|
978
|
-
for (const candidate of sweepMatrix(answered, bare).candidates) {
|
|
979
|
-
const kept = dead.get(candidate.id);
|
|
980
|
-
if (
|
|
981
|
-
kept?.testOfDeadCode !== candidate.testOfDeadCode ||
|
|
982
|
-
kept.relation !== candidate.relation
|
|
983
|
-
) {
|
|
984
|
-
held.add(candidate.id);
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
}
|
|
902
|
+
const componentFiles = [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(
|
|
903
|
+
bytewise,
|
|
904
|
+
);
|
|
988
905
|
return {
|
|
989
906
|
matrix: read.matrix,
|
|
990
|
-
sweep:
|
|
991
|
-
|
|
992
|
-
|
|
907
|
+
sweep: sweep(read.matrix, {
|
|
908
|
+
...input,
|
|
909
|
+
exempt: read.exempt,
|
|
910
|
+
unanswered: read.unansweredHeld,
|
|
911
|
+
componentFiles,
|
|
912
|
+
unseenCallers: !read.declaresConsumers,
|
|
913
|
+
writeOnly: writtenOnly(read, input.mode),
|
|
914
|
+
}),
|
|
993
915
|
notBuilt: read.notBuilt,
|
|
994
916
|
unanswered: read.unanswered,
|
|
995
|
-
heldByUnanswered: [...held],
|
|
996
917
|
skipped: read.projects.flatMap((one) => one.skipped),
|
|
997
|
-
componentFiles: [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(bytewise),
|
|
998
918
|
};
|
|
999
919
|
}
|
|
1000
920
|
|
|
@@ -1011,7 +931,7 @@ export function runSweep(
|
|
|
1011
931
|
host: Host,
|
|
1012
932
|
scope: Scope,
|
|
1013
933
|
config: ResolvedConfig,
|
|
1014
|
-
input: Pick<
|
|
934
|
+
input: Pick<SweepPolicy, "marked" | "mode">,
|
|
1015
935
|
detectors: Detectors = DETECTORS,
|
|
1016
936
|
): RunSweep {
|
|
1017
937
|
return sweptOf(
|
|
@@ -1035,6 +955,7 @@ interface EmitterExtra {
|
|
|
1035
955
|
readonly declarationsNamed: readonly boolean[];
|
|
1036
956
|
readonly parts: ProjectParts;
|
|
1037
957
|
readonly unseenLoads: readonly UnseenLoad[];
|
|
958
|
+
readonly carriers: Carriers;
|
|
1038
959
|
}
|
|
1039
960
|
|
|
1040
961
|
/** The project's own files below the target root that are no test file. */
|
|
@@ -1090,6 +1011,8 @@ export function readEmitterRun(
|
|
|
1090
1011
|
declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
|
|
1091
1012
|
parts: projectParts(project, read.held, references, targetRoot),
|
|
1092
1013
|
unseenLoads,
|
|
1014
|
+
// Only a closed world reports an unnecessary export, which is all the carriers decide.
|
|
1015
|
+
carriers: config.consumers.complete ? carriersOf(project, read.held) : NO_CARRIERS,
|
|
1093
1016
|
}),
|
|
1094
1017
|
);
|
|
1095
1018
|
// Whether a declaration carries the marker decides its code, so one whose answer went
|
|
@@ -1156,7 +1079,7 @@ export function emitterInputOver(
|
|
|
1156
1079
|
scope: Scope,
|
|
1157
1080
|
config: ResolvedConfig,
|
|
1158
1081
|
read: ReadRun<EmitterExtra>,
|
|
1159
|
-
input: Pick<
|
|
1082
|
+
input: Pick<SweepPolicy, "marked" | "mode">,
|
|
1160
1083
|
selfCheck: SelfCheckFacts,
|
|
1161
1084
|
): EmitterInput {
|
|
1162
1085
|
const targetRoot = scope.target.path;
|
|
@@ -1173,7 +1096,7 @@ export function emitterInputOver(
|
|
|
1173
1096
|
support: new Set(one.configured.supportFiles ?? []),
|
|
1174
1097
|
accessors: one.extra.accessors,
|
|
1175
1098
|
})),
|
|
1176
|
-
swept.exempt,
|
|
1099
|
+
swept.sweep.exempt,
|
|
1177
1100
|
input.mode,
|
|
1178
1101
|
),
|
|
1179
1102
|
dependencies: dependenciesOf(
|
|
@@ -1193,6 +1116,16 @@ export function emitterInputOver(
|
|
|
1193
1116
|
generated: new Set(projects.flatMap((one) => [...one.extra.generated.keys()])),
|
|
1194
1117
|
inOperands: new Set(projects.flatMap((one) => one.extra.inOperands)),
|
|
1195
1118
|
unseenLoads: projects.flatMap((one) => one.extra.unseenLoads),
|
|
1119
|
+
unwrittenRuns: new Set(
|
|
1120
|
+
projects.flatMap((one) =>
|
|
1121
|
+
one.heldScripts
|
|
1122
|
+
.filter((held) => held.unwritten)
|
|
1123
|
+
.map(
|
|
1124
|
+
(held) =>
|
|
1125
|
+
relativePath(resolvePath(host.workingDirectory(), targetRoot), held.dir) ?? ".",
|
|
1126
|
+
),
|
|
1127
|
+
),
|
|
1128
|
+
),
|
|
1196
1129
|
},
|
|
1197
1130
|
boundary: {
|
|
1198
1131
|
consumers: {
|
|
@@ -1203,12 +1136,23 @@ export function emitterInputOver(
|
|
|
1203
1136
|
edges: readEdgeSides(host, targetRoot),
|
|
1204
1137
|
filledOutside: new Set(projects.flatMap((one) => one.filledOutside)),
|
|
1205
1138
|
possiblyLoaded: new Set(projects.flatMap((one) => one.possiblyLoaded)),
|
|
1139
|
+
carriers: {
|
|
1140
|
+
named: new Map(
|
|
1141
|
+
[
|
|
1142
|
+
...Map.groupBy(
|
|
1143
|
+
projects.flatMap((one) => [...one.extra.carriers.named]),
|
|
1144
|
+
([id]) => id,
|
|
1145
|
+
),
|
|
1146
|
+
].map(([id, entries]) => [id, [...new Set(entries.flatMap(([, named]) => named))]]),
|
|
1147
|
+
),
|
|
1148
|
+
unread: new Set(projects.flatMap((one) => [...one.extra.carriers.unread])),
|
|
1149
|
+
},
|
|
1206
1150
|
},
|
|
1207
1151
|
selfCheck,
|
|
1208
1152
|
intraFunction: intraFunctionFacts(
|
|
1209
1153
|
projects.map((one) => one.extra.parts),
|
|
1210
1154
|
projects.flatMap((one) => one.configured.references),
|
|
1211
|
-
new Set([...swept.exempt.map((one) => one.id), ...swept.
|
|
1155
|
+
new Set([...swept.sweep.exempt.map((one) => one.id), ...swept.sweep.held]),
|
|
1212
1156
|
),
|
|
1213
1157
|
};
|
|
1214
1158
|
}
|
|
@@ -1448,14 +1392,16 @@ export function runAnalysis(
|
|
|
1448
1392
|
* declarations of its own the run holds for them.
|
|
1449
1393
|
*/
|
|
1450
1394
|
export function unansweredCounts(analysis: RunAnalysis): readonly UnansweredCount[] {
|
|
1451
|
-
const held = new Set(analysis.swept.
|
|
1395
|
+
const held = new Set(analysis.swept.sweep.held);
|
|
1452
1396
|
return analysis.run.projects.flatMap((project) => {
|
|
1453
1397
|
const questions =
|
|
1454
1398
|
analysis.run.unanswered.filter((one) => one.configFile === project.configFile).length +
|
|
1455
1399
|
analysis.run.unread.filter(
|
|
1456
1400
|
(one) => one.kind === "manifest" && one.configFile === project.configFile,
|
|
1457
1401
|
).length +
|
|
1458
|
-
analysis.run.heldScripts.filter(
|
|
1402
|
+
analysis.run.heldScripts.filter(
|
|
1403
|
+
(one) => one.configFile === project.configFile && one.files.length > 0,
|
|
1404
|
+
).length;
|
|
1459
1405
|
|
|
1460
1406
|
if (questions === 0) {
|
|
1461
1407
|
return [];
|
package/src/augmentations.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type Node,
|
|
15
15
|
} from "@typescript/native/ast";
|
|
16
16
|
import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
17
|
-
import { referenceKey } from "./matrix.ts";
|
|
17
|
+
import { referenceKey } from "./liveness/matrix.ts";
|
|
18
18
|
import type { Reference } from "./references.ts";
|
|
19
19
|
import type { ProjectView } from "./session.ts";
|
|
20
20
|
import type { SourceFiles } from "./source-files.ts";
|