@cplieger/deadset-ts 3.11.0 → 3.12.0
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/README.md +2 -2
- package/dist/src/analysis.js +51 -13
- package/dist/src/conformance-record.js +2 -12
- package/dist/src/consumers.js +49 -0
- package/dist/src/discover.js +59 -23
- package/dist/src/findings/completion.js +49 -17
- package/dist/src/findings/non-code-artifacts.js +9 -2
- package/dist/src/findings/reads-and-writes.js +3 -1
- package/dist/src/graph.js +8 -5
- package/dist/src/matrix.js +14 -3
- package/dist/src/references.js +21 -9
- package/dist/src/report.js +4 -1
- package/dist/src/session.js +15 -0
- package/dist/src/sweep.js +11 -0
- package/dist/src/verbs/analyze.js +6 -11
- package/dist/src/verbs/explain.js +49 -35
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +109 -25
- package/src/conformance-record.ts +2 -14
- package/src/consumers.ts +86 -0
- package/src/discover.ts +67 -23
- package/src/findings/completion.ts +56 -20
- package/src/findings/non-code-artifacts.ts +9 -2
- package/src/findings/reads-and-writes.ts +3 -1
- package/src/graph.ts +14 -5
- package/src/matrix.ts +14 -5
- package/src/references.ts +48 -9
- package/src/report.ts +17 -2
- package/src/session.ts +19 -0
- package/src/sweep.ts +13 -1
- package/src/verbs/analyze.ts +6 -14
- package/src/verbs/explain.ts +57 -40
package/dist/src/references.js
CHANGED
|
@@ -3,6 +3,7 @@ import { aliasChains } from "./alias-chain.js";
|
|
|
3
3
|
import { destructuring, propertyReadsOf } from "./destructuring.js";
|
|
4
4
|
import { globExpression } from "./glob.js";
|
|
5
5
|
import { nodeKey } from "./inventory.js";
|
|
6
|
+
import { relativePath } from "./paths.js";
|
|
6
7
|
import { byPosition, renderPosition } from "./position.js";
|
|
7
8
|
/**
|
|
8
9
|
* The batch cap a run takes when the configuration names none. It bounds the size of
|
|
@@ -306,7 +307,9 @@ function compare(a, b) {
|
|
|
306
307
|
*
|
|
307
308
|
* `held` is the inventory of the same project, whose declaration map is what a resolved
|
|
308
309
|
* symbol is looked up in; `targetRoot` is the absolute path every position is rendered
|
|
309
|
-
* against, which is the root the inventory was read against.
|
|
310
|
+
* against, which is the root the inventory was read against. A pass over a consumer
|
|
311
|
+
* walks the consumer's files instead and renders against its root, as
|
|
312
|
+
* {@link ReferenceOptions.consumer} states.
|
|
310
313
|
*
|
|
311
314
|
* The order is by the referencing position, then by the declaration named, the
|
|
312
315
|
* declaration referencing it and the use. That is a total order because one name
|
|
@@ -326,7 +329,14 @@ export function references(project, held, targetRoot, options) {
|
|
|
326
329
|
let fileBatches = 0;
|
|
327
330
|
let residueFallbacks = 0;
|
|
328
331
|
let shorthandLookups = 0;
|
|
329
|
-
const
|
|
332
|
+
const consumer = options.consumer;
|
|
333
|
+
const root = consumer?.root ?? targetRoot;
|
|
334
|
+
const files = consumer === undefined
|
|
335
|
+
? project.ownSourceFiles()
|
|
336
|
+
: project
|
|
337
|
+
.ownSourceFiles()
|
|
338
|
+
.filter((file) => relativePath(consumer.root, file.fileName) !== undefined &&
|
|
339
|
+
relativePath(targetRoot, file.fileName) === undefined);
|
|
330
340
|
const fileIds = new Set(held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id));
|
|
331
341
|
/** The identifier of the declaration one node of this program is, where it is one. */
|
|
332
342
|
const declaredAt = (node) => declarations.get(nodeKey(node.getSourceFile(), node));
|
|
@@ -352,7 +362,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
352
362
|
return project.resolvedSymbolAt(project.handle(site.node));
|
|
353
363
|
};
|
|
354
364
|
for (const file of files) {
|
|
355
|
-
const path = renderPosition(file,
|
|
365
|
+
const path = renderPosition(file, root, 0).path;
|
|
356
366
|
const test = patterns.some((pattern) => pattern.test(path));
|
|
357
367
|
if (test) {
|
|
358
368
|
tests.push(path);
|
|
@@ -365,7 +375,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
365
375
|
found.push({
|
|
366
376
|
from: declaration,
|
|
367
377
|
to: declaration,
|
|
368
|
-
position: renderPosition(file,
|
|
378
|
+
position: renderPosition(file, root, decorator.getStart()),
|
|
369
379
|
use: "decorator",
|
|
370
380
|
resolution: "syntax",
|
|
371
381
|
test,
|
|
@@ -401,7 +411,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
401
411
|
if (symbol === undefined) {
|
|
402
412
|
continue;
|
|
403
413
|
}
|
|
404
|
-
const position = renderPosition(file,
|
|
414
|
+
const position = renderPosition(file, root, link.at.getStart());
|
|
405
415
|
for (const id of carriedBy(link, symbol)) {
|
|
406
416
|
found.push({
|
|
407
417
|
from: link.from,
|
|
@@ -418,7 +428,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
418
428
|
if (module === undefined) {
|
|
419
429
|
continue;
|
|
420
430
|
}
|
|
421
|
-
const position = renderPosition(file,
|
|
431
|
+
const position = renderPosition(file, root, evaluation.node.getStart());
|
|
422
432
|
for (const id of chains.declarationsOf(module)) {
|
|
423
433
|
if (fileIds.has(id)) {
|
|
424
434
|
found.push({
|
|
@@ -439,7 +449,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
439
449
|
if (symbol === undefined || read === undefined) {
|
|
440
450
|
return;
|
|
441
451
|
}
|
|
442
|
-
const position = renderPosition(file,
|
|
452
|
+
const position = renderPosition(file, root, read.key.getStart());
|
|
443
453
|
for (const target of chains.chainOf(symbol)) {
|
|
444
454
|
found.push({
|
|
445
455
|
from: read.from,
|
|
@@ -461,7 +471,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
461
471
|
if (symbol === undefined) {
|
|
462
472
|
continue;
|
|
463
473
|
}
|
|
464
|
-
const position = renderPosition(file,
|
|
474
|
+
const position = renderPosition(file, root, site.node.getStart());
|
|
465
475
|
// A re-export something imports is used and so is the declaration behind it, so
|
|
466
476
|
// the use names every link of the chain its name stands at the head of.
|
|
467
477
|
for (const target of chains.chainOf(symbol)) {
|
|
@@ -482,7 +492,9 @@ export function references(project, held, targetRoot, options) {
|
|
|
482
492
|
compare(a.use, b.use));
|
|
483
493
|
return {
|
|
484
494
|
configFile: project.configFile,
|
|
485
|
-
references:
|
|
495
|
+
references: consumer === undefined
|
|
496
|
+
? found
|
|
497
|
+
: found.map((reference) => ({ ...reference, consumer: consumer.id })),
|
|
486
498
|
testFilePaths: tests.sort(compare),
|
|
487
499
|
cost: {
|
|
488
500
|
batched,
|
package/dist/src/report.js
CHANGED
|
@@ -188,6 +188,9 @@ export function buildReport(input) {
|
|
|
188
188
|
const unavailable = [...new Map(input.unavailable.map((one) => [one.id, one])).values()]
|
|
189
189
|
.sort((a, b) => compare(a.id, b.id))
|
|
190
190
|
.map((one) => ({ id: one.id, role: "consumer", reason: one.reason }));
|
|
191
|
+
const loaded = [...input.loaded]
|
|
192
|
+
.sort((a, b) => compare(a.id, b.id) || compare(a.path, b.path))
|
|
193
|
+
.map((one) => ({ id: one.id, role: "consumer", path: one.path }));
|
|
191
194
|
return {
|
|
192
195
|
schema_version: SCHEMA_VERSION,
|
|
193
196
|
contract_version: input.contractVersion,
|
|
@@ -207,7 +210,7 @@ export function buildReport(input) {
|
|
|
207
210
|
.sort((a, b) => compare(a.id, b.id))
|
|
208
211
|
.map((one) => ({ id: one.id, project: one.project })),
|
|
209
212
|
configurations_not_built: [],
|
|
210
|
-
consumers: { declared: unavailable.length, loaded
|
|
213
|
+
consumers: { declared: loaded.length + unavailable.length, loaded, unavailable },
|
|
211
214
|
findings,
|
|
212
215
|
edge_evaluations: input.result.edgeEvaluations.map(wireEvaluation).sort(byEdge),
|
|
213
216
|
stale_suppressions: input.result.staleSuppressions.map(wireStale).sort(canonicalStale),
|
package/dist/src/session.js
CHANGED
|
@@ -71,6 +71,21 @@ function viewOf(project) {
|
|
|
71
71
|
},
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The same project, its own source files narrowed to the ones `keep` admits. A consumer's
|
|
76
|
+
* program holds the target's files it imports beside its own, and a pass that enumerates
|
|
77
|
+
* the target's declarations in that program reads those files alone.
|
|
78
|
+
*/
|
|
79
|
+
export function narrowedTo(project, keep) {
|
|
80
|
+
let kept;
|
|
81
|
+
return {
|
|
82
|
+
...project,
|
|
83
|
+
ownSourceFiles: () => {
|
|
84
|
+
kept ??= project.ownSourceFiles().filter(keep);
|
|
85
|
+
return kept;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
74
89
|
/** The three diagnostic sets one project carries, read before any analysis. */
|
|
75
90
|
export function diagnosticsOf(project) {
|
|
76
91
|
return {
|
package/dist/src/sweep.js
CHANGED
|
@@ -67,6 +67,13 @@ export function sweep(graph, input) {
|
|
|
67
67
|
});
|
|
68
68
|
return { liveUnder: live, candidates };
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The references a loaded consumer makes that the mode counts. A production mode counts
|
|
72
|
+
* none a consumer's test file made, as it counts none of the target's.
|
|
73
|
+
*/
|
|
74
|
+
function callsOf(graph, mode) {
|
|
75
|
+
return graph.consumed.filter((edge) => !mode.production || !edge.test);
|
|
76
|
+
}
|
|
70
77
|
/** Per declaration of the graph, whether one of the identifiers names it. */
|
|
71
78
|
function flagged(graph, ids) {
|
|
72
79
|
const flags = graph.symbols.map(() => false);
|
|
@@ -126,6 +133,10 @@ function reachable(graph, held, mode) {
|
|
|
126
133
|
for (const root of graph.rooted) {
|
|
127
134
|
enter(root.at, false);
|
|
128
135
|
}
|
|
136
|
+
// A consumer's reference reaches what it names as one of the target's own does.
|
|
137
|
+
for (const edge of callsOf(graph, mode)) {
|
|
138
|
+
enter(edge.to, !edge.evaluation);
|
|
139
|
+
}
|
|
129
140
|
walk(false);
|
|
130
141
|
return reached;
|
|
131
142
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { runAnalysis } from "../analysis.js";
|
|
2
2
|
import { ConfigError } from "../config.js";
|
|
3
3
|
import { CONFORMANCE, DECLARED_GAPS } from "../conformance.js";
|
|
4
|
+
import { moduleIdentity } from "../consumers.js";
|
|
4
5
|
import { analyzerProvenance, recordedFindings, verdictOf } from "../findings-pass.js";
|
|
5
|
-
import { packageScope } from "../inventory.js";
|
|
6
6
|
import { joinPath, normalizePath, relativePath, resolvePath } from "../paths.js";
|
|
7
7
|
import { buildReport, capFindings, sortFindings, } from "../report.js";
|
|
8
8
|
import { json } from "../reporters/json.js";
|
|
@@ -20,8 +20,6 @@ export const ANALYZE_OPTIONS = {
|
|
|
20
20
|
/** The two values `--exit-code` takes; on, the default, puts the run's verdict in the exit code. */
|
|
21
21
|
const EXIT_CODE_ON = "on";
|
|
22
22
|
const EXIT_CODE_OFF = "off";
|
|
23
|
-
/** Why a declared consumer is not loaded, which every report names it with. */
|
|
24
|
-
const NOT_LOADED = "this analyzer loads no consumer beside the target";
|
|
25
23
|
/** A path of the run no report can name, which ends the run with the failure code. */
|
|
26
24
|
class ReportPathError extends Error {
|
|
27
25
|
constructor(message) {
|
|
@@ -73,10 +71,6 @@ function configurationsOf(projects, targetRoot) {
|
|
|
73
71
|
return { id: one.id, project };
|
|
74
72
|
});
|
|
75
73
|
}
|
|
76
|
-
/** The name one module publishes itself under: the scope's, else its manifest's. */
|
|
77
|
-
function identityOf(module, host) {
|
|
78
|
-
return module.id === "" ? packageScope(host, module.path)("index.ts").package : module.id;
|
|
79
|
-
}
|
|
80
74
|
/** A count with its noun, so a message reads for one record as well as for several. */
|
|
81
75
|
function counted(n, noun) {
|
|
82
76
|
return `${String(n)} ${noun}${n === 1 ? "" : "s"}`;
|
|
@@ -163,12 +157,13 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
|
|
|
163
157
|
version,
|
|
164
158
|
conformance: CONFORMANCE,
|
|
165
159
|
declaredGaps: DECLARED_GAPS,
|
|
166
|
-
target: { kind, root, identity:
|
|
160
|
+
target: { kind, root, identity: moduleIdentity(host, scoped.target) },
|
|
167
161
|
configurations: configurationsOf(run.projects, targetRoot),
|
|
168
|
-
|
|
169
|
-
id:
|
|
170
|
-
|
|
162
|
+
loaded: run.consumers.map((consumer) => ({
|
|
163
|
+
id: consumer.id,
|
|
164
|
+
path: runRelative(invoked, consumer.path),
|
|
171
165
|
})),
|
|
166
|
+
unavailable: [],
|
|
172
167
|
result,
|
|
173
168
|
testFileRules: run.testFileRules,
|
|
174
169
|
});
|
|
@@ -2,6 +2,7 @@ import { runAnalysis } from "../analysis.js";
|
|
|
2
2
|
import { listing } from "../components.js";
|
|
3
3
|
import { ConfigError } from "../config.js";
|
|
4
4
|
import { OUTSIDE, swept as judged } from "../graph.js";
|
|
5
|
+
import { referenceKey } from "../matrix.js";
|
|
5
6
|
import { positionKey } from "../position.js";
|
|
6
7
|
import { resolve } from "../resolve.js";
|
|
7
8
|
import { EXIT_CLEAN, EXIT_USAGE } from "./verb.js";
|
|
@@ -78,7 +79,7 @@ function referencesOf(configured) {
|
|
|
78
79
|
const seen = new Set();
|
|
79
80
|
const held = [];
|
|
80
81
|
for (const reference of configured.flatMap((one) => one.references)) {
|
|
81
|
-
const key =
|
|
82
|
+
const key = referenceKey(reference);
|
|
82
83
|
if (!seen.has(key)) {
|
|
83
84
|
seen.add(key);
|
|
84
85
|
held.push(reference);
|
|
@@ -101,8 +102,9 @@ function rootsOf(configured) {
|
|
|
101
102
|
});
|
|
102
103
|
}
|
|
103
104
|
/**
|
|
104
|
-
* The shortest path from a root to one declaration, walked breadth first
|
|
105
|
-
* once in the order the run names them,
|
|
105
|
+
* The shortest path from a root or a loaded consumer to one declaration, walked breadth first
|
|
106
|
+
* from every root at once in the order the run names them, a consumer's reference being the
|
|
107
|
+
* first hop of a path of its own, and following the references the sweep follows: a
|
|
106
108
|
* reference that reads a module reaches what the module exports, and an evaluation or a root
|
|
107
109
|
* reaches the module alone. Under `production` no reference a test file made is followed,
|
|
108
110
|
* which is the set the report's sweep counts.
|
|
@@ -110,10 +112,16 @@ function rootsOf(configured) {
|
|
|
110
112
|
function shortestPath(analysis, to, production) {
|
|
111
113
|
const graph = analysis.swept.matrix.union;
|
|
112
114
|
const out = new Map();
|
|
115
|
+
const consumed = [];
|
|
113
116
|
for (const reference of referencesOf(analysis.configured)) {
|
|
114
117
|
if (production && reference.test) {
|
|
115
118
|
continue;
|
|
116
119
|
}
|
|
120
|
+
const { consumer } = reference;
|
|
121
|
+
if (consumer !== undefined) {
|
|
122
|
+
consumed.push({ ...reference, consumer });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
117
125
|
const held = out.get(reference.from);
|
|
118
126
|
if (held === undefined) {
|
|
119
127
|
out.set(reference.from, [reference]);
|
|
@@ -131,10 +139,32 @@ function shortestPath(analysis, to, production) {
|
|
|
131
139
|
steps.push(step);
|
|
132
140
|
}
|
|
133
141
|
};
|
|
142
|
+
const follow = (reference, previous, origin) => {
|
|
143
|
+
enter({ at: reference.to, hop: { k: "reference", reference }, previous, origin });
|
|
144
|
+
const target = graph.at(reference.to);
|
|
145
|
+
if (reference.use === "evaluation" || target === OUTSIDE || expanded.has(reference.to)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
expanded.add(reference.to);
|
|
149
|
+
for (const exported of graph.exportsOf[target] ?? []) {
|
|
150
|
+
const id = graph.symbols[exported]?.id;
|
|
151
|
+
if (id !== undefined) {
|
|
152
|
+
enter({ at: id, hop: { k: "export", reference, to: id }, previous, origin });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
134
156
|
for (const root of rootsOf(analysis.configured)) {
|
|
135
|
-
enter({ at: root.id, hop: undefined, previous: -1, root });
|
|
157
|
+
enter({ at: root.id, hop: undefined, previous: -1, origin: { k: "root", root } });
|
|
136
158
|
}
|
|
137
|
-
|
|
159
|
+
// A consumer's reference is one hop long, so it joins the walk where the roots' first
|
|
160
|
+
// hops do.
|
|
161
|
+
const firstHops = steps.length;
|
|
162
|
+
for (let head = 0;; head += 1) {
|
|
163
|
+
if (head === firstHops) {
|
|
164
|
+
for (const reference of consumed) {
|
|
165
|
+
follow(reference, -1, { k: "consumer", consumer: reference.consumer });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
138
168
|
const step = steps[head];
|
|
139
169
|
if (step === undefined) {
|
|
140
170
|
break;
|
|
@@ -144,35 +174,18 @@ function shortestPath(analysis, to, production) {
|
|
|
144
174
|
for (let at = step; at?.hop !== undefined; at = steps[at.previous]) {
|
|
145
175
|
path.unshift(at.hop);
|
|
146
176
|
}
|
|
147
|
-
return {
|
|
177
|
+
return { origin: step.origin, path };
|
|
148
178
|
}
|
|
149
179
|
for (const reference of out.get(step.at) ?? []) {
|
|
150
|
-
|
|
151
|
-
at: reference.to,
|
|
152
|
-
hop: { k: "reference", reference },
|
|
153
|
-
previous: head,
|
|
154
|
-
root: step.root,
|
|
155
|
-
});
|
|
156
|
-
const target = graph.at(reference.to);
|
|
157
|
-
if (reference.use === "evaluation" || target === OUTSIDE || expanded.has(reference.to)) {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
expanded.add(reference.to);
|
|
161
|
-
for (const exported of graph.exportsOf[target] ?? []) {
|
|
162
|
-
const id = graph.symbols[exported]?.id;
|
|
163
|
-
if (id !== undefined) {
|
|
164
|
-
enter({
|
|
165
|
-
at: id,
|
|
166
|
-
hop: { k: "export", reference, to: id },
|
|
167
|
-
previous: head,
|
|
168
|
-
root: step.root,
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
180
|
+
follow(reference, head, step.origin);
|
|
172
181
|
}
|
|
173
182
|
}
|
|
174
183
|
return undefined;
|
|
175
184
|
}
|
|
185
|
+
/** What made a reference: a declaration of the target, or a loaded consumer. */
|
|
186
|
+
function madeBy(reference) {
|
|
187
|
+
return reference.consumer === undefined ? reference.from : `consumer ${reference.consumer}`;
|
|
188
|
+
}
|
|
176
189
|
function referenceLine(reference, tail) {
|
|
177
190
|
return ` reference: ${positionKey(reference.position)}\t${reference.use}\t${tail}${reference.test ? "\tfrom a test file" : ""}\n`;
|
|
178
191
|
}
|
|
@@ -190,9 +203,10 @@ function liveness(analysis, subject, cascade) {
|
|
|
190
203
|
}
|
|
191
204
|
const found = shortestPath(analysis, subject.id, true) ?? shortestPath(analysis, subject.id, false);
|
|
192
205
|
if (found !== undefined && found.path.length > 0) {
|
|
193
|
-
|
|
206
|
+
const { origin } = found;
|
|
207
|
+
text += ` reached from: ${origin.k === "root" ? `root ${origin.root.kind} ${origin.root.id}` : `consumer ${origin.consumer}`}\n`;
|
|
194
208
|
for (const hop of found.path) {
|
|
195
|
-
text += referenceLine(hop.reference, `${hop.reference
|
|
209
|
+
text += referenceLine(hop.reference, `${madeBy(hop.reference)} -> ${hop.reference.to}`);
|
|
196
210
|
if (hop.k === "export") {
|
|
197
211
|
text += ` export: ${hop.reference.to} -> ${hop.to}\n`;
|
|
198
212
|
}
|
|
@@ -200,10 +214,10 @@ function liveness(analysis, subject, cascade) {
|
|
|
200
214
|
}
|
|
201
215
|
else if (found === undefined) {
|
|
202
216
|
const references = referencesOf(analysis.configured).filter((one) => one.to === subject.id);
|
|
203
|
-
text += " unreachable: no path of references reaches it from a root\n";
|
|
217
|
+
text += " unreachable: no path of references reaches it from a root or a loaded consumer\n";
|
|
204
218
|
text += ` references: ${String(references.length)}\n`;
|
|
205
219
|
for (const reference of references) {
|
|
206
|
-
text += referenceLine(reference, `from ${reference
|
|
220
|
+
text += referenceLine(reference, `from ${madeBy(reference)}`);
|
|
207
221
|
}
|
|
208
222
|
}
|
|
209
223
|
if (candidate !== undefined) {
|
|
@@ -277,9 +291,9 @@ function explanation(analysis, subject, cascade) {
|
|
|
277
291
|
* Explains one symbol from the analysis the report is built from, so an explanation cannot
|
|
278
292
|
* disagree with a finding: why a reported symbol is reported, which exemption classes held a
|
|
279
293
|
* retained one back, and for any other declaration the relations that hold it live and the
|
|
280
|
-
* shortest path of references from a root, or the references it has where
|
|
281
|
-
* A request naming no one declaration exits with the usage code and names
|
|
282
|
-
* matches it.
|
|
294
|
+
* shortest path of references from a root or a loaded consumer, or the references it has where
|
|
295
|
+
* no path reaches it. A request naming no one declaration exits with the usage code and names
|
|
296
|
+
* what partially matches it.
|
|
283
297
|
*/
|
|
284
298
|
export const explainVerb = ({ out, err, host, inputs, scope, openClient, option }) => {
|
|
285
299
|
const named = namedSymbol(option);
|
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/analysis.ts
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Diagnostic } from "@typescript/native/unstable/sync";
|
|
12
12
|
import type { Config, Provenance } from "./config.ts";
|
|
13
|
+
import {
|
|
14
|
+
consumerLoads,
|
|
15
|
+
consumerReferences,
|
|
16
|
+
moduleIdentity,
|
|
17
|
+
type LoadedConsumer,
|
|
18
|
+
} from "./consumers.ts";
|
|
13
19
|
import { decorator } from "./decorator.ts";
|
|
14
20
|
import { dependenciesOf, projectNeeds } from "./dependencies.ts";
|
|
15
21
|
import { deprecatedDeclarations } from "./deprecation.ts";
|
|
@@ -41,11 +47,18 @@ import { injectionContainer } from "./injection-container.ts";
|
|
|
41
47
|
import { interfaceSatisfaction } from "./interface-satisfaction.ts";
|
|
42
48
|
import { inventory, type Inventory } from "./inventory.ts";
|
|
43
49
|
import { readManifest } from "./manifest.ts";
|
|
44
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
matrixOf,
|
|
52
|
+
referenceKey,
|
|
53
|
+
sweepMatrix,
|
|
54
|
+
type Configured,
|
|
55
|
+
type Matrix,
|
|
56
|
+
type SweepResult,
|
|
57
|
+
} from "./matrix.ts";
|
|
45
58
|
import { relativePath, resolvePath } from "./paths.ts";
|
|
46
59
|
import { byPosition, type Position } from "./position.ts";
|
|
47
60
|
import { reflectiveLookup } from "./reflective-lookup.ts";
|
|
48
|
-
import { references, testFileRulesOf, type TestFileRule } from "./references.ts";
|
|
61
|
+
import { references, testFileRulesOf, type Reference, type TestFileRule } from "./references.ts";
|
|
49
62
|
import { roots, unmatchedEverywhere, type RootKind, type Roots } from "./roots.ts";
|
|
50
63
|
import type { Scope } from "./scope.ts";
|
|
51
64
|
import { serializationContract } from "./serialization-contract.ts";
|
|
@@ -125,11 +138,27 @@ interface ProjectRead {
|
|
|
125
138
|
readonly rooted: Roots;
|
|
126
139
|
}
|
|
127
140
|
|
|
141
|
+
/** What a run read of the consumers its scope declares. */
|
|
142
|
+
interface ConsumerReads {
|
|
143
|
+
/** Every declared consumer, each loaded, in the scope's order. */
|
|
144
|
+
readonly loaded: readonly LoadedConsumer[];
|
|
145
|
+
/** Every reference a consumer makes to a declaration of the target, each once. */
|
|
146
|
+
readonly references: readonly Reference[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** What a run read of its projects: each target project's answer, and its consumers. */
|
|
150
|
+
interface ReadProjects<Answer> {
|
|
151
|
+
readonly answers: readonly Answer[];
|
|
152
|
+
readonly consumers: ConsumerReads;
|
|
153
|
+
}
|
|
154
|
+
|
|
128
155
|
/**
|
|
129
|
-
* Reads every project the scope discovers,
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
156
|
+
* Reads every project the scope discovers, and every project of every consumer it
|
|
157
|
+
* declares, in one snapshot. Each is checked for errors first. A target project is then
|
|
158
|
+
* enumerated and rooted, and `stage` reads the rest of what it needs from the project
|
|
159
|
+
* while its view is open; a consumer project is read for its references to the target's
|
|
160
|
+
* declarations. A project carrying an error fails the run with no answer, as every verb
|
|
161
|
+
* that reads declarations does, and so does a declared consumer that cannot be loaded.
|
|
133
162
|
*/
|
|
134
163
|
function readProjects<Answer>(
|
|
135
164
|
engine: Engine,
|
|
@@ -137,10 +166,16 @@ function readProjects<Answer>(
|
|
|
137
166
|
scope: Scope,
|
|
138
167
|
config: Config,
|
|
139
168
|
stage: <Brand>(project: ProjectView<Brand>, read: ProjectRead) => Answer,
|
|
140
|
-
):
|
|
169
|
+
): ReadProjects<Answer> {
|
|
141
170
|
const targetRoot = scope.target.path;
|
|
142
171
|
const discovered = discoverProjects(engine, host, scope, config.analysis.configurations);
|
|
143
172
|
const ids = new Map(discovered.projects.map((project) => [project.configFile, project.id]));
|
|
173
|
+
const loads = consumerLoads(engine, host, scope, discovered.configFiles);
|
|
174
|
+
const consumerOf = new Map(
|
|
175
|
+
loads.flatMap((load) =>
|
|
176
|
+
load.configFiles.map((configFile) => [configFile, load.consumer] as const),
|
|
177
|
+
),
|
|
178
|
+
);
|
|
144
179
|
const options = {
|
|
145
180
|
manifest: readManifest(host, targetRoot),
|
|
146
181
|
patterns: config.rootPatterns,
|
|
@@ -149,24 +184,51 @@ function readProjects<Answer>(
|
|
|
149
184
|
publishedAPI: config.targetKind === "library",
|
|
150
185
|
};
|
|
151
186
|
const failures: Diagnostic[] = [];
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
187
|
+
const consumed = new Map<string, Reference>();
|
|
188
|
+
const { projects } = runSession(
|
|
189
|
+
engine,
|
|
190
|
+
[...discovered.configFiles, ...consumerOf.keys()],
|
|
191
|
+
(project) => {
|
|
192
|
+
const errors = diagnosticErrors(diagnosticsOf(project));
|
|
193
|
+
if (errors.length > 0) {
|
|
194
|
+
failures.push(...errors);
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
const consumer = consumerOf.get(project.configFile);
|
|
198
|
+
if (consumer !== undefined) {
|
|
199
|
+
for (const reference of consumerReferences(
|
|
200
|
+
project,
|
|
201
|
+
host,
|
|
202
|
+
targetRoot,
|
|
203
|
+
{
|
|
204
|
+
id: consumer.id,
|
|
205
|
+
root: consumer.path,
|
|
206
|
+
},
|
|
207
|
+
config,
|
|
208
|
+
)) {
|
|
209
|
+
consumed.set(referenceKey(reference), reference);
|
|
210
|
+
}
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
const held = inventory(project, host, targetRoot);
|
|
214
|
+
const rooted = roots(project, held, targetRoot, options);
|
|
215
|
+
const configuration = ids.get(project.configFile) ?? project.configFile;
|
|
216
|
+
return stage(project, { configuration, configFile: project.configFile, held, rooted });
|
|
217
|
+
},
|
|
218
|
+
);
|
|
163
219
|
if (failures.length > 0) {
|
|
164
220
|
throw new DiscoveryError(
|
|
165
221
|
`${String(failures.length)} error(s); no answer was produced`,
|
|
166
222
|
failures,
|
|
167
223
|
);
|
|
168
224
|
}
|
|
169
|
-
return
|
|
225
|
+
return {
|
|
226
|
+
answers: projects.filter((answer): answer is Answer => answer !== undefined),
|
|
227
|
+
consumers: {
|
|
228
|
+
loaded: loads.map((load) => load.consumer),
|
|
229
|
+
references: [...consumed.values()],
|
|
230
|
+
},
|
|
231
|
+
};
|
|
170
232
|
}
|
|
171
233
|
|
|
172
234
|
/**
|
|
@@ -183,7 +245,7 @@ export function runRoots(
|
|
|
183
245
|
provenance: Provenance,
|
|
184
246
|
): RunRoots {
|
|
185
247
|
const targetRoot = scope.target.path;
|
|
186
|
-
const projects = readProjects(engine, host, scope, config, (_project, read) => read);
|
|
248
|
+
const projects = readProjects(engine, host, scope, config, (_project, read) => read).answers;
|
|
187
249
|
|
|
188
250
|
const configurations: string[] = [];
|
|
189
251
|
const merged = new Map<string, Omit<RunRoot, "configurations"> & { configurations: string[] }>();
|
|
@@ -266,12 +328,15 @@ interface ReadRun<Extra> {
|
|
|
266
328
|
readonly matrix: Matrix;
|
|
267
329
|
readonly exempt: readonly Exemption[];
|
|
268
330
|
readonly projects: readonly SweptProject<Extra>[];
|
|
331
|
+
/** Every declared consumer, each loaded, in the scope's order. */
|
|
332
|
+
readonly consumers: readonly LoadedConsumer[];
|
|
269
333
|
}
|
|
270
334
|
|
|
271
335
|
/**
|
|
272
336
|
* Reads every project of the run, its references and exemptions, and `extra`'s answer,
|
|
273
337
|
* while the project's view is open, and merges the projects into one matrix in the run's
|
|
274
|
-
* order.
|
|
338
|
+
* order. Every configuration holds every consumer's references beside its own, because a
|
|
339
|
+
* consumer calls the target whichever configuration the target is built under.
|
|
275
340
|
*/
|
|
276
341
|
function readRun<Extra>(
|
|
277
342
|
engine: Engine,
|
|
@@ -288,7 +353,7 @@ function readRun<Extra>(
|
|
|
288
353
|
? { delimiters: config.analysis.templateDelimiters, files: [] }
|
|
289
354
|
: readTemplates(host, targetRoot, config.analysis);
|
|
290
355
|
const consumers = scope.consumers.map((consumer) => consumer.path);
|
|
291
|
-
const
|
|
356
|
+
const read = readProjects(engine, host, scope, config, (project, projectRead) => {
|
|
292
357
|
const resolved = references(project, projectRead.held, targetRoot, {
|
|
293
358
|
testFiles: config.ts.testFiles,
|
|
294
359
|
});
|
|
@@ -315,10 +380,23 @@ function readRun<Extra>(
|
|
|
315
380
|
extra: extra(project, projectRead),
|
|
316
381
|
};
|
|
317
382
|
});
|
|
383
|
+
const consumed = read.consumers.references;
|
|
384
|
+
const projects = read.answers.map((one) =>
|
|
385
|
+
consumed.length === 0
|
|
386
|
+
? one
|
|
387
|
+
: {
|
|
388
|
+
...one,
|
|
389
|
+
configured: {
|
|
390
|
+
...one.configured,
|
|
391
|
+
references: [...one.configured.references, ...consumed],
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
);
|
|
318
395
|
return {
|
|
319
396
|
matrix: matrixOf(projects.map((one) => one.configured)),
|
|
320
397
|
exempt: exemptionsOf(projects.flatMap((one) => one.exempt)),
|
|
321
398
|
projects,
|
|
399
|
+
consumers: read.consumers.loaded,
|
|
322
400
|
};
|
|
323
401
|
}
|
|
324
402
|
|
|
@@ -430,7 +508,10 @@ function emitterInputOver(
|
|
|
430
508
|
heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
|
|
431
509
|
},
|
|
432
510
|
boundary: {
|
|
433
|
-
consumers: {
|
|
511
|
+
consumers: {
|
|
512
|
+
declared: scope.consumers.map((consumer) => moduleIdentity(host, consumer)),
|
|
513
|
+
loaded: read.consumers.map((consumer) => consumer.id),
|
|
514
|
+
},
|
|
434
515
|
encapsulated: readManifest(host, targetRoot).declaresExports,
|
|
435
516
|
edges: readEdgeSides(host, targetRoot),
|
|
436
517
|
},
|
|
@@ -442,8 +523,8 @@ function emitterInputOver(
|
|
|
442
523
|
* What every emitter reads of the run the scope and the configuration describe, swept
|
|
443
524
|
* under the caller's marks: the run's sweep, the facts beside it each kind family reads,
|
|
444
525
|
* and the sides of the target's declared cross-language edges that name a symbol of this
|
|
445
|
-
* language. The run
|
|
446
|
-
*
|
|
526
|
+
* language. The run reads no suppression document, so the self-check family has nothing
|
|
527
|
+
* to report.
|
|
447
528
|
*/
|
|
448
529
|
export function runEmitterInput(
|
|
449
530
|
engine: Engine,
|
|
@@ -526,6 +607,7 @@ function runAnalysisInputs(
|
|
|
526
607
|
config.ts.testFiles,
|
|
527
608
|
read.projects.flatMap((one) => one.configured.testFiles),
|
|
528
609
|
),
|
|
610
|
+
consumers: read.consumers,
|
|
529
611
|
},
|
|
530
612
|
configured: read.projects.map((one) => one.configured),
|
|
531
613
|
};
|
|
@@ -544,6 +626,8 @@ export interface RunFacts {
|
|
|
544
626
|
readonly projects: readonly RunProject[];
|
|
545
627
|
/** The rules that classified files as test files, each counted over every project at once. */
|
|
546
628
|
readonly testFileRules: readonly TestFileRule[];
|
|
629
|
+
/** Every consumer the run loaded beside the target, in the scope's order. */
|
|
630
|
+
readonly consumers: readonly LoadedConsumer[];
|
|
547
631
|
}
|
|
548
632
|
|
|
549
633
|
/** The findings of one run, beside what a report states about the run itself. */
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
export const RECORD = {
|
|
4
4
|
conformance: {
|
|
5
5
|
corpusVersion: "1.9.0",
|
|
6
|
-
result: "
|
|
7
|
-
digest: "sha256:
|
|
6
|
+
result: "pass",
|
|
7
|
+
digest: "sha256:3e9e40876551faa2c196311bf06d47d220ab5127a9fb8921969aae552c42e824",
|
|
8
8
|
},
|
|
9
9
|
gaps: [
|
|
10
10
|
{
|
|
@@ -13,17 +13,5 @@ export const RECORD = {
|
|
|
13
13
|
reason:
|
|
14
14
|
"The reference pass resolves a string-literal element access to the member it names, so the member is live by that reference and the retained listing names no class for it.",
|
|
15
15
|
},
|
|
16
|
-
{
|
|
17
|
-
fixture: "redundant-export-keyword",
|
|
18
|
-
capability: "DS1104",
|
|
19
|
-
reason:
|
|
20
|
-
"No consumer is loaded beside the target, so the published surface is unknown and no export is narrowed; review an export only the target uses by hand.",
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
fixture: "unused-exported-consumer",
|
|
24
|
-
capability: "DS1001",
|
|
25
|
-
reason:
|
|
26
|
-
"No consumer is loaded beside the target, so an export nothing in the target references is reported at the possible class rather than the certain one.",
|
|
27
|
-
},
|
|
28
16
|
],
|
|
29
17
|
} as const;
|