@cplieger/deadset-ts 8.0.0-dev.16 → 8.0.0-dev.17
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 +0 -5
- package/dist/src/amd.js +81 -0
- package/dist/src/analysis.js +58 -22
- package/dist/src/augmentations.js +23 -24
- package/dist/src/bindings.js +382 -0
- package/dist/src/calls.js +25 -8
- package/dist/src/commands/tools.js +5 -9
- package/dist/src/component-files.js +3 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +6 -9
- package/dist/src/contextual-uses.js +1 -17
- package/dist/src/contract/config.generated.js +4 -1
- package/dist/src/contract/report.generated.js +6 -2
- package/dist/src/dependencies.js +25 -29
- package/dist/src/destructuring.js +1 -19
- package/dist/src/element-access.js +179 -164
- package/dist/src/entries/build-scripts.js +2 -2
- package/dist/src/entries/configuration-files.js +3 -7
- package/dist/src/entries/conventions.js +2 -2
- package/dist/src/entries/html-entries.js +7 -10
- package/dist/src/entries/mdx-entries.js +2 -2
- package/dist/src/entries/static-module.js +2 -1
- package/dist/src/facts.js +4 -4
- package/dist/src/findings/completion.js +40 -4
- package/dist/src/{implementations.js → findings/implementations.js} +3 -3
- package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
- package/dist/src/framework-lifecycle.js +41 -14
- package/dist/src/graph.js +12 -4
- package/dist/src/import-bindings.js +73 -28
- package/dist/src/index/sites.js +557 -0
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/interface-satisfaction.js +1 -1
- package/dist/src/keyof-comparisons.js +124 -103
- package/dist/src/load/expected-module.js +2 -2
- package/dist/src/load/specifiers.js +2 -4
- package/dist/src/load/synthesize.js +5 -0
- package/dist/src/load/workspace-resolution.js +2 -2
- package/dist/src/matrix.js +6 -4
- package/dist/src/module-declarations.js +70 -85
- package/dist/src/overrides.js +134 -112
- package/dist/src/paths.js +46 -7
- package/dist/src/platform-calls.js +2 -2
- package/dist/src/platform-symbols.js +13 -19
- package/dist/src/references/read-rule.js +1 -0
- package/dist/src/references/read-rules.js +48 -0
- package/dist/src/references/sink.js +151 -0
- package/dist/src/references/sources.js +450 -0
- package/dist/src/references.js +110 -1285
- package/dist/src/roots/configured-declarations.js +3 -0
- package/dist/src/roots/root-set.js +1 -1
- package/dist/src/roots/unseen-loads.js +38 -10
- package/dist/src/serialization-contract.js +8 -3
- package/dist/src/stores.js +4 -2
- package/dist/src/sweep.js +25 -13
- package/dist/src/symbol-keys.js +89 -83
- package/dist/src/tests/file-class.js +76 -0
- package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
- package/dist/src/{test-support.js → tests/support.js} +2 -11
- package/dist/src/type-parameter-reads.js +178 -139
- package/dist/src/unimplemented-callees.js +262 -242
- package/dist/src/verbs/analyze.js +3 -3
- package/dist/src/verbs/explain.js +50 -28
- package/dist/src/verbs/print-retained.js +2 -2
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +0 -7
- package/src/amd.ts +130 -0
- package/src/analysis.ts +86 -23
- package/src/augmentations.ts +41 -27
- package/src/bindings.ts +460 -0
- package/src/calls.ts +25 -7
- package/src/commands/tools.ts +5 -11
- package/src/component-files.ts +4 -2
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +8 -11
- package/src/contextual-uses.ts +2 -22
- package/src/contract/config.generated.ts +4 -1
- package/src/contract/report.generated.ts +6 -2
- package/src/dependencies.ts +27 -28
- package/src/destructuring.ts +1 -31
- package/src/element-access.ts +188 -182
- package/src/entries/build-scripts.ts +2 -2
- package/src/entries/configuration-files.ts +11 -8
- package/src/entries/conventions.ts +2 -1
- package/src/entries/html-entries.ts +15 -14
- package/src/entries/mdx-entries.ts +2 -2
- package/src/entries/static-module.ts +2 -1
- package/src/facts.ts +5 -3
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +43 -4
- package/src/findings/emitter.ts +1 -1
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
- package/src/framework-lifecycle.ts +42 -14
- package/src/graph.ts +24 -6
- package/src/import-bindings.ts +98 -29
- package/src/index/sites.ts +781 -0
- package/src/indexed-access.ts +570 -527
- package/src/interface-satisfaction.ts +1 -1
- package/src/keyof-comparisons.ts +131 -116
- package/src/load/expected-module.ts +8 -2
- package/src/load/specifiers.ts +8 -5
- package/src/load/synthesize.ts +6 -0
- package/src/load/workspace-resolution.ts +9 -2
- package/src/matrix.ts +6 -3
- package/src/module-declarations.ts +74 -98
- package/src/overrides.ts +162 -134
- package/src/paths.ts +54 -8
- package/src/platform-calls.ts +3 -3
- package/src/platform-symbols.ts +11 -27
- package/src/references/read-rule.ts +60 -0
- package/src/references/read-rules.ts +55 -0
- package/src/references/sink.ts +213 -0
- package/src/references/sources.ts +632 -0
- package/src/references.ts +168 -1733
- package/src/report.ts +1 -1
- package/src/roots/configured-declarations.ts +3 -0
- package/src/roots/root-set.ts +1 -1
- package/src/roots/unseen-loads.ts +49 -11
- package/src/serialization-contract.ts +8 -3
- package/src/stores.ts +13 -2
- package/src/sweep.ts +37 -14
- package/src/symbol-keys.ts +102 -102
- package/src/tests/file-class.ts +118 -0
- package/src/{outside-tests.ts → tests/outside.ts} +16 -10
- package/src/{test-support.ts → tests/support.ts} +9 -20
- package/src/type-parameter-reads.ts +187 -158
- package/src/unimplemented-callees.ts +297 -280
- package/src/verbs/analyze.ts +3 -2
- package/src/verbs/explain.ts +74 -31
- package/src/verbs/print-retained.ts +2 -2
|
@@ -110,12 +110,13 @@ function testedByIn(input: EmitterInput): ReadonlySet<string> {
|
|
|
110
110
|
* published API, a public member of a published type included, is `certain` where the
|
|
111
111
|
* run loaded every consumer the scope declared and declared one, `probable` where one did
|
|
112
112
|
* not load, and `possible` where it declared none, whatever the configuration says of the
|
|
113
|
-
* consumer set. A member an `in` expression tests by name,
|
|
114
|
-
* analysis fills, is `possible`.
|
|
113
|
+
* consumer set. A member an `in` expression tests by name, one a function outside the
|
|
114
|
+
* analysis fills, and one {@link possiblyLoaded} names, is `possible`.
|
|
115
115
|
*/
|
|
116
116
|
function reachabilityClasses(input: EmitterInput): (id: string) => Confidence {
|
|
117
117
|
const tested = testedByIn(input);
|
|
118
118
|
const filled = input.boundary.filledOutside ?? new Set<string>();
|
|
119
|
+
const possible = possiblyLoaded(input);
|
|
119
120
|
const published = publishedIds(input.swept.matrix.union);
|
|
120
121
|
const { declared } = input.boundary.consumers;
|
|
121
122
|
const outside: Confidence =
|
|
@@ -125,7 +126,7 @@ function reachabilityClasses(input: EmitterInput): (id: string) => Confidence {
|
|
|
125
126
|
? "certain"
|
|
126
127
|
: "probable";
|
|
127
128
|
return (id) => {
|
|
128
|
-
if (tested.has(id) || filled.has(id)) {
|
|
129
|
+
if (tested.has(id) || filled.has(id) || possible.has(id)) {
|
|
129
130
|
return "possible";
|
|
130
131
|
}
|
|
131
132
|
return published.has(id) ? outside : "certain";
|
|
@@ -182,6 +183,36 @@ function testCeilings(
|
|
|
182
183
|
: "certain";
|
|
183
184
|
}
|
|
184
185
|
|
|
186
|
+
/**
|
|
187
|
+
* What only a bundler's retry of an import Node leaves unresolved names (program.38), and each
|
|
188
|
+
* dead declaration that reaches through dead declarations, as confidence.20 reads what only
|
|
189
|
+
* tests reach: each is dead only while no loader the analysis does not see loads the file.
|
|
190
|
+
*/
|
|
191
|
+
function possiblyLoaded(input: EmitterInput): ReadonlySet<string> {
|
|
192
|
+
const seeds = input.boundary.possiblyLoaded ?? new Set<string>();
|
|
193
|
+
if (seeds.size === 0) {
|
|
194
|
+
return seeds;
|
|
195
|
+
}
|
|
196
|
+
const union = input.swept.matrix.union;
|
|
197
|
+
const dead = new Set(input.swept.sweep.candidates.map((one) => one.id));
|
|
198
|
+
const reached = new Set<string>();
|
|
199
|
+
const pending = union.symbols.flatMap((symbol, at) => (seeds.has(symbol.id) ? [at] : []));
|
|
200
|
+
for (let at = pending.pop(); at !== undefined; at = pending.pop()) {
|
|
201
|
+
const symbol = union.symbols[at];
|
|
202
|
+
if (symbol === undefined || reached.has(symbol.id)) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
reached.add(symbol.id);
|
|
206
|
+
for (const edge of union.out[at] ?? []) {
|
|
207
|
+
const to = union.symbols[edge.to];
|
|
208
|
+
if (to !== undefined && dead.has(to.id)) {
|
|
209
|
+
pending.push(edge.to);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return reached;
|
|
214
|
+
}
|
|
215
|
+
|
|
185
216
|
/**
|
|
186
217
|
* The subjects dead only because production code only tests use is: each declaration the
|
|
187
218
|
* sweep marks test-used, each `DS1602` file of such code, and each test of dead code every
|
|
@@ -373,6 +404,9 @@ export function completed(
|
|
|
373
404
|
union.symbols.map((symbol, at) => [declarationKey(symbol.ref, symbol.position.path), at]),
|
|
374
405
|
);
|
|
375
406
|
const candidates = new Map(swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
407
|
+
const fileIds = new Map(
|
|
408
|
+
union.symbols.flatMap((one) => (one.kind === "file" ? [[one.position.path, one.id]] : [])),
|
|
409
|
+
);
|
|
376
410
|
const classOf = reachabilityClasses(input);
|
|
377
411
|
const ceilingOf = testCeilings(input, classOf);
|
|
378
412
|
const testUsed = testUsedSubjects(input);
|
|
@@ -465,7 +499,12 @@ export function completed(
|
|
|
465
499
|
? union.made[at]
|
|
466
500
|
: { production: candidate.productionRefs, test: candidate.testRefs };
|
|
467
501
|
const ownClass =
|
|
468
|
-
judged
|
|
502
|
+
judged !== undefined
|
|
503
|
+
? classOf(judged.id)
|
|
504
|
+
: (finding.reachabilityClass ??
|
|
505
|
+
(finding.code === CODE.fileNeverImported
|
|
506
|
+
? classOf(fileIds.get(finding.position.path) ?? "")
|
|
507
|
+
: "certain"));
|
|
469
508
|
const used =
|
|
470
509
|
judged !== undefined
|
|
471
510
|
? candidate !== undefined && testUsed.declarations.has(judged.id)
|
package/src/findings/emitter.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ResolvedConfig } from "../config.ts";
|
|
|
3
3
|
import type { Dependencies } from "../dependencies.ts";
|
|
4
4
|
import type { FileFacts } from "../file-facts.ts";
|
|
5
5
|
import type { Finding } from "../finding.ts";
|
|
6
|
-
import type { Implementations } from "
|
|
6
|
+
import type { Implementations } from "./implementations.ts";
|
|
7
7
|
import type { IntraFunctionFacts } from "../intra-function-parts.ts";
|
|
8
8
|
import type { Stores } from "../stores.ts";
|
|
9
9
|
import type { Boundary } from "./boundary.ts";
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
type Node,
|
|
16
16
|
} from "@typescript/native/ast";
|
|
17
17
|
import { SymbolFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/sync";
|
|
18
|
-
import { aliasChains } from "
|
|
19
|
-
import { nodeKey, type Inventory } from "
|
|
20
|
-
import { must, Unanswerable } from "
|
|
21
|
-
import type { ProjectView } from "
|
|
18
|
+
import { aliasChains } from "../alias-chain.ts";
|
|
19
|
+
import { nodeKey, type Inventory } from "../inventory.ts";
|
|
20
|
+
import { must, Unanswerable } from "../query.ts";
|
|
21
|
+
import type { ProjectView } from "../session.ts";
|
|
22
22
|
|
|
23
23
|
/** What one run knows about how its interfaces are implemented. */
|
|
24
24
|
export interface Implementations {
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
SyntaxKind,
|
|
12
12
|
type Node,
|
|
13
13
|
} from "@typescript/native/ast";
|
|
14
|
-
import type { ProjectView } from "
|
|
15
|
-
import { sourceFilesOf } from "
|
|
16
|
-
import { bytewise } from "
|
|
14
|
+
import type { ProjectView } from "../session.ts";
|
|
15
|
+
import { sourceFilesOf } from "../source-files.ts";
|
|
16
|
+
import { bytewise } from "../text/compare.ts";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* The text of each string literal, or template literal with no substitution, written as
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The framework-lifecycle class: a member a
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* through a chain, a class its `bases` name.
|
|
6
|
-
*
|
|
2
|
+
* The framework-lifecycle class: a member a lifecycle contract lists is held back on each
|
|
3
|
+
* class that is the contract's component, being a class a decorator or a call the
|
|
4
|
+
* contract's `components` name is attached to or passes, or one extending, directly or
|
|
5
|
+
* through a chain, a class its `bases` name. The contracts are the platform's custom
|
|
6
|
+
* elements and each `ts.lifecycle_contracts` entry. A component's members are its type's,
|
|
7
|
+
* so a member a base class declares answers for every subclass.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import {
|
|
@@ -33,6 +34,30 @@ import { renderPosition } from "./position.ts";
|
|
|
33
34
|
import { must } from "./query.ts";
|
|
34
35
|
import type { ProjectView } from "./session.ts";
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The platform's own contract (references-ts.38): `CustomElementRegistry.define` reads
|
|
39
|
+
* these members off the class it registers, its constructor's and its prototype's
|
|
40
|
+
* (https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define),
|
|
41
|
+
* and calls each callback as a custom element reaction.
|
|
42
|
+
*/
|
|
43
|
+
const CUSTOM_ELEMENTS: LifecycleContract = {
|
|
44
|
+
components: [{ global: "CustomElementRegistry.define" }],
|
|
45
|
+
members: [
|
|
46
|
+
"observedAttributes:static",
|
|
47
|
+
"disabledFeatures:static",
|
|
48
|
+
"formAssociated:static",
|
|
49
|
+
"connectedCallback",
|
|
50
|
+
"disconnectedCallback",
|
|
51
|
+
"connectedMoveCallback",
|
|
52
|
+
"adoptedCallback",
|
|
53
|
+
"attributeChangedCallback",
|
|
54
|
+
"formAssociatedCallback",
|
|
55
|
+
"formResetCallback",
|
|
56
|
+
"formDisabledCallback",
|
|
57
|
+
"formStateRestoreCallback",
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
|
|
36
61
|
/** One class that is a component of one contract, and what says it is. */
|
|
37
62
|
interface Component {
|
|
38
63
|
readonly contract: LifecycleContract;
|
|
@@ -238,16 +263,17 @@ function byBases<Brand>(
|
|
|
238
263
|
|
|
239
264
|
/**
|
|
240
265
|
* The framework-lifecycle detector: each member a contract lists, on each class that
|
|
241
|
-
* is a component of that contract's framework, recorded where the class
|
|
242
|
-
* be one.
|
|
266
|
+
* is a component of that contract's framework, recorded once per place where the class
|
|
267
|
+
* was found to be one.
|
|
243
268
|
*/
|
|
244
269
|
export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
|
|
245
270
|
const { project, held, targetRoot } = input;
|
|
246
|
-
|
|
271
|
+
const declared = [CUSTOM_ELEMENTS, ...input.ts.lifecycle_contracts];
|
|
272
|
+
const contracts = resolveContracts(project, held, declared);
|
|
273
|
+
if (contracts.every((one) => one.components.length === 0 && one.bases.length === 0)) {
|
|
247
274
|
return [];
|
|
248
275
|
}
|
|
249
276
|
const chains = aliasChains(project, held);
|
|
250
|
-
const contracts = resolveContracts(project, held, input.ts.lifecycle_contracts);
|
|
251
277
|
const components = [
|
|
252
278
|
...byComponents(project, held, chains, contracts),
|
|
253
279
|
...byBases(project, held, chains, contracts),
|
|
@@ -256,7 +282,7 @@ export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly
|
|
|
256
282
|
return [];
|
|
257
283
|
}
|
|
258
284
|
const ids = new Set(components.map((one) => one.id));
|
|
259
|
-
const sides: readonly ClassSide[] =
|
|
285
|
+
const sides: readonly ClassSide[] = declared.some((contract) =>
|
|
260
286
|
contract.members.some((name) => name.endsWith(":static")),
|
|
261
287
|
)
|
|
262
288
|
? ["instance", "static"]
|
|
@@ -264,7 +290,7 @@ export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly
|
|
|
264
290
|
const members = sides.map((side) => classMembers(project, held, ids, side));
|
|
265
291
|
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
266
292
|
|
|
267
|
-
const found
|
|
293
|
+
const found = new Map<string, Evidence>();
|
|
268
294
|
for (const component of components) {
|
|
269
295
|
const listed = new Set(component.contract.members);
|
|
270
296
|
const site = renderPosition(
|
|
@@ -275,10 +301,12 @@ export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly
|
|
|
275
301
|
for (const member of members.flatMap((side) => side.get(component.id) ?? [])) {
|
|
276
302
|
const symbol = byId.get(member.id);
|
|
277
303
|
const spelled = symbol === undefined ? undefined : memberComponent(symbol, byId);
|
|
278
|
-
|
|
279
|
-
|
|
304
|
+
// A configured contract may name the platform's registry too; one record answers both.
|
|
305
|
+
const key = `${member.id}\0${component.detail}\0${site.path}:${String(site.line)}:${String(site.column)}`;
|
|
306
|
+
if (spelled !== undefined && listed.has(spelled) && !found.has(key)) {
|
|
307
|
+
found.set(key, { id: member.id, detail: component.detail, site });
|
|
280
308
|
}
|
|
281
309
|
}
|
|
282
310
|
}
|
|
283
|
-
return found;
|
|
311
|
+
return [...found.values()];
|
|
284
312
|
}
|
package/src/graph.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { InventorySymbol, SymbolKind } from "./inventory.ts";
|
|
9
9
|
import type { Reference } from "./references.ts";
|
|
10
|
+
import { madeByTestCode } from "./tests/file-class.ts";
|
|
10
11
|
import type { Root, RootKind } from "./roots/root-set.ts";
|
|
11
12
|
|
|
12
13
|
/** The position of a declaration the inventory does not hold. */
|
|
@@ -85,12 +86,22 @@ export interface Edge {
|
|
|
85
86
|
* module's top level and none of what it exports.
|
|
86
87
|
*/
|
|
87
88
|
readonly evaluation: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Whether a loaded consumer made it, whose test references a mode may class as
|
|
91
|
+
* production. Only a consumed edge can be one.
|
|
92
|
+
*/
|
|
93
|
+
readonly consumer: boolean;
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
/**
|
|
91
|
-
|
|
96
|
+
/**
|
|
97
|
+
* How many references one declaration carries, split by the classification of the file that
|
|
98
|
+
* made each; `consumerTest` counts the test references a loaded consumer's test files made,
|
|
99
|
+
* which a mode may class as production (`sweep.ts` `countedRefs`).
|
|
100
|
+
*/
|
|
101
|
+
export interface Counts {
|
|
92
102
|
readonly production: number;
|
|
93
103
|
readonly test: number;
|
|
104
|
+
readonly consumerTest: number;
|
|
94
105
|
}
|
|
95
106
|
|
|
96
107
|
/** One root the inventory holds, at the position of the declaration it names. */
|
|
@@ -156,23 +167,30 @@ export function graphOf(
|
|
|
156
167
|
const tests = new Set(testFiles);
|
|
157
168
|
|
|
158
169
|
const out: Edge[][] = symbols.map(() => []);
|
|
159
|
-
const made = symbols.map(() => ({ production: 0, test: 0 }));
|
|
170
|
+
const made = symbols.map(() => ({ production: 0, test: 0, consumerTest: 0 }));
|
|
160
171
|
const consumed: Edge[] = [];
|
|
161
172
|
for (const reference of references) {
|
|
162
173
|
const to = at(reference.to);
|
|
163
174
|
if (to === OUTSIDE) {
|
|
164
175
|
continue;
|
|
165
176
|
}
|
|
177
|
+
const test = madeByTestCode(reference, supports);
|
|
166
178
|
const counts = made[to];
|
|
167
179
|
if (counts !== undefined) {
|
|
168
|
-
if (
|
|
180
|
+
if (test) {
|
|
169
181
|
counts.test += 1;
|
|
182
|
+
counts.consumerTest += reference.consumer === undefined ? 0 : 1;
|
|
170
183
|
} else {
|
|
171
184
|
counts.production += 1;
|
|
172
185
|
}
|
|
173
186
|
}
|
|
174
|
-
const edge = {
|
|
175
|
-
|
|
187
|
+
const edge = {
|
|
188
|
+
to,
|
|
189
|
+
test,
|
|
190
|
+
evaluation: reference.use === "evaluation",
|
|
191
|
+
consumer: reference.consumer !== undefined,
|
|
192
|
+
};
|
|
193
|
+
if (edge.consumer || reference.unheld === true) {
|
|
176
194
|
consumed.push(edge);
|
|
177
195
|
continue;
|
|
178
196
|
}
|
package/src/import-bindings.ts
CHANGED
|
@@ -4,17 +4,20 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
isExportDeclaration,
|
|
7
8
|
isExportSpecifier,
|
|
8
9
|
isExternalModuleReference,
|
|
9
|
-
isIdentifier,
|
|
10
10
|
isImportDeclaration,
|
|
11
11
|
isImportEqualsDeclaration,
|
|
12
12
|
isJsxElement,
|
|
13
13
|
isJsxFragment,
|
|
14
14
|
isJsxSelfClosingElement,
|
|
15
|
+
isNamedExports,
|
|
15
16
|
isNamespaceImport,
|
|
16
17
|
SyntaxKind,
|
|
17
18
|
type Identifier,
|
|
19
|
+
type ImportDeclaration,
|
|
20
|
+
type ImportEqualsDeclaration,
|
|
18
21
|
type Node,
|
|
19
22
|
type SourceFile,
|
|
20
23
|
} from "@typescript/native/ast";
|
|
@@ -41,42 +44,108 @@ export interface JsxOptions {
|
|
|
41
44
|
readonly jsxFragmentFactory?: unknown;
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
/** One name a file's top-level import declaration binds. */
|
|
48
|
+
interface ImportBinding {
|
|
49
|
+
readonly name: Identifier;
|
|
50
|
+
/** The export name it binds, `default` for a default import; absent where it binds a whole module. */
|
|
51
|
+
readonly imported?: string;
|
|
52
|
+
/** An `import x = …` declaration: whether it is exported, and whether it names an external module. */
|
|
53
|
+
readonly equals?: { readonly exported: boolean; readonly external: boolean };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const bindingsCache = new WeakMap<SourceFile, readonly ImportBinding[]>();
|
|
57
|
+
|
|
44
58
|
/**
|
|
45
|
-
*
|
|
46
|
-
* an `import` declaration
|
|
47
|
-
*
|
|
59
|
+
* Every name one import declaration binds, in source order: the default, named and
|
|
60
|
+
* namespace names of an `import` declaration and the name of an `import x = …` declaration.
|
|
61
|
+
* A namespace import and an import assignment bind a whole module.
|
|
48
62
|
*/
|
|
49
|
-
export function
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
export function declarationBindings(
|
|
64
|
+
declaration: ImportDeclaration | ImportEqualsDeclaration,
|
|
65
|
+
): readonly ImportBinding[] {
|
|
66
|
+
if (isImportEqualsDeclaration(declaration)) {
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
name: declaration.name,
|
|
70
|
+
equals: {
|
|
71
|
+
exported: (declaration.modifiers ?? []).some(
|
|
72
|
+
(modifier) => modifier.kind === SyntaxKind.ExportKeyword,
|
|
73
|
+
),
|
|
74
|
+
external: isExternalModuleReference(declaration.moduleReference),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
];
|
|
52
78
|
}
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
for (const element of named.elements) {
|
|
65
|
-
found.push(element.name);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
} else if (
|
|
69
|
-
isImportEqualsDeclaration(statement) &&
|
|
70
|
-
isExternalModuleReference(statement.moduleReference) &&
|
|
71
|
-
isIdentifier(statement.name) &&
|
|
72
|
-
!(statement.modifiers ?? []).some((modifier) => modifier.kind === SyntaxKind.ExportKeyword)
|
|
73
|
-
) {
|
|
74
|
-
found.push(statement.name);
|
|
79
|
+
const clause = declaration.importClause;
|
|
80
|
+
const found: ImportBinding[] = [];
|
|
81
|
+
if (clause?.name !== undefined) {
|
|
82
|
+
found.push({ name: clause.name, imported: "default" });
|
|
83
|
+
}
|
|
84
|
+
const named = clause?.namedBindings;
|
|
85
|
+
if (named !== undefined && isNamespaceImport(named)) {
|
|
86
|
+
found.push({ name: named.name });
|
|
87
|
+
} else if (named !== undefined) {
|
|
88
|
+
for (const element of named.elements) {
|
|
89
|
+
found.push({ name: element.name, imported: element.propertyName?.text ?? element.name.text });
|
|
75
90
|
}
|
|
76
91
|
}
|
|
77
92
|
return found;
|
|
78
93
|
}
|
|
79
94
|
|
|
95
|
+
/** Every name one file's top-level import declarations bind, in source order. */
|
|
96
|
+
export function bindingsOf(file: SourceFile): readonly ImportBinding[] {
|
|
97
|
+
const known = bindingsCache.get(file);
|
|
98
|
+
if (known !== undefined) {
|
|
99
|
+
return known;
|
|
100
|
+
}
|
|
101
|
+
const found = file.statements.flatMap((statement) =>
|
|
102
|
+
isImportDeclaration(statement) || isImportEqualsDeclaration(statement)
|
|
103
|
+
? declarationBindings(statement)
|
|
104
|
+
: [],
|
|
105
|
+
);
|
|
106
|
+
bindingsCache.set(file, found);
|
|
107
|
+
return found;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The export names an import or a re-export declaration reads from the module it names,
|
|
112
|
+
* `undefined` standing for the whole module where a namespace or a star reads it; none for
|
|
113
|
+
* any other node.
|
|
114
|
+
*/
|
|
115
|
+
export function importedNames(node: Node): readonly (string | undefined)[] {
|
|
116
|
+
if (isImportDeclaration(node)) {
|
|
117
|
+
return declarationBindings(node).map((binding) => binding.imported);
|
|
118
|
+
}
|
|
119
|
+
if (!isExportDeclaration(node)) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const clause = node.exportClause;
|
|
123
|
+
return clause === undefined || !isNamedExports(clause)
|
|
124
|
+
? [undefined]
|
|
125
|
+
: clause.elements.map((one) => (one.propertyName ?? one.name).text);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The export names {@link importedNames} gives one node, without the whole module. */
|
|
129
|
+
export function namesImported(node: Node): readonly string[] {
|
|
130
|
+
return importedNames(node).flatMap((name) => name ?? []);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The bindings of one file that are judged as imports: those of {@link bindingsOf} but an
|
|
135
|
+
* `import x = …` that is exported or names no external module. None is judged in a
|
|
136
|
+
* declaration file or a component file.
|
|
137
|
+
*/
|
|
138
|
+
export function importBindings(file: SourceFile): readonly Identifier[] {
|
|
139
|
+
if (file.isDeclarationFile || !JUDGED.test(file.fileName)) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
return bindingsOf(file).flatMap((binding) =>
|
|
143
|
+
binding.equals !== undefined && (binding.equals.exported || !binding.equals.external)
|
|
144
|
+
? []
|
|
145
|
+
: [binding.name],
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
80
149
|
/** Whether one identifier names a binding in a local export, which reads it whatever it resolves to. */
|
|
81
150
|
export function exportsLocally(use: Identifier): boolean {
|
|
82
151
|
return isExportSpecifier(use.parent);
|