@cplieger/deadset-ts 8.0.0-dev.16 → 8.0.0-dev.18
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/dist/src/alias-chain.js +0 -5
- package/dist/src/amd.js +81 -0
- package/dist/src/analysis.js +82 -91
- package/dist/src/augmentations.js +24 -25
- package/dist/src/bindings.js +382 -0
- package/dist/src/calls.js +279 -43
- package/dist/src/commands/tools.js +5 -9
- package/dist/src/component-files.js +3 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +6 -9
- package/dist/src/contextual-uses.js +1 -17
- package/dist/src/contract/config.generated.js +4 -1
- package/dist/src/contract/contract.generated.js +2 -2
- package/dist/src/contract/report.generated.js +6 -2
- package/dist/src/dependencies.js +25 -29
- package/dist/src/destructuring.js +1 -19
- package/dist/src/element-access.js +179 -164
- package/dist/src/entries/build-scripts.js +2 -2
- package/dist/src/entries/configuration-files.js +3 -7
- package/dist/src/entries/conventions.js +2 -2
- package/dist/src/entries/html-entries.js +7 -10
- package/dist/src/entries/mdx-entries.js +2 -2
- package/dist/src/entries/static-module.js +2 -1
- package/dist/src/exempt.js +0 -26
- package/dist/src/exposure.js +400 -0
- package/dist/src/facts.js +4 -4
- package/dist/src/findings/completion.js +44 -21
- package/dist/src/findings/emitters.js +22 -33
- package/dist/src/{implementations.js → findings/implementations.js} +3 -3
- package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
- package/dist/src/findings/intra-function.js +3 -3
- package/dist/src/findings/reads-and-writes.js +71 -15
- package/dist/src/findings/unused-declarations.js +2 -22
- package/dist/src/findings/visibility-narrowing.js +14 -20
- package/dist/src/framework-lifecycle.js +41 -14
- package/dist/src/import-bindings.js +73 -28
- package/dist/src/index/sites.js +557 -0
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/interface-satisfaction.js +1 -1
- package/dist/src/inventory.js +18 -0
- package/dist/src/keyof-comparisons.js +124 -103
- package/dist/src/{components.js → liveness/components.js} +1 -1
- package/dist/src/{graph.js → liveness/graph.js} +45 -20
- package/dist/src/liveness/matrix.js +99 -0
- package/dist/src/liveness/sweep.js +558 -0
- package/dist/src/load/expected-module.js +2 -2
- package/dist/src/load/specifiers.js +2 -4
- package/dist/src/load/synthesize.js +5 -0
- package/dist/src/load/workspace-resolution.js +2 -2
- package/dist/src/module-declarations.js +70 -85
- package/dist/src/overrides.js +134 -112
- package/dist/src/paths.js +46 -7
- package/dist/src/platform-calls.js +2 -2
- package/dist/src/platform-symbols.js +13 -19
- package/dist/src/query.js +46 -0
- package/dist/src/references/read-rule.js +1 -0
- package/dist/src/references/read-rules.js +48 -0
- package/dist/src/references/sink.js +237 -0
- package/dist/src/references/sources.js +450 -0
- package/dist/src/references.js +110 -1285
- package/dist/src/roots/configured-declarations.js +3 -0
- package/dist/src/roots/published-reach.js +4 -4
- package/dist/src/roots/root-set.js +1 -1
- package/dist/src/roots/unseen-loads.js +38 -10
- package/dist/src/serialization-contract.js +8 -3
- package/dist/src/stores.js +4 -2
- package/dist/src/symbol-keys.js +89 -83
- package/dist/src/tests/file-class.js +76 -0
- package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
- package/dist/src/{test-support.js → tests/support.js} +2 -11
- package/dist/src/type-parameter-reads.js +178 -139
- package/dist/src/unimplemented-callees.js +262 -242
- package/dist/src/verbs/analyze.js +3 -3
- package/dist/src/verbs/explain.js +75 -132
- package/dist/src/verbs/print-retained.js +3 -3
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +0 -7
- package/src/amd.ts +130 -0
- package/src/analysis.ts +125 -128
- package/src/augmentations.ts +42 -28
- package/src/bindings.ts +460 -0
- package/src/calls.ts +338 -46
- package/src/commands/tools.ts +5 -11
- package/src/component-files.ts +4 -2
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +8 -11
- package/src/contextual-uses.ts +2 -22
- package/src/contract/config.generated.ts +4 -1
- package/src/contract/contract.generated.ts +2 -2
- package/src/contract/report.generated.ts +6 -2
- package/src/dependencies.ts +27 -28
- package/src/destructuring.ts +1 -31
- package/src/element-access.ts +188 -182
- package/src/entries/build-scripts.ts +2 -2
- package/src/entries/configuration-files.ts +11 -8
- package/src/entries/conventions.ts +2 -1
- package/src/entries/html-entries.ts +15 -14
- package/src/entries/mdx-entries.ts +2 -2
- package/src/entries/static-module.ts +2 -1
- package/src/exempt.ts +31 -46
- package/src/exposure.ts +492 -0
- package/src/facts.ts +5 -3
- package/src/finding.ts +1 -1
- package/src/findings/boundary.ts +9 -0
- package/src/findings/completion.ts +48 -23
- package/src/findings/emitter.ts +1 -1
- package/src/findings/emitters.ts +23 -41
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
- package/src/findings/interfaces.ts +3 -3
- package/src/findings/intra-function.ts +3 -3
- package/src/findings/reads-and-writes.ts +99 -26
- package/src/findings/unused-declarations.ts +4 -28
- package/src/findings/visibility-narrowing.ts +25 -26
- package/src/framework-lifecycle.ts +42 -14
- package/src/import-bindings.ts +98 -29
- package/src/index/sites.ts +781 -0
- package/src/indexed-access.ts +570 -527
- package/src/interface-satisfaction.ts +2 -2
- package/src/inventory.ts +19 -0
- package/src/keyof-comparisons.ts +131 -116
- package/src/{components.ts → liveness/components.ts} +3 -3
- package/src/{graph.ts → liveness/graph.ts} +93 -27
- package/src/liveness/matrix.ts +162 -0
- package/src/liveness/sweep.ts +855 -0
- package/src/load/expected-module.ts +8 -2
- package/src/load/specifiers.ts +8 -5
- package/src/load/synthesize.ts +6 -0
- package/src/load/workspace-resolution.ts +9 -2
- package/src/module-declarations.ts +74 -98
- package/src/overrides.ts +162 -134
- package/src/paths.ts +54 -8
- package/src/platform-calls.ts +3 -3
- package/src/platform-symbols.ts +11 -27
- package/src/query.ts +81 -0
- package/src/references/read-rule.ts +60 -0
- package/src/references/read-rules.ts +55 -0
- package/src/references/sink.ts +314 -0
- package/src/references/sources.ts +632 -0
- package/src/references.ts +175 -1733
- package/src/report.ts +1 -1
- package/src/roots/configured-declarations.ts +3 -0
- package/src/roots/published-reach.ts +9 -4
- package/src/roots/root-set.ts +1 -1
- package/src/roots/unseen-loads.ts +49 -11
- package/src/serialization-contract.ts +8 -3
- package/src/stores.ts +15 -3
- package/src/symbol-keys.ts +102 -102
- package/src/tests/file-class.ts +135 -0
- package/src/{outside-tests.ts → tests/outside.ts} +16 -10
- package/src/{test-support.ts → tests/support.ts} +9 -20
- package/src/type-parameter-reads.ts +187 -158
- package/src/unimplemented-callees.ts +297 -280
- package/src/verbs/analyze.ts +3 -2
- package/src/verbs/explain.ts +83 -158
- package/src/verbs/print-retained.ts +3 -3
- package/dist/src/matrix.js +0 -273
- package/dist/src/sweep.js +0 -222
- package/src/matrix.ts +0 -463
- package/src/sweep.ts +0 -361
|
@@ -13,17 +13,17 @@ import {
|
|
|
13
13
|
type KindRow,
|
|
14
14
|
} from "../contract/contract.generated.ts";
|
|
15
15
|
import type { RunSweep } from "../analysis.ts";
|
|
16
|
-
import { componentId, type Component } from "../components.ts";
|
|
17
|
-
import { deadComponents } from "../
|
|
16
|
+
import { componentId, type Component } from "../liveness/components.ts";
|
|
17
|
+
import { deadComponents } from "../liveness/sweep.ts";
|
|
18
18
|
import type { Cascade } from "../config.ts";
|
|
19
19
|
import type { CompletedFinding, Finding, FindingComponent, PositionedSymbol } from "../finding.ts";
|
|
20
|
-
import { OUTSIDE, type Graph } from "../graph.ts";
|
|
20
|
+
import { OUTSIDE, type Graph } from "../liveness/graph.ts";
|
|
21
21
|
import type { SymbolKind } from "../inventory.ts";
|
|
22
22
|
import { KINDS } from "../kinds.ts";
|
|
23
23
|
import { byPosition, positionKey } from "../position.ts";
|
|
24
24
|
import { declarationKey } from "../ref.ts";
|
|
25
25
|
import { isPartSubject, isRowSubject, type Dials } from "../suppress.ts";
|
|
26
|
-
import type { Candidate } from "../sweep.ts";
|
|
26
|
+
import type { Candidate } from "../liveness/sweep.ts";
|
|
27
27
|
import type { Boundary } from "./boundary.ts";
|
|
28
28
|
import type { EmitterInput } from "./emitter.ts";
|
|
29
29
|
|
|
@@ -110,12 +110,13 @@ function testedByIn(input: EmitterInput): ReadonlySet<string> {
|
|
|
110
110
|
* published API, a public member of a published type included, is `certain` where the
|
|
111
111
|
* run loaded every consumer the scope declared and declared one, `probable` where one did
|
|
112
112
|
* not load, and `possible` where it declared none, whatever the configuration says of the
|
|
113
|
-
* consumer set. A member an `in` expression tests by name,
|
|
114
|
-
* analysis fills, is `possible`.
|
|
113
|
+
* consumer set. A member an `in` expression tests by name, one a function outside the
|
|
114
|
+
* analysis fills, and one {@link possiblyLoaded} names, is `possible`.
|
|
115
115
|
*/
|
|
116
|
-
function reachabilityClasses(input: EmitterInput): (id: string) => Confidence {
|
|
116
|
+
export function reachabilityClasses(input: EmitterInput): (id: string) => Confidence {
|
|
117
117
|
const tested = testedByIn(input);
|
|
118
118
|
const filled = input.boundary.filledOutside ?? new Set<string>();
|
|
119
|
+
const possible = possiblyLoaded(input);
|
|
119
120
|
const published = publishedIds(input.swept.matrix.union);
|
|
120
121
|
const { declared } = input.boundary.consumers;
|
|
121
122
|
const outside: Confidence =
|
|
@@ -125,27 +126,13 @@ function reachabilityClasses(input: EmitterInput): (id: string) => Confidence {
|
|
|
125
126
|
? "certain"
|
|
126
127
|
: "probable";
|
|
127
128
|
return (id) => {
|
|
128
|
-
if (tested.has(id) || filled.has(id)) {
|
|
129
|
+
if (tested.has(id) || filled.has(id) || possible.has(id)) {
|
|
129
130
|
return "possible";
|
|
130
131
|
}
|
|
131
132
|
return published.has(id) ? outside : "certain";
|
|
132
133
|
};
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
/**
|
|
136
|
-
* Whether one test of dead code references a target symbol that is published API of a run
|
|
137
|
-
* that declares no consumer, which outside programs the run cannot see may use, so the
|
|
138
|
-
* target counts as live for the test and the test is no test of dead code.
|
|
139
|
-
*/
|
|
140
|
-
export function testsUnseenAPI(input: EmitterInput): (id: string) => boolean {
|
|
141
|
-
if (input.boundary.consumers.declared.length > 0) {
|
|
142
|
-
return () => false;
|
|
143
|
-
}
|
|
144
|
-
const union = input.swept.matrix.union;
|
|
145
|
-
const published = publishedIds(union);
|
|
146
|
-
return (id) => testTargets(union, id).some((target) => published.has(target));
|
|
147
|
-
}
|
|
148
|
-
|
|
149
136
|
/** The target symbols one test of dead code references, by id: production declarations the sweep judges. */
|
|
150
137
|
function testTargets(union: Graph, id: string): readonly string[] {
|
|
151
138
|
return (union.out[union.at(id)] ?? []).flatMap((edge) => {
|
|
@@ -182,6 +169,36 @@ function testCeilings(
|
|
|
182
169
|
: "certain";
|
|
183
170
|
}
|
|
184
171
|
|
|
172
|
+
/**
|
|
173
|
+
* What only a bundler's retry of an import Node leaves unresolved names (program.38), and each
|
|
174
|
+
* dead declaration that reaches through dead declarations, as confidence.20 reads what only
|
|
175
|
+
* tests reach: each is dead only while no loader the analysis does not see loads the file.
|
|
176
|
+
*/
|
|
177
|
+
function possiblyLoaded(input: EmitterInput): ReadonlySet<string> {
|
|
178
|
+
const seeds = input.boundary.possiblyLoaded ?? new Set<string>();
|
|
179
|
+
if (seeds.size === 0) {
|
|
180
|
+
return seeds;
|
|
181
|
+
}
|
|
182
|
+
const union = input.swept.matrix.union;
|
|
183
|
+
const dead = new Set(input.swept.sweep.candidates.map((one) => one.id));
|
|
184
|
+
const reached = new Set<string>();
|
|
185
|
+
const pending = union.symbols.flatMap((symbol, at) => (seeds.has(symbol.id) ? [at] : []));
|
|
186
|
+
for (let at = pending.pop(); at !== undefined; at = pending.pop()) {
|
|
187
|
+
const symbol = union.symbols[at];
|
|
188
|
+
if (symbol === undefined || reached.has(symbol.id)) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
reached.add(symbol.id);
|
|
192
|
+
for (const edge of union.out[at] ?? []) {
|
|
193
|
+
const to = union.symbols[edge.to];
|
|
194
|
+
if (to !== undefined && dead.has(to.id)) {
|
|
195
|
+
pending.push(edge.to);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return reached;
|
|
200
|
+
}
|
|
201
|
+
|
|
185
202
|
/**
|
|
186
203
|
* The subjects dead only because production code only tests use is: each declaration the
|
|
187
204
|
* sweep marks test-used, each `DS1602` file of such code, and each test of dead code every
|
|
@@ -373,6 +390,9 @@ export function completed(
|
|
|
373
390
|
union.symbols.map((symbol, at) => [declarationKey(symbol.ref, symbol.position.path), at]),
|
|
374
391
|
);
|
|
375
392
|
const candidates = new Map(swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
393
|
+
const fileIds = new Map(
|
|
394
|
+
union.symbols.flatMap((one) => (one.kind === "file" ? [[one.position.path, one.id]] : [])),
|
|
395
|
+
);
|
|
376
396
|
const classOf = reachabilityClasses(input);
|
|
377
397
|
const ceilingOf = testCeilings(input, classOf);
|
|
378
398
|
const testUsed = testUsedSubjects(input);
|
|
@@ -465,7 +485,12 @@ export function completed(
|
|
|
465
485
|
? union.made[at]
|
|
466
486
|
: { production: candidate.productionRefs, test: candidate.testRefs };
|
|
467
487
|
const ownClass =
|
|
468
|
-
judged
|
|
488
|
+
judged !== undefined
|
|
489
|
+
? classOf(judged.id)
|
|
490
|
+
: (finding.reachabilityClass ??
|
|
491
|
+
(finding.code === CODE.fileNeverImported
|
|
492
|
+
? classOf(fileIds.get(finding.position.path) ?? "")
|
|
493
|
+
: "certain"));
|
|
469
494
|
const used =
|
|
470
495
|
judged !== undefined
|
|
471
496
|
? candidate !== undefined && testUsed.declarations.has(judged.id)
|
package/src/findings/emitter.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ResolvedConfig } from "../config.ts";
|
|
|
3
3
|
import type { Dependencies } from "../dependencies.ts";
|
|
4
4
|
import type { FileFacts } from "../file-facts.ts";
|
|
5
5
|
import type { Finding } from "../finding.ts";
|
|
6
|
-
import type { Implementations } from "
|
|
6
|
+
import type { Implementations } from "./implementations.ts";
|
|
7
7
|
import type { IntraFunctionFacts } from "../intra-function-parts.ts";
|
|
8
8
|
import type { Stores } from "../stores.ts";
|
|
9
9
|
import type { Boundary } from "./boundary.ts";
|
package/src/findings/emitters.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Confidence } from "../contract/contract.generated.ts";
|
|
2
2
|
import { evaluateEdges, type EdgeEvaluation } from "../edges.ts";
|
|
3
3
|
import type { CompletedFinding } from "../finding.ts";
|
|
4
|
-
import { liveFilesAt } from "../matrix.ts";
|
|
5
4
|
import { declarationKey } from "../ref.ts";
|
|
6
5
|
import { ledgerOf, type Dials, type Ledger, type SuppressionRecord } from "../suppress.ts";
|
|
7
6
|
import { insideSkipped } from "../type-errors.ts";
|
|
@@ -11,7 +10,7 @@ import type { Emitter, EmitterInput, Emitters } from "./emitter.ts";
|
|
|
11
10
|
import { interfaces } from "./interfaces.ts";
|
|
12
11
|
import { intraFunction } from "./intra-function.ts";
|
|
13
12
|
import { nonCodeArtifacts } from "./non-code-artifacts.ts";
|
|
14
|
-
import { readsAndWrites } from "./reads-and-writes.ts";
|
|
13
|
+
import { fallingWithWrites, readsAndWrites } from "./reads-and-writes.ts";
|
|
15
14
|
import { selfCheck } from "./self-check.ts";
|
|
16
15
|
import { fallenMembers, unusedDeclarations } from "./unused-declarations.ts";
|
|
17
16
|
import { visibilityNarrowing } from "./visibility-narrowing.ts";
|
|
@@ -28,42 +27,19 @@ export const EMITTERS: Emitters = new Map<string, Emitter>([
|
|
|
28
27
|
["intra-function", intraFunction],
|
|
29
28
|
]);
|
|
30
29
|
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*/
|
|
35
|
-
function heldKeys(input: EmitterInput): ReadonlySet<string> {
|
|
36
|
-
const held = new Set(input.swept.heldByUnanswered);
|
|
37
|
-
if (held.size === 0) {
|
|
38
|
-
return held;
|
|
39
|
-
}
|
|
30
|
+
/** The keys of the declarations the identifiers name, as {@link findingKey} keys a finding. */
|
|
31
|
+
function keysOf(input: EmitterInput, ids: Iterable<string>): ReadonlySet<string> {
|
|
32
|
+
const named = new Set(ids);
|
|
40
33
|
return new Set(
|
|
41
34
|
input.swept.matrix.union.symbols
|
|
42
|
-
.filter((symbol) =>
|
|
35
|
+
.filter((symbol) => named.has(symbol.id))
|
|
43
36
|
.map((symbol) => declarationKey(symbol.ref, symbol.position.path)),
|
|
44
37
|
);
|
|
45
38
|
}
|
|
46
39
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
* reports nothing about one while the file is live.
|
|
51
|
-
*/
|
|
52
|
-
function markupKeys(input: EmitterInput): ReadonlySet<string> {
|
|
53
|
-
const { swept } = input;
|
|
54
|
-
if (swept.componentFiles.length === 0) {
|
|
55
|
-
return new Set();
|
|
56
|
-
}
|
|
57
|
-
const live = new Set(
|
|
58
|
-
liveFilesAt(swept.matrix, swept.sweep, new Set(swept.componentFiles)).map(
|
|
59
|
-
(symbol) => symbol.id,
|
|
60
|
-
),
|
|
61
|
-
);
|
|
62
|
-
return new Set(
|
|
63
|
-
swept.matrix.union.symbols
|
|
64
|
-
.filter((symbol) => live.has(symbol.parent))
|
|
65
|
-
.map((symbol) => declarationKey(symbol.ref, symbol.position.path)),
|
|
66
|
-
);
|
|
40
|
+
/** The key of the declaration a finding is about, or of the one holding the part it is about. */
|
|
41
|
+
function findingKey(finding: CompletedFinding): string {
|
|
42
|
+
return declarationKey(finding.symbol.holder ?? finding.symbol.ref, finding.position.path);
|
|
67
43
|
}
|
|
68
44
|
|
|
69
45
|
/** What the suppressions of a run swept under their marks are decided against. */
|
|
@@ -91,15 +67,20 @@ interface DecidedFindings {
|
|
|
91
67
|
/** How many findings the minimum confidence withheld at each confidence. */
|
|
92
68
|
export type Withheld = Readonly<Record<Confidence, number>>;
|
|
93
69
|
|
|
94
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Every emitter's findings over one sweep, and those of any further emitter, completed by
|
|
72
|
+
* one step, but none about a declaration that falls with a write-only subject: that
|
|
73
|
+
* subject's finding names it, so no finding about it exists for a record to match.
|
|
74
|
+
*/
|
|
95
75
|
function completedOver(
|
|
96
76
|
input: EmitterInput,
|
|
97
77
|
...more: readonly Emitter[]
|
|
98
78
|
): readonly CompletedFinding[] {
|
|
79
|
+
const falls = keysOf(input, fallingWithWrites(input));
|
|
99
80
|
return completed(
|
|
100
81
|
input,
|
|
101
82
|
[...EMITTERS.values(), ...more].flatMap((emit) => emit(input)),
|
|
102
|
-
);
|
|
83
|
+
).filter((finding) => !falls.has(findingKey(finding)));
|
|
103
84
|
}
|
|
104
85
|
|
|
105
86
|
/**
|
|
@@ -161,21 +142,22 @@ function dialledOver(
|
|
|
161
142
|
unmarked: readonly CompletedFinding[],
|
|
162
143
|
): Omit<DecidedFindings, "would"> {
|
|
163
144
|
const would = [...unmarked, ...found];
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
145
|
+
const { sweep } = input.swept;
|
|
146
|
+
// A hold is a gap of the analysis, which leaves a record it touches dormant; a live
|
|
147
|
+
// component file's markup is no gap, so a record on one of its bindings stays in effect.
|
|
148
|
+
const unanswered = keysOf(input, sweep.held);
|
|
149
|
+
const markup = keysOf(input, sweep.bindings);
|
|
150
|
+
const heldIds = new Set(sweep.held);
|
|
168
151
|
const held = (finding: CompletedFinding): boolean =>
|
|
169
|
-
unanswered.has(
|
|
152
|
+
unanswered.has(findingKey(finding)) || insideSkipped(input.swept.skipped, finding.position);
|
|
170
153
|
const ledger = ledgerOf(
|
|
171
154
|
suppressed.records,
|
|
172
155
|
would,
|
|
173
156
|
dialsOver(input, found, suppressed, unmarked),
|
|
174
157
|
{ withholds: held, holdsRecord: (record) => heldIds.has(record.bound) },
|
|
175
158
|
);
|
|
176
|
-
const markup = markupKeys(input);
|
|
177
159
|
const candidates = found.filter(
|
|
178
|
-
(finding) => !ledger.withheld(finding) && !held(finding) && !markup.has(
|
|
160
|
+
(finding) => !ledger.withheld(finding) && !held(finding) && !markup.has(findingKey(finding)),
|
|
179
161
|
);
|
|
180
162
|
const kept = reportable(input, candidates);
|
|
181
163
|
const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
type Node,
|
|
16
16
|
} from "@typescript/native/ast";
|
|
17
17
|
import { SymbolFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/sync";
|
|
18
|
-
import { aliasChains } from "
|
|
19
|
-
import { nodeKey, type Inventory } from "
|
|
20
|
-
import { must, Unanswerable } from "
|
|
21
|
-
import type { ProjectView } from "
|
|
18
|
+
import { aliasChains } from "../alias-chain.ts";
|
|
19
|
+
import { nodeKey, type Inventory } from "../inventory.ts";
|
|
20
|
+
import { must, Unanswerable } from "../query.ts";
|
|
21
|
+
import type { ProjectView } from "../session.ts";
|
|
22
22
|
|
|
23
23
|
/** What one run knows about how its interfaces are implemented. */
|
|
24
24
|
export interface Implementations {
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
SyntaxKind,
|
|
12
12
|
type Node,
|
|
13
13
|
} from "@typescript/native/ast";
|
|
14
|
-
import type { ProjectView } from "
|
|
15
|
-
import { sourceFilesOf } from "
|
|
16
|
-
import { bytewise } from "
|
|
14
|
+
import type { ProjectView } from "../session.ts";
|
|
15
|
+
import { sourceFilesOf } from "../source-files.ts";
|
|
16
|
+
import { bytewise } from "../text/compare.ts";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* The text of each string literal, or template literal with no substitution, written as
|
|
@@ -2,7 +2,7 @@ import { CODE } from "../contract/contract.generated.ts";
|
|
|
2
2
|
import type { Finding, FindingPosition, PositionedSymbol } from "../finding.ts";
|
|
3
3
|
import type { InventorySymbol } from "../inventory.ts";
|
|
4
4
|
import { byPosition } from "../position.ts";
|
|
5
|
-
import type {
|
|
5
|
+
import type { Candidate } from "../liveness/sweep.ts";
|
|
6
6
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
7
7
|
|
|
8
8
|
/** What each finding says, by what the sweep found about the subject's references. */
|
|
@@ -21,7 +21,7 @@ function positionOf(symbol: InventorySymbol): FindingPosition {
|
|
|
21
21
|
function indexed(input: EmitterInput): {
|
|
22
22
|
readonly symbolOf: (id: string) => InventorySymbol | undefined;
|
|
23
23
|
readonly inTest: (id: string) => boolean;
|
|
24
|
-
readonly candidateOf: (id: string) =>
|
|
24
|
+
readonly candidateOf: (id: string) => Candidate | undefined;
|
|
25
25
|
readonly implementationsOf: (id: string) => readonly PositionedSymbol[];
|
|
26
26
|
} {
|
|
27
27
|
const union = input.swept.matrix.union;
|
|
@@ -113,7 +113,7 @@ export const interfaces: Emitter = (input) => {
|
|
|
113
113
|
};
|
|
114
114
|
|
|
115
115
|
/** What one unused interface's finding says, by the references the sweep counted. */
|
|
116
|
-
function unusedMessage(candidate:
|
|
116
|
+
function unusedMessage(candidate: Candidate): string {
|
|
117
117
|
if (candidate.relation === "reachability") {
|
|
118
118
|
return UNUSED_DEAD_MESSAGE;
|
|
119
119
|
}
|
|
@@ -3,7 +3,7 @@ import type { Finding } from "../finding.ts";
|
|
|
3
3
|
import { KINDS } from "../kinds.ts";
|
|
4
4
|
import { parameterRef } from "../ref.ts";
|
|
5
5
|
import { type PartFact } from "../intra-function-parts.ts";
|
|
6
|
-
import { reachedWithTheTests } from "../sweep.ts";
|
|
6
|
+
import { reachedWithTheTests } from "../liveness/sweep.ts";
|
|
7
7
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
8
8
|
|
|
9
9
|
/** A list of rules as a sentence names them. */
|
|
@@ -109,8 +109,8 @@ export const intraFunction: Emitter = (input) => {
|
|
|
109
109
|
const union = input.swept.matrix.union;
|
|
110
110
|
const byId = new Map(union.symbols.map((symbol) => [symbol.id, symbol]));
|
|
111
111
|
const reached = reachedWithTheTests(union, [
|
|
112
|
-
...input.swept.exempt.map((record) => record.id),
|
|
113
|
-
...input.swept.
|
|
112
|
+
...input.swept.sweep.exempt.map((record) => record.id),
|
|
113
|
+
...input.swept.sweep.held,
|
|
114
114
|
]);
|
|
115
115
|
const testsOfDeadCode = new Set(
|
|
116
116
|
input.swept.sweep.candidates.filter((one) => one.testOfDeadCode).map((one) => one.id),
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { CODE } from "../contract/contract.generated.ts";
|
|
2
2
|
import type { Finding, FindingDetails, FindingPosition } from "../finding.ts";
|
|
3
|
-
import type
|
|
4
|
-
import type {
|
|
3
|
+
import { WORDS, type InventorySymbol } from "../inventory.ts";
|
|
4
|
+
import type { Candidate } from "../liveness/sweep.ts";
|
|
5
5
|
import { positionKey } from "../position.ts";
|
|
6
6
|
import { declarationKey } from "../ref.ts";
|
|
7
|
+
import { reachabilityClasses } from "./completion.ts";
|
|
7
8
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
9
|
+
|
|
8
10
|
import { bytewise } from "../text/compare.ts";
|
|
9
11
|
|
|
10
12
|
/** The codes this emitter reports. */
|
|
@@ -18,7 +20,7 @@ const SIGNATURES: ReadonlySet<string> = new Set(["function", "method", "type"]);
|
|
|
18
20
|
interface Run {
|
|
19
21
|
readonly input: EmitterInput;
|
|
20
22
|
readonly symbols: ReadonlyMap<string, InventorySymbol>;
|
|
21
|
-
readonly candidates: ReadonlyMap<string,
|
|
23
|
+
readonly candidates: ReadonlyMap<string, Candidate>;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
function indexed(input: EmitterInput): Run {
|
|
@@ -112,19 +114,16 @@ function parametersOnlyNamedBy(
|
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
/** What one write-only finding says, the declarations its deletion takes with it included. */
|
|
115
|
-
function writeOnlyMessage(
|
|
116
|
-
|
|
117
|
-
namedBy: ReadonlyMap<string, ReadonlySet<string>>,
|
|
118
|
-
symbol: InventorySymbol,
|
|
119
|
-
held: Uses,
|
|
120
|
-
): string {
|
|
117
|
+
function writeOnlyMessage(run: Run, written: Written, subject: Subject): string {
|
|
118
|
+
const { symbol, held } = subject;
|
|
121
119
|
const writes = held.writes.length === 1 ? "once" : `at ${String(held.writes.length)} positions`;
|
|
122
120
|
const read = run.input.stores.readInTests.has(symbol.id)
|
|
123
121
|
? "read only from test files"
|
|
124
122
|
: "never read";
|
|
125
123
|
const cascade = [
|
|
126
|
-
...parametersOnlyNamedBy(run, namedBy, symbol).map((name) => `type parameter ${name}`),
|
|
124
|
+
...parametersOnlyNamedBy(run, written.namedBy, symbol).map((name) => `type parameter ${name}`),
|
|
127
125
|
...[...held.alone].sort(bytewise).map((name) => `import ${name}`),
|
|
126
|
+
...(written.named.get(symbol.id) ?? []),
|
|
128
127
|
];
|
|
129
128
|
const word = SUBJECT_WORDS[symbol.kind] ?? "member";
|
|
130
129
|
const message = `${word} ${symbol.name} is written ${writes} and ${read}`;
|
|
@@ -133,17 +132,55 @@ function writeOnlyMessage(
|
|
|
133
132
|
: `${message}, and deleting it with its writes deletes ${cascade.join(" and ")} too`;
|
|
134
133
|
}
|
|
135
134
|
|
|
135
|
+
/** One declaration the write-only kind reports, with its uses. */
|
|
136
|
+
interface Subject {
|
|
137
|
+
readonly symbol: InventorySymbol;
|
|
138
|
+
readonly held: Uses;
|
|
139
|
+
}
|
|
140
|
+
|
|
136
141
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
142
|
+
* What the write-only kind answers of one run: its subjects in site order, and the
|
|
143
|
+
* declarations that fall with them, each named under the innermost subject of every chain
|
|
144
|
+
* of carriers its references hold.
|
|
145
|
+
*/
|
|
146
|
+
interface Written {
|
|
147
|
+
readonly subjects: readonly Subject[];
|
|
148
|
+
readonly namedBy: ReadonlyMap<string, ReadonlySet<string>>;
|
|
149
|
+
readonly named: ReadonlyMap<string, readonly string[]>;
|
|
150
|
+
readonly falls: ReadonlySet<string>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The kinds of declaration that fall with a write-only subject: a declaration of its own.
|
|
155
|
+
* A member falls with its container, and a type parameter is the type-parameter clause's.
|
|
156
|
+
*/
|
|
157
|
+
const FALLING: ReadonlySet<InventorySymbol["kind"]> = new Set([
|
|
158
|
+
"function",
|
|
159
|
+
"class",
|
|
160
|
+
"interface",
|
|
161
|
+
"type",
|
|
162
|
+
"enum",
|
|
163
|
+
"namespace",
|
|
164
|
+
"variable",
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
const WRITTEN = new WeakMap<EmitterInput, Written>();
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* `DS1301`'s subjects: a module-level variable, a type member or a class member the
|
|
171
|
+
* counted references store into and never read.
|
|
139
172
|
*
|
|
140
173
|
* The subject is a live declaration: a store is a reference, so the sweep holds a
|
|
141
174
|
* written one live and its candidates are never this kind's. Every reference that is
|
|
142
175
|
* not a store reads, and so does an exemption record naming the declaration.
|
|
143
176
|
*/
|
|
144
|
-
function
|
|
177
|
+
function writtenOf(run: Run): Written {
|
|
178
|
+
const known = WRITTEN.get(run.input);
|
|
179
|
+
if (known !== undefined) {
|
|
180
|
+
return known;
|
|
181
|
+
}
|
|
145
182
|
const { stores } = run.input;
|
|
146
|
-
const { matrix } = run.input.swept;
|
|
183
|
+
const { matrix, sweep } = run.input.swept;
|
|
147
184
|
const uses = new Map<string, Uses>();
|
|
148
185
|
for (const reference of stores.references) {
|
|
149
186
|
let held = uses.get(reference.to);
|
|
@@ -174,26 +211,54 @@ function writeOnlySymbols(run: Run): readonly Finding[] {
|
|
|
174
211
|
}
|
|
175
212
|
}
|
|
176
213
|
|
|
177
|
-
const
|
|
178
|
-
for (const symbol of matrix.union.symbols) {
|
|
214
|
+
const subjects = matrix.union.symbols.flatMap((symbol): Subject[] => {
|
|
179
215
|
const held = uses.get(symbol.id);
|
|
180
|
-
|
|
181
|
-
held === undefined ||
|
|
216
|
+
return held === undefined ||
|
|
182
217
|
held.writes.length === 0 ||
|
|
183
218
|
held.reads > 0 ||
|
|
184
219
|
!writable(run, symbol) ||
|
|
185
220
|
stores.exempt.has(symbol.id) ||
|
|
186
221
|
run.candidates.has(symbol.id)
|
|
222
|
+
? []
|
|
223
|
+
: [{ symbol, held }];
|
|
224
|
+
});
|
|
225
|
+
const subjectIds = new Set(subjects.map((subject) => subject.symbol.id));
|
|
226
|
+
const classOf = reachabilityClasses(run.input);
|
|
227
|
+
const named = new Map<string, string[]>();
|
|
228
|
+
const falls = new Set<string>();
|
|
229
|
+
for (const symbol of matrix.union.symbols) {
|
|
230
|
+
const chains = sweep.carried.get(symbol.id);
|
|
231
|
+
if (
|
|
232
|
+
chains === undefined ||
|
|
233
|
+
subjectIds.has(symbol.id) ||
|
|
234
|
+
!FALLING.has(symbol.kind) ||
|
|
235
|
+
classOf(symbol.id) !== "certain"
|
|
187
236
|
) {
|
|
188
237
|
continue;
|
|
189
238
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
);
|
|
239
|
+
const innermost = chains.map((chain) => chain.find((id) => subjectIds.has(id)));
|
|
240
|
+
if (innermost.includes(undefined)) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
falls.add(symbol.id);
|
|
244
|
+
for (const subject of new Set(innermost)) {
|
|
245
|
+
const name = `${WORDS[symbol.kind]} ${symbol.name}`;
|
|
246
|
+
named.set(subject ?? "", [...(named.get(subject ?? "") ?? []), name]);
|
|
247
|
+
}
|
|
195
248
|
}
|
|
196
|
-
|
|
249
|
+
const written = { subjects, namedBy, named, falls };
|
|
250
|
+
WRITTEN.set(run.input, written);
|
|
251
|
+
return written;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** `DS1301`: each subject, named with every position written and what falls with it. */
|
|
255
|
+
function writeOnlySymbols(run: Run): readonly Finding[] {
|
|
256
|
+
const written = writtenOf(run);
|
|
257
|
+
return written.subjects.map((subject) =>
|
|
258
|
+
findingAbout(subject.symbol, CODE.writeOnlySymbol, writeOnlyMessage(run, written, subject), {
|
|
259
|
+
writePositions: subject.held.writes,
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
197
262
|
}
|
|
198
263
|
|
|
199
264
|
/**
|
|
@@ -253,12 +318,20 @@ function unusedTypeParameters(run: Run): readonly Finding[] {
|
|
|
253
318
|
*/
|
|
254
319
|
export function writeOnlyDeclarations(input: EmitterInput): ReadonlySet<string> {
|
|
255
320
|
return new Set(
|
|
256
|
-
|
|
257
|
-
declarationKey(
|
|
321
|
+
writtenOf(indexed(input)).subjects.map(({ symbol }) =>
|
|
322
|
+
declarationKey(symbol.ref, symbol.position.path),
|
|
258
323
|
),
|
|
259
324
|
);
|
|
260
325
|
}
|
|
261
326
|
|
|
327
|
+
/**
|
|
328
|
+
* The declarations that fall with a write-only subject, whatever the severity: a finding
|
|
329
|
+
* about one would name code its subject's deletion already deletes.
|
|
330
|
+
*/
|
|
331
|
+
export function fallingWithWrites(input: EmitterInput): ReadonlySet<string> {
|
|
332
|
+
return writtenOf(indexed(input)).falls;
|
|
333
|
+
}
|
|
334
|
+
|
|
262
335
|
/**
|
|
263
336
|
* The findings of the reads-and-writes kinds, `DS1301`, `DS1005` and `DS1006`, in that
|
|
264
337
|
* order and each code's in site order.
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { CODE } from "../contract/contract.generated.ts";
|
|
2
2
|
import type { Finding } from "../finding.ts";
|
|
3
|
-
import type
|
|
4
|
-
import type {
|
|
3
|
+
import { WORDS, type InventorySymbol, type SymbolKind } from "../inventory.ts";
|
|
4
|
+
import type { Candidate } from "../liveness/sweep.ts";
|
|
5
5
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
6
|
-
import { testsUnseenAPI } from "./completion.ts";
|
|
7
6
|
import { unreachableExports } from "./visibility-narrowing.ts";
|
|
8
7
|
|
|
9
8
|
/** The kinds of declaration that are members of a class, an interface, a type or an enum. */
|
|
@@ -26,39 +25,18 @@ const NOT_REPORTED_HERE: ReadonlySet<SymbolKind> = new Set([
|
|
|
26
25
|
"type-parameter",
|
|
27
26
|
]);
|
|
28
27
|
|
|
29
|
-
/** The reader's word for each kind of declaration a finding of this emitter names. */
|
|
30
|
-
const WORDS: Readonly<Record<SymbolKind, string>> = {
|
|
31
|
-
file: "file",
|
|
32
|
-
function: "function",
|
|
33
|
-
class: "class",
|
|
34
|
-
interface: "interface",
|
|
35
|
-
type: "type alias",
|
|
36
|
-
enum: "enum",
|
|
37
|
-
namespace: "namespace",
|
|
38
|
-
variable: "variable",
|
|
39
|
-
"export-alias": "re-export",
|
|
40
|
-
method: "method",
|
|
41
|
-
"class-member": "class member",
|
|
42
|
-
"interface-method": "interface method",
|
|
43
|
-
"type-member": "type member",
|
|
44
|
-
"enum-member": "enum member",
|
|
45
|
-
"type-parameter": "type parameter",
|
|
46
|
-
};
|
|
47
|
-
|
|
48
28
|
/** What the test-of-dead-code kind says, which states the rule it applies. */
|
|
49
29
|
const TEST_OF_DEAD_CODE = "every target symbol this test references is reported dead";
|
|
50
30
|
|
|
51
31
|
/** What the run holds about one candidate that decides its code. */
|
|
52
32
|
interface Judged {
|
|
53
|
-
readonly candidate:
|
|
33
|
+
readonly candidate: Candidate;
|
|
54
34
|
readonly symbol: InventorySymbol;
|
|
55
35
|
/** Whether a test file declares it. */
|
|
56
36
|
readonly test: boolean;
|
|
57
37
|
readonly deprecated: boolean;
|
|
58
38
|
/** Whether the unreachable-export kind reports it. */
|
|
59
39
|
readonly unreachable: boolean;
|
|
60
|
-
/** Whether it is a test with a target that is published API no declared consumer judges. */
|
|
61
|
-
readonly testsUnseenAPI: boolean;
|
|
62
40
|
}
|
|
63
41
|
|
|
64
42
|
/**
|
|
@@ -72,7 +50,7 @@ interface Judged {
|
|
|
72
50
|
function codeOf(judged: Judged): string | undefined {
|
|
73
51
|
const { candidate, symbol } = judged;
|
|
74
52
|
if (candidate.testOfDeadCode) {
|
|
75
|
-
return
|
|
53
|
+
return CODE.testOfDeadCode;
|
|
76
54
|
}
|
|
77
55
|
if ((judged.test && !candidate.unreferencedTest) || NOT_REPORTED_HERE.has(symbol.kind)) {
|
|
78
56
|
return undefined;
|
|
@@ -245,7 +223,6 @@ function emitterFindings(input: EmitterInput, fallen: boolean): Finding[] {
|
|
|
245
223
|
const union = input.swept.matrix.union;
|
|
246
224
|
const dead = new Set(input.swept.sweep.candidates.map((candidate) => candidate.id));
|
|
247
225
|
const unreachable = unreachableExports(input);
|
|
248
|
-
const unseenAPI = testsUnseenAPI(input);
|
|
249
226
|
let brands: ReadonlySet<string> | undefined;
|
|
250
227
|
|
|
251
228
|
const found: Finding[] = [];
|
|
@@ -265,7 +242,6 @@ function emitterFindings(input: EmitterInput, fallen: boolean): Finding[] {
|
|
|
265
242
|
test: union.test[at] === true,
|
|
266
243
|
deprecated: input.deprecated.has(symbol.id),
|
|
267
244
|
unreachable: unreachable.has(symbol.id),
|
|
268
|
-
testsUnseenAPI: candidate.testOfDeadCode && unseenAPI(candidate.id),
|
|
269
245
|
};
|
|
270
246
|
const code = codeOf(judged);
|
|
271
247
|
if (
|