@cplieger/deadset-ts 6.1.1-dev.1 → 7.0.0-dev.2
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 +45 -32
- package/dist/src/component-layer.js +1 -1
- package/dist/src/configuration-files.js +227 -27
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +68 -10
- package/dist/src/convention-rows.js +48 -0
- package/dist/src/conventions.js +80 -0
- package/dist/src/dependencies.js +121 -46
- package/dist/src/discover.js +1 -1
- package/dist/src/edges.js +1 -1
- package/dist/src/exempt-classes.js +9 -22
- package/dist/src/exempt.js +3 -15
- package/dist/src/findings/completion.js +41 -11
- package/dist/src/findings/dependencies-and-module-machinery.js +6 -26
- package/dist/src/findings/emitters.js +0 -4
- package/dist/src/findings/self-check.js +14 -10
- package/dist/src/html-entries.js +29 -14
- package/dist/src/indexed-access.js +377 -81
- package/dist/src/intra-function-parts.js +57 -11
- package/dist/src/inventory.js +3 -12
- package/dist/src/kinds.js +6 -6
- package/dist/src/references.js +62 -23
- package/dist/src/report.js +3 -0
- package/dist/src/roots.js +96 -7
- package/dist/src/run.js +0 -2
- package/dist/src/schema.js +1 -1
- package/dist/src/scope.js +2 -2
- package/dist/src/session.js +6 -2
- package/dist/src/source-files.js +1 -0
- package/dist/src/suppress-inline.js +1 -1
- package/dist/src/suppress.js +15 -16
- 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 -4
- package/dist/src/workspace-resolution.js +0 -2
- package/dist/src/workspace.js +0 -3
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +55 -47
- package/src/calls.ts +3 -3
- package/src/component-files.ts +7 -7
- package/src/component-layer.ts +2 -2
- package/src/components.ts +1 -1
- package/src/config.ts +4 -4
- package/src/configuration-files.ts +246 -28
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +99 -13
- package/src/convention-rows.ts +76 -0
- package/src/conventions.ts +102 -0
- package/src/dependencies.ts +148 -56
- package/src/discover.ts +3 -3
- package/src/edges.ts +3 -3
- package/src/entry-points.ts +1 -1
- package/src/exempt-classes.ts +10 -24
- package/src/exempt.ts +5 -29
- package/src/file-facts.ts +1 -1
- package/src/finding.ts +5 -1
- package/src/findings/completion.ts +53 -11
- package/src/findings/dependencies-and-module-machinery.ts +9 -28
- package/src/findings/emitters.ts +3 -8
- package/src/findings/self-check.ts +17 -14
- package/src/findings-pass.ts +2 -2
- package/src/graph.ts +2 -2
- package/src/html-entries.ts +34 -16
- package/src/indexed-access.ts +476 -102
- package/src/intra-function-parts.ts +84 -12
- package/src/inventory.ts +5 -33
- package/src/json-document.ts +1 -1
- package/src/kinds.ts +7 -6
- package/src/manifest.ts +1 -1
- package/src/namespace-objects.ts +1 -1
- package/src/pnpm-workspace.ts +1 -1
- package/src/references.ts +93 -33
- package/src/report.ts +8 -4
- package/src/roots.ts +129 -9
- package/src/run.ts +0 -2
- package/src/schema.ts +2 -2
- package/src/scope.ts +2 -2
- package/src/session.ts +7 -3
- package/src/source-files.ts +3 -0
- package/src/stores.ts +1 -1
- package/src/suppress-inline.ts +3 -3
- package/src/suppress.ts +21 -24
- package/src/sweep.ts +29 -17
- package/src/symbol-keys.ts +172 -0
- package/src/template-field.ts +1 -1
- package/src/test-joins.ts +120 -0
- package/src/value-reach.ts +1 -1
- package/src/verbs/verb.ts +1 -4
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +23 -16
- package/src/workspace-programs.ts +3 -10
- package/src/workspace-resolution.ts +0 -3
- package/src/workspace-run.ts +1 -1
- package/src/workspace.ts +1 -9
package/src/indexed-access.ts
CHANGED
|
@@ -1,34 +1,46 @@
|
|
|
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
7
|
|
|
9
8
|
import {
|
|
10
9
|
isArrowFunction,
|
|
11
10
|
isCallExpression,
|
|
11
|
+
isClassDeclaration,
|
|
12
|
+
isClassExpression,
|
|
12
13
|
isFunctionDeclaration,
|
|
13
14
|
isFunctionExpression,
|
|
14
15
|
isIdentifier,
|
|
16
|
+
isNonNullExpression,
|
|
17
|
+
isParenthesizedExpression,
|
|
18
|
+
isNamedImports,
|
|
19
|
+
isNamespaceImport,
|
|
20
|
+
isImportDeclaration,
|
|
15
21
|
isIndexedAccessTypeNode,
|
|
22
|
+
isInterfaceDeclaration,
|
|
16
23
|
isLiteralTypeNode,
|
|
17
24
|
isMethodDeclaration,
|
|
25
|
+
isMethodSignatureDeclaration,
|
|
18
26
|
isNewExpression,
|
|
19
27
|
isNoSubstitutionTemplateLiteral,
|
|
20
28
|
isNumericLiteral,
|
|
21
29
|
isParenthesizedTypeNode,
|
|
22
30
|
isPropertyAccessExpression,
|
|
23
31
|
isStringLiteral,
|
|
32
|
+
isTypeAliasDeclaration,
|
|
24
33
|
isTypeReferenceNode,
|
|
25
34
|
isUnionTypeNode,
|
|
26
|
-
|
|
35
|
+
isVariableDeclaration,
|
|
36
|
+
type CallExpression,
|
|
37
|
+
type NewExpression,
|
|
27
38
|
type Node,
|
|
28
39
|
type SourceFile,
|
|
29
40
|
type TypeNode,
|
|
41
|
+
type TypeReferenceNode,
|
|
30
42
|
} from "@typescript/native/unstable/ast";
|
|
31
|
-
import type
|
|
43
|
+
import { SymbolFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/unstable/sync";
|
|
32
44
|
import type { AliasChains } from "./alias-chain.ts";
|
|
33
45
|
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
34
46
|
import { renderPosition } from "./position.ts";
|
|
@@ -37,22 +49,32 @@ import type { Reference } from "./references.ts";
|
|
|
37
49
|
import type { ProjectView } from "./session.ts";
|
|
38
50
|
import { typeQueryAliases } from "./type-query-alias.ts";
|
|
39
51
|
|
|
40
|
-
/** A
|
|
52
|
+
/** A declaration that declares type parameters, by the kinds of node that can. */
|
|
41
53
|
type Generic = Node & {
|
|
42
54
|
readonly typeParameters?: readonly { readonly name: Node }[];
|
|
43
|
-
readonly parameters
|
|
55
|
+
readonly parameters?: readonly { readonly name: Node; readonly type?: TypeNode }[];
|
|
44
56
|
};
|
|
45
57
|
|
|
46
58
|
function isGeneric(node: Node): node is Generic {
|
|
47
59
|
return (
|
|
48
60
|
(isFunctionDeclaration(node) ||
|
|
49
61
|
isMethodDeclaration(node) ||
|
|
62
|
+
isMethodSignatureDeclaration(node) ||
|
|
50
63
|
isArrowFunction(node) ||
|
|
51
|
-
isFunctionExpression(node)
|
|
64
|
+
isFunctionExpression(node) ||
|
|
65
|
+
isTypeAliasDeclaration(node) ||
|
|
66
|
+
isInterfaceDeclaration(node) ||
|
|
67
|
+
isClassDeclaration(node) ||
|
|
68
|
+
isClassExpression(node)) &&
|
|
52
69
|
(node as { readonly typeParameters?: readonly unknown[] }).typeParameters !== undefined
|
|
53
70
|
);
|
|
54
71
|
}
|
|
55
72
|
|
|
73
|
+
/** Whether one generic is a receiver's: a class or an interface whose methods it parameterizes. */
|
|
74
|
+
function isReceiver(node: Node): boolean {
|
|
75
|
+
return isInterfaceDeclaration(node) || isClassDeclaration(node) || isClassExpression(node);
|
|
76
|
+
}
|
|
77
|
+
|
|
56
78
|
function unwrappedType(node: TypeNode): TypeNode {
|
|
57
79
|
return isParenthesizedTypeNode(node) ? unwrappedType(node.type) : node;
|
|
58
80
|
}
|
|
@@ -92,25 +114,150 @@ function literalTypeNames(
|
|
|
92
114
|
return names;
|
|
93
115
|
}
|
|
94
116
|
|
|
95
|
-
/**
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
117
|
+
/**
|
|
118
|
+
* One side of an indexed access inside a generic, as a site of the generic fixes it: the
|
|
119
|
+
* type parameter of that position, a type the generic writes, or a type parameter of the
|
|
120
|
+
* receiver whose method the generic is.
|
|
121
|
+
*/
|
|
122
|
+
type Side =
|
|
123
|
+
| { readonly kind: "parameter"; readonly index: number }
|
|
124
|
+
| { readonly kind: "written"; readonly node: TypeNode }
|
|
125
|
+
| { readonly kind: "receiver"; readonly index: number };
|
|
126
|
+
|
|
127
|
+
/** An indexed access inside one generic whose index names a type parameter. */
|
|
128
|
+
interface Carried {
|
|
129
|
+
readonly object: Side;
|
|
130
|
+
readonly index: Side;
|
|
100
131
|
}
|
|
101
132
|
|
|
102
|
-
/**
|
|
133
|
+
/** A side a site has fixed: a type node it writes, a type the compiler answers, or a type parameter it passes on. */
|
|
134
|
+
type Fixed =
|
|
135
|
+
| { readonly kind: "node"; readonly node: TypeNode }
|
|
136
|
+
| { readonly kind: "type"; readonly type: Type }
|
|
137
|
+
| { readonly kind: "passed"; readonly generic: Generic; readonly index: number };
|
|
138
|
+
|
|
139
|
+
/** An indexed access to resolve: the object type, the index, and where the reference is made. */
|
|
103
140
|
interface Access {
|
|
104
141
|
readonly file: SourceFile;
|
|
105
142
|
readonly from: string;
|
|
106
143
|
readonly at: Node;
|
|
107
144
|
readonly object: TypeNode | Type;
|
|
108
|
-
|
|
145
|
+
/** The names a literal index spells, or the index to read as a type. */
|
|
146
|
+
readonly index: readonly string[] | TypeNode | Type;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** A reference or a call that may fix a generic's type parameters. */
|
|
150
|
+
interface Site {
|
|
151
|
+
readonly file: SourceFile;
|
|
152
|
+
readonly from: string;
|
|
153
|
+
readonly node: TypeReferenceNode | CallExpression | NewExpression;
|
|
154
|
+
readonly name: string;
|
|
155
|
+
readonly enclosing: readonly Generic[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The type a fixed side stands for, or none where the site passes it on. */
|
|
159
|
+
function concrete(fixed: Fixed): TypeNode | Type | undefined {
|
|
160
|
+
return fixed.kind === "node" ? fixed.node : fixed.kind === "type" ? fixed.type : undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The position of the type parameter `name` among the ones a generic declares, or -1. */
|
|
164
|
+
function parameterAt(generic: Generic, name: string): number {
|
|
165
|
+
return (generic.typeParameters ?? []).findIndex(
|
|
166
|
+
(parameter) => isIdentifier(parameter.name) && parameter.name.text === name,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The type parameter one type node names whole, among the enclosing generics, innermost first. */
|
|
171
|
+
function namedParameter(
|
|
172
|
+
node: TypeNode,
|
|
173
|
+
enclosing: readonly Generic[],
|
|
174
|
+
): { readonly generic: Generic; readonly index: number } | undefined {
|
|
175
|
+
const type = unwrappedType(node);
|
|
176
|
+
if (
|
|
177
|
+
!isTypeReferenceNode(type) ||
|
|
178
|
+
!isIdentifier(type.typeName) ||
|
|
179
|
+
type.typeArguments !== undefined
|
|
180
|
+
) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
const spelled = type.typeName.text;
|
|
184
|
+
for (const generic of [...enclosing].reverse()) {
|
|
185
|
+
const index = parameterAt(generic, spelled);
|
|
186
|
+
if (index >= 0) {
|
|
187
|
+
return { generic, index };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The name a site writes to reach one generic: its own, or its variable's. */
|
|
194
|
+
function calledName(generic: Generic): string | undefined {
|
|
195
|
+
const own = (generic as { readonly name?: Node }).name;
|
|
196
|
+
if (own !== undefined && isIdentifier(own)) {
|
|
197
|
+
return own.text;
|
|
198
|
+
}
|
|
199
|
+
const parent = generic.parent as Node | undefined;
|
|
200
|
+
return parent !== undefined && isVariableDeclaration(parent) && isIdentifier(parent.name)
|
|
201
|
+
? parent.name.text
|
|
202
|
+
: undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** The generic a declaration a symbol names is: the function a variable is initialized with included. */
|
|
206
|
+
function genericOf(node: Node): Generic | undefined {
|
|
207
|
+
if (isGeneric(node)) {
|
|
208
|
+
return node;
|
|
209
|
+
}
|
|
210
|
+
if (
|
|
211
|
+
isVariableDeclaration(node) &&
|
|
212
|
+
node.initializer !== undefined &&
|
|
213
|
+
isGeneric(node.initializer)
|
|
214
|
+
) {
|
|
215
|
+
return node.initializer;
|
|
216
|
+
}
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Each indexed access inside one generic whose index names a type parameter of it, read
|
|
222
|
+
* from its own text: the object side is a type parameter of the same generic, one of the
|
|
223
|
+
* receiver it is a method of, or a type it writes.
|
|
224
|
+
*/
|
|
225
|
+
function carriedIn(generic: Generic): readonly Carried[] {
|
|
226
|
+
const found: Carried[] = [];
|
|
227
|
+
const receiver = generic.parent as Node | undefined;
|
|
228
|
+
const outer =
|
|
229
|
+
receiver !== undefined && isReceiver(receiver) && isGeneric(receiver) ? receiver : undefined;
|
|
230
|
+
const visit = (node: Node): void => {
|
|
231
|
+
if (isIndexedAccessTypeNode(node)) {
|
|
232
|
+
const index = namedParameter(node.indexType, [generic]);
|
|
233
|
+
if (index !== undefined) {
|
|
234
|
+
const own = namedParameter(node.objectType, [generic]);
|
|
235
|
+
const fromReceiver =
|
|
236
|
+
own === undefined && outer !== undefined
|
|
237
|
+
? namedParameter(node.objectType, [outer])
|
|
238
|
+
: undefined;
|
|
239
|
+
const object: Side =
|
|
240
|
+
own !== undefined
|
|
241
|
+
? { kind: "parameter", index: own.index }
|
|
242
|
+
: fromReceiver !== undefined
|
|
243
|
+
? { kind: "receiver", index: fromReceiver.index }
|
|
244
|
+
: { kind: "written", node: node.objectType };
|
|
245
|
+
found.push({ object, index: { kind: "parameter", index: index.index } });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (node !== generic && isGeneric(node)) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
node.forEachChild(visit);
|
|
252
|
+
};
|
|
253
|
+
visit(generic);
|
|
254
|
+
return found;
|
|
109
255
|
}
|
|
110
256
|
|
|
111
257
|
/**
|
|
112
258
|
* The references the indexed-access types of one project's own files make to the members
|
|
113
|
-
* they name, each made by the declaration that holds the type
|
|
259
|
+
* they name, each made by the declaration that holds the type, the reference or the call,
|
|
260
|
+
* or by the file.
|
|
114
261
|
*/
|
|
115
262
|
export function indexedAccessReferences<Brand>(
|
|
116
263
|
project: ProjectView<Brand>,
|
|
@@ -121,9 +268,30 @@ export function indexedAccessReferences<Brand>(
|
|
|
121
268
|
): readonly Reference[] {
|
|
122
269
|
const queries = project.queries;
|
|
123
270
|
const accesses: Access[] = [];
|
|
124
|
-
const
|
|
125
|
-
const calls: { file: SourceFile; from: string; call: Node; name: string }[] = [];
|
|
271
|
+
const carried = new Map<Generic, Carried[]>();
|
|
126
272
|
const names = new Set<string>();
|
|
273
|
+
const sites: Site[] = [];
|
|
274
|
+
/**
|
|
275
|
+
* Calls passing a literal argument whose callee a package binding heads, or that call a
|
|
276
|
+
* method, which a dependency's generic may fix.
|
|
277
|
+
*/
|
|
278
|
+
const literalCalls: Site[] = [];
|
|
279
|
+
|
|
280
|
+
const carry = (generic: Generic, one: Carried): boolean => {
|
|
281
|
+
const known = carried.get(generic) ?? [];
|
|
282
|
+
const same = known.some(
|
|
283
|
+
(other) => JSON.stringify(sideKey(other)) === JSON.stringify(sideKey(one)),
|
|
284
|
+
);
|
|
285
|
+
if (same) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
carried.set(generic, [...known, one]);
|
|
289
|
+
const name = calledName(generic);
|
|
290
|
+
if (name !== undefined) {
|
|
291
|
+
names.add(name);
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
};
|
|
127
295
|
|
|
128
296
|
for (const file of project.ownSourceFiles()) {
|
|
129
297
|
const fileId = held.declarations.get(nodeKey(file, file));
|
|
@@ -131,18 +299,27 @@ export function indexedAccessReferences<Brand>(
|
|
|
131
299
|
continue;
|
|
132
300
|
}
|
|
133
301
|
const aliasTypes = new Set(typeQueryAliases(file).map((alias) => alias.type));
|
|
302
|
+
const imported = packageBindings(file);
|
|
134
303
|
const visit = (node: Node, from: string, enclosing: readonly Generic[]): void => {
|
|
135
304
|
const own = held.declarations.get(nodeKey(file, node));
|
|
136
305
|
const holder = own ?? from;
|
|
137
306
|
const inside = isGeneric(node) ? [...enclosing, node] : enclosing;
|
|
307
|
+
if (isGeneric(node)) {
|
|
308
|
+
carriedIn(node).forEach((one) => carry(node, one));
|
|
309
|
+
}
|
|
138
310
|
if (isIndexedAccessTypeNode(node) && !aliasTypes.has(node)) {
|
|
139
311
|
const literal = literalNames(node.indexType);
|
|
140
312
|
if (literal !== undefined) {
|
|
141
|
-
accesses.push({ file, from: holder, at: node, object: node.objectType,
|
|
142
|
-
} else {
|
|
143
|
-
parameterIndex(node, inside, file, generics, names);
|
|
313
|
+
accesses.push({ file, from: holder, at: node, object: node.objectType, index: literal });
|
|
144
314
|
}
|
|
145
315
|
}
|
|
316
|
+
if (
|
|
317
|
+
isTypeReferenceNode(node) &&
|
|
318
|
+
node.typeArguments !== undefined &&
|
|
319
|
+
isIdentifier(node.typeName)
|
|
320
|
+
) {
|
|
321
|
+
sites.push({ file, from: holder, node, name: node.typeName.text, enclosing: inside });
|
|
322
|
+
}
|
|
146
323
|
if (isCallExpression(node) || isNewExpression(node)) {
|
|
147
324
|
const callee = node.expression;
|
|
148
325
|
const name = isIdentifier(callee)
|
|
@@ -151,7 +328,20 @@ export function indexedAccessReferences<Brand>(
|
|
|
151
328
|
? callee.name.text
|
|
152
329
|
: undefined;
|
|
153
330
|
if (name !== undefined) {
|
|
154
|
-
|
|
331
|
+
const site = { file, from: holder, node, name, enclosing: inside };
|
|
332
|
+
sites.push(site);
|
|
333
|
+
const literal = (node.arguments ?? []).some(
|
|
334
|
+
(argument) => isStringLiteral(argument) || isNumericLiteral(argument),
|
|
335
|
+
);
|
|
336
|
+
const head = rootName(callee);
|
|
337
|
+
const method = isPropertyAccessExpression(callee);
|
|
338
|
+
if (
|
|
339
|
+
literal &&
|
|
340
|
+
isCallExpression(node) &&
|
|
341
|
+
(method || (head !== undefined && imported.has(head)))
|
|
342
|
+
) {
|
|
343
|
+
literalCalls.push(site);
|
|
344
|
+
}
|
|
155
345
|
}
|
|
156
346
|
}
|
|
157
347
|
node.forEachChild((child) => {
|
|
@@ -163,50 +353,220 @@ export function indexedAccessReferences<Brand>(
|
|
|
163
353
|
});
|
|
164
354
|
}
|
|
165
355
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
356
|
+
function partsOf(type: Type): readonly Type[] {
|
|
357
|
+
const parts = queries.constituents(type);
|
|
358
|
+
return parts === UNANSWERED ? [type] : parts;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** The generics one symbol declares, through the alias it may be. */
|
|
362
|
+
const genericsOf = (symbol: TSSymbol): readonly Generic[] => {
|
|
363
|
+
const target = (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : queries.aliased(symbol);
|
|
364
|
+
if (target === UNANSWERED) {
|
|
365
|
+
return [];
|
|
173
366
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
367
|
+
return target.declarations.flatMap((handle) => {
|
|
368
|
+
const node = project.declarationAt(handle)?.node;
|
|
369
|
+
const generic = node === undefined ? undefined : genericOf(node);
|
|
370
|
+
return generic === undefined ? [] : [generic];
|
|
371
|
+
});
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/** What one site fixes for one side of a carried access. */
|
|
375
|
+
const fixedAt = (site: Site, side: Side, generic: Generic): Fixed | undefined => {
|
|
376
|
+
if (side.kind === "written") {
|
|
377
|
+
return { kind: "node", node: side.node };
|
|
181
378
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
379
|
+
const { node } = site;
|
|
380
|
+
if (side.kind === "receiver") {
|
|
381
|
+
const callee = isCallExpression(node) ? node.expression : undefined;
|
|
382
|
+
if (callee === undefined || !isPropertyAccessExpression(callee)) {
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
const receiver = queries.typeAt(callee.expression);
|
|
386
|
+
if (receiver === UNANSWERED || receiver === undefined) {
|
|
387
|
+
return undefined;
|
|
188
388
|
}
|
|
389
|
+
const args = queries.typeArguments(receiver);
|
|
390
|
+
const type = args === UNANSWERED ? undefined : args[side.index];
|
|
391
|
+
return type === undefined ? undefined : { kind: "type", type };
|
|
189
392
|
}
|
|
190
|
-
|
|
393
|
+
const written = node.typeArguments?.[side.index];
|
|
394
|
+
if (written !== undefined) {
|
|
395
|
+
const passed = namedParameter(written, site.enclosing);
|
|
396
|
+
return passed === undefined
|
|
397
|
+
? { kind: "node", node: written }
|
|
398
|
+
: { kind: "passed", generic: passed.generic, index: passed.index };
|
|
399
|
+
}
|
|
400
|
+
if (isTypeReferenceNode(node)) {
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
const at = (generic.parameters ?? []).findIndex((parameter) => {
|
|
404
|
+
const type = parameter.type === undefined ? undefined : unwrappedType(parameter.type);
|
|
405
|
+
return (
|
|
406
|
+
type !== undefined &&
|
|
407
|
+
isTypeReferenceNode(type) &&
|
|
408
|
+
isIdentifier(type.typeName) &&
|
|
409
|
+
parameterAt(generic, type.typeName.text) === side.index
|
|
410
|
+
);
|
|
411
|
+
});
|
|
412
|
+
if (at < 0) {
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
const resolved = queries.resolvedSignature(node);
|
|
416
|
+
if (resolved === UNANSWERED || resolved === undefined) {
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
const type = queries.typeAtPosition(resolved, at);
|
|
420
|
+
return type === UNANSWERED ? undefined : { kind: "type", type };
|
|
421
|
+
};
|
|
191
422
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
423
|
+
/** Reads one carried access at one site: an access where both sides are fixed, a carry where one is passed on. */
|
|
424
|
+
const readAt = (site: Site, generic: Generic, one: Carried): boolean => {
|
|
425
|
+
const object = fixedAt(site, one.object, generic);
|
|
426
|
+
const index = object === undefined ? undefined : fixedAt(site, one.index, generic);
|
|
427
|
+
if (object === undefined || index === undefined) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
const passed = [object, index].find((fixed) => fixed.kind === "passed");
|
|
431
|
+
if (passed !== undefined) {
|
|
432
|
+
const side = (fixed: Fixed): Side | undefined =>
|
|
433
|
+
fixed.kind === "passed"
|
|
434
|
+
? fixed.generic === passed.generic
|
|
435
|
+
? { kind: "parameter", index: fixed.index }
|
|
436
|
+
: undefined
|
|
437
|
+
: fixed.kind === "node"
|
|
438
|
+
? { kind: "written", node: fixed.node }
|
|
439
|
+
: undefined;
|
|
440
|
+
const objectSide = side(object);
|
|
441
|
+
const indexSide = side(index);
|
|
442
|
+
return objectSide !== undefined && indexSide !== undefined
|
|
443
|
+
? carry(passed.generic, { object: objectSide, index: indexSide })
|
|
444
|
+
: false;
|
|
445
|
+
}
|
|
446
|
+
const objectAt = concrete(object);
|
|
447
|
+
const indexAt = concrete(index);
|
|
448
|
+
if (objectAt === undefined || indexAt === undefined) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
accesses.push({
|
|
452
|
+
file: site.file,
|
|
453
|
+
from: site.from,
|
|
454
|
+
at: site.node,
|
|
455
|
+
object: objectAt,
|
|
456
|
+
index: "kind" in indexAt ? (literalNames(indexAt) ?? indexAt) : indexAt,
|
|
457
|
+
});
|
|
458
|
+
return false;
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// A site passing a type parameter on carries the access to its own generic, so the
|
|
462
|
+
// sites are read again until no generic gains an access.
|
|
463
|
+
const resolvedGenerics = new Map<Site, readonly Generic[]>();
|
|
464
|
+
const read = new Set<string>();
|
|
465
|
+
for (let changed = true; changed;) {
|
|
466
|
+
changed = false;
|
|
467
|
+
const pending = sites.filter((site) => names.has(site.name) && !resolvedGenerics.has(site));
|
|
468
|
+
const typeSites = pending.filter((site) => isTypeReferenceNode(site.node));
|
|
469
|
+
const symbols =
|
|
470
|
+
typeSites.length === 0
|
|
471
|
+
? []
|
|
472
|
+
: queries.symbolsAt(typeSites.map((site) => (site.node as TypeReferenceNode).typeName));
|
|
473
|
+
typeSites.forEach((site, at) => {
|
|
474
|
+
const symbol = symbols[at];
|
|
475
|
+
resolvedGenerics.set(
|
|
476
|
+
site,
|
|
477
|
+
symbol === UNANSWERED || symbol === undefined ? [] : genericsOf(symbol),
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
for (const site of pending.filter((one) => !isTypeReferenceNode(one.node))) {
|
|
481
|
+
const resolved = queries.resolvedSignature(site.node);
|
|
482
|
+
const declaration =
|
|
483
|
+
resolved === UNANSWERED || resolved?.declaration === undefined
|
|
484
|
+
? undefined
|
|
485
|
+
: project.declarationAt(resolved.declaration)?.node;
|
|
486
|
+
const generic = declaration === undefined ? undefined : genericOf(declaration);
|
|
487
|
+
resolvedGenerics.set(site, generic === undefined ? [] : [generic]);
|
|
488
|
+
}
|
|
489
|
+
sites.forEach((site, siteAt) => {
|
|
490
|
+
for (const generic of resolvedGenerics.get(site) ?? []) {
|
|
491
|
+
(carried.get(generic) ?? []).forEach((one, at) => {
|
|
492
|
+
const key = `${String(siteAt)}:${String(at)}:${nodeKey(generic.getSourceFile(), generic)}`;
|
|
493
|
+
if (read.has(key)) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
read.add(key);
|
|
497
|
+
changed = readAt(site, generic, one) || changed;
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
});
|
|
195
501
|
}
|
|
196
502
|
|
|
197
|
-
|
|
503
|
+
// A generic function or method declared outside the project's own files carries what
|
|
504
|
+
// its own text indexes by its type parameters and its receiver's.
|
|
505
|
+
const outside = literalCalls.filter((site) => !resolvedGenerics.has(site));
|
|
506
|
+
const methods =
|
|
507
|
+
outside.length === 0
|
|
508
|
+
? []
|
|
509
|
+
: queries.symbolsAt(
|
|
510
|
+
outside.map((site) => {
|
|
511
|
+
const callee = (site.node as CallExpression).expression;
|
|
512
|
+
return isPropertyAccessExpression(callee) ? callee.name : callee;
|
|
513
|
+
}),
|
|
514
|
+
);
|
|
515
|
+
const own = project.ownPaths();
|
|
516
|
+
outside.forEach((site, at) => {
|
|
517
|
+
const named = methods[at];
|
|
518
|
+
const symbol =
|
|
519
|
+
named === UNANSWERED || named === undefined || (named.flags & SymbolFlags.Alias) === 0
|
|
520
|
+
? named
|
|
521
|
+
: queries.aliased(named);
|
|
522
|
+
if (
|
|
523
|
+
symbol === UNANSWERED ||
|
|
524
|
+
symbol === undefined ||
|
|
525
|
+
(symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) === 0
|
|
526
|
+
) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
for (const handle of symbol.declarations) {
|
|
530
|
+
if (own.has(handle.path)) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
const node = project.declarationAt(handle)?.node;
|
|
534
|
+
const generic = node === undefined ? undefined : genericOf(node);
|
|
535
|
+
if (generic === undefined) {
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
for (const one of carriedIn(generic)) {
|
|
539
|
+
readAt(site, generic, one);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const typeNodes = accesses.flatMap((one) => [
|
|
545
|
+
...("kind" in one.object ? [one.object] : []),
|
|
546
|
+
...(!Array.isArray(one.index) && "kind" in one.index ? [one.index] : []),
|
|
547
|
+
]);
|
|
198
548
|
const typeOf = new Map<Node, Type>();
|
|
199
|
-
queries.typesAt(
|
|
200
|
-
const node =
|
|
549
|
+
queries.typesAt(typeNodes).forEach((answer, index) => {
|
|
550
|
+
const node = typeNodes[index];
|
|
201
551
|
if (answer !== UNANSWERED && answer !== undefined && node !== undefined) {
|
|
202
552
|
typeOf.set(node, answer);
|
|
203
553
|
}
|
|
204
554
|
});
|
|
555
|
+
const asType = (side: TypeNode | Type): Type | undefined =>
|
|
556
|
+
"kind" in side ? typeOf.get(side) : side;
|
|
205
557
|
|
|
206
558
|
const found: Reference[] = [];
|
|
207
559
|
for (const access of accesses) {
|
|
208
|
-
const type =
|
|
209
|
-
|
|
560
|
+
const type = asType(access.object);
|
|
561
|
+
const indexType = Array.isArray(access.index)
|
|
562
|
+
? undefined
|
|
563
|
+
: asType(access.index as TypeNode | Type);
|
|
564
|
+
const accessed = Array.isArray(access.index)
|
|
565
|
+
? (access.index as readonly string[])
|
|
566
|
+
: indexType === undefined
|
|
567
|
+
? undefined
|
|
568
|
+
: literalTypeNames(indexType, partsOf);
|
|
569
|
+
if (type === undefined || accessed === undefined) {
|
|
210
570
|
continue;
|
|
211
571
|
}
|
|
212
572
|
const position = renderPosition(access.file, root, access.at.getStart());
|
|
@@ -216,7 +576,7 @@ export function indexedAccessReferences<Brand>(
|
|
|
216
576
|
continue;
|
|
217
577
|
}
|
|
218
578
|
for (const property of properties) {
|
|
219
|
-
if (!
|
|
579
|
+
if (!accessed.includes(property.escapedName)) {
|
|
220
580
|
continue;
|
|
221
581
|
}
|
|
222
582
|
for (const to of chains.declarationsOf(property)) {
|
|
@@ -236,60 +596,74 @@ export function indexedAccessReferences<Brand>(
|
|
|
236
596
|
}
|
|
237
597
|
|
|
238
598
|
/**
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
599
|
+
* The local names one file binds to a package: each name an import declaration with a
|
|
600
|
+
* bare specifier binds, and each variable initialized by a call or a construction whose
|
|
601
|
+
* callee such a name heads, read in source order.
|
|
242
602
|
*/
|
|
243
|
-
function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
if (!isTypeReferenceNode(index) || !isIdentifier(index.typeName)) {
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
const spelled = index.typeName.text;
|
|
255
|
-
for (const generic of [...enclosing].reverse()) {
|
|
256
|
-
const declares = (generic.typeParameters ?? []).some(
|
|
257
|
-
(parameter) => isIdentifier(parameter.name) && parameter.name.text === spelled,
|
|
258
|
-
);
|
|
259
|
-
if (!declares) {
|
|
603
|
+
function packageBindings(file: SourceFile): ReadonlySet<string> {
|
|
604
|
+
const names = new Set<string>();
|
|
605
|
+
for (const statement of file.statements) {
|
|
606
|
+
if (
|
|
607
|
+
!isImportDeclaration(statement) ||
|
|
608
|
+
!isStringLiteral(statement.moduleSpecifier) ||
|
|
609
|
+
/^[./]/u.test(statement.moduleSpecifier.text)
|
|
610
|
+
) {
|
|
260
611
|
continue;
|
|
261
612
|
}
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (at >= 0) {
|
|
272
|
-
const key = nodeKey(file, generic);
|
|
273
|
-
generics.set(key, [...(generics.get(key) ?? []), { object: node.objectType, parameter: at }]);
|
|
274
|
-
const name = calledName(generic);
|
|
275
|
-
if (name !== undefined) {
|
|
276
|
-
names.add(name);
|
|
277
|
-
}
|
|
613
|
+
const clause = statement.importClause;
|
|
614
|
+
if (clause?.name !== undefined) {
|
|
615
|
+
names.add(clause.name.text);
|
|
616
|
+
}
|
|
617
|
+
const bindings = clause?.namedBindings;
|
|
618
|
+
if (bindings !== undefined && isNamespaceImport(bindings)) {
|
|
619
|
+
names.add(bindings.name.text);
|
|
620
|
+
} else if (bindings !== undefined && isNamedImports(bindings)) {
|
|
621
|
+
bindings.elements.forEach((element) => names.add(element.name.text));
|
|
278
622
|
}
|
|
279
|
-
return;
|
|
280
623
|
}
|
|
624
|
+
if (names.size === 0) {
|
|
625
|
+
return names;
|
|
626
|
+
}
|
|
627
|
+
const visit = (node: Node): void => {
|
|
628
|
+
if (
|
|
629
|
+
isVariableDeclaration(node) &&
|
|
630
|
+
isIdentifier(node.name) &&
|
|
631
|
+
node.initializer !== undefined &&
|
|
632
|
+
(isCallExpression(node.initializer) || isNewExpression(node.initializer))
|
|
633
|
+
) {
|
|
634
|
+
const head = rootName(node.initializer.expression);
|
|
635
|
+
if (head !== undefined && names.has(head)) {
|
|
636
|
+
names.add(node.name.text);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
node.forEachChild(visit);
|
|
640
|
+
};
|
|
641
|
+
visit(file);
|
|
642
|
+
return names;
|
|
281
643
|
}
|
|
282
644
|
|
|
283
|
-
/** The
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
645
|
+
/** The identifier an expression is headed by, through property accesses and calls. */
|
|
646
|
+
function rootName(expression: Node): string | undefined {
|
|
647
|
+
let at: Node = expression;
|
|
648
|
+
for (;;) {
|
|
649
|
+
if (isIdentifier(at)) {
|
|
650
|
+
return at.text;
|
|
651
|
+
}
|
|
652
|
+
if (isPropertyAccessExpression(at) || isCallExpression(at) || isNewExpression(at)) {
|
|
653
|
+
at = at.expression;
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (isParenthesizedExpression(at) || isNonNullExpression(at)) {
|
|
657
|
+
at = at.expression;
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
return undefined;
|
|
288
661
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
:
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/** What makes two carried accesses one: the kind and position of each side, or its written node. */
|
|
665
|
+
function sideKey(one: Carried): readonly unknown[] {
|
|
666
|
+
const key = (side: Side): unknown =>
|
|
667
|
+
side.kind === "written" ? ["written", side.node.pos, side.node.end] : [side.kind, side.index];
|
|
668
|
+
return [key(one.object), key(one.index)];
|
|
295
669
|
}
|