@cplieger/deadset-ts 3.13.0 → 4.0.1
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 +67 -49
- package/dist/bin/node-host.js +10 -7
- package/dist/src/alias-chain.js +59 -7
- package/dist/src/analysis.js +120 -24
- package/dist/src/calls.js +23 -22
- package/dist/src/class-members.js +10 -11
- package/dist/src/configured-declarations.js +22 -9
- package/dist/src/dependencies.js +34 -17
- package/dist/src/deprecation.js +35 -24
- package/dist/src/destructuring.js +37 -29
- package/dist/src/discover.js +106 -29
- package/dist/src/entry-point-gaps.js +2 -2
- package/dist/src/entry-points.js +7 -0
- package/dist/src/enum-group.js +46 -40
- package/dist/src/exempt.js +42 -1
- package/dist/src/findings/emitters.js +15 -1
- package/dist/src/findings/interfaces.js +7 -1
- package/dist/src/framework-lifecycle.js +8 -6
- package/dist/src/implementations.js +45 -23
- package/dist/src/interface-satisfaction.js +30 -27
- package/dist/src/intra-function-parts.js +37 -20
- package/dist/src/inventory.js +85 -4
- package/dist/src/json-chunks.js +76 -0
- package/dist/src/partial.js +23 -0
- package/dist/src/query.js +207 -0
- package/dist/src/references.js +92 -20
- package/dist/src/reflective-lookup.js +18 -9
- package/dist/src/report.js +3 -1
- package/dist/src/reporters/annotations.js +9 -5
- package/dist/src/reporters/json.js +3 -2
- package/dist/src/reporters/sarif.js +2 -1
- package/dist/src/reporters/template.js +51 -10
- package/dist/src/reporters/text.js +8 -6
- package/dist/src/roots.js +27 -11
- package/dist/src/schema.js +2 -2
- package/dist/src/serialization-contract.js +12 -10
- package/dist/src/session.js +79 -39
- package/dist/src/suppress-file.js +2 -1
- package/dist/src/sweep.js +4 -1
- package/dist/src/value-reach.js +14 -19
- package/dist/src/verbs/analyze.js +20 -3
- package/dist/src/verbs/explain.js +22 -9
- package/dist/src/verbs/print-projects.js +41 -8
- package/dist/src/verbs/print-retained.js +6 -2
- package/dist/src/verbs/print-roots.js +5 -1
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +75 -11
- package/src/analysis.ts +224 -62
- package/src/calls.ts +10 -9
- package/src/class-members.ts +10 -11
- package/src/configured-declarations.ts +29 -13
- package/src/dependencies.ts +42 -20
- package/src/deprecation.ts +53 -27
- package/src/destructuring.ts +50 -39
- package/src/discover.ts +178 -34
- package/src/entry-point-gaps.ts +2 -2
- package/src/entry-points.ts +7 -0
- package/src/enum-group.ts +56 -42
- package/src/exempt.ts +50 -2
- package/src/findings/emitters.ts +18 -1
- package/src/findings/interfaces.ts +7 -1
- package/src/framework-lifecycle.ts +10 -6
- package/src/host.ts +5 -3
- package/src/implementations.ts +59 -23
- package/src/interface-satisfaction.ts +30 -27
- package/src/intra-function-parts.ts +43 -25
- package/src/inventory.ts +97 -4
- package/src/json-chunks.ts +87 -0
- package/src/partial.ts +40 -0
- package/src/query.ts +522 -0
- package/src/references.ts +119 -38
- package/src/reflective-lookup.ts +21 -11
- package/src/report.ts +14 -2
- package/src/reporters/annotations.ts +23 -21
- package/src/reporters/json.ts +3 -2
- package/src/reporters/reporter.ts +2 -2
- package/src/reporters/sarif.ts +3 -2
- package/src/reporters/template.ts +53 -11
- package/src/reporters/text.ts +12 -12
- package/src/roots.ts +34 -12
- package/src/schema.ts +2 -2
- package/src/serialization-contract.ts +12 -10
- package/src/session.ts +146 -58
- package/src/suppress-file.ts +6 -2
- package/src/sweep.ts +9 -4
- package/src/value-reach.ts +20 -23
- package/src/verbs/analyze.ts +25 -3
- package/src/verbs/explain.ts +20 -9
- package/src/verbs/print-projects.ts +54 -8
- package/src/verbs/print-retained.ts +6 -2
- package/src/verbs/print-roots.ts +5 -1
package/src/analysis.ts
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* project's checker, and that holds only for views the session itself hands out.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { Diagnostic } from "@typescript/native/unstable/sync";
|
|
12
11
|
import type { Config, Provenance } from "./config.ts";
|
|
13
12
|
import {
|
|
14
13
|
consumerLoads,
|
|
@@ -23,14 +22,24 @@ import {
|
|
|
23
22
|
} from "./configured-declarations.ts";
|
|
24
23
|
import { decorator } from "./decorator.ts";
|
|
25
24
|
import { dependenciesOf, projectNeeds } from "./dependencies.ts";
|
|
26
|
-
import { deprecatedDeclarations } from "./deprecation.ts";
|
|
27
|
-
import {
|
|
25
|
+
import { deprecatedDeclarations, type Deprecation } from "./deprecation.ts";
|
|
26
|
+
import {
|
|
27
|
+
discoverProjects,
|
|
28
|
+
DiscoveryError,
|
|
29
|
+
isGuess,
|
|
30
|
+
projectErrors,
|
|
31
|
+
renderDiagnostic,
|
|
32
|
+
targetRelative,
|
|
33
|
+
type LocatedDiagnostic,
|
|
34
|
+
type NotBuilt,
|
|
35
|
+
} from "./discover.ts";
|
|
28
36
|
import { readEdgeSides } from "./edges.ts";
|
|
29
37
|
import { enumGroup } from "./enum-group.ts";
|
|
30
38
|
import {
|
|
31
39
|
computeExemptions,
|
|
32
40
|
disabledClasses,
|
|
33
41
|
exemptionsOf,
|
|
42
|
+
isUnansweredRecord,
|
|
34
43
|
retainedIn,
|
|
35
44
|
type Detector,
|
|
36
45
|
type Detectors,
|
|
@@ -62,6 +71,7 @@ import {
|
|
|
62
71
|
type SweepResult,
|
|
63
72
|
} from "./matrix.ts";
|
|
64
73
|
import { relativePath, resolvePath } from "./paths.ts";
|
|
74
|
+
import type { UnansweredQuestion } from "./query.ts";
|
|
65
75
|
import { byPosition, type Position } from "./position.ts";
|
|
66
76
|
import { reflectiveLookup } from "./reflective-lookup.ts";
|
|
67
77
|
import { references, testFileRulesOf, type Reference, type TestFileRule } from "./references.ts";
|
|
@@ -105,6 +115,10 @@ export interface RunRoots {
|
|
|
105
115
|
readonly roots: readonly RunRoot[];
|
|
106
116
|
/** One finding per configured root or pattern that named nothing in any project. */
|
|
107
117
|
readonly findings: readonly Finding[];
|
|
118
|
+
/** The derived configurations the run dropped. */
|
|
119
|
+
readonly notBuilt: readonly NotBuilt[];
|
|
120
|
+
/** Every question the checker could not answer, each once. */
|
|
121
|
+
readonly unanswered: readonly UnansweredQuestion[];
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
/**
|
|
@@ -156,15 +170,19 @@ interface ConsumerReads {
|
|
|
156
170
|
interface ReadProjects<Answer> {
|
|
157
171
|
readonly answers: readonly Answer[];
|
|
158
172
|
readonly consumers: ConsumerReads;
|
|
173
|
+
/** The derived configurations the run dropped, in the order it met them. */
|
|
174
|
+
readonly notBuilt: readonly NotBuilt[];
|
|
175
|
+
/** Every question the checker could not answer, each once. */
|
|
176
|
+
readonly unanswered: readonly UnansweredQuestion[];
|
|
159
177
|
}
|
|
160
178
|
|
|
161
179
|
/**
|
|
162
|
-
* Reads every project the scope discovers, and every project of
|
|
163
|
-
* declares, in one snapshot
|
|
164
|
-
* enumerated
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
180
|
+
* Reads every project the scope discovers, and every project of each consumer it
|
|
181
|
+
* declares, in one snapshot, each checked for errors first. A target project is
|
|
182
|
+
* enumerated, rooted and handed to `stage`; a consumer project is read for its references
|
|
183
|
+
* to the target. A guess that does not load is dropped into `notBuilt` (see
|
|
184
|
+
* {@link isGuess}); any other project that does not load or carries an error fails the
|
|
185
|
+
* run, as does a run whose every guess was dropped.
|
|
168
186
|
*/
|
|
169
187
|
function readProjects<Answer>(
|
|
170
188
|
engine: Engine,
|
|
@@ -189,18 +207,39 @@ function readProjects<Answer>(
|
|
|
189
207
|
testFiles: config.ts.testFiles,
|
|
190
208
|
publishedAPI: config.targetKind === "library",
|
|
191
209
|
};
|
|
192
|
-
const
|
|
210
|
+
const absoluteRoot = resolvePath(host.workingDirectory(), targetRoot);
|
|
211
|
+
const notBuilt: NotBuilt[] = [...discovered.notBuilt];
|
|
212
|
+
const dropped = (configFile: string, error: string): void => {
|
|
213
|
+
notBuilt.push({
|
|
214
|
+
id: ids.get(configFile) ?? configFile,
|
|
215
|
+
configFile,
|
|
216
|
+
error: targetRelative(error, absoluteRoot),
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
const failures: LocatedDiagnostic[] = [];
|
|
193
220
|
const consumed = new Map<string, Reference>();
|
|
194
|
-
const
|
|
221
|
+
const session = runSession(
|
|
195
222
|
engine,
|
|
196
223
|
[...discovered.configFiles, ...consumerOf.keys()],
|
|
197
224
|
(project) => {
|
|
198
|
-
const errors =
|
|
199
|
-
|
|
200
|
-
|
|
225
|
+
const errors = projectErrors(diagnosticsOf(project));
|
|
226
|
+
const consumer = consumerOf.get(project.configFile);
|
|
227
|
+
const [unloaded] = errors.load;
|
|
228
|
+
if (
|
|
229
|
+
unloaded !== undefined &&
|
|
230
|
+
consumer === undefined &&
|
|
231
|
+
isGuess(discovered, project.configFile)
|
|
232
|
+
) {
|
|
233
|
+
dropped(
|
|
234
|
+
project.configFile,
|
|
235
|
+
renderDiagnostic(unloaded, (path) => relativePath(absoluteRoot, path) ?? path),
|
|
236
|
+
);
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
if (errors.load.length > 0 || errors.check.length > 0) {
|
|
240
|
+
failures.push(...errors.load, ...errors.check);
|
|
201
241
|
return undefined;
|
|
202
242
|
}
|
|
203
|
-
const consumer = consumerOf.get(project.configFile);
|
|
204
243
|
if (consumer !== undefined) {
|
|
205
244
|
for (const reference of consumerReferences(
|
|
206
245
|
project,
|
|
@@ -221,19 +260,36 @@ function readProjects<Answer>(
|
|
|
221
260
|
const configuration = ids.get(project.configFile) ?? project.configFile;
|
|
222
261
|
return stage(project, { configuration, configFile: project.configFile, held, rooted });
|
|
223
262
|
},
|
|
263
|
+
discovered.derived ? "record" : "refuse",
|
|
224
264
|
);
|
|
265
|
+
for (const configFile of session.unopened) {
|
|
266
|
+
if (consumerOf.has(configFile) || !isGuess(discovered, configFile)) {
|
|
267
|
+
throw new DiscoveryError(`${configFile} opened no project`, []);
|
|
268
|
+
}
|
|
269
|
+
dropped(configFile, "the compiler opened no project for the configuration");
|
|
270
|
+
}
|
|
225
271
|
if (failures.length > 0) {
|
|
226
272
|
throw new DiscoveryError(
|
|
227
|
-
`${String(failures.length)} error(s)
|
|
273
|
+
`${String(failures.length)} error(s), so no answer was produced`,
|
|
228
274
|
failures,
|
|
229
275
|
);
|
|
230
276
|
}
|
|
277
|
+
const answers = session.projects.filter((answer): answer is Answer => answer !== undefined);
|
|
278
|
+
const [first] = notBuilt;
|
|
279
|
+
if (answers.length === 0 && first !== undefined) {
|
|
280
|
+
throw new DiscoveryError(
|
|
281
|
+
`no configuration discovery derived could be built; ${first.id}: ${first.error}`,
|
|
282
|
+
[],
|
|
283
|
+
);
|
|
284
|
+
}
|
|
231
285
|
return {
|
|
232
|
-
answers
|
|
286
|
+
answers,
|
|
233
287
|
consumers: {
|
|
234
288
|
loaded: loads.map((load) => load.consumer),
|
|
235
289
|
references: [...consumed.values()],
|
|
236
290
|
},
|
|
291
|
+
notBuilt,
|
|
292
|
+
unanswered: session.unanswered,
|
|
237
293
|
};
|
|
238
294
|
}
|
|
239
295
|
|
|
@@ -251,7 +307,8 @@ export function runRoots(
|
|
|
251
307
|
provenance: Provenance,
|
|
252
308
|
): RunRoots {
|
|
253
309
|
const targetRoot = scope.target.path;
|
|
254
|
-
const
|
|
310
|
+
const read = readProjects(engine, host, scope, config, (_project, projectRead) => projectRead);
|
|
311
|
+
const projects = read.answers;
|
|
255
312
|
|
|
256
313
|
const configurations: string[] = [];
|
|
257
314
|
const merged = new Map<string, Omit<RunRoot, "configurations"> & { configurations: string[] }>();
|
|
@@ -295,6 +352,8 @@ export function runRoots(
|
|
|
295
352
|
unmatched,
|
|
296
353
|
documentOf(host, provenance, ROOTS_SETTING, targetRoot),
|
|
297
354
|
),
|
|
355
|
+
notBuilt: read.notBuilt,
|
|
356
|
+
unanswered: read.unanswered,
|
|
298
357
|
};
|
|
299
358
|
}
|
|
300
359
|
|
|
@@ -307,6 +366,15 @@ export interface RunSweep {
|
|
|
307
366
|
* would otherwise judge dead, in the declarations' site order.
|
|
308
367
|
*/
|
|
309
368
|
readonly retained: readonly Exemption[];
|
|
369
|
+
/** The derived configurations the run dropped. */
|
|
370
|
+
readonly notBuilt: readonly NotBuilt[];
|
|
371
|
+
/** Every question the checker could not answer, each once. */
|
|
372
|
+
readonly unanswered: readonly UnansweredQuestion[];
|
|
373
|
+
/**
|
|
374
|
+
* Every declaration an unanswered question could have kept live, and every one only
|
|
375
|
+
* such a declaration keeps live. The run reports none of them.
|
|
376
|
+
*/
|
|
377
|
+
readonly heldByUnanswered: readonly string[];
|
|
310
378
|
}
|
|
311
379
|
|
|
312
380
|
/** The exemption classes this analyzer detects, each by its detector; any other retains nothing. */
|
|
@@ -326,6 +394,8 @@ interface SweptProject<Extra> {
|
|
|
326
394
|
readonly configFile: string;
|
|
327
395
|
readonly configured: Configured;
|
|
328
396
|
readonly exempt: readonly Exemption[];
|
|
397
|
+
/** The declarations of the project an unanswered question could have kept live. */
|
|
398
|
+
readonly unanswered: readonly string[];
|
|
329
399
|
readonly extra: Extra;
|
|
330
400
|
}
|
|
331
401
|
|
|
@@ -333,9 +403,15 @@ interface SweptProject<Extra> {
|
|
|
333
403
|
interface ReadRun<Extra> {
|
|
334
404
|
readonly matrix: Matrix;
|
|
335
405
|
readonly exempt: readonly Exemption[];
|
|
406
|
+
/** The declarations of the run an unanswered question could have kept live, each once. */
|
|
407
|
+
readonly unansweredHeld: readonly string[];
|
|
336
408
|
readonly projects: readonly SweptProject<Extra>[];
|
|
337
409
|
/** Every declared consumer, each loaded, in the scope's order. */
|
|
338
410
|
readonly consumers: readonly LoadedConsumer[];
|
|
411
|
+
/** The derived configurations the run dropped. */
|
|
412
|
+
readonly notBuilt: readonly NotBuilt[];
|
|
413
|
+
/** Every question the checker could not answer, each once. */
|
|
414
|
+
readonly unanswered: readonly UnansweredQuestion[];
|
|
339
415
|
}
|
|
340
416
|
|
|
341
417
|
/**
|
|
@@ -363,33 +439,48 @@ function readRun<Extra>(
|
|
|
363
439
|
? { delimiters: config.analysis.templateDelimiters, files: [] }
|
|
364
440
|
: readTemplates(host, targetRoot, config.analysis);
|
|
365
441
|
const consumers = scope.consumers.map((consumer) => consumer.path);
|
|
366
|
-
const read = readProjects(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
442
|
+
const read = readProjects<SweptProject<Extra>>(
|
|
443
|
+
engine,
|
|
444
|
+
host,
|
|
445
|
+
scope,
|
|
446
|
+
config,
|
|
447
|
+
(project, projectRead) => {
|
|
448
|
+
const resolved = references(project, projectRead.held, targetRoot, {
|
|
449
|
+
testFiles: config.ts.testFiles,
|
|
450
|
+
});
|
|
451
|
+
const configured: Configured = {
|
|
452
|
+
configuration: projectRead.configuration,
|
|
453
|
+
symbols: projectRead.held.symbols,
|
|
454
|
+
references: resolved.references,
|
|
455
|
+
roots: projectRead.rooted.liveUnderReachability,
|
|
456
|
+
testFiles: resolved.testFilePaths,
|
|
457
|
+
};
|
|
458
|
+
const exempt = computeExemptions(
|
|
459
|
+
{ project, held: projectRead.held, targetRoot, templates, ts: config.ts, consumers },
|
|
460
|
+
detectors,
|
|
461
|
+
{
|
|
462
|
+
disabled,
|
|
463
|
+
mode,
|
|
464
|
+
testFiles: new Set(resolved.testFilePaths),
|
|
465
|
+
},
|
|
466
|
+
);
|
|
467
|
+
const unanswered = [
|
|
468
|
+
...projectRead.held.unanswered,
|
|
469
|
+
...projectRead.rooted.unanswered,
|
|
470
|
+
...resolved.references
|
|
471
|
+
.filter((reference) => reference.resolution === "by-name")
|
|
472
|
+
.map((reference) => reference.to),
|
|
473
|
+
...exempt.filter(isUnansweredRecord).map((record) => record.id),
|
|
474
|
+
];
|
|
475
|
+
return {
|
|
476
|
+
configFile: projectRead.configFile,
|
|
477
|
+
configured,
|
|
478
|
+
exempt,
|
|
479
|
+
unanswered,
|
|
480
|
+
extra: extra(project, projectRead, resolved.references),
|
|
481
|
+
};
|
|
482
|
+
},
|
|
483
|
+
);
|
|
393
484
|
const consumed = read.consumers.references;
|
|
394
485
|
const projects = read.answers.map((one) =>
|
|
395
486
|
consumed.length === 0
|
|
@@ -405,21 +496,72 @@ function readRun<Extra>(
|
|
|
405
496
|
return {
|
|
406
497
|
matrix: matrixOf(projects.map((one) => one.configured)),
|
|
407
498
|
exempt: exemptionsOf(projects.flatMap((one) => one.exempt)),
|
|
499
|
+
unansweredHeld: [
|
|
500
|
+
...new Set([
|
|
501
|
+
...read.answers.flatMap((one) => one.unanswered),
|
|
502
|
+
...consumed.filter((one) => one.resolution === "by-name").map((one) => one.to),
|
|
503
|
+
]),
|
|
504
|
+
],
|
|
408
505
|
projects,
|
|
409
506
|
consumers: read.consumers.loaded,
|
|
507
|
+
notBuilt: read.notBuilt,
|
|
508
|
+
unanswered: read.unanswered,
|
|
410
509
|
};
|
|
411
510
|
}
|
|
412
511
|
|
|
413
|
-
/**
|
|
512
|
+
/**
|
|
513
|
+
* The sweep of a run read once, under one set of marks. Where a question went unanswered,
|
|
514
|
+
* the run is swept once more without what that question could have kept live and without
|
|
515
|
+
* the references that stood in for its answer, and every declaration that sweep judges
|
|
516
|
+
* dead and this one judges live, or dead by another rule, is held by the gap too, as is
|
|
517
|
+
* every declaration that makes such a reference.
|
|
518
|
+
*/
|
|
414
519
|
function sweptOf<Extra>(
|
|
415
520
|
read: ReadRun<Extra>,
|
|
416
521
|
input: Pick<SweepInput, "marked" | "mode">,
|
|
417
522
|
): RunSweep {
|
|
418
|
-
const swept: SweepInput = { ...input, exempt: read.exempt };
|
|
523
|
+
const swept: SweepInput = { ...input, exempt: read.exempt, unanswered: read.unansweredHeld };
|
|
524
|
+
const result = sweepMatrix(read.matrix, swept);
|
|
525
|
+
const held = new Set(read.unansweredHeld);
|
|
526
|
+
if (held.size > 0) {
|
|
527
|
+
// What a declaration reads where the checker did not answer is unknown, so a rule
|
|
528
|
+
// over what it reads, the test of dead code among them, cannot judge it.
|
|
529
|
+
for (const { configured } of read.projects) {
|
|
530
|
+
for (const reference of configured.references) {
|
|
531
|
+
if (reference.resolution === "by-name") {
|
|
532
|
+
held.add(reference.from);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const dead = new Map(result.candidates.map((candidate) => [candidate.id, candidate]));
|
|
537
|
+
const answered = matrixOf(
|
|
538
|
+
read.projects.map(({ configured }) => ({
|
|
539
|
+
...configured,
|
|
540
|
+
references: configured.references.filter((one) => one.resolution !== "by-name"),
|
|
541
|
+
})),
|
|
542
|
+
);
|
|
543
|
+
const bare: SweepInput = {
|
|
544
|
+
...swept,
|
|
545
|
+
exempt: read.exempt.filter((record) => !isUnansweredRecord(record)),
|
|
546
|
+
unanswered: [],
|
|
547
|
+
};
|
|
548
|
+
for (const candidate of sweepMatrix(answered, bare).candidates) {
|
|
549
|
+
const kept = dead.get(candidate.id);
|
|
550
|
+
if (
|
|
551
|
+
kept?.testOfDeadCode !== candidate.testOfDeadCode ||
|
|
552
|
+
kept.relation !== candidate.relation
|
|
553
|
+
) {
|
|
554
|
+
held.add(candidate.id);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
419
558
|
return {
|
|
420
559
|
matrix: read.matrix,
|
|
421
|
-
sweep:
|
|
560
|
+
sweep: result,
|
|
422
561
|
retained: retainedIn(read.matrix, swept),
|
|
562
|
+
notBuilt: read.notBuilt,
|
|
563
|
+
unanswered: read.unanswered,
|
|
564
|
+
heldByUnanswered: [...held],
|
|
423
565
|
};
|
|
424
566
|
}
|
|
425
567
|
|
|
@@ -449,7 +591,7 @@ export function runSweep(
|
|
|
449
591
|
|
|
450
592
|
/** What each project contributes to the facts every emitter reads beside the sweep. */
|
|
451
593
|
interface EmitterExtra {
|
|
452
|
-
readonly
|
|
594
|
+
readonly deprecation: Deprecation;
|
|
453
595
|
readonly accessors: ReturnType<typeof accessorsOf>;
|
|
454
596
|
readonly needs: ReturnType<typeof projectNeeds>;
|
|
455
597
|
readonly implementations: ReturnType<typeof implementations>;
|
|
@@ -470,17 +612,31 @@ function readEmitterRun(
|
|
|
470
612
|
detectors: Detectors,
|
|
471
613
|
): ReadRun<EmitterExtra> {
|
|
472
614
|
const targetRoot = scope.target.path;
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
615
|
+
const run = readRun(
|
|
616
|
+
engine,
|
|
617
|
+
host,
|
|
618
|
+
scope,
|
|
619
|
+
config,
|
|
620
|
+
mode,
|
|
621
|
+
detectors,
|
|
622
|
+
(project, read, references) => ({
|
|
623
|
+
deprecation: deprecatedDeclarations(project, read.held),
|
|
624
|
+
accessors: accessorsOf(project, read.held),
|
|
625
|
+
needs: projectNeeds(project, read.held, host),
|
|
626
|
+
implementations: implementations(project, read.held),
|
|
627
|
+
heldByInclusion: heldByInclusion(project, targetRoot),
|
|
628
|
+
rooted: read.rooted,
|
|
629
|
+
directives: inlineDirectives(project, read.held, targetRoot),
|
|
630
|
+
declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
|
|
631
|
+
parts: projectParts(project, read.held, references, targetRoot),
|
|
632
|
+
}),
|
|
633
|
+
);
|
|
634
|
+
// Whether a declaration carries the marker decides its code, so one whose marker went
|
|
635
|
+
// unread is reported by nothing.
|
|
636
|
+
const unread = run.projects.flatMap((one) => one.extra.deprecation.unanswered);
|
|
637
|
+
return unread.length === 0
|
|
638
|
+
? run
|
|
639
|
+
: { ...run, unansweredHeld: [...new Set([...run.unansweredHeld, ...unread])] };
|
|
484
640
|
}
|
|
485
641
|
|
|
486
642
|
/**
|
|
@@ -501,7 +657,7 @@ function emitterInputOver(
|
|
|
501
657
|
return {
|
|
502
658
|
config,
|
|
503
659
|
swept,
|
|
504
|
-
deprecated: new Set(projects.flatMap((one) => one.extra.deprecated)),
|
|
660
|
+
deprecated: new Set(projects.flatMap((one) => one.extra.deprecation.deprecated)),
|
|
505
661
|
stores: storesOf(
|
|
506
662
|
projects.map((one) => ({
|
|
507
663
|
references: one.configured.references,
|
|
@@ -533,7 +689,7 @@ function emitterInputOver(
|
|
|
533
689
|
intraFunction: intraFunctionFacts(
|
|
534
690
|
projects.map((one) => one.extra.parts),
|
|
535
691
|
projects.flatMap((one) => one.configured.references),
|
|
536
|
-
new Set(read.exempt.map((one) => one.id)),
|
|
692
|
+
new Set([...read.exempt.map((one) => one.id), ...swept.heldByUnanswered]),
|
|
537
693
|
),
|
|
538
694
|
};
|
|
539
695
|
}
|
|
@@ -614,6 +770,8 @@ function readAnalysis(
|
|
|
614
770
|
read.projects.flatMap((one) => one.configured.testFiles),
|
|
615
771
|
),
|
|
616
772
|
consumers: read.consumers,
|
|
773
|
+
notBuilt: read.notBuilt,
|
|
774
|
+
unanswered: read.unanswered,
|
|
617
775
|
},
|
|
618
776
|
configured: read.projects.map((one) => one.configured),
|
|
619
777
|
symbols,
|
|
@@ -680,6 +838,10 @@ export interface RunFacts {
|
|
|
680
838
|
readonly testFileRules: readonly TestFileRule[];
|
|
681
839
|
/** Every consumer the run loaded beside the target, in the scope's order. */
|
|
682
840
|
readonly consumers: readonly LoadedConsumer[];
|
|
841
|
+
/** The derived configurations the run dropped. */
|
|
842
|
+
readonly notBuilt: readonly NotBuilt[];
|
|
843
|
+
/** Every question the checker could not answer, each once. */
|
|
844
|
+
readonly unanswered: readonly UnansweredQuestion[];
|
|
683
845
|
}
|
|
684
846
|
|
|
685
847
|
/** The findings of one run, beside what a report states about the run itself. */
|
package/src/calls.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/unstable/sync";
|
|
27
27
|
import type { AliasChains } from "./alias-chain.ts";
|
|
28
28
|
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
29
|
-
import {
|
|
29
|
+
import { must } from "./query.ts";
|
|
30
30
|
import type { ProjectView } from "./session.ts";
|
|
31
31
|
|
|
32
32
|
/** One value written where a call passes it, and the node its declaration is resolved from. */
|
|
@@ -77,7 +77,8 @@ export function decoratorsOf(node: Node): readonly Decorator[] {
|
|
|
77
77
|
/**
|
|
78
78
|
* What each of a batch of nodes resolves to, every alias followed to the declaration
|
|
79
79
|
* it stands for. A node the checker resolves to nothing, and an alias that resolves
|
|
80
|
-
* to nothing, are absent from the answer. Each alias is followed once.
|
|
80
|
+
* to nothing, are absent from the answer. Each alias is followed once. A question the
|
|
81
|
+
* checker does not answer stops the pass, as {@link must} stops.
|
|
81
82
|
*/
|
|
82
83
|
export function resolvedTargets<Brand>(
|
|
83
84
|
project: ProjectView<Brand>,
|
|
@@ -85,17 +86,18 @@ export function resolvedTargets<Brand>(
|
|
|
85
86
|
): ReadonlyMap<Node, TSSymbol> {
|
|
86
87
|
const followed = new Map<number, TSSymbol | undefined>();
|
|
87
88
|
const targets = new Map<Node, TSSymbol>();
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
project
|
|
90
|
+
.symbolsAt(nodes.map((node) => project.handle(node)))
|
|
91
|
+
.map(must)
|
|
92
|
+
.forEach((symbol, index) => {
|
|
93
|
+
const node = nodes[index];
|
|
92
94
|
if (symbol === undefined || node === undefined) {
|
|
93
95
|
return;
|
|
94
96
|
}
|
|
95
97
|
let target: TSSymbol | undefined = symbol;
|
|
96
98
|
if ((symbol.flags & SymbolFlags.Alias) !== 0) {
|
|
97
99
|
if (!followed.has(symbol.id)) {
|
|
98
|
-
const aliased = project.
|
|
100
|
+
const aliased = must(project.queries.aliased(symbol));
|
|
99
101
|
followed.set(symbol.id, aliased.declarations.length === 0 ? undefined : aliased);
|
|
100
102
|
}
|
|
101
103
|
target = followed.get(symbol.id);
|
|
@@ -104,7 +106,6 @@ export function resolvedTargets<Brand>(
|
|
|
104
106
|
targets.set(node, target);
|
|
105
107
|
}
|
|
106
108
|
});
|
|
107
|
-
}
|
|
108
109
|
return targets;
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -166,7 +167,7 @@ export function classesPassed<Brand>(
|
|
|
166
167
|
);
|
|
167
168
|
const classesOf = (value: PassedValue): readonly string[] => {
|
|
168
169
|
const symbol = value.shorthand
|
|
169
|
-
? project.shorthandValueAt(project.handle(value.name))
|
|
170
|
+
? must(project.shorthandValueAt(project.handle(value.name)))
|
|
170
171
|
: targets.get(value.name);
|
|
171
172
|
return symbol === undefined
|
|
172
173
|
? []
|
package/src/class-members.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { isClassDeclaration, isClassExpression, type Node } from "@typescript/native/unstable/ast";
|
|
8
8
|
import type { Symbol as TSSymbol, SymbolFlags, Type } from "@typescript/native/unstable/sync";
|
|
9
9
|
import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { must } from "./query.ts";
|
|
11
11
|
import type { ProjectView } from "./session.ts";
|
|
12
12
|
|
|
13
13
|
/** One member of a class's value that the target declares. */
|
|
@@ -44,16 +44,15 @@ export function classNames<Brand>(
|
|
|
44
44
|
return names;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* The types of a batch of nodes, for a pass that cannot go on past a type the checker
|
|
49
|
+
* did not answer: it stops there, as {@link must} stops.
|
|
50
|
+
*/
|
|
48
51
|
export function typesAt<Brand>(
|
|
49
52
|
project: ProjectView<Brand>,
|
|
50
53
|
nodes: readonly Node[],
|
|
51
54
|
): (Type | undefined)[] {
|
|
52
|
-
|
|
53
|
-
for (let from = 0; from < nodes.length; from += DEFAULT_BATCH_CAP) {
|
|
54
|
-
types.push(...project.checker.getTypeAtLocation(nodes.slice(from, from + DEFAULT_BATCH_CAP)));
|
|
55
|
-
}
|
|
56
|
-
return types;
|
|
55
|
+
return project.queries.typesAt(nodes).map(must);
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
/**
|
|
@@ -68,9 +67,9 @@ function sideTypes<Brand>(
|
|
|
68
67
|
if (side === "instance") {
|
|
69
68
|
return typesAt(project, nodes);
|
|
70
69
|
}
|
|
71
|
-
const symbols = project.symbolsAt(nodes.map((node) => project.handle(node)));
|
|
70
|
+
const symbols = project.symbolsAt(nodes.map((node) => project.handle(node))).map(must);
|
|
72
71
|
const present = symbols.filter((symbol): symbol is TSSymbol => symbol !== undefined);
|
|
73
|
-
const types =
|
|
72
|
+
const types = project.queries.typesOfSymbols(present).map(must);
|
|
74
73
|
let next = 0;
|
|
75
74
|
return symbols.map((symbol) => (symbol === undefined ? undefined : types[next++]));
|
|
76
75
|
}
|
|
@@ -87,7 +86,7 @@ export function classMembers<Brand>(
|
|
|
87
86
|
side: ClassSide,
|
|
88
87
|
): ReadonlyMap<string, readonly ClassMember[]> {
|
|
89
88
|
const named = [...classNames(project, held)].filter(([id]) => classIds.has(id));
|
|
90
|
-
const own =
|
|
89
|
+
const own = project.ownPaths();
|
|
91
90
|
const types = sideTypes(
|
|
92
91
|
project,
|
|
93
92
|
named.map(([, node]) => node),
|
|
@@ -100,7 +99,7 @@ export function classMembers<Brand>(
|
|
|
100
99
|
return;
|
|
101
100
|
}
|
|
102
101
|
const found: ClassMember[] = [];
|
|
103
|
-
for (const property of project.
|
|
102
|
+
for (const property of must(project.queries.propertiesOf(type))) {
|
|
104
103
|
for (const handle of property.declarations) {
|
|
105
104
|
if (!own.has(handle.path)) {
|
|
106
105
|
continue;
|
|
@@ -25,6 +25,7 @@ import type { AliasChains } from "./alias-chain.ts";
|
|
|
25
25
|
import { resolvedTargets } from "./calls.ts";
|
|
26
26
|
import type { Config, DeclarationEntry } from "./config.ts";
|
|
27
27
|
import type { Inventory } from "./inventory.ts";
|
|
28
|
+
import { must, Unanswerable } from "./query.ts";
|
|
28
29
|
import { nameComponent } from "./ref.ts";
|
|
29
30
|
import type { ProjectView } from "./session.ts";
|
|
30
31
|
|
|
@@ -90,7 +91,7 @@ function unaliased<Brand>(project: ProjectView<Brand>, symbol: TSSymbol): TSSymb
|
|
|
90
91
|
if ((symbol.flags & SymbolFlags.Alias) === 0) {
|
|
91
92
|
return symbol;
|
|
92
93
|
}
|
|
93
|
-
const target = project.
|
|
94
|
+
const target = must(project.queries.aliased(symbol));
|
|
94
95
|
return target.declarations.length === 0 ? undefined : target;
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -105,14 +106,18 @@ function isClassMember(symbol: TSSymbol): boolean {
|
|
|
105
106
|
* with the static suffix names one of the first and a component without it names an
|
|
106
107
|
* instance member or one of the second.
|
|
107
108
|
*/
|
|
108
|
-
function candidatesOf(
|
|
109
|
-
|
|
109
|
+
function candidatesOf<Brand>(
|
|
110
|
+
project: ProjectView<Brand>,
|
|
111
|
+
container: TSSymbol,
|
|
112
|
+
isStatic: boolean,
|
|
113
|
+
): readonly TSSymbol[] {
|
|
114
|
+
const exported = [...must(project.queries.exportsOf(container)).values()];
|
|
110
115
|
if (isStatic) {
|
|
111
116
|
return exported.filter(isClassMember);
|
|
112
117
|
}
|
|
113
118
|
const isClass = (container.flags & SymbolFlags.Class) !== 0;
|
|
114
119
|
return [
|
|
115
|
-
...
|
|
120
|
+
...must(project.queries.membersOf(container)).values(),
|
|
116
121
|
...exported.filter((member) => !isClass || !isClassMember(member)),
|
|
117
122
|
];
|
|
118
123
|
}
|
|
@@ -124,7 +129,7 @@ function memberOf<Brand>(
|
|
|
124
129
|
component: string,
|
|
125
130
|
isStatic: boolean,
|
|
126
131
|
): TSSymbol | undefined {
|
|
127
|
-
const member = candidatesOf(container, isStatic).find(
|
|
132
|
+
const member = candidatesOf(project, container, isStatic).find(
|
|
128
133
|
(candidate) => nameComponent(candidate.name) === component,
|
|
129
134
|
);
|
|
130
135
|
return member === undefined ? undefined : unaliased(project, member);
|
|
@@ -190,7 +195,7 @@ function modulesNamed<Brand>(
|
|
|
190
195
|
file.forEachChild(visit);
|
|
191
196
|
}
|
|
192
197
|
const specified = [...written];
|
|
193
|
-
const symbols = project.symbolsAt(specified.map(([, node]) => project.handle(node)));
|
|
198
|
+
const symbols = project.symbolsAt(specified.map(([, node]) => project.handle(node))).map(must);
|
|
194
199
|
const modules = new Map<string, TSSymbol>();
|
|
195
200
|
specified.forEach(([name], index) => {
|
|
196
201
|
const symbol = symbols[index];
|
|
@@ -238,7 +243,7 @@ export function resolveEntries<Brand>(
|
|
|
238
243
|
const module = modules.get(entry.module);
|
|
239
244
|
start = module === undefined ? undefined : memberOf(project, module, first, false);
|
|
240
245
|
} else if (nameComponent(first) === first) {
|
|
241
|
-
const global = project.
|
|
246
|
+
const global = must(project.queries.resolveName(first, ANY_MEANING, undefined, false));
|
|
242
247
|
start = global === undefined ? undefined : unaliased(project, global);
|
|
243
248
|
}
|
|
244
249
|
const declared = walkPath(project, start, rest, isStatic);
|
|
@@ -334,17 +339,28 @@ export function configuredDeclarations(config: Config): readonly ConfiguredDecla
|
|
|
334
339
|
];
|
|
335
340
|
}
|
|
336
341
|
|
|
337
|
-
/**
|
|
342
|
+
/**
|
|
343
|
+
* Whether each configured entry names a declaration in one project, in the entries'
|
|
344
|
+
* order. Where the checker leaves a question of the resolution unanswered, every entry
|
|
345
|
+
* counts as naming one, so no entry is reported as naming nothing on a gap.
|
|
346
|
+
*/
|
|
338
347
|
export function declarationsNamed<Brand>(
|
|
339
348
|
project: ProjectView<Brand>,
|
|
340
349
|
held: Inventory,
|
|
341
350
|
configured: readonly ConfiguredDeclaration[],
|
|
342
351
|
): readonly boolean[] {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
352
|
+
try {
|
|
353
|
+
return resolveEntries(
|
|
354
|
+
project,
|
|
355
|
+
held,
|
|
356
|
+
configured.map((one) => one.entry),
|
|
357
|
+
).map(namesADeclaration);
|
|
358
|
+
} catch (error: unknown) {
|
|
359
|
+
if (!(error instanceof Unanswerable)) {
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
return configured.map(() => true);
|
|
363
|
+
}
|
|
348
364
|
}
|
|
349
365
|
|
|
350
366
|
/**
|