@cplieger/deadset-ts 8.0.0-dev.16 → 8.0.0-dev.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/alias-chain.js +0 -5
- package/dist/src/amd.js +81 -0
- package/dist/src/analysis.js +82 -91
- package/dist/src/augmentations.js +24 -25
- package/dist/src/bindings.js +382 -0
- package/dist/src/calls.js +279 -43
- package/dist/src/commands/tools.js +5 -9
- package/dist/src/component-files.js +3 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +6 -9
- package/dist/src/contextual-uses.js +1 -17
- package/dist/src/contract/config.generated.js +4 -1
- package/dist/src/contract/contract.generated.js +2 -2
- package/dist/src/contract/report.generated.js +6 -2
- package/dist/src/dependencies.js +25 -29
- package/dist/src/destructuring.js +1 -19
- package/dist/src/element-access.js +179 -164
- package/dist/src/entries/build-scripts.js +2 -2
- package/dist/src/entries/configuration-files.js +3 -7
- package/dist/src/entries/conventions.js +2 -2
- package/dist/src/entries/html-entries.js +7 -10
- package/dist/src/entries/mdx-entries.js +2 -2
- package/dist/src/entries/static-module.js +2 -1
- package/dist/src/exempt.js +0 -26
- package/dist/src/exposure.js +400 -0
- package/dist/src/facts.js +4 -4
- package/dist/src/findings/completion.js +44 -21
- package/dist/src/findings/emitters.js +22 -33
- package/dist/src/{implementations.js → findings/implementations.js} +3 -3
- package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
- package/dist/src/findings/intra-function.js +3 -3
- package/dist/src/findings/reads-and-writes.js +71 -15
- package/dist/src/findings/unused-declarations.js +2 -22
- package/dist/src/findings/visibility-narrowing.js +14 -20
- package/dist/src/framework-lifecycle.js +41 -14
- package/dist/src/import-bindings.js +73 -28
- package/dist/src/index/sites.js +557 -0
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/interface-satisfaction.js +1 -1
- package/dist/src/inventory.js +18 -0
- package/dist/src/keyof-comparisons.js +124 -103
- package/dist/src/{components.js → liveness/components.js} +1 -1
- package/dist/src/{graph.js → liveness/graph.js} +45 -20
- package/dist/src/liveness/matrix.js +99 -0
- package/dist/src/liveness/sweep.js +558 -0
- package/dist/src/load/expected-module.js +2 -2
- package/dist/src/load/specifiers.js +2 -4
- package/dist/src/load/synthesize.js +5 -0
- package/dist/src/load/workspace-resolution.js +2 -2
- package/dist/src/module-declarations.js +70 -85
- package/dist/src/overrides.js +134 -112
- package/dist/src/paths.js +46 -7
- package/dist/src/platform-calls.js +2 -2
- package/dist/src/platform-symbols.js +13 -19
- package/dist/src/query.js +46 -0
- package/dist/src/references/read-rule.js +1 -0
- package/dist/src/references/read-rules.js +48 -0
- package/dist/src/references/sink.js +237 -0
- package/dist/src/references/sources.js +450 -0
- package/dist/src/references.js +110 -1285
- package/dist/src/roots/configured-declarations.js +3 -0
- package/dist/src/roots/published-reach.js +4 -4
- package/dist/src/roots/root-set.js +1 -1
- package/dist/src/roots/unseen-loads.js +38 -10
- package/dist/src/serialization-contract.js +8 -3
- package/dist/src/stores.js +4 -2
- package/dist/src/symbol-keys.js +89 -83
- package/dist/src/tests/file-class.js +76 -0
- package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
- package/dist/src/{test-support.js → tests/support.js} +2 -11
- package/dist/src/type-parameter-reads.js +178 -139
- package/dist/src/unimplemented-callees.js +262 -242
- package/dist/src/verbs/analyze.js +3 -3
- package/dist/src/verbs/explain.js +75 -132
- package/dist/src/verbs/print-retained.js +3 -3
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +0 -7
- package/src/amd.ts +130 -0
- package/src/analysis.ts +125 -128
- package/src/augmentations.ts +42 -28
- package/src/bindings.ts +460 -0
- package/src/calls.ts +338 -46
- package/src/commands/tools.ts +5 -11
- package/src/component-files.ts +4 -2
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +8 -11
- package/src/contextual-uses.ts +2 -22
- package/src/contract/config.generated.ts +4 -1
- package/src/contract/contract.generated.ts +2 -2
- package/src/contract/report.generated.ts +6 -2
- package/src/dependencies.ts +27 -28
- package/src/destructuring.ts +1 -31
- package/src/element-access.ts +188 -182
- package/src/entries/build-scripts.ts +2 -2
- package/src/entries/configuration-files.ts +11 -8
- package/src/entries/conventions.ts +2 -1
- package/src/entries/html-entries.ts +15 -14
- package/src/entries/mdx-entries.ts +2 -2
- package/src/entries/static-module.ts +2 -1
- package/src/exempt.ts +31 -46
- package/src/exposure.ts +492 -0
- package/src/facts.ts +5 -3
- package/src/finding.ts +1 -1
- package/src/findings/boundary.ts +9 -0
- package/src/findings/completion.ts +48 -23
- package/src/findings/emitter.ts +1 -1
- package/src/findings/emitters.ts +23 -41
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
- package/src/findings/interfaces.ts +3 -3
- package/src/findings/intra-function.ts +3 -3
- package/src/findings/reads-and-writes.ts +99 -26
- package/src/findings/unused-declarations.ts +4 -28
- package/src/findings/visibility-narrowing.ts +25 -26
- package/src/framework-lifecycle.ts +42 -14
- package/src/import-bindings.ts +98 -29
- package/src/index/sites.ts +781 -0
- package/src/indexed-access.ts +570 -527
- package/src/interface-satisfaction.ts +2 -2
- package/src/inventory.ts +19 -0
- package/src/keyof-comparisons.ts +131 -116
- package/src/{components.ts → liveness/components.ts} +3 -3
- package/src/{graph.ts → liveness/graph.ts} +93 -27
- package/src/liveness/matrix.ts +162 -0
- package/src/liveness/sweep.ts +855 -0
- package/src/load/expected-module.ts +8 -2
- package/src/load/specifiers.ts +8 -5
- package/src/load/synthesize.ts +6 -0
- package/src/load/workspace-resolution.ts +9 -2
- package/src/module-declarations.ts +74 -98
- package/src/overrides.ts +162 -134
- package/src/paths.ts +54 -8
- package/src/platform-calls.ts +3 -3
- package/src/platform-symbols.ts +11 -27
- package/src/query.ts +81 -0
- package/src/references/read-rule.ts +60 -0
- package/src/references/read-rules.ts +55 -0
- package/src/references/sink.ts +314 -0
- package/src/references/sources.ts +632 -0
- package/src/references.ts +175 -1733
- package/src/report.ts +1 -1
- package/src/roots/configured-declarations.ts +3 -0
- package/src/roots/published-reach.ts +9 -4
- package/src/roots/root-set.ts +1 -1
- package/src/roots/unseen-loads.ts +49 -11
- package/src/serialization-contract.ts +8 -3
- package/src/stores.ts +15 -3
- package/src/symbol-keys.ts +102 -102
- package/src/tests/file-class.ts +135 -0
- package/src/{outside-tests.ts → tests/outside.ts} +16 -10
- package/src/{test-support.ts → tests/support.ts} +9 -20
- package/src/type-parameter-reads.ts +187 -158
- package/src/unimplemented-callees.ts +297 -280
- package/src/verbs/analyze.ts +3 -2
- package/src/verbs/explain.ts +83 -158
- package/src/verbs/print-retained.ts +3 -3
- package/dist/src/matrix.js +0 -273
- package/dist/src/sweep.js +0 -222
- package/src/matrix.ts +0 -463
- package/src/sweep.ts +0 -361
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
* that parameter, and a call of a method of a generic receiver fixes the receiver's by its
|
|
5
5
|
* type arguments. The type query of a type-query alias is no reference.
|
|
6
6
|
*/
|
|
7
|
-
import { isArrowFunction, isBindingElement, isCallExpression, isClassDeclaration, isClassExpression, isConstructorDeclaration, isConstructorTypeNode, isFunctionDeclaration, isFunctionExpression, isFunctionTypeNode, isIdentifier, isIndexedAccessTypeNode, isInterfaceDeclaration, isLiteralTypeNode, isMappedTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isParenthesizedTypeNode, isPropertyAccessExpression, isPropertyAssignment, isPropertySignatureDeclaration, isStringLiteral, isTypeAliasDeclaration, isTypeOperatorNode, isTypeReferenceNode, isUnionTypeNode, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
7
|
+
import { isArrowFunction, isBindingElement, isCallExpression, isClassDeclaration, isClassExpression, isConstructorDeclaration, isConstructorTypeNode, isFunctionDeclaration, isFunctionExpression, isFunctionTypeNode, isIdentifier, isIndexedAccessTypeNode, isInterfaceDeclaration, isLiteralTypeNode, isMappedTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isParenthesizedTypeNode, isPropertyAccessExpression, isPropertyAssignment, isPropertySignatureDeclaration, isSourceFile, isStringLiteral, isTypeAliasDeclaration, isTypeOperatorNode, isTypeReferenceNode, isUnionTypeNode, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
8
8
|
import { SignatureKind, SymbolFlags, } from "@typescript/native/sync";
|
|
9
9
|
import { nodeKey } from "./inventory.js";
|
|
10
|
-
import { renderPosition } from "./position.js";
|
|
11
10
|
import { UNANSWERED } from "./query.js";
|
|
12
11
|
import { typeQueryAliases } from "./type-query-alias.js";
|
|
13
12
|
function isGeneric(node) {
|
|
@@ -242,497 +241,543 @@ function carriedIn(generic) {
|
|
|
242
241
|
visit(generic);
|
|
243
242
|
return found;
|
|
244
243
|
}
|
|
244
|
+
/** The generics enclosing one node, itself included, outermost first. */
|
|
245
|
+
function enclosingGenerics(node) {
|
|
246
|
+
const found = [];
|
|
247
|
+
for (let at = node; !isSourceFile(at); at = at.parent) {
|
|
248
|
+
if (isGeneric(at)) {
|
|
249
|
+
found.push(at);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return found.reverse();
|
|
253
|
+
}
|
|
245
254
|
/**
|
|
246
|
-
* The
|
|
247
|
-
*
|
|
248
|
-
*
|
|
255
|
+
* The reads the indexed-access types of a side's files make of the members they name, each
|
|
256
|
+
* made by the declaration that holds the type, the reference or the call, or by the file. A
|
|
257
|
+
* question the checker leaves unanswered holds what its answer could have read.
|
|
249
258
|
*/
|
|
250
|
-
export
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
carried.set(generic, [...known, one]);
|
|
267
|
-
const name = calledName(generic);
|
|
268
|
-
if (name !== undefined) {
|
|
269
|
-
names.add(name);
|
|
270
|
-
}
|
|
271
|
-
return true;
|
|
272
|
-
};
|
|
273
|
-
for (const file of project.ownSourceFiles()) {
|
|
274
|
-
const fileId = held.declarations.get(nodeKey(file, file));
|
|
275
|
-
if (fileId === undefined) {
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
const aliasTypes = new Set(typeQueryAliases(file).map((alias) => alias.type));
|
|
279
|
-
const visit = (node, from, enclosing) => {
|
|
280
|
-
const own = held.declarations.get(nodeKey(file, node));
|
|
281
|
-
const holder = own ?? from;
|
|
282
|
-
const inside = isGeneric(node) ? [...enclosing, node] : enclosing;
|
|
283
|
-
if (isGeneric(node) || takesKeyof(node)) {
|
|
284
|
-
carriedIn(node).forEach((one) => carry(node, one));
|
|
259
|
+
export const indexedAccess = {
|
|
260
|
+
start() {
|
|
261
|
+
const accesses = [];
|
|
262
|
+
const carried = new Map();
|
|
263
|
+
const names = new Set();
|
|
264
|
+
const sites = [];
|
|
265
|
+
/** Accesses indexed by a type parameter whose constraint is no `keyof`: it requires each key it names. */
|
|
266
|
+
const constrained = [];
|
|
267
|
+
/** Calls passing an argument, which a generic declared outside the own files may fix. */
|
|
268
|
+
const argumentCalls = [];
|
|
269
|
+
const carry = (generic, one) => {
|
|
270
|
+
const known = carried.get(generic) ?? [];
|
|
271
|
+
const same = known.some((other) => JSON.stringify(sideKey(other)) === JSON.stringify(sideKey(one)));
|
|
272
|
+
if (same) {
|
|
273
|
+
return false;
|
|
285
274
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
? undefined
|
|
291
|
-
: parameter.generic.typeParameters?.[parameter.index]?.constraint;
|
|
292
|
-
if (literal !== undefined) {
|
|
293
|
-
accesses.push({ file, from: holder, at: node, object: node.objectType, index: literal });
|
|
294
|
-
}
|
|
295
|
-
else if (constraint !== undefined &&
|
|
296
|
-
keyofOperands(constraint).length === 0 &&
|
|
297
|
-
namedParameter(node.objectType, inside) === undefined) {
|
|
298
|
-
constrained.push({ file, from: holder, at: node, object: node.objectType, constraint });
|
|
299
|
-
}
|
|
275
|
+
carried.set(generic, [...known, one]);
|
|
276
|
+
const name = calledName(generic);
|
|
277
|
+
if (name !== undefined) {
|
|
278
|
+
names.add(name);
|
|
300
279
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
280
|
+
return true;
|
|
281
|
+
};
|
|
282
|
+
const aliasTypes = new Map();
|
|
283
|
+
const aliasTypesOf = (file) => {
|
|
284
|
+
let known = aliasTypes.get(file);
|
|
285
|
+
if (known === undefined) {
|
|
286
|
+
known = new Set(typeQueryAliases(file).map((alias) => alias.type));
|
|
287
|
+
aliasTypes.set(file, known);
|
|
305
288
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
289
|
+
return known;
|
|
290
|
+
};
|
|
291
|
+
return {
|
|
292
|
+
visit(node, { file, from }) {
|
|
293
|
+
const holder = from;
|
|
294
|
+
const inside = enclosingGenerics(node);
|
|
295
|
+
if (isGeneric(node) || takesKeyof(node)) {
|
|
296
|
+
carriedIn(node).forEach((one) => carry(node, one));
|
|
297
|
+
}
|
|
298
|
+
if (isIndexedAccessTypeNode(node) && !aliasTypesOf(file).has(node)) {
|
|
299
|
+
const literal = literalNames(node.indexType);
|
|
300
|
+
const parameter = literal === undefined ? namedParameter(node.indexType, inside) : undefined;
|
|
301
|
+
const constraint = parameter === undefined
|
|
302
|
+
? undefined
|
|
303
|
+
: parameter.generic.typeParameters?.[parameter.index]?.constraint;
|
|
304
|
+
if (literal !== undefined) {
|
|
305
|
+
accesses.push({
|
|
306
|
+
file,
|
|
307
|
+
from: holder,
|
|
308
|
+
at: node,
|
|
309
|
+
object: node.objectType,
|
|
310
|
+
index: literal,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
else if (constraint !== undefined &&
|
|
314
|
+
keyofOperands(constraint).length === 0 &&
|
|
315
|
+
namedParameter(node.objectType, inside) === undefined) {
|
|
316
|
+
constrained.push({ file, from: holder, at: node, object: node.objectType, constraint });
|
|
318
317
|
}
|
|
319
318
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
};
|
|
325
|
-
file.forEachChild((child) => {
|
|
326
|
-
visit(child, fileId, []);
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
/** The literal keys one constraint names, through the type aliases it references. */
|
|
330
|
-
const requiredNames = (node, seen) => {
|
|
331
|
-
const type = unwrappedType(node);
|
|
332
|
-
if (isUnionTypeNode(type)) {
|
|
333
|
-
return type.types.flatMap((one) => requiredNames(one, seen));
|
|
334
|
-
}
|
|
335
|
-
if (!isTypeReferenceNode(type) || type.typeArguments !== undefined) {
|
|
336
|
-
return literalNames(type) ?? [];
|
|
337
|
-
}
|
|
338
|
-
const [symbol] = queries.symbolsAt([type.typeName]);
|
|
339
|
-
if (symbol === UNANSWERED || symbol === undefined) {
|
|
340
|
-
return [];
|
|
341
|
-
}
|
|
342
|
-
return symbol.declarations.flatMap((handle) => {
|
|
343
|
-
const declared = project.declarationAt(handle)?.node;
|
|
344
|
-
return declared !== undefined &&
|
|
345
|
-
isTypeAliasDeclaration(declared) &&
|
|
346
|
-
declared.typeParameters === undefined &&
|
|
347
|
-
!seen.has(declared)
|
|
348
|
-
? requiredNames(declared.type, new Set([...seen, declared]))
|
|
349
|
-
: [];
|
|
350
|
-
});
|
|
351
|
-
};
|
|
352
|
-
for (const { constraint, ...access } of constrained) {
|
|
353
|
-
const index = requiredNames(constraint, new Set());
|
|
354
|
-
if (index.length > 0) {
|
|
355
|
-
accesses.push({ ...access, index });
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
function partsOf(type) {
|
|
359
|
-
const parts = queries.constituents(type);
|
|
360
|
-
return parts === UNANSWERED ? [type] : parts;
|
|
361
|
-
}
|
|
362
|
-
/** The generics one symbol declares, through the alias it may be. */
|
|
363
|
-
const genericsOf = (symbol) => {
|
|
364
|
-
const target = (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : queries.aliased(symbol);
|
|
365
|
-
if (target === UNANSWERED) {
|
|
366
|
-
return [];
|
|
367
|
-
}
|
|
368
|
-
return target.declarations.flatMap((handle) => {
|
|
369
|
-
const node = project.declarationAt(handle)?.node;
|
|
370
|
-
const generic = node === undefined ? undefined : genericOf(node);
|
|
371
|
-
return generic === undefined ? [] : [generic];
|
|
372
|
-
});
|
|
373
|
-
};
|
|
374
|
-
/**
|
|
375
|
-
* The type a call's callee is read off: the receiver of a property access, the value an
|
|
376
|
-
* object binding pattern destructures the callee from, or the value whose member a
|
|
377
|
-
* variable holding the callee is initialized with.
|
|
378
|
-
*/
|
|
379
|
-
const receiverTypeOf = (callee) => {
|
|
380
|
-
let operand;
|
|
381
|
-
if (isPropertyAccessExpression(callee)) {
|
|
382
|
-
const type = queries.typeAt(callee.expression);
|
|
383
|
-
operand = type === UNANSWERED ? undefined : type;
|
|
384
|
-
const args = operand === undefined ? [] : queries.typeArguments(operand);
|
|
385
|
-
if (operand !== undefined && args !== UNANSWERED && args.length > 0) {
|
|
386
|
-
return operand;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
const name = isPropertyAccessExpression(callee) ? callee.name : callee;
|
|
390
|
-
const [named] = queries.symbolsAt([name]);
|
|
391
|
-
const symbol = named === UNANSWERED || named === undefined || (named.flags & SymbolFlags.Alias) === 0
|
|
392
|
-
? named
|
|
393
|
-
: queries.aliased(named);
|
|
394
|
-
if (symbol === UNANSWERED || symbol === undefined) {
|
|
395
|
-
return operand;
|
|
396
|
-
}
|
|
397
|
-
for (const handle of symbol.declarations) {
|
|
398
|
-
const node = project.declarationAt(handle)?.node;
|
|
399
|
-
const holder = node !== undefined &&
|
|
400
|
-
isBindingElement(node) &&
|
|
401
|
-
isObjectBindingPattern(node.parent) &&
|
|
402
|
-
isVariableDeclaration(node.parent.parent)
|
|
403
|
-
? node.parent.parent.initializer
|
|
404
|
-
: node !== undefined &&
|
|
405
|
-
isVariableDeclaration(node) &&
|
|
406
|
-
node.initializer !== undefined &&
|
|
407
|
-
isPropertyAccessExpression(node.initializer)
|
|
408
|
-
? node.initializer.expression
|
|
409
|
-
: undefined;
|
|
410
|
-
const type = holder === undefined ? undefined : queries.typeAt(holder);
|
|
411
|
-
if (type !== UNANSWERED && type !== undefined) {
|
|
412
|
-
return type;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
return operand;
|
|
416
|
-
};
|
|
417
|
-
/**
|
|
418
|
-
* The instantiation of `owner` a receiver's type is, or the first one among the base
|
|
419
|
-
* types it extends, breadth first: a subclass fixes its base's type parameters, and
|
|
420
|
-
* `this` stands for its class.
|
|
421
|
-
*/
|
|
422
|
-
const instantiationOf = (type, owner) => {
|
|
423
|
-
const seen = new Set();
|
|
424
|
-
for (let queue = [type]; queue.length > 0;) {
|
|
425
|
-
const next = [];
|
|
426
|
-
for (const at of queue) {
|
|
427
|
-
if (seen.has(at.id)) {
|
|
428
|
-
continue;
|
|
319
|
+
if (isTypeReferenceNode(node) &&
|
|
320
|
+
node.typeArguments !== undefined &&
|
|
321
|
+
isIdentifier(node.typeName)) {
|
|
322
|
+
sites.push({ file, from: holder, node, name: node.typeName.text, enclosing: inside });
|
|
429
323
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
324
|
+
if (isCallExpression(node) || isNewExpression(node)) {
|
|
325
|
+
const callee = node.expression;
|
|
326
|
+
const name = isIdentifier(callee)
|
|
327
|
+
? callee.text
|
|
328
|
+
: isPropertyAccessExpression(callee) && isIdentifier(callee.name)
|
|
329
|
+
? callee.name.text
|
|
330
|
+
: undefined;
|
|
331
|
+
if (name !== undefined) {
|
|
332
|
+
const site = { file, from: holder, node, name, enclosing: inside };
|
|
333
|
+
sites.push(site);
|
|
334
|
+
if (argumentsOf(node).length > 0) {
|
|
335
|
+
argumentCalls.push(site);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
438
338
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
339
|
+
},
|
|
340
|
+
reads({ project, chains }) {
|
|
341
|
+
const queries = project.queries;
|
|
342
|
+
const found = [];
|
|
343
|
+
/**
|
|
344
|
+
* A site whose generic the checker cannot name may fix any generic of its name, whose
|
|
345
|
+
* accesses may then read any member.
|
|
346
|
+
*/
|
|
347
|
+
const holdSite = (site) => {
|
|
348
|
+
found.push({ kind: "held", from: site.from, members: true });
|
|
349
|
+
};
|
|
350
|
+
/** The literal keys one constraint names, through the type aliases it references. */
|
|
351
|
+
const requiredNames = (node, seen) => {
|
|
352
|
+
const type = unwrappedType(node);
|
|
353
|
+
if (isUnionTypeNode(type)) {
|
|
354
|
+
return type.types.flatMap((one) => requiredNames(one, seen));
|
|
355
|
+
}
|
|
356
|
+
if (!isTypeReferenceNode(type) || type.typeArguments !== undefined) {
|
|
357
|
+
return literalNames(type) ?? [];
|
|
358
|
+
}
|
|
359
|
+
const [symbol] = queries.symbolsAt([type.typeName]);
|
|
360
|
+
if (symbol === UNANSWERED || symbol === undefined) {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
return symbol.declarations.flatMap((handle) => {
|
|
364
|
+
const declared = project.declarationAt(handle)?.node;
|
|
365
|
+
return declared !== undefined &&
|
|
366
|
+
isTypeAliasDeclaration(declared) &&
|
|
367
|
+
declared.typeParameters === undefined &&
|
|
368
|
+
!seen.has(declared)
|
|
369
|
+
? requiredNames(declared.type, new Set([...seen, declared]))
|
|
370
|
+
: [];
|
|
371
|
+
});
|
|
372
|
+
};
|
|
373
|
+
for (const { constraint, ...access } of constrained) {
|
|
374
|
+
const index = requiredNames(constraint, new Set());
|
|
375
|
+
if (index.length > 0) {
|
|
376
|
+
accesses.push({ ...access, index });
|
|
377
|
+
}
|
|
444
378
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
return { kind: "node", node: side.node };
|
|
454
|
-
}
|
|
455
|
-
const { node } = site;
|
|
456
|
-
if (side.kind === "receiver") {
|
|
457
|
-
const callee = isCallExpression(node) ? node.expression : undefined;
|
|
458
|
-
const owner = receiverOf(generic);
|
|
459
|
-
const typed = callee === undefined ? undefined : receiverTypeOf(callee);
|
|
460
|
-
const receiver = typed === undefined || owner === undefined ? typed : instantiationOf(typed, owner);
|
|
461
|
-
if (receiver === undefined) {
|
|
462
|
-
return undefined;
|
|
463
|
-
}
|
|
464
|
-
const args = queries.typeArguments(receiver);
|
|
465
|
-
const type = args === UNANSWERED ? undefined : args[side.index];
|
|
466
|
-
return type === undefined ? undefined : { kind: "type", type };
|
|
467
|
-
}
|
|
468
|
-
if (side.kind === "argument") {
|
|
469
|
-
const argument = isTypeReferenceNode(node) ? undefined : node.arguments?.[side.index];
|
|
470
|
-
const type = argument === undefined ? undefined : queries.typeAt(argument);
|
|
471
|
-
return type === UNANSWERED || type === undefined ? undefined : { kind: "type", type };
|
|
472
|
-
}
|
|
473
|
-
const written = node.typeArguments?.[side.index];
|
|
474
|
-
if (written !== undefined) {
|
|
475
|
-
const passed = namedParameter(written, site.enclosing);
|
|
476
|
-
const mapped = passed === undefined ? mappedConstraint(written) : undefined;
|
|
477
|
-
return passed !== undefined
|
|
478
|
-
? { kind: "passed", generic: passed.generic, index: passed.index }
|
|
479
|
-
: { kind: "node", node: mapped ?? written };
|
|
480
|
-
}
|
|
481
|
-
if (isTypeReferenceNode(node)) {
|
|
482
|
-
return undefined;
|
|
483
|
-
}
|
|
484
|
-
const at = parametersOf(generic).findIndex((parameter) => {
|
|
485
|
-
const type = parameter.type === undefined ? undefined : unwrappedType(parameter.type);
|
|
486
|
-
return (type !== undefined &&
|
|
487
|
-
isTypeReferenceNode(type) &&
|
|
488
|
-
isIdentifier(type.typeName) &&
|
|
489
|
-
parameterAt(generic, type.typeName.text) === side.index);
|
|
490
|
-
});
|
|
491
|
-
if (at < 0) {
|
|
492
|
-
return undefined;
|
|
493
|
-
}
|
|
494
|
-
const resolved = queries.resolvedSignature(node);
|
|
495
|
-
if (resolved === UNANSWERED || resolved === undefined) {
|
|
496
|
-
return undefined;
|
|
497
|
-
}
|
|
498
|
-
const type = queries.typeAtPosition(resolved, at);
|
|
499
|
-
return type === UNANSWERED ? undefined : { kind: "type", type };
|
|
500
|
-
};
|
|
501
|
-
/** Reads one carried access at one site: an access where both sides are fixed, a carry where one is passed on. */
|
|
502
|
-
const readAt = (site, generic, one) => {
|
|
503
|
-
const object = fixedAt(site, one.object, generic);
|
|
504
|
-
const index = object === undefined ? undefined : fixedAt(site, one.index, generic);
|
|
505
|
-
if (object === undefined || index === undefined) {
|
|
506
|
-
return false;
|
|
507
|
-
}
|
|
508
|
-
const passed = [object, index].find((fixed) => fixed.kind === "passed");
|
|
509
|
-
if (passed !== undefined) {
|
|
510
|
-
const side = (fixed) => fixed.kind === "passed"
|
|
511
|
-
? fixed.generic === passed.generic
|
|
512
|
-
? { kind: "parameter", index: fixed.index }
|
|
513
|
-
: undefined
|
|
514
|
-
: fixed.kind === "node"
|
|
515
|
-
? { kind: "written", node: fixed.node }
|
|
516
|
-
: undefined;
|
|
517
|
-
const objectSide = side(object);
|
|
518
|
-
const indexSide = side(index);
|
|
519
|
-
return objectSide !== undefined && indexSide !== undefined
|
|
520
|
-
? carry(passed.generic, { object: objectSide, index: indexSide })
|
|
521
|
-
: false;
|
|
522
|
-
}
|
|
523
|
-
const objectAt = concrete(object);
|
|
524
|
-
const indexAt = concrete(index);
|
|
525
|
-
if (objectAt === undefined || indexAt === undefined) {
|
|
526
|
-
return false;
|
|
527
|
-
}
|
|
528
|
-
accesses.push({
|
|
529
|
-
file: site.file,
|
|
530
|
-
from: site.from,
|
|
531
|
-
at: site.node,
|
|
532
|
-
object: objectAt,
|
|
533
|
-
index: "kind" in indexAt ? (literalNames(indexAt) ?? indexAt) : indexAt,
|
|
534
|
-
});
|
|
535
|
-
return false;
|
|
536
|
-
};
|
|
537
|
-
// A site passing a type parameter on carries the access to its own generic, so the
|
|
538
|
-
// sites are read again until no generic gains an access.
|
|
539
|
-
const resolvedGenerics = new Map();
|
|
540
|
-
const read = new Set();
|
|
541
|
-
for (let changed = true; changed;) {
|
|
542
|
-
changed = false;
|
|
543
|
-
const pending = sites.filter((site) => names.has(site.name) && !resolvedGenerics.has(site));
|
|
544
|
-
const typeSites = pending.filter((site) => isTypeReferenceNode(site.node));
|
|
545
|
-
const symbols = typeSites.length === 0
|
|
546
|
-
? []
|
|
547
|
-
: queries.symbolsAt(typeSites.map((site) => site.node.typeName));
|
|
548
|
-
typeSites.forEach((site, at) => {
|
|
549
|
-
const symbol = symbols[at];
|
|
550
|
-
resolvedGenerics.set(site, symbol === UNANSWERED || symbol === undefined ? [] : genericsOf(symbol));
|
|
551
|
-
});
|
|
552
|
-
for (const site of pending.filter((one) => !isTypeReferenceNode(one.node))) {
|
|
553
|
-
const resolved = queries.resolvedSignature(site.node);
|
|
554
|
-
const declaration = resolved === UNANSWERED || resolved?.declaration === undefined
|
|
555
|
-
? undefined
|
|
556
|
-
: project.declarationAt(resolved.declaration)?.node;
|
|
557
|
-
const generic = declaration === undefined ? undefined : genericOf(declaration);
|
|
558
|
-
resolvedGenerics.set(site, generic === undefined ? [] : [generic]);
|
|
559
|
-
}
|
|
560
|
-
sites.forEach((site, siteAt) => {
|
|
561
|
-
for (const generic of resolvedGenerics.get(site) ?? []) {
|
|
562
|
-
(carried.get(generic) ?? []).forEach((one, at) => {
|
|
563
|
-
const key = `${String(siteAt)}:${String(at)}:${nodeKey(generic.getSourceFile(), generic)}`;
|
|
564
|
-
if (read.has(key)) {
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
read.add(key);
|
|
568
|
-
changed = readAt(site, generic, one) || changed;
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
// A generic declared outside the project's own files carries what its own text indexes
|
|
574
|
-
// by its type parameters and its receiver's. Only a call passing an argument the compiler
|
|
575
|
-
// types as a literal can fix an index, so only such a call's signature is resolved.
|
|
576
|
-
const outside = argumentCalls.filter((site) => !resolvedGenerics.has(site));
|
|
577
|
-
const argumentNodes = argumentCalls.flatMap((site) => argumentsOf(site.node));
|
|
578
|
-
const argumentTypes = queries.typesAt(argumentNodes);
|
|
579
|
-
const literalArgument = new Set();
|
|
580
|
-
argumentTypes.forEach((type, at) => {
|
|
581
|
-
const node = argumentNodes[at];
|
|
582
|
-
if (node !== undefined &&
|
|
583
|
-
type !== UNANSWERED &&
|
|
584
|
-
type !== undefined &&
|
|
585
|
-
literalTypeNames(type, partsOf).length > 0) {
|
|
586
|
-
literalArgument.add(node);
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
const own = project.ownPaths();
|
|
590
|
-
for (const site of outside) {
|
|
591
|
-
if (!argumentsOf(site.node).some((one) => literalArgument.has(one))) {
|
|
592
|
-
continue;
|
|
593
|
-
}
|
|
594
|
-
const resolved = queries.resolvedSignature(site.node);
|
|
595
|
-
const handle = resolved === UNANSWERED || resolved === undefined ? undefined : resolved.declaration;
|
|
596
|
-
if (handle === undefined || own.has(handle.path)) {
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
const node = project.declarationAt(handle)?.node;
|
|
600
|
-
const generic = node === undefined ? undefined : genericOf(node);
|
|
601
|
-
if (generic === undefined) {
|
|
602
|
-
continue;
|
|
603
|
-
}
|
|
604
|
-
for (const one of carriedIn(generic)) {
|
|
605
|
-
readAt(site, generic, one);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
for (const site of argumentCalls) {
|
|
609
|
-
if (argumentsOf(site.node).some((one) => literalArgument.has(one))) {
|
|
610
|
-
accesses.push(...instantiatedAccesses(site));
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
/**
|
|
614
|
-
* The indexed accesses the generic signature a call instantiates holds, read through the
|
|
615
|
-
* checker: the signature its receiver's instantiation gives, so an object type its
|
|
616
|
-
* receiver fixes is concrete whatever declaration spells the receiver's type, a type
|
|
617
|
-
* literal a generic function returns among them. An index naming a type parameter is the
|
|
618
|
-
* literal type the call passes at each parameter of that type.
|
|
619
|
-
*/
|
|
620
|
-
function instantiatedAccesses(site) {
|
|
621
|
-
const resolved = queries.resolvedSignature(site.node);
|
|
622
|
-
const generic = resolved === UNANSWERED || resolved === undefined ? undefined : queries.genericOf(resolved);
|
|
623
|
-
if (resolved === UNANSWERED ||
|
|
624
|
-
resolved === undefined ||
|
|
625
|
-
generic === UNANSWERED ||
|
|
626
|
-
generic === undefined) {
|
|
627
|
-
return [];
|
|
628
|
-
}
|
|
629
|
-
const count = argumentsOf(site.node).length;
|
|
630
|
-
const parameters = [];
|
|
631
|
-
for (let at = 0; at < count; at += 1) {
|
|
632
|
-
const type = queries.parameterType(generic, at);
|
|
633
|
-
if (type !== UNANSWERED && type !== undefined) {
|
|
634
|
-
parameters[at] = type;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
const result = queries.returnTypeOf(generic);
|
|
638
|
-
const held = [
|
|
639
|
-
...parameters,
|
|
640
|
-
...(result === UNANSWERED || result === undefined ? [] : [result]),
|
|
641
|
-
];
|
|
642
|
-
// A parameter of a callback and a type argument, a rest tuple's among them, hold the
|
|
643
|
-
// accesses one level down: `fn: (payload: M[K]) => void` and `...[M[K]]`.
|
|
644
|
-
const nested = held.flatMap((type) => [
|
|
645
|
-
...partsOf(type),
|
|
646
|
-
...partsOf(type).flatMap((part) => {
|
|
647
|
-
const args = queries.typeArguments(part);
|
|
648
|
-
const calls = queries.signaturesOf(part, SignatureKind.Call);
|
|
649
|
-
return [
|
|
650
|
-
...(args === UNANSWERED ? [] : args),
|
|
651
|
-
...(calls === UNANSWERED ? [] : calls).flatMap((signature) => signature.parameters.flatMap((_parameter, index) => {
|
|
652
|
-
const one = queries.parameterType(signature, index);
|
|
653
|
-
return one === UNANSWERED || one === undefined ? [] : [one];
|
|
654
|
-
})),
|
|
655
|
-
];
|
|
656
|
-
}),
|
|
657
|
-
]);
|
|
658
|
-
const found = [];
|
|
659
|
-
for (const type of nested) {
|
|
660
|
-
const access = queries.indexedAccessOf(type);
|
|
661
|
-
if (access === UNANSWERED || access === undefined || access.object.isTypeParameter()) {
|
|
662
|
-
continue;
|
|
663
|
-
}
|
|
664
|
-
const names = access.index.isTypeParameter()
|
|
665
|
-
? parameters.flatMap((parameter, at) => {
|
|
666
|
-
if (!partsOf(parameter).some((part) => part.id === access.index.id)) {
|
|
379
|
+
function partsOf(type) {
|
|
380
|
+
const parts = queries.constituents(type);
|
|
381
|
+
return parts === UNANSWERED ? [type] : parts;
|
|
382
|
+
}
|
|
383
|
+
/** The generics one symbol declares, through the alias it may be. */
|
|
384
|
+
const genericsOf = (symbol) => {
|
|
385
|
+
const target = (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : queries.aliased(symbol);
|
|
386
|
+
if (target === UNANSWERED) {
|
|
667
387
|
return [];
|
|
668
388
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
389
|
+
return target.declarations.flatMap((handle) => {
|
|
390
|
+
const node = project.declarationAt(handle)?.node;
|
|
391
|
+
const generic = node === undefined ? undefined : genericOf(node);
|
|
392
|
+
return generic === undefined ? [] : [generic];
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
/**
|
|
396
|
+
* The type a call's callee is read off: the receiver of a property access, the value an
|
|
397
|
+
* object binding pattern destructures the callee from, or the value whose member a
|
|
398
|
+
* variable holding the callee is initialized with.
|
|
399
|
+
*/
|
|
400
|
+
const receiverTypeOf = (callee) => {
|
|
401
|
+
let operand;
|
|
402
|
+
if (isPropertyAccessExpression(callee)) {
|
|
403
|
+
const type = queries.typeAt(callee.expression);
|
|
404
|
+
operand = type === UNANSWERED ? undefined : type;
|
|
405
|
+
const args = operand === undefined ? [] : queries.typeArguments(operand);
|
|
406
|
+
if (operand !== undefined && args !== UNANSWERED && args.length > 0) {
|
|
407
|
+
return operand;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const name = isPropertyAccessExpression(callee) ? callee.name : callee;
|
|
411
|
+
const [named] = queries.symbolsAt([name]);
|
|
412
|
+
const symbol = named === UNANSWERED || named === undefined || (named.flags & SymbolFlags.Alias) === 0
|
|
413
|
+
? named
|
|
414
|
+
: queries.aliased(named);
|
|
415
|
+
if (symbol === UNANSWERED || symbol === undefined) {
|
|
416
|
+
return operand;
|
|
417
|
+
}
|
|
418
|
+
for (const handle of symbol.declarations) {
|
|
419
|
+
const node = project.declarationAt(handle)?.node;
|
|
420
|
+
const holder = node !== undefined &&
|
|
421
|
+
isBindingElement(node) &&
|
|
422
|
+
isObjectBindingPattern(node.parent) &&
|
|
423
|
+
isVariableDeclaration(node.parent.parent)
|
|
424
|
+
? node.parent.parent.initializer
|
|
425
|
+
: node !== undefined &&
|
|
426
|
+
isVariableDeclaration(node) &&
|
|
427
|
+
node.initializer !== undefined &&
|
|
428
|
+
isPropertyAccessExpression(node.initializer)
|
|
429
|
+
? node.initializer.expression
|
|
430
|
+
: undefined;
|
|
431
|
+
const type = holder === undefined ? undefined : queries.typeAt(holder);
|
|
432
|
+
if (type !== UNANSWERED && type !== undefined) {
|
|
433
|
+
return type;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return operand;
|
|
437
|
+
};
|
|
438
|
+
/**
|
|
439
|
+
* The instantiation of `owner` a receiver's type is, or the first one among the base
|
|
440
|
+
* types it extends, breadth first: a subclass fixes its base's type parameters, and
|
|
441
|
+
* `this` stands for its class.
|
|
442
|
+
*/
|
|
443
|
+
const instantiationOf = (type, owner) => {
|
|
444
|
+
const seen = new Set();
|
|
445
|
+
for (let queue = [type]; queue.length > 0;) {
|
|
446
|
+
const next = [];
|
|
447
|
+
for (const at of queue) {
|
|
448
|
+
if (seen.has(at.id)) {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
seen.add(at.id);
|
|
452
|
+
const target = at.isTypeReference() ? queries.targetOf(at) : at;
|
|
453
|
+
const symbol = target === UNANSWERED ? undefined : queries.symbolOfType(target);
|
|
454
|
+
const declares = symbol !== undefined &&
|
|
455
|
+
symbol !== UNANSWERED &&
|
|
456
|
+
symbol.declarations.some((handle) => project.declarationAt(handle)?.node === owner);
|
|
457
|
+
if (declares) {
|
|
458
|
+
return at;
|
|
459
|
+
}
|
|
460
|
+
const constraint = at.isTypeParameter() ? queries.constraintOf(at) : undefined;
|
|
461
|
+
const bases = at.isTypeParameter() ? [] : queries.baseTypes(at);
|
|
462
|
+
next.push(...(bases === UNANSWERED ? [] : bases));
|
|
463
|
+
if (constraint !== undefined && constraint !== UNANSWERED) {
|
|
464
|
+
next.push(constraint);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
queue = next;
|
|
468
|
+
}
|
|
469
|
+
return type;
|
|
470
|
+
};
|
|
471
|
+
/** What one site fixes for one side of a carried access. */
|
|
472
|
+
const fixedAt = (site, side, generic) => {
|
|
473
|
+
if (side.kind === "written") {
|
|
474
|
+
return { kind: "node", node: side.node };
|
|
475
|
+
}
|
|
476
|
+
const { node } = site;
|
|
477
|
+
if (side.kind === "receiver") {
|
|
478
|
+
const callee = isCallExpression(node) ? node.expression : undefined;
|
|
479
|
+
const owner = receiverOf(generic);
|
|
480
|
+
const typed = callee === undefined ? undefined : receiverTypeOf(callee);
|
|
481
|
+
const receiver = typed === undefined || owner === undefined ? typed : instantiationOf(typed, owner);
|
|
482
|
+
if (receiver === undefined) {
|
|
483
|
+
return undefined;
|
|
484
|
+
}
|
|
485
|
+
const args = queries.typeArguments(receiver);
|
|
486
|
+
const type = args === UNANSWERED ? undefined : args[side.index];
|
|
487
|
+
return type === undefined ? undefined : { kind: "type", type };
|
|
488
|
+
}
|
|
489
|
+
if (side.kind === "argument") {
|
|
490
|
+
const argument = isTypeReferenceNode(node) ? undefined : node.arguments?.[side.index];
|
|
491
|
+
const type = argument === undefined ? undefined : queries.typeAt(argument);
|
|
492
|
+
return type === UNANSWERED || type === undefined ? undefined : { kind: "type", type };
|
|
493
|
+
}
|
|
494
|
+
const written = node.typeArguments?.[side.index];
|
|
495
|
+
if (written !== undefined) {
|
|
496
|
+
const passed = namedParameter(written, site.enclosing);
|
|
497
|
+
const mapped = passed === undefined ? mappedConstraint(written) : undefined;
|
|
498
|
+
return passed !== undefined
|
|
499
|
+
? { kind: "passed", generic: passed.generic, index: passed.index }
|
|
500
|
+
: { kind: "node", node: mapped ?? written };
|
|
501
|
+
}
|
|
502
|
+
if (isTypeReferenceNode(node)) {
|
|
503
|
+
return undefined;
|
|
504
|
+
}
|
|
505
|
+
const at = parametersOf(generic).findIndex((parameter) => {
|
|
506
|
+
const type = parameter.type === undefined ? undefined : unwrappedType(parameter.type);
|
|
507
|
+
return (type !== undefined &&
|
|
508
|
+
isTypeReferenceNode(type) &&
|
|
509
|
+
isIdentifier(type.typeName) &&
|
|
510
|
+
parameterAt(generic, type.typeName.text) === side.index);
|
|
511
|
+
});
|
|
512
|
+
if (at < 0) {
|
|
513
|
+
return undefined;
|
|
514
|
+
}
|
|
515
|
+
const resolved = queries.resolvedSignature(node);
|
|
516
|
+
if (resolved === UNANSWERED || resolved === undefined) {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
const type = queries.typeAtPosition(resolved, at);
|
|
520
|
+
return type === UNANSWERED ? undefined : { kind: "type", type };
|
|
521
|
+
};
|
|
522
|
+
/** Reads one carried access at one site: an access where both sides are fixed, a carry where one is passed on. */
|
|
523
|
+
const readAt = (site, generic, one) => {
|
|
524
|
+
const object = fixedAt(site, one.object, generic);
|
|
525
|
+
const index = object === undefined ? undefined : fixedAt(site, one.index, generic);
|
|
526
|
+
if (object === undefined || index === undefined) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
const passed = [object, index].find((fixed) => fixed.kind === "passed");
|
|
530
|
+
if (passed !== undefined) {
|
|
531
|
+
const side = (fixed) => fixed.kind === "passed"
|
|
532
|
+
? fixed.generic === passed.generic
|
|
533
|
+
? { kind: "parameter", index: fixed.index }
|
|
534
|
+
: undefined
|
|
535
|
+
: fixed.kind === "node"
|
|
536
|
+
? { kind: "written", node: fixed.node }
|
|
537
|
+
: undefined;
|
|
538
|
+
const objectSide = side(object);
|
|
539
|
+
const indexSide = side(index);
|
|
540
|
+
return objectSide !== undefined && indexSide !== undefined
|
|
541
|
+
? carry(passed.generic, { object: objectSide, index: indexSide })
|
|
542
|
+
: false;
|
|
543
|
+
}
|
|
544
|
+
const objectAt = concrete(object);
|
|
545
|
+
const indexAt = concrete(index);
|
|
546
|
+
if (objectAt === undefined || indexAt === undefined) {
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
accesses.push({
|
|
550
|
+
file: site.file,
|
|
551
|
+
from: site.from,
|
|
552
|
+
at: site.node,
|
|
553
|
+
object: objectAt,
|
|
554
|
+
index: "kind" in indexAt ? (literalNames(indexAt) ?? indexAt) : indexAt,
|
|
555
|
+
});
|
|
556
|
+
return false;
|
|
557
|
+
};
|
|
558
|
+
// A site passing a type parameter on carries the access to its own generic, so the
|
|
559
|
+
// sites are read again until no generic gains an access.
|
|
560
|
+
const resolvedGenerics = new Map();
|
|
561
|
+
const read = new Set();
|
|
562
|
+
for (let changed = true; changed;) {
|
|
563
|
+
changed = false;
|
|
564
|
+
const pending = sites.filter((site) => names.has(site.name) && !resolvedGenerics.has(site));
|
|
565
|
+
const typeSites = pending.filter((site) => isTypeReferenceNode(site.node));
|
|
566
|
+
const symbols = typeSites.length === 0
|
|
567
|
+
? []
|
|
568
|
+
: queries.symbolsAt(typeSites.map((site) => site.node.typeName));
|
|
569
|
+
typeSites.forEach((site, at) => {
|
|
570
|
+
const symbol = symbols[at];
|
|
571
|
+
if (symbol === UNANSWERED) {
|
|
572
|
+
holdSite(site);
|
|
573
|
+
}
|
|
574
|
+
resolvedGenerics.set(site, symbol === UNANSWERED || symbol === undefined ? [] : genericsOf(symbol));
|
|
575
|
+
});
|
|
576
|
+
for (const site of pending.filter((one) => !isTypeReferenceNode(one.node))) {
|
|
577
|
+
const resolved = queries.resolvedSignature(site.node);
|
|
578
|
+
if (resolved === UNANSWERED) {
|
|
579
|
+
holdSite(site);
|
|
580
|
+
}
|
|
581
|
+
const declaration = resolved === UNANSWERED || resolved?.declaration === undefined
|
|
582
|
+
? undefined
|
|
583
|
+
: project.declarationAt(resolved.declaration)?.node;
|
|
584
|
+
const generic = declaration === undefined ? undefined : genericOf(declaration);
|
|
585
|
+
resolvedGenerics.set(site, generic === undefined ? [] : [generic]);
|
|
586
|
+
}
|
|
587
|
+
sites.forEach((site, siteAt) => {
|
|
588
|
+
for (const generic of resolvedGenerics.get(site) ?? []) {
|
|
589
|
+
(carried.get(generic) ?? []).forEach((one, at) => {
|
|
590
|
+
const key = `${String(siteAt)}:${String(at)}:${nodeKey(generic.getSourceFile(), generic)}`;
|
|
591
|
+
if (read.has(key)) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
read.add(key);
|
|
595
|
+
changed = readAt(site, generic, one) || changed;
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
// A generic declared outside the project's own files carries what its own text indexes
|
|
601
|
+
// by its type parameters and its receiver's. Only a call passing an argument the compiler
|
|
602
|
+
// types as a literal can fix an index, so only such a call's signature is resolved.
|
|
603
|
+
const outside = argumentCalls.filter((site) => !resolvedGenerics.has(site));
|
|
604
|
+
const argumentNodes = argumentCalls.flatMap((site) => argumentsOf(site.node));
|
|
605
|
+
const argumentTypes = queries.typesAt(argumentNodes);
|
|
606
|
+
const literalArgument = new Set();
|
|
607
|
+
argumentTypes.forEach((type, at) => {
|
|
608
|
+
const node = argumentNodes[at];
|
|
609
|
+
if (node !== undefined &&
|
|
610
|
+
type !== UNANSWERED &&
|
|
611
|
+
type !== undefined &&
|
|
612
|
+
literalTypeNames(type, partsOf).length > 0) {
|
|
613
|
+
literalArgument.add(node);
|
|
614
|
+
}
|
|
680
615
|
});
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
const type = asType(access.object);
|
|
700
|
-
const indexType = Array.isArray(access.index)
|
|
701
|
-
? undefined
|
|
702
|
-
: asType(access.index);
|
|
703
|
-
const accessed = Array.isArray(access.index)
|
|
704
|
-
? access.index
|
|
705
|
-
: indexType === undefined
|
|
706
|
-
? undefined
|
|
707
|
-
: literalTypeNames(indexType, partsOf);
|
|
708
|
-
if (type === undefined || accessed === undefined) {
|
|
709
|
-
continue;
|
|
710
|
-
}
|
|
711
|
-
const position = renderPosition(access.file, root, access.at.getStart());
|
|
712
|
-
for (const part of partsOf(type)) {
|
|
713
|
-
const properties = queries.propertiesOf(part);
|
|
714
|
-
if (properties === UNANSWERED) {
|
|
715
|
-
continue;
|
|
716
|
-
}
|
|
717
|
-
for (const property of properties) {
|
|
718
|
-
if (!accessed.includes(property.escapedName)) {
|
|
719
|
-
continue;
|
|
616
|
+
const own = project.ownPaths();
|
|
617
|
+
for (const site of outside) {
|
|
618
|
+
if (!argumentsOf(site.node).some((one) => literalArgument.has(one))) {
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
const resolved = queries.resolvedSignature(site.node);
|
|
622
|
+
const handle = resolved === UNANSWERED || resolved === undefined ? undefined : resolved.declaration;
|
|
623
|
+
if (handle === undefined || own.has(handle.path)) {
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
const node = project.declarationAt(handle)?.node;
|
|
627
|
+
const generic = node === undefined ? undefined : genericOf(node);
|
|
628
|
+
if (generic === undefined) {
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
for (const one of carriedIn(generic)) {
|
|
632
|
+
readAt(site, generic, one);
|
|
633
|
+
}
|
|
720
634
|
}
|
|
721
|
-
for (const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
position,
|
|
726
|
-
use: "read",
|
|
727
|
-
resolution: "batch",
|
|
728
|
-
test: testFiles.has(position.path),
|
|
729
|
-
});
|
|
635
|
+
for (const site of argumentCalls) {
|
|
636
|
+
if (argumentsOf(site.node).some((one) => literalArgument.has(one))) {
|
|
637
|
+
accesses.push(...instantiatedAccesses(site));
|
|
638
|
+
}
|
|
730
639
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
640
|
+
/**
|
|
641
|
+
* The indexed accesses the generic signature a call instantiates holds, read through the
|
|
642
|
+
* checker: the signature its receiver's instantiation gives, so an object type its
|
|
643
|
+
* receiver fixes is concrete whatever declaration spells the receiver's type, a type
|
|
644
|
+
* literal a generic function returns among them. An index naming a type parameter is the
|
|
645
|
+
* literal type the call passes at each parameter of that type.
|
|
646
|
+
*/
|
|
647
|
+
function instantiatedAccesses(site) {
|
|
648
|
+
const resolved = queries.resolvedSignature(site.node);
|
|
649
|
+
const generic = resolved === UNANSWERED || resolved === undefined
|
|
650
|
+
? undefined
|
|
651
|
+
: queries.genericOf(resolved);
|
|
652
|
+
if (resolved === UNANSWERED ||
|
|
653
|
+
resolved === undefined ||
|
|
654
|
+
generic === UNANSWERED ||
|
|
655
|
+
generic === undefined) {
|
|
656
|
+
return [];
|
|
657
|
+
}
|
|
658
|
+
const count = argumentsOf(site.node).length;
|
|
659
|
+
const parameters = [];
|
|
660
|
+
for (let at = 0; at < count; at += 1) {
|
|
661
|
+
const type = queries.parameterType(generic, at);
|
|
662
|
+
if (type !== UNANSWERED && type !== undefined) {
|
|
663
|
+
parameters[at] = type;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const result = queries.returnTypeOf(generic);
|
|
667
|
+
const held = [
|
|
668
|
+
...parameters,
|
|
669
|
+
...(result === UNANSWERED || result === undefined ? [] : [result]),
|
|
670
|
+
];
|
|
671
|
+
// A parameter of a callback and a type argument, a rest tuple's among them, hold the
|
|
672
|
+
// accesses one level down: `fn: (payload: M[K]) => void` and `...[M[K]]`.
|
|
673
|
+
const nested = held.flatMap((type) => [
|
|
674
|
+
...partsOf(type),
|
|
675
|
+
...partsOf(type).flatMap((part) => {
|
|
676
|
+
const args = queries.typeArguments(part);
|
|
677
|
+
const calls = queries.signaturesOf(part, SignatureKind.Call);
|
|
678
|
+
return [
|
|
679
|
+
...(args === UNANSWERED ? [] : args),
|
|
680
|
+
...(calls === UNANSWERED ? [] : calls).flatMap((signature) => signature.parameters.flatMap((_parameter, index) => {
|
|
681
|
+
const one = queries.parameterType(signature, index);
|
|
682
|
+
return one === UNANSWERED || one === undefined ? [] : [one];
|
|
683
|
+
})),
|
|
684
|
+
];
|
|
685
|
+
}),
|
|
686
|
+
]);
|
|
687
|
+
const found = [];
|
|
688
|
+
for (const type of nested) {
|
|
689
|
+
const access = queries.indexedAccessOf(type);
|
|
690
|
+
if (access === UNANSWERED || access === undefined || access.object.isTypeParameter()) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
const names = access.index.isTypeParameter()
|
|
694
|
+
? parameters.flatMap((parameter, at) => {
|
|
695
|
+
if (!partsOf(parameter).some((part) => part.id === access.index.id)) {
|
|
696
|
+
return [];
|
|
697
|
+
}
|
|
698
|
+
const passed = queries.typeAtPosition(resolved, at);
|
|
699
|
+
return passed === UNANSWERED ? [] : literalTypeNames(passed, partsOf);
|
|
700
|
+
})
|
|
701
|
+
: literalTypeNames(access.index, partsOf);
|
|
702
|
+
if (names.length > 0) {
|
|
703
|
+
found.push({
|
|
704
|
+
file: site.file,
|
|
705
|
+
from: site.from,
|
|
706
|
+
at: site.node,
|
|
707
|
+
object: access.object,
|
|
708
|
+
index: names,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return found;
|
|
713
|
+
}
|
|
714
|
+
const typeNodes = accesses.flatMap((one) => [
|
|
715
|
+
...("kind" in one.object ? [one.object] : []),
|
|
716
|
+
...(!Array.isArray(one.index) && "kind" in one.index ? [one.index] : []),
|
|
717
|
+
]);
|
|
718
|
+
const typeOf = new Map();
|
|
719
|
+
queries.typesAt(typeNodes).forEach((answer, index) => {
|
|
720
|
+
const node = typeNodes[index];
|
|
721
|
+
if (answer !== undefined && node !== undefined) {
|
|
722
|
+
typeOf.set(node, answer);
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
const asType = (side) => "kind" in side ? typeOf.get(side) : side;
|
|
726
|
+
for (const access of accesses) {
|
|
727
|
+
const type = asType(access.object);
|
|
728
|
+
const indexType = Array.isArray(access.index)
|
|
729
|
+
? undefined
|
|
730
|
+
: asType(access.index);
|
|
731
|
+
const accessed = Array.isArray(access.index)
|
|
732
|
+
? access.index
|
|
733
|
+
: indexType === undefined || indexType === UNANSWERED
|
|
734
|
+
? undefined
|
|
735
|
+
: literalTypeNames(indexType, partsOf);
|
|
736
|
+
if (type === UNANSWERED || indexType === UNANSWERED) {
|
|
737
|
+
found.push({
|
|
738
|
+
kind: "held",
|
|
739
|
+
from: access.from,
|
|
740
|
+
...(accessed === undefined ? {} : { names: accessed }),
|
|
741
|
+
...(type === UNANSWERED || type === undefined
|
|
742
|
+
? accessed === undefined
|
|
743
|
+
? { members: true }
|
|
744
|
+
: {}
|
|
745
|
+
: { types: partsOf(type) }),
|
|
746
|
+
});
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (type === undefined || accessed === undefined) {
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
for (const part of partsOf(type)) {
|
|
753
|
+
const properties = queries.propertiesOf(part);
|
|
754
|
+
if (properties === UNANSWERED) {
|
|
755
|
+
found.push({ kind: "held", from: access.from, names: accessed });
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
for (const property of properties) {
|
|
759
|
+
if (!accessed.includes(property.escapedName)) {
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
for (const to of chains.declarationsOf(property)) {
|
|
763
|
+
found.push({
|
|
764
|
+
kind: "named",
|
|
765
|
+
from: access.from,
|
|
766
|
+
to,
|
|
767
|
+
file: access.file,
|
|
768
|
+
at: access.at,
|
|
769
|
+
use: "read",
|
|
770
|
+
via: "batch",
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return found;
|
|
777
|
+
},
|
|
778
|
+
};
|
|
779
|
+
},
|
|
780
|
+
};
|
|
736
781
|
/** The arguments a call or a `new` expression passes, none for a type reference. */
|
|
737
782
|
function argumentsOf(node) {
|
|
738
783
|
return isTypeReferenceNode(node) ? [] : (node.arguments ?? []);
|