@cplieger/deadset-ts 7.2.0-dev.3 → 8.0.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/mapper-connection.js +7 -4
- package/dist/src/analysis.js +116 -16
- package/dist/src/augmentations.js +21 -8
- package/dist/src/automatic-types.js +189 -0
- package/dist/src/base-url.js +18 -0
- package/dist/src/component-files.js +96 -31
- package/dist/src/components.js +7 -6
- package/dist/src/config.js +1 -0
- package/dist/src/configuration-files.js +54 -15
- package/dist/src/configured-declarations.js +24 -2
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +26 -26
- package/dist/src/convention-rows.js +75 -0
- package/dist/src/conventions.js +101 -5
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +185 -30
- package/dist/src/destructuring.js +77 -2
- package/dist/src/discover.js +188 -85
- package/dist/src/edges.js +1 -1
- package/dist/src/entry-point-gaps.js +7 -10
- package/dist/src/entry-points.js +112 -11
- package/dist/src/findings/completion.js +116 -21
- package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +44 -13
- package/dist/src/findings/non-code-artifacts.js +66 -33
- package/dist/src/findings/unused-declarations.js +21 -1
- package/dist/src/findings/visibility-narrowing.js +22 -3
- package/dist/src/generated-file.js +11 -0
- package/dist/src/git-ignore.js +86 -0
- package/dist/src/graph.js +4 -1
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +313 -50
- package/dist/src/inventory.js +63 -8
- package/dist/src/json-document.js +5 -5
- package/dist/src/kinds.js +13 -13
- package/dist/src/matrix.js +18 -9
- package/dist/src/mdx-entries.js +197 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/namespace-objects.js +28 -18
- package/dist/src/overrides.js +45 -9
- package/dist/src/paths.js +9 -0
- package/dist/src/print.js +1 -0
- package/dist/src/ref.js +10 -0
- package/dist/src/references.js +29 -13
- package/dist/src/reflective-lookup.js +8 -6
- package/dist/src/report.js +4 -2
- package/dist/src/reporters/template.js +22 -13
- package/dist/src/resolve.js +2 -0
- package/dist/src/roots.js +110 -38
- package/dist/src/schema.js +1 -0
- package/dist/src/serialization-contract.js +97 -20
- package/dist/src/session.js +26 -1
- package/dist/src/source-files.js +39 -14
- package/dist/src/spelled-names.js +58 -0
- package/dist/src/suppress-file.js +12 -4
- package/dist/src/suppress-inline.js +21 -3
- package/dist/src/suppress.js +7 -5
- package/dist/src/sweep.js +27 -10
- package/dist/src/symbol-keys.js +103 -2
- package/dist/src/template-field.js +9 -6
- package/dist/src/test-support.js +121 -17
- package/dist/src/type-errors.js +116 -35
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/value-reach.js +17 -5
- package/dist/src/verbs/analyze.js +19 -1
- package/dist/src/verbs/explain.js +4 -2
- package/dist/src/version-range.js +10 -5
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +119 -22
- package/dist/src/workspace-programs.js +7 -3
- package/dist/src/workspace-resolution.js +72 -13
- package/dist/src/workspace.js +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +175 -16
- package/src/augmentations.ts +21 -10
- package/src/automatic-types.ts +246 -0
- package/src/base-url.ts +24 -0
- package/src/component-files.ts +116 -31
- package/src/components.ts +7 -6
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +80 -18
- package/src/configured-declarations.ts +33 -4
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +27 -27
- package/src/convention-rows.ts +103 -0
- package/src/conventions.ts +121 -7
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +249 -43
- package/src/destructuring.ts +93 -2
- package/src/discover.ts +234 -86
- package/src/edges.ts +1 -1
- package/src/entry-point-gaps.ts +8 -11
- package/src/entry-points.ts +133 -11
- package/src/finding.ts +10 -0
- package/src/findings/completion.ts +147 -24
- package/src/findings/dependencies-and-module-machinery.ts +90 -28
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +56 -14
- package/src/findings/non-code-artifacts.ts +81 -41
- package/src/findings/unused-declarations.ts +25 -1
- package/src/findings/visibility-narrowing.ts +32 -3
- package/src/generated-file.ts +16 -0
- package/src/git-ignore.ts +98 -0
- package/src/graph.ts +6 -0
- package/src/host.ts +5 -9
- package/src/import-bindings.ts +128 -0
- package/src/indexed-access.ts +152 -30
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +415 -64
- package/src/inventory.ts +83 -7
- package/src/json-document.ts +7 -7
- package/src/kinds.ts +13 -13
- package/src/matrix.ts +32 -14
- package/src/mdx-entries.ts +241 -0
- package/src/module-declarations.ts +11 -13
- package/src/namespace-objects.ts +34 -21
- package/src/overrides.ts +56 -9
- package/src/paths.ts +10 -0
- package/src/print.ts +1 -0
- package/src/ref.ts +12 -0
- package/src/references.ts +38 -17
- package/src/reflective-lookup.ts +9 -7
- package/src/report.ts +16 -3
- package/src/reporters/template.ts +21 -13
- package/src/resolve.ts +3 -0
- package/src/roots.ts +163 -45
- package/src/schema.ts +1 -0
- package/src/serialization-contract.ts +108 -17
- package/src/session.ts +29 -1
- package/src/source-files.ts +66 -16
- package/src/spelled-names.ts +102 -0
- package/src/suppress-file.ts +14 -3
- package/src/suppress-inline.ts +21 -2
- package/src/suppress.ts +9 -5
- package/src/sweep.ts +32 -11
- package/src/symbol-keys.ts +129 -2
- package/src/template-field.ts +9 -6
- package/src/test-support.ts +149 -16
- package/src/type-errors.ts +132 -41
- package/src/type-parameter-reads.ts +227 -0
- package/src/value-reach.ts +19 -6
- package/src/verbs/analyze.ts +28 -1
- package/src/verbs/explain.ts +5 -2
- package/src/version-range.ts +10 -5
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +127 -22
- package/src/workspace-programs.ts +7 -3
- package/src/workspace-resolution.ts +86 -14
- package/src/workspace.ts +1 -1
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
* call sites that use or discard it, each statement control flow cannot reach, each
|
|
5
5
|
* write to a local that no read reaches, and each case an earlier case covers.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { isArrowFunction, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isBindingElement, isCallExpression, isCaseClause, isCatchClause, isClassDeclaration, isClassExpression, isConstructorDeclaration, isContinueStatement, isDecorator, isDefaultClause, isEmptyStatement, isExportAssignment, isExportSpecifier, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isForInStatement, isForOfStatement, isFunctionLikeDeclaration, isHeritageClause, isIdentifier, isIfStatement, isImportClause, isImportSpecifier, isInterfaceDeclaration, isMethodDeclaration, isModuleDeclaration, isNamespaceImport, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPrefixUnaryExpression, isPropertyAssignment, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isSourceFile, isStringLiteral, isSwitchStatement, isThrowStatement, isTypeAliasDeclaration, isTypeReferenceNode, isVariableDeclarationList, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
|
|
8
|
+
import { SignatureKind } from "@typescript/native/sync";
|
|
9
|
+
import { exportsLocally, implicitReads, importBindings, } from "./import-bindings.js";
|
|
8
10
|
import { isParameterProperty, nodeKey } from "./inventory.js";
|
|
9
11
|
import { positionKey, renderPosition } from "./position.js";
|
|
12
|
+
import { importRef } from "./ref.js";
|
|
10
13
|
import { isAnswered, UNANSWERED } from "./query.js";
|
|
11
14
|
import { signatureOf } from "./signatures.js";
|
|
15
|
+
import { spellsNoBinding } from "./spelled-names.js";
|
|
12
16
|
/** The codes of the parts this module reads. */
|
|
13
17
|
export const UNUSED_PARAMETER = "DS1801";
|
|
14
18
|
export const UNUSED_RESULT = "DS1803";
|
|
@@ -117,13 +121,12 @@ function caseKey(expression) {
|
|
|
117
121
|
}
|
|
118
122
|
return undefined;
|
|
119
123
|
}
|
|
120
|
-
/** Every identifier below one node whose text one of the names spells
|
|
124
|
+
/** Every identifier below one node whose text one of the names spells as a reference. */
|
|
121
125
|
function identifiersNaming(node, names) {
|
|
122
126
|
const found = [];
|
|
123
127
|
const visit = (at) => {
|
|
124
128
|
if (isIdentifier(at)) {
|
|
125
|
-
|
|
126
|
-
if (names.has(at.text) && !(isPropertyAccessExpression(parent) && parent.name === at)) {
|
|
129
|
+
if (names.has(at.text) && !spellsNoBinding(at)) {
|
|
127
130
|
found.push(at);
|
|
128
131
|
}
|
|
129
132
|
return;
|
|
@@ -154,6 +157,15 @@ function inNestedFunction(node, outer) {
|
|
|
154
157
|
}
|
|
155
158
|
return false;
|
|
156
159
|
}
|
|
160
|
+
/** The nearest function around one node whose `arguments` object it names: an arrow has none. */
|
|
161
|
+
function ownArgumentsOf(node) {
|
|
162
|
+
for (let at = node.parent; !isSourceFile(at); at = at.parent) {
|
|
163
|
+
if (isFunctionLikeDeclaration(at) && !isArrowFunction(at)) {
|
|
164
|
+
return at;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
157
169
|
/** Whether a body is a stub: it holds no statement, or nothing but `throw` statements. */
|
|
158
170
|
function isStub(signature) {
|
|
159
171
|
const body = signature.body;
|
|
@@ -295,6 +307,33 @@ function readsBinding(use, declared, ids) {
|
|
|
295
307
|
const own = ids.get(declared);
|
|
296
308
|
return at === undefined || at === UNANSWERED || own === UNANSWERED || at === own;
|
|
297
309
|
}
|
|
310
|
+
/** Whether a name is spelled with a leading underscore, which declares it unread. */
|
|
311
|
+
function declaredUnread(name) {
|
|
312
|
+
return name.text.startsWith("_");
|
|
313
|
+
}
|
|
314
|
+
/** A file's leading comments, where the compiler reads its `@ts-check` and `@ts-nocheck` pragmas. */
|
|
315
|
+
const LEADING_COMMENTS = /^(?:#![^\n]*)?(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*/u;
|
|
316
|
+
/** A comment of the leading comments, a line comment's text after its slashes captured. */
|
|
317
|
+
const LEADING_COMMENT = /\/\*[\s\S]*?\*\/|\/\/\/?([^\n]*)/gu;
|
|
318
|
+
/**
|
|
319
|
+
* Whether a file's leading comments hold one pragma: as the compiler reads it, a line
|
|
320
|
+
* comment that starts with the pragma's name, in any case, followed by nothing or a space.
|
|
321
|
+
*/
|
|
322
|
+
function hasPragma(leading, pragma) {
|
|
323
|
+
return [...leading.matchAll(LEADING_COMMENT)].some(([, text]) => text !== undefined && pragma.test(text));
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Whether the compiler checks a file, so its rules report what they find there: a
|
|
327
|
+
* JavaScript file only under `checkJs` or a `@ts-check` pragma, and no file with a
|
|
328
|
+
* `@ts-nocheck` pragma.
|
|
329
|
+
*/
|
|
330
|
+
function checkedByTheCompiler(file, checkJs) {
|
|
331
|
+
const leading = LEADING_COMMENTS.exec(file.text)?.[0] ?? "";
|
|
332
|
+
if (hasPragma(leading, /^\s*@ts-nocheck(?:\s|$)/iu)) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
return (!/\.[cm]?jsx?$/u.test(file.fileName) || checkJs || hasPragma(leading, /^\s*@ts-check(?:\s|$)/iu));
|
|
336
|
+
}
|
|
298
337
|
/**
|
|
299
338
|
* The parts of one project's own declarations. `references` are the project's own
|
|
300
339
|
* references, which say which declarations a discarded call reaches, so the checker is
|
|
@@ -310,7 +349,12 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
310
349
|
const constructions = new Set();
|
|
311
350
|
const sites = [];
|
|
312
351
|
const contextual = [];
|
|
352
|
+
const imports = [];
|
|
353
|
+
const refs = new Map(held.symbols.map((symbol) => [symbol.id, symbol.ref]));
|
|
354
|
+
const options = project.program.getCompilerOptions();
|
|
355
|
+
const jsx = options;
|
|
313
356
|
for (const file of files) {
|
|
357
|
+
const checked = checkedByTheCompiler(file, options.checkJs === true);
|
|
314
358
|
// A parameter property's own parts stay with the parameter when its member goes.
|
|
315
359
|
const declarationAt = (node) => isParameterProperty(node) ? undefined : held.declarations.get(nodeKey(file, node));
|
|
316
360
|
// A part no declaration holds belongs to the file, which is judged while the file is live.
|
|
@@ -329,6 +373,7 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
329
373
|
values: [],
|
|
330
374
|
catches: [],
|
|
331
375
|
blocks: [],
|
|
376
|
+
loops: [],
|
|
332
377
|
unreachable: [],
|
|
333
378
|
cases: [],
|
|
334
379
|
};
|
|
@@ -424,6 +469,15 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
424
469
|
if (isBlock(node) && silenced === 0 && holderOf(node) !== undefined) {
|
|
425
470
|
walk.blocks.push(node);
|
|
426
471
|
}
|
|
472
|
+
const loopHolder = (isForOfStatement(node) || isForInStatement(node)) && silenced === 0
|
|
473
|
+
? holderOf(node)
|
|
474
|
+
: undefined;
|
|
475
|
+
if (loopHolder !== undefined &&
|
|
476
|
+
(isForOfStatement(node) || isForInStatement(node)) &&
|
|
477
|
+
isVariableDeclarationList(node.initializer) &&
|
|
478
|
+
(node.initializer.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0) {
|
|
479
|
+
walk.loops.push({ loop: node, holder: loopHolder });
|
|
480
|
+
}
|
|
427
481
|
if (isBlock(node) || isCaseClause(node) || isDefaultClause(node)) {
|
|
428
482
|
statementsOf(node.statements);
|
|
429
483
|
return;
|
|
@@ -453,7 +507,37 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
453
507
|
for (const store of stores) {
|
|
454
508
|
asked.push(...store.flatMap((local) => [local.name, ...local.uses]));
|
|
455
509
|
}
|
|
510
|
+
const loopVariables = walk.loops.map(({ loop, holder }) => {
|
|
511
|
+
const list = loop.initializer;
|
|
512
|
+
// A loop must bind a name, so one spelled declared unread is no subject.
|
|
513
|
+
const names = list.declarations
|
|
514
|
+
.flatMap((declaration) => boundNames(declaration.name))
|
|
515
|
+
.filter((name) => !declaredUnread(name));
|
|
516
|
+
const uses = identifiersNaming(loop.statement, new Set(names.map((one) => one.text)));
|
|
517
|
+
asked.push(...names, ...uses);
|
|
518
|
+
return { names, uses, holder };
|
|
519
|
+
});
|
|
520
|
+
const bindings = fileId === undefined ? [] : importBindings(file);
|
|
521
|
+
const bound = new Set(bindings);
|
|
522
|
+
const bindingUses = bindings.length === 0
|
|
523
|
+
? []
|
|
524
|
+
: identifiersNaming(file, new Set(bindings.map((one) => one.text))).filter((use) => !bound.has(use));
|
|
525
|
+
asked.push(...bindings, ...bindingUses);
|
|
456
526
|
const ids = resolvedIds(project, [...new Set(asked)]);
|
|
527
|
+
const implicit = bindings.length === 0 ? new Set() : implicitReads(file, jsx);
|
|
528
|
+
for (const binding of bindings) {
|
|
529
|
+
const fileRef = fileId === undefined ? undefined : refs.get(fileId);
|
|
530
|
+
if (fileId === undefined || fileRef === undefined) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
imports.push({
|
|
534
|
+
ref: importRef(fileRef, binding.text),
|
|
535
|
+
name: binding.text,
|
|
536
|
+
position: spanOf(file, targetRoot, binding),
|
|
537
|
+
used: implicit.has(binding.text) ||
|
|
538
|
+
bindingUses.some((use) => use.text === binding.text && (exportsLocally(use) || readsBinding(use, binding, ids))),
|
|
539
|
+
});
|
|
540
|
+
}
|
|
457
541
|
for (const one of walk.declared) {
|
|
458
542
|
const constructor = isConstructorDeclaration(one.node);
|
|
459
543
|
if (!constructor) {
|
|
@@ -467,7 +551,10 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
467
551
|
});
|
|
468
552
|
const reader = readers.get(one.signature);
|
|
469
553
|
if (reader !== undefined) {
|
|
470
|
-
const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids,
|
|
554
|
+
const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, {
|
|
555
|
+
inValue: false,
|
|
556
|
+
checked,
|
|
557
|
+
});
|
|
471
558
|
parts.push(...found.parts);
|
|
472
559
|
contextual.push(...found.contextual);
|
|
473
560
|
}
|
|
@@ -475,7 +562,10 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
475
562
|
for (const one of walk.values) {
|
|
476
563
|
const reader = readers.get(one.signature);
|
|
477
564
|
if (reader !== undefined) {
|
|
478
|
-
const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids,
|
|
565
|
+
const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids, {
|
|
566
|
+
inValue: true,
|
|
567
|
+
checked,
|
|
568
|
+
});
|
|
479
569
|
parts.push(...found.parts);
|
|
480
570
|
contextual.push(...found.contextual);
|
|
481
571
|
}
|
|
@@ -491,6 +581,19 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
491
581
|
used: (caught[at] ?? []).some((use) => readsBinding(use, name, ids)),
|
|
492
582
|
});
|
|
493
583
|
});
|
|
584
|
+
for (const { names, uses, holder } of loopVariables) {
|
|
585
|
+
for (const name of names) {
|
|
586
|
+
parts.push({
|
|
587
|
+
code: DEAD_STORE,
|
|
588
|
+
declaration: holder,
|
|
589
|
+
kind: "store",
|
|
590
|
+
name: name.text,
|
|
591
|
+
position: spanOf(file, targetRoot, name),
|
|
592
|
+
message: `value written to ${name.text} is never read`,
|
|
593
|
+
used: uses.some((use) => use.text === name.text && readsBinding(use, name, ids)),
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
494
597
|
walk.blocks.forEach((block, at) => {
|
|
495
598
|
const holder = holderOf(block);
|
|
496
599
|
if (holder !== undefined) {
|
|
@@ -523,6 +626,7 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
523
626
|
parts.push(...resultParts(project, targetRoot, sites, references, calls));
|
|
524
627
|
return {
|
|
525
628
|
parts: withContextualRead(project, parts, contextual),
|
|
629
|
+
imports,
|
|
526
630
|
signatures,
|
|
527
631
|
calls,
|
|
528
632
|
specifiers,
|
|
@@ -549,7 +653,9 @@ function valueSignature(node) {
|
|
|
549
653
|
/**
|
|
550
654
|
* Every name one function's parameters bind, a binding pattern's names one by one. A
|
|
551
655
|
* `this` parameter receives no argument, a decorated one is read by its decorator, and a
|
|
552
|
-
* parameter property is read by the write to its member, so none binds a subject.
|
|
656
|
+
* parameter property is read by the write to its member, so none binds a subject. A name
|
|
657
|
+
* spelled with a leading underscore is declared unread, so it is listed for its reads and
|
|
658
|
+
* reported never.
|
|
553
659
|
*/
|
|
554
660
|
function parameterNames(signature) {
|
|
555
661
|
const found = [];
|
|
@@ -561,7 +667,7 @@ function parameterNames(signature) {
|
|
|
561
667
|
continue;
|
|
562
668
|
}
|
|
563
669
|
if (isIdentifier(element.name)) {
|
|
564
|
-
const exempt = rest && element.dotDotDotToken === undefined;
|
|
670
|
+
const exempt = (rest && element.dotDotDotToken === undefined) || declaredUnread(element.name);
|
|
565
671
|
found.push({ name: element.name, at, pattern: true, exempt });
|
|
566
672
|
}
|
|
567
673
|
else {
|
|
@@ -577,7 +683,7 @@ function parameterNames(signature) {
|
|
|
577
683
|
}
|
|
578
684
|
if (isIdentifier(name)) {
|
|
579
685
|
if (name.text !== "this") {
|
|
580
|
-
found.push({ name, at, pattern: false, exempt:
|
|
686
|
+
found.push({ name, at, pattern: false, exempt: declaredUnread(name) });
|
|
581
687
|
}
|
|
582
688
|
return;
|
|
583
689
|
}
|
|
@@ -585,11 +691,91 @@ function parameterNames(signature) {
|
|
|
585
691
|
});
|
|
586
692
|
return found;
|
|
587
693
|
}
|
|
694
|
+
/** How deep a type is searched for a type parameter: a callback of a callback is two levels. */
|
|
695
|
+
const TYPE_PARAMETER_DEPTH = 3;
|
|
696
|
+
/** Whether one type is or holds a type parameter, through constituents, type arguments and signatures. */
|
|
697
|
+
function namesTypeParameter(project, type, depth = 0) {
|
|
698
|
+
if (type.isTypeParameter()) {
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
if (depth >= TYPE_PARAMETER_DEPTH) {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
const { queries } = project;
|
|
705
|
+
const parts = type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [];
|
|
706
|
+
const args = queries.typeArguments(type);
|
|
707
|
+
const signatures = queries.signaturesOf(type, SignatureKind.Call);
|
|
708
|
+
if (!isAnswered(parts) || !isAnswered(args) || !isAnswered(signatures)) {
|
|
709
|
+
return true;
|
|
710
|
+
}
|
|
711
|
+
const inner = [
|
|
712
|
+
...parts,
|
|
713
|
+
...args,
|
|
714
|
+
...signatures.flatMap((signature) => signature.parameters.map((_parameter, at) => queries.typeAtPosition(signature, at))),
|
|
715
|
+
];
|
|
716
|
+
return inner.some((one) => !isAnswered(one) || namesTypeParameter(project, one, depth + 1));
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Whether the call a function expression is written in may read the annotation of its
|
|
720
|
+
* parameter at `at`: the callee is generic, and its parameter type at the function's place
|
|
721
|
+
* reads a type parameter there. A function written inside an object literal a generic
|
|
722
|
+
* call is passed is read so too, and a question the checker leaves unanswered reads it.
|
|
723
|
+
*/
|
|
724
|
+
function calleeSteers(project, signature, at) {
|
|
725
|
+
const { queries } = project;
|
|
726
|
+
let value = signature;
|
|
727
|
+
while (isParenthesizedExpression(value.parent)) {
|
|
728
|
+
value = value.parent;
|
|
729
|
+
}
|
|
730
|
+
let nested = false;
|
|
731
|
+
while ((isPropertyAssignment(value.parent) && isObjectLiteralExpression(value.parent.parent)) ||
|
|
732
|
+
isObjectLiteralExpression(value.parent)) {
|
|
733
|
+
value = isObjectLiteralExpression(value.parent) ? value.parent : value.parent.parent;
|
|
734
|
+
nested = true;
|
|
735
|
+
}
|
|
736
|
+
const call = value.parent;
|
|
737
|
+
if (!isCallExpression(call) && !isNewExpression(call)) {
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
const argument = (call.arguments ?? []).findIndex((one) => one === value);
|
|
741
|
+
const resolved = argument < 0 ? undefined : queries.resolvedSignature(call);
|
|
742
|
+
const generic = resolved === undefined || !isAnswered(resolved) ? resolved : queries.genericOf(resolved);
|
|
743
|
+
if (argument < 0 || generic === undefined) {
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
if (!isAnswered(generic) || nested) {
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
const position = queries.typeAtPosition(generic, argument);
|
|
750
|
+
if (!isAnswered(position) || position.isTypeParameter()) {
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
const parts = position.isUnionType() || position.isIntersectionType()
|
|
754
|
+
? queries.constituents(position)
|
|
755
|
+
: [position];
|
|
756
|
+
if (!isAnswered(parts)) {
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
759
|
+
return parts.some((part) => {
|
|
760
|
+
if (part.isTypeParameter()) {
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
763
|
+
const calls = queries.signaturesOf(part, SignatureKind.Call);
|
|
764
|
+
if (!isAnswered(calls)) {
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
return calls.some((one) => {
|
|
768
|
+
const type = at < one.parameters.length ? queries.typeAtPosition(one, at) : undefined;
|
|
769
|
+
return type !== undefined && (!isAnswered(type) || namesTypeParameter(project, type));
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
}
|
|
588
773
|
/**
|
|
589
|
-
* The parts with each name of `contextual`
|
|
590
|
-
* contextual type: the
|
|
591
|
-
*
|
|
592
|
-
* A question the checker leaves unanswered keeps the
|
|
774
|
+
* The parts with each name of `contextual` judged where the compiler gives its function a
|
|
775
|
+
* contextual type: the code handing the argument may act on what an object pattern reads,
|
|
776
|
+
* so each such name is read, and an annotated parameter is marked with whether the call's
|
|
777
|
+
* inference may read its annotation. A question the checker leaves unanswered keeps the
|
|
778
|
+
* name read.
|
|
593
779
|
*/
|
|
594
780
|
function withContextualRead(project, parts, contextual) {
|
|
595
781
|
if (contextual.length === 0) {
|
|
@@ -597,9 +783,25 @@ function withContextualRead(project, parts, contextual) {
|
|
|
597
783
|
}
|
|
598
784
|
const signatures = [...new Set(contextual.map((one) => one.signature))];
|
|
599
785
|
const answers = project.queries.contextualTypes(signatures);
|
|
600
|
-
const
|
|
601
|
-
const
|
|
602
|
-
|
|
786
|
+
const typedBy = new Map(signatures.map((signature, at) => [signature, answers[at]]));
|
|
787
|
+
const judged = new Map();
|
|
788
|
+
for (const one of contextual) {
|
|
789
|
+
const answer = typedBy.get(one.signature);
|
|
790
|
+
if (answer === undefined) {
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
const place = one.part.parameter;
|
|
794
|
+
judged.set(one.part, one.pattern || answer === UNANSWERED || place === undefined
|
|
795
|
+
? { ...one.part, used: true }
|
|
796
|
+
: {
|
|
797
|
+
...one.part,
|
|
798
|
+
parameter: {
|
|
799
|
+
...place,
|
|
800
|
+
annotated: calleeSteers(project, one.signature, one.at) ? "steers" : "free",
|
|
801
|
+
},
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
return parts.map((part) => judged.get(part) ?? part);
|
|
603
805
|
}
|
|
604
806
|
/**
|
|
605
807
|
* Each name one function's parameters bind and whether the function reads it, and the
|
|
@@ -607,9 +809,11 @@ function withContextualRead(project, parts, contextual) {
|
|
|
607
809
|
* that carries a type annotation or is an object binding pattern. A binding pattern counts
|
|
608
810
|
* as read where its function reads any name it binds.
|
|
609
811
|
*/
|
|
610
|
-
function parameterParts(file, root, declaration, signature, reader, ids,
|
|
611
|
-
// A function that reads `arguments` reads every parameter by position.
|
|
612
|
-
|
|
812
|
+
function parameterParts(file, root, declaration, signature, reader, ids, place) {
|
|
813
|
+
// A function that reads its `arguments` reads every parameter by position. A nested
|
|
814
|
+
// function other than an arrow has an `arguments` object of its own.
|
|
815
|
+
const readsArguments = !isArrowFunction(signature) &&
|
|
816
|
+
identifiersNaming(signature, new Set(["arguments"])).some((use) => ownArgumentsOf(use) === signature);
|
|
613
817
|
const read = reader.names.map(({ name }) => readsArguments ||
|
|
614
818
|
reader.uses.some((use) => use.text === name.text && readsBinding(use, name, ids)));
|
|
615
819
|
const lastRead = Math.max(-1, ...reader.names.filter((_, at) => read[at]).map((one) => one.at));
|
|
@@ -628,7 +832,7 @@ function parameterParts(file, root, declaration, signature, reader, ids, inValue
|
|
|
628
832
|
position: spanOf(file, root, name),
|
|
629
833
|
message: `parameter ${name.text} is never read in the body`,
|
|
630
834
|
used: read[index] === true,
|
|
631
|
-
parameter: { beforeARead: !pattern && at < lastRead,
|
|
835
|
+
parameter: { beforeARead: !pattern && at < lastRead, ...place },
|
|
632
836
|
};
|
|
633
837
|
found.push(part);
|
|
634
838
|
const parameter = signature.parameters[at];
|
|
@@ -636,42 +840,79 @@ function parameterParts(file, root, declaration, signature, reader, ids, inValue
|
|
|
636
840
|
!part.used &&
|
|
637
841
|
parameter !== undefined &&
|
|
638
842
|
(parameter.type !== undefined || isObjectBindingPattern(parameter.name))) {
|
|
639
|
-
contextual.push({
|
|
843
|
+
contextual.push({
|
|
844
|
+
part,
|
|
845
|
+
signature: expression,
|
|
846
|
+
at,
|
|
847
|
+
pattern: isObjectBindingPattern(parameter.name),
|
|
848
|
+
});
|
|
640
849
|
}
|
|
641
850
|
});
|
|
642
851
|
return { parts: found, contextual };
|
|
643
852
|
}
|
|
644
853
|
/**
|
|
645
854
|
* The names one declaration binds that a write of its initializer stores each on its own:
|
|
646
|
-
* the declared name, or each element name of
|
|
855
|
+
* the declared name, or each element name of a binding pattern at any depth. A name with a
|
|
856
|
+
* default is left out, because removing it skips the default's evaluation, and so is a name
|
|
857
|
+
* beside a rest element of its object pattern, whose removal changes what the rest holds.
|
|
647
858
|
*/
|
|
648
|
-
function
|
|
859
|
+
function boundNames(name) {
|
|
649
860
|
if (isIdentifier(name)) {
|
|
650
861
|
return [name];
|
|
651
862
|
}
|
|
652
|
-
|
|
653
|
-
|
|
863
|
+
const rest = isObjectBindingPattern(name) &&
|
|
864
|
+
name.elements.some((element) => element.dotDotDotToken !== undefined);
|
|
865
|
+
return name.elements.flatMap((element) => {
|
|
866
|
+
if (!isBindingElement(element) || element.name === undefined) {
|
|
867
|
+
return [];
|
|
868
|
+
}
|
|
869
|
+
if (element.initializer !== undefined || (rest && element.dotDotDotToken === undefined)) {
|
|
870
|
+
return [];
|
|
871
|
+
}
|
|
872
|
+
return boundNames(element.name);
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
/** The function whose scope a `var` of one block belongs to, or none at a module's top level. */
|
|
876
|
+
function functionOf(block) {
|
|
877
|
+
for (let at = block.parent;; at = at.parent) {
|
|
878
|
+
if (isFunctionLikeDeclaration(at)) {
|
|
879
|
+
return at;
|
|
880
|
+
}
|
|
881
|
+
if (isSourceFile(at)) {
|
|
882
|
+
return undefined;
|
|
883
|
+
}
|
|
654
884
|
}
|
|
655
|
-
return name.elements.flatMap((element) => isBindingElement(element) && element.name !== undefined ? arrayPatternNames(element.name) : []);
|
|
656
885
|
}
|
|
657
|
-
/**
|
|
886
|
+
/**
|
|
887
|
+
* The locals one block declares with `let`, `const` or `var`, each with the names of the
|
|
888
|
+
* block that spell it. A `var` of a nested block is one where no nested function of its
|
|
889
|
+
* function names it, since the function scope holds it past the block's end.
|
|
890
|
+
*/
|
|
658
891
|
function localsOf(block) {
|
|
659
892
|
const declared = [];
|
|
893
|
+
const scope = functionOf(block);
|
|
894
|
+
const outlives = scope?.body !== block;
|
|
660
895
|
block.statements.forEach((statement, at) => {
|
|
661
896
|
if (!isVariableStatement(statement)) {
|
|
662
897
|
return;
|
|
663
898
|
}
|
|
664
899
|
const list = statement.declarationList;
|
|
665
|
-
|
|
666
|
-
|
|
900
|
+
const blockScoped = (list.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0;
|
|
901
|
+
if ((!blockScoped && scope === undefined) || (list.flags & NodeFlags.Using) !== 0) {
|
|
667
902
|
return;
|
|
668
903
|
}
|
|
669
904
|
for (const declaration of list.declarations) {
|
|
670
|
-
for (const name of
|
|
905
|
+
for (const name of boundNames(declaration.name)) {
|
|
906
|
+
if (!blockScoped &&
|
|
907
|
+
outlives &&
|
|
908
|
+
identifiersNaming(scope?.body ?? block, new Set([name.text])).some((use) => inNestedFunction(use, scope ?? block))) {
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
671
911
|
declared.push({
|
|
672
912
|
name,
|
|
673
913
|
statement: at,
|
|
674
914
|
initialized: declaration.initializer !== undefined,
|
|
915
|
+
outlives: !blockScoped && outlives,
|
|
675
916
|
});
|
|
676
917
|
}
|
|
677
918
|
}
|
|
@@ -685,19 +926,39 @@ function localsOf(block) {
|
|
|
685
926
|
uses: spelled.filter((use) => use !== one.name && use.text === one.name.text),
|
|
686
927
|
}));
|
|
687
928
|
}
|
|
929
|
+
/** Whether one name is the whole target of a plain assignment, which writes it and reads nothing. */
|
|
930
|
+
function isPlainWrite(use) {
|
|
931
|
+
const parent = outerParent(use);
|
|
932
|
+
return (isBinaryExpression(parent) &&
|
|
933
|
+
parent.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
934
|
+
unwrapped(parent.left) === use);
|
|
935
|
+
}
|
|
688
936
|
/**
|
|
689
937
|
* The writes to the locals of one block that no read reaches. A write is a declaration's
|
|
690
|
-
* initializer or a plain assignment written as a statement of the block
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
*
|
|
694
|
-
* and
|
|
695
|
-
* at any time.
|
|
938
|
+
* initializer or a plain assignment written as a statement of the block; it is dead when
|
|
939
|
+
* the block's later statements read the local nowhere before the next such write or the
|
|
940
|
+
* block's end. A statement writing it inside a nested construct ends the search with the
|
|
941
|
+
* write kept. A local a nested function reads is no subject, since the function may run at
|
|
942
|
+
* any time, and every write to a local nothing reads is dead, a nested function's included.
|
|
696
943
|
*/
|
|
697
944
|
function deadStores(file, root, block, holder, locals, ids) {
|
|
698
945
|
const found = [];
|
|
946
|
+
const deadAt = (local, target) => ({
|
|
947
|
+
code: DEAD_STORE,
|
|
948
|
+
declaration: holder,
|
|
949
|
+
kind: "store",
|
|
950
|
+
name: local.name.text,
|
|
951
|
+
position: spanOf(file, root, target),
|
|
952
|
+
message: `value written to ${local.name.text} is never read`,
|
|
953
|
+
used: false,
|
|
954
|
+
});
|
|
699
955
|
for (const local of locals) {
|
|
700
956
|
const uses = local.uses.filter((use) => readsBinding(use, local.name, ids));
|
|
957
|
+
if (!local.outlives && uses.every(isPlainWrite)) {
|
|
958
|
+
const targets = [...(local.initialized ? [local.name] : []), ...uses];
|
|
959
|
+
found.push(...targets.map((target) => deadAt(local, target)));
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
701
962
|
if (uses.some((use) => inNestedFunction(use, block))) {
|
|
702
963
|
continue;
|
|
703
964
|
}
|
|
@@ -735,7 +996,7 @@ function deadStores(file, root, block, holder, locals, ids) {
|
|
|
735
996
|
if (sameStatement.length > 0) {
|
|
736
997
|
continue;
|
|
737
998
|
}
|
|
738
|
-
let dead =
|
|
999
|
+
let dead = !local.outlives;
|
|
739
1000
|
for (let at = write.at + 1; at < statements.length; at += 1) {
|
|
740
1001
|
const statement = statements[at];
|
|
741
1002
|
if (statement === undefined) {
|
|
@@ -752,19 +1013,9 @@ function deadStores(file, root, block, holder, locals, ids) {
|
|
|
752
1013
|
break;
|
|
753
1014
|
}
|
|
754
1015
|
}
|
|
755
|
-
if (
|
|
756
|
-
|
|
1016
|
+
if (dead) {
|
|
1017
|
+
found.push(deadAt(local, write.target));
|
|
757
1018
|
}
|
|
758
|
-
const position = spanOf(file, root, write.target);
|
|
759
|
-
found.push({
|
|
760
|
-
code: DEAD_STORE,
|
|
761
|
-
declaration: holder,
|
|
762
|
-
kind: "store",
|
|
763
|
-
name: local.name.text,
|
|
764
|
-
position,
|
|
765
|
-
message: `value written to ${local.name.text} is never read`,
|
|
766
|
-
used: false,
|
|
767
|
-
});
|
|
768
1019
|
}
|
|
769
1020
|
}
|
|
770
1021
|
return found;
|
|
@@ -828,6 +1079,12 @@ export function intraFunctionFacts(projects, references, exempted) {
|
|
|
828
1079
|
const known = parts.get(key);
|
|
829
1080
|
parts.set(key, known === undefined ? part : { ...known, used: known.used || part.used });
|
|
830
1081
|
}
|
|
1082
|
+
const imports = new Map();
|
|
1083
|
+
for (const binding of projects.flatMap((one) => one.imports)) {
|
|
1084
|
+
const key = positionKey(binding.position);
|
|
1085
|
+
const known = imports.get(key);
|
|
1086
|
+
imports.set(key, known === undefined ? binding : { ...known, used: known.used || binding.used });
|
|
1087
|
+
}
|
|
831
1088
|
const calls = new Map();
|
|
832
1089
|
const specifiers = new Set();
|
|
833
1090
|
for (const one of projects) {
|
|
@@ -874,5 +1131,11 @@ export function intraFunctionFacts(projects, references, exempted) {
|
|
|
874
1131
|
const discardedEverywhere = new Set([...called]
|
|
875
1132
|
.filter(([id, all]) => all && !valued.has(id) && !sequenced.has(id))
|
|
876
1133
|
.map(([id]) => id));
|
|
877
|
-
return {
|
|
1134
|
+
return {
|
|
1135
|
+
parts: [...parts.values()],
|
|
1136
|
+
imports: [...imports.values()],
|
|
1137
|
+
free,
|
|
1138
|
+
freeButValued,
|
|
1139
|
+
discardedEverywhere,
|
|
1140
|
+
};
|
|
878
1141
|
}
|