@cplieger/deadset-ts 6.1.0 → 7.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/src/analysis.js +39 -16
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +27 -3
- package/dist/src/convention-rows.js +42 -0
- package/dist/src/conventions.js +48 -0
- package/dist/src/dependencies.js +109 -38
- package/dist/src/findings/completion.js +6 -7
- package/dist/src/findings/dependencies-and-module-machinery.js +6 -26
- package/dist/src/html-entries.js +29 -14
- package/dist/src/indexed-access.js +371 -81
- package/dist/src/intra-function-parts.js +57 -11
- package/dist/src/kinds.js +6 -6
- package/dist/src/references.js +60 -21
- package/dist/src/roots.js +96 -6
- package/dist/src/session.js +6 -1
- package/dist/src/source-files.js +1 -0
- package/dist/src/sweep.js +29 -17
- package/dist/src/symbol-keys.js +114 -0
- package/dist/src/test-joins.js +101 -0
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +21 -15
- package/dist/src/workspace-programs.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +45 -17
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +36 -4
- package/src/convention-rows.ts +57 -0
- package/src/conventions.ts +59 -0
- package/src/dependencies.ts +134 -46
- package/src/finding.ts +2 -0
- package/src/findings/completion.ts +7 -7
- package/src/findings/dependencies-and-module-machinery.ts +9 -28
- package/src/html-entries.ts +33 -15
- package/src/indexed-access.ts +468 -102
- package/src/intra-function-parts.ts +84 -12
- package/src/kinds.ts +7 -6
- package/src/references.ts +86 -27
- package/src/roots.ts +129 -6
- package/src/session.ts +7 -1
- package/src/source-files.ts +3 -0
- package/src/sweep.ts +29 -17
- package/src/symbol-keys.ts +172 -0
- package/src/test-joins.ts +120 -0
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +23 -16
- package/src/workspace-programs.ts +1 -1
package/dist/src/html-entries.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The files the
|
|
3
|
-
*
|
|
2
|
+
* The files the scripts of the target's HTML files load: a module script's, and a
|
|
3
|
+
* classic script's that names its file by `src`. An HTML file is read as text and no
|
|
4
|
+
* markup is parsed, so a `<script>` inside a comment is read as well.
|
|
4
5
|
*/
|
|
5
6
|
import { isExportDeclaration, isImportDeclaration, isStringLiteral, } from "@typescript/native/unstable/ast";
|
|
6
7
|
import { resolveRelativeSpecifier } from "./configuration-files.js";
|
|
@@ -15,6 +16,8 @@ const SCRIPT_END = /<\/script\s*>/iu;
|
|
|
15
16
|
const ATTRIBUTE = /([^\s"'=<>/]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/gu;
|
|
16
17
|
/** A value that names a resource on another origin, or by another scheme. */
|
|
17
18
|
const ELSEWHERE = /^(?:\/\/|[A-Za-z][A-Za-z\d+.-]*:)/u;
|
|
19
|
+
/** The `type` values a classic script carries, compared in lowercase. */
|
|
20
|
+
const CLASSIC_TYPES = new Set(["text/javascript", "application/javascript"]);
|
|
18
21
|
/** The prefixes an inline script's specifier names a file of the target with. */
|
|
19
22
|
const FILE_PREFIXES = ["/", "./", "../"];
|
|
20
23
|
/** Every HTML file below `dir`, outside every `node_modules` directory, by absolute path. */
|
|
@@ -62,18 +65,22 @@ function specifiersOf(parse, fileName, content) {
|
|
|
62
65
|
});
|
|
63
66
|
}
|
|
64
67
|
/**
|
|
65
|
-
* Every value a
|
|
66
|
-
*
|
|
67
|
-
* `./` or `../`.
|
|
68
|
+
* Every value a script of one HTML file names a file by: the `src` attribute's value of a
|
|
69
|
+
* module script or of a classic one, or each specifier an inline module script imports or
|
|
70
|
+
* re-exports that starts with `/`, `./` or `../`.
|
|
68
71
|
*/
|
|
69
72
|
function scriptValues(parse, path, text) {
|
|
70
73
|
const values = [];
|
|
71
74
|
for (const match of text.matchAll(SCRIPT_TAG)) {
|
|
72
75
|
const held = attributes(match[1] ?? "");
|
|
73
|
-
|
|
76
|
+
const type = held.get("type");
|
|
77
|
+
const src = held.get("src");
|
|
78
|
+
if (type !== "module") {
|
|
79
|
+
if (src !== undefined && (type === undefined || CLASSIC_TYPES.has(type.toLowerCase()))) {
|
|
80
|
+
values.push(src);
|
|
81
|
+
}
|
|
74
82
|
continue;
|
|
75
83
|
}
|
|
76
|
-
const src = held.get("src");
|
|
77
84
|
if (src !== undefined) {
|
|
78
85
|
values.push(src);
|
|
79
86
|
continue;
|
|
@@ -102,11 +109,11 @@ function manifestDirectory(host, root, dir) {
|
|
|
102
109
|
return root;
|
|
103
110
|
}
|
|
104
111
|
/**
|
|
105
|
-
* The own files the
|
|
112
|
+
* The own files the scripts of every HTML file below the target root load. A value
|
|
106
113
|
* starting with a single `/` is read against the nearest manifest's directory, any other
|
|
107
|
-
* against the HTML file's own,
|
|
108
|
-
*
|
|
109
|
-
*
|
|
114
|
+
* against the HTML file's own, as a configuration file's relative specifier. One naming no
|
|
115
|
+
* source file is read back through the project's emit mapping, and a `/` one also against
|
|
116
|
+
* an output directory that is the HTML file's own. Another origin or scheme loads nothing.
|
|
110
117
|
*/
|
|
111
118
|
export function htmlEntries(host, targetRoot, files, parse) {
|
|
112
119
|
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
@@ -124,12 +131,20 @@ export function htmlEntries(host, targetRoot, files, parse) {
|
|
|
124
131
|
if (ELSEWHERE.test(value)) {
|
|
125
132
|
continue;
|
|
126
133
|
}
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
const base = value.startsWith("/") ? manifestDirectory(host, root, dir) : dir;
|
|
135
|
+
const relative = value.startsWith("/") ? `.${value}` : value;
|
|
136
|
+
const reached = resolveRelativeSpecifier(host, base, relative);
|
|
130
137
|
const file = reached === undefined ? undefined : files.byName.get(reached);
|
|
131
138
|
if (file !== undefined) {
|
|
132
139
|
found.push({ file, source: value });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const served = value.startsWith("/")
|
|
143
|
+
? files.outputDirs.filter((outDir) => outDir === dir)
|
|
144
|
+
: [];
|
|
145
|
+
const emitting = [base, ...served].flatMap((against) => files.named(resolvePath(against, relative)));
|
|
146
|
+
for (const one of new Set(emitting)) {
|
|
147
|
+
found.push({ file: one, source: value });
|
|
133
148
|
}
|
|
134
149
|
}
|
|
135
150
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The members an indexed-access type names
|
|
3
|
-
*
|
|
4
|
-
* a call
|
|
5
|
-
*
|
|
6
|
-
* query of a type-query alias is no reference.
|
|
2
|
+
* The members an indexed-access type names: `T["a" | "b"]` reads `a` and `b` of `T`. An
|
|
3
|
+
* index naming a type parameter is read once the reference or the call holding it fixes
|
|
4
|
+
* that parameter, and a call of a method of a generic receiver fixes the receiver's by its
|
|
5
|
+
* type arguments. The type query of a type-query alias is no reference.
|
|
7
6
|
*/
|
|
8
|
-
import { isArrowFunction, isCallExpression, isFunctionDeclaration, isFunctionExpression, isIdentifier, isIndexedAccessTypeNode, isLiteralTypeNode, isMethodDeclaration, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isParenthesizedTypeNode, isPropertyAccessExpression, isStringLiteral, isTypeReferenceNode, isUnionTypeNode,
|
|
7
|
+
import { isArrowFunction, isCallExpression, isClassDeclaration, isClassExpression, isFunctionDeclaration, isFunctionExpression, isIdentifier, isNonNullExpression, isParenthesizedExpression, isNamedImports, isNamespaceImport, isImportDeclaration, isIndexedAccessTypeNode, isInterfaceDeclaration, isLiteralTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isParenthesizedTypeNode, isPropertyAccessExpression, isStringLiteral, isTypeAliasDeclaration, isTypeReferenceNode, isUnionTypeNode, isVariableDeclaration, } from "@typescript/native/unstable/ast";
|
|
8
|
+
import { SymbolFlags } from "@typescript/native/unstable/sync";
|
|
9
9
|
import { nodeKey } from "./inventory.js";
|
|
10
10
|
import { renderPosition } from "./position.js";
|
|
11
11
|
import { UNANSWERED } from "./query.js";
|
|
@@ -13,10 +13,19 @@ import { typeQueryAliases } from "./type-query-alias.js";
|
|
|
13
13
|
function isGeneric(node) {
|
|
14
14
|
return ((isFunctionDeclaration(node) ||
|
|
15
15
|
isMethodDeclaration(node) ||
|
|
16
|
+
isMethodSignatureDeclaration(node) ||
|
|
16
17
|
isArrowFunction(node) ||
|
|
17
|
-
isFunctionExpression(node)
|
|
18
|
+
isFunctionExpression(node) ||
|
|
19
|
+
isTypeAliasDeclaration(node) ||
|
|
20
|
+
isInterfaceDeclaration(node) ||
|
|
21
|
+
isClassDeclaration(node) ||
|
|
22
|
+
isClassExpression(node)) &&
|
|
18
23
|
node.typeParameters !== undefined);
|
|
19
24
|
}
|
|
25
|
+
/** Whether one generic is a receiver's: a class or an interface whose methods it parameterizes. */
|
|
26
|
+
function isReceiver(node) {
|
|
27
|
+
return isInterfaceDeclaration(node) || isClassDeclaration(node) || isClassExpression(node);
|
|
28
|
+
}
|
|
20
29
|
function unwrappedType(node) {
|
|
21
30
|
return isParenthesizedTypeNode(node) ? unwrappedType(node.type) : node;
|
|
22
31
|
}
|
|
@@ -52,35 +61,138 @@ function literalTypeNames(type, parts) {
|
|
|
52
61
|
}
|
|
53
62
|
return names;
|
|
54
63
|
}
|
|
64
|
+
/** The type a fixed side stands for, or none where the site passes it on. */
|
|
65
|
+
function concrete(fixed) {
|
|
66
|
+
return fixed.kind === "node" ? fixed.node : fixed.kind === "type" ? fixed.type : undefined;
|
|
67
|
+
}
|
|
68
|
+
/** The position of the type parameter `name` among the ones a generic declares, or -1. */
|
|
69
|
+
function parameterAt(generic, name) {
|
|
70
|
+
return (generic.typeParameters ?? []).findIndex((parameter) => isIdentifier(parameter.name) && parameter.name.text === name);
|
|
71
|
+
}
|
|
72
|
+
/** The type parameter one type node names whole, among the enclosing generics, innermost first. */
|
|
73
|
+
function namedParameter(node, enclosing) {
|
|
74
|
+
const type = unwrappedType(node);
|
|
75
|
+
if (!isTypeReferenceNode(type) ||
|
|
76
|
+
!isIdentifier(type.typeName) ||
|
|
77
|
+
type.typeArguments !== undefined) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const spelled = type.typeName.text;
|
|
81
|
+
for (const generic of [...enclosing].reverse()) {
|
|
82
|
+
const index = parameterAt(generic, spelled);
|
|
83
|
+
if (index >= 0) {
|
|
84
|
+
return { generic, index };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
/** The name a site writes to reach one generic: its own, or its variable's. */
|
|
90
|
+
function calledName(generic) {
|
|
91
|
+
const own = generic.name;
|
|
92
|
+
if (own !== undefined && isIdentifier(own)) {
|
|
93
|
+
return own.text;
|
|
94
|
+
}
|
|
95
|
+
const parent = generic.parent;
|
|
96
|
+
return parent !== undefined && isVariableDeclaration(parent) && isIdentifier(parent.name)
|
|
97
|
+
? parent.name.text
|
|
98
|
+
: undefined;
|
|
99
|
+
}
|
|
100
|
+
/** The generic a declaration a symbol names is: the function a variable is initialized with included. */
|
|
101
|
+
function genericOf(node) {
|
|
102
|
+
if (isGeneric(node)) {
|
|
103
|
+
return node;
|
|
104
|
+
}
|
|
105
|
+
if (isVariableDeclaration(node) &&
|
|
106
|
+
node.initializer !== undefined &&
|
|
107
|
+
isGeneric(node.initializer)) {
|
|
108
|
+
return node.initializer;
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Each indexed access inside one generic whose index names a type parameter of it, read
|
|
114
|
+
* from its own text: the object side is a type parameter of the same generic, one of the
|
|
115
|
+
* receiver it is a method of, or a type it writes.
|
|
116
|
+
*/
|
|
117
|
+
function carriedIn(generic) {
|
|
118
|
+
const found = [];
|
|
119
|
+
const receiver = generic.parent;
|
|
120
|
+
const outer = receiver !== undefined && isReceiver(receiver) && isGeneric(receiver) ? receiver : undefined;
|
|
121
|
+
const visit = (node) => {
|
|
122
|
+
if (isIndexedAccessTypeNode(node)) {
|
|
123
|
+
const index = namedParameter(node.indexType, [generic]);
|
|
124
|
+
if (index !== undefined) {
|
|
125
|
+
const own = namedParameter(node.objectType, [generic]);
|
|
126
|
+
const fromReceiver = own === undefined && outer !== undefined
|
|
127
|
+
? namedParameter(node.objectType, [outer])
|
|
128
|
+
: undefined;
|
|
129
|
+
const object = own !== undefined
|
|
130
|
+
? { kind: "parameter", index: own.index }
|
|
131
|
+
: fromReceiver !== undefined
|
|
132
|
+
? { kind: "receiver", index: fromReceiver.index }
|
|
133
|
+
: { kind: "written", node: node.objectType };
|
|
134
|
+
found.push({ object, index: { kind: "parameter", index: index.index } });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (node !== generic && isGeneric(node)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
node.forEachChild(visit);
|
|
141
|
+
};
|
|
142
|
+
visit(generic);
|
|
143
|
+
return found;
|
|
144
|
+
}
|
|
55
145
|
/**
|
|
56
146
|
* The references the indexed-access types of one project's own files make to the members
|
|
57
|
-
* they name, each made by the declaration that holds the type
|
|
147
|
+
* they name, each made by the declaration that holds the type, the reference or the call,
|
|
148
|
+
* or by the file.
|
|
58
149
|
*/
|
|
59
150
|
export function indexedAccessReferences(project, held, chains, root, testFiles) {
|
|
60
151
|
const queries = project.queries;
|
|
61
152
|
const accesses = [];
|
|
62
|
-
const
|
|
63
|
-
const calls = [];
|
|
153
|
+
const carried = new Map();
|
|
64
154
|
const names = new Set();
|
|
155
|
+
const sites = [];
|
|
156
|
+
/** Calls passing a literal argument whose callee a package binding heads, which a dependency's generic may fix. */
|
|
157
|
+
const literalCalls = [];
|
|
158
|
+
const carry = (generic, one) => {
|
|
159
|
+
const known = carried.get(generic) ?? [];
|
|
160
|
+
const same = known.some((other) => JSON.stringify(sideKey(other)) === JSON.stringify(sideKey(one)));
|
|
161
|
+
if (same) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
carried.set(generic, [...known, one]);
|
|
165
|
+
const name = calledName(generic);
|
|
166
|
+
if (name !== undefined) {
|
|
167
|
+
names.add(name);
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
};
|
|
65
171
|
for (const file of project.ownSourceFiles()) {
|
|
66
172
|
const fileId = held.declarations.get(nodeKey(file, file));
|
|
67
173
|
if (fileId === undefined) {
|
|
68
174
|
continue;
|
|
69
175
|
}
|
|
70
176
|
const aliasTypes = new Set(typeQueryAliases(file).map((alias) => alias.type));
|
|
177
|
+
const imported = packageBindings(file);
|
|
71
178
|
const visit = (node, from, enclosing) => {
|
|
72
179
|
const own = held.declarations.get(nodeKey(file, node));
|
|
73
180
|
const holder = own ?? from;
|
|
74
181
|
const inside = isGeneric(node) ? [...enclosing, node] : enclosing;
|
|
182
|
+
if (isGeneric(node)) {
|
|
183
|
+
carriedIn(node).forEach((one) => carry(node, one));
|
|
184
|
+
}
|
|
75
185
|
if (isIndexedAccessTypeNode(node) && !aliasTypes.has(node)) {
|
|
76
186
|
const literal = literalNames(node.indexType);
|
|
77
187
|
if (literal !== undefined) {
|
|
78
|
-
accesses.push({ file, from: holder, at: node, object: node.objectType,
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
parameterIndex(node, inside, file, generics, names);
|
|
188
|
+
accesses.push({ file, from: holder, at: node, object: node.objectType, index: literal });
|
|
82
189
|
}
|
|
83
190
|
}
|
|
191
|
+
if (isTypeReferenceNode(node) &&
|
|
192
|
+
node.typeArguments !== undefined &&
|
|
193
|
+
isIdentifier(node.typeName)) {
|
|
194
|
+
sites.push({ file, from: holder, node, name: node.typeName.text, enclosing: inside });
|
|
195
|
+
}
|
|
84
196
|
if (isCallExpression(node) || isNewExpression(node)) {
|
|
85
197
|
const callee = node.expression;
|
|
86
198
|
const name = isIdentifier(callee)
|
|
@@ -89,7 +201,13 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
89
201
|
? callee.name.text
|
|
90
202
|
: undefined;
|
|
91
203
|
if (name !== undefined) {
|
|
92
|
-
|
|
204
|
+
const site = { file, from: holder, node, name, enclosing: inside };
|
|
205
|
+
sites.push(site);
|
|
206
|
+
const literal = (node.arguments ?? []).some((argument) => isStringLiteral(argument) || isNumericLiteral(argument));
|
|
207
|
+
const head = rootName(callee);
|
|
208
|
+
if (literal && isCallExpression(node) && head !== undefined && imported.has(head)) {
|
|
209
|
+
literalCalls.push(site);
|
|
210
|
+
}
|
|
93
211
|
}
|
|
94
212
|
}
|
|
95
213
|
node.forEachChild((child) => {
|
|
@@ -100,45 +218,198 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
100
218
|
visit(child, fileId, []);
|
|
101
219
|
});
|
|
102
220
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
221
|
+
function partsOf(type) {
|
|
222
|
+
const parts = queries.constituents(type);
|
|
223
|
+
return parts === UNANSWERED ? [type] : parts;
|
|
224
|
+
}
|
|
225
|
+
/** The generics one symbol declares, through the alias it may be. */
|
|
226
|
+
const genericsOf = (symbol) => {
|
|
227
|
+
const target = (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : queries.aliased(symbol);
|
|
228
|
+
if (target === UNANSWERED) {
|
|
229
|
+
return [];
|
|
110
230
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
? undefined
|
|
114
|
-
:
|
|
115
|
-
|
|
116
|
-
|
|
231
|
+
return target.declarations.flatMap((handle) => {
|
|
232
|
+
const node = project.declarationAt(handle)?.node;
|
|
233
|
+
const generic = node === undefined ? undefined : genericOf(node);
|
|
234
|
+
return generic === undefined ? [] : [generic];
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
/** What one site fixes for one side of a carried access. */
|
|
238
|
+
const fixedAt = (site, side, generic) => {
|
|
239
|
+
if (side.kind === "written") {
|
|
240
|
+
return { kind: "node", node: side.node };
|
|
117
241
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
122
|
-
|
|
242
|
+
const { node } = site;
|
|
243
|
+
if (side.kind === "receiver") {
|
|
244
|
+
const callee = isCallExpression(node) ? node.expression : undefined;
|
|
245
|
+
if (callee === undefined || !isPropertyAccessExpression(callee)) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
const receiver = queries.typeAt(callee.expression);
|
|
249
|
+
if (receiver === UNANSWERED || receiver === undefined) {
|
|
250
|
+
return undefined;
|
|
123
251
|
}
|
|
252
|
+
const args = queries.typeArguments(receiver);
|
|
253
|
+
const type = args === UNANSWERED ? undefined : args[side.index];
|
|
254
|
+
return type === undefined ? undefined : { kind: "type", type };
|
|
124
255
|
}
|
|
256
|
+
const written = node.typeArguments?.[side.index];
|
|
257
|
+
if (written !== undefined) {
|
|
258
|
+
const passed = namedParameter(written, site.enclosing);
|
|
259
|
+
return passed === undefined
|
|
260
|
+
? { kind: "node", node: written }
|
|
261
|
+
: { kind: "passed", generic: passed.generic, index: passed.index };
|
|
262
|
+
}
|
|
263
|
+
if (isTypeReferenceNode(node)) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
const at = (generic.parameters ?? []).findIndex((parameter) => {
|
|
267
|
+
const type = parameter.type === undefined ? undefined : unwrappedType(parameter.type);
|
|
268
|
+
return (type !== undefined &&
|
|
269
|
+
isTypeReferenceNode(type) &&
|
|
270
|
+
isIdentifier(type.typeName) &&
|
|
271
|
+
parameterAt(generic, type.typeName.text) === side.index);
|
|
272
|
+
});
|
|
273
|
+
if (at < 0) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
const resolved = queries.resolvedSignature(node);
|
|
277
|
+
if (resolved === UNANSWERED || resolved === undefined) {
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
const type = queries.typeAtPosition(resolved, at);
|
|
281
|
+
return type === UNANSWERED ? undefined : { kind: "type", type };
|
|
282
|
+
};
|
|
283
|
+
/** Reads one carried access at one site: an access where both sides are fixed, a carry where one is passed on. */
|
|
284
|
+
const readAt = (site, generic, one) => {
|
|
285
|
+
const object = fixedAt(site, one.object, generic);
|
|
286
|
+
const index = object === undefined ? undefined : fixedAt(site, one.index, generic);
|
|
287
|
+
if (object === undefined || index === undefined) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
const passed = [object, index].find((fixed) => fixed.kind === "passed");
|
|
291
|
+
if (passed !== undefined) {
|
|
292
|
+
const side = (fixed) => fixed.kind === "passed"
|
|
293
|
+
? fixed.generic === passed.generic
|
|
294
|
+
? { kind: "parameter", index: fixed.index }
|
|
295
|
+
: undefined
|
|
296
|
+
: fixed.kind === "node"
|
|
297
|
+
? { kind: "written", node: fixed.node }
|
|
298
|
+
: undefined;
|
|
299
|
+
const objectSide = side(object);
|
|
300
|
+
const indexSide = side(index);
|
|
301
|
+
return objectSide !== undefined && indexSide !== undefined
|
|
302
|
+
? carry(passed.generic, { object: objectSide, index: indexSide })
|
|
303
|
+
: false;
|
|
304
|
+
}
|
|
305
|
+
const objectAt = concrete(object);
|
|
306
|
+
const indexAt = concrete(index);
|
|
307
|
+
if (objectAt === undefined || indexAt === undefined) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
accesses.push({
|
|
311
|
+
file: site.file,
|
|
312
|
+
from: site.from,
|
|
313
|
+
at: site.node,
|
|
314
|
+
object: objectAt,
|
|
315
|
+
index: "kind" in indexAt ? (literalNames(indexAt) ?? indexAt) : indexAt,
|
|
316
|
+
});
|
|
317
|
+
return false;
|
|
318
|
+
};
|
|
319
|
+
// A site passing a type parameter on carries the access to its own generic, so the
|
|
320
|
+
// sites are read again until no generic gains an access.
|
|
321
|
+
const resolvedGenerics = new Map();
|
|
322
|
+
const read = new Set();
|
|
323
|
+
for (let changed = true; changed;) {
|
|
324
|
+
changed = false;
|
|
325
|
+
const pending = sites.filter((site) => names.has(site.name) && !resolvedGenerics.has(site));
|
|
326
|
+
const typeSites = pending.filter((site) => isTypeReferenceNode(site.node));
|
|
327
|
+
const symbols = typeSites.length === 0
|
|
328
|
+
? []
|
|
329
|
+
: queries.symbolsAt(typeSites.map((site) => site.node.typeName));
|
|
330
|
+
typeSites.forEach((site, at) => {
|
|
331
|
+
const symbol = symbols[at];
|
|
332
|
+
resolvedGenerics.set(site, symbol === UNANSWERED || symbol === undefined ? [] : genericsOf(symbol));
|
|
333
|
+
});
|
|
334
|
+
for (const site of pending.filter((one) => !isTypeReferenceNode(one.node))) {
|
|
335
|
+
const resolved = queries.resolvedSignature(site.node);
|
|
336
|
+
const declaration = resolved === UNANSWERED || resolved?.declaration === undefined
|
|
337
|
+
? undefined
|
|
338
|
+
: project.declarationAt(resolved.declaration)?.node;
|
|
339
|
+
const generic = declaration === undefined ? undefined : genericOf(declaration);
|
|
340
|
+
resolvedGenerics.set(site, generic === undefined ? [] : [generic]);
|
|
341
|
+
}
|
|
342
|
+
sites.forEach((site, siteAt) => {
|
|
343
|
+
for (const generic of resolvedGenerics.get(site) ?? []) {
|
|
344
|
+
(carried.get(generic) ?? []).forEach((one, at) => {
|
|
345
|
+
const key = `${String(siteAt)}:${String(at)}:${nodeKey(generic.getSourceFile(), generic)}`;
|
|
346
|
+
if (read.has(key)) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
read.add(key);
|
|
350
|
+
changed = readAt(site, generic, one) || changed;
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
});
|
|
125
354
|
}
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
355
|
+
// A generic function or method declared outside the project's own files carries what
|
|
356
|
+
// its own text indexes by its type parameters and its receiver's.
|
|
357
|
+
const outside = literalCalls.filter((site) => !resolvedGenerics.has(site));
|
|
358
|
+
const methods = outside.length === 0
|
|
359
|
+
? []
|
|
360
|
+
: queries.symbolsAt(outside.map((site) => {
|
|
361
|
+
const callee = site.node.expression;
|
|
362
|
+
return isPropertyAccessExpression(callee) ? callee.name : callee;
|
|
363
|
+
}));
|
|
364
|
+
const own = project.ownPaths();
|
|
365
|
+
outside.forEach((site, at) => {
|
|
366
|
+
const named = methods[at];
|
|
367
|
+
const symbol = named === UNANSWERED || named === undefined || (named.flags & SymbolFlags.Alias) === 0
|
|
368
|
+
? named
|
|
369
|
+
: queries.aliased(named);
|
|
370
|
+
if (symbol === UNANSWERED ||
|
|
371
|
+
symbol === undefined ||
|
|
372
|
+
(symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) === 0) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
for (const handle of symbol.declarations) {
|
|
376
|
+
if (own.has(handle.path)) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const node = project.declarationAt(handle)?.node;
|
|
380
|
+
const generic = node === undefined ? undefined : genericOf(node);
|
|
381
|
+
if (generic === undefined) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
for (const one of carriedIn(generic)) {
|
|
385
|
+
readAt(site, generic, one);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
const typeNodes = accesses.flatMap((one) => [
|
|
390
|
+
...("kind" in one.object ? [one.object] : []),
|
|
391
|
+
...(!Array.isArray(one.index) && "kind" in one.index ? [one.index] : []),
|
|
392
|
+
]);
|
|
131
393
|
const typeOf = new Map();
|
|
132
|
-
queries.typesAt(
|
|
133
|
-
const node =
|
|
394
|
+
queries.typesAt(typeNodes).forEach((answer, index) => {
|
|
395
|
+
const node = typeNodes[index];
|
|
134
396
|
if (answer !== UNANSWERED && answer !== undefined && node !== undefined) {
|
|
135
397
|
typeOf.set(node, answer);
|
|
136
398
|
}
|
|
137
399
|
});
|
|
400
|
+
const asType = (side) => "kind" in side ? typeOf.get(side) : side;
|
|
138
401
|
const found = [];
|
|
139
402
|
for (const access of accesses) {
|
|
140
|
-
const type =
|
|
141
|
-
|
|
403
|
+
const type = asType(access.object);
|
|
404
|
+
const indexType = Array.isArray(access.index)
|
|
405
|
+
? undefined
|
|
406
|
+
: asType(access.index);
|
|
407
|
+
const accessed = Array.isArray(access.index)
|
|
408
|
+
? access.index
|
|
409
|
+
: indexType === undefined
|
|
410
|
+
? undefined
|
|
411
|
+
: literalTypeNames(indexType, partsOf);
|
|
412
|
+
if (type === undefined || accessed === undefined) {
|
|
142
413
|
continue;
|
|
143
414
|
}
|
|
144
415
|
const position = renderPosition(access.file, root, access.at.getStart());
|
|
@@ -148,7 +419,7 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
148
419
|
continue;
|
|
149
420
|
}
|
|
150
421
|
for (const property of properties) {
|
|
151
|
-
if (!
|
|
422
|
+
if (!accessed.includes(property.escapedName)) {
|
|
152
423
|
continue;
|
|
153
424
|
}
|
|
154
425
|
for (const to of chains.declarationsOf(property)) {
|
|
@@ -167,49 +438,68 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
167
438
|
return found;
|
|
168
439
|
}
|
|
169
440
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
441
|
+
* The local names one file binds to a package: each name an import declaration with a
|
|
442
|
+
* bare specifier binds, and each variable initialized by a call or a construction whose
|
|
443
|
+
* callee such a name heads, read in source order.
|
|
173
444
|
*/
|
|
174
|
-
function
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
for (const generic of [...enclosing].reverse()) {
|
|
181
|
-
const declares = (generic.typeParameters ?? []).some((parameter) => isIdentifier(parameter.name) && parameter.name.text === spelled);
|
|
182
|
-
if (!declares) {
|
|
445
|
+
function packageBindings(file) {
|
|
446
|
+
const names = new Set();
|
|
447
|
+
for (const statement of file.statements) {
|
|
448
|
+
if (!isImportDeclaration(statement) ||
|
|
449
|
+
!isStringLiteral(statement.moduleSpecifier) ||
|
|
450
|
+
/^[./]/u.test(statement.moduleSpecifier.text)) {
|
|
183
451
|
continue;
|
|
184
452
|
}
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (at >= 0) {
|
|
193
|
-
const key = nodeKey(file, generic);
|
|
194
|
-
generics.set(key, [...(generics.get(key) ?? []), { object: node.objectType, parameter: at }]);
|
|
195
|
-
const name = calledName(generic);
|
|
196
|
-
if (name !== undefined) {
|
|
197
|
-
names.add(name);
|
|
198
|
-
}
|
|
453
|
+
const clause = statement.importClause;
|
|
454
|
+
if (clause?.name !== undefined) {
|
|
455
|
+
names.add(clause.name.text);
|
|
456
|
+
}
|
|
457
|
+
const bindings = clause?.namedBindings;
|
|
458
|
+
if (bindings !== undefined && isNamespaceImport(bindings)) {
|
|
459
|
+
names.add(bindings.name.text);
|
|
199
460
|
}
|
|
200
|
-
|
|
461
|
+
else if (bindings !== undefined && isNamedImports(bindings)) {
|
|
462
|
+
bindings.elements.forEach((element) => names.add(element.name.text));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (names.size === 0) {
|
|
466
|
+
return names;
|
|
201
467
|
}
|
|
468
|
+
const visit = (node) => {
|
|
469
|
+
if (isVariableDeclaration(node) &&
|
|
470
|
+
isIdentifier(node.name) &&
|
|
471
|
+
node.initializer !== undefined &&
|
|
472
|
+
(isCallExpression(node.initializer) || isNewExpression(node.initializer))) {
|
|
473
|
+
const head = rootName(node.initializer.expression);
|
|
474
|
+
if (head !== undefined && names.has(head)) {
|
|
475
|
+
names.add(node.name.text);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
node.forEachChild(visit);
|
|
479
|
+
};
|
|
480
|
+
visit(file);
|
|
481
|
+
return names;
|
|
202
482
|
}
|
|
203
|
-
/** The
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
483
|
+
/** The identifier an expression is headed by, through property accesses and calls. */
|
|
484
|
+
function rootName(expression) {
|
|
485
|
+
let at = expression;
|
|
486
|
+
for (;;) {
|
|
487
|
+
if (isIdentifier(at)) {
|
|
488
|
+
return at.text;
|
|
489
|
+
}
|
|
490
|
+
if (isPropertyAccessExpression(at) || isCallExpression(at) || isNewExpression(at)) {
|
|
491
|
+
at = at.expression;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (isParenthesizedExpression(at) || isNonNullExpression(at)) {
|
|
495
|
+
at = at.expression;
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
return undefined;
|
|
208
499
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
: undefined;
|
|
500
|
+
}
|
|
501
|
+
/** What makes two carried accesses one: the kind and position of each side, or its written node. */
|
|
502
|
+
function sideKey(one) {
|
|
503
|
+
const key = (side) => side.kind === "written" ? ["written", side.node.pos, side.node.end] : [side.kind, side.index];
|
|
504
|
+
return [key(one.object), key(one.index)];
|
|
215
505
|
}
|