@cplieger/deadset-ts 8.0.0-dev.14 → 8.0.0-dev.15
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/dist/src/alias-chain.js +2 -2
- package/dist/src/analysis.js +2 -1
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +1 -1
- package/dist/src/contract/contract.generated.js +1 -0
- package/dist/src/dependencies.js +2 -1
- package/dist/src/entry-points.js +26 -95
- package/dist/src/file-facts.js +1 -1
- package/dist/src/graph.js +0 -1
- package/dist/src/inventory/commonjs.js +462 -0
- package/dist/src/inventory/enumerate.js +166 -0
- package/dist/src/inventory/syntax.js +236 -0
- package/dist/src/inventory.js +305 -705
- package/dist/src/load/discover.js +1 -1
- package/dist/src/load/package-scope.js +56 -0
- package/dist/src/own-body.js +28 -0
- package/dist/src/position.js +41 -0
- package/dist/src/ref.js +11 -20
- package/dist/src/references.js +91 -30
- package/dist/src/session.js +3 -0
- package/dist/src/suppress-inline.js +33 -81
- package/dist/src/unseen-loads.js +106 -17
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +2 -2
- package/src/analysis.ts +2 -1
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +1 -1
- package/src/contract/contract.generated.ts +1 -0
- package/src/dependencies.ts +2 -1
- package/src/entry-points.ts +30 -107
- package/src/file-facts.ts +1 -1
- package/src/graph.ts +0 -1
- package/src/inventory/commonjs.ts +611 -0
- package/src/inventory/enumerate.ts +202 -0
- package/src/inventory/syntax.ts +311 -0
- package/src/inventory.ts +424 -878
- package/src/load/discover.ts +1 -1
- package/src/load/package-scope.ts +74 -0
- package/src/own-body.ts +42 -0
- package/src/position.ts +42 -0
- package/src/ref.ts +11 -20
- package/src/references.ts +116 -53
- package/src/session.ts +17 -1
- package/src/suppress-inline.ts +53 -89
- package/src/unseen-loads.ts +166 -18
package/src/load/discover.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { BuildConfiguration, ProjectConfiguration } from "../config.ts";
|
|
|
2
2
|
import { LANGUAGE_ROWS } from "../contract/contract.generated.ts";
|
|
3
3
|
import { filesBelow, filesIn } from "../fs/tree.ts";
|
|
4
4
|
import type { Host } from "../host.ts";
|
|
5
|
-
import { packageScope } from "
|
|
5
|
+
import { packageScope } from "./package-scope.ts";
|
|
6
6
|
import { dirnamePath, joinPath, relativePath, resolvePath } from "../paths.ts";
|
|
7
7
|
import { LANGUAGE, message } from "../product.ts";
|
|
8
8
|
import type { Module, Scope } from "../scope.ts";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { manifestAt } from "../fs/manifest.ts";
|
|
2
|
+
import type { Host } from "../host.ts";
|
|
3
|
+
import { dirnamePath, joinPath } from "../paths.ts";
|
|
4
|
+
import type { Module } from "../ref.ts";
|
|
5
|
+
|
|
6
|
+
/** One manifest that names a package, and the directory below the root that holds it. */
|
|
7
|
+
interface Named {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly at: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves each file's package scope: the name of the nearest manifest at or above its
|
|
14
|
+
* directory up to the target root, and the file's path below that manifest. Where no
|
|
15
|
+
* manifest up to the root carries a name, the package is a single dot and the path is read
|
|
16
|
+
* from the root, which cannot collide, because a package name never starts with a dot. One
|
|
17
|
+
* directory is read at most once.
|
|
18
|
+
*/
|
|
19
|
+
export function packageScope(host: Host, root: string): (path: string) => Module {
|
|
20
|
+
const named = new Map<string, Named | undefined>();
|
|
21
|
+
|
|
22
|
+
const manifestName = (dir: string): string | undefined => {
|
|
23
|
+
const name = manifestAt(host, joinPath(root, dir))?.["name"];
|
|
24
|
+
return typeof name === "string" && name !== "" ? name : undefined;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const nearest = (dir: string): Named | undefined => {
|
|
28
|
+
if (named.has(dir)) {
|
|
29
|
+
return named.get(dir);
|
|
30
|
+
}
|
|
31
|
+
const name = manifestName(dir);
|
|
32
|
+
const found =
|
|
33
|
+
name !== undefined ? { name, at: dir } : dir === "" ? undefined : nearest(parentOf(dir));
|
|
34
|
+
named.set(dir, found);
|
|
35
|
+
return found;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (path) => {
|
|
39
|
+
const found = nearest(parentOf(path));
|
|
40
|
+
if (found === undefined) {
|
|
41
|
+
return { package: ".", path };
|
|
42
|
+
}
|
|
43
|
+
return { package: found.name, path: found.at === "" ? path : path.slice(found.at.length + 1) };
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether Node reads each file's `.js` siblings as ECMAScript modules: the `type` of the
|
|
49
|
+
* nearest manifest at or above its directory up to the target root is `module`
|
|
50
|
+
* (https://nodejs.org/api/packages.html#type). One directory is read at most once.
|
|
51
|
+
*/
|
|
52
|
+
export function packageTypeIsModule(host: Host, root: string): (path: string) => boolean {
|
|
53
|
+
const typed = new Map<string, boolean>();
|
|
54
|
+
|
|
55
|
+
const nearest = (dir: string): boolean => {
|
|
56
|
+
const known = typed.get(dir);
|
|
57
|
+
if (known !== undefined) {
|
|
58
|
+
return known;
|
|
59
|
+
}
|
|
60
|
+
const manifest = manifestAt(host, joinPath(root, dir));
|
|
61
|
+
const found =
|
|
62
|
+
manifest !== undefined ? manifest["type"] === "module" : dir !== "" && nearest(parentOf(dir));
|
|
63
|
+
typed.set(dir, found);
|
|
64
|
+
return found;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (path) => nearest(parentOf(path));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The directory one path below the root sits in, and the root itself as the empty path. */
|
|
71
|
+
function parentOf(path: string): string {
|
|
72
|
+
const dir = dirnamePath(path);
|
|
73
|
+
return dir === "." ? "" : dir;
|
|
74
|
+
}
|
package/src/own-body.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isArrowFunction,
|
|
3
|
+
isClassDeclaration,
|
|
4
|
+
isClassExpression,
|
|
5
|
+
isFunctionDeclaration,
|
|
6
|
+
isFunctionExpression,
|
|
7
|
+
isVariableDeclaration,
|
|
8
|
+
} from "@typescript/native/ast";
|
|
9
|
+
import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/sync";
|
|
10
|
+
import { isAmbient } from "./inventory.ts";
|
|
11
|
+
import type { ProjectView } from "./session.ts";
|
|
12
|
+
import { unwrapped } from "./unwrap.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether `symbol`, through its aliases, names a function or class the target writes with a
|
|
16
|
+
* body, which a call or construction runs in place of the platform's `require` or `URL`. Any
|
|
17
|
+
* other answer, an unresolved one included, is the platform's, whose call loads a file.
|
|
18
|
+
*/
|
|
19
|
+
export function hasOwnBody<Brand>(project: ProjectView<Brand>, symbol: TSSymbol): boolean {
|
|
20
|
+
const named = (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : project.queries.aliased(symbol);
|
|
21
|
+
const own = project.ownPaths();
|
|
22
|
+
return (
|
|
23
|
+
typeof named === "object" &&
|
|
24
|
+
named.declarations.some((handle) => {
|
|
25
|
+
const node = own.has(handle.path) ? project.declarationAt(handle)?.node : undefined;
|
|
26
|
+
const value =
|
|
27
|
+
node !== undefined && isVariableDeclaration(node) && node.initializer !== undefined
|
|
28
|
+
? unwrapped(node.initializer)
|
|
29
|
+
: node;
|
|
30
|
+
return (
|
|
31
|
+
value !== undefined &&
|
|
32
|
+
(isFunctionDeclaration(value)
|
|
33
|
+
? value.body !== undefined
|
|
34
|
+
: isFunctionExpression(value) ||
|
|
35
|
+
isArrowFunction(value) ||
|
|
36
|
+
isClassDeclaration(value) ||
|
|
37
|
+
isClassExpression(value)) &&
|
|
38
|
+
!isAmbient(value.getSourceFile(), value)
|
|
39
|
+
);
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
}
|
package/src/position.ts
CHANGED
|
@@ -48,6 +48,48 @@ export function writtenLength(file: SourceFile): number {
|
|
|
48
48
|
return isComponentFile(file) ? file.originalText.length : file.text.length;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/** Whether one character ends a line, under the language's line terminators. */
|
|
52
|
+
function endsLine(char: string | undefined): boolean {
|
|
53
|
+
return char === "\n" || char === "\r" || char === "\u2028" || char === "\u2029";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The offset where the line holding `from` ends. */
|
|
57
|
+
export function lineEnd(text: string, from: number): number {
|
|
58
|
+
let at = from;
|
|
59
|
+
while (at < text.length && !endsLine(text[at])) {
|
|
60
|
+
at += 1;
|
|
61
|
+
}
|
|
62
|
+
return at;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The offset of the first token at or after `from`: whitespace, comments and a leading
|
|
67
|
+
* hashbang are trivia.
|
|
68
|
+
*/
|
|
69
|
+
export function tokenStart(text: string, from: number): number {
|
|
70
|
+
let at = from;
|
|
71
|
+
if (at === 0 && text.startsWith("#!")) {
|
|
72
|
+
at = lineEnd(text, 0);
|
|
73
|
+
}
|
|
74
|
+
for (;;) {
|
|
75
|
+
if (/\s/u.test(text[at] ?? "")) {
|
|
76
|
+
at += 1;
|
|
77
|
+
} else if (text.startsWith("//", at)) {
|
|
78
|
+
at = lineEnd(text, at);
|
|
79
|
+
} else if (text.startsWith("/*", at)) {
|
|
80
|
+
const close = text.indexOf("*/", at + 2);
|
|
81
|
+
at = close < 0 ? text.length : close + 2;
|
|
82
|
+
} else {
|
|
83
|
+
return at;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The line, counted from one, of the first token at or after one offset of one file. */
|
|
89
|
+
export function startLine(file: SourceFile, offset: number): number {
|
|
90
|
+
return file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
51
93
|
/**
|
|
52
94
|
* The position of one offset of one file, rendered against the target root.
|
|
53
95
|
*
|
package/src/ref.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The stable symbol reference: the identifier that names one declaration across
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* written.
|
|
8
|
-
*
|
|
9
|
-
* The position key of {@link ./position.ts} is the other identifier and has the
|
|
10
|
-
* other job: it locates a finding inside one run, and the two never mix.
|
|
2
|
+
* The stable symbol reference: the identifier that names one declaration across runs,
|
|
3
|
+
* reports, ignore entries, configured roots and cross-language edges. It is built from
|
|
4
|
+
* names and containers only, so an edit above a declaration leaves it unchanged, and it
|
|
5
|
+
* is compared as bytes, so this module writes the one canonical spelling. The position
|
|
6
|
+
* key of {@link ./position.ts} locates a finding inside one run; the two never mix.
|
|
11
7
|
*/
|
|
12
8
|
|
|
13
9
|
/** The language prefix, which namespaces every reference this analyzer emits. */
|
|
@@ -34,16 +30,10 @@ const SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_FILE}`;
|
|
|
34
30
|
const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_DIRS}package\\.json`;
|
|
35
31
|
|
|
36
32
|
/**
|
|
37
|
-
* One expression per
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* They are written here rather than read from the Contract at run time: the Contract
|
|
43
|
-
* is data a reader outside this program consults, and a product that read it while
|
|
44
|
-
* running would need it installed beside itself. A test compares this list against
|
|
45
|
-
* the release this analyzer is written against, so a grammar that grows a form
|
|
46
|
-
* arrives here as a failing test.
|
|
33
|
+
* One anchored expression per spelling the grammar defines; a reference is valid when it
|
|
34
|
+
* matches one. They are written here rather than read from the Contract at run time, so
|
|
35
|
+
* the product needs no Contract installed beside it; a test compares this list against
|
|
36
|
+
* the release this analyzer implements, so a grammar that grows a form fails it.
|
|
47
37
|
*/
|
|
48
38
|
export const REF_EXPRESSIONS: readonly string[] = [
|
|
49
39
|
`^${SCOPE}#$`,
|
|
@@ -170,7 +160,8 @@ export function aliasFragment(name: string, block?: string): string {
|
|
|
170
160
|
return `${head}${nameComponent(name)}:alias`;
|
|
171
161
|
}
|
|
172
162
|
|
|
173
|
-
|
|
163
|
+
/** The canonical spelling of one component: a name, or a computed key. */
|
|
164
|
+
export function spell(component: Component): string {
|
|
174
165
|
return component.computed ? computedComponent(component.text) : nameComponent(component.text);
|
|
175
166
|
}
|
|
176
167
|
|
package/src/references.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
isSpreadAssignment,
|
|
33
33
|
isSpreadElement,
|
|
34
34
|
isStringLiteral,
|
|
35
|
+
isStringLiteralLikeNode,
|
|
35
36
|
isVoidExpression,
|
|
36
37
|
SyntaxKind,
|
|
37
38
|
type Node,
|
|
@@ -45,13 +46,7 @@ import { contextualUses, type FlowAt, type LiteralAt } from "./contextual-uses.t
|
|
|
45
46
|
import { destructuring, propertyReadsOf, type PropertyRead } from "./destructuring.ts";
|
|
46
47
|
import { specifierCandidates } from "./configuration-files.ts";
|
|
47
48
|
import { namespaceUses } from "./namespace-objects.ts";
|
|
48
|
-
import {
|
|
49
|
-
declarationsByName,
|
|
50
|
-
isParameterProperty,
|
|
51
|
-
nodeKey,
|
|
52
|
-
type Inventory,
|
|
53
|
-
type InventorySymbol,
|
|
54
|
-
} from "./inventory.ts";
|
|
49
|
+
import { isParameterProperty, nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
55
50
|
import { ambientGlobalInterfaces, augmentationReferences } from "./augmentations.ts";
|
|
56
51
|
import { elementAccessReferences } from "./element-access.ts";
|
|
57
52
|
import { indexedAccessReferences } from "./indexed-access.ts";
|
|
@@ -65,6 +60,8 @@ import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./qu
|
|
|
65
60
|
import type { ProjectView } from "./session.ts";
|
|
66
61
|
import { typeParameterReads } from "./type-parameter-reads.ts";
|
|
67
62
|
import { typeQueryAliases } from "./type-query-alias.ts";
|
|
63
|
+
import { hasOwnBody } from "./own-body.ts";
|
|
64
|
+
import { computedLoadsOf, isLoadCall, type LoadCall } from "./unseen-loads.ts";
|
|
68
65
|
import {
|
|
69
66
|
computedKeyNames,
|
|
70
67
|
iterationReferences,
|
|
@@ -77,20 +74,12 @@ import { bytewise } from "./text/compare.ts";
|
|
|
77
74
|
import { globExpression } from "./text/glob.ts";
|
|
78
75
|
|
|
79
76
|
/**
|
|
80
|
-
* Every use one project's own files make of the project's own declarations
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* file's name nodes are gathered whole and handed over together.
|
|
87
|
-
*
|
|
88
|
-
* What the pass records and what it does not: a reference carries its use, its
|
|
89
|
-
* position, the declaration that encloses it and the classification of the file that
|
|
90
|
-
* made it, and every place that imports one of the project's own modules is an
|
|
91
|
-
* evaluation of that module's file. It carries no mode. Which of the references a run
|
|
92
|
-
* counts is the reading the sweep makes of this set, so the mode is one value the run
|
|
93
|
-
* decides once and this pass never sees it.
|
|
77
|
+
* Every use one project's own files make of the project's own declarations: one local walk
|
|
78
|
+
* and one batched resolution per file, never a reference query per symbol, whose answers
|
|
79
|
+
* would depend on the order the symbols are asked in. A reference carries its use, its
|
|
80
|
+
* position, the declaration enclosing it and whether a test file made it; every import of
|
|
81
|
+
* one of the project's modules is an evaluation of that module's file. It carries no mode:
|
|
82
|
+
* which references a run counts is the sweep's reading of this set.
|
|
94
83
|
*/
|
|
95
84
|
|
|
96
85
|
/**
|
|
@@ -468,11 +457,16 @@ interface Evaluation {
|
|
|
468
457
|
*/
|
|
469
458
|
interface ModuleURL {
|
|
470
459
|
readonly specifier: string;
|
|
471
|
-
/** The `URL` the constructor names
|
|
460
|
+
/** The `URL` the constructor names; {@link hasOwnBody} owns which one loads nothing. */
|
|
472
461
|
readonly constructor: Node | undefined;
|
|
473
462
|
readonly at: Node;
|
|
474
463
|
}
|
|
475
464
|
|
|
465
|
+
/** One module URL, and the declaration whose code builds it. */
|
|
466
|
+
interface ModuleURLAt extends ModuleURL {
|
|
467
|
+
readonly from: string;
|
|
468
|
+
}
|
|
469
|
+
|
|
476
470
|
/** One use of a module namespace object that may read every export of the module. */
|
|
477
471
|
interface WholeRead {
|
|
478
472
|
/** The module specifier, whose symbol is the module read. */
|
|
@@ -509,7 +503,8 @@ interface FileSites {
|
|
|
509
503
|
readonly links: readonly Link[];
|
|
510
504
|
readonly evaluations: readonly Evaluation[];
|
|
511
505
|
readonly wholes: readonly WholeRead[];
|
|
512
|
-
readonly urls: readonly
|
|
506
|
+
readonly urls: readonly ModuleURLAt[];
|
|
507
|
+
readonly loadCalls: readonly LoadCall[];
|
|
513
508
|
readonly decorated: readonly Decorated[];
|
|
514
509
|
readonly parameterWrites: readonly ParameterWrite[];
|
|
515
510
|
readonly keyed: readonly KeyedAccess[];
|
|
@@ -559,6 +554,17 @@ function moduleURLOf(node: Node): ModuleURL | undefined {
|
|
|
559
554
|
return undefined;
|
|
560
555
|
}
|
|
561
556
|
|
|
557
|
+
/** The file of the target a relative specifier written in `file` names, by its source or output name. */
|
|
558
|
+
function fileNamed(
|
|
559
|
+
file: SourceFile,
|
|
560
|
+
specifier: string,
|
|
561
|
+
fileIdByName: ReadonlyMap<string, string>,
|
|
562
|
+
): string | undefined {
|
|
563
|
+
return specifierCandidates(dirnamePath(file.fileName), specifier)
|
|
564
|
+
.map((path) => fileIdByName.get(path))
|
|
565
|
+
.find((id) => id !== undefined);
|
|
566
|
+
}
|
|
567
|
+
|
|
562
568
|
/** Whether one node is a string literal or a template literal with no substitution. */
|
|
563
569
|
function isLiteralText(node: Node | undefined): node is Node {
|
|
564
570
|
return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
|
|
@@ -698,7 +704,8 @@ function sitesOf(
|
|
|
698
704
|
const links: Link[] = [];
|
|
699
705
|
const evaluations: Evaluation[] = [];
|
|
700
706
|
const wholes: WholeRead[] = [];
|
|
701
|
-
const urls:
|
|
707
|
+
const urls: ModuleURLAt[] = [];
|
|
708
|
+
const loadCalls: LoadCall[] = [];
|
|
702
709
|
const decorated: Decorated[] = [];
|
|
703
710
|
const parameterWrites: ParameterWrite[] = [];
|
|
704
711
|
const namespaces = namespaceUses(file);
|
|
@@ -781,7 +788,10 @@ function sitesOf(
|
|
|
781
788
|
}
|
|
782
789
|
const url = moduleURLOf(node);
|
|
783
790
|
if (url !== undefined) {
|
|
784
|
-
urls.push(url);
|
|
791
|
+
urls.push({ ...url, from: enclosing });
|
|
792
|
+
}
|
|
793
|
+
if (isLoadCall(node)) {
|
|
794
|
+
loadCalls.push({ call: node, from: enclosing });
|
|
785
795
|
}
|
|
786
796
|
const held = declarations.get(nodeKey(file, node));
|
|
787
797
|
const outer = enclosing;
|
|
@@ -815,6 +825,7 @@ function sitesOf(
|
|
|
815
825
|
evaluations,
|
|
816
826
|
wholes,
|
|
817
827
|
urls,
|
|
828
|
+
loadCalls,
|
|
818
829
|
decorated,
|
|
819
830
|
parameterWrites,
|
|
820
831
|
keyed,
|
|
@@ -1005,20 +1016,12 @@ function aloneIn(
|
|
|
1005
1016
|
}
|
|
1006
1017
|
|
|
1007
1018
|
/**
|
|
1008
|
-
* Every reference one project's own files make to
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
* walks the consumer's files instead and renders against its root, as
|
|
1015
|
-
* {@link ReferenceOptions.consumer} states.
|
|
1016
|
-
*
|
|
1017
|
-
* The order is by the referencing position, then by the declaration named, the
|
|
1018
|
-
* declaration referencing it and the use. That is a total order because one name
|
|
1019
|
-
* references one declaration once from one declaration, while a chain of aliases reaches
|
|
1020
|
-
* several declarations at one position and a bare star re-export's specifier both reads
|
|
1021
|
-
* and evaluates the module it names.
|
|
1019
|
+
* Every reference one project's own files make to its own declarations, and the files the
|
|
1020
|
+
* test-file rules classified. `held` is the same project's inventory, where a resolved symbol
|
|
1021
|
+
* is looked up; positions render against `targetRoot`, or a consumer's root as
|
|
1022
|
+
* {@link ReferenceOptions.consumer} states. The order is by position, then the declaration
|
|
1023
|
+
* named, the referencing declaration and the use: an alias chain or a bare star re-export
|
|
1024
|
+
* names several at one position, and one name references one declaration once.
|
|
1022
1025
|
*/
|
|
1023
1026
|
export function references<Brand>(
|
|
1024
1027
|
project: ProjectView<Brand>,
|
|
@@ -1132,7 +1135,7 @@ export function references<Brand>(
|
|
|
1132
1135
|
* live. A name a namespace import binds may be a use of any file.
|
|
1133
1136
|
*/
|
|
1134
1137
|
const named = (node: Node): readonly string[] => {
|
|
1135
|
-
const byName =
|
|
1138
|
+
const byName = held.byName;
|
|
1136
1139
|
const text = node.getText();
|
|
1137
1140
|
const imported = importedNames(node.getSourceFile()).get(text) ?? [];
|
|
1138
1141
|
return [
|
|
@@ -1161,7 +1164,7 @@ export function references<Brand>(
|
|
|
1161
1164
|
aliased.set(
|
|
1162
1165
|
id,
|
|
1163
1166
|
symbol === UNANSWERED
|
|
1164
|
-
? (
|
|
1167
|
+
? (held.byName.get(alias.name) ?? []).map((one) => ({
|
|
1165
1168
|
id: one,
|
|
1166
1169
|
stepped: true,
|
|
1167
1170
|
guessed: true,
|
|
@@ -1212,6 +1215,9 @@ export function references<Brand>(
|
|
|
1212
1215
|
const fileIdByName = new Map<string, string>(
|
|
1213
1216
|
walked.filter((one) => fileIds.has(one.fileId)).map((one) => [one.file.fileName, one.fileId]),
|
|
1214
1217
|
);
|
|
1218
|
+
const loadable = walked
|
|
1219
|
+
.filter((one) => fileIds.has(one.fileId))
|
|
1220
|
+
.map(({ file, fileId }) => ({ file, fileId }));
|
|
1215
1221
|
const contextual = contextualUses(
|
|
1216
1222
|
project,
|
|
1217
1223
|
held,
|
|
@@ -1311,11 +1317,13 @@ export function references<Brand>(
|
|
|
1311
1317
|
evaluations,
|
|
1312
1318
|
wholes,
|
|
1313
1319
|
urls,
|
|
1320
|
+
loadCalls,
|
|
1314
1321
|
decorated,
|
|
1315
1322
|
parameterWrites,
|
|
1316
1323
|
keyed,
|
|
1317
1324
|
} of walked) {
|
|
1318
1325
|
const test = testFile.get(file) === true;
|
|
1326
|
+
const loads = computedLoadsOf(file, loadCalls, loadable);
|
|
1319
1327
|
// A component file's markup may use any binding its blocks declare at the top level.
|
|
1320
1328
|
const markup = isComponentFile(file);
|
|
1321
1329
|
const bindings = markup ? importBindings(file) : [];
|
|
@@ -1359,6 +1367,10 @@ export function references<Brand>(
|
|
|
1359
1367
|
...wholes.map((whole) => whole.specifier),
|
|
1360
1368
|
...urls.flatMap((url) => url.constructor ?? []),
|
|
1361
1369
|
...bindings.map((binding) => binding.node),
|
|
1370
|
+
...loads.flatMap((load) => [
|
|
1371
|
+
...(load.require === undefined ? [] : [load.require]),
|
|
1372
|
+
...(load.asset ? [] : load.files.map((loaded) => loaded.file)),
|
|
1373
|
+
]),
|
|
1362
1374
|
]),
|
|
1363
1375
|
];
|
|
1364
1376
|
const answered = new Map<Node, Answer<TSSymbol>>();
|
|
@@ -1400,6 +1412,22 @@ export function references<Brand>(
|
|
|
1400
1412
|
}
|
|
1401
1413
|
}
|
|
1402
1414
|
|
|
1415
|
+
// A script declares no module, so the checker names nothing for a specifier of one;
|
|
1416
|
+
// the compiler's module resolution still names its file.
|
|
1417
|
+
const unnamed = evaluations.flatMap(({ node }) =>
|
|
1418
|
+
answered.get(node) === undefined &&
|
|
1419
|
+
isStringLiteralLikeNode(node) &&
|
|
1420
|
+
queriedFile(file, node, fileIdByName) === undefined
|
|
1421
|
+
? [node]
|
|
1422
|
+
: [],
|
|
1423
|
+
);
|
|
1424
|
+
const resolved = project.resolvedFileNames(unnamed);
|
|
1425
|
+
const scripts = new Map<Node, string | undefined>(
|
|
1426
|
+
unnamed.map((node, at) => {
|
|
1427
|
+
const name = resolved[at];
|
|
1428
|
+
return [node, name === undefined ? undefined : fileIdByName.get(name)];
|
|
1429
|
+
}),
|
|
1430
|
+
);
|
|
1403
1431
|
for (const evaluation of evaluations) {
|
|
1404
1432
|
const queried = queriedFile(file, evaluation.node, fileIdByName);
|
|
1405
1433
|
if (queried !== undefined) {
|
|
@@ -1413,6 +1441,17 @@ export function references<Brand>(
|
|
|
1413
1441
|
});
|
|
1414
1442
|
}
|
|
1415
1443
|
const module = answered.get(evaluation.node);
|
|
1444
|
+
const script = scripts.get(evaluation.node);
|
|
1445
|
+
if (script !== undefined) {
|
|
1446
|
+
found.push({
|
|
1447
|
+
from: evaluation.from,
|
|
1448
|
+
to: script,
|
|
1449
|
+
position: renderPosition(file, root, evaluation.node.getStart()),
|
|
1450
|
+
use: "evaluation",
|
|
1451
|
+
resolution: "syntax",
|
|
1452
|
+
test,
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1416
1455
|
if (module === undefined) {
|
|
1417
1456
|
continue;
|
|
1418
1457
|
}
|
|
@@ -1435,21 +1474,13 @@ export function references<Brand>(
|
|
|
1435
1474
|
|
|
1436
1475
|
for (const url of urls) {
|
|
1437
1476
|
const constructor = url.constructor === undefined ? undefined : answered.get(url.constructor);
|
|
1438
|
-
|
|
1439
|
-
// library's declaration; a `URL` the target declares has no other.
|
|
1440
|
-
if (
|
|
1441
|
-
typeof constructor === "object" &&
|
|
1442
|
-
constructor.declarations.length > 0 &&
|
|
1443
|
-
constructor.declarations.every((handle) => ownPaths.has(handle.path))
|
|
1444
|
-
) {
|
|
1477
|
+
if (typeof constructor === "object" && hasOwnBody(project, constructor)) {
|
|
1445
1478
|
continue;
|
|
1446
1479
|
}
|
|
1447
|
-
const loaded =
|
|
1448
|
-
.map((path) => fileIdByName.get(path))
|
|
1449
|
-
.find((id) => id !== undefined);
|
|
1480
|
+
const loaded = fileNamed(file, url.specifier, fileIdByName);
|
|
1450
1481
|
if (loaded !== undefined) {
|
|
1451
1482
|
found.push({
|
|
1452
|
-
from:
|
|
1483
|
+
from: url.from,
|
|
1453
1484
|
to: loaded,
|
|
1454
1485
|
position: renderPosition(file, root, url.at.getStart()),
|
|
1455
1486
|
use: "evaluation",
|
|
@@ -1459,6 +1490,38 @@ export function references<Brand>(
|
|
|
1459
1490
|
}
|
|
1460
1491
|
}
|
|
1461
1492
|
|
|
1493
|
+
// A computed load imports each file it loads where the call is written, reading what
|
|
1494
|
+
// the file exports unless it reads the file as text or a URL; hasOwnBody owns which
|
|
1495
|
+
// `require` loads nothing.
|
|
1496
|
+
for (const load of loads) {
|
|
1497
|
+
const callee = load.require === undefined ? undefined : answered.get(load.require);
|
|
1498
|
+
if (typeof callee === "object" && hasOwnBody(project, callee)) {
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
const position = renderPosition(file, root, load.call.getStart());
|
|
1502
|
+
for (const loaded of load.files) {
|
|
1503
|
+
found.push({
|
|
1504
|
+
from: load.eager ? fileId : load.from,
|
|
1505
|
+
to: loaded.fileId,
|
|
1506
|
+
position,
|
|
1507
|
+
use: "evaluation",
|
|
1508
|
+
resolution: "syntax",
|
|
1509
|
+
test,
|
|
1510
|
+
});
|
|
1511
|
+
const module = load.asset ? undefined : answered.get(loaded.file);
|
|
1512
|
+
for (const target of module === undefined ? [] : exportsRead(module)) {
|
|
1513
|
+
found.push({
|
|
1514
|
+
from: load.from,
|
|
1515
|
+
to: target.id,
|
|
1516
|
+
position,
|
|
1517
|
+
use: "read",
|
|
1518
|
+
resolution: target.guessed ? "by-name" : "batch",
|
|
1519
|
+
test,
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1462
1525
|
for (const whole of wholes) {
|
|
1463
1526
|
const module = answered.get(whole.specifier);
|
|
1464
1527
|
if (module === undefined) {
|
|
@@ -1529,7 +1592,7 @@ export function references<Brand>(
|
|
|
1529
1592
|
const position = renderPosition(file, root, read.key.getStart());
|
|
1530
1593
|
const targets =
|
|
1531
1594
|
symbol === UNANSWERED
|
|
1532
|
-
? (
|
|
1595
|
+
? (held.byName.get(read.name) ?? []).map((id) => ({
|
|
1533
1596
|
id,
|
|
1534
1597
|
stepped: false,
|
|
1535
1598
|
guessed: true,
|
package/src/session.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
Symbol as TSSymbol,
|
|
14
14
|
TimingInfo,
|
|
15
15
|
} from "@typescript/native/sync";
|
|
16
|
-
import type { Node, SourceFile } from "@typescript/native/ast";
|
|
16
|
+
import type { Node, SourceFile, StringLiteralLikeNode } from "@typescript/native/ast";
|
|
17
17
|
import { componentLayer, mapperManifest } from "./component-layer.ts";
|
|
18
18
|
import type { Host, TemporaryDirectory } from "./host.ts";
|
|
19
19
|
import { joinPath } from "./paths.ts";
|
|
@@ -242,6 +242,11 @@ export interface ProjectView<Brand> {
|
|
|
242
242
|
* file it is in.
|
|
243
243
|
*/
|
|
244
244
|
ownPaths(): ReadonlySet<string>;
|
|
245
|
+
/**
|
|
246
|
+
* The file the compiler's module resolution names for each module specifier of this
|
|
247
|
+
* project's files, in order, or `undefined` where it names none.
|
|
248
|
+
*/
|
|
249
|
+
resolvedFileNames(specifiers: readonly StringLiteralLikeNode[]): (string | undefined)[];
|
|
245
250
|
/** A handle to one node of this project's program. */
|
|
246
251
|
handle(node: Node): Handle<Brand>;
|
|
247
252
|
/**
|
|
@@ -347,6 +352,17 @@ function viewOf<Brand>(
|
|
|
347
352
|
ownKeys ??= new Set(ownSourceFiles().map((file) => file.path));
|
|
348
353
|
return ownKeys;
|
|
349
354
|
},
|
|
355
|
+
resolvedFileNames: (specifiers) =>
|
|
356
|
+
batched(engine, specifiers, (node) =>
|
|
357
|
+
project.program.getResolvedModuleFromModuleSpecifier.gen(
|
|
358
|
+
node,
|
|
359
|
+
node.getSourceFile().fileName,
|
|
360
|
+
),
|
|
361
|
+
).map((resolved) =>
|
|
362
|
+
resolved === undefined || resolved.resolvedFileName === ""
|
|
363
|
+
? undefined
|
|
364
|
+
: resolved.resolvedFileName,
|
|
365
|
+
),
|
|
350
366
|
handle: (node) => ({ node }),
|
|
351
367
|
symbolsAt: (handles, cap) =>
|
|
352
368
|
queries.symbolsAt(
|