@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10
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/README.md +1 -1
- package/dist/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/analysis.js +245 -309
- package/dist/src/augmentations.js +124 -18
- package/dist/src/automatic-types.js +170 -0
- package/dist/src/build-scripts.js +425 -0
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +9 -9
- package/dist/src/config.js +6 -81
- package/dist/src/configuration-files.js +78 -188
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/consumers.js +3 -30
- package/dist/src/contextual-uses.js +56 -9
- package/dist/src/contract/config.generated.js +558 -0
- package/dist/src/contract/contract.generated.js +1244 -0
- package/dist/src/contract/report.generated.js +862 -0
- package/dist/src/convention-rows.js +146 -1
- package/dist/src/conventions.js +136 -70
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +280 -62
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +9 -18
- package/dist/src/entry-points.js +106 -21
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +1 -63
- package/dist/src/exempt.js +21 -40
- package/dist/src/failure.js +36 -0
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +225 -63
- package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
- package/dist/src/findings/emitters.js +6 -6
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +53 -19
- package/dist/src/findings/non-code-artifacts.js +71 -39
- package/dist/src/findings/reads-and-writes.js +16 -13
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +57 -28
- package/dist/src/findings/visibility-narrowing.js +40 -18
- package/dist/src/findings-pass.js +8 -15
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +52 -0
- package/dist/src/fs/manifest.js +72 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/generated-file.js +18 -4
- package/dist/src/git-ignore.js +99 -0
- package/dist/src/graph.js +9 -4
- package/dist/src/html-entries.js +6 -33
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/in-operands.js +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/injection-container.js +1 -1
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +341 -69
- package/dist/src/inventory.js +54 -29
- package/dist/src/javascript-file.js +33 -0
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +3 -366
- package/dist/src/load/classify.js +270 -0
- package/dist/src/load/diagnostics.js +80 -0
- package/dist/src/load/discover.js +277 -0
- package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
- package/dist/src/load/expected-module.js +136 -0
- package/dist/src/load/load.js +27 -0
- package/dist/src/load/module-lookup.js +37 -0
- package/dist/src/load/plan.js +313 -0
- package/dist/src/load/specifiers.js +115 -0
- package/dist/src/load/synthesize.js +140 -0
- package/dist/src/load/test-globals.js +207 -0
- package/dist/src/load/test-joins.js +82 -0
- package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
- package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
- package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
- package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
- package/dist/src/{workspace.js → load/workspace.js} +62 -124
- package/dist/src/manifest.js +49 -54
- package/dist/src/matrix.js +94 -13
- package/dist/src/mdx-entries.js +174 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/overrides.js +41 -10
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +11 -78
- package/dist/src/product.js +13 -0
- package/dist/src/query.js +1 -0
- package/dist/src/ref.js +11 -0
- package/dist/src/references.js +52 -31
- package/dist/src/report-schema.js +51 -0
- package/dist/src/report.js +59 -41
- package/dist/src/reporters/annotations.js +15 -8
- package/dist/src/reporters/sarif.js +16 -10
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -727
- package/dist/src/roots.js +374 -116
- package/dist/src/run.js +66 -77
- package/dist/src/schema.js +320 -342
- package/dist/src/scope.js +38 -115
- package/dist/src/serialization-contract.js +649 -29
- package/dist/src/session.js +41 -29
- package/dist/src/source-files.js +40 -15
- package/dist/src/suppress-file.js +34 -29
- package/dist/src/suppress-inline.js +21 -4
- package/dist/src/suppress.js +40 -56
- package/dist/src/sweep.js +32 -17
- package/dist/src/symbol-keys.js +105 -6
- package/dist/src/template-field.js +9 -19
- package/dist/src/test-support.js +119 -18
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/named-files.js +54 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/text/yaml.js +393 -0
- package/dist/src/type-errors.js +27 -221
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/unseen-loads.js +100 -0
- package/dist/src/unwrap.js +23 -0
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +181 -125
- package/dist/src/verbs/describe.js +9 -16
- package/dist/src/verbs/explain.js +14 -9
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +6 -4
- package/dist/src/verbs/print-retained.js +4 -2
- package/dist/src/verbs/print-roots.js +4 -2
- package/dist/src/verbs/verb.js +1 -5
- package/dist/src/verbs/version.js +14 -3
- package/dist/src/workflow-steps.js +128 -33
- package/jsr.json +1 -1
- package/package.json +5 -4
- package/src/analysis.ts +333 -457
- package/src/augmentations.ts +152 -23
- package/src/automatic-types.ts +228 -0
- package/src/build-scripts.ts +543 -0
- package/src/component-layer.ts +4 -2
- package/src/components.ts +9 -10
- package/src/config.ts +31 -299
- package/src/configuration-files.ts +100 -191
- package/src/configured-declarations.ts +18 -23
- package/src/conformance-record.ts +2 -2
- package/src/consumers.ts +6 -57
- package/src/contextual-uses.ts +64 -12
- package/src/contract/config.generated.ts +818 -0
- package/src/contract/contract.generated.ts +1308 -0
- package/src/contract/report.generated.ts +1237 -0
- package/src/convention-rows.ts +186 -1
- package/src/conventions.ts +166 -90
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +356 -78
- package/src/edges.ts +41 -140
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +11 -20
- package/src/entry-points.ts +124 -28
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +1 -75
- package/src/exempt.ts +36 -50
- package/src/failure.ts +63 -0
- package/src/file-facts.ts +9 -24
- package/src/finding.ts +24 -9
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +297 -77
- package/src/findings/dependencies-and-module-machinery.ts +90 -31
- package/src/findings/emitter.ts +8 -9
- package/src/findings/emitters.ts +8 -8
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +64 -20
- package/src/findings/non-code-artifacts.ts +88 -50
- package/src/findings/reads-and-writes.ts +20 -16
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +65 -28
- package/src/findings/visibility-narrowing.ts +51 -19
- package/src/findings-pass.ts +10 -19
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +70 -0
- package/src/fs/manifest.ts +92 -0
- package/src/fs/tree.ts +110 -0
- package/src/generated-file.ts +23 -4
- package/src/git-ignore.ts +122 -0
- package/src/graph.ts +11 -3
- package/src/host.ts +17 -14
- package/src/html-entries.ts +6 -33
- package/src/import-bindings.ts +128 -0
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/indexed-access.ts +152 -30
- package/src/injection-container.ts +1 -1
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +449 -85
- package/src/inventory.ts +73 -27
- package/src/javascript-file.ts +37 -0
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +5 -413
- package/src/load/classify.ts +397 -0
- package/src/load/diagnostics.ts +144 -0
- package/src/load/discover.ts +455 -0
- package/src/{emit-map.ts → load/emit-map.ts} +1 -1
- package/src/load/expected-module.ts +188 -0
- package/src/load/load.ts +73 -0
- package/src/load/module-lookup.ts +50 -0
- package/src/load/plan.ts +504 -0
- package/src/load/specifiers.ts +168 -0
- package/src/load/synthesize.ts +211 -0
- package/src/load/test-globals.ts +251 -0
- package/src/load/test-joins.ts +98 -0
- package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
- package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
- package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
- package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
- package/src/{workspace.ts → load/workspace.ts} +90 -134
- package/src/manifest.ts +81 -75
- package/src/matrix.ts +154 -18
- package/src/mdx-entries.ts +218 -0
- package/src/module-declarations.ts +11 -13
- package/src/outside-tests.ts +1 -1
- package/src/overrides.ts +51 -12
- package/src/partial.ts +3 -2
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +12 -91
- package/src/product.ts +18 -0
- package/src/query.ts +11 -0
- package/src/ref.ts +13 -0
- package/src/references.ts +62 -32
- package/src/report-schema.ts +68 -0
- package/src/report.ts +105 -215
- package/src/reporters/annotations.ts +22 -11
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -4
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +29 -16
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +283 -1111
- package/src/roots.ts +490 -137
- package/src/run.ts +75 -91
- package/src/schema.ts +391 -387
- package/src/scope.ts +46 -138
- package/src/serialization-contract.ts +818 -26
- package/src/session.ts +77 -54
- package/src/source-files.ts +67 -17
- package/src/suppress-file.ts +36 -34
- package/src/suppress-inline.ts +20 -3
- package/src/suppress.ts +52 -63
- package/src/sweep.ts +53 -18
- package/src/symbol-keys.ts +131 -7
- package/src/template-field.ts +15 -25
- package/src/test-support.ts +155 -18
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/named-files.inputs.json +1522 -0
- package/src/text/named-files.ts +68 -0
- package/src/text/shell.ts +161 -0
- package/src/text/yaml.ts +423 -0
- package/src/type-errors.ts +35 -273
- package/src/type-parameter-reads.ts +227 -0
- package/src/unseen-loads.ts +134 -0
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +245 -161
- package/src/verbs/describe.ts +14 -15
- package/src/verbs/explain.ts +15 -13
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +6 -4
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +2 -8
- package/src/verbs/version.ts +15 -3
- package/src/workflow-steps.ts +149 -34
- package/dist/src/discover.js +0 -347
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/dist/src/setup-failure.js +0 -23
- package/dist/src/test-joins.js +0 -101
- package/dist/src/version.js +0 -7
- package/src/discover.ts +0 -510
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/setup-failure.ts +0 -39
- package/src/test-joins.ts +0 -120
- package/src/version.ts +0 -7
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* write to a local that no read reaches, and each case an earlier case covers.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { CODE } from "./contract/contract.generated.ts";
|
|
8
9
|
import {
|
|
9
|
-
isArrayBindingPattern,
|
|
10
10
|
isArrowFunction,
|
|
11
11
|
isAwaitExpression,
|
|
12
12
|
isBigIntLiteral,
|
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
isExpressionStatement,
|
|
30
30
|
isFunctionDeclaration,
|
|
31
31
|
isFunctionExpression,
|
|
32
|
+
isForInStatement,
|
|
33
|
+
isForOfStatement,
|
|
32
34
|
isFunctionLikeDeclaration,
|
|
33
35
|
isHeritageClause,
|
|
34
36
|
isIdentifier,
|
|
@@ -46,6 +48,7 @@ import {
|
|
|
46
48
|
isObjectLiteralExpression,
|
|
47
49
|
isParenthesizedExpression,
|
|
48
50
|
isPrefixUnaryExpression,
|
|
51
|
+
isPropertyAssignment,
|
|
49
52
|
isPropertyAccessExpression,
|
|
50
53
|
isQualifiedName,
|
|
51
54
|
isReturnStatement,
|
|
@@ -56,6 +59,7 @@ import {
|
|
|
56
59
|
isThrowStatement,
|
|
57
60
|
isTypeAliasDeclaration,
|
|
58
61
|
isTypeReferenceNode,
|
|
62
|
+
isVariableDeclarationList,
|
|
59
63
|
isVariableStatement,
|
|
60
64
|
isVoidExpression,
|
|
61
65
|
NodeFlags,
|
|
@@ -66,20 +70,33 @@ import {
|
|
|
66
70
|
type CallExpression,
|
|
67
71
|
type CatchClause,
|
|
68
72
|
type ConstructorDeclaration,
|
|
73
|
+
type ForInStatement,
|
|
74
|
+
type ForOfStatement,
|
|
75
|
+
type FunctionLikeDeclaration,
|
|
69
76
|
type FunctionExpression,
|
|
70
77
|
type Identifier,
|
|
71
78
|
type Node,
|
|
72
79
|
type SourceFile,
|
|
73
80
|
type Statement,
|
|
81
|
+
type VariableDeclarationList,
|
|
74
82
|
} from "@typescript/native/ast";
|
|
83
|
+
import { SignatureKind, type Type } from "@typescript/native/sync";
|
|
75
84
|
import type { FindingPosition } from "./finding.ts";
|
|
85
|
+
import {
|
|
86
|
+
exportsLocally,
|
|
87
|
+
implicitReads,
|
|
88
|
+
importBindings,
|
|
89
|
+
type JsxOptions,
|
|
90
|
+
} from "./import-bindings.ts";
|
|
76
91
|
import { isParameterProperty, nodeKey, type Inventory } from "./inventory.ts";
|
|
77
92
|
import { positionKey, renderPosition } from "./position.ts";
|
|
93
|
+
import { importRef } from "./ref.ts";
|
|
78
94
|
import { isAnswered, UNANSWERED, type Answer } from "./query.ts";
|
|
79
95
|
import type { Reference } from "./references.ts";
|
|
80
96
|
import type { ProjectView } from "./session.ts";
|
|
81
97
|
import { signatureOf, type SignatureNode } from "./signatures.ts";
|
|
82
98
|
import { spellsNoBinding } from "./spelled-names.ts";
|
|
99
|
+
import { parenthesesRemoved } from "./unwrap.ts";
|
|
83
100
|
|
|
84
101
|
/** The subject kind of each part this module reads. */
|
|
85
102
|
type PartKind = "parameter" | "result" | "statement" | "store" | "case";
|
|
@@ -116,6 +133,20 @@ interface ParameterPlace {
|
|
|
116
133
|
readonly beforeARead: boolean;
|
|
117
134
|
/** Whether no declaration of the inventory declares the function, which is then a value. */
|
|
118
135
|
readonly inValue: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Where the parameter carries an annotation and the compiler gives its function a
|
|
138
|
+
* contextual type: whether a type parameter of the callee reads the parameter's type,
|
|
139
|
+
* so the annotation may steer the call's inference.
|
|
140
|
+
*/
|
|
141
|
+
readonly annotated?: "steers" | "free";
|
|
142
|
+
/**
|
|
143
|
+
* Whether the name is one an object binding pattern of a parameter binds, and the
|
|
144
|
+
* compiler gives its function a contextual type: the code handing the argument may act on
|
|
145
|
+
* the pattern's read of the property, which the program does not show.
|
|
146
|
+
*/
|
|
147
|
+
readonly contextualPattern?: true;
|
|
148
|
+
/** Whether the compiler checks the parameter's file, so its unused-parameter rule may report it. */
|
|
149
|
+
readonly checked: boolean;
|
|
119
150
|
}
|
|
120
151
|
|
|
121
152
|
/** What one declaration's signature answers to beside its body. */
|
|
@@ -129,9 +160,18 @@ interface SignatureFact {
|
|
|
129
160
|
readonly constructor: boolean;
|
|
130
161
|
}
|
|
131
162
|
|
|
163
|
+
/** One name an import declaration binds, and whether its file reads it. */
|
|
164
|
+
interface ImportFact {
|
|
165
|
+
readonly ref: string;
|
|
166
|
+
readonly name: string;
|
|
167
|
+
readonly position: FindingPosition;
|
|
168
|
+
readonly used: boolean;
|
|
169
|
+
}
|
|
170
|
+
|
|
132
171
|
/** What one project answers about the parts of its declarations. */
|
|
133
172
|
export interface ProjectParts {
|
|
134
173
|
readonly parts: readonly PartFact[];
|
|
174
|
+
readonly imports: readonly ImportFact[];
|
|
135
175
|
readonly signatures: readonly SignatureFact[];
|
|
136
176
|
/** Each name written as the callee of a call, by position, and what the call does with its value. */
|
|
137
177
|
readonly calls: ReadonlyMap<string, CallUse>;
|
|
@@ -141,13 +181,6 @@ export interface ProjectParts {
|
|
|
141
181
|
readonly constructions: ReadonlySet<string>;
|
|
142
182
|
}
|
|
143
183
|
|
|
144
|
-
/** The codes of the parts this module reads. */
|
|
145
|
-
export const UNUSED_PARAMETER = "DS1801";
|
|
146
|
-
export const UNUSED_RESULT = "DS1803";
|
|
147
|
-
const UNREACHABLE_STATEMENT = "DS1805";
|
|
148
|
-
const DEAD_STORE = "DS1807";
|
|
149
|
-
const UNREACHABLE_CASE = "DS1809";
|
|
150
|
-
|
|
151
184
|
/** The result types that carry no value, as the checker spells them. */
|
|
152
185
|
const NO_VALUE: ReadonlySet<string> = new Set([
|
|
153
186
|
"void",
|
|
@@ -165,11 +198,6 @@ function spanOf(file: SourceFile, root: string, node: Node): FindingPosition {
|
|
|
165
198
|
return { ...start, endLine };
|
|
166
199
|
}
|
|
167
200
|
|
|
168
|
-
/** The parenthesized expression's content, through every pair of parentheses. */
|
|
169
|
-
function unwrapped(node: Node): Node {
|
|
170
|
-
return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
201
|
/** The node the parentheses around one expression sit in. */
|
|
174
202
|
function outerParent(node: Node): Node {
|
|
175
203
|
return isParenthesizedExpression(node.parent) ? outerParent(node.parent) : node.parent;
|
|
@@ -177,7 +205,7 @@ function outerParent(node: Node): Node {
|
|
|
177
205
|
|
|
178
206
|
/** The name a call is made through: the identifier, or the member a property access names. */
|
|
179
207
|
function calleeName(call: CallExpression): Identifier | undefined {
|
|
180
|
-
const callee =
|
|
208
|
+
const callee = parenthesesRemoved(call.expression);
|
|
181
209
|
if (isIdentifier(callee)) {
|
|
182
210
|
return callee;
|
|
183
211
|
}
|
|
@@ -244,7 +272,7 @@ function unreachableExempt(statement: Statement): boolean {
|
|
|
244
272
|
|
|
245
273
|
/** The value a case expression is a literal of, as a key two equal literals share, or none. */
|
|
246
274
|
function caseKey(expression: Node): string | undefined {
|
|
247
|
-
const node =
|
|
275
|
+
const node = parenthesesRemoved(expression);
|
|
248
276
|
if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
|
|
249
277
|
return `s:${node.text}`;
|
|
250
278
|
}
|
|
@@ -370,7 +398,7 @@ function extendedNames(files: readonly SourceFile[]): ReadonlySet<string> {
|
|
|
370
398
|
if (isTypeReferenceNode(type)) {
|
|
371
399
|
named = isQualifiedName(type.typeName) ? type.typeName.right : type.typeName;
|
|
372
400
|
} else {
|
|
373
|
-
const expression =
|
|
401
|
+
const expression = parenthesesRemoved(type.expression);
|
|
374
402
|
named = isPropertyAccessExpression(expression) ? expression.name : expression;
|
|
375
403
|
}
|
|
376
404
|
if (isIdentifier(named)) {
|
|
@@ -423,7 +451,7 @@ function decoratorFixes(constructor: ConstructorDeclaration): boolean {
|
|
|
423
451
|
|
|
424
452
|
/** The name an expression spells a class by: an identifier, or the member a property access names. */
|
|
425
453
|
function spelledName(expression: Node): Identifier | undefined {
|
|
426
|
-
const node =
|
|
454
|
+
const node = parenthesesRemoved(expression);
|
|
427
455
|
if (isIdentifier(node)) {
|
|
428
456
|
return node;
|
|
429
457
|
}
|
|
@@ -510,10 +538,49 @@ interface ParameterName {
|
|
|
510
538
|
/** The parameter's index in its list. */
|
|
511
539
|
readonly at: number;
|
|
512
540
|
readonly pattern: boolean;
|
|
513
|
-
/**
|
|
541
|
+
/**
|
|
542
|
+
* Whether the name is never reported: it is spelled declared unread, or it stands beside a
|
|
543
|
+
* rest element of its object pattern, whose removal changes what the rest holds.
|
|
544
|
+
*/
|
|
514
545
|
readonly exempt: boolean;
|
|
515
546
|
}
|
|
516
547
|
|
|
548
|
+
/** Whether a name is spelled with a leading underscore, which declares it unread. */
|
|
549
|
+
function declaredUnread(name: Identifier): boolean {
|
|
550
|
+
return name.text.startsWith("_");
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** A file's leading comments, where the compiler reads its `@ts-check` and `@ts-nocheck` pragmas. */
|
|
554
|
+
const LEADING_COMMENTS = /^(?:#![^\n]*)?(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*/u;
|
|
555
|
+
|
|
556
|
+
/** A comment of the leading comments, a line comment's text after its slashes captured. */
|
|
557
|
+
const LEADING_COMMENT = /\/\*[\s\S]*?\*\/|\/\/\/?([^\n]*)/gu;
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Whether a file's leading comments hold one pragma: as the compiler reads it, a line
|
|
561
|
+
* comment that starts with the pragma's name, in any case, followed by nothing or a space.
|
|
562
|
+
*/
|
|
563
|
+
function hasPragma(leading: string, pragma: RegExp): boolean {
|
|
564
|
+
return [...leading.matchAll(LEADING_COMMENT)].some(
|
|
565
|
+
([, text]) => text !== undefined && pragma.test(text),
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Whether the compiler checks a file, so its rules report what they find there: a
|
|
571
|
+
* JavaScript file only under `checkJs` or a `@ts-check` pragma, and no file with a
|
|
572
|
+
* `@ts-nocheck` pragma.
|
|
573
|
+
*/
|
|
574
|
+
function checkedByTheCompiler(file: SourceFile, checkJs: boolean): boolean {
|
|
575
|
+
const leading = LEADING_COMMENTS.exec(file.text)?.[0] ?? "";
|
|
576
|
+
if (hasPragma(leading, /^\s*@ts-nocheck(?:\s|$)/iu)) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
return (
|
|
580
|
+
!/\.[cm]?jsx?$/u.test(file.fileName) || checkJs || hasPragma(leading, /^\s*@ts-check(?:\s|$)/iu)
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
|
|
517
584
|
/** What one file's walk collects before the batch that resolves its names. */
|
|
518
585
|
interface FileWalk {
|
|
519
586
|
readonly declared: Declared[];
|
|
@@ -524,6 +591,8 @@ interface FileWalk {
|
|
|
524
591
|
readonly holder: string;
|
|
525
592
|
}[];
|
|
526
593
|
readonly blocks: Block[];
|
|
594
|
+
/** The loops whose `let` or `const` variables each iteration writes. */
|
|
595
|
+
readonly loops: { readonly loop: ForOfStatement | ForInStatement; readonly holder: string }[];
|
|
527
596
|
readonly unreachable: { readonly statement: Statement; readonly holder: string }[];
|
|
528
597
|
readonly cases: { readonly clause: Node; readonly text: string; readonly holder: string }[];
|
|
529
598
|
}
|
|
@@ -548,8 +617,13 @@ export function projectParts<Brand>(
|
|
|
548
617
|
const constructions = new Set<string>();
|
|
549
618
|
const sites: { readonly file: SourceFile; readonly declared: Declared }[] = [];
|
|
550
619
|
const contextual: ContextualParameter[] = [];
|
|
620
|
+
const imports: ImportFact[] = [];
|
|
621
|
+
const refs = new Map(held.symbols.map((symbol) => [symbol.id, symbol.ref]));
|
|
622
|
+
const options = project.program.getCompilerOptions();
|
|
623
|
+
const jsx = options as JsxOptions;
|
|
551
624
|
|
|
552
625
|
for (const file of files) {
|
|
626
|
+
const checked = checkedByTheCompiler(file, options.checkJs === true);
|
|
553
627
|
// A parameter property's own parts stay with the parameter when its member goes.
|
|
554
628
|
const declarationAt = (node: Node): string | undefined =>
|
|
555
629
|
isParameterProperty(node) ? undefined : held.declarations.get(nodeKey(file, node));
|
|
@@ -569,6 +643,7 @@ export function projectParts<Brand>(
|
|
|
569
643
|
values: [],
|
|
570
644
|
catches: [],
|
|
571
645
|
blocks: [],
|
|
646
|
+
loops: [],
|
|
572
647
|
unreachable: [],
|
|
573
648
|
cases: [],
|
|
574
649
|
};
|
|
@@ -645,8 +720,8 @@ export function projectParts<Brand>(
|
|
|
645
720
|
if ((isImportClause(node) || isNamespaceImport(node)) && node.name !== undefined) {
|
|
646
721
|
specifiers.add(position(node.name));
|
|
647
722
|
}
|
|
648
|
-
if (isExportAssignment(node) && isIdentifier(
|
|
649
|
-
specifiers.add(position(
|
|
723
|
+
if (isExportAssignment(node) && isIdentifier(parenthesesRemoved(node.expression))) {
|
|
724
|
+
specifiers.add(position(parenthesesRemoved(node.expression)));
|
|
650
725
|
}
|
|
651
726
|
if (isSwitchStatement(node)) {
|
|
652
727
|
const holder = holderOf(node);
|
|
@@ -667,6 +742,18 @@ export function projectParts<Brand>(
|
|
|
667
742
|
if (isBlock(node) && silenced === 0 && holderOf(node) !== undefined) {
|
|
668
743
|
walk.blocks.push(node);
|
|
669
744
|
}
|
|
745
|
+
const loopHolder =
|
|
746
|
+
(isForOfStatement(node) || isForInStatement(node)) && silenced === 0
|
|
747
|
+
? holderOf(node)
|
|
748
|
+
: undefined;
|
|
749
|
+
if (
|
|
750
|
+
loopHolder !== undefined &&
|
|
751
|
+
(isForOfStatement(node) || isForInStatement(node)) &&
|
|
752
|
+
isVariableDeclarationList(node.initializer) &&
|
|
753
|
+
(node.initializer.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0
|
|
754
|
+
) {
|
|
755
|
+
walk.loops.push({ loop: node, holder: loopHolder });
|
|
756
|
+
}
|
|
670
757
|
if (isBlock(node) || isCaseClause(node) || isDefaultClause(node)) {
|
|
671
758
|
statementsOf(node.statements);
|
|
672
759
|
return;
|
|
@@ -699,7 +786,44 @@ export function projectParts<Brand>(
|
|
|
699
786
|
for (const store of stores) {
|
|
700
787
|
asked.push(...store.flatMap((local) => [local.name, ...local.uses]));
|
|
701
788
|
}
|
|
789
|
+
const loopVariables = walk.loops.map(({ loop, holder }) => {
|
|
790
|
+
const list = loop.initializer as VariableDeclarationList;
|
|
791
|
+
// A loop must bind a name, so one spelled declared unread is no subject.
|
|
792
|
+
const names = list.declarations
|
|
793
|
+
.flatMap((declaration) => boundNames(declaration.name))
|
|
794
|
+
.filter((name) => !declaredUnread(name));
|
|
795
|
+
const uses = identifiersNaming(loop.statement, new Set(names.map((one) => one.text)));
|
|
796
|
+
asked.push(...names, ...uses);
|
|
797
|
+
return { names, uses, holder };
|
|
798
|
+
});
|
|
799
|
+
const bindings = fileId === undefined ? [] : importBindings(file);
|
|
800
|
+
const bound = new Set<Node>(bindings);
|
|
801
|
+
const bindingUses =
|
|
802
|
+
bindings.length === 0
|
|
803
|
+
? []
|
|
804
|
+
: identifiersNaming(file, new Set(bindings.map((one) => one.text))).filter(
|
|
805
|
+
(use) => !bound.has(use),
|
|
806
|
+
);
|
|
807
|
+
asked.push(...bindings, ...bindingUses);
|
|
702
808
|
const ids = resolvedIds(project, [...new Set(asked)]);
|
|
809
|
+
const implicit = bindings.length === 0 ? new Set<string>() : implicitReads(file, jsx);
|
|
810
|
+
for (const binding of bindings) {
|
|
811
|
+
const fileRef = fileId === undefined ? undefined : refs.get(fileId);
|
|
812
|
+
if (fileId === undefined || fileRef === undefined) {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
imports.push({
|
|
816
|
+
ref: importRef(fileRef, binding.text),
|
|
817
|
+
name: binding.text,
|
|
818
|
+
position: spanOf(file, targetRoot, binding),
|
|
819
|
+
used:
|
|
820
|
+
implicit.has(binding.text) ||
|
|
821
|
+
bindingUses.some(
|
|
822
|
+
(use) =>
|
|
823
|
+
use.text === binding.text && (exportsLocally(use) || readsBinding(use, binding, ids)),
|
|
824
|
+
),
|
|
825
|
+
});
|
|
826
|
+
}
|
|
703
827
|
|
|
704
828
|
for (const one of walk.declared) {
|
|
705
829
|
const constructor = isConstructorDeclaration(one.node);
|
|
@@ -714,7 +838,10 @@ export function projectParts<Brand>(
|
|
|
714
838
|
});
|
|
715
839
|
const reader = readers.get(one.signature);
|
|
716
840
|
if (reader !== undefined) {
|
|
717
|
-
const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids,
|
|
841
|
+
const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, {
|
|
842
|
+
inValue: false,
|
|
843
|
+
checked,
|
|
844
|
+
});
|
|
718
845
|
parts.push(...found.parts);
|
|
719
846
|
contextual.push(...found.contextual);
|
|
720
847
|
}
|
|
@@ -722,22 +849,17 @@ export function projectParts<Brand>(
|
|
|
722
849
|
for (const one of walk.values) {
|
|
723
850
|
const reader = readers.get(one.signature);
|
|
724
851
|
if (reader !== undefined) {
|
|
725
|
-
const found = parameterParts(
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
one.signature,
|
|
730
|
-
reader,
|
|
731
|
-
ids,
|
|
732
|
-
true,
|
|
733
|
-
);
|
|
852
|
+
const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids, {
|
|
853
|
+
inValue: true,
|
|
854
|
+
checked,
|
|
855
|
+
});
|
|
734
856
|
parts.push(...found.parts);
|
|
735
857
|
contextual.push(...found.contextual);
|
|
736
858
|
}
|
|
737
859
|
}
|
|
738
860
|
walk.catches.forEach(({ name, holder }, at) => {
|
|
739
861
|
parts.push({
|
|
740
|
-
code:
|
|
862
|
+
code: CODE.deadStore,
|
|
741
863
|
declaration: holder,
|
|
742
864
|
kind: "store",
|
|
743
865
|
name: name.text,
|
|
@@ -746,6 +868,19 @@ export function projectParts<Brand>(
|
|
|
746
868
|
used: (caught[at] ?? []).some((use) => readsBinding(use, name, ids)),
|
|
747
869
|
});
|
|
748
870
|
});
|
|
871
|
+
for (const { names, uses, holder } of loopVariables) {
|
|
872
|
+
for (const name of names) {
|
|
873
|
+
parts.push({
|
|
874
|
+
code: CODE.deadStore,
|
|
875
|
+
declaration: holder,
|
|
876
|
+
kind: "store",
|
|
877
|
+
name: name.text,
|
|
878
|
+
position: spanOf(file, targetRoot, name),
|
|
879
|
+
message: `value written to ${name.text} is never read`,
|
|
880
|
+
used: uses.some((use) => use.text === name.text && readsBinding(use, name, ids)),
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
}
|
|
749
884
|
walk.blocks.forEach((block, at) => {
|
|
750
885
|
const holder = holderOf(block);
|
|
751
886
|
if (holder !== undefined) {
|
|
@@ -754,7 +889,7 @@ export function projectParts<Brand>(
|
|
|
754
889
|
});
|
|
755
890
|
for (const { statement, holder } of walk.unreachable) {
|
|
756
891
|
parts.push({
|
|
757
|
-
code:
|
|
892
|
+
code: CODE.unreachableStatement,
|
|
758
893
|
declaration: holder,
|
|
759
894
|
kind: "statement",
|
|
760
895
|
name: "statement",
|
|
@@ -765,7 +900,7 @@ export function projectParts<Brand>(
|
|
|
765
900
|
}
|
|
766
901
|
for (const { clause, text, holder } of walk.cases) {
|
|
767
902
|
parts.push({
|
|
768
|
-
code:
|
|
903
|
+
code: CODE.unreachableCase,
|
|
769
904
|
declaration: holder,
|
|
770
905
|
kind: "case",
|
|
771
906
|
name: text,
|
|
@@ -779,6 +914,7 @@ export function projectParts<Brand>(
|
|
|
779
914
|
parts.push(...resultParts(project, targetRoot, sites, references, calls));
|
|
780
915
|
return {
|
|
781
916
|
parts: withContextualRead(project, parts, contextual),
|
|
917
|
+
imports,
|
|
782
918
|
signatures,
|
|
783
919
|
calls,
|
|
784
920
|
specifiers,
|
|
@@ -807,7 +943,9 @@ function valueSignature(node: Node): SignatureNode | undefined {
|
|
|
807
943
|
/**
|
|
808
944
|
* Every name one function's parameters bind, a binding pattern's names one by one. A
|
|
809
945
|
* `this` parameter receives no argument, a decorated one is read by its decorator, and a
|
|
810
|
-
* parameter property is read by the write to its member, so none binds a subject.
|
|
946
|
+
* parameter property is read by the write to its member, so none binds a subject. A name
|
|
947
|
+
* spelled with a leading underscore is declared unread, so it is listed for its reads and
|
|
948
|
+
* reported never.
|
|
811
949
|
*/
|
|
812
950
|
function parameterNames(signature: SignatureNode): ParameterName[] {
|
|
813
951
|
const found: ParameterName[] = [];
|
|
@@ -820,7 +958,8 @@ function parameterNames(signature: SignatureNode): ParameterName[] {
|
|
|
820
958
|
continue;
|
|
821
959
|
}
|
|
822
960
|
if (isIdentifier(element.name)) {
|
|
823
|
-
const exempt =
|
|
961
|
+
const exempt =
|
|
962
|
+
(rest && element.dotDotDotToken === undefined) || declaredUnread(element.name);
|
|
824
963
|
found.push({ name: element.name, at, pattern: true, exempt });
|
|
825
964
|
} else {
|
|
826
965
|
patternNames(element.name, at);
|
|
@@ -837,7 +976,7 @@ function parameterNames(signature: SignatureNode): ParameterName[] {
|
|
|
837
976
|
}
|
|
838
977
|
if (isIdentifier(name)) {
|
|
839
978
|
if (name.text !== "this") {
|
|
840
|
-
found.push({ name, at, pattern: false, exempt:
|
|
979
|
+
found.push({ name, at, pattern: false, exempt: declaredUnread(name) });
|
|
841
980
|
}
|
|
842
981
|
return;
|
|
843
982
|
}
|
|
@@ -850,13 +989,109 @@ function parameterNames(signature: SignatureNode): ParameterName[] {
|
|
|
850
989
|
interface ContextualParameter {
|
|
851
990
|
readonly part: PartFact;
|
|
852
991
|
readonly signature: ArrowFunction | FunctionExpression;
|
|
992
|
+
/** The parameter's index in its list. */
|
|
993
|
+
readonly at: number;
|
|
994
|
+
/** Whether the parameter is an object binding pattern, rather than an annotated name. */
|
|
995
|
+
readonly pattern: boolean;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/** How deep a type is searched for a type parameter: a callback of a callback is two levels. */
|
|
999
|
+
const TYPE_PARAMETER_DEPTH = 3;
|
|
1000
|
+
|
|
1001
|
+
/** Whether one type is or holds a type parameter, through constituents, type arguments and signatures. */
|
|
1002
|
+
function namesTypeParameter<Brand>(project: ProjectView<Brand>, type: Type, depth = 0): boolean {
|
|
1003
|
+
if (type.isTypeParameter()) {
|
|
1004
|
+
return true;
|
|
1005
|
+
}
|
|
1006
|
+
if (depth >= TYPE_PARAMETER_DEPTH) {
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1009
|
+
const { queries } = project;
|
|
1010
|
+
const parts = type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [];
|
|
1011
|
+
const args = queries.typeArguments(type);
|
|
1012
|
+
const signatures = queries.signaturesOf(type, SignatureKind.Call);
|
|
1013
|
+
if (!isAnswered(parts) || !isAnswered(args) || !isAnswered(signatures)) {
|
|
1014
|
+
return true;
|
|
1015
|
+
}
|
|
1016
|
+
const inner = [
|
|
1017
|
+
...parts,
|
|
1018
|
+
...args,
|
|
1019
|
+
...signatures.flatMap((signature) =>
|
|
1020
|
+
signature.parameters.map((_parameter, at) => queries.typeAtPosition(signature, at)),
|
|
1021
|
+
),
|
|
1022
|
+
];
|
|
1023
|
+
return inner.some((one) => !isAnswered(one) || namesTypeParameter(project, one, depth + 1));
|
|
853
1024
|
}
|
|
854
1025
|
|
|
855
1026
|
/**
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
*
|
|
859
|
-
*
|
|
1027
|
+
* Whether the call a function expression is written in may read the annotation of its
|
|
1028
|
+
* parameter at `at`: the callee is generic, and its parameter type at the function's place
|
|
1029
|
+
* reads a type parameter there. A function written inside an object literal a generic
|
|
1030
|
+
* call is passed is read so too, and a question the checker leaves unanswered reads it.
|
|
1031
|
+
*/
|
|
1032
|
+
function calleeSteers<Brand>(
|
|
1033
|
+
project: ProjectView<Brand>,
|
|
1034
|
+
signature: ArrowFunction | FunctionExpression,
|
|
1035
|
+
at: number,
|
|
1036
|
+
): boolean {
|
|
1037
|
+
const { queries } = project;
|
|
1038
|
+
let value: Node = signature;
|
|
1039
|
+
while (isParenthesizedExpression(value.parent)) {
|
|
1040
|
+
value = value.parent;
|
|
1041
|
+
}
|
|
1042
|
+
let nested = false;
|
|
1043
|
+
while (
|
|
1044
|
+
(isPropertyAssignment(value.parent) && isObjectLiteralExpression(value.parent.parent)) ||
|
|
1045
|
+
isObjectLiteralExpression(value.parent)
|
|
1046
|
+
) {
|
|
1047
|
+
value = isObjectLiteralExpression(value.parent) ? value.parent : value.parent.parent;
|
|
1048
|
+
nested = true;
|
|
1049
|
+
}
|
|
1050
|
+
const call = value.parent;
|
|
1051
|
+
if (!isCallExpression(call) && !isNewExpression(call)) {
|
|
1052
|
+
return false;
|
|
1053
|
+
}
|
|
1054
|
+
const argument = (call.arguments ?? []).findIndex((one) => one === value);
|
|
1055
|
+
const resolved = argument < 0 ? undefined : queries.resolvedSignature(call);
|
|
1056
|
+
const generic =
|
|
1057
|
+
resolved === undefined || !isAnswered(resolved) ? resolved : queries.genericOf(resolved);
|
|
1058
|
+
if (argument < 0 || generic === undefined) {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
if (!isAnswered(generic) || nested) {
|
|
1062
|
+
return true;
|
|
1063
|
+
}
|
|
1064
|
+
const position = queries.typeAtPosition(generic, argument);
|
|
1065
|
+
if (!isAnswered(position) || position.isTypeParameter()) {
|
|
1066
|
+
return true;
|
|
1067
|
+
}
|
|
1068
|
+
const parts =
|
|
1069
|
+
position.isUnionType() || position.isIntersectionType()
|
|
1070
|
+
? queries.constituents(position)
|
|
1071
|
+
: [position];
|
|
1072
|
+
if (!isAnswered(parts)) {
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
return parts.some((part) => {
|
|
1076
|
+
if (part.isTypeParameter()) {
|
|
1077
|
+
return true;
|
|
1078
|
+
}
|
|
1079
|
+
const calls = queries.signaturesOf(part, SignatureKind.Call);
|
|
1080
|
+
if (!isAnswered(calls)) {
|
|
1081
|
+
return true;
|
|
1082
|
+
}
|
|
1083
|
+
return calls.some((one) => {
|
|
1084
|
+
const type = at < one.parameters.length ? queries.typeAtPosition(one, at) : undefined;
|
|
1085
|
+
return type !== undefined && (!isAnswered(type) || namesTypeParameter(project, type));
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* The parts with each name of `contextual` judged where the compiler gives its function a
|
|
1092
|
+
* contextual type: a name an object pattern binds is marked as one, and an annotated
|
|
1093
|
+
* parameter with whether the call's inference may read its annotation. A question the
|
|
1094
|
+
* checker leaves unanswered keeps the name read.
|
|
860
1095
|
*/
|
|
861
1096
|
function withContextualRead<Brand>(
|
|
862
1097
|
project: ProjectView<Brand>,
|
|
@@ -868,9 +1103,30 @@ function withContextualRead<Brand>(
|
|
|
868
1103
|
}
|
|
869
1104
|
const signatures = [...new Set(contextual.map((one) => one.signature))];
|
|
870
1105
|
const answers = project.queries.contextualTypes(signatures);
|
|
871
|
-
const
|
|
872
|
-
const
|
|
873
|
-
|
|
1106
|
+
const typedBy = new Map(signatures.map((signature, at) => [signature, answers[at]]));
|
|
1107
|
+
const judged = new Map<PartFact, PartFact>();
|
|
1108
|
+
for (const one of contextual) {
|
|
1109
|
+
const answer = typedBy.get(one.signature);
|
|
1110
|
+
if (answer === undefined) {
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
const place = one.part.parameter;
|
|
1114
|
+
judged.set(
|
|
1115
|
+
one.part,
|
|
1116
|
+
answer === UNANSWERED || place === undefined
|
|
1117
|
+
? { ...one.part, used: true }
|
|
1118
|
+
: one.pattern
|
|
1119
|
+
? { ...one.part, parameter: { ...place, contextualPattern: true } }
|
|
1120
|
+
: {
|
|
1121
|
+
...one.part,
|
|
1122
|
+
parameter: {
|
|
1123
|
+
...place,
|
|
1124
|
+
annotated: calleeSteers(project, one.signature, one.at) ? "steers" : "free",
|
|
1125
|
+
},
|
|
1126
|
+
},
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
return parts.map((part) => judged.get(part) ?? part);
|
|
874
1130
|
}
|
|
875
1131
|
|
|
876
1132
|
/**
|
|
@@ -886,7 +1142,7 @@ function parameterParts(
|
|
|
886
1142
|
signature: SignatureNode,
|
|
887
1143
|
reader: { readonly names: readonly ParameterName[]; readonly uses: readonly Identifier[] },
|
|
888
1144
|
ids: ReadonlyMap<Node, Answer<number>>,
|
|
889
|
-
|
|
1145
|
+
place: Pick<ParameterPlace, "inValue" | "checked">,
|
|
890
1146
|
): { readonly parts: PartFact[]; readonly contextual: ContextualParameter[] } {
|
|
891
1147
|
// A function that reads its `arguments` reads every parameter by position. A nested
|
|
892
1148
|
// function other than an arrow has an `arguments` object of its own.
|
|
@@ -910,14 +1166,14 @@ function parameterParts(
|
|
|
910
1166
|
return;
|
|
911
1167
|
}
|
|
912
1168
|
const part: PartFact = {
|
|
913
|
-
code:
|
|
1169
|
+
code: CODE.unusedParameter,
|
|
914
1170
|
declaration,
|
|
915
1171
|
kind: "parameter",
|
|
916
1172
|
name: name.text,
|
|
917
1173
|
position: spanOf(file, root, name),
|
|
918
1174
|
message: `parameter ${name.text} is never read in the body`,
|
|
919
1175
|
used: read[index] === true,
|
|
920
|
-
parameter: { beforeARead: !pattern && at < lastRead,
|
|
1176
|
+
parameter: { beforeARead: !pattern && at < lastRead, ...place },
|
|
921
1177
|
};
|
|
922
1178
|
found.push(part);
|
|
923
1179
|
const parameter = signature.parameters[at];
|
|
@@ -927,7 +1183,12 @@ function parameterParts(
|
|
|
927
1183
|
parameter !== undefined &&
|
|
928
1184
|
(parameter.type !== undefined || isObjectBindingPattern(parameter.name))
|
|
929
1185
|
) {
|
|
930
|
-
contextual.push({
|
|
1186
|
+
contextual.push({
|
|
1187
|
+
part,
|
|
1188
|
+
signature: expression,
|
|
1189
|
+
at,
|
|
1190
|
+
pattern: isObjectBindingPattern(parameter.name),
|
|
1191
|
+
});
|
|
931
1192
|
}
|
|
932
1193
|
});
|
|
933
1194
|
return { parts: found, contextual };
|
|
@@ -939,44 +1200,107 @@ interface Local {
|
|
|
939
1200
|
readonly statement: number;
|
|
940
1201
|
readonly initialized: boolean;
|
|
941
1202
|
readonly uses: readonly Identifier[];
|
|
1203
|
+
/** A `var` of a nested block, whose function scope outlives the block's end. */
|
|
1204
|
+
readonly outlives: boolean;
|
|
1205
|
+
/** A function declaration, written at the start of the block whatever its place. */
|
|
1206
|
+
readonly hoisted: boolean;
|
|
942
1207
|
}
|
|
943
1208
|
|
|
944
1209
|
/**
|
|
945
1210
|
* The names one declaration binds that a write of its initializer stores each on its own:
|
|
946
|
-
* the declared name, or each element name of
|
|
1211
|
+
* the declared name, or each element name of a binding pattern at any depth. A name with a
|
|
1212
|
+
* default is left out, because removing it skips the default's evaluation, and so is a name
|
|
1213
|
+
* beside a rest element of its object pattern, whose removal changes what the rest holds.
|
|
947
1214
|
*/
|
|
948
|
-
function
|
|
1215
|
+
function boundNames(name: BindingName): readonly Identifier[] {
|
|
949
1216
|
if (isIdentifier(name)) {
|
|
950
1217
|
return [name];
|
|
951
1218
|
}
|
|
952
|
-
|
|
953
|
-
|
|
1219
|
+
const rest =
|
|
1220
|
+
isObjectBindingPattern(name) &&
|
|
1221
|
+
name.elements.some((element) => element.dotDotDotToken !== undefined);
|
|
1222
|
+
return name.elements.flatMap((element) => {
|
|
1223
|
+
if (!isBindingElement(element) || element.name === undefined) {
|
|
1224
|
+
return [];
|
|
1225
|
+
}
|
|
1226
|
+
if (element.initializer !== undefined || (rest && element.dotDotDotToken === undefined)) {
|
|
1227
|
+
return [];
|
|
1228
|
+
}
|
|
1229
|
+
return boundNames(element.name);
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/** The function whose scope a `var` of one block belongs to, or none at a module's top level. */
|
|
1234
|
+
function functionOf(block: Block): FunctionLikeDeclaration | undefined {
|
|
1235
|
+
for (let at: Node = block.parent; ; at = at.parent) {
|
|
1236
|
+
if (isFunctionLikeDeclaration(at)) {
|
|
1237
|
+
return at;
|
|
1238
|
+
}
|
|
1239
|
+
if (isSourceFile(at)) {
|
|
1240
|
+
return undefined;
|
|
1241
|
+
}
|
|
954
1242
|
}
|
|
955
|
-
return name.elements.flatMap((element) =>
|
|
956
|
-
isBindingElement(element) && element.name !== undefined ? arrayPatternNames(element.name) : [],
|
|
957
|
-
);
|
|
958
1243
|
}
|
|
959
1244
|
|
|
960
|
-
/**
|
|
1245
|
+
/**
|
|
1246
|
+
* The locals one block declares with `let`, `const`, `var` or a function declaration
|
|
1247
|
+
* inside a function, each with the names of the block that spell it. A `var` of a nested
|
|
1248
|
+
* block is one where no nested function of its function names it, since the function scope
|
|
1249
|
+
* holds it past the block's end.
|
|
1250
|
+
*/
|
|
961
1251
|
function localsOf(block: Block): readonly Local[] {
|
|
962
|
-
const declared: {
|
|
1252
|
+
const declared: {
|
|
1253
|
+
name: Identifier;
|
|
1254
|
+
statement: number;
|
|
1255
|
+
initialized: boolean;
|
|
1256
|
+
outlives: boolean;
|
|
1257
|
+
hoisted: boolean;
|
|
1258
|
+
}[] = [];
|
|
1259
|
+
const scope = functionOf(block);
|
|
1260
|
+
const outlives = scope?.body !== block;
|
|
963
1261
|
block.statements.forEach((statement, at) => {
|
|
1262
|
+
if (isFunctionDeclaration(statement)) {
|
|
1263
|
+
if (scope !== undefined && statement.name !== undefined && statement.body !== undefined) {
|
|
1264
|
+
declared.push({
|
|
1265
|
+
name: statement.name,
|
|
1266
|
+
statement: at,
|
|
1267
|
+
initialized: true,
|
|
1268
|
+
outlives: false,
|
|
1269
|
+
hoisted: true,
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
964
1274
|
if (!isVariableStatement(statement)) {
|
|
965
1275
|
return;
|
|
966
1276
|
}
|
|
967
1277
|
const list = statement.declarationList;
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
(list.flags & NodeFlags.Using) !== 0
|
|
971
|
-
) {
|
|
1278
|
+
const blockScoped = (list.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0;
|
|
1279
|
+
if ((!blockScoped && scope === undefined) || (list.flags & NodeFlags.Using) !== 0) {
|
|
972
1280
|
return;
|
|
973
1281
|
}
|
|
974
1282
|
for (const declaration of list.declarations) {
|
|
975
|
-
|
|
1283
|
+
// A write to a local typed `never` is the compiler's exhaustiveness check, which
|
|
1284
|
+
// fails to compile once a case goes unhandled.
|
|
1285
|
+
if (declaration.type?.kind === SyntaxKind.NeverKeyword) {
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
for (const name of boundNames(declaration.name)) {
|
|
1289
|
+
if (
|
|
1290
|
+
!blockScoped &&
|
|
1291
|
+
outlives &&
|
|
1292
|
+
identifiersNaming(scope?.body ?? block, new Set([name.text])).some((use) =>
|
|
1293
|
+
inNestedFunction(use, scope ?? block),
|
|
1294
|
+
)
|
|
1295
|
+
) {
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
976
1298
|
declared.push({
|
|
977
1299
|
name,
|
|
978
1300
|
statement: at,
|
|
979
1301
|
initialized: declaration.initializer !== undefined,
|
|
1302
|
+
outlives: !blockScoped && outlives,
|
|
1303
|
+
hoisted: false,
|
|
980
1304
|
});
|
|
981
1305
|
}
|
|
982
1306
|
}
|
|
@@ -991,14 +1315,23 @@ function localsOf(block: Block): readonly Local[] {
|
|
|
991
1315
|
}));
|
|
992
1316
|
}
|
|
993
1317
|
|
|
1318
|
+
/** Whether one name is the whole target of a plain assignment, which writes it and reads nothing. */
|
|
1319
|
+
function isPlainWrite(use: Identifier): boolean {
|
|
1320
|
+
const parent = outerParent(use);
|
|
1321
|
+
return (
|
|
1322
|
+
isBinaryExpression(parent) &&
|
|
1323
|
+
parent.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
1324
|
+
parenthesesRemoved(parent.left) === use
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
994
1328
|
/**
|
|
995
1329
|
* The writes to the locals of one block that no read reaches. A write is a declaration's
|
|
996
|
-
* initializer or a plain assignment written as a statement of the block
|
|
997
|
-
*
|
|
998
|
-
*
|
|
999
|
-
*
|
|
1000
|
-
* and
|
|
1001
|
-
* at any time.
|
|
1330
|
+
* initializer or a plain assignment written as a statement of the block; it is dead when
|
|
1331
|
+
* the block's later statements read the local nowhere before the next such write or the
|
|
1332
|
+
* block's end. A statement writing it inside a nested construct ends the search with the
|
|
1333
|
+
* write kept. A local a nested function reads is no subject, since the function may run at
|
|
1334
|
+
* any time, and every write to a local nothing reads is dead, a nested function's included.
|
|
1002
1335
|
*/
|
|
1003
1336
|
function deadStores(
|
|
1004
1337
|
file: SourceFile,
|
|
@@ -1009,8 +1342,32 @@ function deadStores(
|
|
|
1009
1342
|
ids: ReadonlyMap<Node, Answer<number>>,
|
|
1010
1343
|
): PartFact[] {
|
|
1011
1344
|
const found: PartFact[] = [];
|
|
1345
|
+
const deadAt = (local: Local, target: Identifier): PartFact => ({
|
|
1346
|
+
code: CODE.deadStore,
|
|
1347
|
+
declaration: holder,
|
|
1348
|
+
kind: "store",
|
|
1349
|
+
name: local.name.text,
|
|
1350
|
+
position: spanOf(file, root, target),
|
|
1351
|
+
message: `value written to ${local.name.text} is never read`,
|
|
1352
|
+
used: false,
|
|
1353
|
+
});
|
|
1012
1354
|
for (const local of locals) {
|
|
1013
|
-
const
|
|
1355
|
+
const own = local.hoisted ? block.statements[local.statement] : undefined;
|
|
1356
|
+
// A function's reads of itself keep it no more than its absence would.
|
|
1357
|
+
const uses = local.uses.filter(
|
|
1358
|
+
(use) => readsBinding(use, local.name, ids) && (own === undefined || !within(use, own)),
|
|
1359
|
+
);
|
|
1360
|
+
if (!local.outlives && uses.every(isPlainWrite)) {
|
|
1361
|
+
// A declaration with no initializer writes `undefined`, the one store left to name.
|
|
1362
|
+
const writes = [...(local.initialized ? [local.name] : []), ...uses];
|
|
1363
|
+
const targets = writes.length === 0 ? [local.name] : writes;
|
|
1364
|
+
found.push(...targets.map((target) => deadAt(local, target)));
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
// A read anywhere in the block reads a function declaration, which is written first.
|
|
1368
|
+
if (local.hoisted) {
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1014
1371
|
if (uses.some((use) => inNestedFunction(use, block))) {
|
|
1015
1372
|
continue;
|
|
1016
1373
|
}
|
|
@@ -1021,14 +1378,14 @@ function deadStores(
|
|
|
1021
1378
|
if (!isExpressionStatement(statement)) {
|
|
1022
1379
|
return undefined;
|
|
1023
1380
|
}
|
|
1024
|
-
const expression =
|
|
1381
|
+
const expression = parenthesesRemoved(statement.expression);
|
|
1025
1382
|
if (
|
|
1026
1383
|
!isBinaryExpression(expression) ||
|
|
1027
1384
|
expression.operatorToken.kind !== SyntaxKind.EqualsToken
|
|
1028
1385
|
) {
|
|
1029
1386
|
return undefined;
|
|
1030
1387
|
}
|
|
1031
|
-
const target =
|
|
1388
|
+
const target = parenthesesRemoved(expression.left);
|
|
1032
1389
|
return isIdentifier(target) && uses.includes(target)
|
|
1033
1390
|
? { target, value: expression.right }
|
|
1034
1391
|
: undefined;
|
|
@@ -1050,7 +1407,7 @@ function deadStores(
|
|
|
1050
1407
|
if (sameStatement.length > 0) {
|
|
1051
1408
|
continue;
|
|
1052
1409
|
}
|
|
1053
|
-
let dead =
|
|
1410
|
+
let dead = !local.outlives;
|
|
1054
1411
|
for (let at = write.at + 1; at < statements.length; at += 1) {
|
|
1055
1412
|
const statement = statements[at];
|
|
1056
1413
|
if (statement === undefined) {
|
|
@@ -1067,19 +1424,9 @@ function deadStores(
|
|
|
1067
1424
|
break;
|
|
1068
1425
|
}
|
|
1069
1426
|
}
|
|
1070
|
-
if (
|
|
1071
|
-
|
|
1427
|
+
if (dead) {
|
|
1428
|
+
found.push(deadAt(local, write.target));
|
|
1072
1429
|
}
|
|
1073
|
-
const position = spanOf(file, root, write.target);
|
|
1074
|
-
found.push({
|
|
1075
|
-
code: DEAD_STORE,
|
|
1076
|
-
declaration: holder,
|
|
1077
|
-
kind: "store",
|
|
1078
|
-
name: local.name.text,
|
|
1079
|
-
position,
|
|
1080
|
-
message: `value written to ${local.name.text} is never read`,
|
|
1081
|
-
used: false,
|
|
1082
|
-
});
|
|
1083
1430
|
}
|
|
1084
1431
|
}
|
|
1085
1432
|
return found;
|
|
@@ -1125,7 +1472,7 @@ function resultParts<Brand>(
|
|
|
1125
1472
|
const valued = project.queries.nonNullable(result);
|
|
1126
1473
|
const members = isAnswered(valued) ? project.queries.propertiesOf(valued) : UNANSWERED;
|
|
1127
1474
|
found.push({
|
|
1128
|
-
code:
|
|
1475
|
+
code: CODE.unusedResult,
|
|
1129
1476
|
declaration: id,
|
|
1130
1477
|
kind: "result",
|
|
1131
1478
|
name: "result",
|
|
@@ -1142,6 +1489,8 @@ function resultParts<Brand>(
|
|
|
1142
1489
|
export interface IntraFunctionFacts {
|
|
1143
1490
|
/** Every part of the run, each once, in use where any project found it in use. */
|
|
1144
1491
|
readonly parts: readonly PartFact[];
|
|
1492
|
+
/** Every import binding of the run, each once, read where any project found it read. */
|
|
1493
|
+
readonly imports: readonly ImportFact[];
|
|
1145
1494
|
/** The declarations whose signature is free to change. */
|
|
1146
1495
|
readonly free: ReadonlySet<string>;
|
|
1147
1496
|
/** The declarations whose signature is free but for a use as a value. */
|
|
@@ -1172,6 +1521,15 @@ export function intraFunctionFacts(
|
|
|
1172
1521
|
const known = parts.get(key);
|
|
1173
1522
|
parts.set(key, known === undefined ? part : { ...known, used: known.used || part.used });
|
|
1174
1523
|
}
|
|
1524
|
+
const imports = new Map<string, ImportFact>();
|
|
1525
|
+
for (const binding of projects.flatMap((one) => one.imports)) {
|
|
1526
|
+
const key = positionKey(binding.position);
|
|
1527
|
+
const known = imports.get(key);
|
|
1528
|
+
imports.set(
|
|
1529
|
+
key,
|
|
1530
|
+
known === undefined ? binding : { ...known, used: known.used || binding.used },
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1175
1533
|
const calls = new Map<string, CallUse>();
|
|
1176
1534
|
const specifiers = new Set<string>();
|
|
1177
1535
|
for (const one of projects) {
|
|
@@ -1231,5 +1589,11 @@ export function intraFunctionFacts(
|
|
|
1231
1589
|
.filter(([id, all]) => all && !valued.has(id) && !sequenced.has(id))
|
|
1232
1590
|
.map(([id]) => id),
|
|
1233
1591
|
);
|
|
1234
|
-
return {
|
|
1592
|
+
return {
|
|
1593
|
+
parts: [...parts.values()],
|
|
1594
|
+
imports: [...imports.values()],
|
|
1595
|
+
free,
|
|
1596
|
+
freeButValued,
|
|
1597
|
+
discardedEverywhere,
|
|
1598
|
+
};
|
|
1235
1599
|
}
|