@cplieger/deadset-ts 7.2.0-dev.3 → 8.0.0-dev.1
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/bin/mapper-connection.js +7 -4
- package/dist/src/analysis.js +116 -16
- package/dist/src/augmentations.js +21 -8
- package/dist/src/automatic-types.js +189 -0
- package/dist/src/base-url.js +18 -0
- package/dist/src/component-files.js +96 -31
- package/dist/src/components.js +7 -6
- package/dist/src/config.js +1 -0
- package/dist/src/configuration-files.js +54 -15
- package/dist/src/configured-declarations.js +24 -2
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +26 -26
- package/dist/src/convention-rows.js +75 -0
- package/dist/src/conventions.js +101 -5
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +185 -30
- package/dist/src/destructuring.js +77 -2
- package/dist/src/discover.js +188 -85
- package/dist/src/edges.js +1 -1
- package/dist/src/entry-point-gaps.js +7 -10
- package/dist/src/entry-points.js +112 -11
- package/dist/src/findings/completion.js +116 -21
- package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +44 -13
- package/dist/src/findings/non-code-artifacts.js +66 -33
- package/dist/src/findings/unused-declarations.js +21 -1
- package/dist/src/findings/visibility-narrowing.js +22 -3
- package/dist/src/generated-file.js +11 -0
- package/dist/src/git-ignore.js +86 -0
- package/dist/src/graph.js +4 -1
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +313 -50
- package/dist/src/inventory.js +63 -8
- package/dist/src/json-document.js +5 -5
- package/dist/src/kinds.js +13 -13
- package/dist/src/matrix.js +18 -9
- package/dist/src/mdx-entries.js +197 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/namespace-objects.js +28 -18
- package/dist/src/overrides.js +45 -9
- package/dist/src/paths.js +9 -0
- package/dist/src/print.js +1 -0
- package/dist/src/ref.js +10 -0
- package/dist/src/references.js +29 -13
- package/dist/src/reflective-lookup.js +8 -6
- package/dist/src/report.js +4 -2
- package/dist/src/reporters/template.js +22 -13
- package/dist/src/resolve.js +2 -0
- package/dist/src/roots.js +110 -38
- package/dist/src/schema.js +1 -0
- package/dist/src/serialization-contract.js +97 -20
- package/dist/src/session.js +26 -1
- package/dist/src/source-files.js +39 -14
- package/dist/src/spelled-names.js +58 -0
- package/dist/src/suppress-file.js +12 -4
- package/dist/src/suppress-inline.js +21 -3
- package/dist/src/suppress.js +7 -5
- package/dist/src/sweep.js +27 -10
- package/dist/src/symbol-keys.js +103 -2
- package/dist/src/template-field.js +9 -6
- package/dist/src/test-support.js +121 -17
- package/dist/src/type-errors.js +116 -35
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/value-reach.js +17 -5
- package/dist/src/verbs/analyze.js +19 -1
- package/dist/src/verbs/explain.js +4 -2
- package/dist/src/version-range.js +10 -5
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +119 -22
- package/dist/src/workspace-programs.js +7 -3
- package/dist/src/workspace-resolution.js +72 -13
- package/dist/src/workspace.js +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +175 -16
- package/src/augmentations.ts +21 -10
- package/src/automatic-types.ts +246 -0
- package/src/base-url.ts +24 -0
- package/src/component-files.ts +116 -31
- package/src/components.ts +7 -6
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +80 -18
- package/src/configured-declarations.ts +33 -4
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +27 -27
- package/src/convention-rows.ts +103 -0
- package/src/conventions.ts +121 -7
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +249 -43
- package/src/destructuring.ts +93 -2
- package/src/discover.ts +234 -86
- package/src/edges.ts +1 -1
- package/src/entry-point-gaps.ts +8 -11
- package/src/entry-points.ts +133 -11
- package/src/finding.ts +10 -0
- package/src/findings/completion.ts +147 -24
- package/src/findings/dependencies-and-module-machinery.ts +90 -28
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +56 -14
- package/src/findings/non-code-artifacts.ts +81 -41
- package/src/findings/unused-declarations.ts +25 -1
- package/src/findings/visibility-narrowing.ts +32 -3
- package/src/generated-file.ts +16 -0
- package/src/git-ignore.ts +98 -0
- package/src/graph.ts +6 -0
- package/src/host.ts +5 -9
- package/src/import-bindings.ts +128 -0
- package/src/indexed-access.ts +152 -30
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +415 -64
- package/src/inventory.ts +83 -7
- package/src/json-document.ts +7 -7
- package/src/kinds.ts +13 -13
- package/src/matrix.ts +32 -14
- package/src/mdx-entries.ts +241 -0
- package/src/module-declarations.ts +11 -13
- package/src/namespace-objects.ts +34 -21
- package/src/overrides.ts +56 -9
- package/src/paths.ts +10 -0
- package/src/print.ts +1 -0
- package/src/ref.ts +12 -0
- package/src/references.ts +38 -17
- package/src/reflective-lookup.ts +9 -7
- package/src/report.ts +16 -3
- package/src/reporters/template.ts +21 -13
- package/src/resolve.ts +3 -0
- package/src/roots.ts +163 -45
- package/src/schema.ts +1 -0
- package/src/serialization-contract.ts +108 -17
- package/src/session.ts +29 -1
- package/src/source-files.ts +66 -16
- package/src/spelled-names.ts +102 -0
- package/src/suppress-file.ts +14 -3
- package/src/suppress-inline.ts +21 -2
- package/src/suppress.ts +9 -5
- package/src/sweep.ts +32 -11
- package/src/symbol-keys.ts +129 -2
- package/src/template-field.ts +9 -6
- package/src/test-support.ts +149 -16
- package/src/type-errors.ts +132 -41
- package/src/type-parameter-reads.ts +227 -0
- package/src/value-reach.ts +19 -6
- package/src/verbs/analyze.ts +28 -1
- package/src/verbs/explain.ts +5 -2
- package/src/version-range.ts +10 -5
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +127 -22
- package/src/workspace-programs.ts +7 -3
- package/src/workspace-resolution.ts +86 -14
- package/src/workspace.ts +1 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files the import declarations of the target's Markdown and MDX documents load. A
|
|
3
|
+
* document is read as text: a line outside a fenced code block that starts with `import`
|
|
4
|
+
* is an import declaration, which an MDX compiler hoists into the module it builds.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { SourceFile } from "@typescript/native/ast";
|
|
8
|
+
import { resolveRelativeSpecifier } from "./configuration-files.ts";
|
|
9
|
+
import type { Host } from "./host.ts";
|
|
10
|
+
import { dirnamePath, joinPath, resolvePath } from "./paths.ts";
|
|
11
|
+
import type { SourceFiles } from "./source-files.ts";
|
|
12
|
+
|
|
13
|
+
/** One file a document imports, the specifier that named it, and the export names it reads. */
|
|
14
|
+
interface DocumentEntry {
|
|
15
|
+
readonly file: SourceFile;
|
|
16
|
+
readonly source: string;
|
|
17
|
+
/** The export names the import reads, or undefined for a namespace import, which reads them all. */
|
|
18
|
+
readonly names: readonly string[] | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The directories no document is read below. */
|
|
22
|
+
const SKIPPED: ReadonlySet<string> = new Set(["node_modules", ".git"]);
|
|
23
|
+
|
|
24
|
+
/** The extensions of a document whose imports an MDX compiler reads. */
|
|
25
|
+
const DOCUMENT = /\.mdx?$/u;
|
|
26
|
+
|
|
27
|
+
/** A fence line, its marker run and what follows it captured. */
|
|
28
|
+
const FENCE = /^ {0,3}(`{3,}|~{3,})(.*)$/u;
|
|
29
|
+
|
|
30
|
+
/** A string literal in either quote, its escapes included. */
|
|
31
|
+
const STRING = String.raw`"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'`;
|
|
32
|
+
|
|
33
|
+
/** An import declaration at the start of a line, its clause and its specifier captured. */
|
|
34
|
+
const IMPORT_LINE = new RegExp(
|
|
35
|
+
String.raw`^import\s+(?:((?:[^"'\n]|${STRING})*?)\s+from\s*)?["']([^"'\n]+)["']`,
|
|
36
|
+
"u",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/** The braces of an import clause, what they hold captured. */
|
|
40
|
+
const BRACES = new RegExp(String.raw`\{((?:[^}"']|${STRING})*)\}`, "u");
|
|
41
|
+
|
|
42
|
+
/** One comma-separated part of a clause's braces. */
|
|
43
|
+
const PART = new RegExp(String.raw`(?:[^,"']|${STRING})+`, "gu");
|
|
44
|
+
|
|
45
|
+
/** A named import of a clause's braces: the exported name and an optional local alias. */
|
|
46
|
+
const NAMED_IMPORT = new RegExp(String.raw`^([\w$]+|${STRING})(?:\s+as\s+[\w$]+)?$`, "u");
|
|
47
|
+
|
|
48
|
+
/** The single-character escapes of a string literal and the character each stands for. */
|
|
49
|
+
const SINGLE_ESCAPES: ReadonlyMap<string, string> = new Map([
|
|
50
|
+
["b", "\b"],
|
|
51
|
+
["f", "\f"],
|
|
52
|
+
["n", "\n"],
|
|
53
|
+
["r", "\r"],
|
|
54
|
+
["t", "\t"],
|
|
55
|
+
["v", "\v"],
|
|
56
|
+
["0", "\0"],
|
|
57
|
+
["'", "'"],
|
|
58
|
+
['"', '"'],
|
|
59
|
+
["\\", "\\"],
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* One escape of a string literal: two hexadecimal digits, a code unit, a code point, or one
|
|
64
|
+
* character and the digit after it.
|
|
65
|
+
*/
|
|
66
|
+
const ESCAPE = /\\(?:x([\da-fA-F]{2})|u([\da-fA-F]{4})|u\{([\da-fA-F]+)\}|(.)(\d?))/gu;
|
|
67
|
+
|
|
68
|
+
/** What one escape stands for, or undefined for one a module cannot hold. */
|
|
69
|
+
function unescaped([, hex, unit, point, single, digit]: RegExpExecArray): string | undefined {
|
|
70
|
+
if (single === undefined) {
|
|
71
|
+
const code = Number.parseInt(hex ?? unit ?? point ?? "", 16);
|
|
72
|
+
return code <= 0x10ffff ? String.fromCodePoint(code) : undefined;
|
|
73
|
+
}
|
|
74
|
+
return /[1-9xu]/u.test(single) || (single === "0" && digit !== "")
|
|
75
|
+
? undefined
|
|
76
|
+
: `${SINGLE_ESCAPES.get(single) ?? single}${digit ?? ""}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The value of a quoted export name, its escapes decoded, or undefined for an escape this
|
|
81
|
+
* reading does not decode. A module is strict code, so no legacy octal escape is valid.
|
|
82
|
+
*/
|
|
83
|
+
function quotedName(literal: string): string | undefined {
|
|
84
|
+
const body = literal.slice(1, -1);
|
|
85
|
+
let value = "";
|
|
86
|
+
let from = 0;
|
|
87
|
+
for (const escape of body.matchAll(ESCAPE)) {
|
|
88
|
+
const decoded = unescaped(escape);
|
|
89
|
+
if (decoded === undefined) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
value += body.slice(from, escape.index) + decoded;
|
|
93
|
+
from = escape.index + escape[0].length;
|
|
94
|
+
}
|
|
95
|
+
return value + body.slice(from);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The export names one import clause reads: `default` for a default binding and each name
|
|
100
|
+
* its braces list, or undefined for a namespace import, which reads every export, and for
|
|
101
|
+
* a clause this reading does not parse, which may read any.
|
|
102
|
+
*/
|
|
103
|
+
function clauseNames(clause: string): readonly string[] | undefined {
|
|
104
|
+
const braces = BRACES.exec(clause);
|
|
105
|
+
const head = (braces === null ? clause : clause.slice(0, braces.index))
|
|
106
|
+
.replace(/,\s*$/u, "")
|
|
107
|
+
.trim();
|
|
108
|
+
if (head.includes("*")) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const names = head === "" ? [] : ["default"];
|
|
112
|
+
if (head !== "" && !/^[\w$]+$/u.test(head)) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
for (const part of (braces?.[1]?.match(PART) ?? []).map((one) => one.trim())) {
|
|
116
|
+
const named = NAMED_IMPORT.exec(part);
|
|
117
|
+
if (part === "") {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (named?.[1] === undefined) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const name = /^["']/u.test(named[1]) ? quotedName(named[1]) : named[1];
|
|
124
|
+
if (name === undefined) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
names.push(name);
|
|
128
|
+
}
|
|
129
|
+
return names;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Every document below `dir`, outside every skipped directory, by absolute path. */
|
|
133
|
+
function documents(host: Host, dir: string): readonly string[] {
|
|
134
|
+
let entries;
|
|
135
|
+
try {
|
|
136
|
+
entries = host.readDirectory(dir);
|
|
137
|
+
} catch {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
const found: string[] = [];
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
const path = joinPath(dir, entry.name);
|
|
143
|
+
if (entry.directory) {
|
|
144
|
+
if (!SKIPPED.has(entry.name)) {
|
|
145
|
+
found.push(...documents(host, path));
|
|
146
|
+
}
|
|
147
|
+
} else if (DOCUMENT.test(entry.name)) {
|
|
148
|
+
found.push(path);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return found.sort();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The relative specifiers one document's import lines name, its fenced code blocks skipped. */
|
|
155
|
+
function importedBy(
|
|
156
|
+
text: string,
|
|
157
|
+
): readonly { readonly specifier: string; readonly names: readonly string[] | undefined }[] {
|
|
158
|
+
const found: { specifier: string; names: readonly string[] | undefined }[] = [];
|
|
159
|
+
let open: string | undefined;
|
|
160
|
+
for (const line of text.split(/\r?\n/u)) {
|
|
161
|
+
const fence = FENCE.exec(line);
|
|
162
|
+
const marker = fence?.[1];
|
|
163
|
+
if (marker !== undefined && open === undefined) {
|
|
164
|
+
open = marker;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
// https://spec.commonmark.org/0.31.2/#fenced-code-blocks: only a run of the opening
|
|
168
|
+
// character, at least as long and followed by nothing, closes a fence.
|
|
169
|
+
if (
|
|
170
|
+
marker !== undefined &&
|
|
171
|
+
open !== undefined &&
|
|
172
|
+
marker.startsWith(open.slice(0, 1)) &&
|
|
173
|
+
marker.length >= open.length &&
|
|
174
|
+
fence?.[2]?.trim() === ""
|
|
175
|
+
) {
|
|
176
|
+
open = undefined;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const matched = open === undefined ? IMPORT_LINE.exec(line) : null;
|
|
180
|
+
const specifier = matched?.[2];
|
|
181
|
+
if (specifier?.startsWith("./") === true || specifier?.startsWith("../") === true) {
|
|
182
|
+
found.push({ specifier, names: clauseNames(matched?.[1] ?? "") });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return found;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** One document below a target root, by absolute path, and what its import lines name. */
|
|
189
|
+
interface DocumentImports {
|
|
190
|
+
readonly path: string;
|
|
191
|
+
readonly imports: ReturnType<typeof importedBy>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The documents each host's target roots hold, which every project of a run reads alike,
|
|
196
|
+
* kept for the host's life as {@link Host} allows.
|
|
197
|
+
*/
|
|
198
|
+
const documentsOf = new WeakMap<Host, Map<string, readonly DocumentImports[]>>();
|
|
199
|
+
|
|
200
|
+
/** Every readable document below `root` and the import lines it writes, read once per host. */
|
|
201
|
+
function documentImports(host: Host, root: string): readonly DocumentImports[] {
|
|
202
|
+
const byRoot = documentsOf.get(host) ?? new Map<string, readonly DocumentImports[]>();
|
|
203
|
+
documentsOf.set(host, byRoot);
|
|
204
|
+
const known = byRoot.get(root);
|
|
205
|
+
if (known !== undefined) {
|
|
206
|
+
return known;
|
|
207
|
+
}
|
|
208
|
+
const found = documents(host, root).flatMap((path) => {
|
|
209
|
+
try {
|
|
210
|
+
return [{ path, imports: importedBy(host.readFile(path)) }];
|
|
211
|
+
} catch {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
byRoot.set(root, found);
|
|
216
|
+
return found;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Every own file one document below the target root imports by a relative specifier,
|
|
221
|
+
* read against the document's directory, on disk or through the emit mapping.
|
|
222
|
+
*/
|
|
223
|
+
export function documentEntries(
|
|
224
|
+
host: Host,
|
|
225
|
+
targetRoot: string,
|
|
226
|
+
files: SourceFiles,
|
|
227
|
+
): readonly DocumentEntry[] {
|
|
228
|
+
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
229
|
+
const found: DocumentEntry[] = [];
|
|
230
|
+
for (const { path, imports } of documentImports(host, root)) {
|
|
231
|
+
const dir = dirnamePath(path);
|
|
232
|
+
for (const { specifier, names } of imports) {
|
|
233
|
+
const reached = resolveRelativeSpecifier(host, dir, specifier);
|
|
234
|
+
const held = reached === undefined ? undefined : files.byName.get(reached);
|
|
235
|
+
for (const file of held === undefined ? files.named(resolvePath(dir, specifier)) : [held]) {
|
|
236
|
+
found.push({ file, source: specifier, names });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return found;
|
|
241
|
+
}
|
|
@@ -104,6 +104,8 @@ function namesModule(specifier: string, name: string): boolean {
|
|
|
104
104
|
interface Named {
|
|
105
105
|
readonly id: string;
|
|
106
106
|
readonly members: readonly string[];
|
|
107
|
+
/** Whether the declaration's name is a pattern, which a specifier names by matching it. */
|
|
108
|
+
readonly wildcard: boolean;
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
/**
|
|
@@ -140,7 +142,7 @@ function namedBy(
|
|
|
140
142
|
]),
|
|
141
143
|
);
|
|
142
144
|
const members = (children.get(id) ?? []).filter((child) => wanted.has(child.ref));
|
|
143
|
-
return [{ id, members: members.map((child) => child.id) }];
|
|
145
|
+
return [{ id, members: members.map((child) => child.id), wildcard: name.includes("*") }];
|
|
144
146
|
});
|
|
145
147
|
}
|
|
146
148
|
|
|
@@ -208,28 +210,24 @@ export function moduleDeclarationReferences<Brand>(
|
|
|
208
210
|
imports.forEach((one, index) => {
|
|
209
211
|
const resolved = moduleOf(declared.length + index);
|
|
210
212
|
const position = renderPosition(one.file, root, one.specifier.getStart());
|
|
211
|
-
const read = (to: string, resolution: Reference["resolution"]): void => {
|
|
212
|
-
found.push({
|
|
213
|
-
from: one.from,
|
|
214
|
-
to,
|
|
215
|
-
position,
|
|
216
|
-
use: "read",
|
|
217
|
-
resolution,
|
|
218
|
-
test: testFiles.has(position.path),
|
|
219
|
-
});
|
|
213
|
+
const read = (to: string, resolution: Reference["resolution"], test: boolean): void => {
|
|
214
|
+
found.push({ from: one.from, to, position, use: "read", resolution, test });
|
|
220
215
|
};
|
|
216
|
+
const test = testFiles.has(position.path);
|
|
221
217
|
const same = new Set(
|
|
222
218
|
declared
|
|
223
219
|
.filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
|
|
224
220
|
.map((declaration) => declaration.id),
|
|
225
221
|
);
|
|
226
222
|
for (const id of same) {
|
|
227
|
-
read(id, "batch");
|
|
223
|
+
read(id, "batch", test);
|
|
228
224
|
}
|
|
229
225
|
for (const declaration of named(one.specifier.text, one.names)) {
|
|
230
|
-
|
|
226
|
+
// A wildcard declaration is how the compiler types the file a matching specifier
|
|
227
|
+
// names, so the import uses it whoever writes it, a test included.
|
|
228
|
+
if (declaration.wildcard || !same.has(declaration.id)) {
|
|
231
229
|
for (const to of [declaration.id, ...declaration.members]) {
|
|
232
|
-
read(to, "syntax");
|
|
230
|
+
read(to, "syntax", test && !declaration.wildcard);
|
|
233
231
|
}
|
|
234
232
|
}
|
|
235
233
|
}
|
package/src/namespace-objects.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module namespace objects: the binding of a namespace import, the value an `await` of a
|
|
3
3
|
* dynamic `import()` produces, the first parameter of the first callback passed to `then`
|
|
4
|
-
* on one, and a
|
|
4
|
+
* on one, and a variable binding initialized with one of these. A use reads the exports it
|
|
5
5
|
* names and no other: a property access by name or by a literal index, or a destructured
|
|
6
6
|
* property, names one export, and any other use may read every export.
|
|
7
7
|
*/
|
|
@@ -9,22 +9,26 @@
|
|
|
9
9
|
import {
|
|
10
10
|
isArrowFunction,
|
|
11
11
|
isAwaitExpression,
|
|
12
|
-
isBindingElement,
|
|
13
12
|
isCallExpression,
|
|
13
|
+
isClassStaticBlockDeclaration,
|
|
14
14
|
isElementAccessExpression,
|
|
15
15
|
isExpressionStatement,
|
|
16
16
|
isFunctionExpression,
|
|
17
|
+
isFunctionLikeDeclaration,
|
|
17
18
|
isIdentifier,
|
|
18
19
|
isImportDeclaration,
|
|
20
|
+
isModuleBlock,
|
|
19
21
|
isNamespaceImport,
|
|
20
22
|
isNoSubstitutionTemplateLiteral,
|
|
21
23
|
isObjectBindingPattern,
|
|
22
24
|
isParenthesizedExpression,
|
|
23
25
|
isPropertyAccessExpression,
|
|
24
26
|
isQualifiedName,
|
|
27
|
+
isSourceFile,
|
|
25
28
|
isStringLiteral,
|
|
26
29
|
isVariableDeclaration,
|
|
27
30
|
isVariableDeclarationList,
|
|
31
|
+
isVariableStatement,
|
|
28
32
|
isVoidExpression,
|
|
29
33
|
NodeFlags,
|
|
30
34
|
SyntaxKind,
|
|
@@ -32,6 +36,7 @@ import {
|
|
|
32
36
|
type Node,
|
|
33
37
|
type SourceFile,
|
|
34
38
|
} from "@typescript/native/ast";
|
|
39
|
+
import { spellsNoBinding } from "./spelled-names.ts";
|
|
35
40
|
|
|
36
41
|
/** What one use of a namespace object reads beyond the names the checker resolves. */
|
|
37
42
|
type NamespaceUse =
|
|
@@ -62,23 +67,33 @@ function isLiteralImport(node: Node): boolean {
|
|
|
62
67
|
return argument !== undefined && isLiteralText(argument);
|
|
63
68
|
}
|
|
64
69
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
/**
|
|
71
|
+
* The node the binding one declaration list declares is visible below: for `let` and
|
|
72
|
+
* `const`, the loop whose head declares it, or else the block or file its statement is
|
|
73
|
+
* in; for `var`, the function, namespace body, static block or file that holds it.
|
|
74
|
+
*/
|
|
75
|
+
function scopeOf(list: Node): Node {
|
|
76
|
+
if ((list.flags & (NodeFlags.Let | NodeFlags.Const | NodeFlags.Using)) !== 0) {
|
|
77
|
+
return isVariableStatement(list.parent) ? list.parent.parent : list.parent;
|
|
78
|
+
}
|
|
79
|
+
let at = list.parent;
|
|
80
|
+
while (
|
|
81
|
+
!isSourceFile(at) &&
|
|
82
|
+
!isFunctionLikeDeclaration(at) &&
|
|
83
|
+
!isModuleBlock(at) &&
|
|
84
|
+
!isClassStaticBlockDeclaration(at)
|
|
85
|
+
) {
|
|
86
|
+
at = at.parent;
|
|
87
|
+
}
|
|
88
|
+
return at;
|
|
69
89
|
}
|
|
70
90
|
|
|
71
|
-
/** Every identifier below `scope` that spells `name
|
|
91
|
+
/** Every identifier below `scope` that spells `name` as a reference, other than `declared`. */
|
|
72
92
|
function occurrences(scope: Node, name: string, declared: Node): readonly Identifier[] {
|
|
73
93
|
const found: Identifier[] = [];
|
|
74
94
|
const visit = (node: Node): void => {
|
|
75
95
|
if (isIdentifier(node)) {
|
|
76
|
-
|
|
77
|
-
const member =
|
|
78
|
-
(isPropertyAccessExpression(parent) && parent.name === node) ||
|
|
79
|
-
(isQualifiedName(parent) && parent.right === node) ||
|
|
80
|
-
(isBindingElement(parent) && parent.propertyName === node);
|
|
81
|
-
if (node !== declared && node.text === name && !member) {
|
|
96
|
+
if (node !== declared && node.text === name && !spellsNoBinding(node)) {
|
|
82
97
|
found.push(node);
|
|
83
98
|
}
|
|
84
99
|
return;
|
|
@@ -92,8 +107,8 @@ function occurrences(scope: Node, name: string, declared: Node): readonly Identi
|
|
|
92
107
|
/**
|
|
93
108
|
* Every use of the namespace objects one file holds, keyed by the node the use is written
|
|
94
109
|
* at, that reads more than a name the checker resolves: an indexed read by a literal, and
|
|
95
|
-
* any use that may read every export. A shadowing declaration of a binding's
|
|
96
|
-
* its scope is read as the binding, which can only keep an export live.
|
|
110
|
+
* any use that may read every export. A use of a shadowing declaration of a binding's
|
|
111
|
+
* name inside its scope is read as a use of the binding, which can only keep an export live.
|
|
97
112
|
*/
|
|
98
113
|
export function namespaceUses(file: SourceFile): ReadonlyMap<Node, NamespaceUse> {
|
|
99
114
|
const uses = new Map<Node, NamespaceUse>();
|
|
@@ -142,13 +157,11 @@ export function namespaceUses(file: SourceFile): ReadonlyMap<Node, NamespaceUse>
|
|
|
142
157
|
}
|
|
143
158
|
return;
|
|
144
159
|
}
|
|
160
|
+
// A binding that is not `const` is followed too: each assignment to it is an
|
|
161
|
+
// occurrence `value` cannot narrow, so a reassigned binding still reads every export.
|
|
145
162
|
const list = parent.parent;
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
isVariableDeclarationList(list) &&
|
|
149
|
-
(list.flags & NodeFlags.Const) !== 0
|
|
150
|
-
) {
|
|
151
|
-
binding(pattern, scopeOf(parent), specifier, seen);
|
|
163
|
+
if (isIdentifier(pattern) && isVariableDeclarationList(list)) {
|
|
164
|
+
binding(pattern, scopeOf(list), specifier, seen);
|
|
152
165
|
return;
|
|
153
166
|
}
|
|
154
167
|
}
|
package/src/overrides.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The overrides of the target's class members. A call through the base class
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* through that class, so its own class references it.
|
|
2
|
+
* The overrides of the target's class members. A call through the base class runs the
|
|
3
|
+
* override of whichever subclass the value is, so the base member references every
|
|
4
|
+
* override. A member, static or not, overriding one of a class declared outside the target
|
|
5
|
+
* at any depth of its `extends` chain, a library's interface-and-variable class such as
|
|
6
|
+
* `Error` included, is used by outside code holding the value, so its own class references it.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
import {
|
|
@@ -26,6 +25,11 @@ import { nameComponent } from "./ref.ts";
|
|
|
26
25
|
import type { Reference } from "./references.ts";
|
|
27
26
|
import type { ProjectView } from "./session.ts";
|
|
28
27
|
|
|
28
|
+
const CLASS_DECLARATIONS: ReadonlySet<SyntaxKind> = new Set([
|
|
29
|
+
SyntaxKind.ClassDeclaration,
|
|
30
|
+
SyntaxKind.ClassExpression,
|
|
31
|
+
]);
|
|
32
|
+
|
|
29
33
|
/** The member kinds a subclass overrides. */
|
|
30
34
|
const OVERRIDABLE: ReadonlySet<string> = new Set(["method", "class-member"]);
|
|
31
35
|
|
|
@@ -110,9 +114,16 @@ export function overrideReferences<Brand>(
|
|
|
110
114
|
if (
|
|
111
115
|
inTarget.length === 0 &&
|
|
112
116
|
declared !== UNANSWERED &&
|
|
113
|
-
(declared.flags & SymbolFlags.Class) !== 0 &&
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
(((declared.flags & SymbolFlags.Class) !== 0 &&
|
|
118
|
+
// The target's augmentation of an outside class merges an interface into it,
|
|
119
|
+
// so only a class declaration says where the class is.
|
|
120
|
+
declared.declarations.some(
|
|
121
|
+
(handle) => CLASS_DECLARATIONS.has(handle.kind) && !own.has(handle.path),
|
|
122
|
+
)) ||
|
|
123
|
+
((declared.flags & SymbolFlags.Interface) !== 0 &&
|
|
124
|
+
declared.declarations.some(
|
|
125
|
+
(handle) => handle.kind === SyntaxKind.VariableDeclaration && !own.has(handle.path),
|
|
126
|
+
)))
|
|
116
127
|
) {
|
|
117
128
|
outsideBases.set(id, declared);
|
|
118
129
|
}
|
|
@@ -177,11 +188,13 @@ function outsideMemberNames<Brand>(
|
|
|
177
188
|
outsideBases: ReadonlyMap<string, TSSymbol>,
|
|
178
189
|
): ReadonlyMap<string, ReadonlySet<string>> {
|
|
179
190
|
const direct = new Map<string, ReadonlySet<string>>();
|
|
191
|
+
const error = errorType(project);
|
|
180
192
|
for (const [classId, base] of outsideBases) {
|
|
181
193
|
const instance = project.queries.declaredTypeOf(base);
|
|
182
194
|
const statics = project.queries.typeOfSymbol(base);
|
|
183
195
|
const components = [
|
|
184
196
|
...namesOf(project, instance === UNANSWERED ? undefined : instance).map(nameComponent),
|
|
197
|
+
...(instance !== UNANSWERED && reaches(project, instance, error) ? ["toString"] : []),
|
|
185
198
|
...namesOf(project, statics === UNANSWERED ? undefined : statics).map(
|
|
186
199
|
(name) => `${nameComponent(name)}:static`,
|
|
187
200
|
),
|
|
@@ -210,6 +223,40 @@ function outsideMemberNames<Brand>(
|
|
|
210
223
|
return names;
|
|
211
224
|
}
|
|
212
225
|
|
|
226
|
+
/** The global `Error` instance type's identifier, where the program's library declares one. */
|
|
227
|
+
function errorType<Brand>(project: ProjectView<Brand>): number | undefined {
|
|
228
|
+
const symbol = project.queries.resolveName("Error", SymbolFlags.Type, undefined, false);
|
|
229
|
+
if (symbol === UNANSWERED || symbol === undefined) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
const type = project.queries.declaredTypeOf(symbol);
|
|
233
|
+
return type === UNANSWERED ? undefined : type.id;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Whether one type is the `Error` instance type or extends it at any depth: the runtime
|
|
238
|
+
* prints an error through its `toString`, a member the library's `Error` interface omits.
|
|
239
|
+
*/
|
|
240
|
+
function reaches<Brand>(
|
|
241
|
+
project: ProjectView<Brand>,
|
|
242
|
+
type: Type,
|
|
243
|
+
target: number | undefined,
|
|
244
|
+
): boolean {
|
|
245
|
+
const seen = new Set<number>();
|
|
246
|
+
const pending = [type];
|
|
247
|
+
for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
|
|
248
|
+
if (next.id === target) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
if (!seen.has(next.id)) {
|
|
252
|
+
seen.add(next.id);
|
|
253
|
+
const bases = project.queries.baseTypes(next);
|
|
254
|
+
pending.push(...(bases === UNANSWERED ? [] : bases));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
213
260
|
/** The names of a type's properties; none where the type or its properties are unanswered. */
|
|
214
261
|
function namesOf<Brand>(project: ProjectView<Brand>, type: Type | undefined): readonly string[] {
|
|
215
262
|
const properties = type === undefined ? UNANSWERED : project.queries.propertiesOf(type);
|
package/src/paths.ts
CHANGED
|
@@ -67,6 +67,16 @@ export function dirnamePath(path: string): string {
|
|
|
67
67
|
return cut === 0 ? SEPARATOR : normalized.slice(0, cut);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* The extension of one path's last segment, from its last full stop, or the empty string
|
|
72
|
+
* where it has none. A segment that only starts with a full stop has none.
|
|
73
|
+
*/
|
|
74
|
+
export function extensionOf(path: string): string {
|
|
75
|
+
const name = path.slice(path.lastIndexOf(SEPARATOR) + 1);
|
|
76
|
+
const dot = name.lastIndexOf(".");
|
|
77
|
+
return dot <= 0 ? "" : name.slice(dot);
|
|
78
|
+
}
|
|
79
|
+
|
|
70
80
|
/** One path resolved against a directory, absolute where that directory is. */
|
|
71
81
|
export function resolvePath(against: string, path: string): string {
|
|
72
82
|
return isAbsolutePath(path) ? normalizePath(path) : joinPath(against, path);
|
package/src/print.ts
CHANGED
|
@@ -84,6 +84,7 @@ export function printConfig(config: Config, provenance: Provenance): string {
|
|
|
84
84
|
members: entry.members,
|
|
85
85
|
})),
|
|
86
86
|
serializers: config.ts.serializers.map(declarationOf),
|
|
87
|
+
roots_complete: config.ts.rootsComplete,
|
|
87
88
|
},
|
|
88
89
|
provenance: Object.fromEntries(
|
|
89
90
|
[...provenance]
|
package/src/ref.ts
CHANGED
|
@@ -49,9 +49,11 @@ export const REF_EXPRESSIONS: readonly string[] = [
|
|
|
49
49
|
`^${SCOPE}#$`,
|
|
50
50
|
`^${SCOPE}#${TS_HEAD}$`,
|
|
51
51
|
`^${SCOPE}#(?:${TS_QUOTED}\\.)?${TS_HEAD}:alias$`,
|
|
52
|
+
`^${SCOPE}#${TS_HEAD}:import$`,
|
|
52
53
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+$`,
|
|
53
54
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+:static$`,
|
|
54
55
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?<${TS_IDENT}>$`,
|
|
56
|
+
`^${SCOPE}#(?:${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?)?\\(${TS_IDENT}\\)$`,
|
|
55
57
|
`^${MANIFEST_SCOPE}#${TS_PACKAGE}:(?:dependency|dev-dependency|peer-dependency)$`,
|
|
56
58
|
];
|
|
57
59
|
|
|
@@ -151,6 +153,16 @@ export function computedComponent(text: string): string {
|
|
|
151
153
|
return `[${text.replaceAll(ASCII_WHITESPACE, "")}]`;
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
/** The reference of an import binding, read against the reference of the module that holds it. */
|
|
157
|
+
export function importRef(moduleRef: string, name: string): string {
|
|
158
|
+
return `${moduleRef}${nameComponent(name)}:import`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The reference of a parameter, read against the reference of the declaration that holds it. */
|
|
162
|
+
export function parameterRef(holderRef: string, name: string): string {
|
|
163
|
+
return `${holderRef}(${name})`;
|
|
164
|
+
}
|
|
165
|
+
|
|
154
166
|
/** The fragment of an export alias, inside the `declare module` block named, if one is. */
|
|
155
167
|
export function aliasFragment(name: string, block?: string): string {
|
|
156
168
|
const head = block === undefined ? "" : `${quoted(block)}.`;
|
package/src/references.ts
CHANGED
|
@@ -60,8 +60,15 @@ import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./pat
|
|
|
60
60
|
import { byPosition, isComponentFile, renderPosition, type Position } from "./position.ts";
|
|
61
61
|
import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./query.ts";
|
|
62
62
|
import type { ProjectView } from "./session.ts";
|
|
63
|
+
import { typeParameterReads } from "./type-parameter-reads.ts";
|
|
63
64
|
import { typeQueryAliases } from "./type-query-alias.ts";
|
|
64
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
computedKeyNames,
|
|
67
|
+
iterationReferences,
|
|
68
|
+
keyNameOf,
|
|
69
|
+
keyedUses,
|
|
70
|
+
type KeyedAccess,
|
|
71
|
+
} from "./symbol-keys.ts";
|
|
65
72
|
import { handedOn, valuesOf } from "./value-flow.ts";
|
|
66
73
|
|
|
67
74
|
/**
|
|
@@ -196,10 +203,10 @@ export interface ReferenceCost {
|
|
|
196
203
|
/** Per-type lookups for the contextual uses: each distinct type's constituents, properties and symbol. */
|
|
197
204
|
readonly contextualLookups: number;
|
|
198
205
|
/**
|
|
199
|
-
* The requests the passes
|
|
200
|
-
* accesses, key comparisons, instantiated signatures
|
|
201
|
-
* callees. They are
|
|
202
|
-
*
|
|
206
|
+
* The requests the passes after the walk make: the bases overrides are read against,
|
|
207
|
+
* element accesses, key comparisons, instantiated signatures, values handed to
|
|
208
|
+
* unimplemented callees and the modules a `declare module` names. They are counted
|
|
209
|
+
* where the caller measures the client, through `requestCount`, and are 0 otherwise.
|
|
203
210
|
*/
|
|
204
211
|
readonly typedLookups: number;
|
|
205
212
|
}
|
|
@@ -1060,19 +1067,28 @@ export function references<Brand>(
|
|
|
1060
1067
|
const fileIds = new Set(
|
|
1061
1068
|
held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id),
|
|
1062
1069
|
);
|
|
1063
|
-
let
|
|
1064
|
-
/**
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1070
|
+
let moduleBodies: ReadonlyMap<string, readonly InventorySymbol[]> | undefined;
|
|
1071
|
+
/**
|
|
1072
|
+
* The declarations one module holds directly: a file's top level, or the body of a
|
|
1073
|
+
* `declare module` whose name is a string. Any other declaration holds none here.
|
|
1074
|
+
*/
|
|
1075
|
+
const declaredIn = (moduleId: string): readonly InventorySymbol[] => {
|
|
1076
|
+
if (moduleBodies === undefined) {
|
|
1077
|
+
const byModule = new Map<string, InventorySymbol[]>();
|
|
1068
1078
|
for (const symbol of held.symbols) {
|
|
1069
|
-
if (fileIds.has(symbol.parent)) {
|
|
1070
|
-
|
|
1079
|
+
if (!fileIds.has(symbol.parent) && !held.modules.has(symbol.parent)) {
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
const siblings = byModule.get(symbol.parent);
|
|
1083
|
+
if (siblings === undefined) {
|
|
1084
|
+
byModule.set(symbol.parent, [symbol]);
|
|
1085
|
+
} else {
|
|
1086
|
+
siblings.push(symbol);
|
|
1071
1087
|
}
|
|
1072
1088
|
}
|
|
1073
|
-
|
|
1089
|
+
moduleBodies = byModule;
|
|
1074
1090
|
}
|
|
1075
|
-
return
|
|
1091
|
+
return moduleBodies.get(moduleId) ?? [];
|
|
1076
1092
|
};
|
|
1077
1093
|
|
|
1078
1094
|
const exportsCache = new Map<number, readonly ChainTarget[]>();
|
|
@@ -1431,9 +1447,12 @@ export function references<Brand>(
|
|
|
1431
1447
|
|
|
1432
1448
|
for (const url of urls) {
|
|
1433
1449
|
const constructor = url.constructor === undefined ? undefined : answered.get(url.constructor);
|
|
1450
|
+
// An augmentation of the global `URL` merges into it, which keeps the
|
|
1451
|
+
// library's declaration; a `URL` the target declares has no other.
|
|
1434
1452
|
if (
|
|
1435
1453
|
typeof constructor === "object" &&
|
|
1436
|
-
constructor.declarations.
|
|
1454
|
+
constructor.declarations.length > 0 &&
|
|
1455
|
+
constructor.declarations.every((handle) => ownPaths.has(handle.path))
|
|
1437
1456
|
) {
|
|
1438
1457
|
continue;
|
|
1439
1458
|
}
|
|
@@ -1604,14 +1623,16 @@ export function references<Brand>(
|
|
|
1604
1623
|
|
|
1605
1624
|
let typedLookups = 0;
|
|
1606
1625
|
if (consumer === undefined) {
|
|
1607
|
-
found.push(...overrideReferences(project, held, chains, new Set(tests)));
|
|
1608
1626
|
const before = options.requestCount?.() ?? 0;
|
|
1627
|
+
found.push(...overrideReferences(project, held, chains, new Set(tests)));
|
|
1609
1628
|
found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
|
|
1610
1629
|
found.push(...elementAccessReferences(project, held, chains, root, new Set(tests)));
|
|
1611
1630
|
found.push(...keyofComparisonReferences(project, held, chains, root, new Set(tests)));
|
|
1631
|
+
found.push(...iterationReferences(project, held, chains, root, new Set(tests)));
|
|
1632
|
+
found.push(...typeParameterReads(project, held, chains, root, new Set(tests)));
|
|
1612
1633
|
found.push(...unimplementedCalleeReads(project, held, chains, root, new Set(tests)));
|
|
1613
|
-
typedLookups = (options.requestCount?.() ?? 0) - before;
|
|
1614
1634
|
found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
|
|
1635
|
+
typedLookups = (options.requestCount?.() ?? 0) - before;
|
|
1615
1636
|
}
|
|
1616
1637
|
const scripts = new Set(
|
|
1617
1638
|
walked.filter((one) => one.file.externalModuleIndicator === undefined).map((one) => one.fileId),
|