@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/dist/src/unseen-loads.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The loads
|
|
3
|
-
* `import()` or `require()` call whose
|
|
4
|
-
* naming a directory below the calling file's. The
|
|
5
|
-
* analysis does not see, so the configuration's
|
|
2
|
+
* The loads by a computed path: those whose files the analysis resolves, and those of a
|
|
3
|
+
* project's production files it cannot resolve, an `import()` or `require()` call whose
|
|
4
|
+
* path starts with no relative literal naming a directory below the calling file's. The
|
|
5
|
+
* files such a load runs are roots the analysis does not see, so the configuration's
|
|
6
|
+
* roots are never complete.
|
|
6
7
|
*/
|
|
7
|
-
import { isBinaryExpression, isCallExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isStringLiteral, isTemplateExpression, SyntaxKind, } from "@typescript/native/ast";
|
|
8
|
+
import { isBinaryExpression, isCallExpression, isIdentifier, isMetaProperty, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isStringLiteral, isTemplateExpression, SyntaxKind, } from "@typescript/native/ast";
|
|
9
|
+
import { literalsOf } from "./entry-points.js";
|
|
10
|
+
import { hasOwnBody } from "./own-body.js";
|
|
8
11
|
import { dirnamePath, relativePath, resolvePath } from "./paths.js";
|
|
9
12
|
import { renderPosition } from "./position.js";
|
|
10
|
-
import { UNANSWERED } from "./query.js";
|
|
11
13
|
import { sourceFilesOf } from "./source-files.js";
|
|
12
14
|
import { unwrapped } from "./unwrap.js";
|
|
15
|
+
import { globExpression } from "./text/glob.js";
|
|
13
16
|
/** The literal text a computed path starts with: a template's head, or a concatenation's first literal. */
|
|
14
|
-
|
|
17
|
+
function leadingLiteral(node) {
|
|
15
18
|
const at = unwrapped(node);
|
|
16
19
|
if (isTemplateExpression(at)) {
|
|
17
20
|
return at.head.text;
|
|
@@ -25,17 +28,107 @@ export function leadingLiteral(node) {
|
|
|
25
28
|
return undefined;
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
|
-
* The directory below `dir` a computed load's leading literal names,
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
+
* The directory below `dir` a computed load's leading literal names, every file of which
|
|
32
|
+
* the load reaches, or none where it names a package, `dir` itself or a directory holding
|
|
33
|
+
* it, each of which would name every sibling.
|
|
31
34
|
*/
|
|
32
|
-
|
|
35
|
+
function computedLoadDirectory(dir, prefix) {
|
|
33
36
|
if (!(prefix.startsWith("./") || prefix.startsWith("../")) || !prefix.endsWith("/")) {
|
|
34
37
|
return undefined;
|
|
35
38
|
}
|
|
36
39
|
const target = resolvePath(dir, prefix);
|
|
37
40
|
return target === dir || relativePath(target, dir) !== undefined ? undefined : target;
|
|
38
41
|
}
|
|
42
|
+
/** Whether one node is a call that may load a module by a computed path. */
|
|
43
|
+
export function isLoadCall(node) {
|
|
44
|
+
if (!isCallExpression(node)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const callee = node.expression;
|
|
48
|
+
return (callee.kind === SyntaxKind.ImportKeyword ||
|
|
49
|
+
(isIdentifier(callee) && callee.text === "require") ||
|
|
50
|
+
isMetaGlob(node));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The resolved computed loads among one file's load calls, each loading the `candidates`
|
|
54
|
+
* but the file itself: an `import()` or `require()` whose path starts with a relative
|
|
55
|
+
* literal naming a directory below the file's, every candidate below it, and an
|
|
56
|
+
* `import.meta.glob` of relative literal globs, every candidate one matches, each read
|
|
57
|
+
* against the file's directory, as a bundler's context module reads them.
|
|
58
|
+
*/
|
|
59
|
+
export function computedLoadsOf(file, calls, candidates) {
|
|
60
|
+
const dir = dirnamePath(file.fileName);
|
|
61
|
+
const others = candidates.filter((candidate) => candidate.file !== file);
|
|
62
|
+
return calls.flatMap(({ call, from }) => {
|
|
63
|
+
const [first, options] = call.arguments;
|
|
64
|
+
if (first === undefined) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
if (isMetaGlob(call)) {
|
|
68
|
+
const globs = (literalsOf(first) ?? [])
|
|
69
|
+
.filter((pattern) => pattern.startsWith("./") || pattern.startsWith("../"))
|
|
70
|
+
.map((pattern) => globExpression(resolvePath(dir, pattern)));
|
|
71
|
+
const files = others.filter((candidate) => globs.some((glob) => glob.test(candidate.file.fileName)));
|
|
72
|
+
const asset = readsAsAsset(options);
|
|
73
|
+
return [{ call, from, require: undefined, files, asset, eager: isEager(options) }];
|
|
74
|
+
}
|
|
75
|
+
const prefix = leadingLiteral(first);
|
|
76
|
+
const target = prefix === undefined ? undefined : computedLoadDirectory(dir, prefix);
|
|
77
|
+
if (target === undefined) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const files = others.filter((candidate) => relativePath(target, candidate.file.fileName) !== undefined);
|
|
81
|
+
const callee = call.expression;
|
|
82
|
+
const require = isIdentifier(callee) ? callee : undefined;
|
|
83
|
+
return [{ call, from, require, files, asset: false, eager: false }];
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** Whether one call is `import.meta.glob(...)`. */
|
|
87
|
+
function isMetaGlob(node) {
|
|
88
|
+
if (!isPropertyAccessExpression(node.expression)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const { expression, name } = node.expression;
|
|
92
|
+
return (isIdentifier(name) &&
|
|
93
|
+
name.text === "glob" &&
|
|
94
|
+
isMetaProperty(expression) &&
|
|
95
|
+
expression.keywordToken === SyntaxKind.ImportKeyword);
|
|
96
|
+
}
|
|
97
|
+
/** The values an `import.meta.glob` options object assigns, each by its literal key. */
|
|
98
|
+
function globOptions(options) {
|
|
99
|
+
const literal = options === undefined ? undefined : unwrapped(options);
|
|
100
|
+
if (literal === undefined || !isObjectLiteralExpression(literal)) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
return literal.properties.flatMap((property) => isPropertyAssignment(property) &&
|
|
104
|
+
(isIdentifier(property.name) || isStringLiteral(property.name))
|
|
105
|
+
? [[property.name.text, unwrapped(property.initializer)]]
|
|
106
|
+
: []);
|
|
107
|
+
}
|
|
108
|
+
/** Whether the options of an `import.meta.glob` set `eager` to `true`. */
|
|
109
|
+
function isEager(options) {
|
|
110
|
+
return globOptions(options).some(([key, value]) => key === "eager" && value.kind === SyntaxKind.TrueKeyword);
|
|
111
|
+
}
|
|
112
|
+
/** The query parameters that make Vite load a file as its text or its URL, each written bare. */
|
|
113
|
+
const ASSET_QUERY = /(?:^\?|&)(?:raw|url|inline)(?:&|$)/u;
|
|
114
|
+
/**
|
|
115
|
+
* Whether the options of an `import.meta.glob` read each file as text or as a URL: a
|
|
116
|
+
* `query` string holding a bare `raw`, `url` or `inline`, or an `as` of `raw` or `url`,
|
|
117
|
+
* which Vite reads as that query (https://vite.dev/guide/features.html#glob-import). Any
|
|
118
|
+
* other query is read as loading the module, which keeps more than it may.
|
|
119
|
+
*/
|
|
120
|
+
function readsAsAsset(options) {
|
|
121
|
+
return globOptions(options).some(([key, value]) => {
|
|
122
|
+
if (!isStringLiteral(value) && !isNoSubstitutionTemplateLiteral(value)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (key === "as") {
|
|
126
|
+
return value.text === "raw" || value.text === "url";
|
|
127
|
+
}
|
|
128
|
+
return (key === "query" &&
|
|
129
|
+
ASSET_QUERY.test(value.text.startsWith("?") ? value.text : `?${value.text}`));
|
|
130
|
+
});
|
|
131
|
+
}
|
|
39
132
|
/** Whether a computed path's leading literal names a package, whose files are none of the target's. */
|
|
40
133
|
function namesPackage(prefix) {
|
|
41
134
|
return (prefix !== undefined &&
|
|
@@ -47,8 +140,7 @@ function namesPackage(prefix) {
|
|
|
47
140
|
/**
|
|
48
141
|
* Every unresolved load the project's production files write, in file then source order.
|
|
49
142
|
* `testFiles` holds the test files by their path below the target root. A `require` call
|
|
50
|
-
* loads a module
|
|
51
|
-
* files: one the project declares is its own function.
|
|
143
|
+
* loads a module unless {@link hasOwnBody} reads it as the project's own function.
|
|
52
144
|
*/
|
|
53
145
|
export function unseenLoadsOf(project, configuration, targetRoot, testFiles) {
|
|
54
146
|
const tests = new Set(testFiles);
|
|
@@ -84,13 +176,10 @@ export function unseenLoadsOf(project, configuration, targetRoot, testFiles) {
|
|
|
84
176
|
file.forEachChild(visit);
|
|
85
177
|
}
|
|
86
178
|
const requires = sites.flatMap((site) => (site.require === undefined ? [] : [site.require]));
|
|
87
|
-
const own = project.ownPaths();
|
|
88
179
|
const local = new Set();
|
|
89
180
|
project.symbolsAt(requires.map((node) => project.handle(node))).forEach((symbol, at) => {
|
|
90
181
|
const node = requires[at];
|
|
91
|
-
if (node !== undefined &&
|
|
92
|
-
symbol !== UNANSWERED &&
|
|
93
|
-
symbol?.declarations.some((handle) => own.has(handle.path)) === true) {
|
|
182
|
+
if (node !== undefined && typeof symbol === "object" && hasOwnBody(project, symbol)) {
|
|
94
183
|
local.add(node);
|
|
95
184
|
}
|
|
96
185
|
});
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/deadset-ts",
|
|
3
|
-
"version": "8.0.0-dev.
|
|
3
|
+
"version": "8.0.0-dev.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Deterministic dead-code analysis for TypeScript, class and type members included",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@stryker-mutator/typescript-checker": "10.1.1",
|
|
44
44
|
"@stryker-mutator/vitest-runner": "10.1.1",
|
|
45
45
|
"@types/node": "24.19.2",
|
|
46
|
-
"eslint": "10.
|
|
46
|
+
"eslint": "10.14.0",
|
|
47
47
|
"fast-check": "4.10.2",
|
|
48
48
|
"prettier": "3.9.10",
|
|
49
49
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
package/src/alias-chain.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
isNamespaceImport,
|
|
15
15
|
} from "@typescript/native/ast";
|
|
16
16
|
import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/sync";
|
|
17
|
-
import {
|
|
17
|
+
import { isParameterProperty, nodeKey, type Inventory } from "./inventory.ts";
|
|
18
18
|
import { UNANSWERED, type Answer } from "./query.ts";
|
|
19
19
|
import type { ProjectView } from "./session.ts";
|
|
20
20
|
|
|
@@ -132,7 +132,7 @@ export function aliasChains<Brand>(project: ProjectView<Brand>, held: Inventory)
|
|
|
132
132
|
names.add(node.expression.text);
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
-
const byName =
|
|
135
|
+
const byName = held.byName;
|
|
136
136
|
const ids = [...names].flatMap((name) => byName.get(name) ?? []);
|
|
137
137
|
return [...new Set([...ids, ...(module ? files : [])])].map((id) => ({
|
|
138
138
|
id,
|
package/src/analysis.ts
CHANGED
|
@@ -51,7 +51,8 @@ import { implementations, mergeImplementations } from "./implementations.ts";
|
|
|
51
51
|
import { injectionContainer } from "./injection-container.ts";
|
|
52
52
|
import { interfaceSatisfaction, settledSatisfaction } from "./interface-satisfaction.ts";
|
|
53
53
|
import { intraFunctionFacts, projectParts, type ProjectParts } from "./intra-function-parts.ts";
|
|
54
|
-
import {
|
|
54
|
+
import { type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
55
|
+
import { inventory } from "./inventory/enumerate.ts";
|
|
55
56
|
import { readManifest, type ManifestEntry } from "./manifest.ts";
|
|
56
57
|
import {
|
|
57
58
|
liveFilesAt,
|
|
@@ -4,7 +4,7 @@ export const RECORD = {
|
|
|
4
4
|
conformance: {
|
|
5
5
|
corpusVersion: "6.0.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:4a66f7f01b0f1a23cf843b77ef48d762f2dcf486153e665560f107f10b0ea838",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
} as const;
|
package/src/consumers.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PartialConfig } from "./config.ts";
|
|
2
2
|
import type { Host } from "./host.ts";
|
|
3
|
-
import { inventory } from "./inventory.ts";
|
|
3
|
+
import { inventory } from "./inventory/enumerate.ts";
|
|
4
4
|
import { relativePath } from "./paths.ts";
|
|
5
5
|
import { references, type ConsumerSide, type Reference } from "./references.ts";
|
|
6
6
|
import { narrowedTo, type ProjectView } from "./session.ts";
|
|
@@ -1297,6 +1297,7 @@ export const FAILURE_CLASSES = {
|
|
|
1297
1297
|
"unreadable-document": "setup",
|
|
1298
1298
|
"missing-import-path": "setup",
|
|
1299
1299
|
"binding-undecodable": "setup",
|
|
1300
|
+
"unsupported-language": "setup",
|
|
1300
1301
|
"unwritten-output": "run",
|
|
1301
1302
|
"unreportable-path": "run",
|
|
1302
1303
|
"memory-exhausted": "run",
|
package/src/dependencies.ts
CHANGED
|
@@ -41,7 +41,8 @@ import {
|
|
|
41
41
|
} from "./configuration-files.ts";
|
|
42
42
|
import type { FindingPosition } from "./finding.ts";
|
|
43
43
|
import type { Host } from "./host.ts";
|
|
44
|
-
import { nodeKey,
|
|
44
|
+
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
45
|
+
import { packageScope } from "./load/package-scope.ts";
|
|
45
46
|
import {
|
|
46
47
|
ancestors,
|
|
47
48
|
dirnamePath,
|
package/src/entry-points.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The files a tool or the platform enters without any import naming them
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* literal.
|
|
6
|
-
*
|
|
7
|
-
* Each rule is read from the program and nothing else: a file name the tool
|
|
8
|
-
* itself looks for, a literal written in a configuration file, or a literal
|
|
9
|
-
* argument of a call the checker resolves to the platform's own declaration. A
|
|
10
|
-
* value that is not a literal is not evaluated and not guessed, so a file it would
|
|
11
|
-
* have named is entered by nothing here.
|
|
2
|
+
* The files a tool or the platform enters without any import naming them, each by a name
|
|
3
|
+
* the tool looks for or a literal it reads. A value that is not a literal is not evaluated
|
|
4
|
+
* or guessed, so a file it would have named is entered by nothing here.
|
|
12
5
|
*/
|
|
13
6
|
|
|
14
7
|
import {
|
|
@@ -23,7 +16,6 @@ import {
|
|
|
23
16
|
isPropertyAccessExpression,
|
|
24
17
|
isPropertyAssignment,
|
|
25
18
|
isStringLiteral,
|
|
26
|
-
SyntaxKind,
|
|
27
19
|
type Node,
|
|
28
20
|
type SourceFile,
|
|
29
21
|
} from "@typescript/native/ast";
|
|
@@ -39,11 +31,11 @@ import { buildScriptEntries } from "./build-scripts.ts";
|
|
|
39
31
|
import type { Host } from "./host.ts";
|
|
40
32
|
import { htmlEntries } from "./html-entries.ts";
|
|
41
33
|
import { documentEntries } from "./mdx-entries.ts";
|
|
34
|
+
import { hasOwnBody } from "./own-body.ts";
|
|
42
35
|
import { dirnamePath, relativePath, resolvePath } from "./paths.ts";
|
|
43
36
|
import { UNANSWERED } from "./query.ts";
|
|
44
37
|
import type { ProjectView } from "./session.ts";
|
|
45
38
|
import type { SourceFiles } from "./source-files.ts";
|
|
46
|
-
import { computedLoadDirectory, leadingLiteral } from "./unseen-loads.ts";
|
|
47
39
|
import { workflowEntries } from "./workflow-steps.ts";
|
|
48
40
|
import { unwrapped } from "./unwrap.ts";
|
|
49
41
|
import { globExpression } from "./text/glob.ts";
|
|
@@ -66,12 +58,6 @@ export type EntryRule =
|
|
|
66
58
|
| "html-entry"
|
|
67
59
|
/** A file a `run:` step of a workflow in the target root's `.github/workflows` names. */
|
|
68
60
|
| "workflow-step"
|
|
69
|
-
/**
|
|
70
|
-
* A file a load whose path is computed may name: below the directory the literal
|
|
71
|
-
* start of an `import()` or `require()` argument names, or matched by the literal
|
|
72
|
-
* glob of `import.meta.glob`.
|
|
73
|
-
*/
|
|
74
|
-
| "computed-import"
|
|
75
61
|
/** A file an import line of a Markdown or MDX document below the target root names. */
|
|
76
62
|
| "document-import";
|
|
77
63
|
|
|
@@ -216,7 +202,7 @@ function memberOf(object: Node, name: string): Node | undefined {
|
|
|
216
202
|
* The strings one member holds, when it is written as a literal or an array of
|
|
217
203
|
* literals. `undefined` is a value the text does not state.
|
|
218
204
|
*/
|
|
219
|
-
function literalsOf(value: Node): readonly string[] | undefined {
|
|
205
|
+
export function literalsOf(value: Node): readonly string[] | undefined {
|
|
220
206
|
const at = unwrapped(value);
|
|
221
207
|
const single = literalOf(at);
|
|
222
208
|
if (single !== undefined) {
|
|
@@ -244,11 +230,6 @@ function isRelative(text: string): boolean {
|
|
|
244
230
|
return text !== "" && !text.startsWith("/") && !URL_SCHEME.test(text);
|
|
245
231
|
}
|
|
246
232
|
|
|
247
|
-
/** Whether a module specifier is relative, which a bare name is not: it names a package. */
|
|
248
|
-
function isModuleRelative(text: string): boolean {
|
|
249
|
-
return text.startsWith("./") || text.startsWith("../");
|
|
250
|
-
}
|
|
251
|
-
|
|
252
233
|
/** A URL reference with its query and its fragment removed, which name no part of the file. */
|
|
253
234
|
function pathPart(text: string): string {
|
|
254
235
|
const cut = text.search(/[?#]/u);
|
|
@@ -396,6 +377,8 @@ function browserTestsOf(file: SourceFile, files: SourceFiles): readonly EntryPoi
|
|
|
396
377
|
interface WorkerSite {
|
|
397
378
|
readonly literal: string;
|
|
398
379
|
readonly platformNames: readonly Node[];
|
|
380
|
+
/** The `URL` the address is built with; {@link hasOwnBody} owns which one names no file. */
|
|
381
|
+
readonly url: Node | undefined;
|
|
399
382
|
}
|
|
400
383
|
|
|
401
384
|
/**
|
|
@@ -405,14 +388,14 @@ interface WorkerSite {
|
|
|
405
388
|
*/
|
|
406
389
|
function workerAddress(
|
|
407
390
|
argument: Node | undefined,
|
|
408
|
-
): { readonly literal: string; readonly
|
|
391
|
+
): { readonly literal: string; readonly url: Node | undefined } | undefined {
|
|
409
392
|
if (argument === undefined) {
|
|
410
393
|
return undefined;
|
|
411
394
|
}
|
|
412
395
|
const at = unwrapped(argument);
|
|
413
396
|
const literal = literalOf(at);
|
|
414
397
|
if (literal !== undefined) {
|
|
415
|
-
return { literal,
|
|
398
|
+
return { literal, url: undefined };
|
|
416
399
|
}
|
|
417
400
|
if (!isNewExpression(at) || !isIdentifier(at.expression) || at.expression.text !== "URL") {
|
|
418
401
|
return undefined;
|
|
@@ -431,7 +414,7 @@ function workerAddress(
|
|
|
431
414
|
) {
|
|
432
415
|
return undefined;
|
|
433
416
|
}
|
|
434
|
-
return { literal: text,
|
|
417
|
+
return { literal: text, url: at.expression };
|
|
435
418
|
}
|
|
436
419
|
|
|
437
420
|
/**
|
|
@@ -451,7 +434,8 @@ function workerSitesOf(file: SourceFile): readonly WorkerSite[] {
|
|
|
451
434
|
if (address !== undefined) {
|
|
452
435
|
found.push({
|
|
453
436
|
literal: address.literal,
|
|
454
|
-
platformNames: [node.expression
|
|
437
|
+
platformNames: [node.expression],
|
|
438
|
+
url: address.url,
|
|
455
439
|
});
|
|
456
440
|
}
|
|
457
441
|
}
|
|
@@ -471,7 +455,8 @@ function workerSitesOf(file: SourceFile): readonly WorkerSite[] {
|
|
|
471
455
|
if (address !== undefined) {
|
|
472
456
|
found.push({
|
|
473
457
|
literal: address.literal,
|
|
474
|
-
platformNames: [callee.expression.expression
|
|
458
|
+
platformNames: [callee.expression.expression],
|
|
459
|
+
url: address.url,
|
|
475
460
|
});
|
|
476
461
|
}
|
|
477
462
|
}
|
|
@@ -483,10 +468,10 @@ function workerSitesOf(file: SourceFile): readonly WorkerSite[] {
|
|
|
483
468
|
}
|
|
484
469
|
|
|
485
470
|
/**
|
|
486
|
-
* The workers one file addresses. A call's
|
|
487
|
-
* declares, which a file of the target may augment and no
|
|
488
|
-
* the target declares is the target's own constructor or
|
|
489
|
-
* worker this rule knows. One batch per file that writes such a call.
|
|
471
|
+
* The workers one file addresses. A call's `Worker`, `SharedWorker` or `navigator` must
|
|
472
|
+
* resolve to a symbol the platform declares, which a file of the target may augment and no
|
|
473
|
+
* import brings in; a name only the target declares is the target's own constructor or
|
|
474
|
+
* object, which addresses no worker this rule knows. One batch per file that writes such a call.
|
|
490
475
|
*/
|
|
491
476
|
function workersOf<Brand>(
|
|
492
477
|
project: ProjectView<Brand>,
|
|
@@ -498,7 +483,8 @@ function workersOf<Brand>(
|
|
|
498
483
|
return [];
|
|
499
484
|
}
|
|
500
485
|
const names = sites.flatMap((site) => site.platformNames);
|
|
501
|
-
const
|
|
486
|
+
const urls = sites.flatMap((site) => site.url ?? []);
|
|
487
|
+
const symbols = project.symbolsAt([...names, ...urls].map((name) => project.handle(name)));
|
|
502
488
|
const platform = new Set<Node>();
|
|
503
489
|
// A name the checker could not resolve is read as the platform's, so a worker it
|
|
504
490
|
// stands on is rooted rather than reported.
|
|
@@ -516,10 +502,19 @@ function workersOf<Brand>(
|
|
|
516
502
|
platform.add(name);
|
|
517
503
|
}
|
|
518
504
|
});
|
|
505
|
+
urls.forEach((url, index) => {
|
|
506
|
+
const symbol = symbols[names.length + index];
|
|
507
|
+
if (typeof symbol !== "object" || !hasOwnBody(project, symbol)) {
|
|
508
|
+
platform.add(url);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
519
511
|
const dir = dirnamePath(file.fileName);
|
|
520
512
|
const found: EntryPoint[] = [];
|
|
521
513
|
for (const site of sites) {
|
|
522
|
-
if (
|
|
514
|
+
if (
|
|
515
|
+
!site.platformNames.every((name) => platform.has(name)) ||
|
|
516
|
+
(site.url !== undefined && !platform.has(site.url))
|
|
517
|
+
) {
|
|
523
518
|
continue;
|
|
524
519
|
}
|
|
525
520
|
for (const named of filesNamed(files, dir, site.literal)) {
|
|
@@ -529,77 +524,6 @@ function workersOf<Brand>(
|
|
|
529
524
|
return found;
|
|
530
525
|
}
|
|
531
526
|
|
|
532
|
-
/** Whether one call is `import.meta.glob(...)`. */
|
|
533
|
-
function isMetaGlob(node: Node): boolean {
|
|
534
|
-
if (!isCallExpression(node) || !isPropertyAccessExpression(node.expression)) {
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
537
|
-
const { expression, name } = node.expression;
|
|
538
|
-
return (
|
|
539
|
-
isIdentifier(name) &&
|
|
540
|
-
name.text === "glob" &&
|
|
541
|
-
isMetaProperty(expression) &&
|
|
542
|
-
expression.keywordToken === SyntaxKind.ImportKeyword
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* The files one file loads by a computed path, each with what exports: every own file
|
|
548
|
-
* below the directory a relative literal start of an `import()` or `require()` argument
|
|
549
|
-
* names, other than the calling file's own directory or one holding it, which would name
|
|
550
|
-
* every sibling, and
|
|
551
|
-
* every own file a relative literal glob of `import.meta.glob` matches, both read against
|
|
552
|
-
* the calling file's directory, as a bundler's context module reads them.
|
|
553
|
-
*/
|
|
554
|
-
function computedImportsOf(file: SourceFile, files: SourceFiles): readonly EntryPoint[] {
|
|
555
|
-
const dir = dirnamePath(file.fileName);
|
|
556
|
-
const found: EntryPoint[] = [];
|
|
557
|
-
const below = (prefix: string): void => {
|
|
558
|
-
const target = computedLoadDirectory(dir, prefix);
|
|
559
|
-
if (target === undefined) {
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
for (const candidate of files.byName.values()) {
|
|
563
|
-
if (candidate !== file && relativePath(target, candidate.fileName) !== undefined) {
|
|
564
|
-
found.push({ file: candidate, rule: "computed-import", source: prefix, exports: true });
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
};
|
|
568
|
-
const globbed = (pattern: string): void => {
|
|
569
|
-
if (!isModuleRelative(pattern)) {
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
const expression = globExpression(resolvePath(dir, pattern));
|
|
573
|
-
for (const candidate of files.byName.values()) {
|
|
574
|
-
if (candidate !== file && expression.test(candidate.fileName)) {
|
|
575
|
-
found.push({ file: candidate, rule: "computed-import", source: pattern, exports: true });
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
};
|
|
579
|
-
const visit = (node: Node): void => {
|
|
580
|
-
if (isCallExpression(node)) {
|
|
581
|
-
const [first] = node.arguments;
|
|
582
|
-
const callee = node.expression;
|
|
583
|
-
const loads =
|
|
584
|
-
callee.kind === SyntaxKind.ImportKeyword ||
|
|
585
|
-
(isIdentifier(callee) && callee.text === "require");
|
|
586
|
-
if (loads && first !== undefined) {
|
|
587
|
-
const prefix = leadingLiteral(first);
|
|
588
|
-
if (prefix !== undefined) {
|
|
589
|
-
below(prefix);
|
|
590
|
-
}
|
|
591
|
-
} else if (isMetaGlob(node) && first !== undefined) {
|
|
592
|
-
for (const pattern of literalsOf(first) ?? []) {
|
|
593
|
-
globbed(pattern);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
node.forEachChild(visit);
|
|
598
|
-
};
|
|
599
|
-
file.forEachChild(visit);
|
|
600
|
-
return found;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
527
|
/**
|
|
604
528
|
* Every file of one project the rules enter, in no particular order and possibly more
|
|
605
529
|
* than once: a file two rules enter appears once per rule.
|
|
@@ -637,7 +561,6 @@ export function entryPoints<Brand>(
|
|
|
637
561
|
}
|
|
638
562
|
}
|
|
639
563
|
found.push(...workersOf(project, file, files));
|
|
640
|
-
found.push(...computedImportsOf(file, files));
|
|
641
564
|
}
|
|
642
565
|
for (const { file, source } of htmlEntries(host, targetRoot, files, parse)) {
|
|
643
566
|
found.push({ file, rule: "html-entry", source, exports: true });
|
package/src/file-facts.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { SourceFile } from "@typescript/native/ast";
|
|
8
8
|
import type { Host } from "./host.ts";
|
|
9
|
-
import { packageScope } from "./
|
|
9
|
+
import { packageScope } from "./load/package-scope.ts";
|
|
10
10
|
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
11
11
|
import { renderRef } from "./ref.ts";
|
|
12
12
|
import type { ProjectView } from "./session.ts";
|