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