@cplieger/deadset-ts 8.0.0-dev.15 → 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 +94 -54
- 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/read.js +6 -7
- 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 +9 -10
- package/dist/src/contextual-uses.js +1 -17
- package/dist/src/contract/config.generated.js +5 -2
- package/dist/src/contract/contract.generated.js +119 -2
- package/dist/src/contract/report.generated.js +6 -2
- package/dist/src/dependencies.js +98 -71
- package/dist/src/destructuring.js +1 -19
- package/dist/src/element-access.js +179 -164
- package/dist/src/entries/build-scripts.js +222 -0
- package/dist/src/{configuration-files.js → entries/configuration-files.js} +22 -64
- package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
- package/dist/src/entries/conventions.js +739 -0
- package/dist/src/{html-entries.js → entries/html-entries.js} +9 -12
- package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +3 -3
- package/dist/src/entries/run-directories.js +77 -0
- package/dist/src/entries/sources.js +144 -0
- package/dist/src/entries/static-module.js +670 -0
- package/dist/src/entries/test-runners.js +684 -0
- package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
- package/dist/src/extensions.js +37 -0
- package/dist/src/facts.js +38 -0
- package/dist/src/findings/completion.js +40 -4
- package/dist/src/findings/emitters.js +5 -8
- 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/findings/self-check.js +36 -20
- package/dist/src/findings-pass.js +5 -6
- package/dist/src/framework-lifecycle.js +42 -15
- package/dist/src/fs/manifest.js +15 -20
- package/dist/src/generated-file.js +172 -29
- package/dist/src/graph.js +14 -8
- package/dist/src/import-bindings.js +73 -28
- package/dist/src/index/sites.js +557 -0
- package/dist/src/index.js +1 -1
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/injection-container.js +1 -1
- package/dist/src/interface-satisfaction.js +1 -1
- package/dist/src/inventory/commonjs.js +62 -17
- package/dist/src/inventory/enumerate.js +2 -1
- package/dist/src/javascript-file.js +26 -11
- package/dist/src/keyof-comparisons.js +124 -103
- package/dist/src/load/classify.js +28 -9
- package/dist/src/load/diagnostics.js +8 -1
- package/dist/src/load/discover.js +15 -7
- package/dist/src/load/expected-module.js +2 -2
- package/dist/src/load/plan.js +2 -2
- package/dist/src/load/specifiers.js +2 -4
- package/dist/src/load/synthesize.js +10 -5
- package/dist/src/load/workspace-manifests.js +1 -1
- package/dist/src/load/workspace-resolution.js +2 -2
- package/dist/src/matrix.js +12 -17
- package/dist/src/module-declarations.js +70 -85
- package/dist/src/overrides.js +134 -112
- package/dist/src/paths.js +56 -1
- package/dist/src/platform-calls.js +78 -0
- package/dist/src/platform-symbols.js +134 -0
- 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 +111 -1116
- package/dist/src/resolve.js +1 -1
- package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +7 -4
- package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
- package/dist/src/{manifest.js → roots/manifest.js} +4 -4
- package/dist/src/roots/published-reach.js +412 -0
- package/dist/src/roots/root-set.js +489 -0
- package/dist/src/roots/unseen-loads.js +236 -0
- package/dist/src/serialization-contract.js +9 -4
- package/dist/src/source-files.js +5 -9
- package/dist/src/stores.js +4 -2
- package/dist/src/suppress.js +30 -32
- 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} +5 -12
- package/dist/src/type-parameter-reads.js +178 -139
- package/dist/src/unimplemented-callees.js +262 -242
- package/dist/src/verbs/analyze.js +15 -3
- package/dist/src/verbs/describe.js +5 -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 +3 -3
- package/src/alias-chain.ts +0 -7
- package/src/amd.ts +130 -0
- package/src/analysis.ts +156 -70
- package/src/augmentations.ts +41 -27
- package/src/automatic-types.ts +1 -1
- package/src/bindings.ts +460 -0
- package/src/calls.ts +25 -7
- package/src/commands/read.ts +6 -8
- 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 +12 -11
- package/src/contextual-uses.ts +2 -22
- package/src/contract/config.generated.ts +5 -2
- package/src/contract/contract.generated.ts +138 -2
- package/src/contract/report.generated.ts +6 -2
- package/src/dependencies.ts +117 -71
- package/src/destructuring.ts +1 -31
- package/src/element-access.ts +188 -182
- package/src/entries/build-scripts.ts +292 -0
- package/src/{configuration-files.ts → entries/configuration-files.ts} +33 -82
- package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
- package/src/{conventions.ts → entries/conventions.ts} +349 -523
- package/src/{html-entries.ts → entries/html-entries.ts} +19 -18
- package/src/{mdx-entries.ts → entries/mdx-entries.ts} +5 -5
- package/src/entries/run-directories.ts +108 -0
- package/src/entries/sources.ts +266 -0
- package/src/entries/static-module.ts +858 -0
- package/src/entries/test-runners.ts +868 -0
- package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
- package/src/exempt.ts +3 -2
- package/src/extensions.ts +45 -0
- package/src/facts.ts +46 -0
- package/src/file-facts.ts +1 -1
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +43 -4
- package/src/findings/emitter.ts +1 -1
- package/src/findings/emitters.ts +14 -10
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
- package/src/findings/self-check.ts +41 -22
- package/src/findings/visibility-narrowing.ts +1 -1
- package/src/findings-pass.ts +5 -12
- package/src/framework-lifecycle.ts +43 -15
- package/src/fs/manifest.ts +25 -21
- package/src/generated-file.ts +240 -36
- package/src/graph.ts +27 -11
- package/src/import-bindings.ts +98 -29
- package/src/index/sites.ts +781 -0
- package/src/index.ts +1 -1
- package/src/indexed-access.ts +570 -527
- package/src/injection-container.ts +1 -1
- package/src/interface-satisfaction.ts +1 -1
- package/src/inventory/commonjs.ts +102 -17
- package/src/inventory/enumerate.ts +2 -1
- package/src/javascript-file.ts +29 -12
- package/src/keyof-comparisons.ts +131 -116
- package/src/load/classify.ts +49 -13
- package/src/load/diagnostics.ts +11 -1
- package/src/load/discover.ts +27 -11
- package/src/load/expected-module.ts +8 -2
- package/src/load/plan.ts +11 -2
- package/src/load/specifiers.ts +8 -5
- package/src/load/synthesize.ts +12 -6
- package/src/load/test-globals.ts +1 -1
- package/src/load/workspace-manifests.ts +1 -1
- package/src/load/workspace-resolution.ts +9 -2
- package/src/load/workspace-run.ts +1 -1
- package/src/matrix.ts +13 -17
- package/src/module-declarations.ts +74 -98
- package/src/overrides.ts +162 -134
- package/src/paths.ts +65 -1
- package/src/platform-calls.ts +119 -0
- package/src/platform-symbols.ts +189 -0
- 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 +174 -1492
- package/src/report.ts +2 -2
- package/src/resolve.ts +1 -1
- package/src/{configured-declarations.ts → roots/configured-declarations.ts} +10 -7
- package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
- package/src/{manifest.ts → roots/manifest.ts} +5 -5
- package/src/roots/published-reach.ts +481 -0
- package/src/{roots.ts → roots/root-set.ts} +203 -630
- package/src/roots/unseen-loads.ts +345 -0
- package/src/serialization-contract.ts +9 -4
- package/src/source-files.ts +5 -10
- package/src/stores.ts +13 -2
- package/src/suppress.ts +48 -33
- 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} +15 -21
- package/src/type-parameter-reads.ts +187 -158
- package/src/unimplemented-callees.ts +297 -280
- package/src/verbs/analyze.ts +19 -5
- package/src/verbs/describe.ts +5 -2
- package/src/verbs/explain.ts +75 -32
- package/src/verbs/print-retained.ts +2 -2
- package/dist/src/build-scripts.js +0 -418
- package/dist/src/conventions.js +0 -907
- package/dist/src/entry-points.js +0 -444
- package/dist/src/own-body.js +0 -28
- package/dist/src/roots.js +0 -865
- package/dist/src/unseen-loads.js +0 -189
- package/src/build-scripts.ts +0 -536
- package/src/entry-points.ts +0 -584
- package/src/own-body.ts +0 -42
- package/src/unseen-loads.ts +0 -282
package/dist/src/roots.js
DELETED
|
@@ -1,865 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The root set of one project: every declaration kept live without a reference, and why.
|
|
3
|
-
* Every root seeds reachability and none counts as a reference; a root of a kind that
|
|
4
|
-
* names a caller is live under reference counting too, which the sweep decides, so a
|
|
5
|
-
* published export, which only supposes a consumer, stays a reference-counting candidate.
|
|
6
|
-
*/
|
|
7
|
-
import { isArrowFunction, isAssertionExpression, isExpressionWithTypeArguments, isFunctionLikeDeclaration, isIdentifier, isMethodDeclaration, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAssignment, isReturnStatement, isSatisfiesExpression, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, isPropertyDeclaration, isVariableDeclaration, isVariableStatement, SyntaxKind, } from "@typescript/native/ast";
|
|
8
|
-
import { aliasChains } from "./alias-chain.js";
|
|
9
|
-
import { isGlobalBlock } from "./augmentations.js";
|
|
10
|
-
import { filesNamedBy, outsideChain, relativeSpecifiers, } from "./configuration-files.js";
|
|
11
|
-
import { conventionEntries } from "./conventions.js";
|
|
12
|
-
import { entryPoints } from "./entry-points.js";
|
|
13
|
-
import { nodeKey } from "./inventory.js";
|
|
14
|
-
import { ancestors, dirnamePath, relativePath, resolvePath } from "./paths.js";
|
|
15
|
-
import { byPosition } from "./position.js";
|
|
16
|
-
import { UNANSWERED } from "./query.js";
|
|
17
|
-
import { sourceFilesOf } from "./source-files.js";
|
|
18
|
-
import { typeQueryAliases } from "./type-query-alias.js";
|
|
19
|
-
import { bytewise } from "./text/compare.js";
|
|
20
|
-
import { holdsManifest } from "./fs/manifest.js";
|
|
21
|
-
import { globExpression } from "./text/glob.js";
|
|
22
|
-
import { spelledFiles } from "./text/named-files.js";
|
|
23
|
-
import { commandFileTexts, workflowCommands } from "./workflow-steps.js";
|
|
24
|
-
import { readCommands } from "./commands/read.js";
|
|
25
|
-
/** Whether one declaration is written below the directory of a published workspace member. */
|
|
26
|
-
function inPublishedMember(held, id, root, members) {
|
|
27
|
-
const path = held.symbols.find((symbol) => symbol.id === id)?.position.path;
|
|
28
|
-
if (path === undefined || members === undefined) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
const file = resolvePath(root, path);
|
|
32
|
-
return [...members].some((dir) => file.startsWith(`${dir}/`));
|
|
33
|
-
}
|
|
34
|
-
/** The kind of root each role of a manifest entry makes. */
|
|
35
|
-
const ENTRY_KINDS = {
|
|
36
|
-
import: "manifest-entry",
|
|
37
|
-
run: "manifest-binary",
|
|
38
|
-
script: "script",
|
|
39
|
-
};
|
|
40
|
-
/** The rules whose rooted files a manifest target is read back to by name. */
|
|
41
|
-
const READ_BACK_RULES = new Set([
|
|
42
|
-
"configuration-file",
|
|
43
|
-
"configuration-string",
|
|
44
|
-
"html-entry",
|
|
45
|
-
]);
|
|
46
|
-
/** The extensions a manifest target's name drops: the ones a build writes. */
|
|
47
|
-
const OUTPUT_EXTENSION = /\.(?:d\.ts|d\.mts|d\.cts|js|mjs|cjs|jsx)$/u;
|
|
48
|
-
/** The extension a source file's name drops. */
|
|
49
|
-
const SOURCE_EXTENSION = /\.(?:d\.)?[cm]?[jt]sx?$/u;
|
|
50
|
-
/** Whether no `package.json` sits between one file and the manifest's directory, which holds it. */
|
|
51
|
-
function ofPackage(host, dir, file) {
|
|
52
|
-
const between = ancestors(dirnamePath(file), dir);
|
|
53
|
-
return between.length > 0 && !between.slice(0, -1).some((at) => holdsManifest(host, at));
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* The rooted files one manifest target reads back to by name: its path below the
|
|
57
|
-
* manifest's directory, past its first directory where it names one, without its output
|
|
58
|
-
* extension, is the name, and a rooted file of the manifest's own package is one whose
|
|
59
|
-
* path below the same directory, without its source extension, ends with that name at a
|
|
60
|
-
* directory boundary.
|
|
61
|
-
*/
|
|
62
|
-
function readBackByName(host, entry, rooted) {
|
|
63
|
-
const dir = resolvePath(host.workingDirectory(), entry.dir);
|
|
64
|
-
const below = relativePath(dir, resolvePath(host.workingDirectory(), entry.path));
|
|
65
|
-
if (below === undefined || below === "") {
|
|
66
|
-
return [];
|
|
67
|
-
}
|
|
68
|
-
const cut = below.indexOf("/");
|
|
69
|
-
const name = (cut === -1 ? below : below.slice(cut + 1)).replace(OUTPUT_EXTENSION, "");
|
|
70
|
-
if (name === "") {
|
|
71
|
-
return [];
|
|
72
|
-
}
|
|
73
|
-
const found = new Map();
|
|
74
|
-
for (const file of rooted) {
|
|
75
|
-
const path = relativePath(dir, file.fileName)?.replace(SOURCE_EXTENSION, "");
|
|
76
|
-
if (path !== undefined &&
|
|
77
|
-
(path === name || path.endsWith(`/${name}`)) &&
|
|
78
|
-
ofPackage(host, dir, file.fileName)) {
|
|
79
|
-
found.set(file.fileName, file);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return [...found.values()];
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* The program files a manifest target no program holds reaches: the target is a file
|
|
86
|
-
* read as a configuration file outside the program is, and each file of the chain it
|
|
87
|
-
* starts loads the program files its relative specifiers name, read through the emit
|
|
88
|
-
* mapping, as a command's shim loads the compiled entry it runs.
|
|
89
|
-
*/
|
|
90
|
-
function throughOutside(options, targetRoot, files, entry) {
|
|
91
|
-
const cwd = options.host.workingDirectory();
|
|
92
|
-
const path = resolvePath(cwd, entry.path);
|
|
93
|
-
if (options.parse === undefined ||
|
|
94
|
-
options.runFiles?.has(path) === true ||
|
|
95
|
-
options.host.kindOf(path) !== "file") {
|
|
96
|
-
return [];
|
|
97
|
-
}
|
|
98
|
-
const chain = outsideChain(options.host, targetRoot, files, options.parse, [
|
|
99
|
-
{ path, manifestDir: resolvePath(cwd, entry.dir) },
|
|
100
|
-
]);
|
|
101
|
-
const found = new Map();
|
|
102
|
-
for (const { file } of chain) {
|
|
103
|
-
for (const specifier of relativeSpecifiers(file)) {
|
|
104
|
-
for (const named of filesNamedBy(files, dirnamePath(file.fileName), specifier)) {
|
|
105
|
-
found.set(named.fileName, named);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return [...found.values()];
|
|
110
|
-
}
|
|
111
|
-
/** Whether a configured string carries either special character. */
|
|
112
|
-
function isPattern(text) {
|
|
113
|
-
return text.includes("*") || text.includes("?");
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Whether ref matches a pattern whose only special characters are `*`, which stands
|
|
117
|
-
* for any run of characters including the solidus, and `?`, which stands for exactly
|
|
118
|
-
* one character counted as a code point. A star keeps the position it last stood at,
|
|
119
|
-
* so the shortest run it can stand for is tried first.
|
|
120
|
-
*/
|
|
121
|
-
function matchGlob(pattern, ref) {
|
|
122
|
-
let next = 0;
|
|
123
|
-
let star = -1;
|
|
124
|
-
let resume = 0;
|
|
125
|
-
let at = 0;
|
|
126
|
-
while (at < ref.length) {
|
|
127
|
-
if (next < pattern.length && (pattern[next] === "?" || pattern[next] === ref[at])) {
|
|
128
|
-
next += 1;
|
|
129
|
-
at += 1;
|
|
130
|
-
}
|
|
131
|
-
else if (next < pattern.length && pattern[next] === "*") {
|
|
132
|
-
star = next;
|
|
133
|
-
resume = at;
|
|
134
|
-
next += 1;
|
|
135
|
-
}
|
|
136
|
-
else if (star >= 0) {
|
|
137
|
-
resume += 1;
|
|
138
|
-
next = star + 1;
|
|
139
|
-
at = resume;
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
while (next < pattern.length && pattern[next] === "*") {
|
|
146
|
-
next += 1;
|
|
147
|
-
}
|
|
148
|
-
return next === pattern.length;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Whether one configured string names the symbol reference ref. A string carrying
|
|
152
|
-
* neither special character names only the reference it spells.
|
|
153
|
-
*
|
|
154
|
-
* Both sides are read as code points: a character outside the basic plane is two
|
|
155
|
-
* string units, and `?` stands for one character.
|
|
156
|
-
*/
|
|
157
|
-
export function matchRef(pattern, ref) {
|
|
158
|
-
if (!isPattern(pattern)) {
|
|
159
|
-
return pattern === ref;
|
|
160
|
-
}
|
|
161
|
-
return matchGlob(Array.from(pattern), Array.from(ref));
|
|
162
|
-
}
|
|
163
|
-
/** The members of one declaration, indexed by the container that holds them. */
|
|
164
|
-
function childrenOf(held) {
|
|
165
|
-
const byParent = new Map();
|
|
166
|
-
for (const symbol of held.symbols) {
|
|
167
|
-
const siblings = byParent.get(symbol.parent);
|
|
168
|
-
if (siblings === undefined) {
|
|
169
|
-
byParent.set(symbol.parent, [symbol]);
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
siblings.push(symbol);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return byParent;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Per package, the own files its held texts name, as `spelledFiles` reads them, but those a
|
|
179
|
-
* root enters with all they export: a command can use no more of such a file than the root
|
|
180
|
-
* keeps live. A manifest's are its entries' held texts, and the target root's package has the
|
|
181
|
-
* workflow steps, git hooks and makefile recipes beside its own, with the whole text of each
|
|
182
|
-
* file `commandFileTexts` reads.
|
|
183
|
-
*/
|
|
184
|
-
function scriptHolds(entries, files, entered, host, targetRoot) {
|
|
185
|
-
const rooted = new Set(entered.filter((one) => one.exports && one.names === undefined).map((one) => one.file.fileName));
|
|
186
|
-
const spelled = [...files.byName.values()]
|
|
187
|
-
.filter((file) => !rooted.has(file.fileName))
|
|
188
|
-
.map((file) => ({ file, paths: [file.fileName, ...files.writtenAs(file)] }));
|
|
189
|
-
const cwd = host.workingDirectory();
|
|
190
|
-
const commands = new Map([
|
|
191
|
-
[
|
|
192
|
-
resolvePath(cwd, targetRoot),
|
|
193
|
-
[
|
|
194
|
-
...workflowCommands(host, targetRoot).flatMap(({ text, dir, writtenDirectory }) => [
|
|
195
|
-
readCommands(text, dir).held,
|
|
196
|
-
...(writtenDirectory === undefined ? [] : [writtenDirectory]),
|
|
197
|
-
]),
|
|
198
|
-
...commandFileTexts(host, targetRoot),
|
|
199
|
-
],
|
|
200
|
-
],
|
|
201
|
-
]);
|
|
202
|
-
for (const entry of entries) {
|
|
203
|
-
if (entry.held !== undefined) {
|
|
204
|
-
const dir = resolvePath(cwd, entry.dir);
|
|
205
|
-
commands.set(dir, [...(commands.get(dir) ?? []), entry.held]);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
const held = [];
|
|
209
|
-
for (const [dir, texts] of commands) {
|
|
210
|
-
const named = spelledFiles(texts.join("\n"), dir, spelled);
|
|
211
|
-
if (named.length > 0) {
|
|
212
|
-
const paths = named.map((file) => relativePath(targetRoot, file.fileName) ?? file.fileName);
|
|
213
|
-
held.push({ dir, files: paths });
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return held;
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* The roots one project holds, and every configured string that named nothing in it. A
|
|
220
|
-
* file a manifest member, an entry-file pattern or a tool's configuration names is a root
|
|
221
|
-
* with each name of its export table and every link of a re-export chain to the
|
|
222
|
-
* declaration it carries; in a library, the published API alone roots what a published
|
|
223
|
-
* file exports. A file a runtime executes without reading its exports, a test file or a
|
|
224
|
-
* worker, is a root by itself.
|
|
225
|
-
*/
|
|
226
|
-
export function roots(project, held, targetRoot, options) {
|
|
227
|
-
const files = sourceFilesOf(project, targetRoot);
|
|
228
|
-
const declared = held.declarations;
|
|
229
|
-
const fileIds = new Map();
|
|
230
|
-
for (const file of files.byName.values()) {
|
|
231
|
-
const id = declared.get(nodeKey(file, file));
|
|
232
|
-
if (id !== undefined) {
|
|
233
|
-
fileIds.set(file.fileName, id);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const found = new Map();
|
|
237
|
-
const add = (id, kind, source) => {
|
|
238
|
-
found.set([id, kind, source].join("\u0000"), { id, kind, source });
|
|
239
|
-
};
|
|
240
|
-
const entered = [];
|
|
241
|
-
const enter = (file, kind, source, exports, names) => {
|
|
242
|
-
entered.push({ file, kind, source, exports, ...(names === undefined ? {} : { names }) });
|
|
243
|
-
};
|
|
244
|
-
const points = entryPoints(project, files, options.testFiles, options.host, targetRoot, options.parse, options.conventions?.selected);
|
|
245
|
-
const published = [];
|
|
246
|
-
const entries = [
|
|
247
|
-
...options.manifest.entries.map((entry) => ({
|
|
248
|
-
entry,
|
|
249
|
-
publishes: options.publishedAPI &&
|
|
250
|
-
entry.published &&
|
|
251
|
-
(options.manifest.declaresExports ? entry.member.startsWith("exports") : true),
|
|
252
|
-
})),
|
|
253
|
-
...(options.otherPackages ?? []).map((entry) => ({
|
|
254
|
-
entry,
|
|
255
|
-
publishes: entry.published &&
|
|
256
|
-
(options.publishedAPI || options.publishedMembers?.has(entry.dir) === true),
|
|
257
|
-
})),
|
|
258
|
-
];
|
|
259
|
-
const scripted = entries.flatMap(({ entry }) => entry.role === "script" && entry.held === undefined ? files.named(entry.path) : []);
|
|
260
|
-
const readBackTo = [
|
|
261
|
-
...scripted,
|
|
262
|
-
...points.filter((point) => READ_BACK_RULES.has(point.rule)).map((point) => point.file),
|
|
263
|
-
];
|
|
264
|
-
const entryFilesOf = new Map();
|
|
265
|
-
const entryFiles = (entry) => {
|
|
266
|
-
const known = entryFilesOf.get(entry);
|
|
267
|
-
if (known !== undefined) {
|
|
268
|
-
return known;
|
|
269
|
-
}
|
|
270
|
-
const found = readEntryFiles(entry);
|
|
271
|
-
entryFilesOf.set(entry, found);
|
|
272
|
-
return found;
|
|
273
|
-
};
|
|
274
|
-
const readEntryFiles = (entry) => {
|
|
275
|
-
const named = files.named(entry.path);
|
|
276
|
-
if (named.length > 0 || entry.role === "script") {
|
|
277
|
-
return named;
|
|
278
|
-
}
|
|
279
|
-
const cwd = options.host.workingDirectory();
|
|
280
|
-
const shipped = files.namedAsOutput(resolvePath(cwd, entry.dir), resolvePath(cwd, entry.path));
|
|
281
|
-
if (shipped.length > 0) {
|
|
282
|
-
return shipped;
|
|
283
|
-
}
|
|
284
|
-
const byName = readBackByName(options.host, entry, readBackTo);
|
|
285
|
-
return byName.length > 0 ? byName : throughOutside(options, targetRoot, files, entry);
|
|
286
|
-
};
|
|
287
|
-
for (const { entry, publishes } of entries) {
|
|
288
|
-
if (publishes) {
|
|
289
|
-
published.push(...entryFiles(entry));
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
if (options.publishedAPI) {
|
|
293
|
-
// A library's bundle hands what its entry exports to every program that loads it.
|
|
294
|
-
published.push(...points.filter((point) => point.rule === "bundler-entry").map((point) => point.file));
|
|
295
|
-
}
|
|
296
|
-
const publishedNames = new Set(published.map((file) => file.fileName));
|
|
297
|
-
const patterned = [];
|
|
298
|
-
for (const pattern of options.entryFiles) {
|
|
299
|
-
const expression = globExpression(pattern);
|
|
300
|
-
for (const [path, file] of files.byPath) {
|
|
301
|
-
if (expression.test(path)) {
|
|
302
|
-
patterned.push({ file, pattern });
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
const unmapped = [];
|
|
307
|
-
const mapped = patterned.map(({ file }) => file);
|
|
308
|
-
for (const { entry } of entries) {
|
|
309
|
-
if (entry.held !== undefined) {
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
const named = entryFiles(entry);
|
|
313
|
-
if (named.length === 0 && entry.role !== "script") {
|
|
314
|
-
unmapped.push(entry);
|
|
315
|
-
}
|
|
316
|
-
mapped.push(...named);
|
|
317
|
-
for (const file of named) {
|
|
318
|
-
const kind = ENTRY_KINDS[entry.role];
|
|
319
|
-
enter(file, kind, entry.member, !publishedNames.has(file.fileName));
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
const unmappedEntries = unmapped.filter((entry) => readBackByName(options.host, entry, mapped).length === 0);
|
|
323
|
-
for (const { file, pattern } of patterned) {
|
|
324
|
-
enter(file, "entry-file", pattern, true);
|
|
325
|
-
}
|
|
326
|
-
for (const { file, row } of options.conventions === undefined
|
|
327
|
-
? []
|
|
328
|
-
: conventionEntries(options.conventions, files)) {
|
|
329
|
-
enter(file, "convention", row, true);
|
|
330
|
-
}
|
|
331
|
-
for (const point of points) {
|
|
332
|
-
enter(point.file, point.rule, point.source, point.exports, point.names);
|
|
333
|
-
}
|
|
334
|
-
const heldScripts = scriptHolds(entries.map(({ entry }) => entry), files, entered, options.host, targetRoot);
|
|
335
|
-
const heldFiles = [...new Set(heldScripts.flatMap((one) => one.files))].flatMap((path) => files.byPath.get(path) ?? []);
|
|
336
|
-
for (const file of files.byPath.values()) {
|
|
337
|
-
for (const alias of typeQueryAliases(file)) {
|
|
338
|
-
const source = alias.type.getText().replace(/\s+/gu, " ");
|
|
339
|
-
for (const node of [alias.declaration, ...alias.containers]) {
|
|
340
|
-
const id = declared.get(nodeKey(file, node));
|
|
341
|
-
if (id !== undefined) {
|
|
342
|
-
add(id, "type-query-alias", source);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
const { tables: exportsOf, named, answered, } = exportTables(project, held, [
|
|
348
|
-
...entered.filter((entry) => entry.exports).map((entry) => entry.file),
|
|
349
|
-
...published,
|
|
350
|
-
...heldFiles,
|
|
351
|
-
]);
|
|
352
|
-
for (const entry of entered) {
|
|
353
|
-
const id = fileIds.get(entry.file.fileName);
|
|
354
|
-
if (id === undefined) {
|
|
355
|
-
continue;
|
|
356
|
-
}
|
|
357
|
-
add(id, entry.kind, entry.source);
|
|
358
|
-
if (entry.exports) {
|
|
359
|
-
const byName = named.get(entry.file.fileName);
|
|
360
|
-
const exported = entry.names === undefined || byName === undefined
|
|
361
|
-
? (exportsOf.get(entry.file.fileName) ?? [])
|
|
362
|
-
: entry.names.flatMap((name) => byName.get(name) ?? []);
|
|
363
|
-
for (const one of exported) {
|
|
364
|
-
add(one, entry.kind, entry.source);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
const tagged = releaseTagged(held, files).filter((id) => options.publishedAPI || inPublishedMember(held, id, targetRoot, options.publishedMembers));
|
|
369
|
-
const api = published.length > 0 || tagged.length > 0
|
|
370
|
-
? publishedAPI(project, published, exportsOf, held, files.byPath, tagged)
|
|
371
|
-
: { ids: [], guessed: [] };
|
|
372
|
-
for (const id of api.ids) {
|
|
373
|
-
add(id, "published-api", "");
|
|
374
|
-
}
|
|
375
|
-
const unmatched = configured(held, options.patterns, add);
|
|
376
|
-
const sites = new Map(held.symbols.map((symbol) => [symbol.id, symbol.position]));
|
|
377
|
-
const ordered = [...found.values()].sort((a, b) => {
|
|
378
|
-
const left = sites.get(a.id);
|
|
379
|
-
const right = sites.get(b.id);
|
|
380
|
-
const site = left === undefined || right === undefined ? 0 : byPosition(left, right);
|
|
381
|
-
return site || bytewise(a.kind, b.kind) || bytewise(a.source, b.source);
|
|
382
|
-
});
|
|
383
|
-
return {
|
|
384
|
-
liveUnderReachability: ordered,
|
|
385
|
-
unmatched,
|
|
386
|
-
unanswered: [
|
|
387
|
-
...(answered ? api.guessed : held.symbols.map((symbol) => symbol.id)),
|
|
388
|
-
// A command runs a held file and may read any export of it, so the file, which reaches its
|
|
389
|
-
// top level, and its exports are held; a declaration neither reaches is still dead.
|
|
390
|
-
...heldFiles.flatMap((file) => {
|
|
391
|
-
const id = fileIds.get(file.fileName);
|
|
392
|
-
return [...(id === undefined ? [] : [id]), ...(exportsOf.get(file.fileName) ?? [])];
|
|
393
|
-
}),
|
|
394
|
-
],
|
|
395
|
-
unmappedEntries,
|
|
396
|
-
heldScripts,
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Every declaration each file's module exports, by the file's name: the declaration
|
|
401
|
-
* each name of the export table is, and every link of a re-export's chain. A name
|
|
402
|
-
* whose declaration is outside the project's own files names nothing here. One batch
|
|
403
|
-
* resolves every file to its module and one request per module reads its export
|
|
404
|
-
* table, star re-exports resolved into it. A file that is no module exports nothing.
|
|
405
|
-
* `answered` is false where the checker left a table or a link of a chain unanswered.
|
|
406
|
-
*/
|
|
407
|
-
function exportTables(project, held, wanted) {
|
|
408
|
-
const unique = [...new Map(wanted.map((file) => [file.fileName, file])).values()];
|
|
409
|
-
const tables = new Map();
|
|
410
|
-
const named = new Map();
|
|
411
|
-
let answered = true;
|
|
412
|
-
if (unique.length === 0) {
|
|
413
|
-
return { tables, named, answered };
|
|
414
|
-
}
|
|
415
|
-
const modules = project.symbolsAt(unique.map((file) => project.handle(file)));
|
|
416
|
-
const chains = aliasChains(project, held);
|
|
417
|
-
const topLevel = (file) => {
|
|
418
|
-
const fileId = held.declarations.get(nodeKey(file, file));
|
|
419
|
-
return held.symbols.filter((symbol) => symbol.parent === fileId).map((symbol) => symbol.id);
|
|
420
|
-
};
|
|
421
|
-
unique.forEach((file, index) => {
|
|
422
|
-
const module = modules[index];
|
|
423
|
-
if (module === undefined) {
|
|
424
|
-
tables.set(file.fileName, []);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
const table = module === UNANSWERED ? UNANSWERED : project.queries.exportsOfModule(module);
|
|
428
|
-
if (table === UNANSWERED) {
|
|
429
|
-
answered = false;
|
|
430
|
-
tables.set(file.fileName, topLevel(file));
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
const ids = new Set();
|
|
434
|
-
const byName = new Map();
|
|
435
|
-
for (const exported of table) {
|
|
436
|
-
for (const target of chains.chainOf(exported)) {
|
|
437
|
-
answered &&= !target.guessed;
|
|
438
|
-
ids.add(target.id);
|
|
439
|
-
byName.set(exported.name, [...(byName.get(exported.name) ?? []), target.id]);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
tables.set(file.fileName, [...ids]);
|
|
443
|
-
named.set(file.fileName, byName);
|
|
444
|
-
});
|
|
445
|
-
return { tables, named, answered };
|
|
446
|
-
}
|
|
447
|
-
/** A release tag of a documentation comment: `@` and its name, between white space or the ends. */
|
|
448
|
-
const RELEASE_TAG = /(?:^|\s)@(?:public|beta|alpha)(?=\s|$)/u;
|
|
449
|
-
/**
|
|
450
|
-
* Whether the documentation comment of the statement that starts at `start`, the last
|
|
451
|
-
* `/** *\/` comment before it with only white space between the two, holds a release tag.
|
|
452
|
-
*/
|
|
453
|
-
function releaseTagIn(text, from, start) {
|
|
454
|
-
const leading = text.slice(from, start);
|
|
455
|
-
const open = leading.lastIndexOf("/**");
|
|
456
|
-
const close = open < 0 ? -1 : leading.indexOf("*/", open + 3);
|
|
457
|
-
if (close < 0 || leading.slice(close + 2).trim() !== "") {
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
return RELEASE_TAG.test(leading.slice(open + 3, close));
|
|
461
|
-
}
|
|
462
|
-
/** The exported top-level declarations of the program's files a release tag marks. */
|
|
463
|
-
function releaseTagged(held, files) {
|
|
464
|
-
const exported = new Set(held.symbols.filter((symbol) => symbol.exported).map((one) => one.id));
|
|
465
|
-
const found = [];
|
|
466
|
-
for (const file of files.byPath.values()) {
|
|
467
|
-
for (const statement of file.statements) {
|
|
468
|
-
if (!releaseTagIn(file.text, statement.pos, statement.getStart())) {
|
|
469
|
-
continue;
|
|
470
|
-
}
|
|
471
|
-
const nodes = isVariableStatement(statement)
|
|
472
|
-
? statement.declarationList.declarations
|
|
473
|
-
: [statement];
|
|
474
|
-
for (const node of nodes) {
|
|
475
|
-
const id = held.declarations.get(nodeKey(file, node));
|
|
476
|
-
if (id !== undefined && exported.has(id)) {
|
|
477
|
-
found.push(id);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
return found;
|
|
483
|
-
}
|
|
484
|
-
/** The kinds of container whose export table, not its members' visibility, says what it publishes. */
|
|
485
|
-
const EXPORTING_CONTAINERS = new Set(["file", "namespace"]);
|
|
486
|
-
/**
|
|
487
|
-
* Every declaration a consumer of the library can name, a published file itself excluded:
|
|
488
|
-
* what the published files export, with a namespace's exported members and any other
|
|
489
|
-
* declaration's public and protected ones; a declaration a published one's type names; an
|
|
490
|
-
* exported declaration a release tag marks; each declaration a `declare global` block holds
|
|
491
|
-
* in a published file or one holding a published declaration; and, as `guessed`, each
|
|
492
|
-
* declaration spelled as a type name the checker leaves unanswered.
|
|
493
|
-
*/
|
|
494
|
-
function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
|
|
495
|
-
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
496
|
-
const children = childrenOf(held);
|
|
497
|
-
const nodes = declarationNodes(held, byPath);
|
|
498
|
-
const chains = aliasChains(project, held);
|
|
499
|
-
const reached = new Set();
|
|
500
|
-
const entered = new Set();
|
|
501
|
-
const guessed = new Set();
|
|
502
|
-
const pending = [];
|
|
503
|
-
const reach = (id) => {
|
|
504
|
-
const file = byId.get(id)?.kind === "file";
|
|
505
|
-
if (!(file ? entered : reached).has(id)) {
|
|
506
|
-
(file ? entered : reached).add(id);
|
|
507
|
-
pending.push(id);
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
for (const id of tagged) {
|
|
511
|
-
reach(id);
|
|
512
|
-
}
|
|
513
|
-
for (const file of published) {
|
|
514
|
-
for (const id of exportsOf.get(file.fileName) ?? []) {
|
|
515
|
-
reach(id);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
const files = new Set(held.symbols.filter((one) => one.kind === "file").map((one) => one.id));
|
|
519
|
-
const blocks = held.symbols.filter((one) => isGlobalBlock(one, held, files));
|
|
520
|
-
const fileOf = (id) => {
|
|
521
|
-
let at = byId.get(id);
|
|
522
|
-
while (at !== undefined && at.kind !== "file") {
|
|
523
|
-
at = byId.get(at.parent);
|
|
524
|
-
}
|
|
525
|
-
return at?.id;
|
|
526
|
-
};
|
|
527
|
-
const publishedFiles = published.flatMap((file) => held.declarations.get(nodeKey(file, file)) ?? []);
|
|
528
|
-
const globalsReached = () => {
|
|
529
|
-
const typed = new Set([...publishedFiles, ...[...reached].flatMap((id) => fileOf(id) ?? [])]);
|
|
530
|
-
const before = pending.length;
|
|
531
|
-
for (const block of blocks.filter((one) => typed.has(one.parent))) {
|
|
532
|
-
reach(block.id);
|
|
533
|
-
for (const member of children.get(block.id) ?? []) {
|
|
534
|
-
if (member.kind !== "type-parameter") {
|
|
535
|
-
reach(member.id);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
return pending.length > before;
|
|
540
|
-
};
|
|
541
|
-
do {
|
|
542
|
-
while (pending.length > 0) {
|
|
543
|
-
const round = [];
|
|
544
|
-
for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
|
|
545
|
-
round.push(next);
|
|
546
|
-
const exporting = EXPORTING_CONTAINERS.has(byId.get(next)?.kind ?? "");
|
|
547
|
-
for (const member of children.get(next) ?? []) {
|
|
548
|
-
const reachable = exporting
|
|
549
|
-
? member.exported
|
|
550
|
-
: member.visibility !== "private" && member.visibility !== "private-name";
|
|
551
|
-
if (member.kind !== "type-parameter" && reachable) {
|
|
552
|
-
reach(member.id);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
const named = round.flatMap((id) => {
|
|
557
|
-
const symbol = byId.get(id);
|
|
558
|
-
return symbol === undefined || symbol.kind === "file" ? [] : nodes.typeNamesOf(symbol);
|
|
559
|
-
});
|
|
560
|
-
const inferred = inferredTypeSymbols(project, round.flatMap((id) => {
|
|
561
|
-
const symbol = byId.get(id);
|
|
562
|
-
return symbol === undefined ? [] : nodes.nodesOf(symbol);
|
|
563
|
-
}));
|
|
564
|
-
for (const symbol of inferred.types) {
|
|
565
|
-
for (const target of chains.chainOf(symbol)) {
|
|
566
|
-
reach(target.id);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
for (const symbol of inferred.members) {
|
|
570
|
-
for (const target of chains.chainOf(symbol)) {
|
|
571
|
-
const member = byId.get(target.id);
|
|
572
|
-
if (member?.visibility === "public" || member?.visibility === "protected") {
|
|
573
|
-
reach(target.id);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
project.symbolsAt(named.map((node) => project.handle(node))).forEach((symbol, index) => {
|
|
578
|
-
if (symbol === UNANSWERED) {
|
|
579
|
-
for (const id of held.byName.get(named[index]?.getText() ?? "") ?? []) {
|
|
580
|
-
guessed.add(id);
|
|
581
|
-
reach(id);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
else if (symbol !== undefined) {
|
|
585
|
-
for (const target of chains.chainOf(symbol)) {
|
|
586
|
-
reach(target.id);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
} while (globalsReached());
|
|
592
|
-
return { ids: [...reached], guessed: [...guessed] };
|
|
593
|
-
}
|
|
594
|
-
/** The type names declarations of the inventory write, each file walked once, when first asked. */
|
|
595
|
-
function declarationNodes(held, byPath) {
|
|
596
|
-
const walked = new Map();
|
|
597
|
-
const nodesIn = (path) => {
|
|
598
|
-
const known = walked.get(path);
|
|
599
|
-
if (known !== undefined) {
|
|
600
|
-
return known;
|
|
601
|
-
}
|
|
602
|
-
const found = { byId: new Map(), nodes: new Set() };
|
|
603
|
-
const file = byPath.get(path);
|
|
604
|
-
if (file !== undefined) {
|
|
605
|
-
const visit = (node) => {
|
|
606
|
-
const id = held.declarations.get(nodeKey(file, node));
|
|
607
|
-
if (id !== undefined) {
|
|
608
|
-
found.nodes.add(node);
|
|
609
|
-
found.byId.set(id, [...(found.byId.get(id) ?? []), node]);
|
|
610
|
-
}
|
|
611
|
-
node.forEachChild(visit);
|
|
612
|
-
};
|
|
613
|
-
visit(file);
|
|
614
|
-
}
|
|
615
|
-
walked.set(path, found);
|
|
616
|
-
return found;
|
|
617
|
-
};
|
|
618
|
-
return {
|
|
619
|
-
typeNamesOf: (symbol) => {
|
|
620
|
-
const { byId, nodes } = nodesIn(symbol.position.path);
|
|
621
|
-
return (byId.get(symbol.id) ?? []).flatMap((declaration) => typeNamesOf(declaration, nodes));
|
|
622
|
-
},
|
|
623
|
-
nodesOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id) ?? [],
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
/** How deep the type arguments and constituents of an inferred type are followed. */
|
|
627
|
-
const INFERRED_DEPTH = 4;
|
|
628
|
-
/**
|
|
629
|
-
* The values one function body returns, each through the type assertions around it: the
|
|
630
|
-
* value a caller receives carries the operand's type whatever type the assertion or the
|
|
631
|
-
* signature names. A nested function's returns are its own.
|
|
632
|
-
*/
|
|
633
|
-
function returnedValues(body) {
|
|
634
|
-
const found = [];
|
|
635
|
-
const add = (value) => {
|
|
636
|
-
for (let at = value; at !== undefined;) {
|
|
637
|
-
found.push(at);
|
|
638
|
-
at =
|
|
639
|
-
isAssertionExpression(at) || isSatisfiesExpression(at) || isParenthesizedExpression(at)
|
|
640
|
-
? at.expression
|
|
641
|
-
: undefined;
|
|
642
|
-
}
|
|
643
|
-
};
|
|
644
|
-
if (!isFunctionLikeDeclaration(body.parent) || body.parent.body !== body) {
|
|
645
|
-
return found;
|
|
646
|
-
}
|
|
647
|
-
if (isArrowFunction(body.parent) && body.kind !== SyntaxKind.Block) {
|
|
648
|
-
add(body);
|
|
649
|
-
return found;
|
|
650
|
-
}
|
|
651
|
-
const visit = (node) => {
|
|
652
|
-
if (isFunctionLikeDeclaration(node)) {
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
if (isReturnStatement(node) && node.expression !== undefined) {
|
|
656
|
-
add(node.expression);
|
|
657
|
-
}
|
|
658
|
-
node.forEachChild(visit);
|
|
659
|
-
};
|
|
660
|
-
body.forEachChild(visit);
|
|
661
|
-
return found;
|
|
662
|
-
}
|
|
663
|
-
/** The object literal one returned value is, written there or as the initializer of a variable `body` declares. */
|
|
664
|
-
function returnedLiteral(value, body) {
|
|
665
|
-
if (isObjectLiteralExpression(value)) {
|
|
666
|
-
return value;
|
|
667
|
-
}
|
|
668
|
-
if (!isIdentifier(value)) {
|
|
669
|
-
return undefined;
|
|
670
|
-
}
|
|
671
|
-
let found;
|
|
672
|
-
const visit = (node) => {
|
|
673
|
-
if (isVariableDeclaration(node) &&
|
|
674
|
-
isIdentifier(node.name) &&
|
|
675
|
-
node.name.text === value.text &&
|
|
676
|
-
node.initializer !== undefined) {
|
|
677
|
-
let at = node.initializer;
|
|
678
|
-
while (isAssertionExpression(at) ||
|
|
679
|
-
isSatisfiesExpression(at) ||
|
|
680
|
-
isParenthesizedExpression(at)) {
|
|
681
|
-
at = at.expression;
|
|
682
|
-
}
|
|
683
|
-
found = isObjectLiteralExpression(at) ? at : found;
|
|
684
|
-
}
|
|
685
|
-
if (!isFunctionLikeDeclaration(node)) {
|
|
686
|
-
node.forEachChild(visit);
|
|
687
|
-
}
|
|
688
|
-
};
|
|
689
|
-
body.forEachChild(visit);
|
|
690
|
-
return found;
|
|
691
|
-
}
|
|
692
|
-
/**
|
|
693
|
-
* The values one function body returns, and those each method of an object literal it
|
|
694
|
-
* returns returns, to a fixed depth: a caller holding the literal calls its methods, and
|
|
695
|
-
* receives what they return.
|
|
696
|
-
*/
|
|
697
|
-
function returnedThroughLiterals(body, depth = 0) {
|
|
698
|
-
const values = returnedValues(body);
|
|
699
|
-
if (depth >= INFERRED_DEPTH) {
|
|
700
|
-
return values;
|
|
701
|
-
}
|
|
702
|
-
const found = [...values];
|
|
703
|
-
for (const value of values) {
|
|
704
|
-
for (const property of returnedLiteral(value, body)?.properties ?? []) {
|
|
705
|
-
const fn = isMethodDeclaration(property)
|
|
706
|
-
? property
|
|
707
|
-
: isPropertyAssignment(property) && isFunctionLikeDeclaration(property.initializer)
|
|
708
|
-
? property.initializer
|
|
709
|
-
: undefined;
|
|
710
|
-
if (fn?.body !== undefined) {
|
|
711
|
-
found.push(...returnedThroughLiterals(fn.body, depth + 1));
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
return found;
|
|
716
|
-
}
|
|
717
|
-
/**
|
|
718
|
-
* The declarations of the types the compiler infers where a declaration writes none, and
|
|
719
|
-
* of the values a function returns: the type of a variable or a property with no
|
|
720
|
-
* annotation, the result of a function or a method with no result annotation, and the type
|
|
721
|
-
* of each value its body returns, each with the types its type arguments and its union or
|
|
722
|
-
* intersection constituents name, to a fixed depth; and as `members` the properties of each
|
|
723
|
-
* returned value's type. A question the checker leaves unanswered names nothing.
|
|
724
|
-
*/
|
|
725
|
-
function inferredTypeSymbols(project, declarations) {
|
|
726
|
-
const { queries } = project;
|
|
727
|
-
const valued = declarations.flatMap((node) => (isVariableDeclaration(node) || isPropertyDeclaration(node)) &&
|
|
728
|
-
node.type === undefined &&
|
|
729
|
-
!isFunctionLikeDeclaration(node.initializer ?? node)
|
|
730
|
-
? [node.name]
|
|
731
|
-
: []);
|
|
732
|
-
const returned = declarations.flatMap((node) => {
|
|
733
|
-
const signature = isFunctionLikeDeclaration(node)
|
|
734
|
-
? node
|
|
735
|
-
: isVariableDeclaration(node) &&
|
|
736
|
-
node.initializer !== undefined &&
|
|
737
|
-
isFunctionLikeDeclaration(node.initializer)
|
|
738
|
-
? node.initializer
|
|
739
|
-
: undefined;
|
|
740
|
-
return signature?.body === undefined ? [] : returnedThroughLiterals(signature.body);
|
|
741
|
-
});
|
|
742
|
-
const types = [];
|
|
743
|
-
const returnedTypes = [];
|
|
744
|
-
queries.typesAt([...valued, ...returned]).forEach((type, at) => {
|
|
745
|
-
if (type !== UNANSWERED && type !== undefined) {
|
|
746
|
-
(at < valued.length ? types : returnedTypes).push(type);
|
|
747
|
-
}
|
|
748
|
-
});
|
|
749
|
-
types.push(...returnedTypes);
|
|
750
|
-
for (const node of declarations) {
|
|
751
|
-
const signature = isFunctionLikeDeclaration(node)
|
|
752
|
-
? node
|
|
753
|
-
: isVariableDeclaration(node) &&
|
|
754
|
-
node.type === undefined &&
|
|
755
|
-
node.initializer !== undefined &&
|
|
756
|
-
isFunctionLikeDeclaration(node.initializer)
|
|
757
|
-
? node.initializer
|
|
758
|
-
: undefined;
|
|
759
|
-
if (signature === undefined || signature.type !== undefined || signature.body === undefined) {
|
|
760
|
-
continue;
|
|
761
|
-
}
|
|
762
|
-
const typed = queries.signatureOf(signature);
|
|
763
|
-
const result = typed === UNANSWERED || typed === undefined ? undefined : queries.returnTypeOf(typed);
|
|
764
|
-
if (result !== UNANSWERED && result !== undefined) {
|
|
765
|
-
types.push(result);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
const found = new Map();
|
|
769
|
-
const seen = new Set();
|
|
770
|
-
const visit = (type, depth) => {
|
|
771
|
-
if (depth > INFERRED_DEPTH || seen.has(type)) {
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
seen.add(type);
|
|
775
|
-
const symbol = queries.symbolOfType(type);
|
|
776
|
-
if (symbol !== UNANSWERED && symbol !== undefined) {
|
|
777
|
-
found.set(symbol.id, symbol);
|
|
778
|
-
}
|
|
779
|
-
const parts = queries.constituents(type);
|
|
780
|
-
const args = queries.typeArguments(type);
|
|
781
|
-
for (const next of [
|
|
782
|
-
...(parts === UNANSWERED || (parts.length === 1 && parts[0] === type) ? [] : parts),
|
|
783
|
-
...(args === UNANSWERED ? [] : args),
|
|
784
|
-
]) {
|
|
785
|
-
visit(next, depth + 1);
|
|
786
|
-
}
|
|
787
|
-
};
|
|
788
|
-
types.forEach((type) => {
|
|
789
|
-
visit(type, 0);
|
|
790
|
-
});
|
|
791
|
-
// A returned value carries each property of its type, one an anonymous type declares or
|
|
792
|
-
// an intersection merges included, which no type name reaches.
|
|
793
|
-
const members = new Map();
|
|
794
|
-
for (const type of returnedTypes) {
|
|
795
|
-
const parts = queries.constituents(type);
|
|
796
|
-
for (const one of [type, ...(parts === UNANSWERED ? [] : parts)]) {
|
|
797
|
-
const properties = queries.propertiesOf(one);
|
|
798
|
-
for (const property of properties === UNANSWERED ? [] : properties) {
|
|
799
|
-
members.set(property.id, property);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
return { types: [...found.values()], members: [...members.values()] };
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* The names of types one declaration's own syntax writes: in a type reference, a
|
|
807
|
-
* heritage clause or a type query. A function body is not read, and neither is a
|
|
808
|
-
* declaration nested in this one, which `nested` holds and which is read on its own.
|
|
809
|
-
*/
|
|
810
|
-
function typeNamesOf(declaration, nested) {
|
|
811
|
-
const found = [];
|
|
812
|
-
const visit = (node) => {
|
|
813
|
-
if (node !== declaration && nested.has(node)) {
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
|
-
if (isTypeReferenceNode(node)) {
|
|
817
|
-
found.push(isQualifiedName(node.typeName) ? node.typeName.right : node.typeName);
|
|
818
|
-
}
|
|
819
|
-
else if (isExpressionWithTypeArguments(node)) {
|
|
820
|
-
found.push(isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression);
|
|
821
|
-
}
|
|
822
|
-
else if (isTypeQueryNode(node)) {
|
|
823
|
-
found.push(isQualifiedName(node.exprName) ? node.exprName.right : node.exprName);
|
|
824
|
-
}
|
|
825
|
-
const body = isFunctionLikeDeclaration(node) ? node.body : undefined;
|
|
826
|
-
node.forEachChild((child) => {
|
|
827
|
-
if (child !== body) {
|
|
828
|
-
visit(child);
|
|
829
|
-
}
|
|
830
|
-
});
|
|
831
|
-
};
|
|
832
|
-
visit(declaration);
|
|
833
|
-
return found;
|
|
834
|
-
}
|
|
835
|
-
/**
|
|
836
|
-
* Keeps every declaration the configuration names and returns every string that
|
|
837
|
-
* named none, in configuration order. A string the configuration lists twice is one
|
|
838
|
-
* configured root.
|
|
839
|
-
*/
|
|
840
|
-
function configured(held, patterns, add) {
|
|
841
|
-
const unmatched = [];
|
|
842
|
-
for (const pattern of new Set(patterns)) {
|
|
843
|
-
const kind = isPattern(pattern) ? "pattern" : "configured";
|
|
844
|
-
let matched = false;
|
|
845
|
-
for (const symbol of held.symbols) {
|
|
846
|
-
if (matchRef(pattern, symbol.ref)) {
|
|
847
|
-
add(symbol.id, kind, pattern);
|
|
848
|
-
matched = true;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
if (!matched) {
|
|
852
|
-
unmatched.push(pattern);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
return unmatched;
|
|
856
|
-
}
|
|
857
|
-
/**
|
|
858
|
-
* Every configured string that named no declaration in any project of the run, in
|
|
859
|
-
* configuration order and each once. The finding is about the configuration, so a string
|
|
860
|
-
* naming a declaration of one project has named something, and a run with no project
|
|
861
|
-
* names nothing with any of them.
|
|
862
|
-
*/
|
|
863
|
-
export function unmatchedEverywhere(patterns, perProject) {
|
|
864
|
-
return [...new Set(patterns)].filter((pattern) => perProject.every((project) => project.unmatched.includes(pattern)));
|
|
865
|
-
}
|