@cplieger/deadset-ts 7.0.0-dev.2 → 7.0.0-dev.3
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 +89 -44
- package/dist/src/augmentations.js +14 -12
- package/dist/src/components.js +62 -20
- package/dist/src/configuration-files.js +5 -5
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contextual-uses.js +71 -14
- package/dist/src/convention-rows.js +6 -2
- package/dist/src/conventions.js +3 -1
- package/dist/src/dependencies.js +80 -26
- package/dist/src/element-access.js +341 -0
- package/dist/src/erased-stores.js +317 -0
- package/dist/src/findings/completion.js +46 -15
- package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/self-check.js +17 -4
- package/dist/src/findings-pass.js +6 -5
- package/dist/src/in-operands.js +30 -0
- package/dist/src/indexed-access.js +264 -125
- package/dist/src/intra-function-parts.js +25 -11
- package/dist/src/inventory.js +25 -0
- package/dist/src/keyof-comparisons.js +170 -0
- package/dist/src/kinds.js +17 -7
- package/dist/src/manifest.js +6 -16
- package/dist/src/matrix.js +20 -2
- package/dist/src/query.js +3 -0
- package/dist/src/references.js +12 -1
- package/dist/src/report.js +1 -1
- package/dist/src/reporters/sarif.js +4 -0
- package/dist/src/serialization-contract.js +43 -14
- package/dist/src/session.js +12 -7
- package/dist/src/suppress-file.js +58 -6
- package/dist/src/suppress-inline.js +13 -4
- package/dist/src/suppress.js +54 -24
- package/dist/src/unimplemented-callees.js +312 -0
- package/dist/src/value-reach.js +7 -7
- package/dist/src/verbs/analyze.js +11 -9
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +135 -52
- package/src/augmentations.ts +14 -11
- package/src/components.ts +103 -21
- package/src/configuration-files.ts +5 -4
- package/src/conformance-record.ts +1 -1
- package/src/contextual-uses.ts +81 -17
- package/src/convention-rows.ts +6 -2
- package/src/conventions.ts +10 -1
- package/src/dependencies.ts +107 -30
- package/src/element-access.ts +446 -0
- package/src/erased-stores.ts +413 -0
- package/src/file-facts.ts +2 -0
- package/src/findings/completion.ts +54 -20
- package/src/findings/dependencies-and-module-machinery.ts +1 -1
- package/src/findings/emitters.ts +1 -1
- package/src/findings/self-check.ts +18 -4
- package/src/findings-pass.ts +12 -5
- package/src/in-operands.ts +45 -0
- package/src/indexed-access.ts +303 -145
- package/src/intra-function-parts.ts +41 -16
- package/src/inventory.ts +32 -0
- package/src/keyof-comparisons.ts +211 -0
- package/src/kinds.ts +20 -7
- package/src/manifest.ts +6 -21
- package/src/matrix.ts +34 -2
- package/src/query.ts +10 -0
- package/src/references.ts +23 -1
- package/src/report.ts +3 -1
- package/src/reporters/sarif.ts +4 -0
- package/src/serialization-contract.ts +53 -14
- package/src/session.ts +34 -29
- package/src/suppress-file.ts +68 -5
- package/src/suppress-inline.ts +14 -4
- package/src/suppress.ts +63 -28
- package/src/unimplemented-callees.ts +411 -0
- package/src/value-reach.ts +11 -7
- package/src/verbs/analyze.ts +15 -9
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The property names `in` tests in a project's production files. A value narrowed by
|
|
3
|
+
* `"name" in value` is read back by that name, often through `unknown`, where no type
|
|
4
|
+
* links the read to a declaration, so only a text match can tell which member it reads.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
isBinaryExpression,
|
|
9
|
+
isNoSubstitutionTemplateLiteral,
|
|
10
|
+
isStringLiteral,
|
|
11
|
+
SyntaxKind,
|
|
12
|
+
type Node,
|
|
13
|
+
} from "@typescript/native/unstable/ast";
|
|
14
|
+
import type { ProjectView } from "./session.ts";
|
|
15
|
+
import { sourceFilesOf } from "./source-files.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The text of each string literal, or template literal with no substitution, written as
|
|
19
|
+
* the left operand of an `in` expression in a file of the project that is no test file.
|
|
20
|
+
* `testFiles` holds the test files by their path below the target root.
|
|
21
|
+
*/
|
|
22
|
+
export function inOperandNames<Brand>(
|
|
23
|
+
project: ProjectView<Brand>,
|
|
24
|
+
targetRoot: string,
|
|
25
|
+
testFiles: readonly string[],
|
|
26
|
+
): readonly string[] {
|
|
27
|
+
const tests = new Set(testFiles);
|
|
28
|
+
const found = new Set<string>();
|
|
29
|
+
const visit = (node: Node): void => {
|
|
30
|
+
if (
|
|
31
|
+
isBinaryExpression(node) &&
|
|
32
|
+
node.operatorToken.kind === SyntaxKind.InKeyword &&
|
|
33
|
+
(isStringLiteral(node.left) || isNoSubstitutionTemplateLiteral(node.left))
|
|
34
|
+
) {
|
|
35
|
+
found.add(node.left.text);
|
|
36
|
+
}
|
|
37
|
+
node.forEachChild(visit);
|
|
38
|
+
};
|
|
39
|
+
for (const [path, file] of sourceFilesOf(project, targetRoot).byPath) {
|
|
40
|
+
if (!tests.has(path)) {
|
|
41
|
+
file.forEachChild(visit);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [...found].sort();
|
|
45
|
+
}
|
package/src/indexed-access.ts
CHANGED
|
@@ -7,40 +7,48 @@
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
isArrowFunction,
|
|
10
|
+
isBindingElement,
|
|
10
11
|
isCallExpression,
|
|
11
12
|
isClassDeclaration,
|
|
12
13
|
isClassExpression,
|
|
13
14
|
isFunctionDeclaration,
|
|
14
15
|
isFunctionExpression,
|
|
16
|
+
isFunctionTypeNode,
|
|
15
17
|
isIdentifier,
|
|
16
|
-
isNonNullExpression,
|
|
17
|
-
isParenthesizedExpression,
|
|
18
|
-
isNamedImports,
|
|
19
|
-
isNamespaceImport,
|
|
20
|
-
isImportDeclaration,
|
|
21
18
|
isIndexedAccessTypeNode,
|
|
22
19
|
isInterfaceDeclaration,
|
|
23
20
|
isLiteralTypeNode,
|
|
21
|
+
isMappedTypeNode,
|
|
24
22
|
isMethodDeclaration,
|
|
25
23
|
isMethodSignatureDeclaration,
|
|
26
24
|
isNewExpression,
|
|
27
25
|
isNoSubstitutionTemplateLiteral,
|
|
28
26
|
isNumericLiteral,
|
|
27
|
+
isObjectBindingPattern,
|
|
29
28
|
isParenthesizedTypeNode,
|
|
30
29
|
isPropertyAccessExpression,
|
|
30
|
+
isPropertySignatureDeclaration,
|
|
31
31
|
isStringLiteral,
|
|
32
32
|
isTypeAliasDeclaration,
|
|
33
|
+
isTypeOperatorNode,
|
|
33
34
|
isTypeReferenceNode,
|
|
34
35
|
isUnionTypeNode,
|
|
35
36
|
isVariableDeclaration,
|
|
37
|
+
SyntaxKind,
|
|
36
38
|
type CallExpression,
|
|
39
|
+
type Expression,
|
|
37
40
|
type NewExpression,
|
|
38
41
|
type Node,
|
|
39
42
|
type SourceFile,
|
|
40
43
|
type TypeNode,
|
|
41
44
|
type TypeReferenceNode,
|
|
42
45
|
} from "@typescript/native/unstable/ast";
|
|
43
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
SignatureKind,
|
|
48
|
+
SymbolFlags,
|
|
49
|
+
type Symbol as TSSymbol,
|
|
50
|
+
type Type,
|
|
51
|
+
} from "@typescript/native/unstable/sync";
|
|
44
52
|
import type { AliasChains } from "./alias-chain.ts";
|
|
45
53
|
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
46
54
|
import { renderPosition } from "./position.ts";
|
|
@@ -62,6 +70,7 @@ function isGeneric(node: Node): node is Generic {
|
|
|
62
70
|
isMethodSignatureDeclaration(node) ||
|
|
63
71
|
isArrowFunction(node) ||
|
|
64
72
|
isFunctionExpression(node) ||
|
|
73
|
+
isFunctionTypeNode(node) ||
|
|
65
74
|
isTypeAliasDeclaration(node) ||
|
|
66
75
|
isInterfaceDeclaration(node) ||
|
|
67
76
|
isClassDeclaration(node) ||
|
|
@@ -75,6 +84,35 @@ function isReceiver(node: Node): boolean {
|
|
|
75
84
|
return isInterfaceDeclaration(node) || isClassDeclaration(node) || isClassExpression(node);
|
|
76
85
|
}
|
|
77
86
|
|
|
87
|
+
/**
|
|
88
|
+
* The class or the interface one member function belongs to: a method's parent, or for a
|
|
89
|
+
* function type a property declares, the declaration that holds the property.
|
|
90
|
+
*/
|
|
91
|
+
function receiverOf(member: Node): Node | undefined {
|
|
92
|
+
const parent = member.parent as Node | undefined;
|
|
93
|
+
if (
|
|
94
|
+
isFunctionTypeNode(member) &&
|
|
95
|
+
parent !== undefined &&
|
|
96
|
+
isPropertySignatureDeclaration(parent)
|
|
97
|
+
) {
|
|
98
|
+
return parent.parent;
|
|
99
|
+
}
|
|
100
|
+
return parent;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Whether a member function of a generic receiver declares no type parameter of its own. */
|
|
104
|
+
function isReceiverMember(node: Node): node is Generic {
|
|
105
|
+
if (!(
|
|
106
|
+
isMethodDeclaration(node) ||
|
|
107
|
+
isMethodSignatureDeclaration(node) ||
|
|
108
|
+
isFunctionTypeNode(node)
|
|
109
|
+
)) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
const receiver = receiverOf(node);
|
|
113
|
+
return receiver !== undefined && isReceiver(receiver) && isGeneric(receiver);
|
|
114
|
+
}
|
|
115
|
+
|
|
78
116
|
function unwrappedType(node: TypeNode): TypeNode {
|
|
79
117
|
return isParenthesizedTypeNode(node) ? unwrappedType(node.type) : node;
|
|
80
118
|
}
|
|
@@ -122,7 +160,8 @@ function literalTypeNames(
|
|
|
122
160
|
type Side =
|
|
123
161
|
| { readonly kind: "parameter"; readonly index: number }
|
|
124
162
|
| { readonly kind: "written"; readonly node: TypeNode }
|
|
125
|
-
| { readonly kind: "receiver"; readonly index: number }
|
|
163
|
+
| { readonly kind: "receiver"; readonly index: number }
|
|
164
|
+
| { readonly kind: "argument"; readonly index: number };
|
|
126
165
|
|
|
127
166
|
/** An indexed access inside one generic whose index names a type parameter. */
|
|
128
167
|
interface Carried {
|
|
@@ -190,6 +229,35 @@ function namedParameter(
|
|
|
190
229
|
return undefined;
|
|
191
230
|
}
|
|
192
231
|
|
|
232
|
+
/**
|
|
233
|
+
* The constraint of the mapped type whose type parameter one type node names whole: a
|
|
234
|
+
* mapped type instantiates what it holds once per constituent of that constraint.
|
|
235
|
+
*/
|
|
236
|
+
function mappedConstraint(node: TypeNode): TypeNode | undefined {
|
|
237
|
+
const type = unwrappedType(node);
|
|
238
|
+
if (
|
|
239
|
+
!isTypeReferenceNode(type) ||
|
|
240
|
+
!isIdentifier(type.typeName) ||
|
|
241
|
+
type.typeArguments !== undefined
|
|
242
|
+
) {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
const spelled = type.typeName.text;
|
|
246
|
+
for (
|
|
247
|
+
let at = type.parent as Node | undefined;
|
|
248
|
+
at !== undefined;
|
|
249
|
+
at = at.parent as Node | undefined
|
|
250
|
+
) {
|
|
251
|
+
if (isMappedTypeNode(at)) {
|
|
252
|
+
const { name, constraint } = at.typeParameter;
|
|
253
|
+
if (isIdentifier(name) && name.text === spelled) {
|
|
254
|
+
return constraint;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
193
261
|
/** The name a site writes to reach one generic: its own, or its variable's. */
|
|
194
262
|
function calledName(generic: Generic): string | undefined {
|
|
195
263
|
const own = (generic as { readonly name?: Node }).name;
|
|
@@ -197,16 +265,24 @@ function calledName(generic: Generic): string | undefined {
|
|
|
197
265
|
return own.text;
|
|
198
266
|
}
|
|
199
267
|
const parent = generic.parent as Node | undefined;
|
|
200
|
-
return parent !== undefined &&
|
|
268
|
+
return parent !== undefined &&
|
|
269
|
+
(isVariableDeclaration(parent) || isPropertySignatureDeclaration(parent)) &&
|
|
270
|
+
isIdentifier(parent.name)
|
|
201
271
|
? parent.name.text
|
|
202
272
|
: undefined;
|
|
203
273
|
}
|
|
204
274
|
|
|
205
275
|
/** The generic a declaration a symbol names is: the function a variable is initialized with included. */
|
|
206
276
|
function genericOf(node: Node): Generic | undefined {
|
|
207
|
-
if (isGeneric(node)) {
|
|
277
|
+
if (isGeneric(node) || isReceiverMember(node)) {
|
|
208
278
|
return node;
|
|
209
279
|
}
|
|
280
|
+
if (isPropertySignatureDeclaration(node)) {
|
|
281
|
+
// The typings mark the annotation present; a signature written without one has none.
|
|
282
|
+
const declared = (node as { readonly type?: TypeNode }).type;
|
|
283
|
+
const type = declared === undefined ? undefined : unwrappedType(declared);
|
|
284
|
+
return type !== undefined && isFunctionTypeNode(type) ? genericOf(type) : undefined;
|
|
285
|
+
}
|
|
210
286
|
if (
|
|
211
287
|
isVariableDeclaration(node) &&
|
|
212
288
|
node.initializer !== undefined &&
|
|
@@ -217,32 +293,63 @@ function genericOf(node: Node): Generic | undefined {
|
|
|
217
293
|
return undefined;
|
|
218
294
|
}
|
|
219
295
|
|
|
296
|
+
/** The operand of each `keyof` one type node holds, through every type node it is built of. */
|
|
297
|
+
function keyofOperands(node: TypeNode): readonly TypeNode[] {
|
|
298
|
+
const found: TypeNode[] = [];
|
|
299
|
+
const visit = (at: Node): void => {
|
|
300
|
+
if (isTypeOperatorNode(at) && at.operator === SyntaxKind.KeyOfKeyword) {
|
|
301
|
+
found.push(at.type);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
at.forEachChild(visit);
|
|
305
|
+
};
|
|
306
|
+
visit(node);
|
|
307
|
+
return found;
|
|
308
|
+
}
|
|
309
|
+
|
|
220
310
|
/**
|
|
221
311
|
* Each indexed access inside one generic whose index names a type parameter of it, read
|
|
222
312
|
* from its own text: the object side is a type parameter of the same generic, one of the
|
|
223
|
-
* receiver it is a
|
|
313
|
+
* receiver it is a member of, or a type it writes. A type parameter whose constraint holds
|
|
314
|
+
* `keyof X` is an index into `X` as `X[K]` is, and so is a parameter whose declared type
|
|
315
|
+
* holds `keyof X`, indexed by the argument a call passes it.
|
|
224
316
|
*/
|
|
225
317
|
function carriedIn(generic: Generic): readonly Carried[] {
|
|
226
318
|
const found: Carried[] = [];
|
|
227
|
-
const receiver = generic
|
|
319
|
+
const receiver = receiverOf(generic);
|
|
228
320
|
const outer =
|
|
229
321
|
receiver !== undefined && isReceiver(receiver) && isGeneric(receiver) ? receiver : undefined;
|
|
322
|
+
const sideOf = (object: TypeNode): Side => {
|
|
323
|
+
const own = namedParameter(object, [generic]);
|
|
324
|
+
const fromReceiver =
|
|
325
|
+
own === undefined && outer !== undefined ? namedParameter(object, [outer]) : undefined;
|
|
326
|
+
return own !== undefined
|
|
327
|
+
? { kind: "parameter", index: own.index }
|
|
328
|
+
: fromReceiver !== undefined
|
|
329
|
+
? { kind: "receiver", index: fromReceiver.index }
|
|
330
|
+
: { kind: "written", node: object };
|
|
331
|
+
};
|
|
332
|
+
(generic.typeParameters ?? []).forEach((parameter, index) => {
|
|
333
|
+
const constraint = (parameter as { readonly constraint?: TypeNode }).constraint;
|
|
334
|
+
for (const object of constraint === undefined ? [] : keyofOperands(constraint)) {
|
|
335
|
+
found.push({ object: sideOf(object), index: { kind: "parameter", index } });
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
(generic.parameters ?? []).forEach((parameter, index) => {
|
|
339
|
+
for (const object of parameter.type === undefined ? [] : keyofOperands(parameter.type)) {
|
|
340
|
+
if (namedParameter(object, [generic]) === undefined) {
|
|
341
|
+
found.push({ object: sideOf(object), index: { kind: "argument", index } });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
});
|
|
230
345
|
const visit = (node: Node): void => {
|
|
231
346
|
if (isIndexedAccessTypeNode(node)) {
|
|
232
347
|
const index = namedParameter(node.indexType, [generic]);
|
|
233
348
|
if (index !== undefined) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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 } });
|
|
349
|
+
found.push({
|
|
350
|
+
object: sideOf(node.objectType),
|
|
351
|
+
index: { kind: "parameter", index: index.index },
|
|
352
|
+
});
|
|
246
353
|
}
|
|
247
354
|
}
|
|
248
355
|
if (node !== generic && isGeneric(node)) {
|
|
@@ -271,11 +378,8 @@ export function indexedAccessReferences<Brand>(
|
|
|
271
378
|
const carried = new Map<Generic, Carried[]>();
|
|
272
379
|
const names = new Set<string>();
|
|
273
380
|
const sites: Site[] = [];
|
|
274
|
-
/**
|
|
275
|
-
|
|
276
|
-
* method, which a dependency's generic may fix.
|
|
277
|
-
*/
|
|
278
|
-
const literalCalls: Site[] = [];
|
|
381
|
+
/** Calls passing an argument, which a generic declared outside the own files may fix. */
|
|
382
|
+
const argumentCalls: Site[] = [];
|
|
279
383
|
|
|
280
384
|
const carry = (generic: Generic, one: Carried): boolean => {
|
|
281
385
|
const known = carried.get(generic) ?? [];
|
|
@@ -299,7 +403,6 @@ export function indexedAccessReferences<Brand>(
|
|
|
299
403
|
continue;
|
|
300
404
|
}
|
|
301
405
|
const aliasTypes = new Set(typeQueryAliases(file).map((alias) => alias.type));
|
|
302
|
-
const imported = packageBindings(file);
|
|
303
406
|
const visit = (node: Node, from: string, enclosing: readonly Generic[]): void => {
|
|
304
407
|
const own = held.declarations.get(nodeKey(file, node));
|
|
305
408
|
const holder = own ?? from;
|
|
@@ -330,17 +433,8 @@ export function indexedAccessReferences<Brand>(
|
|
|
330
433
|
if (name !== undefined) {
|
|
331
434
|
const site = { file, from: holder, node, name, enclosing: inside };
|
|
332
435
|
sites.push(site);
|
|
333
|
-
|
|
334
|
-
(
|
|
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);
|
|
436
|
+
if (isCallExpression(node) && node.arguments.length > 0) {
|
|
437
|
+
argumentCalls.push(site);
|
|
344
438
|
}
|
|
345
439
|
}
|
|
346
440
|
}
|
|
@@ -371,6 +465,50 @@ export function indexedAccessReferences<Brand>(
|
|
|
371
465
|
});
|
|
372
466
|
};
|
|
373
467
|
|
|
468
|
+
/**
|
|
469
|
+
* The type a call's callee is read off: the receiver of a property access, the value an
|
|
470
|
+
* object binding pattern destructures the callee from, or the value whose member a
|
|
471
|
+
* variable holding the callee is initialized with.
|
|
472
|
+
*/
|
|
473
|
+
const receiverTypeOf = (callee: Expression): Type | undefined => {
|
|
474
|
+
if (isPropertyAccessExpression(callee)) {
|
|
475
|
+
const type = queries.typeAt(callee.expression);
|
|
476
|
+
const args = type === UNANSWERED || type === undefined ? [] : queries.typeArguments(type);
|
|
477
|
+
if (type !== UNANSWERED && type !== undefined && args !== UNANSWERED && args.length > 0) {
|
|
478
|
+
return type;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const name = isPropertyAccessExpression(callee) ? callee.name : callee;
|
|
482
|
+
const [named] = queries.symbolsAt([name]);
|
|
483
|
+
const symbol =
|
|
484
|
+
named === UNANSWERED || named === undefined || (named.flags & SymbolFlags.Alias) === 0
|
|
485
|
+
? named
|
|
486
|
+
: queries.aliased(named);
|
|
487
|
+
if (symbol === UNANSWERED || symbol === undefined) {
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
490
|
+
for (const handle of symbol.declarations) {
|
|
491
|
+
const node = project.declarationAt(handle)?.node;
|
|
492
|
+
const holder =
|
|
493
|
+
node !== undefined &&
|
|
494
|
+
isBindingElement(node) &&
|
|
495
|
+
isObjectBindingPattern(node.parent) &&
|
|
496
|
+
isVariableDeclaration(node.parent.parent)
|
|
497
|
+
? node.parent.parent.initializer
|
|
498
|
+
: node !== undefined &&
|
|
499
|
+
isVariableDeclaration(node) &&
|
|
500
|
+
node.initializer !== undefined &&
|
|
501
|
+
isPropertyAccessExpression(node.initializer)
|
|
502
|
+
? node.initializer.expression
|
|
503
|
+
: undefined;
|
|
504
|
+
const type = holder === undefined ? undefined : queries.typeAt(holder);
|
|
505
|
+
if (type !== UNANSWERED && type !== undefined) {
|
|
506
|
+
return type;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return undefined;
|
|
510
|
+
};
|
|
511
|
+
|
|
374
512
|
/** What one site fixes for one side of a carried access. */
|
|
375
513
|
const fixedAt = (site: Site, side: Side, generic: Generic): Fixed | undefined => {
|
|
376
514
|
if (side.kind === "written") {
|
|
@@ -379,23 +517,26 @@ export function indexedAccessReferences<Brand>(
|
|
|
379
517
|
const { node } = site;
|
|
380
518
|
if (side.kind === "receiver") {
|
|
381
519
|
const callee = isCallExpression(node) ? node.expression : undefined;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
const receiver = queries.typeAt(callee.expression);
|
|
386
|
-
if (receiver === UNANSWERED || receiver === undefined) {
|
|
520
|
+
const receiver = callee === undefined ? undefined : receiverTypeOf(callee);
|
|
521
|
+
if (receiver === undefined) {
|
|
387
522
|
return undefined;
|
|
388
523
|
}
|
|
389
524
|
const args = queries.typeArguments(receiver);
|
|
390
525
|
const type = args === UNANSWERED ? undefined : args[side.index];
|
|
391
526
|
return type === undefined ? undefined : { kind: "type", type };
|
|
392
527
|
}
|
|
528
|
+
if (side.kind === "argument") {
|
|
529
|
+
const argument = isTypeReferenceNode(node) ? undefined : node.arguments?.[side.index];
|
|
530
|
+
const type = argument === undefined ? undefined : queries.typeAt(argument);
|
|
531
|
+
return type === UNANSWERED || type === undefined ? undefined : { kind: "type", type };
|
|
532
|
+
}
|
|
393
533
|
const written = node.typeArguments?.[side.index];
|
|
394
534
|
if (written !== undefined) {
|
|
395
535
|
const passed = namedParameter(written, site.enclosing);
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
536
|
+
const mapped = passed === undefined ? mappedConstraint(written) : undefined;
|
|
537
|
+
return passed !== undefined
|
|
538
|
+
? { kind: "passed", generic: passed.generic, index: passed.index }
|
|
539
|
+
: { kind: "node", node: mapped ?? written };
|
|
399
540
|
}
|
|
400
541
|
if (isTypeReferenceNode(node)) {
|
|
401
542
|
return undefined;
|
|
@@ -500,46 +641,129 @@ export function indexedAccessReferences<Brand>(
|
|
|
500
641
|
});
|
|
501
642
|
}
|
|
502
643
|
|
|
503
|
-
// A generic
|
|
504
|
-
//
|
|
505
|
-
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
644
|
+
// A generic declared outside the project's own files carries what its own text indexes
|
|
645
|
+
// by its type parameters and its receiver's. Only a call passing an argument the compiler
|
|
646
|
+
// types as a literal can fix an index, so only such a call's signature is resolved.
|
|
647
|
+
const outside = argumentCalls.filter((site) => !resolvedGenerics.has(site));
|
|
648
|
+
const argumentNodes = argumentCalls.flatMap((site) => [
|
|
649
|
+
...(site.node as CallExpression).arguments,
|
|
650
|
+
]);
|
|
651
|
+
const argumentTypes = queries.typesAt(argumentNodes);
|
|
652
|
+
const literalArgument = new Set<Node>();
|
|
653
|
+
argumentTypes.forEach((type, at) => {
|
|
654
|
+
const node = argumentNodes[at];
|
|
655
|
+
if (
|
|
656
|
+
node !== undefined &&
|
|
657
|
+
type !== UNANSWERED &&
|
|
658
|
+
type !== undefined &&
|
|
659
|
+
(literalTypeNames(type, partsOf)?.length ?? 0) > 0
|
|
660
|
+
) {
|
|
661
|
+
literalArgument.add(node);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
515
664
|
const own = project.ownPaths();
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
665
|
+
for (const site of outside) {
|
|
666
|
+
if (!(site.node as CallExpression).arguments.some((one) => literalArgument.has(one))) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const resolved = queries.resolvedSignature(site.node);
|
|
670
|
+
const handle =
|
|
671
|
+
resolved === UNANSWERED || resolved === undefined ? undefined : resolved.declaration;
|
|
672
|
+
if (handle === undefined || own.has(handle.path)) {
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const node = project.declarationAt(handle)?.node;
|
|
676
|
+
const generic = node === undefined ? undefined : genericOf(node);
|
|
677
|
+
if (generic === undefined) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
for (const one of carriedIn(generic)) {
|
|
681
|
+
readAt(site, generic, one);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
for (const site of argumentCalls) {
|
|
685
|
+
if ((site.node as CallExpression).arguments.some((one) => literalArgument.has(one))) {
|
|
686
|
+
accesses.push(...instantiatedAccesses(site));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* The indexed accesses the generic signature a call instantiates holds, read through the
|
|
692
|
+
* checker: the signature its receiver's instantiation gives, so an object type its
|
|
693
|
+
* receiver fixes is concrete whatever declaration spells the receiver's type, a type
|
|
694
|
+
* literal a generic function returns among them. An index naming a type parameter is the
|
|
695
|
+
* literal type the call passes at each parameter of that type.
|
|
696
|
+
*/
|
|
697
|
+
function instantiatedAccesses(site: Site): readonly Access[] {
|
|
698
|
+
const resolved = queries.resolvedSignature(site.node);
|
|
699
|
+
const generic =
|
|
700
|
+
resolved === UNANSWERED || resolved === undefined ? undefined : queries.genericOf(resolved);
|
|
522
701
|
if (
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
702
|
+
resolved === UNANSWERED ||
|
|
703
|
+
resolved === undefined ||
|
|
704
|
+
generic === UNANSWERED ||
|
|
705
|
+
generic === undefined
|
|
526
706
|
) {
|
|
527
|
-
return;
|
|
707
|
+
return [];
|
|
528
708
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
709
|
+
const count = (site.node as CallExpression).arguments.length;
|
|
710
|
+
const parameters: Type[] = [];
|
|
711
|
+
for (let at = 0; at < count; at += 1) {
|
|
712
|
+
const type = queries.parameterType(generic, at);
|
|
713
|
+
if (type !== UNANSWERED && type !== undefined) {
|
|
714
|
+
parameters[at] = type;
|
|
532
715
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
716
|
+
}
|
|
717
|
+
const result = queries.returnTypeOf(generic);
|
|
718
|
+
const held: Type[] = [
|
|
719
|
+
...parameters,
|
|
720
|
+
...(result === UNANSWERED || result === undefined ? [] : [result]),
|
|
721
|
+
];
|
|
722
|
+
// A parameter of a callback and a type argument, a rest tuple's among them, hold the
|
|
723
|
+
// accesses one level down: `fn: (payload: M[K]) => void` and `...[M[K]]`.
|
|
724
|
+
const nested = held.flatMap((type) => [
|
|
725
|
+
...partsOf(type),
|
|
726
|
+
...partsOf(type).flatMap((part) => {
|
|
727
|
+
const args = queries.typeArguments(part);
|
|
728
|
+
const calls = queries.signaturesOf(part, SignatureKind.Call);
|
|
729
|
+
return [
|
|
730
|
+
...(args === UNANSWERED ? [] : args),
|
|
731
|
+
...(calls === UNANSWERED ? [] : calls).flatMap((signature) =>
|
|
732
|
+
signature.parameters.flatMap((_parameter, index) => {
|
|
733
|
+
const one = queries.parameterType(signature, index);
|
|
734
|
+
return one === UNANSWERED || one === undefined ? [] : [one];
|
|
735
|
+
}),
|
|
736
|
+
),
|
|
737
|
+
];
|
|
738
|
+
}),
|
|
739
|
+
]);
|
|
740
|
+
const found: Access[] = [];
|
|
741
|
+
for (const type of nested) {
|
|
742
|
+
const access = queries.indexedAccessOf(type);
|
|
743
|
+
if (access === UNANSWERED || access === undefined || access.object.isTypeParameter()) {
|
|
536
744
|
continue;
|
|
537
745
|
}
|
|
538
|
-
|
|
539
|
-
|
|
746
|
+
const names = access.index.isTypeParameter()
|
|
747
|
+
? parameters.flatMap((parameter, at) => {
|
|
748
|
+
if (parameter.id !== access.index.id) {
|
|
749
|
+
return [];
|
|
750
|
+
}
|
|
751
|
+
const passed = queries.typeAtPosition(resolved, at);
|
|
752
|
+
return passed === UNANSWERED ? [] : (literalTypeNames(passed, partsOf) ?? []);
|
|
753
|
+
})
|
|
754
|
+
: (literalTypeNames(access.index, partsOf) ?? []);
|
|
755
|
+
if (names.length > 0) {
|
|
756
|
+
found.push({
|
|
757
|
+
file: site.file,
|
|
758
|
+
from: site.from,
|
|
759
|
+
at: site.node,
|
|
760
|
+
object: access.object,
|
|
761
|
+
index: names,
|
|
762
|
+
});
|
|
540
763
|
}
|
|
541
764
|
}
|
|
542
|
-
|
|
765
|
+
return found;
|
|
766
|
+
}
|
|
543
767
|
|
|
544
768
|
const typeNodes = accesses.flatMap((one) => [
|
|
545
769
|
...("kind" in one.object ? [one.object] : []),
|
|
@@ -595,72 +819,6 @@ export function indexedAccessReferences<Brand>(
|
|
|
595
819
|
return found;
|
|
596
820
|
}
|
|
597
821
|
|
|
598
|
-
/**
|
|
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.
|
|
602
|
-
*/
|
|
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
|
-
) {
|
|
611
|
-
continue;
|
|
612
|
-
}
|
|
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));
|
|
622
|
-
}
|
|
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;
|
|
643
|
-
}
|
|
644
|
-
|
|
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;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
822
|
/** What makes two carried accesses one: the kind and position of each side, or its written node. */
|
|
665
823
|
function sideKey(one: Carried): readonly unknown[] {
|
|
666
824
|
const key = (side: Side): unknown =>
|