@cplieger/deadset-ts 3.6.0 → 3.8.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.
Files changed (39) hide show
  1. package/dist/src/analysis.js +92 -23
  2. package/dist/src/edges.js +190 -0
  3. package/dist/src/finding.js +0 -11
  4. package/dist/src/findings/completion.js +92 -35
  5. package/dist/src/findings/dependencies-and-module-machinery.js +3 -11
  6. package/dist/src/findings/emitters.js +42 -4
  7. package/dist/src/findings/reads-and-writes.js +16 -69
  8. package/dist/src/findings/self-check.js +127 -2
  9. package/dist/src/findings/unused-declarations.js +8 -40
  10. package/dist/src/findings/visibility-narrowing.js +9 -40
  11. package/dist/src/findings-pass.js +97 -0
  12. package/dist/src/json-document.js +113 -0
  13. package/dist/src/kinds.js +47 -45
  14. package/dist/src/roots.js +0 -34
  15. package/dist/src/suppress-file.js +196 -0
  16. package/dist/src/suppress-inline.js +259 -0
  17. package/dist/src/suppress.js +148 -0
  18. package/jsr.json +1 -1
  19. package/package.json +1 -1
  20. package/src/analysis.ts +176 -32
  21. package/src/edges.ts +241 -0
  22. package/src/finding.ts +76 -10
  23. package/src/findings/boundary.ts +0 -11
  24. package/src/findings/completion.ts +117 -71
  25. package/src/findings/dependencies-and-module-machinery.ts +4 -19
  26. package/src/findings/emitter.ts +6 -2
  27. package/src/findings/emitters.ts +81 -7
  28. package/src/findings/interfaces.ts +5 -17
  29. package/src/findings/reads-and-writes.ts +37 -151
  30. package/src/findings/self-check.ts +205 -2
  31. package/src/findings/unused-declarations.ts +11 -58
  32. package/src/findings/visibility-narrowing.ts +11 -58
  33. package/src/findings-pass.ts +145 -0
  34. package/src/json-document.ts +130 -0
  35. package/src/kinds.ts +79 -45
  36. package/src/roots.ts +0 -39
  37. package/src/suppress-file.ts +283 -0
  38. package/src/suppress-inline.ts +318 -0
  39. package/src/suppress.ts +262 -0
@@ -0,0 +1,148 @@
1
+ /**
2
+ * The suppression grammar's records, and the ledger that decides what each one did. A
3
+ * record is one code bound to one site with its reason, from an inline directive, an
4
+ * ignore entry or a baseline row. A bound record marks its declaration live before the
5
+ * sweep, and is in effect when it withholds the finding its code would have produced
6
+ * there, dormant when the configuration withholds that finding anyway, and stale otherwise.
7
+ */
8
+ import { ConfigError } from "./config.js";
9
+ import { positionKey } from "./position.js";
10
+ import { isRef } from "./ref.js";
11
+ /** The code a suppression that carries no reason is reported under. */
12
+ export const SUPPRESSION_WITHOUT_REASON = "DS1701";
13
+ /** The code an ignore entry or a baseline row that names no path is reported under. */
14
+ export const UNSCOPED_ENTRY = "DS1702";
15
+ /** The code a suppression that withholds nothing is reported under. */
16
+ export const STALE_SUPPRESSION = "DS1703";
17
+ /** A target that carries no suppression. */
18
+ export const NO_SUPPRESSIONS = { records: [], refusals: [] };
19
+ /**
20
+ * A directive, an entry or a document the grammar refuses before any finding exists: an
21
+ * instruction the analysis cannot carry out. It is a malformed input, so the command line
22
+ * answers it with the usage code.
23
+ */
24
+ export class SuppressionError extends ConfigError {
25
+ constructor(mechanism, at, text, want) {
26
+ super("malformed", at, `${mechanism} ${at}: ${text}: want ${want}`);
27
+ this.name = "SuppressionError";
28
+ }
29
+ }
30
+ /** Where a position is written, as an error names it. */
31
+ export function siteText(site) {
32
+ return site.line > 0 ? positionKey(site) : site.path;
33
+ }
34
+ /** An issue-kind code: `DS` and four digits. */
35
+ export const CODE_FORM = /^DS[0-9]{4}$/u;
36
+ /**
37
+ * A path inside the target: relative to the target root, the solidus as separator, no
38
+ * leading `./`, no trailing solidus, no `.` or `..` segment, never absolute.
39
+ */
40
+ export const PATH_EXPRESSION = String.raw `^(?:[^/\\.\r\n][^/\\\r\n]*|\.[^/\\.\r\n][^/\\\r\n]*|\.\.[^/\\\r\n]+)(?:/(?:[^/\\.\r\n][^/\\\r\n]*|\.[^/\\.\r\n][^/\\\r\n]*|\.\.[^/\\\r\n]+))*$`;
41
+ const PATH_FORM = new RegExp(PATH_EXPRESSION, "u");
42
+ /** Whether one value is a path in the form an entry's `path` takes. */
43
+ export function isEntryPath(value) {
44
+ return PATH_FORM.test(value);
45
+ }
46
+ const GO_IDENT = String.raw `(?:[A-Za-z_]|[^\x00-\x7F])(?:[A-Za-z0-9_]|[^\x00-\x7F])*`;
47
+ const GO_PATH = String.raw `[A-Za-z0-9._~-]+(?:/[A-Za-z0-9._~-]+)*`;
48
+ const GO_VERSION = String.raw `[A-Za-z0-9.+-]+`;
49
+ const GO_FILE = String.raw `[^/\\:#\r\n]+\.go`;
50
+ /**
51
+ * The reference forms of the Go analyzer's grammar. One target holds one ignore file,
52
+ * whose entries may name a symbol of either language, so an entry naming a Go symbol is
53
+ * well formed here and binds nothing.
54
+ */
55
+ export const GO_REF_EXPRESSIONS = [
56
+ `^go://${GO_PATH}#$`,
57
+ `^go://${GO_PATH}#${GO_IDENT}$`,
58
+ `^go://${GO_PATH}#${GO_IDENT}(?:\\.${GO_IDENT})+$`,
59
+ `^go://${GO_PATH}#${GO_IDENT}(?:\\.${GO_IDENT})?\\[${GO_IDENT}\\]$`,
60
+ `^go://${GO_PATH}#${GO_FILE}:file$`,
61
+ `^go://${GO_PATH}#${GO_PATH}(?:@${GO_VERSION})?:require$`,
62
+ `^go://${GO_PATH}#${GO_PATH}(?:@${GO_VERSION})?:replace$`,
63
+ ];
64
+ const GO_REF_FORMS = GO_REF_EXPRESSIONS.map((expression) => new RegExp(expression, "u"));
65
+ /** Whether one value is a stable symbol reference of either language's grammar. */
66
+ export function isSymbolRef(value) {
67
+ return isRef(value) || GO_REF_FORMS.some((form) => form.test(value));
68
+ }
69
+ /** Whether a reason is absent, empty or whitespace only, which the grammar reads as none. */
70
+ export function blankReason(reason) {
71
+ return reason === undefined || /^[ \t\r\n]*$/u.test(reason);
72
+ }
73
+ /**
74
+ * The subject kinds that are a row of a document rather than a declaration of the program.
75
+ * A record binds to a declaration, so nothing withholds a finding about one.
76
+ */
77
+ const ROW_SUBJECTS = new Set([
78
+ "file",
79
+ "dependency",
80
+ "module-directive",
81
+ "root",
82
+ "suppression",
83
+ "edge",
84
+ ]);
85
+ /** Whether a finding's subject is a row of a document, which no record can withhold. */
86
+ export function isRowSubject(kind) {
87
+ return ROW_SUBJECTS.has(kind);
88
+ }
89
+ /** One finding's identity: its code and the position of the thing it names. */
90
+ function claimKey(finding) {
91
+ return `${finding.code}\u0000${positionKey(finding.position)}`;
92
+ }
93
+ /**
94
+ * Whether one record names one finding. A record reaches a finding in two ways: it bound
95
+ * the declaration at the finding's position, or it names the finding's reference and the
96
+ * file the finding is reported in, each exactly. The second is how an entry copied from a
97
+ * finding about a part of a declaration reaches the part.
98
+ */
99
+ export function pairs(record, finding) {
100
+ if (record.code !== finding.code || ROW_SUBJECTS.has(finding.symbol.kind)) {
101
+ return false;
102
+ }
103
+ if (record.bound !== "" && record.bound === positionKey(finding.position)) {
104
+ return true;
105
+ }
106
+ return record.symbol === finding.symbol.ref && record.path === finding.position.path;
107
+ }
108
+ /**
109
+ * Decides every record against `would`, the findings the run would produce were no record
110
+ * there: swept with no mark, where a kind about liveness reports the marked declaration,
111
+ * and as swept, where a kind whose subject the sweep holds live reports it. The first
112
+ * record in reading order claims a finding, so a second naming it is stale. A record whose
113
+ * code the dials withhold from its declaration, or whose claimed finding they withhold, is
114
+ * dormant.
115
+ */
116
+ export function ledgerOf(records, would, dials) {
117
+ const taken = new Set();
118
+ const verdicts = [];
119
+ const claims = [];
120
+ for (const record of records) {
121
+ if (record.bound !== "" && dials.withholdsCode(record.code, record.bound)) {
122
+ verdicts.push("dormant");
123
+ claims.push(undefined);
124
+ continue;
125
+ }
126
+ const claimed = would.find((finding) => !taken.has(claimKey(finding)) && pairs(record, finding));
127
+ claims.push(claimed);
128
+ if (claimed === undefined) {
129
+ verdicts.push("stale");
130
+ continue;
131
+ }
132
+ taken.add(claimKey(claimed));
133
+ verdicts.push(dials.withholds(claimed) ? "dormant" : "in-effect");
134
+ }
135
+ return {
136
+ verdicts,
137
+ claims,
138
+ withheld: (finding) => taken.has(claimKey(finding)) && !ROW_SUBJECTS.has(finding.symbol.kind),
139
+ };
140
+ }
141
+ /** The suppression counts of one run's records and the ledger over them. */
142
+ export function suppressionCounts(records, ledger) {
143
+ const sites = new Set(records.map((record) => `${record.mechanism}\u0000${positionKey(record.site)}`));
144
+ return {
145
+ inEffect: ledger.verdicts.filter((verdict) => verdict === "in-effect").length,
146
+ reasonsRecorded: sites.size,
147
+ };
148
+ }
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "3.6.0",
3
+ "version": "3.8.0",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "3.6.0",
3
+ "version": "3.8.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Deterministic dead-code analysis for TypeScript, class and type members included",
package/src/analysis.ts CHANGED
@@ -14,6 +14,7 @@ import { decorator } from "./decorator.ts";
14
14
  import { dependenciesOf, projectNeeds } from "./dependencies.ts";
15
15
  import { deprecatedDeclarations } from "./deprecation.ts";
16
16
  import { diagnosticErrors, discoverProjects, DiscoveryError } from "./discover.ts";
17
+ import { readEdgeSides } from "./edges.ts";
17
18
  import { enumGroup } from "./enum-group.ts";
18
19
  import {
19
20
  computeExemptions,
@@ -26,7 +27,13 @@ import {
26
27
  import type { TSExemptionClass } from "./exempt-classes.ts";
27
28
  import { heldByInclusion, sourceTree } from "./file-facts.ts";
28
29
  import type { Finding } from "./finding.ts";
30
+ import { findingsPass, type PassResult } from "./findings-pass.ts";
29
31
  import type { EmitterInput } from "./findings/emitter.ts";
32
+ import {
33
+ NO_SELF_CHECK,
34
+ unmatchedRootFindings,
35
+ type SelfCheckFacts,
36
+ } from "./findings/self-check.ts";
30
37
  import { frameworkLifecycle } from "./framework-lifecycle.ts";
31
38
  import type { Host } from "./host.ts";
32
39
  import { implementations, mergeImplementations } from "./implementations.ts";
@@ -39,12 +46,15 @@ import { relativePath, resolvePath } from "./paths.ts";
39
46
  import { byPosition, type Position } from "./position.ts";
40
47
  import { reflectiveLookup } from "./reflective-lookup.ts";
41
48
  import { references } from "./references.ts";
42
- import { roots, unmatchedEverywhere, unmatchedRoots, type RootKind, type Roots } from "./roots.ts";
49
+ import { roots, unmatchedEverywhere, type RootKind, type Roots } from "./roots.ts";
43
50
  import type { Scope } from "./scope.ts";
44
51
  import { serializationContract } from "./serialization-contract.ts";
45
52
  import { diagnosticsOf, runSession, type Engine, type ProjectView } from "./session.ts";
46
53
  import { accessorsOf, storesOf } from "./stores.ts";
47
- import type { Exemption, SweepInput } from "./sweep.ts";
54
+ import type { Suppressions } from "./suppress.ts";
55
+ import { readBaseline, readIgnoreFile } from "./suppress-file.ts";
56
+ import { inlineDirectives, inlineSuppressions, type InlineDirective } from "./suppress-inline.ts";
57
+ import type { Exemption, Mode, SweepInput } from "./sweep.ts";
48
58
  import { readTemplates, templateField } from "./template-field.ts";
49
59
 
50
60
  /** The setting whose source decides where a finding about a configured root sits. */
@@ -211,7 +221,10 @@ export function runRoots(
211
221
  return {
212
222
  configurations,
213
223
  roots: ordered,
214
- findings: unmatchedRoots(unmatched, documentOf(host, provenance, ROOTS_SETTING, targetRoot)),
224
+ findings: unmatchedRootFindings(
225
+ unmatched,
226
+ documentOf(host, provenance, ROOTS_SETTING, targetRoot),
227
+ ),
215
228
  };
216
229
  }
217
230
 
@@ -245,26 +258,27 @@ interface SweptProject<Extra> {
245
258
  readonly extra: Extra;
246
259
  }
247
260
 
248
- /** The run's sweep, the exemption records it was swept with, and each project's extra answer. */
249
- interface SweptRun<Extra> {
250
- readonly swept: RunSweep;
261
+ /** What a run read of its projects before any sweep: the matrix, its exemptions, and each project's answers. */
262
+ interface ReadRun<Extra> {
263
+ readonly matrix: Matrix;
251
264
  readonly exempt: readonly Exemption[];
252
265
  readonly projects: readonly SweptProject<Extra>[];
253
266
  }
254
267
 
255
268
  /**
256
- * Sweeps the run, reading each project's references and exemptions, and `extra`'s
257
- * answer, while the project's view is open.
269
+ * Reads every project of the run, its references and exemptions, and `extra`'s answer,
270
+ * while the project's view is open, and merges the projects into one matrix in the run's
271
+ * order.
258
272
  */
259
- function sweepRun<Extra>(
273
+ function readRun<Extra>(
260
274
  engine: Engine,
261
275
  host: Host,
262
276
  scope: Scope,
263
277
  config: Config,
264
- input: Pick<SweepInput, "marked" | "mode">,
278
+ mode: Mode,
265
279
  detectors: Detectors,
266
280
  extra: <Brand>(project: ProjectView<Brand>, read: ProjectRead) => Extra,
267
- ): SweptRun<Extra> {
281
+ ): ReadRun<Extra> {
268
282
  const targetRoot = scope.target.path;
269
283
  const disabled = disabledClasses(config);
270
284
  const templates = disabled.has("template-field")
@@ -287,22 +301,32 @@ function sweepRun<Extra>(
287
301
  detectors,
288
302
  {
289
303
  disabled,
290
- mode: input.mode,
304
+ mode,
291
305
  testFiles: new Set(resolved.testFilePaths),
292
306
  },
293
307
  );
294
308
  return { configured, exempt, extra: extra(project, projectRead) };
295
309
  });
296
- const matrix = matrixOf(projects.map((one) => one.configured));
297
- const exempt = exemptionsOf(projects.flatMap((one) => one.exempt));
298
- const swept: SweepInput = { ...input, exempt };
299
310
  return {
300
- swept: { matrix, sweep: sweepMatrix(matrix, swept), retained: retainedIn(matrix, swept) },
301
- exempt,
311
+ matrix: matrixOf(projects.map((one) => one.configured)),
312
+ exempt: exemptionsOf(projects.flatMap((one) => one.exempt)),
302
313
  projects,
303
314
  };
304
315
  }
305
316
 
317
+ /** The sweep of a run read once, under one set of marks. */
318
+ function sweptOf<Extra>(
319
+ read: ReadRun<Extra>,
320
+ input: Pick<SweepInput, "marked" | "mode">,
321
+ ): RunSweep {
322
+ const swept: SweepInput = { ...input, exempt: read.exempt };
323
+ return {
324
+ matrix: read.matrix,
325
+ sweep: sweepMatrix(read.matrix, swept),
326
+ retained: retainedIn(read.matrix, swept),
327
+ };
328
+ }
329
+
306
330
  /**
307
331
  * Sweeps the run the scope and the configuration describe. Each project's references
308
332
  * and exemptions are read against its own inventory while its view is open; the
@@ -321,32 +345,59 @@ export function runSweep(
321
345
  input: Pick<SweepInput, "marked" | "mode">,
322
346
  detectors: Detectors = DETECTORS,
323
347
  ): RunSweep {
324
- return sweepRun(engine, host, scope, config, input, detectors, () => undefined).swept;
348
+ return sweptOf(
349
+ readRun(engine, host, scope, config, input.mode, detectors, () => undefined),
350
+ input,
351
+ );
325
352
  }
326
353
 
327
- /**
328
- * What every emitter reads of the run the scope and the configuration describe: the
329
- * run's sweep, and the facts beside it each kind family reads, every per-project one
330
- * read in the same session as the sweep. The run loads no consumer and reads no
331
- * declared cross-language edge, so its boundary names neither.
332
- */
333
- export function runEmitterInput(
354
+ /** What each project contributes to the facts every emitter reads beside the sweep. */
355
+ interface EmitterExtra {
356
+ readonly deprecated: readonly string[];
357
+ readonly accessors: ReturnType<typeof accessorsOf>;
358
+ readonly needs: ReturnType<typeof projectNeeds>;
359
+ readonly implementations: ReturnType<typeof implementations>;
360
+ readonly heldByInclusion: readonly string[];
361
+ readonly rooted: Roots;
362
+ readonly directives: readonly InlineDirective[];
363
+ }
364
+
365
+ /** Reads a run's projects with every per-project fact an emitter reads. */
366
+ function readEmitterRun(
334
367
  engine: Engine,
335
368
  host: Host,
336
369
  scope: Scope,
337
370
  config: Config,
338
- input: Pick<SweepInput, "marked" | "mode">,
339
- detectors: Detectors = DETECTORS,
340
- ): EmitterInput {
371
+ mode: Mode,
372
+ detectors: Detectors,
373
+ ): ReadRun<EmitterExtra> {
341
374
  const targetRoot = scope.target.path;
342
- const run = sweepRun(engine, host, scope, config, input, detectors, (project, read) => ({
375
+ return readRun(engine, host, scope, config, mode, detectors, (project, read) => ({
343
376
  deprecated: deprecatedDeclarations(project, read.held),
344
377
  accessors: accessorsOf(project, read.held),
345
378
  needs: projectNeeds(project, read.held, host),
346
379
  implementations: implementations(project, read.held),
347
380
  heldByInclusion: heldByInclusion(project, targetRoot),
381
+ rooted: read.rooted,
382
+ directives: inlineDirectives(project, read.held, targetRoot),
348
383
  }));
349
- const { swept, projects } = run;
384
+ }
385
+
386
+ /**
387
+ * The input every emitter reads over one sweep of a read run: the facts beside the sweep
388
+ * each kind family reads, every per-project one read in the same session as the sweep.
389
+ */
390
+ function emitterInputOver(
391
+ host: Host,
392
+ scope: Scope,
393
+ config: Config,
394
+ read: ReadRun<EmitterExtra>,
395
+ input: Pick<SweepInput, "marked" | "mode">,
396
+ selfCheck: SelfCheckFacts,
397
+ ): EmitterInput {
398
+ const targetRoot = scope.target.path;
399
+ const swept = sweptOf(read, input);
400
+ const { projects } = read;
350
401
  return {
351
402
  config,
352
403
  swept,
@@ -356,7 +407,7 @@ export function runEmitterInput(
356
407
  references: one.configured.references,
357
408
  accessors: one.extra.accessors,
358
409
  })),
359
- run.exempt,
410
+ read.exempt,
360
411
  input.mode,
361
412
  ),
362
413
  dependencies: dependenciesOf(
@@ -373,7 +424,100 @@ export function runEmitterInput(
373
424
  boundary: {
374
425
  consumers: { declared: scope.consumers.map((consumer) => consumer.id), loaded: [] },
375
426
  encapsulated: readManifest(host, targetRoot).declaresExports,
376
- edges: [],
427
+ edges: readEdgeSides(host, targetRoot),
377
428
  },
429
+ selfCheck,
430
+ };
431
+ }
432
+
433
+ /**
434
+ * What every emitter reads of the run the scope and the configuration describe, swept
435
+ * under the caller's marks: the run's sweep, the facts beside it each kind family reads,
436
+ * and the sides of the target's declared cross-language edges that name a symbol of this
437
+ * language. The run loads no consumer, and it reads no suppression document, so the
438
+ * self-check family has nothing to report.
439
+ */
440
+ export function runEmitterInput(
441
+ engine: Engine,
442
+ host: Host,
443
+ scope: Scope,
444
+ config: Config,
445
+ input: Pick<SweepInput, "marked" | "mode">,
446
+ detectors: Detectors = DETECTORS,
447
+ ): EmitterInput {
448
+ const read = readEmitterRun(engine, host, scope, config, input.mode, detectors);
449
+ return emitterInputOver(host, scope, config, read, input, NO_SELF_CHECK);
450
+ }
451
+
452
+ /** One run read for its findings: the input swept with and without its suppressions' marks. */
453
+ export interface AnalysisInputs {
454
+ /** The run swept under the marks of every bound suppression record. */
455
+ readonly marked: EmitterInput;
456
+ /** The same run swept with no mark, which is what a record would have withheld. */
457
+ readonly unmarked: EmitterInput;
458
+ /** The run's suppression records and refusals, in reading order. */
459
+ readonly suppressions: Suppressions;
460
+ }
461
+
462
+ /**
463
+ * Reads the run the scope and the configuration describe for its findings. The inline
464
+ * directives, the ignore file's entries and the baseline's rows are read and bound before
465
+ * any sweep, and the run is swept once under every bound record's mark and once under
466
+ * none, so a record is decided against the finding its code would have produced. The
467
+ * self-check family reports the refused suppressions and the roots that named nothing.
468
+ */
469
+ function runAnalysisInputs(
470
+ engine: Engine,
471
+ host: Host,
472
+ scope: Scope,
473
+ config: Config,
474
+ provenance: Provenance,
475
+ mode: Mode,
476
+ detectors: Detectors = DETECTORS,
477
+ ): AnalysisInputs {
478
+ const targetRoot = scope.target.path;
479
+ const read = readEmitterRun(engine, host, scope, config, mode, detectors);
480
+ const symbols = read.matrix.union.symbols;
481
+ const documents = [
482
+ inlineSuppressions(read.projects.map((one) => one.extra.directives)),
483
+ readIgnoreFile(host, targetRoot, symbols),
484
+ readBaseline(host, targetRoot, symbols),
485
+ ];
486
+ const suppressions: Suppressions = {
487
+ records: documents.flatMap((one) => one.records),
488
+ refusals: documents.flatMap((one) => one.refusals),
489
+ };
490
+ const facts: SelfCheckFacts = {
491
+ refusals: suppressions.refusals,
492
+ unmatchedRoots: unmatchedEverywhere(
493
+ config.rootPatterns,
494
+ read.projects.map((one) => one.extra.rooted),
495
+ ),
496
+ rootsDocument: documentOf(host, provenance, ROOTS_SETTING, targetRoot),
378
497
  };
498
+ const marked = [...new Set(suppressions.records.map((record) => record.bound))].filter(
499
+ (id) => id !== "",
500
+ );
501
+ return {
502
+ marked: emitterInputOver(host, scope, config, read, { marked, mode }, facts),
503
+ unmarked: emitterInputOver(host, scope, config, read, { marked: [], mode }, facts),
504
+ suppressions,
505
+ };
506
+ }
507
+
508
+ /**
509
+ * The findings of the run the scope and the configuration describe: the run read with its
510
+ * suppressions bound, then every family's findings with the suppressions applied and the
511
+ * declared edges evaluated.
512
+ */
513
+ export function runFindings(
514
+ engine: Engine,
515
+ host: Host,
516
+ scope: Scope,
517
+ config: Config,
518
+ provenance: Provenance,
519
+ mode: Mode,
520
+ detectors: Detectors = DETECTORS,
521
+ ): PassResult {
522
+ return findingsPass(runAnalysisInputs(engine, host, scope, config, provenance, mode, detectors));
379
523
  }