@cplieger/deadset-ts 2.3.0 → 3.1.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/alias-chain.js +96 -0
- package/dist/src/analysis.js +50 -16
- package/dist/src/components.js +289 -0
- package/dist/src/discover.js +98 -24
- package/dist/src/graph.js +120 -0
- package/dist/src/index.js +0 -2
- package/dist/src/matrix.js +138 -0
- package/dist/src/print.js +3 -6
- package/dist/src/references.js +162 -79
- package/dist/src/resolve.js +82 -20
- package/dist/src/roots.js +14 -37
- package/dist/src/run.js +11 -7
- package/dist/src/schema.js +1 -1
- package/dist/src/session.js +16 -5
- package/dist/src/sweep.js +152 -0
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alias-chain.ts +135 -0
- package/src/analysis.ts +85 -24
- package/src/components.ts +372 -0
- package/src/config.ts +23 -2
- package/src/discover.ts +144 -29
- package/src/graph.ts +187 -0
- package/src/index.ts +0 -20
- package/src/matrix.ts +212 -0
- package/src/print.ts +5 -6
- package/src/references.ts +235 -93
- package/src/resolve.ts +117 -24
- package/src/roots.ts +15 -46
- package/src/run.ts +11 -7
- package/src/schema.ts +5 -2
- package/src/session.ts +18 -5
- package/src/sweep.ts +224 -0
- package/src/version.ts +1 -1
package/src/references.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isArrayLiteralExpression,
|
|
3
3
|
isBinaryExpression,
|
|
4
|
+
isCallExpression,
|
|
4
5
|
isDeleteExpression,
|
|
5
6
|
isElementAccessExpression,
|
|
7
|
+
isExportDeclaration,
|
|
8
|
+
isExternalModuleReference,
|
|
6
9
|
isForInStatement,
|
|
7
10
|
isForOfStatement,
|
|
11
|
+
isImportDeclaration,
|
|
12
|
+
isImportEqualsDeclaration,
|
|
13
|
+
isNamespaceExport,
|
|
14
|
+
isNoSubstitutionTemplateLiteral,
|
|
8
15
|
isObjectLiteralExpression,
|
|
9
16
|
isParenthesizedExpression,
|
|
10
17
|
isPostfixUnaryExpression,
|
|
@@ -14,11 +21,13 @@ import {
|
|
|
14
21
|
isShorthandPropertyAssignment,
|
|
15
22
|
isSpreadAssignment,
|
|
16
23
|
isSpreadElement,
|
|
24
|
+
isStringLiteral,
|
|
17
25
|
SyntaxKind,
|
|
18
26
|
type Node,
|
|
19
27
|
type SourceFile,
|
|
20
28
|
} from "@typescript/native/unstable/ast";
|
|
21
|
-
import {
|
|
29
|
+
import type { Symbol as TSSymbol } from "@typescript/native/unstable/sync";
|
|
30
|
+
import { aliasChains } from "./alias-chain.ts";
|
|
22
31
|
import { globExpression } from "./glob.ts";
|
|
23
32
|
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
24
33
|
import { byPosition, renderPosition, type Position } from "./position.ts";
|
|
@@ -35,13 +44,18 @@ import type { ProjectView } from "./session.ts";
|
|
|
35
44
|
*
|
|
36
45
|
* What the pass records and what it does not: a reference carries its use, its
|
|
37
46
|
* position, the declaration that encloses it and the classification of the file that
|
|
38
|
-
* made it
|
|
39
|
-
*
|
|
40
|
-
*
|
|
47
|
+
* made it, and every place that imports one of the project's own modules is an
|
|
48
|
+
* evaluation of that module's file. It carries no mode. Which of the references a run
|
|
49
|
+
* counts is the reading the sweep makes of this set, so the mode is one value the run
|
|
50
|
+
* decides once and this pass never sees it.
|
|
41
51
|
*/
|
|
42
52
|
|
|
43
|
-
/**
|
|
44
|
-
|
|
53
|
+
/**
|
|
54
|
+
* How one reference uses the declaration it names. An `evaluation` names a file: the
|
|
55
|
+
* place it is written imports the module, which runs the module's top level and reads
|
|
56
|
+
* none of what it exports. A `read` of a file reads the module's namespace.
|
|
57
|
+
*/
|
|
58
|
+
export type Use = "read" | "write" | "evaluation";
|
|
45
59
|
|
|
46
60
|
/**
|
|
47
61
|
* Which accessor answered for one reference, so that the cost of a run is attributable
|
|
@@ -110,6 +124,12 @@ export interface References {
|
|
|
110
124
|
readonly references: readonly Reference[];
|
|
111
125
|
/** The rules that classified files as test files, ordered by rule. */
|
|
112
126
|
readonly testFileRules: readonly TestFileRule[];
|
|
127
|
+
/**
|
|
128
|
+
* Every file the rules classified as a test file, by its path below the target root,
|
|
129
|
+
* ascending. A declaration such a file holds is a test declaration, by the same
|
|
130
|
+
* classification that marks the references the file makes.
|
|
131
|
+
*/
|
|
132
|
+
readonly testFilePaths: readonly string[];
|
|
113
133
|
readonly cost: ReferenceCost;
|
|
114
134
|
}
|
|
115
135
|
|
|
@@ -273,22 +293,131 @@ interface Site {
|
|
|
273
293
|
readonly use: Use;
|
|
274
294
|
}
|
|
275
295
|
|
|
296
|
+
/**
|
|
297
|
+
* One re-export the walk found, which names what it carries forward without a name
|
|
298
|
+
* that is a use: an export specifier, a namespace export, or a bare star re-export.
|
|
299
|
+
*/
|
|
300
|
+
interface Link {
|
|
301
|
+
/**
|
|
302
|
+
* The node whose symbol says what the re-export carries: the alias a specifier or a
|
|
303
|
+
* namespace export declares, or the module a bare star re-export names.
|
|
304
|
+
*/
|
|
305
|
+
readonly node: Node;
|
|
306
|
+
/** Where the reference is written: the name a specifier re-exports, or the module specifier. */
|
|
307
|
+
readonly at: Node;
|
|
308
|
+
/** The re-export's own declaration, or for a bare star the declaration enclosing it. */
|
|
309
|
+
readonly from: string;
|
|
310
|
+
/** Whether the node's symbol is an alias, which is followed one link along. */
|
|
311
|
+
readonly alias: boolean;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** One place the walk found that evaluates a module, and the declaration that holds it. */
|
|
315
|
+
interface Evaluation {
|
|
316
|
+
/** The module specifier, whose symbol is the module evaluated. */
|
|
317
|
+
readonly node: Node;
|
|
318
|
+
readonly from: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Everything one file's walk found to resolve. */
|
|
322
|
+
interface FileSites {
|
|
323
|
+
readonly uses: readonly Site[];
|
|
324
|
+
readonly links: readonly Link[];
|
|
325
|
+
readonly evaluations: readonly Evaluation[];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Whether one node is a string literal or a template literal with no substitution. */
|
|
329
|
+
function isLiteralText(node: Node | undefined): node is Node {
|
|
330
|
+
return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* The module specifier of the module one node evaluates, if it evaluates one: an import
|
|
335
|
+
* declaration, an import-equals declaration of an external module, an export
|
|
336
|
+
* declaration that names a module, and an `import()` call whose argument is literal
|
|
337
|
+
* text. A type-only form evaluates nothing. An `import()` of a computed argument names
|
|
338
|
+
* no module this pass can know.
|
|
339
|
+
*/
|
|
340
|
+
function evaluatedBy(node: Node): Node | undefined {
|
|
341
|
+
if (isImportDeclaration(node)) {
|
|
342
|
+
return node.importClause?.phaseModifier === SyntaxKind.TypeKeyword
|
|
343
|
+
? undefined
|
|
344
|
+
: node.moduleSpecifier;
|
|
345
|
+
}
|
|
346
|
+
if (isExportDeclaration(node)) {
|
|
347
|
+
return node.isTypeOnly ? undefined : node.moduleSpecifier;
|
|
348
|
+
}
|
|
349
|
+
if (isImportEqualsDeclaration(node)) {
|
|
350
|
+
const reference = node.moduleReference;
|
|
351
|
+
return !node.isTypeOnly && isExternalModuleReference(reference)
|
|
352
|
+
? reference.expression
|
|
353
|
+
: undefined;
|
|
354
|
+
}
|
|
355
|
+
if (isCallExpression(node) && node.expression.kind === SyntaxKind.ImportKeyword) {
|
|
356
|
+
const [argument] = node.arguments;
|
|
357
|
+
return isLiteralText(argument) ? argument : undefined;
|
|
358
|
+
}
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* The re-exports one node writes, if it is an export declaration. A specifier and a
|
|
364
|
+
* namespace export are declarations whose names are where they are written, so no use is
|
|
365
|
+
* read from them; each references what it carries forward instead. A bare star declares
|
|
366
|
+
* nothing, so the declaration enclosing it, at the top level the file, references the
|
|
367
|
+
* module it names.
|
|
368
|
+
*/
|
|
369
|
+
function linksOf(
|
|
370
|
+
file: SourceFile,
|
|
371
|
+
node: Node,
|
|
372
|
+
declarations: ReadonlyMap<string, string>,
|
|
373
|
+
enclosing: string,
|
|
374
|
+
): Link[] {
|
|
375
|
+
if (!isExportDeclaration(node)) {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
const clause = node.exportClause;
|
|
379
|
+
const specifier = node.moduleSpecifier;
|
|
380
|
+
if (clause === undefined) {
|
|
381
|
+
return specifier === undefined
|
|
382
|
+
? []
|
|
383
|
+
: [{ node: specifier, at: specifier, from: enclosing, alias: false }];
|
|
384
|
+
}
|
|
385
|
+
if (isNamespaceExport(clause)) {
|
|
386
|
+
const from = declarations.get(nodeKey(file, clause));
|
|
387
|
+
return from === undefined
|
|
388
|
+
? []
|
|
389
|
+
: [{ node: clause.name, at: specifier ?? clause.name, from, alias: true }];
|
|
390
|
+
}
|
|
391
|
+
const found: Link[] = [];
|
|
392
|
+
for (const element of clause.elements) {
|
|
393
|
+
const from = declarations.get(nodeKey(file, element));
|
|
394
|
+
if (from !== undefined) {
|
|
395
|
+
found.push({
|
|
396
|
+
node: element.name,
|
|
397
|
+
at: element.propertyName ?? element.name,
|
|
398
|
+
from,
|
|
399
|
+
alias: true,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return found;
|
|
404
|
+
}
|
|
405
|
+
|
|
276
406
|
/**
|
|
277
407
|
* 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.
|
|
408
|
+
* order, each with its enclosing declaration and its use, and every re-export the file
|
|
409
|
+
* writes. One walk answers all of it, because a second would have to agree with this one
|
|
410
|
+
* about which nodes are names. A declaration's own declared name is left out, which is
|
|
411
|
+
* what makes a declaration referenced only from its own site an unreferenced one.
|
|
285
412
|
*/
|
|
286
413
|
function sitesOf(
|
|
287
414
|
file: SourceFile,
|
|
288
415
|
declarations: ReadonlyMap<string, string>,
|
|
289
416
|
fileId: string,
|
|
290
|
-
):
|
|
417
|
+
): FileSites {
|
|
291
418
|
const found: Site[] = [];
|
|
419
|
+
const links: Link[] = [];
|
|
420
|
+
const evaluations: Evaluation[] = [];
|
|
292
421
|
const writes = new Set<number>();
|
|
293
422
|
const declared = new Set<number>();
|
|
294
423
|
const shorthands = new Map<number, Node>();
|
|
@@ -326,6 +455,11 @@ function sitesOf(
|
|
|
326
455
|
}
|
|
327
456
|
markNames(node);
|
|
328
457
|
markStores(node, writes);
|
|
458
|
+
links.push(...linksOf(file, node, declarations, enclosing));
|
|
459
|
+
const evaluated = evaluatedBy(node);
|
|
460
|
+
if (evaluated !== undefined) {
|
|
461
|
+
evaluations.push({ node: evaluated, from: enclosing });
|
|
462
|
+
}
|
|
329
463
|
const held = declarations.get(nodeKey(file, node));
|
|
330
464
|
const outer = enclosing;
|
|
331
465
|
if (held !== undefined) {
|
|
@@ -336,7 +470,7 @@ function sitesOf(
|
|
|
336
470
|
};
|
|
337
471
|
|
|
338
472
|
file.forEachChild(visit);
|
|
339
|
-
return found;
|
|
473
|
+
return { uses: found, links, evaluations };
|
|
340
474
|
}
|
|
341
475
|
|
|
342
476
|
/**
|
|
@@ -356,12 +490,6 @@ function compare(a: string, b: string): number {
|
|
|
356
490
|
return a < b ? -1 : 1;
|
|
357
491
|
}
|
|
358
492
|
|
|
359
|
-
/** One declaration one symbol reaches, and whether a step along an alias reached it. */
|
|
360
|
-
interface Target {
|
|
361
|
-
readonly id: string;
|
|
362
|
-
readonly stepped: boolean;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
493
|
/**
|
|
366
494
|
* Every reference one project's own files make to the project's own declarations, in
|
|
367
495
|
* position order, and the test-file rules that classified those files.
|
|
@@ -373,7 +501,8 @@ interface Target {
|
|
|
373
501
|
* The order is by the referencing position, then by the declaration named, the
|
|
374
502
|
* declaration referencing it and the use. That is a total order because one name
|
|
375
503
|
* references one declaration once from one declaration, while a chain of aliases reaches
|
|
376
|
-
* several declarations at one position
|
|
504
|
+
* several declarations at one position and a bare star re-export's specifier both reads
|
|
505
|
+
* and evaluates the module it names.
|
|
377
506
|
*/
|
|
378
507
|
export function references<Brand>(
|
|
379
508
|
project: ProjectView<Brand>,
|
|
@@ -386,78 +515,39 @@ export function references<Brand>(
|
|
|
386
515
|
const patterns = options.testFiles.map(globExpression);
|
|
387
516
|
const matched = options.testFiles.map(() => 0);
|
|
388
517
|
const found: Reference[] = [];
|
|
389
|
-
const
|
|
518
|
+
const tests: string[] = [];
|
|
519
|
+
const chains = aliasChains(project, held);
|
|
390
520
|
let batched = 0;
|
|
391
521
|
let fileBatches = 0;
|
|
392
522
|
let residueFallbacks = 0;
|
|
393
523
|
let shorthandLookups = 0;
|
|
394
|
-
let aliasSteps = 0;
|
|
395
524
|
|
|
396
525
|
const files = project.ownSourceFiles();
|
|
397
|
-
const
|
|
526
|
+
const fileIds = new Set(
|
|
527
|
+
held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id),
|
|
528
|
+
);
|
|
398
529
|
|
|
399
530
|
/** The identifier of the declaration one node of this program is, where it is one. */
|
|
400
531
|
const declaredAt = (node: Node): string | undefined =>
|
|
401
532
|
declarations.get(nodeKey(node.getSourceFile(), node));
|
|
402
533
|
|
|
403
534
|
/**
|
|
404
|
-
* The declarations one
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
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.
|
|
535
|
+
* The declarations one re-export carries forward: for an alias, the first link along
|
|
536
|
+
* its chain that the inventory declares, stepping over the links it does not, such as
|
|
537
|
+
* an import the re-export is written against; for a bare star, the module it names.
|
|
538
|
+
* Each later link is a declaration of its own that references the next, so one link
|
|
539
|
+
* per re-export is what lets a chain be followed one declaration at a time.
|
|
420
540
|
*/
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
if (cached !== undefined) {
|
|
424
|
-
return cached;
|
|
425
|
-
}
|
|
426
|
-
const targets: Target[] = [];
|
|
427
|
-
const walked = new Set<number>();
|
|
428
|
-
let at: TSSymbol | undefined = symbol;
|
|
429
|
-
let stepped = false;
|
|
430
|
-
while (at !== undefined && !walked.has(at.id)) {
|
|
431
|
-
walked.add(at.id);
|
|
432
|
-
const alias = (at.flags & SymbolFlags.Alias) !== 0;
|
|
433
|
-
for (const handle of at.declarations) {
|
|
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)) {
|
|
440
|
-
targets.push({ id, stepped });
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
if (!alias) {
|
|
444
|
-
break;
|
|
445
|
-
}
|
|
446
|
-
at = project.aliasStepOf(at);
|
|
447
|
-
aliasSteps += 1;
|
|
448
|
-
stepped = true;
|
|
449
|
-
}
|
|
450
|
-
reached.set(symbol.id, targets);
|
|
451
|
-
return targets;
|
|
452
|
-
};
|
|
541
|
+
const carriedBy = (link: Link, symbol: TSSymbol): readonly string[] =>
|
|
542
|
+
link.alias ? chains.nextDeclared(symbol) : chains.declarationsOf(symbol);
|
|
453
543
|
|
|
454
544
|
/** The symbol one site resolves to, asking only the accessor the site's form names. */
|
|
455
|
-
const symbolFor = (site: Site, answered: ReadonlyMap<
|
|
545
|
+
const symbolFor = (site: Site, answered: ReadonlyMap<Node, TSSymbol>): TSSymbol | undefined => {
|
|
456
546
|
if (site.shorthand !== undefined) {
|
|
457
547
|
shorthandLookups += 1;
|
|
458
548
|
return project.shorthandValueAt(project.handle(site.shorthand));
|
|
459
549
|
}
|
|
460
|
-
const batched = answered.get(site.node
|
|
550
|
+
const batched = answered.get(site.node);
|
|
461
551
|
if (batched !== undefined) {
|
|
462
552
|
return batched;
|
|
463
553
|
}
|
|
@@ -467,38 +557,87 @@ export function references<Brand>(
|
|
|
467
557
|
|
|
468
558
|
for (const file of files) {
|
|
469
559
|
const path = renderPosition(file, targetRoot, 0).path;
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (
|
|
560
|
+
const hits = patterns.map((pattern) => pattern.test(path));
|
|
561
|
+
hits.forEach((hit, index) => {
|
|
562
|
+
if (hit) {
|
|
473
563
|
matched[index] = (matched[index] ?? 0) + 1;
|
|
474
|
-
test = true;
|
|
475
564
|
}
|
|
476
565
|
});
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
566
|
+
const test = hits.includes(true);
|
|
567
|
+
if (test) {
|
|
568
|
+
tests.push(path);
|
|
569
|
+
}
|
|
570
|
+
const { uses, links, evaluations } = sitesOf(file, declarations, declaredAt(file) ?? "");
|
|
571
|
+
|
|
572
|
+
// One batch per capped run of the file's name nodes, the re-exports' nodes after
|
|
573
|
+
// the uses and the evaluated modules' specifiers last, each node once: a bare star
|
|
574
|
+
// re-export's specifier is a link and an evaluation both. A shorthand is left out
|
|
575
|
+
// of the batch: its name resolves to the property the literal declares, so the
|
|
576
|
+
// batch's answer for it would name something this project's inventory never holds.
|
|
577
|
+
const batching = [
|
|
578
|
+
...new Set([
|
|
579
|
+
...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
|
|
580
|
+
...links.map((link) => link.node),
|
|
581
|
+
...evaluations.map((evaluation) => evaluation.node),
|
|
582
|
+
]),
|
|
583
|
+
];
|
|
584
|
+
const answered = new Map<Node, TSSymbol>();
|
|
484
585
|
batched += batching.length;
|
|
485
586
|
for (let from = 0; from < batching.length; from += cap) {
|
|
486
587
|
const run = batching.slice(from, from + cap);
|
|
487
|
-
const answers = project.symbolsAt(run.map((
|
|
588
|
+
const answers = project.symbolsAt(run.map((node) => project.handle(node)));
|
|
488
589
|
fileBatches += 1;
|
|
489
|
-
run.forEach((
|
|
590
|
+
run.forEach((node, index) => {
|
|
490
591
|
const symbol = answers[index];
|
|
491
592
|
if (symbol !== undefined) {
|
|
492
|
-
answered.set(
|
|
593
|
+
answered.set(node, symbol);
|
|
493
594
|
}
|
|
494
595
|
});
|
|
495
596
|
}
|
|
496
597
|
|
|
497
|
-
for (const
|
|
598
|
+
for (const link of links) {
|
|
599
|
+
const symbol = answered.get(link.node);
|
|
600
|
+
if (symbol === undefined) {
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
const position = renderPosition(file, targetRoot, link.at.getStart());
|
|
604
|
+
for (const id of carriedBy(link, symbol)) {
|
|
605
|
+
found.push({
|
|
606
|
+
from: link.from,
|
|
607
|
+
to: id,
|
|
608
|
+
position,
|
|
609
|
+
use: "read",
|
|
610
|
+
resolution: link.alias ? "alias" : "batch",
|
|
611
|
+
test,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
for (const evaluation of evaluations) {
|
|
617
|
+
const module = answered.get(evaluation.node);
|
|
618
|
+
if (module === undefined) {
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
const position = renderPosition(file, targetRoot, evaluation.node.getStart());
|
|
622
|
+
for (const id of chains.declarationsOf(module)) {
|
|
623
|
+
if (fileIds.has(id)) {
|
|
624
|
+
found.push({
|
|
625
|
+
from: evaluation.from,
|
|
626
|
+
to: id,
|
|
627
|
+
position,
|
|
628
|
+
use: "evaluation",
|
|
629
|
+
resolution: "batch",
|
|
630
|
+
test,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
for (const site of uses) {
|
|
498
637
|
const direct: Resolution =
|
|
499
638
|
site.shorthand !== undefined
|
|
500
639
|
? "shorthand"
|
|
501
|
-
: answered.has(site.node
|
|
640
|
+
: answered.has(site.node)
|
|
502
641
|
? "batch"
|
|
503
642
|
: "resolved-symbol";
|
|
504
643
|
const symbol = symbolFor(site, answered);
|
|
@@ -506,7 +645,9 @@ export function references<Brand>(
|
|
|
506
645
|
continue;
|
|
507
646
|
}
|
|
508
647
|
const position = renderPosition(file, targetRoot, site.node.getStart());
|
|
509
|
-
|
|
648
|
+
// A re-export something imports is used and so is the declaration behind it, so
|
|
649
|
+
// the use names every link of the chain its name stands at the head of.
|
|
650
|
+
for (const target of chains.chainOf(symbol)) {
|
|
510
651
|
found.push({
|
|
511
652
|
from: site.from,
|
|
512
653
|
to: target.id,
|
|
@@ -533,6 +674,7 @@ export function references<Brand>(
|
|
|
533
674
|
testFileRules: options.testFiles
|
|
534
675
|
.map((_pattern, index) => ({ rule: patternRule(index), matched: matched[index] ?? 0 }))
|
|
535
676
|
.sort((a, b) => compare(a.rule, b.rule)),
|
|
536
|
-
|
|
677
|
+
testFilePaths: tests.sort(compare),
|
|
678
|
+
cost: { batched, fileBatches, residueFallbacks, shorthandLookups, aliasSteps: chains.steps },
|
|
537
679
|
};
|
|
538
680
|
}
|
package/src/resolve.ts
CHANGED
|
@@ -38,6 +38,24 @@ const SEVERITY_KEY = /^DS[0-9]{2}([0-9]{2})?$/u;
|
|
|
38
38
|
const EXEMPTION_CLASS = /^[a-z][a-z0-9-]*$/u;
|
|
39
39
|
const CONTRACT_VERSION_PATTERN = /^[0-9]+\.[0-9]+\.[0-9]+$/u;
|
|
40
40
|
|
|
41
|
+
/** The members only a platform build configuration carries. */
|
|
42
|
+
const PLATFORM_MEMBERS = ["os", "arch", "tags"] as const;
|
|
43
|
+
|
|
44
|
+
/** The member only a project build configuration carries. */
|
|
45
|
+
const PROJECT_MEMBER = "project";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One segment of a project path: not empty, not `.` or `..`, and holding no
|
|
49
|
+
* solidus, backslash or line break.
|
|
50
|
+
*/
|
|
51
|
+
const PROJECT_SEGMENT = String.raw`(?:[^/\\.\r\n][^/\\\r\n]*|\.[^/\\.\r\n][^/\\\r\n]*|\.\.[^/\\\r\n]+)`;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A project path: segments joined by a solidus, so the path is relative, below
|
|
55
|
+
* the root it is read against, and spelled one way on every machine.
|
|
56
|
+
*/
|
|
57
|
+
const PROJECT_PATH = new RegExp(`^${PROJECT_SEGMENT}(?:/${PROJECT_SEGMENT})*$`, "u");
|
|
58
|
+
|
|
41
59
|
const TARGET_KINDS: readonly TargetKind[] = ["application", "library"];
|
|
42
60
|
const CONFIDENCES: readonly Confidence[] = ["certain", "probable", "possible"];
|
|
43
61
|
const GENERATED_FILES: readonly GeneratedFiles[] = ["exclude", "include"];
|
|
@@ -252,10 +270,75 @@ function requireMember(value: string | undefined, path: string, label: string):
|
|
|
252
270
|
return value;
|
|
253
271
|
}
|
|
254
272
|
|
|
273
|
+
/** One build configuration entry as a platform: an identifier, an operating system and an architecture. */
|
|
274
|
+
function readPlatform(
|
|
275
|
+
entry: Record<string, unknown>,
|
|
276
|
+
at: string,
|
|
277
|
+
label: string,
|
|
278
|
+
): BuildConfiguration {
|
|
279
|
+
return {
|
|
280
|
+
shape: "platform",
|
|
281
|
+
id: requireMember(readString(entry, "id", `${at}.id`, label), `${at}.id`, label),
|
|
282
|
+
os: requireMember(readString(entry, "os", `${at}.os`, label), `${at}.os`, label),
|
|
283
|
+
arch: requireMember(readString(entry, "arch", `${at}.arch`, label), `${at}.arch`, label),
|
|
284
|
+
tags: readStrings(entry, "tags", `${at}.tags`, label, 0) ?? [],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** One build configuration entry as a project: an identifier and a configuration file below the target root. */
|
|
289
|
+
function readProject(
|
|
290
|
+
entry: Record<string, unknown>,
|
|
291
|
+
at: string,
|
|
292
|
+
label: string,
|
|
293
|
+
): BuildConfiguration {
|
|
294
|
+
const id = requireMember(readString(entry, "id", `${at}.id`, label), `${at}.id`, label);
|
|
295
|
+
const path = `${at}.${PROJECT_MEMBER}`;
|
|
296
|
+
const project = requireMember(readString(entry, PROJECT_MEMBER, path, label), path, label);
|
|
297
|
+
if (!PROJECT_PATH.test(project)) {
|
|
298
|
+
throw malformed(
|
|
299
|
+
label,
|
|
300
|
+
path,
|
|
301
|
+
`${JSON.stringify(project)} is not a path below the target root: segments joined by /, ` +
|
|
302
|
+
"none empty, . or .., and no backslash or line break",
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
return { shape: "project", id, project };
|
|
306
|
+
}
|
|
307
|
+
|
|
255
308
|
/**
|
|
256
|
-
*
|
|
257
|
-
*
|
|
309
|
+
* One build configuration entry, read as the one shape its members name: a member
|
|
310
|
+
* only a platform carries makes it a platform, the member only a project carries
|
|
311
|
+
* makes it a project, and an entry naming members of both, or of neither, is
|
|
312
|
+
* refused at the entry, because the two shapes are exclusive.
|
|
258
313
|
*/
|
|
314
|
+
function readConfiguration(entry: unknown, at: string, label: string): BuildConfiguration {
|
|
315
|
+
if (!isRecord(entry)) {
|
|
316
|
+
throw malformed(label, at, "is not an object");
|
|
317
|
+
}
|
|
318
|
+
const platform = PLATFORM_MEMBERS.filter((name) => Object.hasOwn(entry, name));
|
|
319
|
+
const project = Object.hasOwn(entry, PROJECT_MEMBER);
|
|
320
|
+
if (platform.length > 0 && project) {
|
|
321
|
+
throw malformed(
|
|
322
|
+
label,
|
|
323
|
+
at,
|
|
324
|
+
`carries ${spell(platform)} of a platform and ${JSON.stringify(PROJECT_MEMBER)} of a ` +
|
|
325
|
+
"project, and an entry is one shape or the other",
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
if (project) {
|
|
329
|
+
return readProject(entry, at, label);
|
|
330
|
+
}
|
|
331
|
+
if (platform.length === 0) {
|
|
332
|
+
throw malformed(
|
|
333
|
+
label,
|
|
334
|
+
at,
|
|
335
|
+
`names neither a platform's "os" and "arch" nor a project's ${JSON.stringify(PROJECT_MEMBER)}`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return readPlatform(entry, at, label);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** The build matrix, one entry in one shape at a time. */
|
|
259
342
|
function readConfigurations(
|
|
260
343
|
analysis: Record<string, unknown> | undefined,
|
|
261
344
|
label: string,
|
|
@@ -267,18 +350,9 @@ function readConfigurations(
|
|
|
267
350
|
if (!Array.isArray(value)) {
|
|
268
351
|
throw malformed(label, "analysis.configurations", "is not an array");
|
|
269
352
|
}
|
|
270
|
-
return (value as unknown[]).map((entry, index) =>
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
throw malformed(label, at, "is not an object");
|
|
274
|
-
}
|
|
275
|
-
return {
|
|
276
|
-
id: requireMember(readString(entry, "id", `${at}.id`, label), `${at}.id`, label),
|
|
277
|
-
os: requireMember(readString(entry, "os", `${at}.os`, label), `${at}.os`, label),
|
|
278
|
-
arch: requireMember(readString(entry, "arch", `${at}.arch`, label), `${at}.arch`, label),
|
|
279
|
-
tags: readStrings(entry, "tags", `${at}.tags`, label, 0) ?? [],
|
|
280
|
-
};
|
|
281
|
-
});
|
|
353
|
+
return (value as unknown[]).map((entry, index) =>
|
|
354
|
+
readConfiguration(entry, `analysis.configurations[${String(index)}]`, label),
|
|
355
|
+
);
|
|
282
356
|
}
|
|
283
357
|
|
|
284
358
|
/**
|
|
@@ -642,6 +716,35 @@ function checkLabels(provenance: ReadonlyMap<string, Origin>): void {
|
|
|
642
716
|
* the usage exit code.
|
|
643
717
|
*/
|
|
644
718
|
export function resolve(inputs: Inputs): { config: Config; provenance: Provenance } {
|
|
719
|
+
const { config, provenance } = settleAll(inputs);
|
|
720
|
+
if (config.targetKind === "") {
|
|
721
|
+
throw new ConfigError(
|
|
722
|
+
"missing-target-kind",
|
|
723
|
+
"target.kind",
|
|
724
|
+
"target.kind is not set, it has no default and is never inferred: searched " +
|
|
725
|
+
`${describeSource("the repository configuration", inputs.repository, inputs.repositoryLabel)} and ` +
|
|
726
|
+
describeSource("the central configuration", inputs.central, inputs.centralLabel),
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
checkLabels(provenance);
|
|
730
|
+
return { config, provenance };
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* The build matrix the configuration documents resolve to, every document read and
|
|
735
|
+
* refused as {@link resolve} refuses it, and nothing else required of them: which
|
|
736
|
+
* projects a run analyzes depends on the matrix alone, so a question about the
|
|
737
|
+
* projects asks no target kind.
|
|
738
|
+
*/
|
|
739
|
+
export function resolveMatrix(inputs: Inputs): readonly BuildConfiguration[] {
|
|
740
|
+
return settleAll(inputs).config.analysis.configurations;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Every setting the documents supply, each from the highest-ranked source carrying
|
|
745
|
+
* it, with the origin of each. The target kind is empty where no source names one.
|
|
746
|
+
*/
|
|
747
|
+
function settleAll(inputs: Inputs): { config: Config; provenance: Map<string, Origin> } {
|
|
645
748
|
const sources = readSources(inputs);
|
|
646
749
|
const defaults = defaultConfig();
|
|
647
750
|
const provenance = new Map<string, Origin>();
|
|
@@ -718,15 +821,5 @@ export function resolve(inputs: Inputs): { config: Config; provenance: Provenanc
|
|
|
718
821
|
},
|
|
719
822
|
};
|
|
720
823
|
|
|
721
|
-
if (config.targetKind === "") {
|
|
722
|
-
throw new ConfigError(
|
|
723
|
-
"missing-target-kind",
|
|
724
|
-
"target.kind",
|
|
725
|
-
"target.kind is not set, it has no default and is never inferred: searched " +
|
|
726
|
-
`${describeSource("the repository configuration", inputs.repository, inputs.repositoryLabel)} and ` +
|
|
727
|
-
describeSource("the central configuration", inputs.central, inputs.centralLabel),
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
checkLabels(provenance);
|
|
731
824
|
return { config, provenance };
|
|
732
825
|
}
|