@cplieger/deadset-ts 3.12.1 → 4.0.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 +4 -4
- package/dist/bin/node-host.js +10 -7
- package/dist/src/alias-chain.js +59 -7
- package/dist/src/analysis.js +217 -48
- package/dist/src/calls.js +23 -22
- package/dist/src/class-members.js +10 -11
- package/dist/src/components.js +4 -2
- package/dist/src/configured-declarations.js +56 -7
- package/dist/src/conformance-record.js +2 -2
- 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 +105 -28
- package/dist/src/edges.js +4 -1
- 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/completion.js +37 -8
- package/dist/src/findings/emitters.js +20 -6
- package/dist/src/findings/interfaces.js +7 -1
- package/dist/src/findings/intra-function.js +91 -2
- package/dist/src/findings/self-check.js +30 -7
- package/dist/src/findings/unused-declarations.js +15 -3
- package/dist/src/findings-pass.js +18 -11
- 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 +660 -0
- package/dist/src/inventory.js +85 -4
- package/dist/src/json-chunks.js +76 -0
- package/dist/src/kinds.js +337 -35
- 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 +46 -4
- package/dist/src/report.js +14 -3
- package/dist/src/reporters/annotations.js +9 -5
- package/dist/src/reporters/json.js +3 -2
- package/dist/src/reporters/sarif.js +22 -16
- package/dist/src/reporters/template.js +112 -12
- package/dist/src/reporters/text.js +8 -6
- package/dist/src/roots.js +27 -11
- package/dist/src/serialization-contract.js +53 -32
- package/dist/src/session.js +79 -39
- package/dist/src/signatures.js +32 -0
- package/dist/src/suppress-file.js +29 -7
- package/dist/src/suppress-inline.js +8 -1
- package/dist/src/suppress.js +27 -0
- package/dist/src/sweep.js +4 -1
- package/dist/src/value-reach.js +44 -25
- package/dist/src/verbs/analyze.js +39 -14
- package/dist/src/verbs/describe.js +12 -8
- package/dist/src/verbs/explain.js +22 -9
- package/dist/src/verbs/print-projects.js +40 -7
- package/dist/src/verbs/print-retained.js +6 -2
- package/dist/src/verbs/print-roots.js +5 -1
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +75 -11
- package/src/analysis.ts +385 -104
- package/src/calls.ts +10 -9
- package/src/class-members.ts +10 -11
- package/src/components.ts +4 -2
- package/src/configured-declarations.ts +80 -8
- package/src/conformance-record.ts +2 -2
- package/src/dependencies.ts +42 -20
- package/src/deprecation.ts +53 -27
- package/src/destructuring.ts +50 -39
- package/src/discover.ts +177 -33
- package/src/edges.ts +4 -1
- package/src/entry-points.ts +7 -0
- package/src/enum-group.ts +56 -42
- package/src/exempt.ts +50 -2
- package/src/finding.ts +5 -1
- package/src/findings/completion.ts +46 -10
- package/src/findings/emitter.ts +3 -0
- package/src/findings/emitters.ts +27 -6
- package/src/findings/interfaces.ts +7 -1
- package/src/findings/intra-function.ts +106 -3
- package/src/findings/self-check.ts +48 -7
- package/src/findings/unused-declarations.ts +18 -4
- package/src/findings-pass.ts +26 -11
- 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 +889 -0
- package/src/inventory.ts +97 -4
- package/src/json-chunks.ts +87 -0
- package/src/kinds.ts +355 -41
- package/src/partial.ts +40 -0
- package/src/query.ts +522 -0
- package/src/references.ts +119 -38
- package/src/reflective-lookup.ts +56 -6
- package/src/report.ts +32 -4
- 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 +27 -18
- package/src/reporters/template.ts +119 -14
- package/src/reporters/text.ts +12 -12
- package/src/roots.ts +34 -12
- package/src/serialization-contract.ts +59 -40
- package/src/session.ts +146 -58
- package/src/signatures.ts +55 -0
- package/src/suppress-file.ts +40 -8
- package/src/suppress-inline.ts +8 -1
- package/src/suppress.ts +31 -0
- package/src/sweep.ts +9 -4
- package/src/value-reach.ts +66 -28
- package/src/verbs/analyze.ts +51 -14
- package/src/verbs/describe.ts +13 -9
- package/src/verbs/explain.ts +20 -9
- package/src/verbs/print-projects.ts +53 -7
- package/src/verbs/print-retained.ts +6 -2
- package/src/verbs/print-roots.ts +5 -1
- package/src/version.ts +1 -1
- package/dist/src/reporters/rules.js +0 -178
- package/src/reporters/rules.ts +0 -195
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,
|
|
@@ -16,16 +15,31 @@ import {
|
|
|
16
15
|
moduleIdentity,
|
|
17
16
|
type LoadedConsumer,
|
|
18
17
|
} from "./consumers.ts";
|
|
18
|
+
import {
|
|
19
|
+
configuredDeclarations,
|
|
20
|
+
configuredRef,
|
|
21
|
+
declarationsNamed,
|
|
22
|
+
} from "./configured-declarations.ts";
|
|
19
23
|
import { decorator } from "./decorator.ts";
|
|
20
24
|
import { dependenciesOf, projectNeeds } from "./dependencies.ts";
|
|
21
|
-
import { deprecatedDeclarations } from "./deprecation.ts";
|
|
22
|
-
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";
|
|
23
36
|
import { readEdgeSides } from "./edges.ts";
|
|
24
37
|
import { enumGroup } from "./enum-group.ts";
|
|
25
38
|
import {
|
|
26
39
|
computeExemptions,
|
|
27
40
|
disabledClasses,
|
|
28
41
|
exemptionsOf,
|
|
42
|
+
isUnansweredRecord,
|
|
29
43
|
retainedIn,
|
|
30
44
|
type Detector,
|
|
31
45
|
type Detectors,
|
|
@@ -33,7 +47,7 @@ import {
|
|
|
33
47
|
import type { TSExemptionClass } from "./exempt-classes.ts";
|
|
34
48
|
import { heldByInclusion, sourceTree } from "./file-facts.ts";
|
|
35
49
|
import type { Finding } from "./finding.ts";
|
|
36
|
-
import { findingsPass, type PassResult } from "./findings-pass.ts";
|
|
50
|
+
import { findingsPass, recordedFindings, rowKey, type PassResult } from "./findings-pass.ts";
|
|
37
51
|
import type { EmitterInput } from "./findings/emitter.ts";
|
|
38
52
|
import {
|
|
39
53
|
NO_SELF_CHECK,
|
|
@@ -45,7 +59,8 @@ import type { Host } from "./host.ts";
|
|
|
45
59
|
import { implementations, mergeImplementations } from "./implementations.ts";
|
|
46
60
|
import { injectionContainer } from "./injection-container.ts";
|
|
47
61
|
import { interfaceSatisfaction } from "./interface-satisfaction.ts";
|
|
48
|
-
import {
|
|
62
|
+
import { intraFunctionFacts, projectParts, type ProjectParts } from "./intra-function-parts.ts";
|
|
63
|
+
import { inventory, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
49
64
|
import { readManifest } from "./manifest.ts";
|
|
50
65
|
import {
|
|
51
66
|
matrixOf,
|
|
@@ -56,6 +71,7 @@ import {
|
|
|
56
71
|
type SweepResult,
|
|
57
72
|
} from "./matrix.ts";
|
|
58
73
|
import { relativePath, resolvePath } from "./paths.ts";
|
|
74
|
+
import type { UnansweredQuestion } from "./query.ts";
|
|
59
75
|
import { byPosition, type Position } from "./position.ts";
|
|
60
76
|
import { reflectiveLookup } from "./reflective-lookup.ts";
|
|
61
77
|
import { references, testFileRulesOf, type Reference, type TestFileRule } from "./references.ts";
|
|
@@ -64,8 +80,8 @@ import type { Scope } from "./scope.ts";
|
|
|
64
80
|
import { serializationContract } from "./serialization-contract.ts";
|
|
65
81
|
import { diagnosticsOf, runSession, type Engine, type ProjectView } from "./session.ts";
|
|
66
82
|
import { accessorsOf, storesOf } from "./stores.ts";
|
|
67
|
-
import type
|
|
68
|
-
import { readBaseline, readIgnoreFile } from "./suppress-file.ts";
|
|
83
|
+
import { isPartKind, type SuppressionRecord, type Suppressions, type Verdict } from "./suppress.ts";
|
|
84
|
+
import { bindRows, readBaseline, readIgnoreFile, type Recorded } from "./suppress-file.ts";
|
|
69
85
|
import { inlineDirectives, inlineSuppressions, type InlineDirective } from "./suppress-inline.ts";
|
|
70
86
|
import type { Exemption, Mode, SweepInput } from "./sweep.ts";
|
|
71
87
|
import { readTemplates, templateField } from "./template-field.ts";
|
|
@@ -99,6 +115,10 @@ export interface RunRoots {
|
|
|
99
115
|
readonly roots: readonly RunRoot[];
|
|
100
116
|
/** One finding per configured root or pattern that named nothing in any project. */
|
|
101
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[];
|
|
102
122
|
}
|
|
103
123
|
|
|
104
124
|
/**
|
|
@@ -150,15 +170,19 @@ interface ConsumerReads {
|
|
|
150
170
|
interface ReadProjects<Answer> {
|
|
151
171
|
readonly answers: readonly Answer[];
|
|
152
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[];
|
|
153
177
|
}
|
|
154
178
|
|
|
155
179
|
/**
|
|
156
|
-
* Reads every project the scope discovers, and every project of
|
|
157
|
-
* declares, in one snapshot
|
|
158
|
-
* enumerated
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
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.
|
|
162
186
|
*/
|
|
163
187
|
function readProjects<Answer>(
|
|
164
188
|
engine: Engine,
|
|
@@ -183,18 +207,39 @@ function readProjects<Answer>(
|
|
|
183
207
|
testFiles: config.ts.testFiles,
|
|
184
208
|
publishedAPI: config.targetKind === "library",
|
|
185
209
|
};
|
|
186
|
-
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[] = [];
|
|
187
220
|
const consumed = new Map<string, Reference>();
|
|
188
|
-
const
|
|
221
|
+
const session = runSession(
|
|
189
222
|
engine,
|
|
190
223
|
[...discovered.configFiles, ...consumerOf.keys()],
|
|
191
224
|
(project) => {
|
|
192
|
-
const errors =
|
|
193
|
-
|
|
194
|
-
|
|
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);
|
|
195
241
|
return undefined;
|
|
196
242
|
}
|
|
197
|
-
const consumer = consumerOf.get(project.configFile);
|
|
198
243
|
if (consumer !== undefined) {
|
|
199
244
|
for (const reference of consumerReferences(
|
|
200
245
|
project,
|
|
@@ -215,19 +260,36 @@ function readProjects<Answer>(
|
|
|
215
260
|
const configuration = ids.get(project.configFile) ?? project.configFile;
|
|
216
261
|
return stage(project, { configuration, configFile: project.configFile, held, rooted });
|
|
217
262
|
},
|
|
263
|
+
discovered.derived ? "record" : "refuse",
|
|
218
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
|
+
}
|
|
219
271
|
if (failures.length > 0) {
|
|
220
272
|
throw new DiscoveryError(
|
|
221
273
|
`${String(failures.length)} error(s); no answer was produced`,
|
|
222
274
|
failures,
|
|
223
275
|
);
|
|
224
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
|
+
}
|
|
225
285
|
return {
|
|
226
|
-
answers
|
|
286
|
+
answers,
|
|
227
287
|
consumers: {
|
|
228
288
|
loaded: loads.map((load) => load.consumer),
|
|
229
289
|
references: [...consumed.values()],
|
|
230
290
|
},
|
|
291
|
+
notBuilt,
|
|
292
|
+
unanswered: session.unanswered,
|
|
231
293
|
};
|
|
232
294
|
}
|
|
233
295
|
|
|
@@ -245,7 +307,8 @@ export function runRoots(
|
|
|
245
307
|
provenance: Provenance,
|
|
246
308
|
): RunRoots {
|
|
247
309
|
const targetRoot = scope.target.path;
|
|
248
|
-
const
|
|
310
|
+
const read = readProjects(engine, host, scope, config, (_project, projectRead) => projectRead);
|
|
311
|
+
const projects = read.answers;
|
|
249
312
|
|
|
250
313
|
const configurations: string[] = [];
|
|
251
314
|
const merged = new Map<string, Omit<RunRoot, "configurations"> & { configurations: string[] }>();
|
|
@@ -289,6 +352,8 @@ export function runRoots(
|
|
|
289
352
|
unmatched,
|
|
290
353
|
documentOf(host, provenance, ROOTS_SETTING, targetRoot),
|
|
291
354
|
),
|
|
355
|
+
notBuilt: read.notBuilt,
|
|
356
|
+
unanswered: read.unanswered,
|
|
292
357
|
};
|
|
293
358
|
}
|
|
294
359
|
|
|
@@ -301,6 +366,15 @@ export interface RunSweep {
|
|
|
301
366
|
* would otherwise judge dead, in the declarations' site order.
|
|
302
367
|
*/
|
|
303
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[];
|
|
304
378
|
}
|
|
305
379
|
|
|
306
380
|
/** The exemption classes this analyzer detects, each by its detector; any other retains nothing. */
|
|
@@ -320,6 +394,8 @@ interface SweptProject<Extra> {
|
|
|
320
394
|
readonly configFile: string;
|
|
321
395
|
readonly configured: Configured;
|
|
322
396
|
readonly exempt: readonly Exemption[];
|
|
397
|
+
/** The declarations of the project an unanswered question could have kept live. */
|
|
398
|
+
readonly unanswered: readonly string[];
|
|
323
399
|
readonly extra: Extra;
|
|
324
400
|
}
|
|
325
401
|
|
|
@@ -327,9 +403,15 @@ interface SweptProject<Extra> {
|
|
|
327
403
|
interface ReadRun<Extra> {
|
|
328
404
|
readonly matrix: Matrix;
|
|
329
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[];
|
|
330
408
|
readonly projects: readonly SweptProject<Extra>[];
|
|
331
409
|
/** Every declared consumer, each loaded, in the scope's order. */
|
|
332
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[];
|
|
333
415
|
}
|
|
334
416
|
|
|
335
417
|
/**
|
|
@@ -345,7 +427,11 @@ function readRun<Extra>(
|
|
|
345
427
|
config: Config,
|
|
346
428
|
mode: Mode,
|
|
347
429
|
detectors: Detectors,
|
|
348
|
-
extra: <Brand>(
|
|
430
|
+
extra: <Brand>(
|
|
431
|
+
project: ProjectView<Brand>,
|
|
432
|
+
read: ProjectRead,
|
|
433
|
+
references: readonly Reference[],
|
|
434
|
+
) => Extra,
|
|
349
435
|
): ReadRun<Extra> {
|
|
350
436
|
const targetRoot = scope.target.path;
|
|
351
437
|
const disabled = disabledClasses(config);
|
|
@@ -353,33 +439,48 @@ function readRun<Extra>(
|
|
|
353
439
|
? { delimiters: config.analysis.templateDelimiters, files: [] }
|
|
354
440
|
: readTemplates(host, targetRoot, config.analysis);
|
|
355
441
|
const consumers = scope.consumers.map((consumer) => consumer.path);
|
|
356
|
-
const read = readProjects(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
+
);
|
|
383
484
|
const consumed = read.consumers.references;
|
|
384
485
|
const projects = read.answers.map((one) =>
|
|
385
486
|
consumed.length === 0
|
|
@@ -395,21 +496,72 @@ function readRun<Extra>(
|
|
|
395
496
|
return {
|
|
396
497
|
matrix: matrixOf(projects.map((one) => one.configured)),
|
|
397
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
|
+
],
|
|
398
505
|
projects,
|
|
399
506
|
consumers: read.consumers.loaded,
|
|
507
|
+
notBuilt: read.notBuilt,
|
|
508
|
+
unanswered: read.unanswered,
|
|
400
509
|
};
|
|
401
510
|
}
|
|
402
511
|
|
|
403
|
-
/**
|
|
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
|
+
*/
|
|
404
519
|
function sweptOf<Extra>(
|
|
405
520
|
read: ReadRun<Extra>,
|
|
406
521
|
input: Pick<SweepInput, "marked" | "mode">,
|
|
407
522
|
): RunSweep {
|
|
408
|
-
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
|
+
}
|
|
409
558
|
return {
|
|
410
559
|
matrix: read.matrix,
|
|
411
|
-
sweep:
|
|
560
|
+
sweep: result,
|
|
412
561
|
retained: retainedIn(read.matrix, swept),
|
|
562
|
+
notBuilt: read.notBuilt,
|
|
563
|
+
unanswered: read.unanswered,
|
|
564
|
+
heldByUnanswered: [...held],
|
|
413
565
|
};
|
|
414
566
|
}
|
|
415
567
|
|
|
@@ -439,13 +591,15 @@ export function runSweep(
|
|
|
439
591
|
|
|
440
592
|
/** What each project contributes to the facts every emitter reads beside the sweep. */
|
|
441
593
|
interface EmitterExtra {
|
|
442
|
-
readonly
|
|
594
|
+
readonly deprecation: Deprecation;
|
|
443
595
|
readonly accessors: ReturnType<typeof accessorsOf>;
|
|
444
596
|
readonly needs: ReturnType<typeof projectNeeds>;
|
|
445
597
|
readonly implementations: ReturnType<typeof implementations>;
|
|
446
598
|
readonly heldByInclusion: readonly string[];
|
|
447
599
|
readonly rooted: Roots;
|
|
448
600
|
readonly directives: readonly InlineDirective[];
|
|
601
|
+
readonly declarationsNamed: readonly boolean[];
|
|
602
|
+
readonly parts: ProjectParts;
|
|
449
603
|
}
|
|
450
604
|
|
|
451
605
|
/** Reads a run's projects with every per-project fact an emitter reads. */
|
|
@@ -458,15 +612,31 @@ function readEmitterRun(
|
|
|
458
612
|
detectors: Detectors,
|
|
459
613
|
): ReadRun<EmitterExtra> {
|
|
460
614
|
const targetRoot = scope.target.path;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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])] };
|
|
470
640
|
}
|
|
471
641
|
|
|
472
642
|
/**
|
|
@@ -487,7 +657,7 @@ function emitterInputOver(
|
|
|
487
657
|
return {
|
|
488
658
|
config,
|
|
489
659
|
swept,
|
|
490
|
-
deprecated: new Set(projects.flatMap((one) => one.extra.deprecated)),
|
|
660
|
+
deprecated: new Set(projects.flatMap((one) => one.extra.deprecation.deprecated)),
|
|
491
661
|
stores: storesOf(
|
|
492
662
|
projects.map((one) => ({
|
|
493
663
|
references: one.configured.references,
|
|
@@ -516,6 +686,11 @@ function emitterInputOver(
|
|
|
516
686
|
edges: readEdgeSides(host, targetRoot),
|
|
517
687
|
},
|
|
518
688
|
selfCheck,
|
|
689
|
+
intraFunction: intraFunctionFacts(
|
|
690
|
+
projects.map((one) => one.extra.parts),
|
|
691
|
+
projects.flatMap((one) => one.configured.references),
|
|
692
|
+
new Set([...read.exempt.map((one) => one.id), ...swept.heldByUnanswered]),
|
|
693
|
+
),
|
|
519
694
|
};
|
|
520
695
|
}
|
|
521
696
|
|
|
@@ -548,15 +723,25 @@ export interface AnalysisInputs {
|
|
|
548
723
|
readonly suppressions: Suppressions;
|
|
549
724
|
}
|
|
550
725
|
|
|
726
|
+
/** One run read once, and analyzed under any set of baseline rows without reading it again. */
|
|
727
|
+
interface AnalysisReader {
|
|
728
|
+
readonly run: RunFacts;
|
|
729
|
+
readonly configured: readonly Configured[];
|
|
730
|
+
/** The declarations of the run, which a baseline row binds against. */
|
|
731
|
+
readonly symbols: readonly InventorySymbol[];
|
|
732
|
+
/** The inputs of the run under the directives, the ignore file and the baseline given. */
|
|
733
|
+
readonly inputsUnder: (baseline: Suppressions) => AnalysisInputs;
|
|
734
|
+
}
|
|
735
|
+
|
|
551
736
|
/**
|
|
552
737
|
* Reads the run the scope and the configuration describe for its findings. The inline
|
|
553
|
-
* directives, the ignore file's entries and the baseline's rows are
|
|
554
|
-
*
|
|
555
|
-
*
|
|
738
|
+
* directives, the ignore file's entries and the baseline's rows are bound before any
|
|
739
|
+
* sweep, and the run is swept once under every bound record's mark and once under none,
|
|
740
|
+
* so a record is decided against the finding its code would have produced. The
|
|
556
741
|
* self-check family reports the refused suppressions and the roots that named nothing.
|
|
557
742
|
* Beside the inputs it answers the projects and test-file rules a report names.
|
|
558
743
|
*/
|
|
559
|
-
function
|
|
744
|
+
function readAnalysis(
|
|
560
745
|
engine: Engine,
|
|
561
746
|
host: Host,
|
|
562
747
|
scope: Scope,
|
|
@@ -564,40 +749,17 @@ function runAnalysisInputs(
|
|
|
564
749
|
provenance: Provenance,
|
|
565
750
|
mode: Mode,
|
|
566
751
|
detectors: Detectors,
|
|
567
|
-
): {
|
|
568
|
-
readonly inputs: AnalysisInputs;
|
|
569
|
-
readonly run: RunFacts;
|
|
570
|
-
readonly configured: readonly Configured[];
|
|
571
|
-
} {
|
|
752
|
+
): AnalysisReader {
|
|
572
753
|
const targetRoot = scope.target.path;
|
|
573
754
|
const read = readEmitterRun(engine, host, scope, config, mode, detectors);
|
|
574
755
|
const symbols = read.matrix.union.symbols;
|
|
575
|
-
const
|
|
756
|
+
const fixed = [
|
|
576
757
|
inlineSuppressions(read.projects.map((one) => one.extra.directives)),
|
|
577
758
|
readIgnoreFile(host, targetRoot, symbols),
|
|
578
|
-
readBaseline(host, targetRoot, symbols),
|
|
579
759
|
];
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
refusals: documents.flatMap((one) => one.refusals),
|
|
583
|
-
};
|
|
584
|
-
const facts: SelfCheckFacts = {
|
|
585
|
-
refusals: suppressions.refusals,
|
|
586
|
-
unmatchedRoots: unmatchedEverywhere(
|
|
587
|
-
config.rootPatterns,
|
|
588
|
-
read.projects.map((one) => one.extra.rooted),
|
|
589
|
-
),
|
|
590
|
-
rootsDocument: documentOf(host, provenance, ROOTS_SETTING, targetRoot),
|
|
591
|
-
};
|
|
592
|
-
const marked = [...new Set(suppressions.records.map((record) => record.bound))].filter(
|
|
593
|
-
(id) => id !== "",
|
|
594
|
-
);
|
|
760
|
+
const inputsUnder = (baseline: Suppressions): AnalysisInputs =>
|
|
761
|
+
inputsOver(host, scope, config, provenance, mode, read, [...fixed, baseline]);
|
|
595
762
|
return {
|
|
596
|
-
inputs: {
|
|
597
|
-
marked: emitterInputOver(host, scope, config, read, { marked, mode }, facts),
|
|
598
|
-
unmarked: emitterInputOver(host, scope, config, read, { marked: [], mode }, facts),
|
|
599
|
-
suppressions,
|
|
600
|
-
},
|
|
601
763
|
run: {
|
|
602
764
|
projects: read.projects.map((one) => ({
|
|
603
765
|
id: one.configured.configuration,
|
|
@@ -608,8 +770,56 @@ function runAnalysisInputs(
|
|
|
608
770
|
read.projects.flatMap((one) => one.configured.testFiles),
|
|
609
771
|
),
|
|
610
772
|
consumers: read.consumers,
|
|
773
|
+
notBuilt: read.notBuilt,
|
|
774
|
+
unanswered: read.unanswered,
|
|
611
775
|
},
|
|
612
776
|
configured: read.projects.map((one) => one.configured),
|
|
777
|
+
symbols,
|
|
778
|
+
inputsUnder,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/** The inputs of one read run under the suppression documents given, in reading order. */
|
|
783
|
+
function inputsOver(
|
|
784
|
+
host: Host,
|
|
785
|
+
scope: Scope,
|
|
786
|
+
config: Config,
|
|
787
|
+
provenance: Provenance,
|
|
788
|
+
mode: Mode,
|
|
789
|
+
read: ReadRun<EmitterExtra>,
|
|
790
|
+
documents: readonly Suppressions[],
|
|
791
|
+
): AnalysisInputs {
|
|
792
|
+
const targetRoot = scope.target.path;
|
|
793
|
+
const suppressions: Suppressions = {
|
|
794
|
+
records: documents.flatMap((one) => one.records),
|
|
795
|
+
refusals: documents.flatMap((one) => one.refusals),
|
|
796
|
+
};
|
|
797
|
+
const facts: SelfCheckFacts = {
|
|
798
|
+
refusals: suppressions.refusals,
|
|
799
|
+
unmatchedRoots: unmatchedEverywhere(
|
|
800
|
+
config.rootPatterns,
|
|
801
|
+
read.projects.map((one) => one.extra.rooted),
|
|
802
|
+
),
|
|
803
|
+
rootsDocument: documentOf(host, provenance, ROOTS_SETTING, targetRoot),
|
|
804
|
+
unmatchedDeclarations: configuredDeclarations(config)
|
|
805
|
+
.filter((_, at) => !read.projects.some((one) => one.extra.declarationsNamed[at] === true))
|
|
806
|
+
.map((one) => ({
|
|
807
|
+
ref: configuredRef(one.entry),
|
|
808
|
+
key: one.key,
|
|
809
|
+
document: documentOf(host, provenance, one.key, targetRoot),
|
|
810
|
+
})),
|
|
811
|
+
};
|
|
812
|
+
const marked = [
|
|
813
|
+
...new Set(
|
|
814
|
+
suppressions.records
|
|
815
|
+
.filter((record) => !isPartKind(record.code))
|
|
816
|
+
.map((record) => record.bound),
|
|
817
|
+
),
|
|
818
|
+
].filter((id) => id !== "");
|
|
819
|
+
return {
|
|
820
|
+
marked: emitterInputOver(host, scope, config, read, { marked, mode }, facts),
|
|
821
|
+
unmarked: emitterInputOver(host, scope, config, read, { marked: [], mode }, facts),
|
|
822
|
+
suppressions,
|
|
613
823
|
};
|
|
614
824
|
}
|
|
615
825
|
|
|
@@ -628,6 +838,10 @@ export interface RunFacts {
|
|
|
628
838
|
readonly testFileRules: readonly TestFileRule[];
|
|
629
839
|
/** Every consumer the run loaded beside the target, in the scope's order. */
|
|
630
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[];
|
|
631
845
|
}
|
|
632
846
|
|
|
633
847
|
/** The findings of one run, beside what a report states about the run itself. */
|
|
@@ -655,14 +869,81 @@ export function runAnalysis(
|
|
|
655
869
|
mode: Mode,
|
|
656
870
|
detectors: Detectors = DETECTORS,
|
|
657
871
|
): RunAnalysis {
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
872
|
+
const reader = readAnalysis(engine, host, scope, config, provenance, mode, detectors);
|
|
873
|
+
const inputs = reader.inputsUnder(readBaseline(host, scope.target.path, reader.symbols));
|
|
874
|
+
return analysisOf(reader, inputs);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function analysisOf(reader: AnalysisReader, inputs: AnalysisInputs): RunAnalysis {
|
|
878
|
+
return {
|
|
879
|
+
result: findingsPass(inputs),
|
|
880
|
+
run: reader.run,
|
|
881
|
+
swept: inputs.marked.swept,
|
|
882
|
+
configured: reader.configured,
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/** A baseline write: the analysis of its first round, and the rows its fixpoint holds. */
|
|
887
|
+
interface BaselineWrite {
|
|
888
|
+
/** The first round, which reads no baseline. */
|
|
889
|
+
readonly first: RunAnalysis;
|
|
890
|
+
/** The rows that remain when a round records no row and removes none, in recording order. */
|
|
891
|
+
readonly rows: readonly Recorded[];
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* The fixpoint a baseline write records. The first round reads no baseline; each later
|
|
896
|
+
* round reads the rows recorded so far, records a row for each recordable finding no row
|
|
897
|
+
* already names, and removes every row it reports stale. A removed row is not recorded
|
|
898
|
+
* again, so the write ends.
|
|
899
|
+
*/
|
|
900
|
+
export function runBaselineWrite(
|
|
901
|
+
engine: Engine,
|
|
902
|
+
host: Host,
|
|
903
|
+
scope: Scope,
|
|
904
|
+
config: Config,
|
|
905
|
+
provenance: Provenance,
|
|
906
|
+
mode: Mode,
|
|
907
|
+
reason: string,
|
|
908
|
+
detectors: Detectors = DETECTORS,
|
|
909
|
+
): BaselineWrite {
|
|
910
|
+
const reader = readAnalysis(engine, host, scope, config, provenance, mode, detectors);
|
|
911
|
+
const first = analysisOf(reader, reader.inputsUnder({ records: [], refusals: [] }));
|
|
912
|
+
let rows: readonly Recorded[] = [];
|
|
913
|
+
const removed = new Set<string>();
|
|
914
|
+
let result = first.result;
|
|
915
|
+
let stale: ReadonlySet<string> = new Set<string>();
|
|
916
|
+
for (;;) {
|
|
917
|
+
const held = new Set(rows.map(rowKey));
|
|
918
|
+
const added = recordedFindings(result.findings).filter(
|
|
919
|
+
(row) => !held.has(rowKey(row)) && !removed.has(rowKey(row)),
|
|
920
|
+
);
|
|
921
|
+
if (added.length === 0 && stale.size === 0) {
|
|
922
|
+
return { first, rows };
|
|
923
|
+
}
|
|
924
|
+
for (const key of stale) {
|
|
925
|
+
removed.add(key);
|
|
926
|
+
}
|
|
927
|
+
rows = [...rows.filter((row) => !stale.has(rowKey(row))), ...added];
|
|
928
|
+
const inputs = reader.inputsUnder(bindRows(rows, reason, reader.symbols));
|
|
929
|
+
result = findingsPass(inputs);
|
|
930
|
+
stale = staleRows(inputs.suppressions.records, result.ledger.verdicts);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/** The baseline rows a round reports stale: every record a row binds to matched nothing. */
|
|
935
|
+
function staleRows(
|
|
936
|
+
records: readonly SuppressionRecord[],
|
|
937
|
+
verdicts: readonly Verdict[],
|
|
938
|
+
): ReadonlySet<string> {
|
|
939
|
+
const live = new Set<string>();
|
|
940
|
+
const stale = new Set<string>();
|
|
941
|
+
records.forEach((record, at) => {
|
|
942
|
+
if (record.mechanism !== "baseline") {
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const key = rowKey(record);
|
|
946
|
+
(verdicts[at] === "stale" ? stale : live).add(key);
|
|
947
|
+
});
|
|
948
|
+
return new Set([...stale].filter((key) => !live.has(key)));
|
|
668
949
|
}
|