@cplieger/deadset-ts 5.1.0 → 5.2.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 +1 -1
- package/dist/src/analysis.js +79 -37
- package/dist/src/config.js +3 -0
- package/dist/src/configuration-files.js +24 -25
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +46 -18
- package/dist/src/convention-rows.js +7 -0
- package/dist/src/conventions.js +97 -33
- package/dist/src/dependencies.js +8 -0
- package/dist/src/entry-point-gaps.js +10 -26
- package/dist/src/entry-points.js +4 -0
- package/dist/src/exempt.js +23 -8
- package/dist/src/file-facts.js +23 -20
- package/dist/src/findings/completion.js +27 -11
- package/dist/src/findings/interfaces.js +2 -6
- package/dist/src/findings/intra-function.js +15 -3
- package/dist/src/findings/unused-declarations.js +5 -6
- package/dist/src/findings/visibility-narrowing.js +6 -1
- package/dist/src/generated-file.js +72 -12
- package/dist/src/graph.js +6 -5
- package/dist/src/html-entries.js +137 -0
- package/dist/src/inventory.js +13 -3
- package/dist/src/kinds.js +2 -2
- package/dist/src/manifest.js +34 -5
- package/dist/src/matrix.js +9 -3
- package/dist/src/namespace-objects.js +158 -0
- package/dist/src/outside-tests.js +139 -0
- package/dist/src/overrides.js +104 -0
- package/dist/src/paths.js +17 -0
- package/dist/src/references.js +150 -4
- package/dist/src/roots.js +172 -39
- package/dist/src/session.js +4 -3
- package/dist/src/sweep.js +17 -0
- package/dist/src/template-field.js +60 -3
- package/dist/src/test-support.js +35 -23
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +122 -49
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +29 -25
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +51 -20
- package/src/convention-rows.ts +15 -0
- package/src/conventions.ts +119 -30
- package/src/dependencies.ts +9 -0
- package/src/entry-point-gaps.ts +11 -28
- package/src/entry-points.ts +7 -1
- package/src/exempt.ts +29 -12
- package/src/file-facts.ts +25 -21
- package/src/findings/completion.ts +31 -11
- package/src/findings/interfaces.ts +2 -9
- package/src/findings/intra-function.ts +17 -3
- package/src/findings/unused-declarations.ts +5 -8
- package/src/findings/visibility-narrowing.ts +7 -1
- package/src/generated-file.ts +80 -12
- package/src/graph.ts +9 -8
- package/src/html-entries.ts +170 -0
- package/src/inventory.ts +15 -3
- package/src/kinds.ts +2 -2
- package/src/manifest.ts +49 -7
- package/src/matrix.ts +21 -3
- package/src/namespace-objects.ts +216 -0
- package/src/outside-tests.ts +191 -0
- package/src/overrides.ts +136 -0
- package/src/paths.ts +18 -0
- package/src/references.ts +206 -9
- package/src/roots.ts +210 -53
- package/src/session.ts +4 -2
- package/src/sweep.ts +19 -0
- package/src/template-field.ts +65 -3
- package/src/test-support.ts +34 -22
- package/src/version.ts +1 -1
package/src/references.ts
CHANGED
|
@@ -12,8 +12,11 @@ import {
|
|
|
12
12
|
isForInStatement,
|
|
13
13
|
isForOfStatement,
|
|
14
14
|
isForStatement,
|
|
15
|
+
isIdentifier,
|
|
15
16
|
isImportDeclaration,
|
|
16
17
|
isImportEqualsDeclaration,
|
|
18
|
+
isMetaProperty,
|
|
19
|
+
isNewExpression,
|
|
17
20
|
isNamespaceExport,
|
|
18
21
|
isNoSubstitutionTemplateLiteral,
|
|
19
22
|
isObjectLiteralExpression,
|
|
@@ -35,9 +38,12 @@ import type { Symbol as TSSymbol } from "@typescript/native/unstable/sync";
|
|
|
35
38
|
import { aliasChains, type ChainTarget } from "./alias-chain.ts";
|
|
36
39
|
import { contextualUses, type FlowAt, type LiteralAt } from "./contextual-uses.ts";
|
|
37
40
|
import { destructuring, propertyReadsOf, type PropertyRead } from "./destructuring.ts";
|
|
41
|
+
import { specifierCandidates } from "./configuration-files.ts";
|
|
42
|
+
import { namespaceUses } from "./namespace-objects.ts";
|
|
38
43
|
import { globExpression } from "./glob.ts";
|
|
39
44
|
import { declarationsByName, nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
40
|
-
import {
|
|
45
|
+
import { overrideReferences } from "./overrides.ts";
|
|
46
|
+
import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.ts";
|
|
41
47
|
import { byPosition, isComponentFile, renderPosition, type Position } from "./position.ts";
|
|
42
48
|
import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./query.ts";
|
|
43
49
|
import type { ProjectView } from "./session.ts";
|
|
@@ -71,12 +77,12 @@ import { handedOn, valuesOf } from "./value-flow.ts";
|
|
|
71
77
|
export type Use = "read" | "write" | "evaluation" | "decorator";
|
|
72
78
|
|
|
73
79
|
/**
|
|
74
|
-
* Which path answered for one reference: `batch`,
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* `by-name`, every declaration spelled
|
|
80
|
+
* Which path answered for one reference: `batch`, a file's batched lookup; `resolved-symbol`,
|
|
81
|
+
* the per-node lookup for its residue; `shorthand`, the value `{ a }` reads; `alias`, a step
|
|
82
|
+
* along an import or export chain; `syntax`, the tree alone (a decorator's target, a
|
|
83
|
+
* component's markup); `destructured`, the property a pattern names; `contextual`, the member
|
|
84
|
+
* a literal property writes or a value's position reads; `override`, the override a base
|
|
85
|
+
* member's call dispatches to; `by-name`, every declaration so spelled, where the checker failed.
|
|
80
86
|
*/
|
|
81
87
|
export type Resolution =
|
|
82
88
|
| "batch"
|
|
@@ -86,6 +92,7 @@ export type Resolution =
|
|
|
86
92
|
| "syntax"
|
|
87
93
|
| "destructured"
|
|
88
94
|
| "contextual"
|
|
95
|
+
| "override"
|
|
89
96
|
| "by-name";
|
|
90
97
|
|
|
91
98
|
/** One use of one declaration by one declaration. */
|
|
@@ -112,6 +119,8 @@ export interface Reference {
|
|
|
112
119
|
* the consumer's root.
|
|
113
120
|
*/
|
|
114
121
|
readonly consumer?: string;
|
|
122
|
+
/** Made by a target file no project of the run holds, so it enters as a consumer's reference does. */
|
|
123
|
+
readonly unheld?: true;
|
|
115
124
|
/**
|
|
116
125
|
* On a write, the local names of the writing file's import bindings that only the
|
|
117
126
|
* values its file writes into the same declaration use, which deleting the
|
|
@@ -420,6 +429,27 @@ interface Evaluation {
|
|
|
420
429
|
readonly from: string;
|
|
421
430
|
}
|
|
422
431
|
|
|
432
|
+
/**
|
|
433
|
+
* One module URL a source file builds: `new URL(<literal>, import.meta.url)` or
|
|
434
|
+
* `import.meta.resolve(<literal>)`, the literal relative. A runtime starts the file it
|
|
435
|
+
* names as a module, so it is an import of that file for its effects.
|
|
436
|
+
*/
|
|
437
|
+
interface ModuleURL {
|
|
438
|
+
readonly specifier: string;
|
|
439
|
+
/** The `URL` the constructor names, which must name no declaration of the target. */
|
|
440
|
+
readonly constructor: Node | undefined;
|
|
441
|
+
readonly at: Node;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** One use of a module namespace object that may read every export of the module. */
|
|
445
|
+
interface WholeRead {
|
|
446
|
+
/** The module specifier, whose symbol is the module read. */
|
|
447
|
+
readonly specifier: Node;
|
|
448
|
+
/** The use, whose position the reference carries. */
|
|
449
|
+
readonly at: Node;
|
|
450
|
+
readonly from: string;
|
|
451
|
+
}
|
|
452
|
+
|
|
423
453
|
/** One decorator expression, and the declaration it is attached to. */
|
|
424
454
|
interface Decorated {
|
|
425
455
|
readonly decorator: Node;
|
|
@@ -439,9 +469,55 @@ interface FileSites {
|
|
|
439
469
|
readonly reads: readonly Read[];
|
|
440
470
|
readonly links: readonly Link[];
|
|
441
471
|
readonly evaluations: readonly Evaluation[];
|
|
472
|
+
readonly wholes: readonly WholeRead[];
|
|
473
|
+
readonly urls: readonly ModuleURL[];
|
|
442
474
|
readonly decorated: readonly Decorated[];
|
|
443
475
|
}
|
|
444
476
|
|
|
477
|
+
/** Whether one node is `import.meta.url`. */
|
|
478
|
+
function isImportMetaURL(node: Node): boolean {
|
|
479
|
+
const at = unparenthesized(node);
|
|
480
|
+
return (
|
|
481
|
+
isPropertyAccessExpression(at) &&
|
|
482
|
+
isMetaProperty(at.expression) &&
|
|
483
|
+
at.expression.keywordToken === SyntaxKind.ImportKeyword &&
|
|
484
|
+
at.name.text === "url"
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** The module URL one node builds, where it builds one. */
|
|
489
|
+
function moduleURLOf(node: Node): ModuleURL | undefined {
|
|
490
|
+
const relative = (argument: Node | undefined): string | undefined => {
|
|
491
|
+
const at = argument === undefined ? undefined : unparenthesized(argument);
|
|
492
|
+
return at !== undefined &&
|
|
493
|
+
(isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at)) &&
|
|
494
|
+
/^\.\.?\//u.test(at.text)
|
|
495
|
+
? at.text
|
|
496
|
+
: undefined;
|
|
497
|
+
};
|
|
498
|
+
if (isNewExpression(node) && isIdentifier(node.expression) && node.expression.text === "URL") {
|
|
499
|
+
const [reference, base] = node.arguments ?? [];
|
|
500
|
+
const specifier = relative(reference);
|
|
501
|
+
return specifier !== undefined && base !== undefined && isImportMetaURL(base)
|
|
502
|
+
? { specifier, constructor: node.expression, at: node }
|
|
503
|
+
: undefined;
|
|
504
|
+
}
|
|
505
|
+
if (isCallExpression(node)) {
|
|
506
|
+
const callee = unparenthesized(node.expression);
|
|
507
|
+
const specifier = relative(node.arguments[0]);
|
|
508
|
+
if (
|
|
509
|
+
specifier !== undefined &&
|
|
510
|
+
isPropertyAccessExpression(callee) &&
|
|
511
|
+
isMetaProperty(callee.expression) &&
|
|
512
|
+
callee.expression.keywordToken === SyntaxKind.ImportKeyword &&
|
|
513
|
+
callee.name.text === "resolve"
|
|
514
|
+
) {
|
|
515
|
+
return { specifier, constructor: undefined, at: node };
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
520
|
+
|
|
445
521
|
/** Whether one node is a string literal or a template literal with no substitution. */
|
|
446
522
|
function isLiteralText(node: Node | undefined): node is Node {
|
|
447
523
|
return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
|
|
@@ -476,6 +552,24 @@ function evaluatedBy(node: Node): Node | undefined {
|
|
|
476
552
|
return undefined;
|
|
477
553
|
}
|
|
478
554
|
|
|
555
|
+
/**
|
|
556
|
+
* The own file a module specifier with a query evaluates ({@link queriedModulePath}): the
|
|
557
|
+
* checker resolves the whole specifier to whatever module declaration matches it.
|
|
558
|
+
*/
|
|
559
|
+
function queriedFile(
|
|
560
|
+
file: SourceFile,
|
|
561
|
+
specifier: Node,
|
|
562
|
+
fileIdByName: ReadonlyMap<string, string>,
|
|
563
|
+
): string | undefined {
|
|
564
|
+
if (!isStringLiteral(specifier) && !isNoSubstitutionTemplateLiteral(specifier)) {
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|
|
567
|
+
const path = queriedModulePath(specifier.text);
|
|
568
|
+
return path === undefined
|
|
569
|
+
? undefined
|
|
570
|
+
: fileIdByName.get(resolvePath(dirnamePath(file.fileName), path));
|
|
571
|
+
}
|
|
572
|
+
|
|
479
573
|
/**
|
|
480
574
|
* The re-exports one node writes, if it is an export declaration. A specifier and a
|
|
481
575
|
* namespace export are declarations whose names are where they are written, so no use is
|
|
@@ -562,7 +656,10 @@ function sitesOf(
|
|
|
562
656
|
const reads: Read[] = [];
|
|
563
657
|
const links: Link[] = [];
|
|
564
658
|
const evaluations: Evaluation[] = [];
|
|
659
|
+
const wholes: WholeRead[] = [];
|
|
660
|
+
const urls: ModuleURL[] = [];
|
|
565
661
|
const decorated: Decorated[] = [];
|
|
662
|
+
const namespaces = namespaceUses(file);
|
|
566
663
|
const literals: LiteralAt[] = [];
|
|
567
664
|
const flows: FlowAt[] = [];
|
|
568
665
|
const writes = new Set<number>();
|
|
@@ -593,6 +690,12 @@ function sitesOf(
|
|
|
593
690
|
if (unread.has(node)) {
|
|
594
691
|
return;
|
|
595
692
|
}
|
|
693
|
+
const namespace = namespaces.get(node);
|
|
694
|
+
if (namespace?.kind === "whole") {
|
|
695
|
+
wholes.push({ specifier: namespace.specifier, at: node, from: enclosing });
|
|
696
|
+
} else if (namespace?.kind === "indexed") {
|
|
697
|
+
found.push({ node: namespace.literal, shorthand: undefined, from: enclosing, use: "read" });
|
|
698
|
+
}
|
|
596
699
|
if (isName(node)) {
|
|
597
700
|
if (!declared.has(node.pos)) {
|
|
598
701
|
const site = { node, shorthand: shorthands.get(node.pos), from: enclosing };
|
|
@@ -622,6 +725,10 @@ function sitesOf(
|
|
|
622
725
|
if (evaluated !== undefined) {
|
|
623
726
|
evaluations.push({ node: evaluated, from: enclosing });
|
|
624
727
|
}
|
|
728
|
+
const url = moduleURLOf(node);
|
|
729
|
+
if (url !== undefined) {
|
|
730
|
+
urls.push(url);
|
|
731
|
+
}
|
|
625
732
|
const held = declarations.get(nodeKey(file, node));
|
|
626
733
|
const outer = enclosing;
|
|
627
734
|
if (held !== undefined) {
|
|
@@ -637,7 +744,7 @@ function sitesOf(
|
|
|
637
744
|
};
|
|
638
745
|
|
|
639
746
|
file.forEachChild(visit);
|
|
640
|
-
return { uses: found, literals, flows, reads, links, evaluations, decorated };
|
|
747
|
+
return { uses: found, literals, flows, reads, links, evaluations, wholes, urls, decorated };
|
|
641
748
|
}
|
|
642
749
|
|
|
643
750
|
/** The declaration kinds of a property an object literal declares. */
|
|
@@ -900,6 +1007,36 @@ export function references<Brand>(
|
|
|
900
1007
|
return topLevel.get(fileId) ?? [];
|
|
901
1008
|
};
|
|
902
1009
|
|
|
1010
|
+
const exportsCache = new Map<number, readonly ChainTarget[]>();
|
|
1011
|
+
/**
|
|
1012
|
+
* Every declaration one module exports, each re-export walked along its chain. A module
|
|
1013
|
+
* the checker left unanswered may be any file, so it reads every file's exports.
|
|
1014
|
+
*/
|
|
1015
|
+
const exportsRead = (module: Answer<TSSymbol>): readonly ChainTarget[] => {
|
|
1016
|
+
if (module === UNANSWERED) {
|
|
1017
|
+
return [...fileIds].flatMap((id) =>
|
|
1018
|
+
declaredIn(id)
|
|
1019
|
+
.filter((symbol) => symbol.exported)
|
|
1020
|
+
.map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })),
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
const known = exportsCache.get(module.id);
|
|
1024
|
+
if (known !== undefined) {
|
|
1025
|
+
return known;
|
|
1026
|
+
}
|
|
1027
|
+
const table = project.queries.exportsOfModule(module);
|
|
1028
|
+
const read =
|
|
1029
|
+
table === UNANSWERED
|
|
1030
|
+
? chains.declarationsOf(module).flatMap((id) =>
|
|
1031
|
+
declaredIn(id)
|
|
1032
|
+
.filter((symbol) => symbol.exported)
|
|
1033
|
+
.map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })),
|
|
1034
|
+
)
|
|
1035
|
+
: table.flatMap((exported) => chains.chainOf(exported));
|
|
1036
|
+
exportsCache.set(module.id, read);
|
|
1037
|
+
return read;
|
|
1038
|
+
};
|
|
1039
|
+
|
|
903
1040
|
/** The identifier of the declaration one node of this program is, where it is one. */
|
|
904
1041
|
const declaredAt = (node: Node): string | undefined =>
|
|
905
1042
|
declarations.get(nodeKey(node.getSourceFile(), node));
|
|
@@ -1000,6 +1137,9 @@ export function references<Brand>(
|
|
|
1000
1137
|
const fileId = declaredAt(file) ?? "";
|
|
1001
1138
|
return { file, fileId, ...sitesOf(file, declarations, fileId, unread) };
|
|
1002
1139
|
});
|
|
1140
|
+
const fileIdByName = new Map<string, string>(
|
|
1141
|
+
walked.filter((one) => fileIds.has(one.fileId)).map((one) => [one.file.fileName, one.fileId]),
|
|
1142
|
+
);
|
|
1003
1143
|
const contextual = contextualUses(
|
|
1004
1144
|
project,
|
|
1005
1145
|
held,
|
|
@@ -1087,7 +1227,7 @@ export function references<Brand>(
|
|
|
1087
1227
|
}
|
|
1088
1228
|
}
|
|
1089
1229
|
|
|
1090
|
-
for (const { file, fileId, uses, reads, links, evaluations, decorated } of walked) {
|
|
1230
|
+
for (const { file, fileId, uses, reads, links, evaluations, wholes, urls, decorated } of walked) {
|
|
1091
1231
|
const test = testFile.get(file) === true;
|
|
1092
1232
|
// A component file's markup may use any binding its blocks declare at the top level.
|
|
1093
1233
|
const markup = isComponentFile(file);
|
|
@@ -1117,6 +1257,8 @@ export function references<Brand>(
|
|
|
1117
1257
|
...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
|
|
1118
1258
|
...links.map((link) => link.node),
|
|
1119
1259
|
...evaluations.map((evaluation) => evaluation.node),
|
|
1260
|
+
...wholes.map((whole) => whole.specifier),
|
|
1261
|
+
...urls.flatMap((url) => url.constructor ?? []),
|
|
1120
1262
|
...bindings.map((binding) => binding.node),
|
|
1121
1263
|
]),
|
|
1122
1264
|
];
|
|
@@ -1160,6 +1302,17 @@ export function references<Brand>(
|
|
|
1160
1302
|
}
|
|
1161
1303
|
|
|
1162
1304
|
for (const evaluation of evaluations) {
|
|
1305
|
+
const queried = queriedFile(file, evaluation.node, fileIdByName);
|
|
1306
|
+
if (queried !== undefined) {
|
|
1307
|
+
found.push({
|
|
1308
|
+
from: evaluation.from,
|
|
1309
|
+
to: queried,
|
|
1310
|
+
position: renderPosition(file, root, evaluation.node.getStart()),
|
|
1311
|
+
use: "evaluation",
|
|
1312
|
+
resolution: "syntax",
|
|
1313
|
+
test,
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1163
1316
|
const module = answered.get(evaluation.node);
|
|
1164
1317
|
if (module === undefined) {
|
|
1165
1318
|
continue;
|
|
@@ -1181,6 +1334,47 @@ export function references<Brand>(
|
|
|
1181
1334
|
}
|
|
1182
1335
|
}
|
|
1183
1336
|
|
|
1337
|
+
for (const url of urls) {
|
|
1338
|
+
const constructor = url.constructor === undefined ? undefined : answered.get(url.constructor);
|
|
1339
|
+
if (
|
|
1340
|
+
typeof constructor === "object" &&
|
|
1341
|
+
constructor.declarations.some((handle) => ownPaths.has(handle.path))
|
|
1342
|
+
) {
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
const loaded = specifierCandidates(dirnamePath(file.fileName), url.specifier)
|
|
1346
|
+
.map((path) => fileIdByName.get(path))
|
|
1347
|
+
.find((id) => id !== undefined);
|
|
1348
|
+
if (loaded !== undefined) {
|
|
1349
|
+
found.push({
|
|
1350
|
+
from: fileId,
|
|
1351
|
+
to: loaded,
|
|
1352
|
+
position: renderPosition(file, root, url.at.getStart()),
|
|
1353
|
+
use: "evaluation",
|
|
1354
|
+
resolution: "syntax",
|
|
1355
|
+
test,
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
for (const whole of wholes) {
|
|
1361
|
+
const module = answered.get(whole.specifier);
|
|
1362
|
+
if (module === undefined) {
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
const position = renderPosition(file, root, whole.at.getStart());
|
|
1366
|
+
for (const target of exportsRead(module)) {
|
|
1367
|
+
found.push({
|
|
1368
|
+
from: whole.from,
|
|
1369
|
+
to: target.id,
|
|
1370
|
+
position,
|
|
1371
|
+
use: "read",
|
|
1372
|
+
resolution: target.guessed ? "by-name" : "batch",
|
|
1373
|
+
test,
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1184
1378
|
if (markup) {
|
|
1185
1379
|
for (const symbol of declaredIn(fileId)) {
|
|
1186
1380
|
found.push({
|
|
@@ -1298,6 +1492,9 @@ export function references<Brand>(
|
|
|
1298
1492
|
}
|
|
1299
1493
|
}
|
|
1300
1494
|
|
|
1495
|
+
if (consumer === undefined) {
|
|
1496
|
+
found.push(...overrideReferences(project, held, chains, new Set(tests)));
|
|
1497
|
+
}
|
|
1301
1498
|
found.sort(
|
|
1302
1499
|
(a, b) =>
|
|
1303
1500
|
byPosition(a.position, b.position) ||
|