@cplieger/deadset-ts 8.0.0-dev.16 → 8.0.0-dev.18
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 +82 -91
- package/dist/src/augmentations.js +24 -25
- package/dist/src/bindings.js +382 -0
- package/dist/src/calls.js +279 -43
- 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/contract.generated.js +2 -2
- 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/exempt.js +0 -26
- package/dist/src/exposure.js +400 -0
- package/dist/src/facts.js +4 -4
- package/dist/src/findings/completion.js +44 -21
- package/dist/src/findings/emitters.js +22 -33
- 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/intra-function.js +3 -3
- package/dist/src/findings/reads-and-writes.js +71 -15
- package/dist/src/findings/unused-declarations.js +2 -22
- package/dist/src/findings/visibility-narrowing.js +14 -20
- package/dist/src/framework-lifecycle.js +41 -14
- 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/inventory.js +18 -0
- package/dist/src/keyof-comparisons.js +124 -103
- package/dist/src/{components.js → liveness/components.js} +1 -1
- package/dist/src/{graph.js → liveness/graph.js} +45 -20
- package/dist/src/liveness/matrix.js +99 -0
- package/dist/src/liveness/sweep.js +558 -0
- 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/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/query.js +46 -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 +237 -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/published-reach.js +4 -4
- 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/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 +75 -132
- package/dist/src/verbs/print-retained.js +3 -3
- 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 +125 -128
- package/src/augmentations.ts +42 -28
- package/src/bindings.ts +460 -0
- package/src/calls.ts +338 -46
- 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/contract.generated.ts +2 -2
- 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/exempt.ts +31 -46
- package/src/exposure.ts +492 -0
- package/src/facts.ts +5 -3
- package/src/finding.ts +1 -1
- package/src/findings/boundary.ts +9 -0
- package/src/findings/completion.ts +48 -23
- package/src/findings/emitter.ts +1 -1
- package/src/findings/emitters.ts +23 -41
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
- package/src/findings/interfaces.ts +3 -3
- package/src/findings/intra-function.ts +3 -3
- package/src/findings/reads-and-writes.ts +99 -26
- package/src/findings/unused-declarations.ts +4 -28
- package/src/findings/visibility-narrowing.ts +25 -26
- package/src/framework-lifecycle.ts +42 -14
- 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 +2 -2
- package/src/inventory.ts +19 -0
- package/src/keyof-comparisons.ts +131 -116
- package/src/{components.ts → liveness/components.ts} +3 -3
- package/src/{graph.ts → liveness/graph.ts} +93 -27
- package/src/liveness/matrix.ts +162 -0
- package/src/liveness/sweep.ts +855 -0
- 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/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/query.ts +81 -0
- package/src/references/read-rule.ts +60 -0
- package/src/references/read-rules.ts +55 -0
- package/src/references/sink.ts +314 -0
- package/src/references/sources.ts +632 -0
- package/src/references.ts +175 -1733
- package/src/report.ts +1 -1
- package/src/roots/configured-declarations.ts +3 -0
- package/src/roots/published-reach.ts +9 -4
- 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 +15 -3
- package/src/symbol-keys.ts +102 -102
- package/src/tests/file-class.ts +135 -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 +83 -158
- package/src/verbs/print-retained.ts +3 -3
- package/dist/src/matrix.js +0 -273
- package/dist/src/sweep.js +0 -222
- package/src/matrix.ts +0 -463
- package/src/sweep.ts +0 -361
|
@@ -8,7 +8,13 @@ import { packageOfSpecifier } from "../dependencies.ts";
|
|
|
8
8
|
import { MANIFEST } from "../fs/manifest.ts";
|
|
9
9
|
import { entriesOf } from "../fs/tree.ts";
|
|
10
10
|
import type { Host } from "../host.ts";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
cannotStartPackageName,
|
|
13
|
+
dirnamePath,
|
|
14
|
+
joinPath,
|
|
15
|
+
relativePath,
|
|
16
|
+
resolvePath,
|
|
17
|
+
} from "../paths.ts";
|
|
12
18
|
import { dependencySpecifiers, installedPackage, manifestsAbove } from "./module-lookup.ts";
|
|
13
19
|
import type { CompilerOptions } from "./plan.ts";
|
|
14
20
|
|
|
@@ -119,7 +125,7 @@ function expectedModule(
|
|
|
119
125
|
): string | undefined {
|
|
120
126
|
const { host, targetRoot } = context;
|
|
121
127
|
const fix = "run the generator or the build that writes it";
|
|
122
|
-
if (
|
|
128
|
+
if (cannotStartPackageName(specifier)) {
|
|
123
129
|
return provided(host, resolvePath(dirnamePath(importer), specifier), false)
|
|
124
130
|
? undefined
|
|
125
131
|
: `which names no file of the target: ${fix}`;
|
package/src/load/specifiers.ts
CHANGED
|
@@ -23,14 +23,17 @@ import type { Failure } from "../failure.ts";
|
|
|
23
23
|
import { readManifestAt } from "../fs/manifest.ts";
|
|
24
24
|
import type { Host } from "../host.ts";
|
|
25
25
|
import { JAVASCRIPT_FILE } from "../javascript-file.ts";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
ancestors,
|
|
28
|
+
dirnamePath,
|
|
29
|
+
isRelativeSpecifier,
|
|
30
|
+
relativePath,
|
|
31
|
+
resolvePath,
|
|
32
|
+
} from "../paths.ts";
|
|
27
33
|
import { renderPosition, type Position } from "../position.ts";
|
|
28
34
|
import { batched, type Engine, type ProjectView } from "../session.ts";
|
|
29
35
|
import { dependencySpecifiers } from "./module-lookup.ts";
|
|
30
36
|
|
|
31
|
-
/** A relative specifier: `.`, `..`, or one starting `./` or `../`. */
|
|
32
|
-
const RELATIVE = /^\.\.?(?:\/|$)/u;
|
|
33
|
-
|
|
34
37
|
/** One relative specifier of a JavaScript file that resolves to no file. */
|
|
35
38
|
export interface UnresolvedSpecifier {
|
|
36
39
|
readonly specifier: string;
|
|
@@ -141,7 +144,7 @@ export function readSpecifiers<Brand>(
|
|
|
141
144
|
const resolved = (answers[at]?.resolvedFileName ?? "") !== "";
|
|
142
145
|
const specifier = usage.node.text;
|
|
143
146
|
const name = packageOfSpecifier(specifier);
|
|
144
|
-
if (
|
|
147
|
+
if (isRelativeSpecifier(specifier)) {
|
|
145
148
|
const named = resolvePath(dirnamePath(usage.file.fileName), specifier);
|
|
146
149
|
if (!resolved && !outputs.some((dir) => relativePath(dir, named) !== undefined)) {
|
|
147
150
|
unresolved.push({
|
package/src/load/synthesize.ts
CHANGED
|
@@ -132,6 +132,12 @@ const RUNTIME_OPTIONS = {
|
|
|
132
132
|
node: { module: "nodenext", moduleResolution: "nodenext" },
|
|
133
133
|
} as const satisfies Record<Runtime, Readonly<Record<string, string>>>;
|
|
134
134
|
|
|
135
|
+
/** Whether one configuration is a program the run formed to resolve its modules as Node does. */
|
|
136
|
+
export function formedForNode(configuration: Configuration): boolean {
|
|
137
|
+
const options = configuration.written?.["compilerOptions"];
|
|
138
|
+
return isRecord(options) && options["moduleResolution"] === RUNTIME_OPTIONS.node.moduleResolution;
|
|
139
|
+
}
|
|
140
|
+
|
|
135
141
|
/** The manifest members that say a package is written for Node. */
|
|
136
142
|
const NODE_MEMBERS = ["main", "exports", "bin", "type"] as const;
|
|
137
143
|
|
|
@@ -10,7 +10,14 @@ import { emittedFrom } from "./emit-map.ts";
|
|
|
10
10
|
import { packageOfSpecifier } from "../dependencies.ts";
|
|
11
11
|
import { gitIgnored } from "../git-ignore.ts";
|
|
12
12
|
import type { Host } from "../host.ts";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ancestors,
|
|
15
|
+
cannotStartPackageName,
|
|
16
|
+
dirnamePath,
|
|
17
|
+
joinPath,
|
|
18
|
+
relativePath,
|
|
19
|
+
resolvePath,
|
|
20
|
+
} from "../paths.ts";
|
|
14
21
|
import { satisfies } from "../version-range.ts";
|
|
15
22
|
import type { Workspace, WorkspaceMember } from "./workspace.ts";
|
|
16
23
|
import { isRecord } from "../json-document.ts";
|
|
@@ -140,7 +147,7 @@ function dependsOn(specifier: string, member: WorkspaceMember, fromDir: string):
|
|
|
140
147
|
member.version !== undefined && satisfies(member.version, range);
|
|
141
148
|
if (specifier.startsWith("workspace:")) {
|
|
142
149
|
const rest = specifier.slice("workspace:".length);
|
|
143
|
-
if (
|
|
150
|
+
if (cannotStartPackageName(rest)) {
|
|
144
151
|
return atPath(rest);
|
|
145
152
|
}
|
|
146
153
|
const alias = /^((?:@[^/@]+\/)?[^/@]+)@(.*)$/u.exec(rest);
|
|
@@ -15,20 +15,18 @@ import {
|
|
|
15
15
|
isImportTypeNode,
|
|
16
16
|
isLiteralTypeNode,
|
|
17
17
|
isModuleDeclaration,
|
|
18
|
-
isNamespaceExport,
|
|
19
|
-
isNamespaceImport,
|
|
20
18
|
isNoSubstitutionTemplateLiteral,
|
|
21
19
|
isStringLiteral,
|
|
22
20
|
SyntaxKind,
|
|
23
21
|
type Node,
|
|
24
22
|
type SourceFile,
|
|
25
23
|
} from "@typescript/native/ast";
|
|
24
|
+
import { namesImported } from "./import-bindings.ts";
|
|
26
25
|
import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
27
|
-
import { renderPosition } from "./position.ts";
|
|
28
26
|
import { UNANSWERED } from "./query.ts";
|
|
29
27
|
import { aliasFragment, nameComponent } from "./ref.ts";
|
|
30
|
-
import type {
|
|
31
|
-
import
|
|
28
|
+
import type { Read, ReadRule } from "./references/read-rule.ts";
|
|
29
|
+
import { isPathSpecifier } from "./paths.ts";
|
|
32
30
|
|
|
33
31
|
/** Whether a node is a specifier's text: a string literal or a template with no substitution. */
|
|
34
32
|
function literalText(node: Node | undefined): node is Node & { readonly text: string } {
|
|
@@ -58,33 +56,12 @@ function specifierOf(node: Node): (Node & { readonly text: string }) | undefined
|
|
|
58
56
|
return undefined;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
/**
|
|
62
|
-
* The export names an import or a re-export reads from the module it names: none for a
|
|
63
|
-
* namespace, a star or a bare form.
|
|
64
|
-
*/
|
|
65
|
-
export function namesImported(node: Node): readonly string[] {
|
|
66
|
-
if (isImportDeclaration(node)) {
|
|
67
|
-
const clause = node.importClause;
|
|
68
|
-
const bindings = clause?.namedBindings;
|
|
69
|
-
return [
|
|
70
|
-
...(clause?.name === undefined ? [] : ["default"]),
|
|
71
|
-
...(bindings === undefined || isNamespaceImport(bindings)
|
|
72
|
-
? []
|
|
73
|
-
: bindings.elements.map((one) => (one.propertyName ?? one.name).text)),
|
|
74
|
-
];
|
|
75
|
-
}
|
|
76
|
-
const clause = isExportDeclaration(node) ? node.exportClause : undefined;
|
|
77
|
-
return clause === undefined || isNamespaceExport(clause)
|
|
78
|
-
? []
|
|
79
|
-
: clause.elements.map((one) => (one.propertyName ?? one.name).text);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
59
|
/**
|
|
83
60
|
* Whether a specifier matches a declaration's name, the name's one `*` standing for any run.
|
|
84
61
|
* A relative name is resolved against its own file, so no specifier names it by its text.
|
|
85
62
|
*/
|
|
86
63
|
function namesModule(specifier: string, name: string): boolean {
|
|
87
|
-
if (
|
|
64
|
+
if (isPathSpecifier(name)) {
|
|
88
65
|
return false;
|
|
89
66
|
}
|
|
90
67
|
const star = name.indexOf("*");
|
|
@@ -161,76 +138,75 @@ interface Declared {
|
|
|
161
138
|
}
|
|
162
139
|
|
|
163
140
|
/**
|
|
164
|
-
* The
|
|
165
|
-
*
|
|
141
|
+
* The reads the imports of a side's files make of the module declarations of the target they
|
|
142
|
+
* name, each at the import's specifier. A specifier the checker leaves unanswered still names
|
|
143
|
+
* a declaration by its name or pattern.
|
|
166
144
|
*/
|
|
167
|
-
export
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
for (const file of project.ownSourceFiles()) {
|
|
176
|
-
const fileId = held.declarations.get(nodeKey(file, file));
|
|
177
|
-
if (fileId === undefined) {
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
const visit = (node: Node, from: string): void => {
|
|
181
|
-
const own = held.declarations.get(nodeKey(file, node));
|
|
182
|
-
if (isModuleDeclaration(node) && literalText(node.name) && own !== undefined) {
|
|
183
|
-
declared.push({ id: own, name: node.name });
|
|
184
|
-
}
|
|
185
|
-
const specifier = specifierOf(node);
|
|
186
|
-
if (specifier !== undefined) {
|
|
187
|
-
imports.push({ file, from, specifier, names: namesImported(node) });
|
|
188
|
-
}
|
|
189
|
-
node.forEachChild((child) => {
|
|
190
|
-
visit(child, own ?? from);
|
|
191
|
-
});
|
|
192
|
-
};
|
|
193
|
-
file.forEachChild((child) => {
|
|
194
|
-
visit(child, fileId);
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
if (declared.length === 0 || imports.length === 0) {
|
|
198
|
-
return [];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const asked = [...declared.map((one) => one.name), ...imports.map((one) => one.specifier)];
|
|
202
|
-
const answers = project.symbolsAt(asked.map((node) => project.handle(node)));
|
|
203
|
-
const moduleOf = (at: number): number | undefined => {
|
|
204
|
-
const symbol = answers[at];
|
|
205
|
-
return symbol === undefined || symbol === UNANSWERED ? undefined : symbol.id;
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const named = namedBy(held);
|
|
209
|
-
const found: Reference[] = [];
|
|
210
|
-
imports.forEach((one, index) => {
|
|
211
|
-
const resolved = moduleOf(declared.length + index);
|
|
212
|
-
const position = renderPosition(one.file, root, one.specifier.getStart());
|
|
213
|
-
const read = (to: string, resolution: Reference["resolution"], test: boolean): void => {
|
|
214
|
-
found.push({ from: one.from, to, position, use: "read", resolution, test });
|
|
215
|
-
};
|
|
216
|
-
const test = testFiles.has(position.path);
|
|
217
|
-
const same = new Set(
|
|
218
|
-
declared
|
|
219
|
-
.filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
|
|
220
|
-
.map((declaration) => declaration.id),
|
|
221
|
-
);
|
|
222
|
-
for (const id of same) {
|
|
223
|
-
read(id, "batch", test);
|
|
224
|
-
}
|
|
225
|
-
for (const declaration of named(one.specifier.text, one.names)) {
|
|
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)) {
|
|
229
|
-
for (const to of [declaration.id, ...declaration.members]) {
|
|
230
|
-
read(to, "syntax", test && !declaration.wildcard);
|
|
145
|
+
export const moduleDeclarations: ReadRule = {
|
|
146
|
+
start() {
|
|
147
|
+
const imports: Import[] = [];
|
|
148
|
+
return {
|
|
149
|
+
visit(node, { file, from }) {
|
|
150
|
+
const specifier = specifierOf(node);
|
|
151
|
+
if (specifier !== undefined) {
|
|
152
|
+
imports.push({ file, from, specifier, names: namesImported(node) });
|
|
231
153
|
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
154
|
+
},
|
|
155
|
+
reads({ project, held }) {
|
|
156
|
+
const found: Read[] = [];
|
|
157
|
+
// A module declaration whose name is a string is written at a file's top level.
|
|
158
|
+
const declared: Declared[] = project.ownSourceFiles().flatMap((file) =>
|
|
159
|
+
file.statements.flatMap((statement) => {
|
|
160
|
+
const id = held.declarations.get(nodeKey(file, statement));
|
|
161
|
+
return isModuleDeclaration(statement) && literalText(statement.name) && id !== undefined
|
|
162
|
+
? [{ id, name: statement.name }]
|
|
163
|
+
: [];
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
if (declared.length === 0 || imports.length === 0) {
|
|
167
|
+
return found;
|
|
168
|
+
}
|
|
169
|
+
const asked = [...declared.map((one) => one.name), ...imports.map((one) => one.specifier)];
|
|
170
|
+
const answers = project.symbolsAt(asked.map((node) => project.handle(node)));
|
|
171
|
+
const moduleOf = (at: number): number | undefined => {
|
|
172
|
+
const symbol = answers[at];
|
|
173
|
+
return symbol === undefined || symbol === UNANSWERED ? undefined : symbol.id;
|
|
174
|
+
};
|
|
175
|
+
const named = namedBy(held);
|
|
176
|
+
imports.forEach((one, index) => {
|
|
177
|
+
const resolved = moduleOf(declared.length + index);
|
|
178
|
+
const read = (to: string, via: "batch" | "syntax", production: boolean): void => {
|
|
179
|
+
found.push({
|
|
180
|
+
kind: "named",
|
|
181
|
+
from: one.from,
|
|
182
|
+
to,
|
|
183
|
+
file: one.file,
|
|
184
|
+
at: one.specifier,
|
|
185
|
+
use: "read",
|
|
186
|
+
via,
|
|
187
|
+
...(production ? { production: true as const } : {}),
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
const same = new Set(
|
|
191
|
+
declared
|
|
192
|
+
.filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
|
|
193
|
+
.map((declaration) => declaration.id),
|
|
194
|
+
);
|
|
195
|
+
for (const id of same) {
|
|
196
|
+
read(id, "batch", false);
|
|
197
|
+
}
|
|
198
|
+
for (const declaration of named(one.specifier.text, one.names)) {
|
|
199
|
+
// A wildcard declaration is how the compiler types the file a matching specifier
|
|
200
|
+
// names, so the import uses it whoever writes it, a test included.
|
|
201
|
+
if (declaration.wildcard || !same.has(declaration.id)) {
|
|
202
|
+
for (const to of [declaration.id, ...declaration.members]) {
|
|
203
|
+
read(to, "syntax", declaration.wildcard);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
return found;
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
};
|
package/src/overrides.ts
CHANGED
|
@@ -15,14 +15,14 @@ import {
|
|
|
15
15
|
SyntaxKind,
|
|
16
16
|
type HeritageClauseElement,
|
|
17
17
|
type Node,
|
|
18
|
+
type SourceFile,
|
|
18
19
|
} from "@typescript/native/ast";
|
|
19
|
-
import type { AliasChains } from "./alias-chain.ts";
|
|
20
20
|
import { memberComponent } from "./class-members.ts";
|
|
21
|
-
import { nodeKey, type
|
|
21
|
+
import { nodeKey, type InventorySymbol } from "./inventory.ts";
|
|
22
22
|
import { SymbolFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/sync";
|
|
23
23
|
import { UNANSWERED } from "./query.ts";
|
|
24
24
|
import { nameComponent } from "./ref.ts";
|
|
25
|
-
import type {
|
|
25
|
+
import type { Read, ReadRule } from "./references/read-rule.ts";
|
|
26
26
|
import type { ProjectView } from "./session.ts";
|
|
27
27
|
|
|
28
28
|
const CLASS_DECLARATIONS: ReadonlySet<SyntaxKind> = new Set([
|
|
@@ -41,141 +41,157 @@ function nameOf(entry: HeritageClauseElement): Node {
|
|
|
41
41
|
return isQualifiedName(entry.typeName) ? entry.typeName.right : entry.typeName;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const found: { id: string; base: Node }[] = [];
|
|
50
|
-
for (const file of project.ownSourceFiles()) {
|
|
51
|
-
const visit = (node: Node): void => {
|
|
52
|
-
if (isClassDeclaration(node) || isClassExpression(node)) {
|
|
53
|
-
const id = held.declarations.get(nodeKey(file, node));
|
|
54
|
-
const clause = node.heritageClauses?.find((one) => one.token === SyntaxKind.ExtendsKeyword);
|
|
55
|
-
const base = clause?.types[0];
|
|
56
|
-
if (id !== undefined && base !== undefined) {
|
|
57
|
-
found.push({ id, base: nameOf(base) });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
node.forEachChild(visit);
|
|
61
|
-
};
|
|
62
|
-
file.forEachChild(visit);
|
|
63
|
-
}
|
|
64
|
-
return found;
|
|
44
|
+
/** One class whose `extends` clause names a class, with the name node and its file. */
|
|
45
|
+
interface Extending {
|
|
46
|
+
readonly node: Node;
|
|
47
|
+
readonly file: SourceFile;
|
|
48
|
+
readonly base: Node;
|
|
65
49
|
}
|
|
66
50
|
|
|
67
51
|
/**
|
|
68
|
-
* The
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
52
|
+
* The reads each base class member makes of its overrides, for the classes of the target that
|
|
53
|
+
* extend another class of the target, and each class makes of its members that override a
|
|
54
|
+
* member of a class declared outside the target. A member a nearer base already overrides is
|
|
55
|
+
* reached through that override. A private member is not overridden. Each read is placed at
|
|
56
|
+
* the override. A class whose base the checker leaves unanswered holds every member it could
|
|
57
|
+
* override.
|
|
73
58
|
*/
|
|
74
|
-
export
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
const own = members.get(symbol.parent) ?? new Map<string, InventorySymbol>();
|
|
97
|
-
own.set(component, symbol);
|
|
98
|
-
members.set(symbol.parent, own);
|
|
99
|
-
}
|
|
100
|
-
const bases = new Map<string, readonly string[]>();
|
|
101
|
-
const outsideBases = new Map<string, TSSymbol>();
|
|
102
|
-
const own = project.ownPaths();
|
|
103
|
-
const symbols = project.symbolsAt(classes.map((one) => project.handle(one.base)));
|
|
104
|
-
classes.forEach(({ id }, index) => {
|
|
105
|
-
const symbol = symbols[index];
|
|
106
|
-
if (symbol !== undefined && symbol !== UNANSWERED) {
|
|
107
|
-
const inTarget = chains
|
|
108
|
-
.chainOf(symbol)
|
|
109
|
-
.map((target) => target.id)
|
|
110
|
-
.filter((target) => byId.get(target)?.kind === "class" && target !== id);
|
|
111
|
-
bases.set(id, inTarget);
|
|
112
|
-
const declared =
|
|
113
|
-
(symbol.flags & SymbolFlags.Alias) === 0 ? symbol : project.queries.aliased(symbol);
|
|
114
|
-
if (
|
|
115
|
-
inTarget.length === 0 &&
|
|
116
|
-
declared !== UNANSWERED &&
|
|
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
|
-
)))
|
|
127
|
-
) {
|
|
128
|
-
outsideBases.set(id, declared);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
/** The nearest member of one component the class or a class it extends declares. */
|
|
134
|
-
const declaredAbove = (classId: string, component: string, seen: Set<string>): string[] => {
|
|
135
|
-
if (seen.has(classId)) {
|
|
136
|
-
return [];
|
|
137
|
-
}
|
|
138
|
-
seen.add(classId);
|
|
139
|
-
const own = members.get(classId)?.get(component);
|
|
140
|
-
if (own !== undefined) {
|
|
141
|
-
return [own.id];
|
|
142
|
-
}
|
|
143
|
-
return (bases.get(classId) ?? []).flatMap((base) => declaredAbove(base, component, seen));
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const found: Reference[] = [];
|
|
147
|
-
const outsideNames = outsideMemberNames(project, bases, outsideBases);
|
|
148
|
-
for (const [classId, names] of outsideNames) {
|
|
149
|
-
for (const [component, member] of members.get(classId) ?? []) {
|
|
150
|
-
if (names.has(component)) {
|
|
151
|
-
found.push({
|
|
152
|
-
from: classId,
|
|
153
|
-
to: member.id,
|
|
154
|
-
position: member.position,
|
|
155
|
-
use: "read",
|
|
156
|
-
resolution: "override",
|
|
157
|
-
test: testFiles.has(member.position.path),
|
|
59
|
+
export const overrides: ReadRule = {
|
|
60
|
+
start() {
|
|
61
|
+
const extending: Extending[] = [];
|
|
62
|
+
return {
|
|
63
|
+
visit(node, { file }) {
|
|
64
|
+
if (isClassDeclaration(node) || isClassExpression(node)) {
|
|
65
|
+
const clause = node.heritageClauses?.find(
|
|
66
|
+
(one) => one.token === SyntaxKind.ExtendsKeyword,
|
|
67
|
+
);
|
|
68
|
+
const base = clause?.types[0];
|
|
69
|
+
if (base !== undefined) {
|
|
70
|
+
extending.push({ node, file, base: nameOf(base) });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
reads({ project, held, chains }) {
|
|
75
|
+
const found: Read[] = [];
|
|
76
|
+
const classes = extending.flatMap((one) => {
|
|
77
|
+
const id = held.declarations.get(nodeKey(one.file, one.node));
|
|
78
|
+
return id === undefined ? [] : [{ ...one, id }];
|
|
158
79
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
80
|
+
if (classes.length === 0) {
|
|
81
|
+
return found;
|
|
82
|
+
}
|
|
83
|
+
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
84
|
+
const members = new Map<string, Map<string, InventorySymbol>>();
|
|
85
|
+
for (const symbol of held.symbols) {
|
|
86
|
+
const component = memberComponent(symbol, byId);
|
|
87
|
+
if (
|
|
88
|
+
component === undefined ||
|
|
89
|
+
!OVERRIDABLE.has(symbol.kind) ||
|
|
90
|
+
symbol.visibility === "private" ||
|
|
91
|
+
symbol.visibility === "private-name"
|
|
92
|
+
) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const own = members.get(symbol.parent) ?? new Map<string, InventorySymbol>();
|
|
96
|
+
own.set(component, symbol);
|
|
97
|
+
members.set(symbol.parent, own);
|
|
98
|
+
}
|
|
99
|
+
const bases = new Map<string, readonly string[]>();
|
|
100
|
+
const outsideBases = new Map<string, TSSymbol>();
|
|
101
|
+
const fileOf = new Map(classes.map((one) => [one.id, one.file]));
|
|
102
|
+
const own = project.ownPaths();
|
|
103
|
+
const unanswered = new Set<string>();
|
|
104
|
+
const symbols = project.symbolsAt(classes.map((one) => project.handle(one.base)));
|
|
105
|
+
classes.forEach(({ id }, index) => {
|
|
106
|
+
const symbol = symbols[index];
|
|
107
|
+
if (symbol === UNANSWERED) {
|
|
108
|
+
unanswered.add(id);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (symbol === undefined) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const inTarget = chains
|
|
115
|
+
.chainOf(symbol)
|
|
116
|
+
.map((target) => target.id)
|
|
117
|
+
.filter((target) => byId.get(target)?.kind === "class" && target !== id);
|
|
118
|
+
bases.set(id, inTarget);
|
|
119
|
+
const declared =
|
|
120
|
+
(symbol.flags & SymbolFlags.Alias) === 0 ? symbol : project.queries.aliased(symbol);
|
|
121
|
+
if (declared === UNANSWERED) {
|
|
122
|
+
unanswered.add(id);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (
|
|
126
|
+
inTarget.length === 0 &&
|
|
127
|
+
(((declared.flags & SymbolFlags.Class) !== 0 &&
|
|
128
|
+
// The target's augmentation of an outside class merges an interface into it,
|
|
129
|
+
// so only a class declaration says where the class is.
|
|
130
|
+
declared.declarations.some(
|
|
131
|
+
(handle) => CLASS_DECLARATIONS.has(handle.kind) && !own.has(handle.path),
|
|
132
|
+
)) ||
|
|
133
|
+
((declared.flags & SymbolFlags.Interface) !== 0 &&
|
|
134
|
+
declared.declarations.some(
|
|
135
|
+
(handle) =>
|
|
136
|
+
handle.kind === SyntaxKind.VariableDeclaration && !own.has(handle.path),
|
|
137
|
+
)))
|
|
138
|
+
) {
|
|
139
|
+
outsideBases.set(id, declared);
|
|
140
|
+
}
|
|
173
141
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
142
|
+
|
|
143
|
+
/** The nearest member of one component the class or a class it extends declares. */
|
|
144
|
+
const declaredAbove = (classId: string, component: string, seen: Set<string>): string[] => {
|
|
145
|
+
if (seen.has(classId)) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
seen.add(classId);
|
|
149
|
+
const member = members.get(classId)?.get(component);
|
|
150
|
+
if (member !== undefined) {
|
|
151
|
+
return [member.id];
|
|
152
|
+
}
|
|
153
|
+
return (bases.get(classId) ?? []).flatMap((base) => declaredAbove(base, component, seen));
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const read = (classId: string, from: string, member: InventorySymbol): void => {
|
|
157
|
+
const file = fileOf.get(classId);
|
|
158
|
+
if (file !== undefined) {
|
|
159
|
+
found.push({
|
|
160
|
+
kind: "named",
|
|
161
|
+
from,
|
|
162
|
+
to: member.id,
|
|
163
|
+
file,
|
|
164
|
+
at: member.position,
|
|
165
|
+
use: "read",
|
|
166
|
+
via: "override",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const outside = outsideMemberNames(project, bases, outsideBases);
|
|
171
|
+
for (const classId of [...unanswered, ...outside.unanswered]) {
|
|
172
|
+
const ids = [...(members.get(classId)?.values() ?? [])].map((member) => member.id);
|
|
173
|
+
found.push({ kind: "held", from: classId, ids });
|
|
174
|
+
}
|
|
175
|
+
for (const [classId, names] of outside.names) {
|
|
176
|
+
for (const [component, member] of members.get(classId) ?? []) {
|
|
177
|
+
if (names.has(component)) {
|
|
178
|
+
read(classId, classId, member);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const [classId, baseIds] of bases) {
|
|
183
|
+
for (const [component, member] of members.get(classId) ?? []) {
|
|
184
|
+
const seen = new Set([classId]);
|
|
185
|
+
for (const base of baseIds.flatMap((one) => declaredAbove(one, component, seen))) {
|
|
186
|
+
read(classId, base, member);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return found;
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
};
|
|
179
195
|
|
|
180
196
|
/**
|
|
181
197
|
* Per class of the target with a class declared outside the target in its `extends`
|
|
@@ -186,12 +202,24 @@ function outsideMemberNames<Brand>(
|
|
|
186
202
|
project: ProjectView<Brand>,
|
|
187
203
|
bases: ReadonlyMap<string, readonly string[]>,
|
|
188
204
|
outsideBases: ReadonlyMap<string, TSSymbol>,
|
|
189
|
-
):
|
|
205
|
+
): {
|
|
206
|
+
readonly names: ReadonlyMap<string, ReadonlySet<string>>;
|
|
207
|
+
readonly unanswered: readonly string[];
|
|
208
|
+
} {
|
|
190
209
|
const direct = new Map<string, ReadonlySet<string>>();
|
|
210
|
+
const unanswered: string[] = [];
|
|
191
211
|
const error = errorType(project);
|
|
192
212
|
for (const [classId, base] of outsideBases) {
|
|
193
213
|
const instance = project.queries.declaredTypeOf(base);
|
|
194
214
|
const statics = project.queries.typeOfSymbol(base);
|
|
215
|
+
if (
|
|
216
|
+
instance === UNANSWERED ||
|
|
217
|
+
statics === UNANSWERED ||
|
|
218
|
+
project.queries.propertiesOf(instance) === UNANSWERED ||
|
|
219
|
+
(statics !== undefined && project.queries.propertiesOf(statics) === UNANSWERED)
|
|
220
|
+
) {
|
|
221
|
+
unanswered.push(classId);
|
|
222
|
+
}
|
|
195
223
|
const components = [
|
|
196
224
|
...namesOf(project, instance === UNANSWERED ? undefined : instance).map(nameComponent),
|
|
197
225
|
...(instance !== UNANSWERED && reaches(project, instance, error) ? ["toString"] : []),
|
|
@@ -220,7 +248,7 @@ function outsideMemberNames<Brand>(
|
|
|
220
248
|
names.set(classId, found);
|
|
221
249
|
}
|
|
222
250
|
}
|
|
223
|
-
return names;
|
|
251
|
+
return { names, unanswered };
|
|
224
252
|
}
|
|
225
253
|
|
|
226
254
|
/** The global `Error` instance type's identifier, where the program's library declares one. */
|