@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
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
isArrayBindingPattern,
|
|
10
9
|
isArrowFunction,
|
|
11
10
|
isAwaitExpression,
|
|
12
11
|
isBigIntLiteral,
|
|
@@ -29,6 +28,9 @@ import {
|
|
|
29
28
|
isExpressionStatement,
|
|
30
29
|
isFunctionDeclaration,
|
|
31
30
|
isFunctionExpression,
|
|
31
|
+
isForInStatement,
|
|
32
|
+
isForOfStatement,
|
|
33
|
+
isFunctionLikeDeclaration,
|
|
32
34
|
isHeritageClause,
|
|
33
35
|
isIdentifier,
|
|
34
36
|
isIfStatement,
|
|
@@ -45,15 +47,18 @@ import {
|
|
|
45
47
|
isObjectLiteralExpression,
|
|
46
48
|
isParenthesizedExpression,
|
|
47
49
|
isPrefixUnaryExpression,
|
|
50
|
+
isPropertyAssignment,
|
|
48
51
|
isPropertyAccessExpression,
|
|
49
52
|
isQualifiedName,
|
|
50
53
|
isReturnStatement,
|
|
51
54
|
isShorthandPropertyAssignment,
|
|
55
|
+
isSourceFile,
|
|
52
56
|
isStringLiteral,
|
|
53
57
|
isSwitchStatement,
|
|
54
58
|
isThrowStatement,
|
|
55
59
|
isTypeAliasDeclaration,
|
|
56
60
|
isTypeReferenceNode,
|
|
61
|
+
isVariableDeclarationList,
|
|
57
62
|
isVariableStatement,
|
|
58
63
|
isVoidExpression,
|
|
59
64
|
NodeFlags,
|
|
@@ -64,19 +69,32 @@ import {
|
|
|
64
69
|
type CallExpression,
|
|
65
70
|
type CatchClause,
|
|
66
71
|
type ConstructorDeclaration,
|
|
72
|
+
type ForInStatement,
|
|
73
|
+
type ForOfStatement,
|
|
74
|
+
type FunctionLikeDeclaration,
|
|
67
75
|
type FunctionExpression,
|
|
68
76
|
type Identifier,
|
|
69
77
|
type Node,
|
|
70
78
|
type SourceFile,
|
|
71
79
|
type Statement,
|
|
80
|
+
type VariableDeclarationList,
|
|
72
81
|
} from "@typescript/native/ast";
|
|
82
|
+
import { SignatureKind, type Type } from "@typescript/native/sync";
|
|
73
83
|
import type { FindingPosition } from "./finding.ts";
|
|
84
|
+
import {
|
|
85
|
+
exportsLocally,
|
|
86
|
+
implicitReads,
|
|
87
|
+
importBindings,
|
|
88
|
+
type JsxOptions,
|
|
89
|
+
} from "./import-bindings.ts";
|
|
74
90
|
import { isParameterProperty, nodeKey, type Inventory } from "./inventory.ts";
|
|
75
91
|
import { positionKey, renderPosition } from "./position.ts";
|
|
92
|
+
import { importRef } from "./ref.ts";
|
|
76
93
|
import { isAnswered, UNANSWERED, type Answer } from "./query.ts";
|
|
77
94
|
import type { Reference } from "./references.ts";
|
|
78
95
|
import type { ProjectView } from "./session.ts";
|
|
79
96
|
import { signatureOf, type SignatureNode } from "./signatures.ts";
|
|
97
|
+
import { spellsNoBinding } from "./spelled-names.ts";
|
|
80
98
|
|
|
81
99
|
/** The subject kind of each part this module reads. */
|
|
82
100
|
type PartKind = "parameter" | "result" | "statement" | "store" | "case";
|
|
@@ -113,6 +131,14 @@ interface ParameterPlace {
|
|
|
113
131
|
readonly beforeARead: boolean;
|
|
114
132
|
/** Whether no declaration of the inventory declares the function, which is then a value. */
|
|
115
133
|
readonly inValue: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Where the parameter carries an annotation and the compiler gives its function a
|
|
136
|
+
* contextual type: whether a type parameter of the callee reads the parameter's type,
|
|
137
|
+
* so the annotation may steer the call's inference.
|
|
138
|
+
*/
|
|
139
|
+
readonly annotated?: "steers" | "free";
|
|
140
|
+
/** Whether the compiler checks the parameter's file, so its unused-parameter rule may report it. */
|
|
141
|
+
readonly checked: boolean;
|
|
116
142
|
}
|
|
117
143
|
|
|
118
144
|
/** What one declaration's signature answers to beside its body. */
|
|
@@ -126,9 +152,18 @@ interface SignatureFact {
|
|
|
126
152
|
readonly constructor: boolean;
|
|
127
153
|
}
|
|
128
154
|
|
|
155
|
+
/** One name an import declaration binds, and whether its file reads it. */
|
|
156
|
+
interface ImportFact {
|
|
157
|
+
readonly ref: string;
|
|
158
|
+
readonly name: string;
|
|
159
|
+
readonly position: FindingPosition;
|
|
160
|
+
readonly used: boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
129
163
|
/** What one project answers about the parts of its declarations. */
|
|
130
164
|
export interface ProjectParts {
|
|
131
165
|
readonly parts: readonly PartFact[];
|
|
166
|
+
readonly imports: readonly ImportFact[];
|
|
132
167
|
readonly signatures: readonly SignatureFact[];
|
|
133
168
|
/** Each name written as the callee of a call, by position, and what the call does with its value. */
|
|
134
169
|
readonly calls: ReadonlyMap<string, CallUse>;
|
|
@@ -268,13 +303,12 @@ function caseKey(expression: Node): string | undefined {
|
|
|
268
303
|
return undefined;
|
|
269
304
|
}
|
|
270
305
|
|
|
271
|
-
/** Every identifier below one node whose text one of the names spells
|
|
306
|
+
/** Every identifier below one node whose text one of the names spells as a reference. */
|
|
272
307
|
function identifiersNaming(node: Node, names: ReadonlySet<string>): Identifier[] {
|
|
273
308
|
const found: Identifier[] = [];
|
|
274
309
|
const visit = (at: Node): void => {
|
|
275
310
|
if (isIdentifier(at)) {
|
|
276
|
-
|
|
277
|
-
if (names.has(at.text) && !(isPropertyAccessExpression(parent) && parent.name === at)) {
|
|
311
|
+
if (names.has(at.text) && !spellsNoBinding(at)) {
|
|
278
312
|
found.push(at);
|
|
279
313
|
}
|
|
280
314
|
return;
|
|
@@ -310,6 +344,16 @@ function inNestedFunction(node: Node, outer: Node): boolean {
|
|
|
310
344
|
return false;
|
|
311
345
|
}
|
|
312
346
|
|
|
347
|
+
/** The nearest function around one node whose `arguments` object it names: an arrow has none. */
|
|
348
|
+
function ownArgumentsOf(node: Node): Node | undefined {
|
|
349
|
+
for (let at = node.parent; !isSourceFile(at); at = at.parent) {
|
|
350
|
+
if (isFunctionLikeDeclaration(at) && !isArrowFunction(at)) {
|
|
351
|
+
return at;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
|
|
313
357
|
/** Whether a body is a stub: it holds no statement, or nothing but `throw` statements. */
|
|
314
358
|
function isStub(signature: SignatureNode): boolean {
|
|
315
359
|
const body = signature.body;
|
|
@@ -498,10 +542,49 @@ interface ParameterName {
|
|
|
498
542
|
/** The parameter's index in its list. */
|
|
499
543
|
readonly at: number;
|
|
500
544
|
readonly pattern: boolean;
|
|
501
|
-
/**
|
|
545
|
+
/**
|
|
546
|
+
* Whether the name is never reported: it is spelled declared unread, or it stands beside a
|
|
547
|
+
* rest element of its object pattern, whose removal changes what the rest holds.
|
|
548
|
+
*/
|
|
502
549
|
readonly exempt: boolean;
|
|
503
550
|
}
|
|
504
551
|
|
|
552
|
+
/** Whether a name is spelled with a leading underscore, which declares it unread. */
|
|
553
|
+
function declaredUnread(name: Identifier): boolean {
|
|
554
|
+
return name.text.startsWith("_");
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/** A file's leading comments, where the compiler reads its `@ts-check` and `@ts-nocheck` pragmas. */
|
|
558
|
+
const LEADING_COMMENTS = /^(?:#![^\n]*)?(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*/u;
|
|
559
|
+
|
|
560
|
+
/** A comment of the leading comments, a line comment's text after its slashes captured. */
|
|
561
|
+
const LEADING_COMMENT = /\/\*[\s\S]*?\*\/|\/\/\/?([^\n]*)/gu;
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Whether a file's leading comments hold one pragma: as the compiler reads it, a line
|
|
565
|
+
* comment that starts with the pragma's name, in any case, followed by nothing or a space.
|
|
566
|
+
*/
|
|
567
|
+
function hasPragma(leading: string, pragma: RegExp): boolean {
|
|
568
|
+
return [...leading.matchAll(LEADING_COMMENT)].some(
|
|
569
|
+
([, text]) => text !== undefined && pragma.test(text),
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Whether the compiler checks a file, so its rules report what they find there: a
|
|
575
|
+
* JavaScript file only under `checkJs` or a `@ts-check` pragma, and no file with a
|
|
576
|
+
* `@ts-nocheck` pragma.
|
|
577
|
+
*/
|
|
578
|
+
function checkedByTheCompiler(file: SourceFile, checkJs: boolean): boolean {
|
|
579
|
+
const leading = LEADING_COMMENTS.exec(file.text)?.[0] ?? "";
|
|
580
|
+
if (hasPragma(leading, /^\s*@ts-nocheck(?:\s|$)/iu)) {
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
return (
|
|
584
|
+
!/\.[cm]?jsx?$/u.test(file.fileName) || checkJs || hasPragma(leading, /^\s*@ts-check(?:\s|$)/iu)
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
505
588
|
/** What one file's walk collects before the batch that resolves its names. */
|
|
506
589
|
interface FileWalk {
|
|
507
590
|
readonly declared: Declared[];
|
|
@@ -512,6 +595,8 @@ interface FileWalk {
|
|
|
512
595
|
readonly holder: string;
|
|
513
596
|
}[];
|
|
514
597
|
readonly blocks: Block[];
|
|
598
|
+
/** The loops whose `let` or `const` variables each iteration writes. */
|
|
599
|
+
readonly loops: { readonly loop: ForOfStatement | ForInStatement; readonly holder: string }[];
|
|
515
600
|
readonly unreachable: { readonly statement: Statement; readonly holder: string }[];
|
|
516
601
|
readonly cases: { readonly clause: Node; readonly text: string; readonly holder: string }[];
|
|
517
602
|
}
|
|
@@ -536,8 +621,13 @@ export function projectParts<Brand>(
|
|
|
536
621
|
const constructions = new Set<string>();
|
|
537
622
|
const sites: { readonly file: SourceFile; readonly declared: Declared }[] = [];
|
|
538
623
|
const contextual: ContextualParameter[] = [];
|
|
624
|
+
const imports: ImportFact[] = [];
|
|
625
|
+
const refs = new Map(held.symbols.map((symbol) => [symbol.id, symbol.ref]));
|
|
626
|
+
const options = project.program.getCompilerOptions();
|
|
627
|
+
const jsx = options as JsxOptions;
|
|
539
628
|
|
|
540
629
|
for (const file of files) {
|
|
630
|
+
const checked = checkedByTheCompiler(file, options.checkJs === true);
|
|
541
631
|
// A parameter property's own parts stay with the parameter when its member goes.
|
|
542
632
|
const declarationAt = (node: Node): string | undefined =>
|
|
543
633
|
isParameterProperty(node) ? undefined : held.declarations.get(nodeKey(file, node));
|
|
@@ -557,6 +647,7 @@ export function projectParts<Brand>(
|
|
|
557
647
|
values: [],
|
|
558
648
|
catches: [],
|
|
559
649
|
blocks: [],
|
|
650
|
+
loops: [],
|
|
560
651
|
unreachable: [],
|
|
561
652
|
cases: [],
|
|
562
653
|
};
|
|
@@ -655,6 +746,18 @@ export function projectParts<Brand>(
|
|
|
655
746
|
if (isBlock(node) && silenced === 0 && holderOf(node) !== undefined) {
|
|
656
747
|
walk.blocks.push(node);
|
|
657
748
|
}
|
|
749
|
+
const loopHolder =
|
|
750
|
+
(isForOfStatement(node) || isForInStatement(node)) && silenced === 0
|
|
751
|
+
? holderOf(node)
|
|
752
|
+
: undefined;
|
|
753
|
+
if (
|
|
754
|
+
loopHolder !== undefined &&
|
|
755
|
+
(isForOfStatement(node) || isForInStatement(node)) &&
|
|
756
|
+
isVariableDeclarationList(node.initializer) &&
|
|
757
|
+
(node.initializer.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0
|
|
758
|
+
) {
|
|
759
|
+
walk.loops.push({ loop: node, holder: loopHolder });
|
|
760
|
+
}
|
|
658
761
|
if (isBlock(node) || isCaseClause(node) || isDefaultClause(node)) {
|
|
659
762
|
statementsOf(node.statements);
|
|
660
763
|
return;
|
|
@@ -687,7 +790,44 @@ export function projectParts<Brand>(
|
|
|
687
790
|
for (const store of stores) {
|
|
688
791
|
asked.push(...store.flatMap((local) => [local.name, ...local.uses]));
|
|
689
792
|
}
|
|
793
|
+
const loopVariables = walk.loops.map(({ loop, holder }) => {
|
|
794
|
+
const list = loop.initializer as VariableDeclarationList;
|
|
795
|
+
// A loop must bind a name, so one spelled declared unread is no subject.
|
|
796
|
+
const names = list.declarations
|
|
797
|
+
.flatMap((declaration) => boundNames(declaration.name))
|
|
798
|
+
.filter((name) => !declaredUnread(name));
|
|
799
|
+
const uses = identifiersNaming(loop.statement, new Set(names.map((one) => one.text)));
|
|
800
|
+
asked.push(...names, ...uses);
|
|
801
|
+
return { names, uses, holder };
|
|
802
|
+
});
|
|
803
|
+
const bindings = fileId === undefined ? [] : importBindings(file);
|
|
804
|
+
const bound = new Set<Node>(bindings);
|
|
805
|
+
const bindingUses =
|
|
806
|
+
bindings.length === 0
|
|
807
|
+
? []
|
|
808
|
+
: identifiersNaming(file, new Set(bindings.map((one) => one.text))).filter(
|
|
809
|
+
(use) => !bound.has(use),
|
|
810
|
+
);
|
|
811
|
+
asked.push(...bindings, ...bindingUses);
|
|
690
812
|
const ids = resolvedIds(project, [...new Set(asked)]);
|
|
813
|
+
const implicit = bindings.length === 0 ? new Set<string>() : implicitReads(file, jsx);
|
|
814
|
+
for (const binding of bindings) {
|
|
815
|
+
const fileRef = fileId === undefined ? undefined : refs.get(fileId);
|
|
816
|
+
if (fileId === undefined || fileRef === undefined) {
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
imports.push({
|
|
820
|
+
ref: importRef(fileRef, binding.text),
|
|
821
|
+
name: binding.text,
|
|
822
|
+
position: spanOf(file, targetRoot, binding),
|
|
823
|
+
used:
|
|
824
|
+
implicit.has(binding.text) ||
|
|
825
|
+
bindingUses.some(
|
|
826
|
+
(use) =>
|
|
827
|
+
use.text === binding.text && (exportsLocally(use) || readsBinding(use, binding, ids)),
|
|
828
|
+
),
|
|
829
|
+
});
|
|
830
|
+
}
|
|
691
831
|
|
|
692
832
|
for (const one of walk.declared) {
|
|
693
833
|
const constructor = isConstructorDeclaration(one.node);
|
|
@@ -702,7 +842,10 @@ export function projectParts<Brand>(
|
|
|
702
842
|
});
|
|
703
843
|
const reader = readers.get(one.signature);
|
|
704
844
|
if (reader !== undefined) {
|
|
705
|
-
const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids,
|
|
845
|
+
const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, {
|
|
846
|
+
inValue: false,
|
|
847
|
+
checked,
|
|
848
|
+
});
|
|
706
849
|
parts.push(...found.parts);
|
|
707
850
|
contextual.push(...found.contextual);
|
|
708
851
|
}
|
|
@@ -710,15 +853,10 @@ export function projectParts<Brand>(
|
|
|
710
853
|
for (const one of walk.values) {
|
|
711
854
|
const reader = readers.get(one.signature);
|
|
712
855
|
if (reader !== undefined) {
|
|
713
|
-
const found = parameterParts(
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
one.signature,
|
|
718
|
-
reader,
|
|
719
|
-
ids,
|
|
720
|
-
true,
|
|
721
|
-
);
|
|
856
|
+
const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids, {
|
|
857
|
+
inValue: true,
|
|
858
|
+
checked,
|
|
859
|
+
});
|
|
722
860
|
parts.push(...found.parts);
|
|
723
861
|
contextual.push(...found.contextual);
|
|
724
862
|
}
|
|
@@ -734,6 +872,19 @@ export function projectParts<Brand>(
|
|
|
734
872
|
used: (caught[at] ?? []).some((use) => readsBinding(use, name, ids)),
|
|
735
873
|
});
|
|
736
874
|
});
|
|
875
|
+
for (const { names, uses, holder } of loopVariables) {
|
|
876
|
+
for (const name of names) {
|
|
877
|
+
parts.push({
|
|
878
|
+
code: DEAD_STORE,
|
|
879
|
+
declaration: holder,
|
|
880
|
+
kind: "store",
|
|
881
|
+
name: name.text,
|
|
882
|
+
position: spanOf(file, targetRoot, name),
|
|
883
|
+
message: `value written to ${name.text} is never read`,
|
|
884
|
+
used: uses.some((use) => use.text === name.text && readsBinding(use, name, ids)),
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
}
|
|
737
888
|
walk.blocks.forEach((block, at) => {
|
|
738
889
|
const holder = holderOf(block);
|
|
739
890
|
if (holder !== undefined) {
|
|
@@ -767,6 +918,7 @@ export function projectParts<Brand>(
|
|
|
767
918
|
parts.push(...resultParts(project, targetRoot, sites, references, calls));
|
|
768
919
|
return {
|
|
769
920
|
parts: withContextualRead(project, parts, contextual),
|
|
921
|
+
imports,
|
|
770
922
|
signatures,
|
|
771
923
|
calls,
|
|
772
924
|
specifiers,
|
|
@@ -795,7 +947,9 @@ function valueSignature(node: Node): SignatureNode | undefined {
|
|
|
795
947
|
/**
|
|
796
948
|
* Every name one function's parameters bind, a binding pattern's names one by one. A
|
|
797
949
|
* `this` parameter receives no argument, a decorated one is read by its decorator, and a
|
|
798
|
-
* parameter property is read by the write to its member, so none binds a subject.
|
|
950
|
+
* parameter property is read by the write to its member, so none binds a subject. A name
|
|
951
|
+
* spelled with a leading underscore is declared unread, so it is listed for its reads and
|
|
952
|
+
* reported never.
|
|
799
953
|
*/
|
|
800
954
|
function parameterNames(signature: SignatureNode): ParameterName[] {
|
|
801
955
|
const found: ParameterName[] = [];
|
|
@@ -808,7 +962,8 @@ function parameterNames(signature: SignatureNode): ParameterName[] {
|
|
|
808
962
|
continue;
|
|
809
963
|
}
|
|
810
964
|
if (isIdentifier(element.name)) {
|
|
811
|
-
const exempt =
|
|
965
|
+
const exempt =
|
|
966
|
+
(rest && element.dotDotDotToken === undefined) || declaredUnread(element.name);
|
|
812
967
|
found.push({ name: element.name, at, pattern: true, exempt });
|
|
813
968
|
} else {
|
|
814
969
|
patternNames(element.name, at);
|
|
@@ -825,7 +980,7 @@ function parameterNames(signature: SignatureNode): ParameterName[] {
|
|
|
825
980
|
}
|
|
826
981
|
if (isIdentifier(name)) {
|
|
827
982
|
if (name.text !== "this") {
|
|
828
|
-
found.push({ name, at, pattern: false, exempt:
|
|
983
|
+
found.push({ name, at, pattern: false, exempt: declaredUnread(name) });
|
|
829
984
|
}
|
|
830
985
|
return;
|
|
831
986
|
}
|
|
@@ -838,13 +993,110 @@ function parameterNames(signature: SignatureNode): ParameterName[] {
|
|
|
838
993
|
interface ContextualParameter {
|
|
839
994
|
readonly part: PartFact;
|
|
840
995
|
readonly signature: ArrowFunction | FunctionExpression;
|
|
996
|
+
/** The parameter's index in its list. */
|
|
997
|
+
readonly at: number;
|
|
998
|
+
/** Whether the parameter is an object binding pattern, rather than an annotated name. */
|
|
999
|
+
readonly pattern: boolean;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/** How deep a type is searched for a type parameter: a callback of a callback is two levels. */
|
|
1003
|
+
const TYPE_PARAMETER_DEPTH = 3;
|
|
1004
|
+
|
|
1005
|
+
/** Whether one type is or holds a type parameter, through constituents, type arguments and signatures. */
|
|
1006
|
+
function namesTypeParameter<Brand>(project: ProjectView<Brand>, type: Type, depth = 0): boolean {
|
|
1007
|
+
if (type.isTypeParameter()) {
|
|
1008
|
+
return true;
|
|
1009
|
+
}
|
|
1010
|
+
if (depth >= TYPE_PARAMETER_DEPTH) {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
const { queries } = project;
|
|
1014
|
+
const parts = type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [];
|
|
1015
|
+
const args = queries.typeArguments(type);
|
|
1016
|
+
const signatures = queries.signaturesOf(type, SignatureKind.Call);
|
|
1017
|
+
if (!isAnswered(parts) || !isAnswered(args) || !isAnswered(signatures)) {
|
|
1018
|
+
return true;
|
|
1019
|
+
}
|
|
1020
|
+
const inner = [
|
|
1021
|
+
...parts,
|
|
1022
|
+
...args,
|
|
1023
|
+
...signatures.flatMap((signature) =>
|
|
1024
|
+
signature.parameters.map((_parameter, at) => queries.typeAtPosition(signature, at)),
|
|
1025
|
+
),
|
|
1026
|
+
];
|
|
1027
|
+
return inner.some((one) => !isAnswered(one) || namesTypeParameter(project, one, depth + 1));
|
|
841
1028
|
}
|
|
842
1029
|
|
|
843
1030
|
/**
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
1031
|
+
* Whether the call a function expression is written in may read the annotation of its
|
|
1032
|
+
* parameter at `at`: the callee is generic, and its parameter type at the function's place
|
|
1033
|
+
* reads a type parameter there. A function written inside an object literal a generic
|
|
1034
|
+
* call is passed is read so too, and a question the checker leaves unanswered reads it.
|
|
1035
|
+
*/
|
|
1036
|
+
function calleeSteers<Brand>(
|
|
1037
|
+
project: ProjectView<Brand>,
|
|
1038
|
+
signature: ArrowFunction | FunctionExpression,
|
|
1039
|
+
at: number,
|
|
1040
|
+
): boolean {
|
|
1041
|
+
const { queries } = project;
|
|
1042
|
+
let value: Node = signature;
|
|
1043
|
+
while (isParenthesizedExpression(value.parent)) {
|
|
1044
|
+
value = value.parent;
|
|
1045
|
+
}
|
|
1046
|
+
let nested = false;
|
|
1047
|
+
while (
|
|
1048
|
+
(isPropertyAssignment(value.parent) && isObjectLiteralExpression(value.parent.parent)) ||
|
|
1049
|
+
isObjectLiteralExpression(value.parent)
|
|
1050
|
+
) {
|
|
1051
|
+
value = isObjectLiteralExpression(value.parent) ? value.parent : value.parent.parent;
|
|
1052
|
+
nested = true;
|
|
1053
|
+
}
|
|
1054
|
+
const call = value.parent;
|
|
1055
|
+
if (!isCallExpression(call) && !isNewExpression(call)) {
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
const argument = (call.arguments ?? []).findIndex((one) => one === value);
|
|
1059
|
+
const resolved = argument < 0 ? undefined : queries.resolvedSignature(call);
|
|
1060
|
+
const generic =
|
|
1061
|
+
resolved === undefined || !isAnswered(resolved) ? resolved : queries.genericOf(resolved);
|
|
1062
|
+
if (argument < 0 || generic === undefined) {
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
1065
|
+
if (!isAnswered(generic) || nested) {
|
|
1066
|
+
return true;
|
|
1067
|
+
}
|
|
1068
|
+
const position = queries.typeAtPosition(generic, argument);
|
|
1069
|
+
if (!isAnswered(position) || position.isTypeParameter()) {
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
1072
|
+
const parts =
|
|
1073
|
+
position.isUnionType() || position.isIntersectionType()
|
|
1074
|
+
? queries.constituents(position)
|
|
1075
|
+
: [position];
|
|
1076
|
+
if (!isAnswered(parts)) {
|
|
1077
|
+
return true;
|
|
1078
|
+
}
|
|
1079
|
+
return parts.some((part) => {
|
|
1080
|
+
if (part.isTypeParameter()) {
|
|
1081
|
+
return true;
|
|
1082
|
+
}
|
|
1083
|
+
const calls = queries.signaturesOf(part, SignatureKind.Call);
|
|
1084
|
+
if (!isAnswered(calls)) {
|
|
1085
|
+
return true;
|
|
1086
|
+
}
|
|
1087
|
+
return calls.some((one) => {
|
|
1088
|
+
const type = at < one.parameters.length ? queries.typeAtPosition(one, at) : undefined;
|
|
1089
|
+
return type !== undefined && (!isAnswered(type) || namesTypeParameter(project, type));
|
|
1090
|
+
});
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* The parts with each name of `contextual` judged where the compiler gives its function a
|
|
1096
|
+
* contextual type: the code handing the argument may act on what an object pattern reads,
|
|
1097
|
+
* so each such name is read, and an annotated parameter is marked with whether the call's
|
|
1098
|
+
* inference may read its annotation. A question the checker leaves unanswered keeps the
|
|
1099
|
+
* name read.
|
|
848
1100
|
*/
|
|
849
1101
|
function withContextualRead<Brand>(
|
|
850
1102
|
project: ProjectView<Brand>,
|
|
@@ -856,9 +1108,28 @@ function withContextualRead<Brand>(
|
|
|
856
1108
|
}
|
|
857
1109
|
const signatures = [...new Set(contextual.map((one) => one.signature))];
|
|
858
1110
|
const answers = project.queries.contextualTypes(signatures);
|
|
859
|
-
const
|
|
860
|
-
const
|
|
861
|
-
|
|
1111
|
+
const typedBy = new Map(signatures.map((signature, at) => [signature, answers[at]]));
|
|
1112
|
+
const judged = new Map<PartFact, PartFact>();
|
|
1113
|
+
for (const one of contextual) {
|
|
1114
|
+
const answer = typedBy.get(one.signature);
|
|
1115
|
+
if (answer === undefined) {
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
const place = one.part.parameter;
|
|
1119
|
+
judged.set(
|
|
1120
|
+
one.part,
|
|
1121
|
+
one.pattern || answer === UNANSWERED || place === undefined
|
|
1122
|
+
? { ...one.part, used: true }
|
|
1123
|
+
: {
|
|
1124
|
+
...one.part,
|
|
1125
|
+
parameter: {
|
|
1126
|
+
...place,
|
|
1127
|
+
annotated: calleeSteers(project, one.signature, one.at) ? "steers" : "free",
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
return parts.map((part) => judged.get(part) ?? part);
|
|
862
1133
|
}
|
|
863
1134
|
|
|
864
1135
|
/**
|
|
@@ -874,11 +1145,15 @@ function parameterParts(
|
|
|
874
1145
|
signature: SignatureNode,
|
|
875
1146
|
reader: { readonly names: readonly ParameterName[]; readonly uses: readonly Identifier[] },
|
|
876
1147
|
ids: ReadonlyMap<Node, Answer<number>>,
|
|
877
|
-
|
|
1148
|
+
place: Pick<ParameterPlace, "inValue" | "checked">,
|
|
878
1149
|
): { readonly parts: PartFact[]; readonly contextual: ContextualParameter[] } {
|
|
879
|
-
// A function that reads `arguments` reads every parameter by position.
|
|
1150
|
+
// A function that reads its `arguments` reads every parameter by position. A nested
|
|
1151
|
+
// function other than an arrow has an `arguments` object of its own.
|
|
880
1152
|
const readsArguments =
|
|
881
|
-
!isArrowFunction(signature) &&
|
|
1153
|
+
!isArrowFunction(signature) &&
|
|
1154
|
+
identifiersNaming(signature, new Set(["arguments"])).some(
|
|
1155
|
+
(use) => ownArgumentsOf(use) === signature,
|
|
1156
|
+
);
|
|
882
1157
|
const read = reader.names.map(
|
|
883
1158
|
({ name }) =>
|
|
884
1159
|
readsArguments ||
|
|
@@ -901,7 +1176,7 @@ function parameterParts(
|
|
|
901
1176
|
position: spanOf(file, root, name),
|
|
902
1177
|
message: `parameter ${name.text} is never read in the body`,
|
|
903
1178
|
used: read[index] === true,
|
|
904
|
-
parameter: { beforeARead: !pattern && at < lastRead,
|
|
1179
|
+
parameter: { beforeARead: !pattern && at < lastRead, ...place },
|
|
905
1180
|
};
|
|
906
1181
|
found.push(part);
|
|
907
1182
|
const parameter = signature.parameters[at];
|
|
@@ -911,7 +1186,12 @@ function parameterParts(
|
|
|
911
1186
|
parameter !== undefined &&
|
|
912
1187
|
(parameter.type !== undefined || isObjectBindingPattern(parameter.name))
|
|
913
1188
|
) {
|
|
914
|
-
contextual.push({
|
|
1189
|
+
contextual.push({
|
|
1190
|
+
part,
|
|
1191
|
+
signature: expression,
|
|
1192
|
+
at,
|
|
1193
|
+
pattern: isObjectBindingPattern(parameter.name),
|
|
1194
|
+
});
|
|
915
1195
|
}
|
|
916
1196
|
});
|
|
917
1197
|
return { parts: found, contextual };
|
|
@@ -923,44 +1203,85 @@ interface Local {
|
|
|
923
1203
|
readonly statement: number;
|
|
924
1204
|
readonly initialized: boolean;
|
|
925
1205
|
readonly uses: readonly Identifier[];
|
|
1206
|
+
/** A `var` of a nested block, whose function scope outlives the block's end. */
|
|
1207
|
+
readonly outlives: boolean;
|
|
926
1208
|
}
|
|
927
1209
|
|
|
928
1210
|
/**
|
|
929
1211
|
* The names one declaration binds that a write of its initializer stores each on its own:
|
|
930
|
-
* the declared name, or each element name of
|
|
1212
|
+
* the declared name, or each element name of a binding pattern at any depth. A name with a
|
|
1213
|
+
* default is left out, because removing it skips the default's evaluation, and so is a name
|
|
1214
|
+
* beside a rest element of its object pattern, whose removal changes what the rest holds.
|
|
931
1215
|
*/
|
|
932
|
-
function
|
|
1216
|
+
function boundNames(name: BindingName): readonly Identifier[] {
|
|
933
1217
|
if (isIdentifier(name)) {
|
|
934
1218
|
return [name];
|
|
935
1219
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1220
|
+
const rest =
|
|
1221
|
+
isObjectBindingPattern(name) &&
|
|
1222
|
+
name.elements.some((element) => element.dotDotDotToken !== undefined);
|
|
1223
|
+
return name.elements.flatMap((element) => {
|
|
1224
|
+
if (!isBindingElement(element) || element.name === undefined) {
|
|
1225
|
+
return [];
|
|
1226
|
+
}
|
|
1227
|
+
if (element.initializer !== undefined || (rest && element.dotDotDotToken === undefined)) {
|
|
1228
|
+
return [];
|
|
1229
|
+
}
|
|
1230
|
+
return boundNames(element.name);
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/** The function whose scope a `var` of one block belongs to, or none at a module's top level. */
|
|
1235
|
+
function functionOf(block: Block): FunctionLikeDeclaration | undefined {
|
|
1236
|
+
for (let at: Node = block.parent; ; at = at.parent) {
|
|
1237
|
+
if (isFunctionLikeDeclaration(at)) {
|
|
1238
|
+
return at;
|
|
1239
|
+
}
|
|
1240
|
+
if (isSourceFile(at)) {
|
|
1241
|
+
return undefined;
|
|
1242
|
+
}
|
|
938
1243
|
}
|
|
939
|
-
return name.elements.flatMap((element) =>
|
|
940
|
-
isBindingElement(element) && element.name !== undefined ? arrayPatternNames(element.name) : [],
|
|
941
|
-
);
|
|
942
1244
|
}
|
|
943
1245
|
|
|
944
|
-
/**
|
|
1246
|
+
/**
|
|
1247
|
+
* The locals one block declares with `let`, `const` or `var`, each with the names of the
|
|
1248
|
+
* block that spell it. A `var` of a nested block is one where no nested function of its
|
|
1249
|
+
* function names it, since the function scope holds it past the block's end.
|
|
1250
|
+
*/
|
|
945
1251
|
function localsOf(block: Block): readonly Local[] {
|
|
946
|
-
const declared: {
|
|
1252
|
+
const declared: {
|
|
1253
|
+
name: Identifier;
|
|
1254
|
+
statement: number;
|
|
1255
|
+
initialized: boolean;
|
|
1256
|
+
outlives: boolean;
|
|
1257
|
+
}[] = [];
|
|
1258
|
+
const scope = functionOf(block);
|
|
1259
|
+
const outlives = scope?.body !== block;
|
|
947
1260
|
block.statements.forEach((statement, at) => {
|
|
948
1261
|
if (!isVariableStatement(statement)) {
|
|
949
1262
|
return;
|
|
950
1263
|
}
|
|
951
1264
|
const list = statement.declarationList;
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
(list.flags & NodeFlags.Using) !== 0
|
|
955
|
-
) {
|
|
1265
|
+
const blockScoped = (list.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0;
|
|
1266
|
+
if ((!blockScoped && scope === undefined) || (list.flags & NodeFlags.Using) !== 0) {
|
|
956
1267
|
return;
|
|
957
1268
|
}
|
|
958
1269
|
for (const declaration of list.declarations) {
|
|
959
|
-
for (const name of
|
|
1270
|
+
for (const name of boundNames(declaration.name)) {
|
|
1271
|
+
if (
|
|
1272
|
+
!blockScoped &&
|
|
1273
|
+
outlives &&
|
|
1274
|
+
identifiersNaming(scope?.body ?? block, new Set([name.text])).some((use) =>
|
|
1275
|
+
inNestedFunction(use, scope ?? block),
|
|
1276
|
+
)
|
|
1277
|
+
) {
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
960
1280
|
declared.push({
|
|
961
1281
|
name,
|
|
962
1282
|
statement: at,
|
|
963
1283
|
initialized: declaration.initializer !== undefined,
|
|
1284
|
+
outlives: !blockScoped && outlives,
|
|
964
1285
|
});
|
|
965
1286
|
}
|
|
966
1287
|
}
|
|
@@ -975,14 +1296,23 @@ function localsOf(block: Block): readonly Local[] {
|
|
|
975
1296
|
}));
|
|
976
1297
|
}
|
|
977
1298
|
|
|
1299
|
+
/** Whether one name is the whole target of a plain assignment, which writes it and reads nothing. */
|
|
1300
|
+
function isPlainWrite(use: Identifier): boolean {
|
|
1301
|
+
const parent = outerParent(use);
|
|
1302
|
+
return (
|
|
1303
|
+
isBinaryExpression(parent) &&
|
|
1304
|
+
parent.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
1305
|
+
unwrapped(parent.left) === use
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
978
1309
|
/**
|
|
979
1310
|
* The writes to the locals of one block that no read reaches. A write is a declaration's
|
|
980
|
-
* initializer or a plain assignment written as a statement of the block
|
|
981
|
-
*
|
|
982
|
-
*
|
|
983
|
-
*
|
|
984
|
-
* and
|
|
985
|
-
* at any time.
|
|
1311
|
+
* initializer or a plain assignment written as a statement of the block; it is dead when
|
|
1312
|
+
* the block's later statements read the local nowhere before the next such write or the
|
|
1313
|
+
* block's end. A statement writing it inside a nested construct ends the search with the
|
|
1314
|
+
* write kept. A local a nested function reads is no subject, since the function may run at
|
|
1315
|
+
* any time, and every write to a local nothing reads is dead, a nested function's included.
|
|
986
1316
|
*/
|
|
987
1317
|
function deadStores(
|
|
988
1318
|
file: SourceFile,
|
|
@@ -993,8 +1323,22 @@ function deadStores(
|
|
|
993
1323
|
ids: ReadonlyMap<Node, Answer<number>>,
|
|
994
1324
|
): PartFact[] {
|
|
995
1325
|
const found: PartFact[] = [];
|
|
1326
|
+
const deadAt = (local: Local, target: Identifier): PartFact => ({
|
|
1327
|
+
code: DEAD_STORE,
|
|
1328
|
+
declaration: holder,
|
|
1329
|
+
kind: "store",
|
|
1330
|
+
name: local.name.text,
|
|
1331
|
+
position: spanOf(file, root, target),
|
|
1332
|
+
message: `value written to ${local.name.text} is never read`,
|
|
1333
|
+
used: false,
|
|
1334
|
+
});
|
|
996
1335
|
for (const local of locals) {
|
|
997
1336
|
const uses = local.uses.filter((use) => readsBinding(use, local.name, ids));
|
|
1337
|
+
if (!local.outlives && uses.every(isPlainWrite)) {
|
|
1338
|
+
const targets = [...(local.initialized ? [local.name] : []), ...uses];
|
|
1339
|
+
found.push(...targets.map((target) => deadAt(local, target)));
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
998
1342
|
if (uses.some((use) => inNestedFunction(use, block))) {
|
|
999
1343
|
continue;
|
|
1000
1344
|
}
|
|
@@ -1034,7 +1378,7 @@ function deadStores(
|
|
|
1034
1378
|
if (sameStatement.length > 0) {
|
|
1035
1379
|
continue;
|
|
1036
1380
|
}
|
|
1037
|
-
let dead =
|
|
1381
|
+
let dead = !local.outlives;
|
|
1038
1382
|
for (let at = write.at + 1; at < statements.length; at += 1) {
|
|
1039
1383
|
const statement = statements[at];
|
|
1040
1384
|
if (statement === undefined) {
|
|
@@ -1051,19 +1395,9 @@ function deadStores(
|
|
|
1051
1395
|
break;
|
|
1052
1396
|
}
|
|
1053
1397
|
}
|
|
1054
|
-
if (
|
|
1055
|
-
|
|
1398
|
+
if (dead) {
|
|
1399
|
+
found.push(deadAt(local, write.target));
|
|
1056
1400
|
}
|
|
1057
|
-
const position = spanOf(file, root, write.target);
|
|
1058
|
-
found.push({
|
|
1059
|
-
code: DEAD_STORE,
|
|
1060
|
-
declaration: holder,
|
|
1061
|
-
kind: "store",
|
|
1062
|
-
name: local.name.text,
|
|
1063
|
-
position,
|
|
1064
|
-
message: `value written to ${local.name.text} is never read`,
|
|
1065
|
-
used: false,
|
|
1066
|
-
});
|
|
1067
1401
|
}
|
|
1068
1402
|
}
|
|
1069
1403
|
return found;
|
|
@@ -1126,6 +1460,8 @@ function resultParts<Brand>(
|
|
|
1126
1460
|
export interface IntraFunctionFacts {
|
|
1127
1461
|
/** Every part of the run, each once, in use where any project found it in use. */
|
|
1128
1462
|
readonly parts: readonly PartFact[];
|
|
1463
|
+
/** Every import binding of the run, each once, read where any project found it read. */
|
|
1464
|
+
readonly imports: readonly ImportFact[];
|
|
1129
1465
|
/** The declarations whose signature is free to change. */
|
|
1130
1466
|
readonly free: ReadonlySet<string>;
|
|
1131
1467
|
/** The declarations whose signature is free but for a use as a value. */
|
|
@@ -1156,6 +1492,15 @@ export function intraFunctionFacts(
|
|
|
1156
1492
|
const known = parts.get(key);
|
|
1157
1493
|
parts.set(key, known === undefined ? part : { ...known, used: known.used || part.used });
|
|
1158
1494
|
}
|
|
1495
|
+
const imports = new Map<string, ImportFact>();
|
|
1496
|
+
for (const binding of projects.flatMap((one) => one.imports)) {
|
|
1497
|
+
const key = positionKey(binding.position);
|
|
1498
|
+
const known = imports.get(key);
|
|
1499
|
+
imports.set(
|
|
1500
|
+
key,
|
|
1501
|
+
known === undefined ? binding : { ...known, used: known.used || binding.used },
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1159
1504
|
const calls = new Map<string, CallUse>();
|
|
1160
1505
|
const specifiers = new Set<string>();
|
|
1161
1506
|
for (const one of projects) {
|
|
@@ -1215,5 +1560,11 @@ export function intraFunctionFacts(
|
|
|
1215
1560
|
.filter(([id, all]) => all && !valued.has(id) && !sequenced.has(id))
|
|
1216
1561
|
.map(([id]) => id),
|
|
1217
1562
|
);
|
|
1218
|
-
return {
|
|
1563
|
+
return {
|
|
1564
|
+
parts: [...parts.values()],
|
|
1565
|
+
imports: [...imports.values()],
|
|
1566
|
+
free,
|
|
1567
|
+
freeButValued,
|
|
1568
|
+
discardedEverywhere,
|
|
1569
|
+
};
|
|
1219
1570
|
}
|