@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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { EXIT_CLEAN, EXIT_USAGE } from "../contract/contract.generated.js";
|
|
2
|
-
import { runAnalysis } from "../analysis.js";
|
|
3
|
-
import { listing } from "../components.js";
|
|
4
|
-
import {
|
|
5
|
-
import { referenceKey } from "../matrix.js";
|
|
2
|
+
import { productionMode, runAnalysis } from "../analysis.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";
|
|
8
|
+
import { madeByTestCode } from "../tests/file-class.js";
|
|
9
9
|
import { resolve } from "../resolve.js";
|
|
10
10
|
import {} from "./verb.js";
|
|
11
11
|
import { UsageError } from "../usage.js";
|
|
@@ -79,150 +79,94 @@ function subjectOf(symbols, named) {
|
|
|
79
79
|
.sort(bytewise);
|
|
80
80
|
return { candidates: partial.slice(0, MAX_PARTIAL_MATCHES) };
|
|
81
81
|
}
|
|
82
|
-
/**
|
|
83
|
-
function
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
return held;
|
|
94
|
-
}
|
|
95
|
-
/** Every root of the run once per reason, in the order the projects name them. */
|
|
96
|
-
function rootsOf(configured) {
|
|
97
|
-
const seen = new Set();
|
|
98
|
-
return configured
|
|
99
|
-
.flatMap((one) => one.roots)
|
|
100
|
-
.filter((root) => {
|
|
101
|
-
const key = [root.id, root.kind, root.source].join("\u0000");
|
|
102
|
-
if (seen.has(key)) {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
seen.add(key);
|
|
106
|
-
return true;
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* The shortest path from a root or a loaded consumer to one declaration, walked breadth first
|
|
111
|
-
* from every root at once in the order the run names them, a consumer's reference being the
|
|
112
|
-
* first hop of a path of its own, and following the references the sweep follows: a
|
|
113
|
-
* reference that reads a module reaches what the module exports, and an evaluation or a root
|
|
114
|
-
* reaches the module alone. Under `production` no reference a test file made is followed,
|
|
115
|
-
* which is the set the report's sweep counts.
|
|
116
|
-
*/
|
|
117
|
-
function shortestPath(analysis, to, production) {
|
|
118
|
-
const graph = analysis.swept.matrix.union;
|
|
119
|
-
const out = new Map();
|
|
120
|
-
const consumed = [];
|
|
121
|
-
for (const reference of referencesOf(analysis.configured)) {
|
|
122
|
-
if (production && reference.test) {
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
const { consumer } = reference;
|
|
126
|
-
if (consumer !== undefined) {
|
|
127
|
-
consumed.push({ ...reference, consumer });
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
const held = out.get(reference.from);
|
|
131
|
-
if (held === undefined) {
|
|
132
|
-
out.set(reference.from, [reference]);
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
held.push(reference);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
const steps = [];
|
|
139
|
-
const seen = new Set();
|
|
140
|
-
const expanded = new Set();
|
|
141
|
-
const enter = (step) => {
|
|
142
|
-
if (!seen.has(step.at)) {
|
|
143
|
-
seen.add(step.at);
|
|
144
|
-
steps.push(step);
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
const follow = (reference, previous, origin) => {
|
|
148
|
-
enter({ at: reference.to, hop: { k: "reference", reference }, previous, origin });
|
|
149
|
-
const target = graph.at(reference.to);
|
|
150
|
-
if (reference.use === "evaluation" || target === OUTSIDE || expanded.has(reference.to)) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
expanded.add(reference.to);
|
|
154
|
-
for (const exported of graph.exportsOf[target] ?? []) {
|
|
155
|
-
const id = graph.symbols[exported]?.id;
|
|
156
|
-
if (id !== undefined) {
|
|
157
|
-
enter({ at: id, hop: { k: "export", reference, to: id }, previous, origin });
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
for (const root of rootsOf(analysis.configured)) {
|
|
162
|
-
enter({ at: root.id, hop: undefined, previous: -1, origin: { k: "root", root } });
|
|
163
|
-
}
|
|
164
|
-
// A consumer's reference is one hop long, so it joins the walk where the roots' first
|
|
165
|
-
// hops do.
|
|
166
|
-
const firstHops = steps.length;
|
|
167
|
-
for (let head = 0;; head += 1) {
|
|
168
|
-
if (head === firstHops) {
|
|
169
|
-
for (const reference of consumed) {
|
|
170
|
-
follow(reference, -1, { k: "consumer", consumer: reference.consumer });
|
|
82
|
+
/** Where a path starts, as a line names it. */
|
|
83
|
+
function originText(origin, graph) {
|
|
84
|
+
const id = (at) => graph.symbols[at]?.id ?? "";
|
|
85
|
+
switch (origin.k) {
|
|
86
|
+
case "root":
|
|
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}`;
|
|
171
92
|
}
|
|
93
|
+
return reference?.unheld === true
|
|
94
|
+
? `test file no project holds ${reference.from.replace(/#$/u, "")}`
|
|
95
|
+
: `declaration no configuration judges ${reference?.from ?? ""}`;
|
|
172
96
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
for (let at = step; at?.hop !== undefined; at = steps[at.previous]) {
|
|
180
|
-
path.unshift(at.hop);
|
|
181
|
-
}
|
|
182
|
-
return { origin: step.origin, path };
|
|
183
|
-
}
|
|
184
|
-
for (const reference of out.get(step.at) ?? []) {
|
|
185
|
-
follow(reference, head, step.origin);
|
|
186
|
-
}
|
|
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)}`;
|
|
187
103
|
}
|
|
188
|
-
return undefined;
|
|
189
104
|
}
|
|
190
105
|
/** What made a reference: a declaration of the target, or a loaded consumer. */
|
|
191
106
|
function madeBy(reference) {
|
|
192
107
|
return reference.consumer === undefined ? reference.from : `consumer ${reference.consumer}`;
|
|
193
108
|
}
|
|
194
|
-
|
|
195
|
-
|
|
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`;
|
|
196
116
|
}
|
|
197
117
|
/** Why a declaration no finding names and no exemption held back is live, or how it fell. */
|
|
198
118
|
function liveness(analysis, subject, cascade) {
|
|
199
|
-
const { sweep } = analysis.swept;
|
|
119
|
+
const { matrix, sweep } = analysis.swept;
|
|
120
|
+
const graph = matrix.union;
|
|
200
121
|
const candidate = sweep.candidates.find((one) => one.id === subject.id);
|
|
201
122
|
if (candidate === undefined && !judged(subject.kind)) {
|
|
202
123
|
return `answer: unjudged\n unjudged: neither liveness relation judges a ${subject.kind}\n`;
|
|
203
124
|
}
|
|
204
125
|
let text = `answer: ${candidate === undefined ? "live" : "dead"}\n`;
|
|
205
126
|
text += ` live under: ${listed(sweep.liveUnder.get(subject.id) ?? [])}\n`;
|
|
206
|
-
|
|
207
|
-
|
|
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
|
+
}
|
|
208
135
|
}
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
136
|
+
const support = new Set(analysis.configured.flatMap((one) => one.supportFiles ?? []));
|
|
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}`);
|
|
215
151
|
if (hop.k === "export") {
|
|
216
|
-
|
|
152
|
+
const reference = graph.references[hop.edge.reference];
|
|
153
|
+
text += ` export: ${reference?.to ?? ""} -> ${graph.symbols[hop.to]?.id ?? ""}\n`;
|
|
217
154
|
}
|
|
218
155
|
}
|
|
219
156
|
}
|
|
220
157
|
else if (found === undefined) {
|
|
221
|
-
const references =
|
|
222
|
-
text +=
|
|
158
|
+
const references = graph.references.flatMap((one, place) => one.to === subject.id ? [place] : []);
|
|
159
|
+
text +=
|
|
160
|
+
" unreachable: no path of references reaches it from a root, a loaded consumer or a test file no project holds\n";
|
|
223
161
|
text += ` references: ${String(references.length)}\n`;
|
|
224
|
-
for (const
|
|
225
|
-
|
|
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
|
+
}
|
|
226
170
|
}
|
|
227
171
|
}
|
|
228
172
|
if (candidate !== undefined) {
|
|
@@ -279,7 +223,7 @@ function explanation(analysis, subject, cascade) {
|
|
|
279
223
|
text += `position: ${subject.id}\n`;
|
|
280
224
|
text += `configurations: ${listed(analysis.swept.matrix.heldIn[at] ?? [])}\n`;
|
|
281
225
|
const found = analysis.result.findings.find((one) => positionKey(one.position) === subject.id);
|
|
282
|
-
const retained = analysis.swept.retained.filter((one) => one.id === subject.id);
|
|
226
|
+
const retained = analysis.swept.sweep.retained.filter((one) => one.id === subject.id);
|
|
283
227
|
if (found !== undefined) {
|
|
284
228
|
text += reported(found);
|
|
285
229
|
}
|
|
@@ -289,7 +233,7 @@ function explanation(analysis, subject, cascade) {
|
|
|
289
233
|
text += ` class: ${one.class}\t${positionKey(one.site)}\t${one.detail}\n`;
|
|
290
234
|
}
|
|
291
235
|
}
|
|
292
|
-
else if (analysis.swept.
|
|
236
|
+
else if (analysis.swept.sweep.held.includes(subject.id)) {
|
|
293
237
|
text +=
|
|
294
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";
|
|
295
239
|
}
|
|
@@ -301,18 +245,17 @@ function explanation(analysis, subject, cascade) {
|
|
|
301
245
|
/**
|
|
302
246
|
* Explains one symbol from the analysis the report is built from: why a reported symbol is
|
|
303
247
|
* reported, which exemption classes held a retained one back, that an unanswered question held
|
|
304
|
-
* a held one, and for any other declaration the relations that hold it live and the
|
|
305
|
-
*
|
|
306
|
-
* request naming no one declaration exits with the usage code and names what partially
|
|
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
|
|
307
251
|
* matches it. Where the analysis was partial, the error stream says so.
|
|
308
252
|
*/
|
|
309
253
|
export const explainVerb = ({ out, err, host, inputs, scope, openClient, option }) => {
|
|
310
254
|
const named = namedSymbol(option);
|
|
311
255
|
const { config, provenance } = resolve(inputs());
|
|
312
256
|
const scoped = scope();
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
});
|
|
257
|
+
const mode = productionMode(config);
|
|
258
|
+
const analysis = runAnalysis(openClient(config), host, scoped, config, provenance, mode);
|
|
316
259
|
const { subject, candidates } = subjectOf(analysis.swept.matrix.union.symbols, named);
|
|
317
260
|
const notes = partialNotes({
|
|
318
261
|
notBuilt: analysis.run.notBuilt,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EXIT_CLEAN } from "../contract/contract.generated.js";
|
|
2
|
-
import { runSweep } from "../analysis.js";
|
|
2
|
+
import { productionMode, runSweep } from "../analysis.js";
|
|
3
3
|
import { retainedLines } from "../exempt.js";
|
|
4
4
|
import { partialNotes } from "../partial.js";
|
|
5
5
|
import { resolvePath } from "../paths.js";
|
|
@@ -18,9 +18,9 @@ export const printRetainedVerb = ({ out, err, host, inputs, scope, openClient })
|
|
|
18
18
|
const scoped = scope();
|
|
19
19
|
const swept = runSweep(openClient(config), host, scoped, config, {
|
|
20
20
|
marked: [],
|
|
21
|
-
mode:
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/deadset-ts",
|
|
3
|
-
"version": "8.0.0-dev.
|
|
3
|
+
"version": "8.0.0-dev.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Deterministic dead-code analysis for TypeScript, class and type members included",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"prettier": "3.9.10",
|
|
49
49
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
50
50
|
"typescript-eslint": "8.72.1",
|
|
51
|
-
"vitest": "5.0
|
|
51
|
+
"vitest": "5.1.0"
|
|
52
52
|
},
|
|
53
53
|
"publishConfig": {
|
|
54
54
|
"access": "public"
|
package/src/alias-chain.ts
CHANGED
|
@@ -49,8 +49,6 @@ export interface AliasChains {
|
|
|
49
49
|
* link is declared.
|
|
50
50
|
*/
|
|
51
51
|
nextDeclared(symbol: TSSymbol): readonly ChainTarget[];
|
|
52
|
-
/** How many steps the compiler was asked for: one per alias, whatever the chains it heads. */
|
|
53
|
-
readonly steps: number;
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
/**
|
|
@@ -65,7 +63,6 @@ export function aliasChains<Brand>(project: ProjectView<Brand>, held: Inventory)
|
|
|
65
63
|
const ownFiles = project.ownPaths();
|
|
66
64
|
const steps = new Map<number, Answer<TSSymbol | undefined>>();
|
|
67
65
|
const chains = new Map<number, readonly ChainTarget[]>();
|
|
68
|
-
let asked = 0;
|
|
69
66
|
|
|
70
67
|
const declarationsOf = (symbol: TSSymbol): string[] => {
|
|
71
68
|
const ids: string[] = [];
|
|
@@ -102,7 +99,6 @@ export function aliasChains<Brand>(project: ProjectView<Brand>, held: Inventory)
|
|
|
102
99
|
return steps.get(symbol.id);
|
|
103
100
|
}
|
|
104
101
|
const next = project.aliasStepOf(symbol);
|
|
105
|
-
asked += 1;
|
|
106
102
|
steps.set(symbol.id, next);
|
|
107
103
|
return next;
|
|
108
104
|
};
|
|
@@ -197,8 +193,5 @@ export function aliasChains<Brand>(project: ProjectView<Brand>, held: Inventory)
|
|
|
197
193
|
declarationsOf,
|
|
198
194
|
chainOf,
|
|
199
195
|
nextDeclared,
|
|
200
|
-
get steps() {
|
|
201
|
-
return asked;
|
|
202
|
-
},
|
|
203
196
|
};
|
|
204
197
|
}
|
package/src/amd.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AMD's module forms (references-ts.37), read from the syntax because the binder declares
|
|
3
|
+
* none of them: `define([<ids>], factory)`, `define("<name>", [<ids>], factory)` and
|
|
4
|
+
* `require([<ids>], callback)` (https://github.com/amdjs/amdjs-api/blob/master/AMD.md).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
isArrayLiteralExpression,
|
|
9
|
+
isArrowFunction,
|
|
10
|
+
isFunctionExpression,
|
|
11
|
+
isCallExpression,
|
|
12
|
+
isIdentifier,
|
|
13
|
+
isNoSubstitutionTemplateLiteral,
|
|
14
|
+
isStringLiteral,
|
|
15
|
+
type Identifier,
|
|
16
|
+
type Node,
|
|
17
|
+
type StringLiteralLikeNode,
|
|
18
|
+
} from "@typescript/native/ast";
|
|
19
|
+
import { hasURLScheme, isAbsolutePath, resolvePath, startsRelativePath } from "./paths.ts";
|
|
20
|
+
import { parenthesesRemoved } from "./unwrap.ts";
|
|
21
|
+
|
|
22
|
+
/** The platform function an AMD call is written as. */
|
|
23
|
+
type AmdPlatform = "define" | "require";
|
|
24
|
+
|
|
25
|
+
/** One AMD call's dependency list. */
|
|
26
|
+
export interface AmdLoad {
|
|
27
|
+
/** The name the call is written with; {@link callsPlatform} owns whether it is AMD's. */
|
|
28
|
+
readonly callee: Identifier;
|
|
29
|
+
/** Each dependency id written as literal text, AMD's own `require`, `exports` and `module` left out. */
|
|
30
|
+
readonly ids: readonly AmdDependency[];
|
|
31
|
+
/** Whether the list, or an element of it, is computed, so the call may load any module. */
|
|
32
|
+
readonly computed: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** One dependency id of an AMD call. */
|
|
36
|
+
interface AmdDependency {
|
|
37
|
+
readonly id: StringLiteralLikeNode;
|
|
38
|
+
/**
|
|
39
|
+
* Whether the factory may receive the module's value: it declares a parameter at the id's
|
|
40
|
+
* place, a rest parameter included, or it is written as anything but a function.
|
|
41
|
+
*/
|
|
42
|
+
readonly received: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The ids AMD binds to the factory's CommonJS parameters, which load no module. */
|
|
46
|
+
const FREE_VARIABLES: ReadonlySet<string> = new Set(["require", "exports", "module"]);
|
|
47
|
+
|
|
48
|
+
/** The platform function `callee` is written as. */
|
|
49
|
+
export function amdPlatform(callee: Identifier): AmdPlatform {
|
|
50
|
+
return callee.text === "define" ? "define" : "require";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The dependency list one call writes, where it is a call of a bare `define` or `require` in
|
|
55
|
+
* one of AMD's forms. A `define` with no list, of a factory or a value alone, loads nothing;
|
|
56
|
+
* a `require` whose first argument is no array literal is CommonJS's.
|
|
57
|
+
*/
|
|
58
|
+
export function amdLoadOf(node: Node): AmdLoad | undefined {
|
|
59
|
+
if (!isCallExpression(node)) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const callee = parenthesesRemoved(node.expression);
|
|
63
|
+
if (!isIdentifier(callee) || (callee.text !== "define" && callee.text !== "require")) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const written = node.arguments.map((one) => parenthesesRemoved(one));
|
|
67
|
+
const [first] = written;
|
|
68
|
+
if (first === undefined) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const define = callee.text === "define";
|
|
72
|
+
const named = define && isLiteralText(first);
|
|
73
|
+
const list = written[named ? 1 : 0];
|
|
74
|
+
if (list !== undefined && isArrayLiteralExpression(list)) {
|
|
75
|
+
const elements = list.elements.map((one) => parenthesesRemoved(one));
|
|
76
|
+
const factory = written[named ? 2 : 1];
|
|
77
|
+
const ids = elements.flatMap((one, at) =>
|
|
78
|
+
isLiteralText(one) && !FREE_VARIABLES.has(one.text)
|
|
79
|
+
? [{ id: one, received: receives(factory, at) }]
|
|
80
|
+
: [],
|
|
81
|
+
);
|
|
82
|
+
return { callee, ids, computed: elements.some((one) => !isLiteralText(one)) };
|
|
83
|
+
}
|
|
84
|
+
if (!define) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
// A factory after the list's place makes what stands there a list, here a computed one.
|
|
88
|
+
return { callee, ids: [], computed: written.length > (named ? 2 : 1) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isLiteralText(node: Node): node is StringLiteralLikeNode {
|
|
92
|
+
return isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Whether a factory may receive the dependency at place `at` of its list. */
|
|
96
|
+
function receives(factory: Node | undefined, at: number): boolean {
|
|
97
|
+
if (factory === undefined) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (!isFunctionExpression(factory) && !isArrowFunction(factory)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
const { parameters } = factory;
|
|
104
|
+
return at < parameters.length || parameters.some((one) => one.dotDotDotToken !== undefined);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The path a dependency id names against the calling file's directory: a relative module id,
|
|
109
|
+
* `./` or `../` with no `.js` ending, with `.js` appended. RequireJS reads any other id
|
|
110
|
+
* through its configuration or as a URL against the page, which the analysis does not know.
|
|
111
|
+
*/
|
|
112
|
+
export function amdPath(dir: string, id: string): string | undefined {
|
|
113
|
+
return startsRelativePath(id) && !id.endsWith(".js") ? resolvePath(dir, `${id}.js`) : undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Whether one call may load a file the analysis cannot name: a computed list, or an id that
|
|
118
|
+
* RequireJS reads as a URL, one ending in `.js`, starting with `/` or carrying a scheme. Any
|
|
119
|
+
* other id {@link amdPath} reads no path from is a package's.
|
|
120
|
+
*/
|
|
121
|
+
export function amdUnresolved(load: AmdLoad): boolean {
|
|
122
|
+
return (
|
|
123
|
+
load.computed ||
|
|
124
|
+
load.ids.some(
|
|
125
|
+
({ id }) =>
|
|
126
|
+
amdPath("/", id.text) === undefined &&
|
|
127
|
+
(id.text.endsWith(".js") || isAbsolutePath(id.text) || hasURLScheme(id.text)),
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
}
|