@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10
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 +1 -1
- package/dist/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/analysis.js +245 -309
- package/dist/src/augmentations.js +124 -18
- package/dist/src/automatic-types.js +170 -0
- package/dist/src/build-scripts.js +425 -0
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +9 -9
- package/dist/src/config.js +6 -81
- package/dist/src/configuration-files.js +78 -188
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/consumers.js +3 -30
- package/dist/src/contextual-uses.js +56 -9
- package/dist/src/contract/config.generated.js +558 -0
- package/dist/src/contract/contract.generated.js +1244 -0
- package/dist/src/contract/report.generated.js +862 -0
- package/dist/src/convention-rows.js +146 -1
- package/dist/src/conventions.js +136 -70
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +280 -62
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +9 -18
- package/dist/src/entry-points.js +106 -21
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +1 -63
- package/dist/src/exempt.js +21 -40
- package/dist/src/failure.js +36 -0
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +225 -63
- package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
- package/dist/src/findings/emitters.js +6 -6
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +53 -19
- package/dist/src/findings/non-code-artifacts.js +71 -39
- package/dist/src/findings/reads-and-writes.js +16 -13
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +57 -28
- package/dist/src/findings/visibility-narrowing.js +40 -18
- package/dist/src/findings-pass.js +8 -15
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +52 -0
- package/dist/src/fs/manifest.js +72 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/generated-file.js +18 -4
- package/dist/src/git-ignore.js +99 -0
- package/dist/src/graph.js +9 -4
- package/dist/src/html-entries.js +6 -33
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/in-operands.js +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/injection-container.js +1 -1
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +341 -69
- package/dist/src/inventory.js +54 -29
- package/dist/src/javascript-file.js +33 -0
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +3 -366
- package/dist/src/load/classify.js +270 -0
- package/dist/src/load/diagnostics.js +80 -0
- package/dist/src/load/discover.js +277 -0
- package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
- package/dist/src/load/expected-module.js +136 -0
- package/dist/src/load/load.js +27 -0
- package/dist/src/load/module-lookup.js +37 -0
- package/dist/src/load/plan.js +313 -0
- package/dist/src/load/specifiers.js +115 -0
- package/dist/src/load/synthesize.js +140 -0
- package/dist/src/load/test-globals.js +207 -0
- package/dist/src/load/test-joins.js +82 -0
- package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
- package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
- package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
- package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
- package/dist/src/{workspace.js → load/workspace.js} +62 -124
- package/dist/src/manifest.js +49 -54
- package/dist/src/matrix.js +94 -13
- package/dist/src/mdx-entries.js +174 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/overrides.js +41 -10
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +11 -78
- package/dist/src/product.js +13 -0
- package/dist/src/query.js +1 -0
- package/dist/src/ref.js +11 -0
- package/dist/src/references.js +52 -31
- package/dist/src/report-schema.js +51 -0
- package/dist/src/report.js +59 -41
- package/dist/src/reporters/annotations.js +15 -8
- package/dist/src/reporters/sarif.js +16 -10
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -727
- package/dist/src/roots.js +374 -116
- package/dist/src/run.js +66 -77
- package/dist/src/schema.js +320 -342
- package/dist/src/scope.js +38 -115
- package/dist/src/serialization-contract.js +649 -29
- package/dist/src/session.js +41 -29
- package/dist/src/source-files.js +40 -15
- package/dist/src/suppress-file.js +34 -29
- package/dist/src/suppress-inline.js +21 -4
- package/dist/src/suppress.js +40 -56
- package/dist/src/sweep.js +32 -17
- package/dist/src/symbol-keys.js +105 -6
- package/dist/src/template-field.js +9 -19
- package/dist/src/test-support.js +119 -18
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/named-files.js +54 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/text/yaml.js +393 -0
- package/dist/src/type-errors.js +27 -221
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/unseen-loads.js +100 -0
- package/dist/src/unwrap.js +23 -0
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +181 -125
- package/dist/src/verbs/describe.js +9 -16
- package/dist/src/verbs/explain.js +14 -9
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +6 -4
- package/dist/src/verbs/print-retained.js +4 -2
- package/dist/src/verbs/print-roots.js +4 -2
- package/dist/src/verbs/verb.js +1 -5
- package/dist/src/verbs/version.js +14 -3
- package/dist/src/workflow-steps.js +128 -33
- package/jsr.json +1 -1
- package/package.json +5 -4
- package/src/analysis.ts +333 -457
- package/src/augmentations.ts +152 -23
- package/src/automatic-types.ts +228 -0
- package/src/build-scripts.ts +543 -0
- package/src/component-layer.ts +4 -2
- package/src/components.ts +9 -10
- package/src/config.ts +31 -299
- package/src/configuration-files.ts +100 -191
- package/src/configured-declarations.ts +18 -23
- package/src/conformance-record.ts +2 -2
- package/src/consumers.ts +6 -57
- package/src/contextual-uses.ts +64 -12
- package/src/contract/config.generated.ts +818 -0
- package/src/contract/contract.generated.ts +1308 -0
- package/src/contract/report.generated.ts +1237 -0
- package/src/convention-rows.ts +186 -1
- package/src/conventions.ts +166 -90
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +356 -78
- package/src/edges.ts +41 -140
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +11 -20
- package/src/entry-points.ts +124 -28
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +1 -75
- package/src/exempt.ts +36 -50
- package/src/failure.ts +63 -0
- package/src/file-facts.ts +9 -24
- package/src/finding.ts +24 -9
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +297 -77
- package/src/findings/dependencies-and-module-machinery.ts +90 -31
- package/src/findings/emitter.ts +8 -9
- package/src/findings/emitters.ts +8 -8
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +64 -20
- package/src/findings/non-code-artifacts.ts +88 -50
- package/src/findings/reads-and-writes.ts +20 -16
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +65 -28
- package/src/findings/visibility-narrowing.ts +51 -19
- package/src/findings-pass.ts +10 -19
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +70 -0
- package/src/fs/manifest.ts +92 -0
- package/src/fs/tree.ts +110 -0
- package/src/generated-file.ts +23 -4
- package/src/git-ignore.ts +122 -0
- package/src/graph.ts +11 -3
- package/src/host.ts +17 -14
- package/src/html-entries.ts +6 -33
- package/src/import-bindings.ts +128 -0
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/indexed-access.ts +152 -30
- package/src/injection-container.ts +1 -1
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +449 -85
- package/src/inventory.ts +73 -27
- package/src/javascript-file.ts +37 -0
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +5 -413
- package/src/load/classify.ts +397 -0
- package/src/load/diagnostics.ts +144 -0
- package/src/load/discover.ts +455 -0
- package/src/{emit-map.ts → load/emit-map.ts} +1 -1
- package/src/load/expected-module.ts +188 -0
- package/src/load/load.ts +73 -0
- package/src/load/module-lookup.ts +50 -0
- package/src/load/plan.ts +504 -0
- package/src/load/specifiers.ts +168 -0
- package/src/load/synthesize.ts +211 -0
- package/src/load/test-globals.ts +251 -0
- package/src/load/test-joins.ts +98 -0
- package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
- package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
- package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
- package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
- package/src/{workspace.ts → load/workspace.ts} +90 -134
- package/src/manifest.ts +81 -75
- package/src/matrix.ts +154 -18
- package/src/mdx-entries.ts +218 -0
- package/src/module-declarations.ts +11 -13
- package/src/outside-tests.ts +1 -1
- package/src/overrides.ts +51 -12
- package/src/partial.ts +3 -2
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +12 -91
- package/src/product.ts +18 -0
- package/src/query.ts +11 -0
- package/src/ref.ts +13 -0
- package/src/references.ts +62 -32
- package/src/report-schema.ts +68 -0
- package/src/report.ts +105 -215
- package/src/reporters/annotations.ts +22 -11
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -4
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +29 -16
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +283 -1111
- package/src/roots.ts +490 -137
- package/src/run.ts +75 -91
- package/src/schema.ts +391 -387
- package/src/scope.ts +46 -138
- package/src/serialization-contract.ts +818 -26
- package/src/session.ts +77 -54
- package/src/source-files.ts +67 -17
- package/src/suppress-file.ts +36 -34
- package/src/suppress-inline.ts +20 -3
- package/src/suppress.ts +52 -63
- package/src/sweep.ts +53 -18
- package/src/symbol-keys.ts +131 -7
- package/src/template-field.ts +15 -25
- package/src/test-support.ts +155 -18
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/named-files.inputs.json +1522 -0
- package/src/text/named-files.ts +68 -0
- package/src/text/shell.ts +161 -0
- package/src/text/yaml.ts +423 -0
- package/src/type-errors.ts +35 -273
- package/src/type-parameter-reads.ts +227 -0
- package/src/unseen-loads.ts +134 -0
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +245 -161
- package/src/verbs/describe.ts +14 -15
- package/src/verbs/explain.ts +15 -13
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +6 -4
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +2 -8
- package/src/verbs/version.ts +15 -3
- package/src/workflow-steps.ts +149 -34
- package/dist/src/discover.js +0 -347
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/dist/src/setup-failure.js +0 -23
- package/dist/src/test-joins.js +0 -101
- package/dist/src/version.js +0 -7
- package/src/discover.ts +0 -510
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/setup-failure.ts +0 -39
- package/src/test-joins.ts +0 -120
- package/src/version.ts +0 -7
package/dist/src/exempt.js
CHANGED
|
@@ -6,26 +6,18 @@
|
|
|
6
6
|
* which records held something back. It names no class's detector: the composition
|
|
7
7
|
* hands it the table of the classes it has.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { EXEMPTION_CLASSES } from "./contract/contract.generated.js";
|
|
10
|
+
import { TS_EXEMPTION_CLASSES, typescriptVisibilityOf, } from "./exempt-classes.js";
|
|
11
11
|
import { byPosition, positionKey } from "./position.js";
|
|
12
12
|
import { Unanswerable } from "./query.js";
|
|
13
13
|
import { sweep } from "./sweep.js";
|
|
14
|
+
import { bytewise } from "./text/compare.js";
|
|
14
15
|
/**
|
|
15
|
-
* The classes `exemptions.disabled` switches off. A
|
|
16
|
-
*
|
|
17
|
-
* switches nothing off here, so one configuration serves every analyzer.
|
|
16
|
+
* The classes `exemptions.disabled` switches off. A class that runs on another language
|
|
17
|
+
* only switches nothing off here, so one configuration serves every analyzer.
|
|
18
18
|
*/
|
|
19
19
|
export function disabledClasses(config) {
|
|
20
|
-
|
|
21
|
-
for (const name of config.exemptionsDisabled) {
|
|
22
|
-
if (!isExemptionClass(name)) {
|
|
23
|
-
const known = EXEMPTION_CLASSES.map((row) => row.class).join(", ");
|
|
24
|
-
throw new ConfigError("malformed", "exemptions.disabled", `exemptions.disabled: ${JSON.stringify(name)} is not an exemption class: the classes are ${known}`);
|
|
25
|
-
}
|
|
26
|
-
disabled.add(name);
|
|
27
|
-
}
|
|
28
|
-
return disabled;
|
|
20
|
+
return new Set(config.exemptions.disabled);
|
|
29
21
|
}
|
|
30
22
|
/**
|
|
31
23
|
* Whether a class whose row states `allowed` may retain a member of one visibility. A
|
|
@@ -40,20 +32,8 @@ function retainable(allowed, visibility) {
|
|
|
40
32
|
}
|
|
41
33
|
return true;
|
|
42
34
|
}
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
* back whole. A class with no row here holds back every declaration but a file.
|
|
46
|
-
*/
|
|
47
|
-
const RETAINABLE = new Map([
|
|
48
|
-
["interface-satisfaction", new Set(["method", "class-member"])],
|
|
49
|
-
["enum-group", new Set(["enum-member"])],
|
|
50
|
-
["injection-container", new Set(["method", "class-member"])],
|
|
51
|
-
["framework-lifecycle", new Set(["method", "class-member"])],
|
|
52
|
-
[
|
|
53
|
-
"serialization-contract",
|
|
54
|
-
new Set(["method", "class-member", "interface-method", "type-member"]),
|
|
55
|
-
],
|
|
56
|
-
]);
|
|
35
|
+
/** The kinds of declaration each class can hold back, which a detector that stops holds back whole. */
|
|
36
|
+
const RETAINABLE = new Map(EXEMPTION_CLASSES.flatMap((row) => "ts" in row.retainsKinds ? [[row.class, new Set(row.retainsKinds.ts)]] : []));
|
|
57
37
|
/** The detail of a record a detector that could not finish holds. */
|
|
58
38
|
const UNANSWERED_DETAIL = "kept live by a question the checker did not answer";
|
|
59
39
|
/** Whether one record is held by a detector that could not finish. */
|
|
@@ -76,7 +56,7 @@ function detected(exemptionClass, detect, input) {
|
|
|
76
56
|
}
|
|
77
57
|
const kinds = RETAINABLE.get(exemptionClass);
|
|
78
58
|
return input.held.symbols
|
|
79
|
-
.filter((symbol) =>
|
|
59
|
+
.filter((symbol) => kinds?.has(symbol.kind) === true)
|
|
80
60
|
.map((symbol) => ({ id: symbol.id, detail: UNANSWERED_DETAIL, site: symbol.position }));
|
|
81
61
|
}
|
|
82
62
|
}
|
|
@@ -106,11 +86,19 @@ export function evidenceRecords(input, detectors, disabled) {
|
|
|
106
86
|
site: evidence.site,
|
|
107
87
|
...(evidence.whileLive === undefined ? {} : { whileLive: evidence.whileLive }),
|
|
108
88
|
...(evidence.implementing === undefined ? {} : { implementing: evidence.implementing }),
|
|
89
|
+
...(evidence.whileWritten === undefined ? {} : { whileWritten: evidence.whileWritten }),
|
|
109
90
|
});
|
|
110
91
|
}
|
|
111
92
|
}
|
|
112
93
|
return found;
|
|
113
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* The records that hold given the declarations a write the mode counts stores into: one
|
|
97
|
+
* that holds only while written holds where its declaration is among them.
|
|
98
|
+
*/
|
|
99
|
+
export function writtenRecords(records, written) {
|
|
100
|
+
return records.flatMap(({ whileWritten, ...record }) => whileWritten === undefined || written.has(record.id) ? [record] : []);
|
|
101
|
+
}
|
|
114
102
|
/** The records that hold under one mode, beside the evidence a production mode found in a test file. */
|
|
115
103
|
export function heldRecords(records, holding) {
|
|
116
104
|
const found = [];
|
|
@@ -125,20 +113,13 @@ export function heldRecords(records, holding) {
|
|
|
125
113
|
}
|
|
126
114
|
return { records: exemptionsOf(found), inTestFiles };
|
|
127
115
|
}
|
|
128
|
-
/** Two strings ordered bytewise. */
|
|
129
|
-
function compare(a, b) {
|
|
130
|
-
if (a === b) {
|
|
131
|
-
return 0;
|
|
132
|
-
}
|
|
133
|
-
return a < b ? -1 : 1;
|
|
134
|
-
}
|
|
135
116
|
/**
|
|
136
117
|
* Records as a run holds them: ordered by site, then class, then declaration, and one
|
|
137
118
|
* per declaration, class, detail and file it holds while live, the one at the first
|
|
138
119
|
* site. A fact found at many sites, or by several projects, is one record.
|
|
139
120
|
*/
|
|
140
121
|
export function exemptionsOf(found) {
|
|
141
|
-
const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) ||
|
|
122
|
+
const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) || bytewise(a.class, b.class) || bytewise(a.id, b.id));
|
|
142
123
|
const seen = new Set();
|
|
143
124
|
return ordered.filter((record) => {
|
|
144
125
|
const fact = [record.id, record.class, record.detail, record.whileLive ?? ""].join("\u0000");
|
|
@@ -189,8 +170,8 @@ export function retainedIn(matrix, input, writeOnly = new Set()) {
|
|
|
189
170
|
return matrix.union.symbols.flatMap((symbol) => held.get(symbol.id) ?? []);
|
|
190
171
|
}
|
|
191
172
|
/**
|
|
192
|
-
* One line per held-back declaration, in site order: its reference, then
|
|
193
|
-
* class, site and detail, all separated by tabs.
|
|
173
|
+
* One line per held-back declaration, in site order: its reference and position, then
|
|
174
|
+
* each record's class, site and detail, all separated by tabs.
|
|
194
175
|
*/
|
|
195
176
|
export function retainedLines(symbols, retained) {
|
|
196
177
|
const held = byDeclaration(retained);
|
|
@@ -204,6 +185,6 @@ export function retainedLines(symbols, retained) {
|
|
|
204
185
|
positionKey(record.site),
|
|
205
186
|
record.detail,
|
|
206
187
|
]);
|
|
207
|
-
return [[symbol.ref, ...fields].join("\t")];
|
|
188
|
+
return [[symbol.ref, positionKey(symbol.position), ...fields].join("\t")];
|
|
208
189
|
});
|
|
209
190
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { FAILURE_CLASSES, FAILURE_PREFIXES } from "./contract/contract.generated.js";
|
|
2
|
+
import { PRODUCT } from "./product.js";
|
|
3
|
+
/** The failure as a class line of the error stream, opened by its group's prefix. */
|
|
4
|
+
export function failureLine(failure) {
|
|
5
|
+
return `${FAILURE_PREFIXES[FAILURE_CLASSES[failure.failureClass]]}${failure.failureClass}: ${failure.detail}`;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The lines of the error stream that name the failures: the first one's class line, the run's
|
|
9
|
+
* only one, then each further failure as its class and detail with no group prefix.
|
|
10
|
+
*/
|
|
11
|
+
export function failureLines([first, ...further]) {
|
|
12
|
+
return [failureLine(first), ...further.map((one) => `${one.failureClass}: ${one.detail}`)];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A run the analysis cannot answer. It ends the run with the failure code and no finding
|
|
16
|
+
* list, printing the compiler's located errors and then the failures' lines.
|
|
17
|
+
*/
|
|
18
|
+
export class FailureExit extends Error {
|
|
19
|
+
failures;
|
|
20
|
+
/** The compiler's errors behind the failures, in the order it reported them. */
|
|
21
|
+
diagnostics;
|
|
22
|
+
constructor(failures, diagnostics = []) {
|
|
23
|
+
super(failureLines(failures).join("\n"));
|
|
24
|
+
this.name = "FailureExit";
|
|
25
|
+
this.failures = failures;
|
|
26
|
+
this.diagnostics = diagnostics;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** A failure exit of one failure. */
|
|
30
|
+
export function failureExit(failureClass, detail, diagnostics = []) {
|
|
31
|
+
return new FailureExit([{ failureClass, detail }], diagnostics);
|
|
32
|
+
}
|
|
33
|
+
/** A fault of this analyzer itself, `fault` saying what it cannot do. */
|
|
34
|
+
export function productFault(fault) {
|
|
35
|
+
return failureExit("product-fault", `${PRODUCT} ${fault}: reinstall ${PRODUCT}, and report the fault to its maintainers if a reinstalled ${PRODUCT} fails the same way`);
|
|
36
|
+
}
|
package/dist/src/file-facts.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* file below the target root, whether or not a project includes it, and the files a
|
|
4
4
|
* program holds by inclusion rather than by an import.
|
|
5
5
|
*/
|
|
6
|
-
import { IGNORED_DIRECTORIES } from "./discover.js";
|
|
7
6
|
import { packageScope } from "./inventory.js";
|
|
8
7
|
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
|
|
9
8
|
import { renderRef } from "./ref.js";
|
|
9
|
+
import { filesBelow } from "./fs/tree.js";
|
|
10
10
|
/** A file name the compiler reads as a source file. */
|
|
11
11
|
const SOURCE_FILE = /\.(?:ts|tsx|mts|cts)$/u;
|
|
12
12
|
/** A file name the compiler reads as a declaration file, which no build emits code from. */
|
|
@@ -30,30 +30,7 @@ function lastLine(text) {
|
|
|
30
30
|
* that cannot be read is skipped, as discovery skips it.
|
|
31
31
|
*/
|
|
32
32
|
export function sourcePaths(host, targetRoot) {
|
|
33
|
-
|
|
34
|
-
const pending = [""];
|
|
35
|
-
for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
|
|
36
|
-
let entries;
|
|
37
|
-
try {
|
|
38
|
-
entries = host.readDirectory(joinPath(targetRoot, dir));
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
for (const entry of entries) {
|
|
44
|
-
const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
|
|
45
|
-
if (entry.directory) {
|
|
46
|
-
if (!IGNORED_DIRECTORIES.has(entry.name)) {
|
|
47
|
-
pending.push(path);
|
|
48
|
-
}
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (SOURCE_FILE.test(entry.name) && !DECLARATION_FILE.test(entry.name)) {
|
|
52
|
-
found.push(path);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return found.sort((a, b) => (a < b ? -1 : 1));
|
|
33
|
+
return filesBelow(host, targetRoot, (name) => SOURCE_FILE.test(name) && !DECLARATION_FILE.test(name));
|
|
57
34
|
}
|
|
58
35
|
/**
|
|
59
36
|
* Every source file below the target root, in path order. A file that cannot be read is
|
|
@@ -3,14 +3,20 @@
|
|
|
3
3
|
* the code's row of the issue-kind vocabulary, and the dials that withhold a finding
|
|
4
4
|
* together with every finding that falls with a root they withhold.
|
|
5
5
|
*/
|
|
6
|
+
import { CODE, FAMILY_KEY_LENGTH, KIND_ROWS, } from "../contract/contract.generated.js";
|
|
6
7
|
import { componentId } from "../components.js";
|
|
8
|
+
import { deadComponents } from "../matrix.js";
|
|
7
9
|
import { OUTSIDE } from "../graph.js";
|
|
8
10
|
import { KINDS } from "../kinds.js";
|
|
9
11
|
import { byPosition, positionKey } from "../position.js";
|
|
10
12
|
import { declarationKey } from "../ref.js";
|
|
11
|
-
import { FAMILY_KEY_LENGTH } from "../resolve.js";
|
|
12
13
|
import { isPartSubject, isRowSubject } from "../suppress.js";
|
|
13
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The codes whose subject the run that counts test references holds live, so a test
|
|
16
|
+
* file's candidate reported under one carries none of a dead declaration's sweep facts.
|
|
17
|
+
*/
|
|
18
|
+
const LIVE_SUBJECT = new Set(KIND_ROWS.filter((row) => row.holdsLive).map((row) => row.code));
|
|
19
|
+
/** The fixability grammar/analysis/fixability.md gives a finding about a held declaration. */
|
|
14
20
|
const MANUAL = "manual";
|
|
15
21
|
/** The member kinds an object property can declare, which an `in` expression tests by name. */
|
|
16
22
|
const IN_TESTED = new Set([
|
|
@@ -35,8 +41,9 @@ function everyDeclaredLoaded(boundary) {
|
|
|
35
41
|
* naming its family, else the code's default.
|
|
36
42
|
*/
|
|
37
43
|
function severityOf(input, code, fallback) {
|
|
38
|
-
const {
|
|
39
|
-
|
|
44
|
+
const { severity } = input.config;
|
|
45
|
+
const key = [code, code.slice(0, FAMILY_KEY_LENGTH)].find((one) => Object.hasOwn(severity, one));
|
|
46
|
+
return key === undefined ? fallback : (severity[key] ?? fallback);
|
|
40
47
|
}
|
|
41
48
|
/**
|
|
42
49
|
* The declarations the roots name as a library's published API, each public member of a
|
|
@@ -72,15 +79,13 @@ function testedByIn(input) {
|
|
|
72
79
|
* published API, a public member of a published type included, is `certain` where the
|
|
73
80
|
* run loaded every consumer the scope declared and declared one, `probable` where one did
|
|
74
81
|
* not load, and `possible` where it declared none, whatever the configuration says of the
|
|
75
|
-
* consumer set.
|
|
76
|
-
*
|
|
82
|
+
* consumer set. A member an `in` expression tests by name, and one a function outside the
|
|
83
|
+
* analysis fills, is `possible`.
|
|
77
84
|
*/
|
|
78
85
|
function reachabilityClasses(input) {
|
|
79
86
|
const tested = testedByIn(input);
|
|
87
|
+
const filled = input.boundary.filledOutside ?? new Set();
|
|
80
88
|
const published = publishedIds(input.swept.matrix.union);
|
|
81
|
-
const usedByTests = new Set(input.swept.sweep.candidates
|
|
82
|
-
.filter((candidate) => candidate.heldByTests !== undefined)
|
|
83
|
-
.map((candidate) => candidate.id));
|
|
84
89
|
const { declared } = input.boundary.consumers;
|
|
85
90
|
const outside = declared.length === 0
|
|
86
91
|
? "possible"
|
|
@@ -88,12 +93,38 @@ function reachabilityClasses(input) {
|
|
|
88
93
|
? "certain"
|
|
89
94
|
: "probable";
|
|
90
95
|
return (id) => {
|
|
91
|
-
if (
|
|
96
|
+
if (tested.has(id) || filled.has(id)) {
|
|
92
97
|
return "possible";
|
|
93
98
|
}
|
|
94
99
|
return published.has(id) ? outside : "certain";
|
|
95
100
|
};
|
|
96
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Whether one test of dead code references a target symbol that is published API of a run
|
|
104
|
+
* that declares no consumer, which outside programs the run cannot see may use, so the
|
|
105
|
+
* target counts as live for the test and the test is no test of dead code.
|
|
106
|
+
*/
|
|
107
|
+
export function testsUnseenAPI(input) {
|
|
108
|
+
if (input.boundary.consumers.declared.length > 0) {
|
|
109
|
+
return () => false;
|
|
110
|
+
}
|
|
111
|
+
const union = input.swept.matrix.union;
|
|
112
|
+
const published = publishedIds(union);
|
|
113
|
+
return (id) => testTargets(union, id).some((target) => published.has(target));
|
|
114
|
+
}
|
|
115
|
+
/** The target symbols one test of dead code references, by id: production declarations the sweep judges. */
|
|
116
|
+
function testTargets(union, id) {
|
|
117
|
+
return (union.out[union.at(id)] ?? []).flatMap((edge) => {
|
|
118
|
+
const target = union.symbols[edge.to];
|
|
119
|
+
const testCode = union.test[edge.to] === true || union.support[edge.to] === true;
|
|
120
|
+
return target !== undefined &&
|
|
121
|
+
!testCode &&
|
|
122
|
+
union.ambient[edge.to] !== true &&
|
|
123
|
+
union.subject[edge.to] === true
|
|
124
|
+
? [target.id]
|
|
125
|
+
: [];
|
|
126
|
+
});
|
|
127
|
+
}
|
|
97
128
|
/**
|
|
98
129
|
* The confidence ceiling of a test of dead code: the lowest reachability class among the
|
|
99
130
|
* target symbols it references, because its deadness is theirs, whether or not they share
|
|
@@ -102,34 +133,71 @@ function reachabilityClasses(input) {
|
|
|
102
133
|
function testCeilings(input, classOf) {
|
|
103
134
|
const union = input.swept.matrix.union;
|
|
104
135
|
const tests = new Set(input.swept.sweep.candidates.filter((one) => one.testOfDeadCode).map((one) => one.id));
|
|
105
|
-
return (id) =>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
136
|
+
return (id) => tests.has(id)
|
|
137
|
+
? testTargets(union, id).reduce((lowest, target) => capped(lowest, classOf(target)), "certain")
|
|
138
|
+
: "certain";
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The subjects dead only because production code only tests use is: each declaration the
|
|
142
|
+
* sweep marks test-used, each `DS1602` file of such code, and each test of dead code every
|
|
143
|
+
* target of which is one. Nothing they hold references a dead declaration outside them, so
|
|
144
|
+
* the rest of a component they share is reported without them.
|
|
145
|
+
*/
|
|
146
|
+
function testUsedSubjects(input) {
|
|
147
|
+
const { matrix, sweep } = input.swept;
|
|
148
|
+
const union = matrix.union;
|
|
149
|
+
const used = new Set(sweep.candidates.filter((one) => one.testUsed).map((one) => one.id));
|
|
150
|
+
for (const test of sweep.candidates.filter((one) => one.testOfDeadCode)) {
|
|
151
|
+
const targets = testTargets(union, test.id);
|
|
152
|
+
if (targets.length > 0 && targets.every((target) => used.has(target))) {
|
|
153
|
+
used.add(test.id);
|
|
116
154
|
}
|
|
117
|
-
|
|
118
|
-
|
|
155
|
+
}
|
|
156
|
+
const files = new Set(union.symbols.flatMap((symbol, at) => matrix.testUsed[at] === true ? [symbol.position.path] : []));
|
|
157
|
+
return { declarations: used, files };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The ceiling the roots the run found put on one dead subject: a subject other declarations
|
|
161
|
+
* reference, found dead only because no root reaches them, is `probable` unless the
|
|
162
|
+
* configuration declares the roots complete and no configuration holding the subject loads
|
|
163
|
+
* a module the analysis cannot resolve, because a root the analysis cannot see would make
|
|
164
|
+
* it live; every other declaration has none below `certain`.
|
|
165
|
+
*/
|
|
166
|
+
function rootsCeiling(input, finding, candidate, configurations) {
|
|
167
|
+
const unreachable = candidate?.relation === "reachability" || finding.unreachableOnly === true;
|
|
168
|
+
const unseen = new Set((input.files.unseenLoads ?? []).map((load) => load.configuration));
|
|
169
|
+
const complete = input.config.ts.roots_complete && !configurations.some((one) => unseen.has(one));
|
|
170
|
+
return unreachable && !complete ? "probable" : "certain";
|
|
119
171
|
}
|
|
120
172
|
/**
|
|
121
173
|
* The component each dead declaration of the run falls in, and none for a live one, with
|
|
122
174
|
* its members listed in the canonical order where the run lists a component in full.
|
|
123
175
|
*/
|
|
124
|
-
function findingComponents(swept, cascade) {
|
|
176
|
+
function findingComponents(swept, components, cascade) {
|
|
125
177
|
const held = new Map();
|
|
126
|
-
for (const component of
|
|
178
|
+
for (const component of components) {
|
|
127
179
|
for (const member of component.members) {
|
|
128
180
|
held.set(member, component);
|
|
129
181
|
}
|
|
130
182
|
}
|
|
183
|
+
const listed = membersListed(swept);
|
|
184
|
+
return (id) => {
|
|
185
|
+
const component = held.get(id);
|
|
186
|
+
return component === undefined
|
|
187
|
+
? undefined
|
|
188
|
+
: {
|
|
189
|
+
id: component.id,
|
|
190
|
+
root: component.roots.includes(id),
|
|
191
|
+
symbolCount: component.members.length,
|
|
192
|
+
deletableLines: component.deletableLines,
|
|
193
|
+
...(cascade === "full" ? { members: listed(component.members) } : {}),
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/** The members of a component as the run lists them, positioned and in the canonical order. */
|
|
198
|
+
function membersListed(swept) {
|
|
131
199
|
const symbols = new Map(swept.matrix.union.symbols.map((symbol) => [symbol.id, symbol]));
|
|
132
|
-
|
|
200
|
+
return (members) => members
|
|
133
201
|
.flatMap((member) => {
|
|
134
202
|
const symbol = symbols.get(member);
|
|
135
203
|
return symbol === undefined
|
|
@@ -146,17 +214,58 @@ function findingComponents(swept, cascade) {
|
|
|
146
214
|
];
|
|
147
215
|
})
|
|
148
216
|
.sort((a, b) => byPosition(a.position, b.position));
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The run's dead components with each file a `DS1602` finding reports dead among them, so
|
|
220
|
+
* the file's declarations fall with it, and with the components each subject that is no
|
|
221
|
+
* declaration falls with joined into one.
|
|
222
|
+
*/
|
|
223
|
+
function runComponents(swept, findings) {
|
|
224
|
+
const union = swept.matrix.union;
|
|
225
|
+
const reported = new Set(findings.filter((one) => one.code === CODE.fileNeverImported).map((one) => one.position.path));
|
|
226
|
+
const files = new Map(union.symbols.flatMap((symbol, at) => symbol.kind === "file" && reported.has(symbol.position.path) ? [[at, symbol]] : []));
|
|
227
|
+
const computed = files.size === 0
|
|
228
|
+
? swept.sweep.components
|
|
229
|
+
: deadComponents(swept.matrix, swept.sweep, new Set(files.keys()));
|
|
230
|
+
const place = new Map();
|
|
231
|
+
computed.forEach((component, at) => {
|
|
232
|
+
for (const member of component.members) {
|
|
233
|
+
place.set(member, at);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
const leader = computed.map((_component, at) => at);
|
|
237
|
+
const find = (at) => {
|
|
238
|
+
let top = at;
|
|
239
|
+
while ((leader[top] ?? top) !== top) {
|
|
240
|
+
top = leader[top] ?? top;
|
|
241
|
+
}
|
|
242
|
+
return top;
|
|
243
|
+
};
|
|
244
|
+
for (const { fallsWith } of findings) {
|
|
245
|
+
const joined = (fallsWith ?? []).flatMap((id) => place.get(id) ?? []).map(find);
|
|
246
|
+
const first = Math.min(...joined);
|
|
247
|
+
for (const one of joined) {
|
|
248
|
+
leader[one] = first;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const merged = new Map();
|
|
252
|
+
computed.forEach((component, at) => {
|
|
253
|
+
const top = find(at);
|
|
254
|
+
const into = merged.get(top);
|
|
255
|
+
merged.set(top, into === undefined
|
|
256
|
+
? component
|
|
153
257
|
: {
|
|
154
|
-
id:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
deletableLines: component.deletableLines,
|
|
158
|
-
|
|
159
|
-
};
|
|
258
|
+
id: into.id,
|
|
259
|
+
members: [...into.members, ...component.members],
|
|
260
|
+
roots: [...into.roots, ...component.roots],
|
|
261
|
+
deletableLines: into.deletableLines + component.deletableLines,
|
|
262
|
+
held: into.held && component.held,
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
return {
|
|
266
|
+
components: [...merged.values()],
|
|
267
|
+
computed: computed.length,
|
|
268
|
+
files: new Map([...files.values()].map((symbol) => [symbol.position.path, symbol.id])),
|
|
160
269
|
};
|
|
161
270
|
}
|
|
162
271
|
/**
|
|
@@ -174,16 +283,20 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
174
283
|
const candidates = new Map(swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
175
284
|
const classOf = reachabilityClasses(input);
|
|
176
285
|
const ceilingOf = testCeilings(input, classOf);
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
const
|
|
286
|
+
const testUsed = testUsedSubjects(input);
|
|
287
|
+
const { components, computed, files } = runComponents(swept, findings);
|
|
288
|
+
const componentOf = findingComponents(swept, components, input.config.reporters.cascade);
|
|
289
|
+
const held = new Set(components.filter((one) => one.held).flatMap((one) => one.members));
|
|
290
|
+
// A join keeps its first component's identifier, so a minted one counts past every computed one.
|
|
291
|
+
let minted = computed;
|
|
292
|
+
const dead = new Set(components.map((component) => component.id));
|
|
181
293
|
const fallingWith = new Map();
|
|
182
|
-
for (const { heldByTests } of swept.sweep.candidates) {
|
|
294
|
+
for (const { id, heldByTests } of swept.sweep.candidates) {
|
|
183
295
|
if (heldByTests !== undefined) {
|
|
184
|
-
fallingWith.set(heldByTests, (fallingWith.get(heldByTests) ??
|
|
296
|
+
fallingWith.set(heldByTests, [...(fallingWith.get(heldByTests) ?? []), id]);
|
|
185
297
|
}
|
|
186
298
|
}
|
|
299
|
+
const listed = membersListed(swept);
|
|
187
300
|
const done = findings.map((finding) => {
|
|
188
301
|
const row = kinds.get(finding.code);
|
|
189
302
|
if (row === undefined) {
|
|
@@ -193,18 +306,30 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
193
306
|
// a row is still never the declaration. Two declarations of one file can share a
|
|
194
307
|
// reference, so the one the finding is positioned at is the subject where there is one.
|
|
195
308
|
const declared = union.at(positionKey(finding.position));
|
|
309
|
+
const ref = finding.symbol.holder ?? finding.symbol.ref;
|
|
196
310
|
const at = isRowSubject(finding.symbol.kind)
|
|
197
311
|
? OUTSIDE
|
|
198
|
-
: union.symbols[declared]?.ref ===
|
|
312
|
+
: union.symbols[declared]?.ref === ref
|
|
199
313
|
? declared
|
|
200
|
-
: (positions.get(declarationKey(
|
|
314
|
+
: (positions.get(declarationKey(ref, finding.position.path)) ?? OUTSIDE);
|
|
201
315
|
// A part names the declaration that holds it, and is decided inside it rather than by
|
|
202
316
|
// the declaration's liveness, so it carries none of the declaration's sweep facts.
|
|
203
317
|
const part = isPartSubject(finding.symbol.kind);
|
|
204
318
|
const symbol = union.symbols[at];
|
|
205
319
|
const judged = !part && union.subject[at] === true ? symbol : undefined;
|
|
206
|
-
const candidate = judged === undefined
|
|
207
|
-
|
|
320
|
+
const candidate = judged === undefined || LIVE_SUBJECT.has(finding.code)
|
|
321
|
+
? undefined
|
|
322
|
+
: candidates.get(judged.id);
|
|
323
|
+
const fallsWith = finding.fallsWith?.[0];
|
|
324
|
+
const joined = fallsWith === undefined ? undefined : componentOf(fallsWith);
|
|
325
|
+
const file = finding.code === CODE.fileNeverImported ? files.get(finding.position.path) : undefined;
|
|
326
|
+
const found = joined !== undefined
|
|
327
|
+
? { ...joined, root: false }
|
|
328
|
+
: file !== undefined
|
|
329
|
+
? componentOf(file)
|
|
330
|
+
: symbol === undefined || part
|
|
331
|
+
? undefined
|
|
332
|
+
: componentOf(symbol.id);
|
|
208
333
|
// A test file's declaration is a component member only as a test of dead code, and
|
|
209
334
|
// test code keeps the test-support code it references out of every component.
|
|
210
335
|
if (candidate !== undefined &&
|
|
@@ -216,17 +341,40 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
216
341
|
let component = found;
|
|
217
342
|
if (component === undefined) {
|
|
218
343
|
minted += 1;
|
|
344
|
+
const falling = symbol?.kind === "interface" ? fallingWith.get(symbol.id) : undefined;
|
|
219
345
|
component = {
|
|
220
346
|
id: componentId(minted),
|
|
221
347
|
root: true,
|
|
222
|
-
symbolCount:
|
|
348
|
+
symbolCount: falling?.length ?? 1,
|
|
223
349
|
deletableLines: 0,
|
|
350
|
+
...(input.config.reporters.cascade !== "full"
|
|
351
|
+
? {}
|
|
352
|
+
: falling === undefined
|
|
353
|
+
? {
|
|
354
|
+
members: [
|
|
355
|
+
{
|
|
356
|
+
ref: finding.symbol.ref,
|
|
357
|
+
name: finding.symbol.name,
|
|
358
|
+
position: finding.position,
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
}
|
|
362
|
+
: { members: listed(falling) }),
|
|
224
363
|
};
|
|
225
364
|
}
|
|
226
365
|
const counts = candidate === undefined
|
|
227
366
|
? union.made[at]
|
|
228
367
|
: { production: candidate.productionRefs, test: candidate.testRefs };
|
|
229
|
-
const
|
|
368
|
+
const ownClass = judged === undefined ? (finding.reachabilityClass ?? "certain") : classOf(judged.id);
|
|
369
|
+
const used = judged !== undefined
|
|
370
|
+
? candidate !== undefined && testUsed.declarations.has(judged.id)
|
|
371
|
+
: finding.code === CODE.fileNeverImported && testUsed.files.has(finding.position.path);
|
|
372
|
+
const reachabilityClass = used && candidate?.testOfDeadCode !== true ? "possible" : ownClass;
|
|
373
|
+
const configurations = candidate?.configurations ??
|
|
374
|
+
(judged === undefined && !part
|
|
375
|
+
? swept.matrix.configurations
|
|
376
|
+
: (swept.matrix.heldIn[at] ?? swept.matrix.configurations));
|
|
377
|
+
const untested = capped(capped(capped(ownClass, row.maxClass), rootsCeiling(input, finding, candidate, configurations)), judged === undefined ? "certain" : ceilingOf(judged.id));
|
|
230
378
|
const generated = input.files.generated?.has(finding.position.path) === true;
|
|
231
379
|
const fixability = generated
|
|
232
380
|
? "none"
|
|
@@ -236,9 +384,13 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
236
384
|
row.findingFixabilities?.includes(MANUAL) === true
|
|
237
385
|
? MANUAL
|
|
238
386
|
: row.fixability;
|
|
239
|
-
const lastUses = fixability
|
|
240
|
-
?
|
|
241
|
-
: undefined
|
|
387
|
+
const lastUses = fixability !== "deletable"
|
|
388
|
+
? undefined
|
|
389
|
+
: candidate !== undefined
|
|
390
|
+
? input.dependencies.lastUses.get(candidate.id)
|
|
391
|
+
: finding.code === CODE.fileNeverImported
|
|
392
|
+
? input.dependencies.fileLastUses.get(finding.position.path)
|
|
393
|
+
: undefined;
|
|
242
394
|
return {
|
|
243
395
|
code: finding.code,
|
|
244
396
|
kind: row.name,
|
|
@@ -246,16 +398,14 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
246
398
|
position: finding.position,
|
|
247
399
|
symbol: finding.symbol,
|
|
248
400
|
reachabilityClass,
|
|
249
|
-
confidence:
|
|
401
|
+
confidence: used ? "possible" : untested,
|
|
402
|
+
...(used ? { componentConfidence: untested } : {}),
|
|
250
403
|
...(candidate === undefined ? {} : { livenessRelation: candidate.relation }),
|
|
251
404
|
testOnly: judged !== undefined && counts?.production === 0 && counts.test > 0,
|
|
252
405
|
generated,
|
|
253
406
|
component,
|
|
254
407
|
retainedBy: [],
|
|
255
|
-
configurations
|
|
256
|
-
(judged === undefined && !part
|
|
257
|
-
? swept.matrix.configurations
|
|
258
|
-
: (swept.matrix.heldIn[at] ?? swept.matrix.configurations)),
|
|
408
|
+
configurations,
|
|
259
409
|
consumersLoaded: input.boundary.consumers.loaded,
|
|
260
410
|
fixability,
|
|
261
411
|
severity: severityOf(input, finding.code, row.defaultSeverity),
|
|
@@ -270,7 +420,7 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
270
420
|
/**
|
|
271
421
|
* The findings with every finding of a dead component capped by the lowest confidence
|
|
272
422
|
* among the component's root members, so the minimum confidence reports or withholds a
|
|
273
|
-
* component whole.
|
|
423
|
+
* component whole but for the findings test-used code lowers on their own.
|
|
274
424
|
*/
|
|
275
425
|
function componentCapped(findings, dead) {
|
|
276
426
|
const lowest = new Map();
|
|
@@ -278,16 +428,27 @@ function componentCapped(findings, dead) {
|
|
|
278
428
|
const { id, root } = finding.component;
|
|
279
429
|
if (root && dead.has(id)) {
|
|
280
430
|
const held = lowest.get(id);
|
|
281
|
-
|
|
431
|
+
const own = componentConfidenceOf(finding);
|
|
432
|
+
lowest.set(id, held === undefined ? own : capped(held, own));
|
|
282
433
|
}
|
|
283
434
|
}
|
|
284
435
|
return findings.map((finding) => {
|
|
285
436
|
const cap = lowest.get(finding.component.id);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
437
|
+
if (cap === undefined) {
|
|
438
|
+
return finding;
|
|
439
|
+
}
|
|
440
|
+
const own = finding.componentConfidence;
|
|
441
|
+
return {
|
|
442
|
+
...finding,
|
|
443
|
+
confidence: capped(finding.confidence, cap),
|
|
444
|
+
...(own === undefined ? {} : { componentConfidence: capped(own, cap) }),
|
|
445
|
+
};
|
|
289
446
|
});
|
|
290
447
|
}
|
|
448
|
+
/** The confidence a finding gives its component's cap and the dials. */
|
|
449
|
+
function componentConfidenceOf(finding) {
|
|
450
|
+
return finding.componentConfidence ?? finding.confidence;
|
|
451
|
+
}
|
|
291
452
|
/** Whether the dials withhold a finding by its own severity or confidence. */
|
|
292
453
|
function dialed(finding, minConfidence) {
|
|
293
454
|
return finding.severity === "allow" || RANK[finding.confidence] < RANK[minConfidence];
|
|
@@ -300,9 +461,10 @@ function dialed(finding, minConfidence) {
|
|
|
300
461
|
* root withholds itself alone.
|
|
301
462
|
*/
|
|
302
463
|
export function dialsOf(input, findings) {
|
|
303
|
-
const minConfidence = input.config.analysis.
|
|
464
|
+
const minConfidence = input.config.analysis.min_confidence;
|
|
304
465
|
const components = new Set(findings
|
|
305
|
-
.filter((finding) => finding.component.root &&
|
|
466
|
+
.filter((finding) => finding.component.root &&
|
|
467
|
+
dialed({ ...finding, confidence: componentConfidenceOf(finding) }, minConfidence))
|
|
306
468
|
.map((finding) => finding.component.id));
|
|
307
469
|
return {
|
|
308
470
|
allows: (code) => {
|