@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
|
@@ -4,8 +4,8 @@
|
|
|
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, isCallExpression, isClassDeclaration, isClassExpression, isFunctionDeclaration, isFunctionExpression,
|
|
8
|
-
import { SymbolFlags } from "@typescript/native/unstable/sync";
|
|
7
|
+
import { isArrowFunction, isBindingElement, isCallExpression, isClassDeclaration, isClassExpression, isFunctionDeclaration, isFunctionExpression, isFunctionTypeNode, isIdentifier, isIndexedAccessTypeNode, isInterfaceDeclaration, isLiteralTypeNode, isMappedTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isParenthesizedTypeNode, isPropertyAccessExpression, isPropertySignatureDeclaration, isStringLiteral, isTypeAliasDeclaration, isTypeOperatorNode, isTypeReferenceNode, isUnionTypeNode, isVariableDeclaration, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
8
|
+
import { SignatureKind, SymbolFlags, } from "@typescript/native/unstable/sync";
|
|
9
9
|
import { nodeKey } from "./inventory.js";
|
|
10
10
|
import { renderPosition } from "./position.js";
|
|
11
11
|
import { UNANSWERED } from "./query.js";
|
|
@@ -16,6 +16,7 @@ function isGeneric(node) {
|
|
|
16
16
|
isMethodSignatureDeclaration(node) ||
|
|
17
17
|
isArrowFunction(node) ||
|
|
18
18
|
isFunctionExpression(node) ||
|
|
19
|
+
isFunctionTypeNode(node) ||
|
|
19
20
|
isTypeAliasDeclaration(node) ||
|
|
20
21
|
isInterfaceDeclaration(node) ||
|
|
21
22
|
isClassDeclaration(node) ||
|
|
@@ -26,6 +27,29 @@ function isGeneric(node) {
|
|
|
26
27
|
function isReceiver(node) {
|
|
27
28
|
return isInterfaceDeclaration(node) || isClassDeclaration(node) || isClassExpression(node);
|
|
28
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* The class or the interface one member function belongs to: a method's parent, or for a
|
|
32
|
+
* function type a property declares, the declaration that holds the property.
|
|
33
|
+
*/
|
|
34
|
+
function receiverOf(member) {
|
|
35
|
+
const parent = member.parent;
|
|
36
|
+
if (isFunctionTypeNode(member) &&
|
|
37
|
+
parent !== undefined &&
|
|
38
|
+
isPropertySignatureDeclaration(parent)) {
|
|
39
|
+
return parent.parent;
|
|
40
|
+
}
|
|
41
|
+
return parent;
|
|
42
|
+
}
|
|
43
|
+
/** Whether a member function of a generic receiver declares no type parameter of its own. */
|
|
44
|
+
function isReceiverMember(node) {
|
|
45
|
+
if (!(isMethodDeclaration(node) ||
|
|
46
|
+
isMethodSignatureDeclaration(node) ||
|
|
47
|
+
isFunctionTypeNode(node))) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const receiver = receiverOf(node);
|
|
51
|
+
return receiver !== undefined && isReceiver(receiver) && isGeneric(receiver);
|
|
52
|
+
}
|
|
29
53
|
function unwrappedType(node) {
|
|
30
54
|
return isParenthesizedTypeNode(node) ? unwrappedType(node.type) : node;
|
|
31
55
|
}
|
|
@@ -86,6 +110,28 @@ function namedParameter(node, enclosing) {
|
|
|
86
110
|
}
|
|
87
111
|
return undefined;
|
|
88
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* The constraint of the mapped type whose type parameter one type node names whole: a
|
|
115
|
+
* mapped type instantiates what it holds once per constituent of that constraint.
|
|
116
|
+
*/
|
|
117
|
+
function mappedConstraint(node) {
|
|
118
|
+
const type = unwrappedType(node);
|
|
119
|
+
if (!isTypeReferenceNode(type) ||
|
|
120
|
+
!isIdentifier(type.typeName) ||
|
|
121
|
+
type.typeArguments !== undefined) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
const spelled = type.typeName.text;
|
|
125
|
+
for (let at = type.parent; at !== undefined; at = at.parent) {
|
|
126
|
+
if (isMappedTypeNode(at)) {
|
|
127
|
+
const { name, constraint } = at.typeParameter;
|
|
128
|
+
if (isIdentifier(name) && name.text === spelled) {
|
|
129
|
+
return constraint;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
89
135
|
/** The name a site writes to reach one generic: its own, or its variable's. */
|
|
90
136
|
function calledName(generic) {
|
|
91
137
|
const own = generic.name;
|
|
@@ -93,15 +139,23 @@ function calledName(generic) {
|
|
|
93
139
|
return own.text;
|
|
94
140
|
}
|
|
95
141
|
const parent = generic.parent;
|
|
96
|
-
return parent !== undefined &&
|
|
142
|
+
return parent !== undefined &&
|
|
143
|
+
(isVariableDeclaration(parent) || isPropertySignatureDeclaration(parent)) &&
|
|
144
|
+
isIdentifier(parent.name)
|
|
97
145
|
? parent.name.text
|
|
98
146
|
: undefined;
|
|
99
147
|
}
|
|
100
148
|
/** The generic a declaration a symbol names is: the function a variable is initialized with included. */
|
|
101
149
|
function genericOf(node) {
|
|
102
|
-
if (isGeneric(node)) {
|
|
150
|
+
if (isGeneric(node) || isReceiverMember(node)) {
|
|
103
151
|
return node;
|
|
104
152
|
}
|
|
153
|
+
if (isPropertySignatureDeclaration(node)) {
|
|
154
|
+
// The typings mark the annotation present; a signature written without one has none.
|
|
155
|
+
const declared = node.type;
|
|
156
|
+
const type = declared === undefined ? undefined : unwrappedType(declared);
|
|
157
|
+
return type !== undefined && isFunctionTypeNode(type) ? genericOf(type) : undefined;
|
|
158
|
+
}
|
|
105
159
|
if (isVariableDeclaration(node) &&
|
|
106
160
|
node.initializer !== undefined &&
|
|
107
161
|
isGeneric(node.initializer)) {
|
|
@@ -109,29 +163,60 @@ function genericOf(node) {
|
|
|
109
163
|
}
|
|
110
164
|
return undefined;
|
|
111
165
|
}
|
|
166
|
+
/** The operand of each `keyof` one type node holds, through every type node it is built of. */
|
|
167
|
+
function keyofOperands(node) {
|
|
168
|
+
const found = [];
|
|
169
|
+
const visit = (at) => {
|
|
170
|
+
if (isTypeOperatorNode(at) && at.operator === SyntaxKind.KeyOfKeyword) {
|
|
171
|
+
found.push(at.type);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
at.forEachChild(visit);
|
|
175
|
+
};
|
|
176
|
+
visit(node);
|
|
177
|
+
return found;
|
|
178
|
+
}
|
|
112
179
|
/**
|
|
113
180
|
* Each indexed access inside one generic whose index names a type parameter of it, read
|
|
114
181
|
* from its own text: the object side is a type parameter of the same generic, one of the
|
|
115
|
-
* receiver it is a
|
|
182
|
+
* receiver it is a member of, or a type it writes. A type parameter whose constraint holds
|
|
183
|
+
* `keyof X` is an index into `X` as `X[K]` is, and so is a parameter whose declared type
|
|
184
|
+
* holds `keyof X`, indexed by the argument a call passes it.
|
|
116
185
|
*/
|
|
117
186
|
function carriedIn(generic) {
|
|
118
187
|
const found = [];
|
|
119
|
-
const receiver = generic
|
|
188
|
+
const receiver = receiverOf(generic);
|
|
120
189
|
const outer = receiver !== undefined && isReceiver(receiver) && isGeneric(receiver) ? receiver : undefined;
|
|
190
|
+
const sideOf = (object) => {
|
|
191
|
+
const own = namedParameter(object, [generic]);
|
|
192
|
+
const fromReceiver = own === undefined && outer !== undefined ? namedParameter(object, [outer]) : undefined;
|
|
193
|
+
return own !== undefined
|
|
194
|
+
? { kind: "parameter", index: own.index }
|
|
195
|
+
: fromReceiver !== undefined
|
|
196
|
+
? { kind: "receiver", index: fromReceiver.index }
|
|
197
|
+
: { kind: "written", node: object };
|
|
198
|
+
};
|
|
199
|
+
(generic.typeParameters ?? []).forEach((parameter, index) => {
|
|
200
|
+
const constraint = parameter.constraint;
|
|
201
|
+
for (const object of constraint === undefined ? [] : keyofOperands(constraint)) {
|
|
202
|
+
found.push({ object: sideOf(object), index: { kind: "parameter", index } });
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
(generic.parameters ?? []).forEach((parameter, index) => {
|
|
206
|
+
for (const object of parameter.type === undefined ? [] : keyofOperands(parameter.type)) {
|
|
207
|
+
if (namedParameter(object, [generic]) === undefined) {
|
|
208
|
+
found.push({ object: sideOf(object), index: { kind: "argument", index } });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
121
212
|
const visit = (node) => {
|
|
122
213
|
if (isIndexedAccessTypeNode(node)) {
|
|
123
214
|
const index = namedParameter(node.indexType, [generic]);
|
|
124
215
|
if (index !== undefined) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const object = own !== undefined
|
|
130
|
-
? { kind: "parameter", index: own.index }
|
|
131
|
-
: fromReceiver !== undefined
|
|
132
|
-
? { kind: "receiver", index: fromReceiver.index }
|
|
133
|
-
: { kind: "written", node: node.objectType };
|
|
134
|
-
found.push({ object, index: { kind: "parameter", index: index.index } });
|
|
216
|
+
found.push({
|
|
217
|
+
object: sideOf(node.objectType),
|
|
218
|
+
index: { kind: "parameter", index: index.index },
|
|
219
|
+
});
|
|
135
220
|
}
|
|
136
221
|
}
|
|
137
222
|
if (node !== generic && isGeneric(node)) {
|
|
@@ -153,11 +238,8 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
153
238
|
const carried = new Map();
|
|
154
239
|
const names = new Set();
|
|
155
240
|
const sites = [];
|
|
156
|
-
/**
|
|
157
|
-
|
|
158
|
-
* method, which a dependency's generic may fix.
|
|
159
|
-
*/
|
|
160
|
-
const literalCalls = [];
|
|
241
|
+
/** Calls passing an argument, which a generic declared outside the own files may fix. */
|
|
242
|
+
const argumentCalls = [];
|
|
161
243
|
const carry = (generic, one) => {
|
|
162
244
|
const known = carried.get(generic) ?? [];
|
|
163
245
|
const same = known.some((other) => JSON.stringify(sideKey(other)) === JSON.stringify(sideKey(one)));
|
|
@@ -177,7 +259,6 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
177
259
|
continue;
|
|
178
260
|
}
|
|
179
261
|
const aliasTypes = new Set(typeQueryAliases(file).map((alias) => alias.type));
|
|
180
|
-
const imported = packageBindings(file);
|
|
181
262
|
const visit = (node, from, enclosing) => {
|
|
182
263
|
const own = held.declarations.get(nodeKey(file, node));
|
|
183
264
|
const holder = own ?? from;
|
|
@@ -206,13 +287,8 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
206
287
|
if (name !== undefined) {
|
|
207
288
|
const site = { file, from: holder, node, name, enclosing: inside };
|
|
208
289
|
sites.push(site);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const method = isPropertyAccessExpression(callee);
|
|
212
|
-
if (literal &&
|
|
213
|
-
isCallExpression(node) &&
|
|
214
|
-
(method || (head !== undefined && imported.has(head)))) {
|
|
215
|
-
literalCalls.push(site);
|
|
290
|
+
if (isCallExpression(node) && node.arguments.length > 0) {
|
|
291
|
+
argumentCalls.push(site);
|
|
216
292
|
}
|
|
217
293
|
}
|
|
218
294
|
}
|
|
@@ -240,6 +316,47 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
240
316
|
return generic === undefined ? [] : [generic];
|
|
241
317
|
});
|
|
242
318
|
};
|
|
319
|
+
/**
|
|
320
|
+
* The type a call's callee is read off: the receiver of a property access, the value an
|
|
321
|
+
* object binding pattern destructures the callee from, or the value whose member a
|
|
322
|
+
* variable holding the callee is initialized with.
|
|
323
|
+
*/
|
|
324
|
+
const receiverTypeOf = (callee) => {
|
|
325
|
+
if (isPropertyAccessExpression(callee)) {
|
|
326
|
+
const type = queries.typeAt(callee.expression);
|
|
327
|
+
const args = type === UNANSWERED || type === undefined ? [] : queries.typeArguments(type);
|
|
328
|
+
if (type !== UNANSWERED && type !== undefined && args !== UNANSWERED && args.length > 0) {
|
|
329
|
+
return type;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const name = isPropertyAccessExpression(callee) ? callee.name : callee;
|
|
333
|
+
const [named] = queries.symbolsAt([name]);
|
|
334
|
+
const symbol = named === UNANSWERED || named === undefined || (named.flags & SymbolFlags.Alias) === 0
|
|
335
|
+
? named
|
|
336
|
+
: queries.aliased(named);
|
|
337
|
+
if (symbol === UNANSWERED || symbol === undefined) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
for (const handle of symbol.declarations) {
|
|
341
|
+
const node = project.declarationAt(handle)?.node;
|
|
342
|
+
const holder = node !== undefined &&
|
|
343
|
+
isBindingElement(node) &&
|
|
344
|
+
isObjectBindingPattern(node.parent) &&
|
|
345
|
+
isVariableDeclaration(node.parent.parent)
|
|
346
|
+
? node.parent.parent.initializer
|
|
347
|
+
: node !== undefined &&
|
|
348
|
+
isVariableDeclaration(node) &&
|
|
349
|
+
node.initializer !== undefined &&
|
|
350
|
+
isPropertyAccessExpression(node.initializer)
|
|
351
|
+
? node.initializer.expression
|
|
352
|
+
: undefined;
|
|
353
|
+
const type = holder === undefined ? undefined : queries.typeAt(holder);
|
|
354
|
+
if (type !== UNANSWERED && type !== undefined) {
|
|
355
|
+
return type;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return undefined;
|
|
359
|
+
};
|
|
243
360
|
/** What one site fixes for one side of a carried access. */
|
|
244
361
|
const fixedAt = (site, side, generic) => {
|
|
245
362
|
if (side.kind === "written") {
|
|
@@ -248,23 +365,26 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
248
365
|
const { node } = site;
|
|
249
366
|
if (side.kind === "receiver") {
|
|
250
367
|
const callee = isCallExpression(node) ? node.expression : undefined;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
const receiver = queries.typeAt(callee.expression);
|
|
255
|
-
if (receiver === UNANSWERED || receiver === undefined) {
|
|
368
|
+
const receiver = callee === undefined ? undefined : receiverTypeOf(callee);
|
|
369
|
+
if (receiver === undefined) {
|
|
256
370
|
return undefined;
|
|
257
371
|
}
|
|
258
372
|
const args = queries.typeArguments(receiver);
|
|
259
373
|
const type = args === UNANSWERED ? undefined : args[side.index];
|
|
260
374
|
return type === undefined ? undefined : { kind: "type", type };
|
|
261
375
|
}
|
|
376
|
+
if (side.kind === "argument") {
|
|
377
|
+
const argument = isTypeReferenceNode(node) ? undefined : node.arguments?.[side.index];
|
|
378
|
+
const type = argument === undefined ? undefined : queries.typeAt(argument);
|
|
379
|
+
return type === UNANSWERED || type === undefined ? undefined : { kind: "type", type };
|
|
380
|
+
}
|
|
262
381
|
const written = node.typeArguments?.[side.index];
|
|
263
382
|
if (written !== undefined) {
|
|
264
383
|
const passed = namedParameter(written, site.enclosing);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
384
|
+
const mapped = passed === undefined ? mappedConstraint(written) : undefined;
|
|
385
|
+
return passed !== undefined
|
|
386
|
+
? { kind: "passed", generic: passed.generic, index: passed.index }
|
|
387
|
+
: { kind: "node", node: mapped ?? written };
|
|
268
388
|
}
|
|
269
389
|
if (isTypeReferenceNode(node)) {
|
|
270
390
|
return undefined;
|
|
@@ -358,40 +478,120 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
358
478
|
}
|
|
359
479
|
});
|
|
360
480
|
}
|
|
361
|
-
// A generic
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
481
|
+
// A generic declared outside the project's own files carries what its own text indexes
|
|
482
|
+
// by its type parameters and its receiver's. Only a call passing an argument the compiler
|
|
483
|
+
// types as a literal can fix an index, so only such a call's signature is resolved.
|
|
484
|
+
const outside = argumentCalls.filter((site) => !resolvedGenerics.has(site));
|
|
485
|
+
const argumentNodes = argumentCalls.flatMap((site) => [
|
|
486
|
+
...site.node.arguments,
|
|
487
|
+
]);
|
|
488
|
+
const argumentTypes = queries.typesAt(argumentNodes);
|
|
489
|
+
const literalArgument = new Set();
|
|
490
|
+
argumentTypes.forEach((type, at) => {
|
|
491
|
+
const node = argumentNodes[at];
|
|
492
|
+
if (node !== undefined &&
|
|
493
|
+
type !== UNANSWERED &&
|
|
494
|
+
type !== undefined &&
|
|
495
|
+
(literalTypeNames(type, partsOf)?.length ?? 0) > 0) {
|
|
496
|
+
literalArgument.add(node);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
370
499
|
const own = project.ownPaths();
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
? named
|
|
375
|
-
: queries.aliased(named);
|
|
376
|
-
if (symbol === UNANSWERED ||
|
|
377
|
-
symbol === undefined ||
|
|
378
|
-
(symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) === 0) {
|
|
379
|
-
return;
|
|
500
|
+
for (const site of outside) {
|
|
501
|
+
if (!site.node.arguments.some((one) => literalArgument.has(one))) {
|
|
502
|
+
continue;
|
|
380
503
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
504
|
+
const resolved = queries.resolvedSignature(site.node);
|
|
505
|
+
const handle = resolved === UNANSWERED || resolved === undefined ? undefined : resolved.declaration;
|
|
506
|
+
if (handle === undefined || own.has(handle.path)) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
const node = project.declarationAt(handle)?.node;
|
|
510
|
+
const generic = node === undefined ? undefined : genericOf(node);
|
|
511
|
+
if (generic === undefined) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
for (const one of carriedIn(generic)) {
|
|
515
|
+
readAt(site, generic, one);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
for (const site of argumentCalls) {
|
|
519
|
+
if (site.node.arguments.some((one) => literalArgument.has(one))) {
|
|
520
|
+
accesses.push(...instantiatedAccesses(site));
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* The indexed accesses the generic signature a call instantiates holds, read through the
|
|
525
|
+
* checker: the signature its receiver's instantiation gives, so an object type its
|
|
526
|
+
* receiver fixes is concrete whatever declaration spells the receiver's type, a type
|
|
527
|
+
* literal a generic function returns among them. An index naming a type parameter is the
|
|
528
|
+
* literal type the call passes at each parameter of that type.
|
|
529
|
+
*/
|
|
530
|
+
function instantiatedAccesses(site) {
|
|
531
|
+
const resolved = queries.resolvedSignature(site.node);
|
|
532
|
+
const generic = resolved === UNANSWERED || resolved === undefined ? undefined : queries.genericOf(resolved);
|
|
533
|
+
if (resolved === UNANSWERED ||
|
|
534
|
+
resolved === undefined ||
|
|
535
|
+
generic === UNANSWERED ||
|
|
536
|
+
generic === undefined) {
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
const count = site.node.arguments.length;
|
|
540
|
+
const parameters = [];
|
|
541
|
+
for (let at = 0; at < count; at += 1) {
|
|
542
|
+
const type = queries.parameterType(generic, at);
|
|
543
|
+
if (type !== UNANSWERED && type !== undefined) {
|
|
544
|
+
parameters[at] = type;
|
|
384
545
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
546
|
+
}
|
|
547
|
+
const result = queries.returnTypeOf(generic);
|
|
548
|
+
const held = [
|
|
549
|
+
...parameters,
|
|
550
|
+
...(result === UNANSWERED || result === undefined ? [] : [result]),
|
|
551
|
+
];
|
|
552
|
+
// A parameter of a callback and a type argument, a rest tuple's among them, hold the
|
|
553
|
+
// accesses one level down: `fn: (payload: M[K]) => void` and `...[M[K]]`.
|
|
554
|
+
const nested = held.flatMap((type) => [
|
|
555
|
+
...partsOf(type),
|
|
556
|
+
...partsOf(type).flatMap((part) => {
|
|
557
|
+
const args = queries.typeArguments(part);
|
|
558
|
+
const calls = queries.signaturesOf(part, SignatureKind.Call);
|
|
559
|
+
return [
|
|
560
|
+
...(args === UNANSWERED ? [] : args),
|
|
561
|
+
...(calls === UNANSWERED ? [] : calls).flatMap((signature) => signature.parameters.flatMap((_parameter, index) => {
|
|
562
|
+
const one = queries.parameterType(signature, index);
|
|
563
|
+
return one === UNANSWERED || one === undefined ? [] : [one];
|
|
564
|
+
})),
|
|
565
|
+
];
|
|
566
|
+
}),
|
|
567
|
+
]);
|
|
568
|
+
const found = [];
|
|
569
|
+
for (const type of nested) {
|
|
570
|
+
const access = queries.indexedAccessOf(type);
|
|
571
|
+
if (access === UNANSWERED || access === undefined || access.object.isTypeParameter()) {
|
|
388
572
|
continue;
|
|
389
573
|
}
|
|
390
|
-
|
|
391
|
-
|
|
574
|
+
const names = access.index.isTypeParameter()
|
|
575
|
+
? parameters.flatMap((parameter, at) => {
|
|
576
|
+
if (parameter.id !== access.index.id) {
|
|
577
|
+
return [];
|
|
578
|
+
}
|
|
579
|
+
const passed = queries.typeAtPosition(resolved, at);
|
|
580
|
+
return passed === UNANSWERED ? [] : (literalTypeNames(passed, partsOf) ?? []);
|
|
581
|
+
})
|
|
582
|
+
: (literalTypeNames(access.index, partsOf) ?? []);
|
|
583
|
+
if (names.length > 0) {
|
|
584
|
+
found.push({
|
|
585
|
+
file: site.file,
|
|
586
|
+
from: site.from,
|
|
587
|
+
at: site.node,
|
|
588
|
+
object: access.object,
|
|
589
|
+
index: names,
|
|
590
|
+
});
|
|
392
591
|
}
|
|
393
592
|
}
|
|
394
|
-
|
|
593
|
+
return found;
|
|
594
|
+
}
|
|
395
595
|
const typeNodes = accesses.flatMap((one) => [
|
|
396
596
|
...("kind" in one.object ? [one.object] : []),
|
|
397
597
|
...(!Array.isArray(one.index) && "kind" in one.index ? [one.index] : []),
|
|
@@ -443,67 +643,6 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
443
643
|
}
|
|
444
644
|
return found;
|
|
445
645
|
}
|
|
446
|
-
/**
|
|
447
|
-
* The local names one file binds to a package: each name an import declaration with a
|
|
448
|
-
* bare specifier binds, and each variable initialized by a call or a construction whose
|
|
449
|
-
* callee such a name heads, read in source order.
|
|
450
|
-
*/
|
|
451
|
-
function packageBindings(file) {
|
|
452
|
-
const names = new Set();
|
|
453
|
-
for (const statement of file.statements) {
|
|
454
|
-
if (!isImportDeclaration(statement) ||
|
|
455
|
-
!isStringLiteral(statement.moduleSpecifier) ||
|
|
456
|
-
/^[./]/u.test(statement.moduleSpecifier.text)) {
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
const clause = statement.importClause;
|
|
460
|
-
if (clause?.name !== undefined) {
|
|
461
|
-
names.add(clause.name.text);
|
|
462
|
-
}
|
|
463
|
-
const bindings = clause?.namedBindings;
|
|
464
|
-
if (bindings !== undefined && isNamespaceImport(bindings)) {
|
|
465
|
-
names.add(bindings.name.text);
|
|
466
|
-
}
|
|
467
|
-
else if (bindings !== undefined && isNamedImports(bindings)) {
|
|
468
|
-
bindings.elements.forEach((element) => names.add(element.name.text));
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
if (names.size === 0) {
|
|
472
|
-
return names;
|
|
473
|
-
}
|
|
474
|
-
const visit = (node) => {
|
|
475
|
-
if (isVariableDeclaration(node) &&
|
|
476
|
-
isIdentifier(node.name) &&
|
|
477
|
-
node.initializer !== undefined &&
|
|
478
|
-
(isCallExpression(node.initializer) || isNewExpression(node.initializer))) {
|
|
479
|
-
const head = rootName(node.initializer.expression);
|
|
480
|
-
if (head !== undefined && names.has(head)) {
|
|
481
|
-
names.add(node.name.text);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
node.forEachChild(visit);
|
|
485
|
-
};
|
|
486
|
-
visit(file);
|
|
487
|
-
return names;
|
|
488
|
-
}
|
|
489
|
-
/** The identifier an expression is headed by, through property accesses and calls. */
|
|
490
|
-
function rootName(expression) {
|
|
491
|
-
let at = expression;
|
|
492
|
-
for (;;) {
|
|
493
|
-
if (isIdentifier(at)) {
|
|
494
|
-
return at.text;
|
|
495
|
-
}
|
|
496
|
-
if (isPropertyAccessExpression(at) || isCallExpression(at) || isNewExpression(at)) {
|
|
497
|
-
at = at.expression;
|
|
498
|
-
continue;
|
|
499
|
-
}
|
|
500
|
-
if (isParenthesizedExpression(at) || isNonNullExpression(at)) {
|
|
501
|
-
at = at.expression;
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
return undefined;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
646
|
/** What makes two carried accesses one: the kind and position of each side, or its written node. */
|
|
508
647
|
function sideKey(one) {
|
|
509
648
|
const key = (side) => side.kind === "written" ? ["written", side.node.pos, side.node.end] : [side.kind, side.index];
|
|
@@ -46,16 +46,15 @@ function calleeName(call) {
|
|
|
46
46
|
}
|
|
47
47
|
return isPropertyAccessExpression(callee) && isIdentifier(callee.name) ? callee.name : undefined;
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
* statement, or the operand of `void`.
|
|
52
|
-
*/
|
|
53
|
-
function discarded(call) {
|
|
49
|
+
/** What a call does with its value. */
|
|
50
|
+
function callUse(call) {
|
|
54
51
|
const parent = outerParent(call);
|
|
55
52
|
if (isExpressionStatement(parent) || isVoidExpression(parent)) {
|
|
56
|
-
return
|
|
53
|
+
return "discarded";
|
|
57
54
|
}
|
|
58
|
-
return isAwaitExpression(parent) && isExpressionStatement(outerParent(parent))
|
|
55
|
+
return isAwaitExpression(parent) && isExpressionStatement(outerParent(parent))
|
|
56
|
+
? "awaited"
|
|
57
|
+
: "used";
|
|
59
58
|
}
|
|
60
59
|
/** Whether one statement leaves its statement list on every path: the rest of the list never runs. */
|
|
61
60
|
function abrupt(statement) {
|
|
@@ -348,7 +347,7 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
348
347
|
if (isCallExpression(node)) {
|
|
349
348
|
const name = calleeName(node);
|
|
350
349
|
if (name !== undefined) {
|
|
351
|
-
calls.set(position(name),
|
|
350
|
+
calls.set(position(name), callUse(node));
|
|
352
351
|
}
|
|
353
352
|
}
|
|
354
353
|
if (isImportSpecifier(node) || isExportSpecifier(node)) {
|
|
@@ -728,7 +727,9 @@ function deadStores(file, root, block, holder, locals, ids) {
|
|
|
728
727
|
* whether it is used is the call sites' to say, which the run decides over every project.
|
|
729
728
|
*/
|
|
730
729
|
function resultParts(project, root, sites, references, calls) {
|
|
731
|
-
const discardedAt = new Set(references
|
|
730
|
+
const discardedAt = new Set(references
|
|
731
|
+
.filter((one) => (calls.get(positionKey(one.position)) ?? "used") !== "used")
|
|
732
|
+
.map((one) => one.to));
|
|
732
733
|
const found = [];
|
|
733
734
|
for (const { file, declared } of sites) {
|
|
734
735
|
const { signature, node, id } = declared;
|
|
@@ -750,6 +751,8 @@ function resultParts(project, root, sites, references, calls) {
|
|
|
750
751
|
continue;
|
|
751
752
|
}
|
|
752
753
|
const at = signature.type ?? node.name ?? node;
|
|
754
|
+
const valued = project.queries.nonNullable(result);
|
|
755
|
+
const members = isAnswered(valued) ? project.queries.propertiesOf(valued) : UNANSWERED;
|
|
753
756
|
found.push({
|
|
754
757
|
code: UNUSED_RESULT,
|
|
755
758
|
declaration: id,
|
|
@@ -758,6 +761,7 @@ function resultParts(project, root, sites, references, calls) {
|
|
|
758
761
|
position: spanOf(file, root, at),
|
|
759
762
|
message: "result is discarded at every call site",
|
|
760
763
|
used: false,
|
|
764
|
+
thenable: !isAnswered(members) || members.some((member) => member.name === "then"),
|
|
761
765
|
});
|
|
762
766
|
}
|
|
763
767
|
return found;
|
|
@@ -789,6 +793,7 @@ export function intraFunctionFacts(projects, references, exempted) {
|
|
|
789
793
|
}
|
|
790
794
|
const valued = new Set();
|
|
791
795
|
const called = new Map();
|
|
796
|
+
const awaited = new Set();
|
|
792
797
|
for (const reference of references) {
|
|
793
798
|
if (reference.use === "decorator" || reference.use === "evaluation") {
|
|
794
799
|
continue;
|
|
@@ -796,7 +801,10 @@ export function intraFunctionFacts(projects, references, exempted) {
|
|
|
796
801
|
const at = positionKey(reference.position);
|
|
797
802
|
const call = reference.consumer === undefined ? calls.get(at) : undefined;
|
|
798
803
|
if (call !== undefined) {
|
|
799
|
-
called.set(reference.to, (called.get(reference.to) ?? true) && call);
|
|
804
|
+
called.set(reference.to, (called.get(reference.to) ?? true) && call !== "used");
|
|
805
|
+
if (call === "awaited") {
|
|
806
|
+
awaited.add(reference.to);
|
|
807
|
+
}
|
|
800
808
|
}
|
|
801
809
|
else if (reference.consumer !== undefined || !specifiers.has(at)) {
|
|
802
810
|
valued.add(reference.to);
|
|
@@ -810,6 +818,12 @@ export function intraFunctionFacts(projects, references, exempted) {
|
|
|
810
818
|
const freeButValued = new Set(signatures
|
|
811
819
|
.map((one) => one.declaration)
|
|
812
820
|
.filter((id) => !fixed.has(id) && !exempted.has(id) && valued.has(id)));
|
|
813
|
-
|
|
821
|
+
// Awaiting a thenable result sequences on it, so a call that awaits one uses it.
|
|
822
|
+
const sequenced = new Set([...parts.values()]
|
|
823
|
+
.filter((part) => part.thenable === true && awaited.has(part.declaration))
|
|
824
|
+
.map((part) => part.declaration));
|
|
825
|
+
const discardedEverywhere = new Set([...called]
|
|
826
|
+
.filter(([id, all]) => all && !valued.has(id) && !sequenced.has(id))
|
|
827
|
+
.map(([id]) => id));
|
|
814
828
|
return { parts: [...parts.values()], free, freeButValued, discardedEverywhere };
|
|
815
829
|
}
|
package/dist/src/inventory.js
CHANGED
|
@@ -693,6 +693,30 @@ export function inventory(project, host, targetRoot) {
|
|
|
693
693
|
}
|
|
694
694
|
return undefined;
|
|
695
695
|
}
|
|
696
|
+
const dependents = new Map();
|
|
697
|
+
for (const container of containers.filter((one) => one.memberKind === "enum-member")) {
|
|
698
|
+
const ids = container.members.map((member) => {
|
|
699
|
+
const record = byNode.get(nodeKey(member.getSourceFile(), member));
|
|
700
|
+
return record === undefined ? undefined : positionKey(record.position);
|
|
701
|
+
});
|
|
702
|
+
container.members.forEach((_member, index) => {
|
|
703
|
+
const after = [];
|
|
704
|
+
for (let next = index + 1; next < container.members.length; next += 1) {
|
|
705
|
+
const member = container.members[next];
|
|
706
|
+
if (member === undefined || !isEnumMember(member) || member.initializer !== undefined) {
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
const id = ids[next];
|
|
710
|
+
if (id !== undefined) {
|
|
711
|
+
after.push(id);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
const id = ids[index];
|
|
715
|
+
if (id !== undefined && after.length > 0) {
|
|
716
|
+
dependents.set(id, after);
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
}
|
|
696
720
|
const byId = new Map(building.map((record) => [positionKey(record.position), record]));
|
|
697
721
|
const symbols = building
|
|
698
722
|
.map(render)
|
|
@@ -706,6 +730,7 @@ export function inventory(project, host, targetRoot) {
|
|
|
706
730
|
.map((symbol) => symbol.id),
|
|
707
731
|
byName: namesOf(building),
|
|
708
732
|
modules: new Map(modules.map(([record, name]) => [positionKey(record.position), name])),
|
|
733
|
+
dependents,
|
|
709
734
|
};
|
|
710
735
|
}
|
|
711
736
|
/** Whether one declaration is, or is declared under, a scope or container left unanswered. */
|