@cplieger/deadset-ts 3.13.0 → 4.0.1
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 +67 -49
- package/dist/bin/node-host.js +10 -7
- package/dist/src/alias-chain.js +59 -7
- package/dist/src/analysis.js +120 -24
- 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 +106 -29
- package/dist/src/entry-point-gaps.js +2 -2
- 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 +51 -10
- package/dist/src/reporters/text.js +8 -6
- package/dist/src/roots.js +27 -11
- package/dist/src/schema.js +2 -2
- 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 +41 -8
- 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 +224 -62
- 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 +178 -34
- package/src/entry-point-gaps.ts +2 -2
- 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 +53 -11
- package/src/reporters/text.ts +12 -12
- package/src/roots.ts +34 -12
- package/src/schema.ts +2 -2
- 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 +54 -8
- package/src/verbs/print-retained.ts +6 -2
- package/src/verbs/print-roots.ts +5 -1
package/src/roots.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { globExpression } from "./glob.ts";
|
|
|
15
15
|
import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
16
16
|
import type { Manifest, ManifestEntry } from "./manifest.ts";
|
|
17
17
|
import { byPosition } from "./position.ts";
|
|
18
|
+
import { UNANSWERED } from "./query.ts";
|
|
18
19
|
import type { ProjectView } from "./session.ts";
|
|
19
20
|
import { sourceFilesOf } from "./source-files.ts";
|
|
20
21
|
|
|
@@ -70,6 +71,12 @@ export interface Roots {
|
|
|
70
71
|
* {@link unmatchedEverywhere} is what turns one into the other.
|
|
71
72
|
*/
|
|
72
73
|
readonly unmatched: readonly string[];
|
|
74
|
+
/**
|
|
75
|
+
* Every declaration of the project, where the checker left the module or export table
|
|
76
|
+
* of an entry or a published file unanswered, and none otherwise: which of them that
|
|
77
|
+
* file exports is unknown.
|
|
78
|
+
*/
|
|
79
|
+
readonly unanswered: readonly string[];
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
/** What decides the roots beside the declarations themselves. */
|
|
@@ -225,7 +232,7 @@ export function roots<Brand>(
|
|
|
225
232
|
enter(point.file, point.rule, point.source, point.exports);
|
|
226
233
|
}
|
|
227
234
|
|
|
228
|
-
const exportsOf = exportTables(project, held, [
|
|
235
|
+
const { tables: exportsOf, answered } = exportTables(project, held, [
|
|
229
236
|
...entered.filter((entry) => entry.exports).map((entry) => entry.file),
|
|
230
237
|
...published,
|
|
231
238
|
]);
|
|
@@ -258,47 +265,62 @@ export function roots<Brand>(
|
|
|
258
265
|
return site || compare(a.kind, b.kind) || compare(a.source, b.source);
|
|
259
266
|
});
|
|
260
267
|
|
|
261
|
-
return {
|
|
268
|
+
return {
|
|
269
|
+
configFile: project.configFile,
|
|
270
|
+
liveUnderReachability: ordered,
|
|
271
|
+
unmatched,
|
|
272
|
+
unanswered: answered ? [] : held.symbols.map((symbol) => symbol.id),
|
|
273
|
+
};
|
|
262
274
|
}
|
|
263
275
|
|
|
264
276
|
/**
|
|
265
277
|
* Every declaration each file's module exports, by the file's name: the declaration
|
|
266
278
|
* each name of the export table is, and every link of a re-export's chain. A name
|
|
267
|
-
* whose declaration is outside the project's own files
|
|
268
|
-
* and
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
* its export table, star re-exports resolved into it. A file that is no module
|
|
272
|
-
* exports nothing.
|
|
279
|
+
* whose declaration is outside the project's own files names nothing here. One batch
|
|
280
|
+
* resolves every file to its module and one request per module reads its export
|
|
281
|
+
* table, star re-exports resolved into it. A file that is no module exports nothing.
|
|
282
|
+
* `answered` is false where the checker left a table or a link of a chain unanswered.
|
|
273
283
|
*/
|
|
274
284
|
function exportTables<Brand>(
|
|
275
285
|
project: ProjectView<Brand>,
|
|
276
286
|
held: Inventory,
|
|
277
287
|
wanted: readonly SourceFile[],
|
|
278
|
-
): ReadonlyMap<string, readonly string[]
|
|
288
|
+
): { readonly tables: ReadonlyMap<string, readonly string[]>; readonly answered: boolean } {
|
|
279
289
|
const unique = [...new Map(wanted.map((file) => [file.fileName, file])).values()];
|
|
280
290
|
const tables = new Map<string, readonly string[]>();
|
|
291
|
+
let answered = true;
|
|
281
292
|
if (unique.length === 0) {
|
|
282
|
-
return tables;
|
|
293
|
+
return { tables, answered };
|
|
283
294
|
}
|
|
284
295
|
const modules = project.symbolsAt(unique.map((file) => project.handle(file)));
|
|
285
296
|
const chains = aliasChains(project, held);
|
|
286
297
|
|
|
298
|
+
const topLevel = (file: SourceFile): readonly string[] => {
|
|
299
|
+
const fileId = held.declarations.get(nodeKey(file, file));
|
|
300
|
+
return held.symbols.filter((symbol) => symbol.parent === fileId).map((symbol) => symbol.id);
|
|
301
|
+
};
|
|
287
302
|
unique.forEach((file, index) => {
|
|
288
303
|
const module = modules[index];
|
|
289
304
|
if (module === undefined) {
|
|
290
305
|
tables.set(file.fileName, []);
|
|
291
306
|
return;
|
|
292
307
|
}
|
|
308
|
+
const table = module === UNANSWERED ? UNANSWERED : project.queries.exportsOfModule(module);
|
|
309
|
+
if (table === UNANSWERED) {
|
|
310
|
+
answered = false;
|
|
311
|
+
tables.set(file.fileName, topLevel(file));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
293
314
|
const ids = new Set<string>();
|
|
294
|
-
for (const exported of
|
|
315
|
+
for (const exported of table) {
|
|
295
316
|
for (const target of chains.chainOf(exported)) {
|
|
317
|
+
answered &&= !target.guessed;
|
|
296
318
|
ids.add(target.id);
|
|
297
319
|
}
|
|
298
320
|
}
|
|
299
321
|
tables.set(file.fileName, [...ids]);
|
|
300
322
|
});
|
|
301
|
-
return tables;
|
|
323
|
+
return { tables, answered };
|
|
302
324
|
}
|
|
303
325
|
|
|
304
326
|
/** The kinds of container whose export table, not its members' visibility, says what it publishes. */
|
package/src/schema.ts
CHANGED
|
@@ -261,9 +261,9 @@ export function unimplementedKey(label: string, path: string): ConfigError {
|
|
|
261
261
|
const nearest = nearestKey(path);
|
|
262
262
|
let hint = "";
|
|
263
263
|
if (nearest === path) {
|
|
264
|
-
hint =
|
|
264
|
+
hint = `. ${JSON.stringify(path)} is one setting written as nested objects, not as one key`;
|
|
265
265
|
} else if (nearest !== "") {
|
|
266
|
-
hint =
|
|
266
|
+
hint = `. The nearest implemented key is ${JSON.stringify(nearest)}`;
|
|
267
267
|
}
|
|
268
268
|
return new ConfigError(
|
|
269
269
|
"unimplemented-key",
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
import type { DetectorInput, Evidence } from "./exempt.ts";
|
|
58
58
|
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
59
59
|
import { renderPosition } from "./position.ts";
|
|
60
|
+
import { must } from "./query.ts";
|
|
60
61
|
import type { ProjectView } from "./session.ts";
|
|
61
62
|
import { valueReach, type Reached } from "./value-reach.ts";
|
|
62
63
|
|
|
@@ -316,12 +317,13 @@ function erasedParameters<Brand>(
|
|
|
316
317
|
if (!parameter.rest) {
|
|
317
318
|
return (type.flags & ERASED) !== 0;
|
|
318
319
|
}
|
|
319
|
-
const [element] =
|
|
320
|
+
const [element] = must(project.queries.typeArguments(type));
|
|
320
321
|
return element !== undefined && (element.flags & ERASED) !== 0;
|
|
321
322
|
});
|
|
322
323
|
const bySymbol = new Map<number, Parameter>();
|
|
323
324
|
project
|
|
324
325
|
.symbolsAt(erased.map((parameter) => project.handle(parameter.name)))
|
|
326
|
+
.map(must)
|
|
325
327
|
.forEach((symbol, index) => {
|
|
326
328
|
const parameter = erased[index];
|
|
327
329
|
if (symbol !== undefined && parameter !== undefined) {
|
|
@@ -347,11 +349,11 @@ function handedOn<Brand>(
|
|
|
347
349
|
),
|
|
348
350
|
);
|
|
349
351
|
const plain = candidates.filter((one) => !one.value.shorthand);
|
|
350
|
-
const resolved = project.symbolsAt(plain.map((one) => project.handle(one.value.name)));
|
|
352
|
+
const resolved = project.symbolsAt(plain.map((one) => project.handle(one.value.name))).map(must);
|
|
351
353
|
const symbolOf = new Map(plain.map((one, index) => [one, resolved[index]]));
|
|
352
354
|
return candidates.flatMap((one) => {
|
|
353
355
|
const symbol = one.value.shorthand
|
|
354
|
-
? project.shorthandValueAt(project.handle(one.value.name))
|
|
356
|
+
? must(project.shorthandValueAt(project.handle(one.value.name)))
|
|
355
357
|
: symbolOf.get(one);
|
|
356
358
|
const parameter = symbol === undefined ? undefined : erased.get(symbol.id);
|
|
357
359
|
return parameter === undefined ? [] : [{ parameter, site: one.site, argument: one.argument }];
|
|
@@ -382,8 +384,8 @@ function destinations<Brand>(
|
|
|
382
384
|
walked: Walked,
|
|
383
385
|
): Destinations {
|
|
384
386
|
const { project, held } = input;
|
|
385
|
-
const
|
|
386
|
-
const own =
|
|
387
|
+
const queries = project.queries;
|
|
388
|
+
const own = project.ownPaths();
|
|
387
389
|
const named = resolveEntries(project, held, [JSON_STRINGIFY, ...input.ts.serializers]).filter(
|
|
388
390
|
namesADeclaration,
|
|
389
391
|
);
|
|
@@ -422,7 +424,7 @@ function destinations<Brand>(
|
|
|
422
424
|
form !== undefined &&
|
|
423
425
|
method !== undefined &&
|
|
424
426
|
outside(method) &&
|
|
425
|
-
FUNCTION_INTERFACES.has(
|
|
427
|
+
FUNCTION_INTERFACES.has(must(project.queries.parentOf(method))?.name ?? "")
|
|
426
428
|
) {
|
|
427
429
|
return form.invocation;
|
|
428
430
|
}
|
|
@@ -439,16 +441,16 @@ function destinations<Brand>(
|
|
|
439
441
|
const invoked = invocation(call);
|
|
440
442
|
// Through a method of the prototype the resolved signature is the method's, so
|
|
441
443
|
// the parameters are read off the callee's own signatures, any one erasing.
|
|
442
|
-
const type = invoked.indirect ?
|
|
444
|
+
const type = invoked.indirect ? must(queries.typeAt(invoked.callee)) : undefined;
|
|
443
445
|
const signatures = invoked.indirect
|
|
444
446
|
? type === undefined
|
|
445
447
|
? []
|
|
446
|
-
:
|
|
447
|
-
: [
|
|
448
|
+
: must(queries.signaturesOf(type, SignatureKind.Call))
|
|
449
|
+
: [must(queries.resolvedSignature(call))].filter((one) => one !== undefined);
|
|
448
450
|
indexes = new Set(
|
|
449
451
|
invoked.arguments.flatMap((_unused, index) =>
|
|
450
452
|
signatures.some((signature) => {
|
|
451
|
-
const parameter =
|
|
453
|
+
const parameter = must(queries.parameterType(signature, index));
|
|
452
454
|
return parameter !== undefined && (parameter.flags & ERASED) !== 0;
|
|
453
455
|
})
|
|
454
456
|
? [index]
|
package/src/session.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { API, SymbolFlags } from "@typescript/native/unstable/sync";
|
|
2
2
|
import type {
|
|
3
|
-
|
|
3
|
+
APIRequestGenerator,
|
|
4
4
|
Diagnostic,
|
|
5
5
|
DocumentIdentifier,
|
|
6
6
|
NodeHandle,
|
|
@@ -10,9 +10,15 @@ import type {
|
|
|
10
10
|
Symbol as TSSymbol,
|
|
11
11
|
TimingInfo,
|
|
12
12
|
} from "@typescript/native/unstable/sync";
|
|
13
|
-
import { isIdentifier } from "@typescript/native/unstable/ast";
|
|
14
13
|
import type { Node, SourceFile } from "@typescript/native/unstable/ast";
|
|
15
14
|
import { DiscoveryError } from "./discover.ts";
|
|
15
|
+
import {
|
|
16
|
+
queriesOf,
|
|
17
|
+
UnansweredLog,
|
|
18
|
+
type Answer,
|
|
19
|
+
type Queries,
|
|
20
|
+
type UnansweredQuestion,
|
|
21
|
+
} from "./query.ts";
|
|
16
22
|
|
|
17
23
|
/**
|
|
18
24
|
* The compiler the analysis reads through. It is a client over a protocol to a
|
|
@@ -31,7 +37,15 @@ export interface Engine {
|
|
|
31
37
|
*/
|
|
32
38
|
parseConfigFile(file: DocumentIdentifier): ReturnType<API["parseConfigFile"]>;
|
|
33
39
|
/** Opens one snapshot holding every project named. */
|
|
34
|
-
|
|
40
|
+
createSnapshot(openProjects: readonly string[]): Snapshot;
|
|
41
|
+
/** Sends many questions in one round trip and answers each, in order. */
|
|
42
|
+
batch<T>(questions: readonly APIRequestGenerator<T>[]): T[];
|
|
43
|
+
/**
|
|
44
|
+
* Puts one question to a project's checker, by the accessor's name and the locations
|
|
45
|
+
* it asks about. Every guarded question passes through here, so a caller that
|
|
46
|
+
* observes or fails questions does it at this seam.
|
|
47
|
+
*/
|
|
48
|
+
ask<T>(accessor: string, locations: () => readonly string[], question: () => T): T;
|
|
35
49
|
/** Round-trip latency, bytes transferred and server time, when collection is on. */
|
|
36
50
|
getTimingInfo(): TimingInfo;
|
|
37
51
|
/** Releases the client and the process behind it. */
|
|
@@ -56,7 +70,10 @@ export function openEngine(options: EngineOptions): Engine {
|
|
|
56
70
|
let closed = false;
|
|
57
71
|
return {
|
|
58
72
|
parseConfigFile: (file) => api.parseConfigFile(file),
|
|
59
|
-
|
|
73
|
+
createSnapshot: (openProjects) => api.createSnapshot({ openProjects: [...openProjects] }),
|
|
74
|
+
batch: <T>(questions: readonly APIRequestGenerator<T>[]) =>
|
|
75
|
+
api.batch<readonly APIRequestGenerator<T>[]>(...questions),
|
|
76
|
+
ask: (_accessor, _locations, question) => question(),
|
|
60
77
|
getTimingInfo: () => api.getTimingInfo(),
|
|
61
78
|
close: () => {
|
|
62
79
|
if (closed) {
|
|
@@ -90,38 +107,45 @@ export interface ProjectView<Brand> {
|
|
|
90
107
|
readonly configFile: string;
|
|
91
108
|
/** The project's program, whose walk is local to this process. */
|
|
92
109
|
readonly program: Program;
|
|
93
|
-
/**
|
|
94
|
-
|
|
110
|
+
/**
|
|
111
|
+
* The questions the project's checker answers, each guarded. Every one is a round
|
|
112
|
+
* trip, and one may go unanswered.
|
|
113
|
+
*/
|
|
114
|
+
readonly queries: Queries;
|
|
95
115
|
/** The source files the program holds that are the target's own. */
|
|
96
116
|
ownSourceFiles(): readonly SourceFile[];
|
|
117
|
+
/**
|
|
118
|
+
* The keys of the target's own files, which is how a declaration handle names the
|
|
119
|
+
* file it is in.
|
|
120
|
+
*/
|
|
121
|
+
ownPaths(): ReadonlySet<string>;
|
|
97
122
|
/** A handle to one node of this project's program. */
|
|
98
123
|
handle(node: Node): Handle<Brand>;
|
|
99
124
|
/**
|
|
100
|
-
* The symbols a batch of this project's nodes resolve to,
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* resolve.
|
|
125
|
+
* The symbols a batch of this project's nodes resolve to, in the order the handles
|
|
126
|
+
* were given, in runs of at most `cap`. The batch is how a file's identifiers reach
|
|
127
|
+
* the checker, and `undefined` marks a node the batch did not resolve.
|
|
104
128
|
*/
|
|
105
|
-
symbolsAt(handles: readonly Handle<Brand>[]):
|
|
129
|
+
symbolsAt(handles: readonly Handle<Brand>[], cap?: number): Answer<TSSymbol | undefined>[];
|
|
106
130
|
/**
|
|
107
131
|
* The symbol one node resolves to, asked for that node alone. The accessor behind
|
|
108
|
-
* it takes one identifier and offers no array
|
|
109
|
-
*
|
|
110
|
-
*
|
|
132
|
+
* it takes one identifier and offers no array form, so it answers only for the
|
|
133
|
+
* residue a batch left unresolved. A node that is not an identifier has no such
|
|
134
|
+
* answer.
|
|
111
135
|
*/
|
|
112
|
-
resolvedSymbolAt(handle: Handle<Brand>): TSSymbol | undefined
|
|
136
|
+
resolvedSymbolAt(handle: Handle<Brand>): Answer<TSSymbol | undefined>;
|
|
113
137
|
/**
|
|
114
138
|
* The symbol the value of one shorthand property assignment names, which is the
|
|
115
139
|
* declaration `{ a }` reads; the symbol the property's own name resolves to is the
|
|
116
140
|
* property the literal declares. The handle names the assignment, not its name.
|
|
117
141
|
*/
|
|
118
|
-
shorthandValueAt(handle: Handle<Brand>): TSSymbol | undefined
|
|
142
|
+
shorthandValueAt(handle: Handle<Brand>): Answer<TSSymbol | undefined>;
|
|
119
143
|
/**
|
|
120
144
|
* The symbol one alias names, one link along: an import or export specifier
|
|
121
145
|
* resolves to the declaration it carries forward, or to the next alias of a chain.
|
|
122
146
|
* `undefined` where the symbol is no alias, or where the alias resolves to nothing.
|
|
123
147
|
*/
|
|
124
|
-
aliasStepOf(symbol: TSSymbol): TSSymbol | undefined
|
|
148
|
+
aliasStepOf(symbol: TSSymbol): Answer<TSSymbol | undefined>;
|
|
125
149
|
/**
|
|
126
150
|
* The node one symbol's declaration handle names, read in this project's program,
|
|
127
151
|
* or `undefined` where this project's program does not hold that file.
|
|
@@ -142,49 +166,70 @@ export interface ProjectView<Brand> {
|
|
|
142
166
|
*/
|
|
143
167
|
export type ProjectVisitor<Result> = <Brand>(project: ProjectView<Brand>) => Result;
|
|
144
168
|
|
|
145
|
-
function viewOf<Brand>(
|
|
169
|
+
function viewOf<Brand>(
|
|
170
|
+
project: Project,
|
|
171
|
+
configFile: string,
|
|
172
|
+
engine: Engine,
|
|
173
|
+
log: UnansweredLog,
|
|
174
|
+
): ProjectView<Brand> {
|
|
175
|
+
const queries = queriesOf(
|
|
176
|
+
project.checker,
|
|
177
|
+
configFile,
|
|
178
|
+
(questions) => engine.batch(questions),
|
|
179
|
+
log,
|
|
180
|
+
(accessor, locations, question) => engine.ask(accessor, locations, question),
|
|
181
|
+
);
|
|
146
182
|
// The snapshot is never updated, so a project's own file set is the same answer
|
|
147
183
|
// every time it is asked for. It is read once because the question is not free:
|
|
148
184
|
// deciding it consults the metadata of every file the program holds, the compiler's
|
|
149
185
|
// own library files included, and a run asks for the set once per pass.
|
|
150
186
|
let ownFiles: readonly SourceFile[] | undefined;
|
|
187
|
+
let ownKeys: ReadonlySet<string> | undefined;
|
|
188
|
+
const ownSourceFiles = (): readonly SourceFile[] => {
|
|
189
|
+
ownFiles ??= project.program
|
|
190
|
+
.getSourceFileNames()
|
|
191
|
+
// The metadata decides which files belong to the target, and it is read by
|
|
192
|
+
// name. Fetching each file first and filtering afterwards costs one round
|
|
193
|
+
// trip per library file the answer then discards, and a program holds far
|
|
194
|
+
// more library files than target files.
|
|
195
|
+
.filter((name) => {
|
|
196
|
+
const metadata = project.program.getSourceFileMetadata(name);
|
|
197
|
+
return (
|
|
198
|
+
metadata !== undefined && !metadata.isDefaultLibrary && !metadata.isFromExternalLibrary
|
|
199
|
+
);
|
|
200
|
+
})
|
|
201
|
+
.map((name) => project.program.getSourceFile(name))
|
|
202
|
+
.filter((file): file is SourceFile => file !== undefined);
|
|
203
|
+
return ownFiles;
|
|
204
|
+
};
|
|
151
205
|
return {
|
|
152
|
-
configFile
|
|
206
|
+
configFile,
|
|
153
207
|
program: project.program,
|
|
154
|
-
|
|
155
|
-
ownSourceFiles
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// name. Fetching each file first and filtering afterwards costs one round
|
|
160
|
-
// trip per library file the answer then discards, and a program holds far
|
|
161
|
-
// more library files than target files.
|
|
162
|
-
.filter((name) => {
|
|
163
|
-
const metadata = project.program.getSourceFileMetadata(name);
|
|
164
|
-
return (
|
|
165
|
-
metadata !== undefined && !metadata.isDefaultLibrary && !metadata.isFromExternalLibrary
|
|
166
|
-
);
|
|
167
|
-
})
|
|
168
|
-
.map((name) => project.program.getSourceFile(name))
|
|
169
|
-
.filter((file): file is SourceFile => file !== undefined);
|
|
170
|
-
return ownFiles;
|
|
208
|
+
queries,
|
|
209
|
+
ownSourceFiles,
|
|
210
|
+
ownPaths: () => {
|
|
211
|
+
ownKeys ??= new Set(ownSourceFiles().map((file) => file.path));
|
|
212
|
+
return ownKeys;
|
|
171
213
|
},
|
|
172
214
|
handle: (node) => ({ node }),
|
|
173
|
-
symbolsAt: (handles
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
215
|
+
symbolsAt: (handles, cap) =>
|
|
216
|
+
queries.symbolsAt(
|
|
217
|
+
handles.map(({ node }) => node),
|
|
218
|
+
cap,
|
|
219
|
+
),
|
|
220
|
+
resolvedSymbolAt: ({ node }) => queries.resolvedSymbol(node),
|
|
221
|
+
shorthandValueAt: ({ node }) => queries.shorthandValue(node),
|
|
177
222
|
aliasStepOf: (symbol) => {
|
|
178
223
|
// The step is asked of an alias only: asked of any other symbol, the compiler
|
|
179
224
|
// fails an assertion and the session ends.
|
|
180
225
|
if ((symbol.flags & SymbolFlags.Alias) === 0) {
|
|
181
226
|
return undefined;
|
|
182
227
|
}
|
|
183
|
-
const held =
|
|
228
|
+
const held = queries.immediateAliased(symbol);
|
|
184
229
|
// An alias that resolves to nothing answers with the checker's own unknown
|
|
185
230
|
// symbol, which declares nothing; a symbol that is no alias answers with
|
|
186
231
|
// nothing at all.
|
|
187
|
-
return held ===
|
|
232
|
+
return typeof held === "object" && held.declarations.length === 0 ? undefined : held;
|
|
188
233
|
},
|
|
189
234
|
declarationAt: (handle) => {
|
|
190
235
|
const node = handle.resolve(project);
|
|
@@ -203,11 +248,17 @@ export function narrowedTo<Brand>(
|
|
|
203
248
|
keep: (file: SourceFile) => boolean,
|
|
204
249
|
): ProjectView<Brand> {
|
|
205
250
|
let kept: readonly SourceFile[] | undefined;
|
|
251
|
+
let keys: ReadonlySet<string> | undefined;
|
|
252
|
+
const ownSourceFiles = (): readonly SourceFile[] => {
|
|
253
|
+
kept ??= project.ownSourceFiles().filter(keep);
|
|
254
|
+
return kept;
|
|
255
|
+
};
|
|
206
256
|
return {
|
|
207
257
|
...project,
|
|
208
|
-
ownSourceFiles
|
|
209
|
-
|
|
210
|
-
|
|
258
|
+
ownSourceFiles,
|
|
259
|
+
ownPaths: () => {
|
|
260
|
+
keys ??= new Set(ownSourceFiles().map((file) => file.path));
|
|
261
|
+
return keys;
|
|
211
262
|
},
|
|
212
263
|
};
|
|
213
264
|
}
|
|
@@ -218,6 +269,11 @@ export interface ProjectDiagnostics {
|
|
|
218
269
|
readonly syntactic: readonly Diagnostic[];
|
|
219
270
|
readonly semantic: readonly Diagnostic[];
|
|
220
271
|
readonly configParsing: readonly Diagnostic[];
|
|
272
|
+
/** The line and column of one offset into one of the program's files, counted from one. */
|
|
273
|
+
readonly locate: (
|
|
274
|
+
fileName: string,
|
|
275
|
+
offset: number,
|
|
276
|
+
) => { line: number; column: number } | undefined;
|
|
221
277
|
}
|
|
222
278
|
|
|
223
279
|
/** The three diagnostic sets one project carries, read before any analysis. */
|
|
@@ -227,15 +283,31 @@ export function diagnosticsOf<Brand>(project: ProjectView<Brand>): ProjectDiagno
|
|
|
227
283
|
syntactic: project.program.getSyntacticDiagnostics(),
|
|
228
284
|
semantic: project.program.getSemanticDiagnostics(),
|
|
229
285
|
configParsing: project.program.getConfigFileParsingDiagnostics(),
|
|
286
|
+
locate: (fileName, offset) => {
|
|
287
|
+
const file = project.program.getSourceFile(fileName);
|
|
288
|
+
if (file === undefined || offset < 0 || offset > file.text.length) {
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
const { line, character } = file.getLineAndCharacterOfPosition(offset);
|
|
292
|
+
return { line: line + 1, column: character + 1 };
|
|
293
|
+
},
|
|
230
294
|
};
|
|
231
295
|
}
|
|
232
296
|
|
|
233
297
|
/** One run's result: what the visitor answered for each project, and what it cost. */
|
|
234
298
|
export interface SessionResult<Result> {
|
|
299
|
+
/** One answer per configuration the snapshot opened a project for, in the order named. */
|
|
235
300
|
readonly projects: readonly Result[];
|
|
301
|
+
/** The configurations the snapshot opened no project for, in the order named. */
|
|
302
|
+
readonly unopened: readonly string[];
|
|
236
303
|
readonly timing: TimingInfo;
|
|
304
|
+
/** Every question the checker could not answer, each once, in the order first asked. */
|
|
305
|
+
readonly unanswered: readonly UnansweredQuestion[];
|
|
237
306
|
}
|
|
238
307
|
|
|
308
|
+
/** What a run does about a configuration the snapshot opened no project for. */
|
|
309
|
+
type Unopened = "refuse" | "record";
|
|
310
|
+
|
|
239
311
|
/**
|
|
240
312
|
* Runs `visit` over every project the compiler configurations name, inside one
|
|
241
313
|
* snapshot of one client, in the order they are named, and answers in that order.
|
|
@@ -244,30 +316,46 @@ export interface SessionResult<Result> {
|
|
|
244
316
|
* with its own program and checker; it is never updated, because the analysis
|
|
245
317
|
* reads and the tree does not change under it. The order the snapshot lists its
|
|
246
318
|
* projects in is the compiler's own, so each named configuration is looked up in it
|
|
247
|
-
* instead
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
319
|
+
* instead. A configuration the snapshot opened no project for ends the run under
|
|
320
|
+
* `"refuse"` and is named in the result under `"record"`. The snapshot is disposed once
|
|
321
|
+
* the last project has been visited, and the client is released after it, whether the
|
|
322
|
+
* visit completed or threw.
|
|
251
323
|
*/
|
|
252
324
|
export function runSession<Result>(
|
|
253
325
|
engine: Engine,
|
|
254
326
|
configFiles: readonly string[],
|
|
255
327
|
visit: ProjectVisitor<Result>,
|
|
328
|
+
unopened: Unopened = "refuse",
|
|
256
329
|
): SessionResult<Result> {
|
|
257
330
|
try {
|
|
258
|
-
const snapshot = engine.
|
|
331
|
+
const snapshot = engine.createSnapshot(configFiles);
|
|
259
332
|
try {
|
|
260
|
-
const opened = new Map(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
333
|
+
const opened = new Map<string, Project>();
|
|
334
|
+
for (const project of snapshot.getProjects()) {
|
|
335
|
+
if (project.configFileName !== undefined) {
|
|
336
|
+
opened.set(project.configFileName, project);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const log = new UnansweredLog();
|
|
340
|
+
const projects: Result[] = [];
|
|
341
|
+
const missing: string[] = [];
|
|
342
|
+
for (const configFile of configFiles) {
|
|
264
343
|
const project = opened.get(configFile);
|
|
265
344
|
if (project === undefined) {
|
|
266
|
-
|
|
345
|
+
if (unopened === "refuse") {
|
|
346
|
+
throw new DiscoveryError(`${configFile} opened no project`, []);
|
|
347
|
+
}
|
|
348
|
+
missing.push(configFile);
|
|
349
|
+
continue;
|
|
267
350
|
}
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
return {
|
|
351
|
+
projects.push(visit(viewOf(project, configFile, engine, log)));
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
projects,
|
|
355
|
+
unopened: missing,
|
|
356
|
+
timing: engine.getTimingInfo(),
|
|
357
|
+
unanswered: log.questions,
|
|
358
|
+
};
|
|
271
359
|
} finally {
|
|
272
360
|
snapshot.dispose();
|
|
273
361
|
}
|
package/src/suppress-file.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Host } from "./host.ts";
|
|
9
9
|
import type { InventorySymbol } from "./inventory.ts";
|
|
10
|
+
import { jsonChunks } from "./json-chunks.ts";
|
|
10
11
|
import { joinPath } from "./paths.ts";
|
|
11
12
|
import { positionAt, walkDocument } from "./json-document.ts";
|
|
12
13
|
import type { Position } from "./position.ts";
|
|
@@ -292,7 +293,10 @@ export function rowReason(provenance: Provenance): string {
|
|
|
292
293
|
* identity missing its name or version, are refused rather than written, because the
|
|
293
294
|
* grammar requires a reason on every row.
|
|
294
295
|
*/
|
|
295
|
-
export function writeBaseline(
|
|
296
|
+
export function writeBaseline(
|
|
297
|
+
findings: readonly Recorded[],
|
|
298
|
+
provenance: Provenance,
|
|
299
|
+
): Iterable<string> {
|
|
296
300
|
if (provenance.analyzer === "" || provenance.version === "") {
|
|
297
301
|
throw new Error(
|
|
298
302
|
`a baseline row would carry no reason: the analyzer identity names ${JSON.stringify(provenance.analyzer)} at version ${JSON.stringify(provenance.version)}`,
|
|
@@ -307,5 +311,5 @@ export function writeBaseline(findings: readonly Recorded[], provenance: Provena
|
|
|
307
311
|
}
|
|
308
312
|
return { code: found.code, symbol: found.symbol, path: found.path, reason };
|
|
309
313
|
});
|
|
310
|
-
return
|
|
314
|
+
return jsonChunks({ description: BASELINE_DESCRIPTION, baseline }, 2);
|
|
311
315
|
}
|
package/src/sweep.ts
CHANGED
|
@@ -51,6 +51,11 @@ export interface SweepInput {
|
|
|
51
51
|
* holds nothing back.
|
|
52
52
|
*/
|
|
53
53
|
readonly exempt?: readonly Exemption[];
|
|
54
|
+
/**
|
|
55
|
+
* The declarations a question the checker did not answer could have kept live. Each
|
|
56
|
+
* is held as an exempt declaration is. Absent, the sweep holds nothing back for one.
|
|
57
|
+
*/
|
|
58
|
+
readonly unanswered?: readonly string[];
|
|
54
59
|
readonly mode: Mode;
|
|
55
60
|
}
|
|
56
61
|
|
|
@@ -94,10 +99,10 @@ export interface Liveness {
|
|
|
94
99
|
*/
|
|
95
100
|
export function sweep(graph: Graph, input: SweepInput): Liveness {
|
|
96
101
|
const marked = flagged(graph, input.marked);
|
|
97
|
-
const exempt = flagged(
|
|
98
|
-
|
|
99
|
-
(input.
|
|
100
|
-
);
|
|
102
|
+
const exempt = flagged(graph, [
|
|
103
|
+
...(input.exempt ?? []).map((record) => record.id),
|
|
104
|
+
...(input.unanswered ?? []),
|
|
105
|
+
]);
|
|
101
106
|
const held = marked.map((mark, at) => mark || exempt[at] === true);
|
|
102
107
|
// Every root, every mark and every exemption seeds reachability. A root of a kind that
|
|
103
108
|
// names a caller, and a mark, are live under reference counting as well; a root that
|