@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
|
@@ -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. */
|
package/src/paths.ts
CHANGED
|
@@ -11,6 +11,58 @@ export function isAbsolutePath(path: string): boolean {
|
|
|
11
11
|
return path.startsWith(SEPARATOR);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/** Whether a specifier starts a path relative to the file that writes it: `./` or `../`. */
|
|
15
|
+
export function startsRelativePath(specifier: string): boolean {
|
|
16
|
+
return specifier.startsWith("./") || specifier.startsWith("../");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Whether a specifier names a path relative to the file that writes it: `.`, `..`, `./` or `../` first. */
|
|
20
|
+
export function isRelativeSpecifier(specifier: string): boolean {
|
|
21
|
+
return specifier === "." || specifier === ".." || startsRelativePath(specifier);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether a module specifier names a path rather than a package, as the compiler reads one:
|
|
26
|
+
* a relative specifier or an absolute path.
|
|
27
|
+
*/
|
|
28
|
+
export function isPathSpecifier(specifier: string): boolean {
|
|
29
|
+
return isRelativeSpecifier(specifier) || isAbsolutePath(specifier);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether a string starts with `./`, `../` or `/`, the specifiers a browser reads as a URL
|
|
34
|
+
* rather than a bare name, so a bare `.` or `..` stays a request:
|
|
35
|
+
* https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-url-like-module-specifier
|
|
36
|
+
*/
|
|
37
|
+
export function isExplicitPath(text: string): boolean {
|
|
38
|
+
return startsRelativePath(text) || isAbsolutePath(text);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether a string starts with `.` or `/`, which no npm package name does, so a reader that
|
|
43
|
+
* takes any other string as a package's takes it as a path:
|
|
44
|
+
* https://docs.npmjs.com/cli/v11/configuring-npm/package-json#name
|
|
45
|
+
*/
|
|
46
|
+
export function cannotStartPackageName(text: string): boolean {
|
|
47
|
+
return text.startsWith(".") || isAbsolutePath(text);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A URL scheme, `file:` and `node:` among them: https://url.spec.whatwg.org/#url-scheme-string */
|
|
51
|
+
const URL_SCHEME = /^[a-z][a-z\d+.-]*:/iu;
|
|
52
|
+
|
|
53
|
+
/** Whether a string starts with a URL scheme. */
|
|
54
|
+
export function hasURLScheme(text: string): boolean {
|
|
55
|
+
return URL_SCHEME.test(text);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether a URL reference is a relative one that does not start with a solidus, the bare
|
|
60
|
+
* name included: https://url.spec.whatwg.org/#relative-url-string
|
|
61
|
+
*/
|
|
62
|
+
export function isPathRelativeURL(reference: string): boolean {
|
|
63
|
+
return reference !== "" && !reference.startsWith(SEPARATOR) && !hasURLScheme(reference);
|
|
64
|
+
}
|
|
65
|
+
|
|
14
66
|
/**
|
|
15
67
|
* One path with `.` removed, `..` applied to the segment before it, and repeated
|
|
16
68
|
* separators collapsed. A `..` that walks past the root of an absolute path is
|
|
@@ -97,6 +149,18 @@ export function resolvePath(against: string, path: string): string {
|
|
|
97
149
|
return isAbsolutePath(path) ? normalizePath(path) : joinPath(against, path);
|
|
98
150
|
}
|
|
99
151
|
|
|
152
|
+
/**
|
|
153
|
+
* The path a relative URL reference names against `dir`, its query and its fragment
|
|
154
|
+
* removed, which name no part of the file; undefined where the reference is not relative.
|
|
155
|
+
*/
|
|
156
|
+
export function relativeURLPath(dir: string, reference: string): string | undefined {
|
|
157
|
+
if (!isPathRelativeURL(reference)) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const cut = reference.search(/[?#]/u);
|
|
161
|
+
return resolvePath(dir, cut < 0 ? reference : reference.slice(0, cut));
|
|
162
|
+
}
|
|
163
|
+
|
|
100
164
|
/**
|
|
101
165
|
* The relative path before the query of a relative module specifier such as
|
|
102
166
|
* `./worker.ts?worker`, the module a bundler loads; undefined for a specifier with no
|
|
@@ -105,7 +169,7 @@ export function resolvePath(against: string, path: string): string {
|
|
|
105
169
|
*/
|
|
106
170
|
export function queriedModulePath(specifier: string): string | undefined {
|
|
107
171
|
const query = specifier.indexOf("?");
|
|
108
|
-
if (query < 0 || !(specifier
|
|
172
|
+
if (query < 0 || !startsRelativePath(specifier)) {
|
|
109
173
|
return undefined;
|
|
110
174
|
}
|
|
111
175
|
const raw = specifier
|