@cplieger/deadset-ts 2.3.0 → 3.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 +7 -16
- package/dist/src/analysis.js +49 -16
- package/dist/src/components.js +289 -0
- package/dist/src/discover.js +98 -24
- package/dist/src/graph.js +116 -0
- package/dist/src/index.js +0 -2
- package/dist/src/print.js +3 -6
- package/dist/src/references.js +154 -49
- package/dist/src/resolve.js +82 -20
- package/dist/src/run.js +11 -7
- package/dist/src/schema.js +1 -1
- package/dist/src/sweep.js +156 -0
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +91 -24
- package/src/components.ts +372 -0
- package/src/config.ts +23 -2
- package/src/discover.ts +144 -29
- package/src/graph.ts +178 -0
- package/src/index.ts +0 -20
- package/src/print.ts +5 -6
- package/src/references.ts +199 -52
- package/src/resolve.ts +117 -24
- package/src/run.ts +11 -7
- package/src/schema.ts +5 -2
- package/src/sweep.ts +230 -0
- package/src/version.ts +1 -1
package/src/graph.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One project's declarations, the references between them and the roots that seed
|
|
3
|
+
* reachability, indexed for the sweep. The graph reads no file and asks the compiler
|
|
4
|
+
* nothing, so a sweep over it is a function of the values it was built from, and the
|
|
5
|
+
* inventory's order is the order every set a sweep answers reads in.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { InventorySymbol, SymbolKind } from "./inventory.ts";
|
|
9
|
+
import type { Reference } from "./references.ts";
|
|
10
|
+
import type { Root, RootKind } from "./roots.ts";
|
|
11
|
+
|
|
12
|
+
/** The position of a declaration the inventory does not hold. */
|
|
13
|
+
export const OUTSIDE = -1;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether the sweep judges a declaration of each kind. A file is not judged by either
|
|
17
|
+
* relation: whether a file is built and whether anything imports it are the subjects of
|
|
18
|
+
* the file kinds, which read the program rather than ask whether anything references the
|
|
19
|
+
* file. The Contract's finding schema names the kinds that carry no liveness relation,
|
|
20
|
+
* and a test holds this table to that list.
|
|
21
|
+
*/
|
|
22
|
+
const SWEPT: Readonly<Record<SymbolKind, boolean>> = {
|
|
23
|
+
file: false,
|
|
24
|
+
function: true,
|
|
25
|
+
class: true,
|
|
26
|
+
interface: true,
|
|
27
|
+
type: true,
|
|
28
|
+
enum: true,
|
|
29
|
+
namespace: true,
|
|
30
|
+
variable: true,
|
|
31
|
+
"export-alias": true,
|
|
32
|
+
method: true,
|
|
33
|
+
"class-member": true,
|
|
34
|
+
"interface-method": true,
|
|
35
|
+
"type-member": true,
|
|
36
|
+
"enum-member": true,
|
|
37
|
+
"type-parameter": true,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether a root of each kind names a caller, which holds it live under both relations: a
|
|
42
|
+
* tool reads the configuration it loads, a runtime runs a test file, a setup file and a
|
|
43
|
+
* worker, and a configured root asserts a caller. The other kinds only suppose a consumer
|
|
44
|
+
* that imports what a manifest entry, a binary or an entry file exports, or names a
|
|
45
|
+
* library's published API, so such a root is live under reachability alone.
|
|
46
|
+
*/
|
|
47
|
+
const NAMES_A_CALLER: Readonly<Record<RootKind, boolean>> = {
|
|
48
|
+
"entry-file": false,
|
|
49
|
+
"manifest-entry": false,
|
|
50
|
+
"manifest-binary": false,
|
|
51
|
+
"published-api": false,
|
|
52
|
+
configured: true,
|
|
53
|
+
pattern: true,
|
|
54
|
+
"test-runner": true,
|
|
55
|
+
"lint-configuration": true,
|
|
56
|
+
"mutation-testing": true,
|
|
57
|
+
"browser-tests": true,
|
|
58
|
+
worker: true,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Whether the sweep judges a declaration of one kind. */
|
|
62
|
+
export function swept(kind: SymbolKind): boolean {
|
|
63
|
+
return SWEPT[kind];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Whether a root of one kind names a caller rather than supposing one. */
|
|
67
|
+
export function namesACaller(kind: RootKind): boolean {
|
|
68
|
+
return NAMES_A_CALLER[kind];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** One reference as the sweep reads it: the declaration named, and whether a test file made it. */
|
|
72
|
+
export interface Edge {
|
|
73
|
+
readonly to: number;
|
|
74
|
+
readonly test: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** How many references one declaration carries, split by the classification of the file that made each. */
|
|
78
|
+
export interface Counts {
|
|
79
|
+
readonly production: number;
|
|
80
|
+
readonly test: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** One root the inventory holds, at the position of the declaration it names. */
|
|
84
|
+
export interface Rooted {
|
|
85
|
+
readonly at: number;
|
|
86
|
+
readonly kind: RootKind;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The index the sweep and the component pass read. */
|
|
90
|
+
export interface Graph {
|
|
91
|
+
/** The declarations, in the order the inventory returns them. */
|
|
92
|
+
readonly symbols: readonly InventorySymbol[];
|
|
93
|
+
/** Per declaration, the references it makes to declarations the inventory holds. */
|
|
94
|
+
readonly out: readonly (readonly Edge[])[];
|
|
95
|
+
/** Per declaration, the references made to it, whoever made them. */
|
|
96
|
+
readonly made: readonly Counts[];
|
|
97
|
+
/** Per declaration, the position of its container, or {@link OUTSIDE} for a file. */
|
|
98
|
+
readonly parent: readonly number[];
|
|
99
|
+
/** Per declaration, whether a test file holds it. */
|
|
100
|
+
readonly test: readonly boolean[];
|
|
101
|
+
/** Per declaration, whether the sweep judges its liveness. */
|
|
102
|
+
readonly subject: readonly boolean[];
|
|
103
|
+
/**
|
|
104
|
+
* Per declaration, the declarations its export table names where it is a file, and
|
|
105
|
+
* nothing for any other declaration. A reference to a file is a reference to the
|
|
106
|
+
* module's namespace, which reaches everything the module exports.
|
|
107
|
+
*/
|
|
108
|
+
readonly exportsOf: readonly (readonly number[])[];
|
|
109
|
+
/** Every root naming a declaration the inventory holds, in the order the root set gives them. */
|
|
110
|
+
readonly rooted: readonly Rooted[];
|
|
111
|
+
/** The position of the declaration one identifier names, or {@link OUTSIDE}. */
|
|
112
|
+
at(id: string): number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Indexes one project's inventory, references and roots. `testFiles` are the paths the
|
|
117
|
+
* reference pass classified as test files, so a declaration one holds is a test
|
|
118
|
+
* declaration. A reference to a declaration the inventory does not hold is dropped; one
|
|
119
|
+
* from such a declaration still counts toward its target, because reference counting
|
|
120
|
+
* asks whether any declaration names the target. A root naming nothing is dropped.
|
|
121
|
+
*/
|
|
122
|
+
export function graphOf(
|
|
123
|
+
symbols: readonly InventorySymbol[],
|
|
124
|
+
references: readonly Reference[],
|
|
125
|
+
roots: readonly Root[],
|
|
126
|
+
testFiles: readonly string[],
|
|
127
|
+
): Graph {
|
|
128
|
+
const index = new Map(symbols.map((symbol, at) => [symbol.id, at]));
|
|
129
|
+
const at = (id: string): number => index.get(id) ?? OUTSIDE;
|
|
130
|
+
const tests = new Set(testFiles);
|
|
131
|
+
|
|
132
|
+
const out: Edge[][] = symbols.map(() => []);
|
|
133
|
+
const made = symbols.map(() => ({ production: 0, test: 0 }));
|
|
134
|
+
for (const reference of references) {
|
|
135
|
+
const to = at(reference.to);
|
|
136
|
+
if (to === OUTSIDE) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const counts = made[to];
|
|
140
|
+
if (counts !== undefined) {
|
|
141
|
+
if (reference.test) {
|
|
142
|
+
counts.test += 1;
|
|
143
|
+
} else {
|
|
144
|
+
counts.production += 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
out[at(reference.from)]?.push({ to, test: reference.test });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const parent = symbols.map((symbol) => at(symbol.parent));
|
|
151
|
+
const exportsOf: number[][] = symbols.map(() => []);
|
|
152
|
+
symbols.forEach((symbol, position) => {
|
|
153
|
+
const container = parent[position] ?? OUTSIDE;
|
|
154
|
+
if (symbol.exported && symbols[container]?.kind === "file") {
|
|
155
|
+
exportsOf[container]?.push(position);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const rooted: Rooted[] = [];
|
|
160
|
+
for (const root of roots) {
|
|
161
|
+
const position = at(root.id);
|
|
162
|
+
if (position !== OUTSIDE) {
|
|
163
|
+
rooted.push({ at: position, kind: root.kind });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
symbols,
|
|
169
|
+
out,
|
|
170
|
+
made,
|
|
171
|
+
parent,
|
|
172
|
+
test: symbols.map((symbol) => tests.has(symbol.position.path)),
|
|
173
|
+
subject: symbols.map((symbol) => swept(symbol.kind)),
|
|
174
|
+
exportsOf,
|
|
175
|
+
rooted,
|
|
176
|
+
at,
|
|
177
|
+
};
|
|
178
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,26 +1,6 @@
|
|
|
1
1
|
export { run, SETTING_OPTIONS, type Writer } from "./run.ts";
|
|
2
2
|
export type { DirectoryEntry, Host, PathKind } from "./host.ts";
|
|
3
3
|
export { DECLINED_CONVENTIONS, type DeclinedConvention } from "./entry-point-gaps.ts";
|
|
4
|
-
export {
|
|
5
|
-
inventory,
|
|
6
|
-
nodeKey,
|
|
7
|
-
type Inventory,
|
|
8
|
-
type InventoryCost,
|
|
9
|
-
type InventorySymbol,
|
|
10
|
-
type SymbolKind,
|
|
11
|
-
type Visibility,
|
|
12
|
-
} from "./inventory.ts";
|
|
13
|
-
export {
|
|
14
|
-
DEFAULT_BATCH_CAP,
|
|
15
|
-
references,
|
|
16
|
-
type Reference,
|
|
17
|
-
type ReferenceCost,
|
|
18
|
-
type ReferenceOptions,
|
|
19
|
-
type References,
|
|
20
|
-
type Resolution,
|
|
21
|
-
type TestFileRule,
|
|
22
|
-
type Use,
|
|
23
|
-
} from "./references.ts";
|
|
24
4
|
export {
|
|
25
5
|
byPosition,
|
|
26
6
|
positionKey,
|
package/src/print.ts
CHANGED
|
@@ -20,12 +20,11 @@ export function printConfig(config: Config, provenance: Provenance): string {
|
|
|
20
20
|
min_confidence: config.analysis.minConfidence,
|
|
21
21
|
generated_files: config.analysis.generatedFiles,
|
|
22
22
|
consumer_tests: config.analysis.consumerTests,
|
|
23
|
-
configurations: config.analysis.configurations.map((entry) =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
})),
|
|
23
|
+
configurations: config.analysis.configurations.map((entry) =>
|
|
24
|
+
entry.shape === "project"
|
|
25
|
+
? { id: entry.id, project: entry.project }
|
|
26
|
+
: { id: entry.id, os: entry.os, arch: entry.arch, tags: entry.tags },
|
|
27
|
+
),
|
|
29
28
|
matrix: { complete: config.analysis.matrixComplete },
|
|
30
29
|
template_dirs: config.analysis.templateDirs,
|
|
31
30
|
template_delimiters: {
|
package/src/references.ts
CHANGED
|
@@ -3,8 +3,10 @@ import {
|
|
|
3
3
|
isBinaryExpression,
|
|
4
4
|
isDeleteExpression,
|
|
5
5
|
isElementAccessExpression,
|
|
6
|
+
isExportDeclaration,
|
|
6
7
|
isForInStatement,
|
|
7
8
|
isForOfStatement,
|
|
9
|
+
isNamespaceExport,
|
|
8
10
|
isObjectLiteralExpression,
|
|
9
11
|
isParenthesizedExpression,
|
|
10
12
|
isPostfixUnaryExpression,
|
|
@@ -110,6 +112,12 @@ export interface References {
|
|
|
110
112
|
readonly references: readonly Reference[];
|
|
111
113
|
/** The rules that classified files as test files, ordered by rule. */
|
|
112
114
|
readonly testFileRules: readonly TestFileRule[];
|
|
115
|
+
/**
|
|
116
|
+
* Every file the rules classified as a test file, by its path below the target root,
|
|
117
|
+
* ascending. A declaration such a file holds is a test declaration, by the same
|
|
118
|
+
* classification that marks the references the file makes.
|
|
119
|
+
*/
|
|
120
|
+
readonly testFilePaths: readonly string[];
|
|
113
121
|
readonly cost: ReferenceCost;
|
|
114
122
|
}
|
|
115
123
|
|
|
@@ -273,22 +281,88 @@ interface Site {
|
|
|
273
281
|
readonly use: Use;
|
|
274
282
|
}
|
|
275
283
|
|
|
284
|
+
/**
|
|
285
|
+
* One re-export the walk found, which names what it carries forward without a name
|
|
286
|
+
* that is a use: an export specifier, a namespace export, or a bare star re-export.
|
|
287
|
+
*/
|
|
288
|
+
interface Link {
|
|
289
|
+
/**
|
|
290
|
+
* The node whose symbol says what the re-export carries: the alias a specifier or a
|
|
291
|
+
* namespace export declares, or the module a bare star re-export names.
|
|
292
|
+
*/
|
|
293
|
+
readonly node: Node;
|
|
294
|
+
/** Where the reference is written: the name a specifier re-exports, or the module specifier. */
|
|
295
|
+
readonly at: Node;
|
|
296
|
+
/** The re-export's own declaration, or for a bare star the declaration enclosing it. */
|
|
297
|
+
readonly from: string;
|
|
298
|
+
/** Whether the node's symbol is an alias, which is followed one link along. */
|
|
299
|
+
readonly alias: boolean;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Everything one file's walk found to resolve. */
|
|
303
|
+
interface FileSites {
|
|
304
|
+
readonly uses: readonly Site[];
|
|
305
|
+
readonly links: readonly Link[];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The re-exports one node writes, if it is an export declaration. A specifier and a
|
|
310
|
+
* namespace export are declarations whose names are where they are written, so no use is
|
|
311
|
+
* read from them; each references what it carries forward instead. A bare star declares
|
|
312
|
+
* nothing, so the declaration enclosing it, at the top level the file, references the
|
|
313
|
+
* module it names.
|
|
314
|
+
*/
|
|
315
|
+
function linksOf(
|
|
316
|
+
file: SourceFile,
|
|
317
|
+
node: Node,
|
|
318
|
+
declarations: ReadonlyMap<string, string>,
|
|
319
|
+
enclosing: string,
|
|
320
|
+
): Link[] {
|
|
321
|
+
if (!isExportDeclaration(node)) {
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
const clause = node.exportClause;
|
|
325
|
+
const specifier = node.moduleSpecifier;
|
|
326
|
+
if (clause === undefined) {
|
|
327
|
+
return specifier === undefined
|
|
328
|
+
? []
|
|
329
|
+
: [{ node: specifier, at: specifier, from: enclosing, alias: false }];
|
|
330
|
+
}
|
|
331
|
+
if (isNamespaceExport(clause)) {
|
|
332
|
+
const from = declarations.get(nodeKey(file, clause));
|
|
333
|
+
return from === undefined
|
|
334
|
+
? []
|
|
335
|
+
: [{ node: clause.name, at: specifier ?? clause.name, from, alias: true }];
|
|
336
|
+
}
|
|
337
|
+
const found: Link[] = [];
|
|
338
|
+
for (const element of clause.elements) {
|
|
339
|
+
const from = declarations.get(nodeKey(file, element));
|
|
340
|
+
if (from !== undefined) {
|
|
341
|
+
found.push({
|
|
342
|
+
node: element.name,
|
|
343
|
+
at: element.propertyName ?? element.name,
|
|
344
|
+
from,
|
|
345
|
+
alias: true,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return found;
|
|
350
|
+
}
|
|
351
|
+
|
|
276
352
|
/**
|
|
277
353
|
* Every name node one file holds that is a use rather than a declaration, in source
|
|
278
|
-
* order, each with
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
* declared name of the declaration it belongs to is left out: it is where that
|
|
283
|
-
* declaration is written rather than a use of it, which is what makes a declaration
|
|
284
|
-
* referenced only from its own site an unreferenced declaration.
|
|
354
|
+
* order, each with its enclosing declaration and its use, and every re-export the file
|
|
355
|
+
* writes. One walk answers all of it, because a second would have to agree with this one
|
|
356
|
+
* about which nodes are names. A declaration's own declared name is left out, which is
|
|
357
|
+
* what makes a declaration referenced only from its own site an unreferenced one.
|
|
285
358
|
*/
|
|
286
359
|
function sitesOf(
|
|
287
360
|
file: SourceFile,
|
|
288
361
|
declarations: ReadonlyMap<string, string>,
|
|
289
362
|
fileId: string,
|
|
290
|
-
):
|
|
363
|
+
): FileSites {
|
|
291
364
|
const found: Site[] = [];
|
|
365
|
+
const links: Link[] = [];
|
|
292
366
|
const writes = new Set<number>();
|
|
293
367
|
const declared = new Set<number>();
|
|
294
368
|
const shorthands = new Map<number, Node>();
|
|
@@ -326,6 +400,7 @@ function sitesOf(
|
|
|
326
400
|
}
|
|
327
401
|
markNames(node);
|
|
328
402
|
markStores(node, writes);
|
|
403
|
+
links.push(...linksOf(file, node, declarations, enclosing));
|
|
329
404
|
const held = declarations.get(nodeKey(file, node));
|
|
330
405
|
const outer = enclosing;
|
|
331
406
|
if (held !== undefined) {
|
|
@@ -336,7 +411,7 @@ function sitesOf(
|
|
|
336
411
|
};
|
|
337
412
|
|
|
338
413
|
file.forEachChild(visit);
|
|
339
|
-
return found;
|
|
414
|
+
return { uses: found, links };
|
|
340
415
|
}
|
|
341
416
|
|
|
342
417
|
/**
|
|
@@ -386,7 +461,9 @@ export function references<Brand>(
|
|
|
386
461
|
const patterns = options.testFiles.map(globExpression);
|
|
387
462
|
const matched = options.testFiles.map(() => 0);
|
|
388
463
|
const found: Reference[] = [];
|
|
464
|
+
const tests: string[] = [];
|
|
389
465
|
const reached = new Map<number, readonly Target[]>();
|
|
466
|
+
const steps = new Map<number, TSSymbol | undefined>();
|
|
390
467
|
let batched = 0;
|
|
391
468
|
let fileBatches = 0;
|
|
392
469
|
let residueFallbacks = 0;
|
|
@@ -400,23 +477,53 @@ export function references<Brand>(
|
|
|
400
477
|
const declaredAt = (node: Node): string | undefined =>
|
|
401
478
|
declarations.get(nodeKey(node.getSourceFile(), node));
|
|
402
479
|
|
|
480
|
+
/**
|
|
481
|
+
* The declarations of the inventory one symbol is.
|
|
482
|
+
*
|
|
483
|
+
* A declaration handle carries the file it is in, so the question is answered before
|
|
484
|
+
* the handle is resolved: a declaration outside the project's own files belongs to
|
|
485
|
+
* another program and is never read, which is what keeps the pass from fetching a
|
|
486
|
+
* library file to discover that the inventory does not hold what is in it.
|
|
487
|
+
*/
|
|
488
|
+
const declarationsOf = (symbol: TSSymbol): string[] => {
|
|
489
|
+
const ids: string[] = [];
|
|
490
|
+
for (const handle of symbol.declarations) {
|
|
491
|
+
if (!ownFiles.has(handle.path)) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const node = project.declarationAt(handle)?.node;
|
|
495
|
+
const id = node === undefined ? undefined : declaredAt(node);
|
|
496
|
+
if (id !== undefined && !ids.includes(id)) {
|
|
497
|
+
ids.push(id);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return ids;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* What one alias names, one link along, asked once per alias whatever the number of
|
|
505
|
+
* chains it is a link of. Only a symbol the binder flagged an alias is asked, which is
|
|
506
|
+
* the condition the step asserts on: a default export of an expression is written in
|
|
507
|
+
* an alias form and names nothing declared elsewhere, so its chain ends at itself.
|
|
508
|
+
*/
|
|
509
|
+
const stepOf = (symbol: TSSymbol): TSSymbol | undefined => {
|
|
510
|
+
if ((symbol.flags & SymbolFlags.Alias) === 0) {
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
if (steps.has(symbol.id)) {
|
|
514
|
+
return steps.get(symbol.id);
|
|
515
|
+
}
|
|
516
|
+
const next = project.aliasStepOf(symbol);
|
|
517
|
+
aliasSteps += 1;
|
|
518
|
+
steps.set(symbol.id, next);
|
|
519
|
+
return next;
|
|
520
|
+
};
|
|
521
|
+
|
|
403
522
|
/**
|
|
404
523
|
* The declarations one symbol names: the declaration it is, and every link of the
|
|
405
524
|
* alias chain it stands at the head of. A re-export something imports is used and so
|
|
406
525
|
* is the declaration behind it, so both are named; a link this project does not
|
|
407
526
|
* declare is stepped over rather than recorded.
|
|
408
|
-
*
|
|
409
|
-
* A symbol is asked to step only where the binder flagged it an alias, which is the
|
|
410
|
-
* same condition the step itself asserts on. The declaration's syntax is not that
|
|
411
|
-
* condition and does not stand in for it: a default export of an expression is
|
|
412
|
-
* written as one of the alias forms and names nothing declared elsewhere, so it is
|
|
413
|
-
* recorded as the declaration it is and the chain ends there.
|
|
414
|
-
*
|
|
415
|
-
* A declaration handle carries the file it is in, so the question the walk asks of a
|
|
416
|
-
* declaration is answered before it is resolved: a declaration outside the project's
|
|
417
|
-
* own files belongs to another program and is never read, which is what keeps the
|
|
418
|
-
* pass from fetching a library file to discover that the inventory does not hold what
|
|
419
|
-
* is in it.
|
|
420
527
|
*/
|
|
421
528
|
const targetsOf = (symbol: TSSymbol): readonly Target[] => {
|
|
422
529
|
const cached = reached.get(symbol.id);
|
|
@@ -429,35 +536,49 @@ export function references<Brand>(
|
|
|
429
536
|
let stepped = false;
|
|
430
537
|
while (at !== undefined && !walked.has(at.id)) {
|
|
431
538
|
walked.add(at.id);
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
if (!ownFiles.has(handle.path)) {
|
|
435
|
-
continue;
|
|
436
|
-
}
|
|
437
|
-
const node = project.declarationAt(handle)?.node;
|
|
438
|
-
const id = node === undefined ? undefined : declaredAt(node);
|
|
439
|
-
if (id !== undefined && !targets.some((target) => target.id === id)) {
|
|
539
|
+
for (const id of declarationsOf(at)) {
|
|
540
|
+
if (!targets.some((target) => target.id === id)) {
|
|
440
541
|
targets.push({ id, stepped });
|
|
441
542
|
}
|
|
442
543
|
}
|
|
443
|
-
|
|
444
|
-
break;
|
|
445
|
-
}
|
|
446
|
-
at = project.aliasStepOf(at);
|
|
447
|
-
aliasSteps += 1;
|
|
544
|
+
at = stepOf(at);
|
|
448
545
|
stepped = true;
|
|
449
546
|
}
|
|
450
547
|
reached.set(symbol.id, targets);
|
|
451
548
|
return targets;
|
|
452
549
|
};
|
|
453
550
|
|
|
551
|
+
/**
|
|
552
|
+
* The declarations one re-export carries forward: for an alias, the first link along
|
|
553
|
+
* its chain that the inventory declares, stepping over the links it does not, such as
|
|
554
|
+
* an import the re-export is written against; for a bare star, the module it names.
|
|
555
|
+
* Each later link is a declaration of its own that references the next, so one link
|
|
556
|
+
* per re-export is what lets a chain be followed one declaration at a time.
|
|
557
|
+
*/
|
|
558
|
+
const carriedBy = (link: Link, symbol: TSSymbol): readonly string[] => {
|
|
559
|
+
if (!link.alias) {
|
|
560
|
+
return declarationsOf(symbol);
|
|
561
|
+
}
|
|
562
|
+
const walked = new Set<number>([symbol.id]);
|
|
563
|
+
let at = stepOf(symbol);
|
|
564
|
+
while (at !== undefined && !walked.has(at.id)) {
|
|
565
|
+
walked.add(at.id);
|
|
566
|
+
const ids = declarationsOf(at);
|
|
567
|
+
if (ids.length > 0) {
|
|
568
|
+
return ids;
|
|
569
|
+
}
|
|
570
|
+
at = stepOf(at);
|
|
571
|
+
}
|
|
572
|
+
return [];
|
|
573
|
+
};
|
|
574
|
+
|
|
454
575
|
/** The symbol one site resolves to, asking only the accessor the site's form names. */
|
|
455
|
-
const symbolFor = (site: Site, answered: ReadonlyMap<
|
|
576
|
+
const symbolFor = (site: Site, answered: ReadonlyMap<Node, TSSymbol>): TSSymbol | undefined => {
|
|
456
577
|
if (site.shorthand !== undefined) {
|
|
457
578
|
shorthandLookups += 1;
|
|
458
579
|
return project.shorthandValueAt(project.handle(site.shorthand));
|
|
459
580
|
}
|
|
460
|
-
const batched = answered.get(site.node
|
|
581
|
+
const batched = answered.get(site.node);
|
|
461
582
|
if (batched !== undefined) {
|
|
462
583
|
return batched;
|
|
463
584
|
}
|
|
@@ -467,38 +588,63 @@ export function references<Brand>(
|
|
|
467
588
|
|
|
468
589
|
for (const file of files) {
|
|
469
590
|
const path = renderPosition(file, targetRoot, 0).path;
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (
|
|
591
|
+
const hits = patterns.map((pattern) => pattern.test(path));
|
|
592
|
+
hits.forEach((hit, index) => {
|
|
593
|
+
if (hit) {
|
|
473
594
|
matched[index] = (matched[index] ?? 0) + 1;
|
|
474
|
-
test = true;
|
|
475
595
|
}
|
|
476
596
|
});
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
597
|
+
const test = hits.includes(true);
|
|
598
|
+
if (test) {
|
|
599
|
+
tests.push(path);
|
|
600
|
+
}
|
|
601
|
+
const { uses, links } = sitesOf(file, declarations, declaredAt(file) ?? "");
|
|
602
|
+
|
|
603
|
+
// One batch per capped run of the file's name nodes, the re-exports' nodes after
|
|
604
|
+
// the uses. A shorthand is left out of the batch: its name resolves to the property
|
|
605
|
+
// the literal declares, so the batch's answer for it would name something this
|
|
606
|
+
// project's inventory never holds.
|
|
607
|
+
const batching = [
|
|
608
|
+
...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
|
|
609
|
+
...links.map((link) => link.node),
|
|
610
|
+
];
|
|
611
|
+
const answered = new Map<Node, TSSymbol>();
|
|
484
612
|
batched += batching.length;
|
|
485
613
|
for (let from = 0; from < batching.length; from += cap) {
|
|
486
614
|
const run = batching.slice(from, from + cap);
|
|
487
|
-
const answers = project.symbolsAt(run.map((
|
|
615
|
+
const answers = project.symbolsAt(run.map((node) => project.handle(node)));
|
|
488
616
|
fileBatches += 1;
|
|
489
|
-
run.forEach((
|
|
617
|
+
run.forEach((node, index) => {
|
|
490
618
|
const symbol = answers[index];
|
|
491
619
|
if (symbol !== undefined) {
|
|
492
|
-
answered.set(
|
|
620
|
+
answered.set(node, symbol);
|
|
493
621
|
}
|
|
494
622
|
});
|
|
495
623
|
}
|
|
496
624
|
|
|
497
|
-
for (const
|
|
625
|
+
for (const link of links) {
|
|
626
|
+
const symbol = answered.get(link.node);
|
|
627
|
+
if (symbol === undefined) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
const position = renderPosition(file, targetRoot, link.at.getStart());
|
|
631
|
+
for (const id of carriedBy(link, symbol)) {
|
|
632
|
+
found.push({
|
|
633
|
+
from: link.from,
|
|
634
|
+
to: id,
|
|
635
|
+
position,
|
|
636
|
+
use: "read",
|
|
637
|
+
resolution: link.alias ? "alias" : "batch",
|
|
638
|
+
test,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
for (const site of uses) {
|
|
498
644
|
const direct: Resolution =
|
|
499
645
|
site.shorthand !== undefined
|
|
500
646
|
? "shorthand"
|
|
501
|
-
: answered.has(site.node
|
|
647
|
+
: answered.has(site.node)
|
|
502
648
|
? "batch"
|
|
503
649
|
: "resolved-symbol";
|
|
504
650
|
const symbol = symbolFor(site, answered);
|
|
@@ -533,6 +679,7 @@ export function references<Brand>(
|
|
|
533
679
|
testFileRules: options.testFiles
|
|
534
680
|
.map((_pattern, index) => ({ rule: patternRule(index), matched: matched[index] ?? 0 }))
|
|
535
681
|
.sort((a, b) => compare(a.rule, b.rule)),
|
|
682
|
+
testFilePaths: tests.sort(compare),
|
|
536
683
|
cost: { batched, fileBatches, residueFallbacks, shorthandLookups, aliasSteps },
|
|
537
684
|
};
|
|
538
685
|
}
|