@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
|
@@ -4,18 +4,17 @@
|
|
|
4
4
|
* call sites that use or discard it, each statement control flow cannot reach, each
|
|
5
5
|
* write to a local that no read reaches, and each case an earlier case covers.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { CODE } from "./contract/contract.generated.js";
|
|
8
|
+
import { isArrowFunction, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isBindingElement, isCallExpression, isCaseClause, isCatchClause, isClassDeclaration, isClassExpression, isConstructorDeclaration, isContinueStatement, isDecorator, isDefaultClause, isEmptyStatement, isExportAssignment, isExportSpecifier, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isForInStatement, isForOfStatement, isFunctionLikeDeclaration, isHeritageClause, isIdentifier, isIfStatement, isImportClause, isImportSpecifier, isInterfaceDeclaration, isMethodDeclaration, isModuleDeclaration, isNamespaceImport, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPrefixUnaryExpression, isPropertyAssignment, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isSourceFile, isStringLiteral, isSwitchStatement, isThrowStatement, isTypeAliasDeclaration, isTypeReferenceNode, isVariableDeclarationList, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
|
|
9
|
+
import { SignatureKind } from "@typescript/native/sync";
|
|
10
|
+
import { exportsLocally, implicitReads, importBindings, } from "./import-bindings.js";
|
|
8
11
|
import { isParameterProperty, nodeKey } from "./inventory.js";
|
|
9
12
|
import { positionKey, renderPosition } from "./position.js";
|
|
13
|
+
import { importRef } from "./ref.js";
|
|
10
14
|
import { isAnswered, UNANSWERED } from "./query.js";
|
|
11
15
|
import { signatureOf } from "./signatures.js";
|
|
12
16
|
import { spellsNoBinding } from "./spelled-names.js";
|
|
13
|
-
|
|
14
|
-
export const UNUSED_PARAMETER = "DS1801";
|
|
15
|
-
export const UNUSED_RESULT = "DS1803";
|
|
16
|
-
const UNREACHABLE_STATEMENT = "DS1805";
|
|
17
|
-
const DEAD_STORE = "DS1807";
|
|
18
|
-
const UNREACHABLE_CASE = "DS1809";
|
|
17
|
+
import { parenthesesRemoved } from "./unwrap.js";
|
|
19
18
|
/** The result types that carry no value, as the checker spells them. */
|
|
20
19
|
const NO_VALUE = new Set([
|
|
21
20
|
"void",
|
|
@@ -31,17 +30,13 @@ function spanOf(file, root, node) {
|
|
|
31
30
|
const endLine = file.getLineAndCharacterOfPosition(node.end).line + 1;
|
|
32
31
|
return { ...start, endLine };
|
|
33
32
|
}
|
|
34
|
-
/** The parenthesized expression's content, through every pair of parentheses. */
|
|
35
|
-
function unwrapped(node) {
|
|
36
|
-
return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
|
|
37
|
-
}
|
|
38
33
|
/** The node the parentheses around one expression sit in. */
|
|
39
34
|
function outerParent(node) {
|
|
40
35
|
return isParenthesizedExpression(node.parent) ? outerParent(node.parent) : node.parent;
|
|
41
36
|
}
|
|
42
37
|
/** The name a call is made through: the identifier, or the member a property access names. */
|
|
43
38
|
function calleeName(call) {
|
|
44
|
-
const callee =
|
|
39
|
+
const callee = parenthesesRemoved(call.expression);
|
|
45
40
|
if (isIdentifier(callee)) {
|
|
46
41
|
return callee;
|
|
47
42
|
}
|
|
@@ -96,7 +91,7 @@ function unreachableExempt(statement) {
|
|
|
96
91
|
}
|
|
97
92
|
/** The value a case expression is a literal of, as a key two equal literals share, or none. */
|
|
98
93
|
function caseKey(expression) {
|
|
99
|
-
const node =
|
|
94
|
+
const node = parenthesesRemoved(expression);
|
|
100
95
|
if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
|
|
101
96
|
return `s:${node.text}`;
|
|
102
97
|
}
|
|
@@ -207,7 +202,7 @@ function extendedNames(files) {
|
|
|
207
202
|
named = isQualifiedName(type.typeName) ? type.typeName.right : type.typeName;
|
|
208
203
|
}
|
|
209
204
|
else {
|
|
210
|
-
const expression =
|
|
205
|
+
const expression = parenthesesRemoved(type.expression);
|
|
211
206
|
named = isPropertyAccessExpression(expression) ? expression.name : expression;
|
|
212
207
|
}
|
|
213
208
|
if (isIdentifier(named)) {
|
|
@@ -251,7 +246,7 @@ function decoratorFixes(constructor) {
|
|
|
251
246
|
}
|
|
252
247
|
/** The name an expression spells a class by: an identifier, or the member a property access names. */
|
|
253
248
|
function spelledName(expression) {
|
|
254
|
-
const node =
|
|
249
|
+
const node = parenthesesRemoved(expression);
|
|
255
250
|
if (isIdentifier(node)) {
|
|
256
251
|
return node;
|
|
257
252
|
}
|
|
@@ -304,6 +299,33 @@ function readsBinding(use, declared, ids) {
|
|
|
304
299
|
const own = ids.get(declared);
|
|
305
300
|
return at === undefined || at === UNANSWERED || own === UNANSWERED || at === own;
|
|
306
301
|
}
|
|
302
|
+
/** Whether a name is spelled with a leading underscore, which declares it unread. */
|
|
303
|
+
function declaredUnread(name) {
|
|
304
|
+
return name.text.startsWith("_");
|
|
305
|
+
}
|
|
306
|
+
/** A file's leading comments, where the compiler reads its `@ts-check` and `@ts-nocheck` pragmas. */
|
|
307
|
+
const LEADING_COMMENTS = /^(?:#![^\n]*)?(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*/u;
|
|
308
|
+
/** A comment of the leading comments, a line comment's text after its slashes captured. */
|
|
309
|
+
const LEADING_COMMENT = /\/\*[\s\S]*?\*\/|\/\/\/?([^\n]*)/gu;
|
|
310
|
+
/**
|
|
311
|
+
* Whether a file's leading comments hold one pragma: as the compiler reads it, a line
|
|
312
|
+
* comment that starts with the pragma's name, in any case, followed by nothing or a space.
|
|
313
|
+
*/
|
|
314
|
+
function hasPragma(leading, pragma) {
|
|
315
|
+
return [...leading.matchAll(LEADING_COMMENT)].some(([, text]) => text !== undefined && pragma.test(text));
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Whether the compiler checks a file, so its rules report what they find there: a
|
|
319
|
+
* JavaScript file only under `checkJs` or a `@ts-check` pragma, and no file with a
|
|
320
|
+
* `@ts-nocheck` pragma.
|
|
321
|
+
*/
|
|
322
|
+
function checkedByTheCompiler(file, checkJs) {
|
|
323
|
+
const leading = LEADING_COMMENTS.exec(file.text)?.[0] ?? "";
|
|
324
|
+
if (hasPragma(leading, /^\s*@ts-nocheck(?:\s|$)/iu)) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
return (!/\.[cm]?jsx?$/u.test(file.fileName) || checkJs || hasPragma(leading, /^\s*@ts-check(?:\s|$)/iu));
|
|
328
|
+
}
|
|
307
329
|
/**
|
|
308
330
|
* The parts of one project's own declarations. `references` are the project's own
|
|
309
331
|
* references, which say which declarations a discarded call reaches, so the checker is
|
|
@@ -319,7 +341,12 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
319
341
|
const constructions = new Set();
|
|
320
342
|
const sites = [];
|
|
321
343
|
const contextual = [];
|
|
344
|
+
const imports = [];
|
|
345
|
+
const refs = new Map(held.symbols.map((symbol) => [symbol.id, symbol.ref]));
|
|
346
|
+
const options = project.program.getCompilerOptions();
|
|
347
|
+
const jsx = options;
|
|
322
348
|
for (const file of files) {
|
|
349
|
+
const checked = checkedByTheCompiler(file, options.checkJs === true);
|
|
323
350
|
// A parameter property's own parts stay with the parameter when its member goes.
|
|
324
351
|
const declarationAt = (node) => isParameterProperty(node) ? undefined : held.declarations.get(nodeKey(file, node));
|
|
325
352
|
// A part no declaration holds belongs to the file, which is judged while the file is live.
|
|
@@ -338,6 +365,7 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
338
365
|
values: [],
|
|
339
366
|
catches: [],
|
|
340
367
|
blocks: [],
|
|
368
|
+
loops: [],
|
|
341
369
|
unreachable: [],
|
|
342
370
|
cases: [],
|
|
343
371
|
};
|
|
@@ -411,8 +439,8 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
411
439
|
if ((isImportClause(node) || isNamespaceImport(node)) && node.name !== undefined) {
|
|
412
440
|
specifiers.add(position(node.name));
|
|
413
441
|
}
|
|
414
|
-
if (isExportAssignment(node) && isIdentifier(
|
|
415
|
-
specifiers.add(position(
|
|
442
|
+
if (isExportAssignment(node) && isIdentifier(parenthesesRemoved(node.expression))) {
|
|
443
|
+
specifiers.add(position(parenthesesRemoved(node.expression)));
|
|
416
444
|
}
|
|
417
445
|
if (isSwitchStatement(node)) {
|
|
418
446
|
const holder = holderOf(node);
|
|
@@ -433,6 +461,15 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
433
461
|
if (isBlock(node) && silenced === 0 && holderOf(node) !== undefined) {
|
|
434
462
|
walk.blocks.push(node);
|
|
435
463
|
}
|
|
464
|
+
const loopHolder = (isForOfStatement(node) || isForInStatement(node)) && silenced === 0
|
|
465
|
+
? holderOf(node)
|
|
466
|
+
: undefined;
|
|
467
|
+
if (loopHolder !== undefined &&
|
|
468
|
+
(isForOfStatement(node) || isForInStatement(node)) &&
|
|
469
|
+
isVariableDeclarationList(node.initializer) &&
|
|
470
|
+
(node.initializer.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0) {
|
|
471
|
+
walk.loops.push({ loop: node, holder: loopHolder });
|
|
472
|
+
}
|
|
436
473
|
if (isBlock(node) || isCaseClause(node) || isDefaultClause(node)) {
|
|
437
474
|
statementsOf(node.statements);
|
|
438
475
|
return;
|
|
@@ -462,7 +499,37 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
462
499
|
for (const store of stores) {
|
|
463
500
|
asked.push(...store.flatMap((local) => [local.name, ...local.uses]));
|
|
464
501
|
}
|
|
502
|
+
const loopVariables = walk.loops.map(({ loop, holder }) => {
|
|
503
|
+
const list = loop.initializer;
|
|
504
|
+
// A loop must bind a name, so one spelled declared unread is no subject.
|
|
505
|
+
const names = list.declarations
|
|
506
|
+
.flatMap((declaration) => boundNames(declaration.name))
|
|
507
|
+
.filter((name) => !declaredUnread(name));
|
|
508
|
+
const uses = identifiersNaming(loop.statement, new Set(names.map((one) => one.text)));
|
|
509
|
+
asked.push(...names, ...uses);
|
|
510
|
+
return { names, uses, holder };
|
|
511
|
+
});
|
|
512
|
+
const bindings = fileId === undefined ? [] : importBindings(file);
|
|
513
|
+
const bound = new Set(bindings);
|
|
514
|
+
const bindingUses = bindings.length === 0
|
|
515
|
+
? []
|
|
516
|
+
: identifiersNaming(file, new Set(bindings.map((one) => one.text))).filter((use) => !bound.has(use));
|
|
517
|
+
asked.push(...bindings, ...bindingUses);
|
|
465
518
|
const ids = resolvedIds(project, [...new Set(asked)]);
|
|
519
|
+
const implicit = bindings.length === 0 ? new Set() : implicitReads(file, jsx);
|
|
520
|
+
for (const binding of bindings) {
|
|
521
|
+
const fileRef = fileId === undefined ? undefined : refs.get(fileId);
|
|
522
|
+
if (fileId === undefined || fileRef === undefined) {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
imports.push({
|
|
526
|
+
ref: importRef(fileRef, binding.text),
|
|
527
|
+
name: binding.text,
|
|
528
|
+
position: spanOf(file, targetRoot, binding),
|
|
529
|
+
used: implicit.has(binding.text) ||
|
|
530
|
+
bindingUses.some((use) => use.text === binding.text && (exportsLocally(use) || readsBinding(use, binding, ids))),
|
|
531
|
+
});
|
|
532
|
+
}
|
|
466
533
|
for (const one of walk.declared) {
|
|
467
534
|
const constructor = isConstructorDeclaration(one.node);
|
|
468
535
|
if (!constructor) {
|
|
@@ -476,7 +543,10 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
476
543
|
});
|
|
477
544
|
const reader = readers.get(one.signature);
|
|
478
545
|
if (reader !== undefined) {
|
|
479
|
-
const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids,
|
|
546
|
+
const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, {
|
|
547
|
+
inValue: false,
|
|
548
|
+
checked,
|
|
549
|
+
});
|
|
480
550
|
parts.push(...found.parts);
|
|
481
551
|
contextual.push(...found.contextual);
|
|
482
552
|
}
|
|
@@ -484,14 +554,17 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
484
554
|
for (const one of walk.values) {
|
|
485
555
|
const reader = readers.get(one.signature);
|
|
486
556
|
if (reader !== undefined) {
|
|
487
|
-
const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids,
|
|
557
|
+
const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids, {
|
|
558
|
+
inValue: true,
|
|
559
|
+
checked,
|
|
560
|
+
});
|
|
488
561
|
parts.push(...found.parts);
|
|
489
562
|
contextual.push(...found.contextual);
|
|
490
563
|
}
|
|
491
564
|
}
|
|
492
565
|
walk.catches.forEach(({ name, holder }, at) => {
|
|
493
566
|
parts.push({
|
|
494
|
-
code:
|
|
567
|
+
code: CODE.deadStore,
|
|
495
568
|
declaration: holder,
|
|
496
569
|
kind: "store",
|
|
497
570
|
name: name.text,
|
|
@@ -500,6 +573,19 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
500
573
|
used: (caught[at] ?? []).some((use) => readsBinding(use, name, ids)),
|
|
501
574
|
});
|
|
502
575
|
});
|
|
576
|
+
for (const { names, uses, holder } of loopVariables) {
|
|
577
|
+
for (const name of names) {
|
|
578
|
+
parts.push({
|
|
579
|
+
code: CODE.deadStore,
|
|
580
|
+
declaration: holder,
|
|
581
|
+
kind: "store",
|
|
582
|
+
name: name.text,
|
|
583
|
+
position: spanOf(file, targetRoot, name),
|
|
584
|
+
message: `value written to ${name.text} is never read`,
|
|
585
|
+
used: uses.some((use) => use.text === name.text && readsBinding(use, name, ids)),
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
503
589
|
walk.blocks.forEach((block, at) => {
|
|
504
590
|
const holder = holderOf(block);
|
|
505
591
|
if (holder !== undefined) {
|
|
@@ -508,7 +594,7 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
508
594
|
});
|
|
509
595
|
for (const { statement, holder } of walk.unreachable) {
|
|
510
596
|
parts.push({
|
|
511
|
-
code:
|
|
597
|
+
code: CODE.unreachableStatement,
|
|
512
598
|
declaration: holder,
|
|
513
599
|
kind: "statement",
|
|
514
600
|
name: "statement",
|
|
@@ -519,7 +605,7 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
519
605
|
}
|
|
520
606
|
for (const { clause, text, holder } of walk.cases) {
|
|
521
607
|
parts.push({
|
|
522
|
-
code:
|
|
608
|
+
code: CODE.unreachableCase,
|
|
523
609
|
declaration: holder,
|
|
524
610
|
kind: "case",
|
|
525
611
|
name: text,
|
|
@@ -532,6 +618,7 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
532
618
|
parts.push(...resultParts(project, targetRoot, sites, references, calls));
|
|
533
619
|
return {
|
|
534
620
|
parts: withContextualRead(project, parts, contextual),
|
|
621
|
+
imports,
|
|
535
622
|
signatures,
|
|
536
623
|
calls,
|
|
537
624
|
specifiers,
|
|
@@ -558,7 +645,9 @@ function valueSignature(node) {
|
|
|
558
645
|
/**
|
|
559
646
|
* Every name one function's parameters bind, a binding pattern's names one by one. A
|
|
560
647
|
* `this` parameter receives no argument, a decorated one is read by its decorator, and a
|
|
561
|
-
* parameter property is read by the write to its member, so none binds a subject.
|
|
648
|
+
* parameter property is read by the write to its member, so none binds a subject. A name
|
|
649
|
+
* spelled with a leading underscore is declared unread, so it is listed for its reads and
|
|
650
|
+
* reported never.
|
|
562
651
|
*/
|
|
563
652
|
function parameterNames(signature) {
|
|
564
653
|
const found = [];
|
|
@@ -570,7 +659,7 @@ function parameterNames(signature) {
|
|
|
570
659
|
continue;
|
|
571
660
|
}
|
|
572
661
|
if (isIdentifier(element.name)) {
|
|
573
|
-
const exempt = rest && element.dotDotDotToken === undefined;
|
|
662
|
+
const exempt = (rest && element.dotDotDotToken === undefined) || declaredUnread(element.name);
|
|
574
663
|
found.push({ name: element.name, at, pattern: true, exempt });
|
|
575
664
|
}
|
|
576
665
|
else {
|
|
@@ -586,7 +675,7 @@ function parameterNames(signature) {
|
|
|
586
675
|
}
|
|
587
676
|
if (isIdentifier(name)) {
|
|
588
677
|
if (name.text !== "this") {
|
|
589
|
-
found.push({ name, at, pattern: false, exempt:
|
|
678
|
+
found.push({ name, at, pattern: false, exempt: declaredUnread(name) });
|
|
590
679
|
}
|
|
591
680
|
return;
|
|
592
681
|
}
|
|
@@ -594,11 +683,90 @@ function parameterNames(signature) {
|
|
|
594
683
|
});
|
|
595
684
|
return found;
|
|
596
685
|
}
|
|
686
|
+
/** How deep a type is searched for a type parameter: a callback of a callback is two levels. */
|
|
687
|
+
const TYPE_PARAMETER_DEPTH = 3;
|
|
688
|
+
/** Whether one type is or holds a type parameter, through constituents, type arguments and signatures. */
|
|
689
|
+
function namesTypeParameter(project, type, depth = 0) {
|
|
690
|
+
if (type.isTypeParameter()) {
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
if (depth >= TYPE_PARAMETER_DEPTH) {
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
const { queries } = project;
|
|
697
|
+
const parts = type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [];
|
|
698
|
+
const args = queries.typeArguments(type);
|
|
699
|
+
const signatures = queries.signaturesOf(type, SignatureKind.Call);
|
|
700
|
+
if (!isAnswered(parts) || !isAnswered(args) || !isAnswered(signatures)) {
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
const inner = [
|
|
704
|
+
...parts,
|
|
705
|
+
...args,
|
|
706
|
+
...signatures.flatMap((signature) => signature.parameters.map((_parameter, at) => queries.typeAtPosition(signature, at))),
|
|
707
|
+
];
|
|
708
|
+
return inner.some((one) => !isAnswered(one) || namesTypeParameter(project, one, depth + 1));
|
|
709
|
+
}
|
|
597
710
|
/**
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
711
|
+
* Whether the call a function expression is written in may read the annotation of its
|
|
712
|
+
* parameter at `at`: the callee is generic, and its parameter type at the function's place
|
|
713
|
+
* reads a type parameter there. A function written inside an object literal a generic
|
|
714
|
+
* call is passed is read so too, and a question the checker leaves unanswered reads it.
|
|
715
|
+
*/
|
|
716
|
+
function calleeSteers(project, signature, at) {
|
|
717
|
+
const { queries } = project;
|
|
718
|
+
let value = signature;
|
|
719
|
+
while (isParenthesizedExpression(value.parent)) {
|
|
720
|
+
value = value.parent;
|
|
721
|
+
}
|
|
722
|
+
let nested = false;
|
|
723
|
+
while ((isPropertyAssignment(value.parent) && isObjectLiteralExpression(value.parent.parent)) ||
|
|
724
|
+
isObjectLiteralExpression(value.parent)) {
|
|
725
|
+
value = isObjectLiteralExpression(value.parent) ? value.parent : value.parent.parent;
|
|
726
|
+
nested = true;
|
|
727
|
+
}
|
|
728
|
+
const call = value.parent;
|
|
729
|
+
if (!isCallExpression(call) && !isNewExpression(call)) {
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
const argument = (call.arguments ?? []).findIndex((one) => one === value);
|
|
733
|
+
const resolved = argument < 0 ? undefined : queries.resolvedSignature(call);
|
|
734
|
+
const generic = resolved === undefined || !isAnswered(resolved) ? resolved : queries.genericOf(resolved);
|
|
735
|
+
if (argument < 0 || generic === undefined) {
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
if (!isAnswered(generic) || nested) {
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
const position = queries.typeAtPosition(generic, argument);
|
|
742
|
+
if (!isAnswered(position) || position.isTypeParameter()) {
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
const parts = position.isUnionType() || position.isIntersectionType()
|
|
746
|
+
? queries.constituents(position)
|
|
747
|
+
: [position];
|
|
748
|
+
if (!isAnswered(parts)) {
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
751
|
+
return parts.some((part) => {
|
|
752
|
+
if (part.isTypeParameter()) {
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
const calls = queries.signaturesOf(part, SignatureKind.Call);
|
|
756
|
+
if (!isAnswered(calls)) {
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
759
|
+
return calls.some((one) => {
|
|
760
|
+
const type = at < one.parameters.length ? queries.typeAtPosition(one, at) : undefined;
|
|
761
|
+
return type !== undefined && (!isAnswered(type) || namesTypeParameter(project, type));
|
|
762
|
+
});
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* The parts with each name of `contextual` judged where the compiler gives its function a
|
|
767
|
+
* contextual type: a name an object pattern binds is marked as one, and an annotated
|
|
768
|
+
* parameter with whether the call's inference may read its annotation. A question the
|
|
769
|
+
* checker leaves unanswered keeps the name read.
|
|
602
770
|
*/
|
|
603
771
|
function withContextualRead(project, parts, contextual) {
|
|
604
772
|
if (contextual.length === 0) {
|
|
@@ -606,9 +774,27 @@ function withContextualRead(project, parts, contextual) {
|
|
|
606
774
|
}
|
|
607
775
|
const signatures = [...new Set(contextual.map((one) => one.signature))];
|
|
608
776
|
const answers = project.queries.contextualTypes(signatures);
|
|
609
|
-
const
|
|
610
|
-
const
|
|
611
|
-
|
|
777
|
+
const typedBy = new Map(signatures.map((signature, at) => [signature, answers[at]]));
|
|
778
|
+
const judged = new Map();
|
|
779
|
+
for (const one of contextual) {
|
|
780
|
+
const answer = typedBy.get(one.signature);
|
|
781
|
+
if (answer === undefined) {
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
const place = one.part.parameter;
|
|
785
|
+
judged.set(one.part, answer === UNANSWERED || place === undefined
|
|
786
|
+
? { ...one.part, used: true }
|
|
787
|
+
: one.pattern
|
|
788
|
+
? { ...one.part, parameter: { ...place, contextualPattern: true } }
|
|
789
|
+
: {
|
|
790
|
+
...one.part,
|
|
791
|
+
parameter: {
|
|
792
|
+
...place,
|
|
793
|
+
annotated: calleeSteers(project, one.signature, one.at) ? "steers" : "free",
|
|
794
|
+
},
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
return parts.map((part) => judged.get(part) ?? part);
|
|
612
798
|
}
|
|
613
799
|
/**
|
|
614
800
|
* Each name one function's parameters bind and whether the function reads it, and the
|
|
@@ -616,7 +802,7 @@ function withContextualRead(project, parts, contextual) {
|
|
|
616
802
|
* that carries a type annotation or is an object binding pattern. A binding pattern counts
|
|
617
803
|
* as read where its function reads any name it binds.
|
|
618
804
|
*/
|
|
619
|
-
function parameterParts(file, root, declaration, signature, reader, ids,
|
|
805
|
+
function parameterParts(file, root, declaration, signature, reader, ids, place) {
|
|
620
806
|
// A function that reads its `arguments` reads every parameter by position. A nested
|
|
621
807
|
// function other than an arrow has an `arguments` object of its own.
|
|
622
808
|
const readsArguments = !isArrowFunction(signature) &&
|
|
@@ -632,14 +818,14 @@ function parameterParts(file, root, declaration, signature, reader, ids, inValue
|
|
|
632
818
|
return;
|
|
633
819
|
}
|
|
634
820
|
const part = {
|
|
635
|
-
code:
|
|
821
|
+
code: CODE.unusedParameter,
|
|
636
822
|
declaration,
|
|
637
823
|
kind: "parameter",
|
|
638
824
|
name: name.text,
|
|
639
825
|
position: spanOf(file, root, name),
|
|
640
826
|
message: `parameter ${name.text} is never read in the body`,
|
|
641
827
|
used: read[index] === true,
|
|
642
|
-
parameter: { beforeARead: !pattern && at < lastRead,
|
|
828
|
+
parameter: { beforeARead: !pattern && at < lastRead, ...place },
|
|
643
829
|
};
|
|
644
830
|
found.push(part);
|
|
645
831
|
const parameter = signature.parameters[at];
|
|
@@ -647,42 +833,98 @@ function parameterParts(file, root, declaration, signature, reader, ids, inValue
|
|
|
647
833
|
!part.used &&
|
|
648
834
|
parameter !== undefined &&
|
|
649
835
|
(parameter.type !== undefined || isObjectBindingPattern(parameter.name))) {
|
|
650
|
-
contextual.push({
|
|
836
|
+
contextual.push({
|
|
837
|
+
part,
|
|
838
|
+
signature: expression,
|
|
839
|
+
at,
|
|
840
|
+
pattern: isObjectBindingPattern(parameter.name),
|
|
841
|
+
});
|
|
651
842
|
}
|
|
652
843
|
});
|
|
653
844
|
return { parts: found, contextual };
|
|
654
845
|
}
|
|
655
846
|
/**
|
|
656
847
|
* The names one declaration binds that a write of its initializer stores each on its own:
|
|
657
|
-
* the declared name, or each element name of
|
|
848
|
+
* the declared name, or each element name of a binding pattern at any depth. A name with a
|
|
849
|
+
* default is left out, because removing it skips the default's evaluation, and so is a name
|
|
850
|
+
* beside a rest element of its object pattern, whose removal changes what the rest holds.
|
|
658
851
|
*/
|
|
659
|
-
function
|
|
852
|
+
function boundNames(name) {
|
|
660
853
|
if (isIdentifier(name)) {
|
|
661
854
|
return [name];
|
|
662
855
|
}
|
|
663
|
-
|
|
664
|
-
|
|
856
|
+
const rest = isObjectBindingPattern(name) &&
|
|
857
|
+
name.elements.some((element) => element.dotDotDotToken !== undefined);
|
|
858
|
+
return name.elements.flatMap((element) => {
|
|
859
|
+
if (!isBindingElement(element) || element.name === undefined) {
|
|
860
|
+
return [];
|
|
861
|
+
}
|
|
862
|
+
if (element.initializer !== undefined || (rest && element.dotDotDotToken === undefined)) {
|
|
863
|
+
return [];
|
|
864
|
+
}
|
|
865
|
+
return boundNames(element.name);
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
/** The function whose scope a `var` of one block belongs to, or none at a module's top level. */
|
|
869
|
+
function functionOf(block) {
|
|
870
|
+
for (let at = block.parent;; at = at.parent) {
|
|
871
|
+
if (isFunctionLikeDeclaration(at)) {
|
|
872
|
+
return at;
|
|
873
|
+
}
|
|
874
|
+
if (isSourceFile(at)) {
|
|
875
|
+
return undefined;
|
|
876
|
+
}
|
|
665
877
|
}
|
|
666
|
-
return name.elements.flatMap((element) => isBindingElement(element) && element.name !== undefined ? arrayPatternNames(element.name) : []);
|
|
667
878
|
}
|
|
668
|
-
/**
|
|
879
|
+
/**
|
|
880
|
+
* The locals one block declares with `let`, `const`, `var` or a function declaration
|
|
881
|
+
* inside a function, each with the names of the block that spell it. A `var` of a nested
|
|
882
|
+
* block is one where no nested function of its function names it, since the function scope
|
|
883
|
+
* holds it past the block's end.
|
|
884
|
+
*/
|
|
669
885
|
function localsOf(block) {
|
|
670
886
|
const declared = [];
|
|
887
|
+
const scope = functionOf(block);
|
|
888
|
+
const outlives = scope?.body !== block;
|
|
671
889
|
block.statements.forEach((statement, at) => {
|
|
890
|
+
if (isFunctionDeclaration(statement)) {
|
|
891
|
+
if (scope !== undefined && statement.name !== undefined && statement.body !== undefined) {
|
|
892
|
+
declared.push({
|
|
893
|
+
name: statement.name,
|
|
894
|
+
statement: at,
|
|
895
|
+
initialized: true,
|
|
896
|
+
outlives: false,
|
|
897
|
+
hoisted: true,
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
672
902
|
if (!isVariableStatement(statement)) {
|
|
673
903
|
return;
|
|
674
904
|
}
|
|
675
905
|
const list = statement.declarationList;
|
|
676
|
-
|
|
677
|
-
|
|
906
|
+
const blockScoped = (list.flags & (NodeFlags.Let | NodeFlags.Const)) !== 0;
|
|
907
|
+
if ((!blockScoped && scope === undefined) || (list.flags & NodeFlags.Using) !== 0) {
|
|
678
908
|
return;
|
|
679
909
|
}
|
|
680
910
|
for (const declaration of list.declarations) {
|
|
681
|
-
|
|
911
|
+
// A write to a local typed `never` is the compiler's exhaustiveness check, which
|
|
912
|
+
// fails to compile once a case goes unhandled.
|
|
913
|
+
if (declaration.type?.kind === SyntaxKind.NeverKeyword) {
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
for (const name of boundNames(declaration.name)) {
|
|
917
|
+
if (!blockScoped &&
|
|
918
|
+
outlives &&
|
|
919
|
+
identifiersNaming(scope?.body ?? block, new Set([name.text])).some((use) => inNestedFunction(use, scope ?? block))) {
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
682
922
|
declared.push({
|
|
683
923
|
name,
|
|
684
924
|
statement: at,
|
|
685
925
|
initialized: declaration.initializer !== undefined,
|
|
926
|
+
outlives: !blockScoped && outlives,
|
|
927
|
+
hoisted: false,
|
|
686
928
|
});
|
|
687
929
|
}
|
|
688
930
|
}
|
|
@@ -696,19 +938,47 @@ function localsOf(block) {
|
|
|
696
938
|
uses: spelled.filter((use) => use !== one.name && use.text === one.name.text),
|
|
697
939
|
}));
|
|
698
940
|
}
|
|
941
|
+
/** Whether one name is the whole target of a plain assignment, which writes it and reads nothing. */
|
|
942
|
+
function isPlainWrite(use) {
|
|
943
|
+
const parent = outerParent(use);
|
|
944
|
+
return (isBinaryExpression(parent) &&
|
|
945
|
+
parent.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
946
|
+
parenthesesRemoved(parent.left) === use);
|
|
947
|
+
}
|
|
699
948
|
/**
|
|
700
949
|
* The writes to the locals of one block that no read reaches. A write is a declaration's
|
|
701
|
-
* initializer or a plain assignment written as a statement of the block
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
* and
|
|
706
|
-
* at any time.
|
|
950
|
+
* initializer or a plain assignment written as a statement of the block; it is dead when
|
|
951
|
+
* the block's later statements read the local nowhere before the next such write or the
|
|
952
|
+
* block's end. A statement writing it inside a nested construct ends the search with the
|
|
953
|
+
* write kept. A local a nested function reads is no subject, since the function may run at
|
|
954
|
+
* any time, and every write to a local nothing reads is dead, a nested function's included.
|
|
707
955
|
*/
|
|
708
956
|
function deadStores(file, root, block, holder, locals, ids) {
|
|
709
957
|
const found = [];
|
|
958
|
+
const deadAt = (local, target) => ({
|
|
959
|
+
code: CODE.deadStore,
|
|
960
|
+
declaration: holder,
|
|
961
|
+
kind: "store",
|
|
962
|
+
name: local.name.text,
|
|
963
|
+
position: spanOf(file, root, target),
|
|
964
|
+
message: `value written to ${local.name.text} is never read`,
|
|
965
|
+
used: false,
|
|
966
|
+
});
|
|
710
967
|
for (const local of locals) {
|
|
711
|
-
const
|
|
968
|
+
const own = local.hoisted ? block.statements[local.statement] : undefined;
|
|
969
|
+
// A function's reads of itself keep it no more than its absence would.
|
|
970
|
+
const uses = local.uses.filter((use) => readsBinding(use, local.name, ids) && (own === undefined || !within(use, own)));
|
|
971
|
+
if (!local.outlives && uses.every(isPlainWrite)) {
|
|
972
|
+
// A declaration with no initializer writes `undefined`, the one store left to name.
|
|
973
|
+
const writes = [...(local.initialized ? [local.name] : []), ...uses];
|
|
974
|
+
const targets = writes.length === 0 ? [local.name] : writes;
|
|
975
|
+
found.push(...targets.map((target) => deadAt(local, target)));
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
// A read anywhere in the block reads a function declaration, which is written first.
|
|
979
|
+
if (local.hoisted) {
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
712
982
|
if (uses.some((use) => inNestedFunction(use, block))) {
|
|
713
983
|
continue;
|
|
714
984
|
}
|
|
@@ -719,12 +989,12 @@ function deadStores(file, root, block, holder, locals, ids) {
|
|
|
719
989
|
if (!isExpressionStatement(statement)) {
|
|
720
990
|
return undefined;
|
|
721
991
|
}
|
|
722
|
-
const expression =
|
|
992
|
+
const expression = parenthesesRemoved(statement.expression);
|
|
723
993
|
if (!isBinaryExpression(expression) ||
|
|
724
994
|
expression.operatorToken.kind !== SyntaxKind.EqualsToken) {
|
|
725
995
|
return undefined;
|
|
726
996
|
}
|
|
727
|
-
const target =
|
|
997
|
+
const target = parenthesesRemoved(expression.left);
|
|
728
998
|
return isIdentifier(target) && uses.includes(target)
|
|
729
999
|
? { target, value: expression.right }
|
|
730
1000
|
: undefined;
|
|
@@ -746,7 +1016,7 @@ function deadStores(file, root, block, holder, locals, ids) {
|
|
|
746
1016
|
if (sameStatement.length > 0) {
|
|
747
1017
|
continue;
|
|
748
1018
|
}
|
|
749
|
-
let dead =
|
|
1019
|
+
let dead = !local.outlives;
|
|
750
1020
|
for (let at = write.at + 1; at < statements.length; at += 1) {
|
|
751
1021
|
const statement = statements[at];
|
|
752
1022
|
if (statement === undefined) {
|
|
@@ -763,19 +1033,9 @@ function deadStores(file, root, block, holder, locals, ids) {
|
|
|
763
1033
|
break;
|
|
764
1034
|
}
|
|
765
1035
|
}
|
|
766
|
-
if (
|
|
767
|
-
|
|
1036
|
+
if (dead) {
|
|
1037
|
+
found.push(deadAt(local, write.target));
|
|
768
1038
|
}
|
|
769
|
-
const position = spanOf(file, root, write.target);
|
|
770
|
-
found.push({
|
|
771
|
-
code: DEAD_STORE,
|
|
772
|
-
declaration: holder,
|
|
773
|
-
kind: "store",
|
|
774
|
-
name: local.name.text,
|
|
775
|
-
position,
|
|
776
|
-
message: `value written to ${local.name.text} is never read`,
|
|
777
|
-
used: false,
|
|
778
|
-
});
|
|
779
1039
|
}
|
|
780
1040
|
}
|
|
781
1041
|
return found;
|
|
@@ -812,7 +1072,7 @@ function resultParts(project, root, sites, references, calls) {
|
|
|
812
1072
|
const valued = project.queries.nonNullable(result);
|
|
813
1073
|
const members = isAnswered(valued) ? project.queries.propertiesOf(valued) : UNANSWERED;
|
|
814
1074
|
found.push({
|
|
815
|
-
code:
|
|
1075
|
+
code: CODE.unusedResult,
|
|
816
1076
|
declaration: id,
|
|
817
1077
|
kind: "result",
|
|
818
1078
|
name: "result",
|
|
@@ -839,6 +1099,12 @@ export function intraFunctionFacts(projects, references, exempted) {
|
|
|
839
1099
|
const known = parts.get(key);
|
|
840
1100
|
parts.set(key, known === undefined ? part : { ...known, used: known.used || part.used });
|
|
841
1101
|
}
|
|
1102
|
+
const imports = new Map();
|
|
1103
|
+
for (const binding of projects.flatMap((one) => one.imports)) {
|
|
1104
|
+
const key = positionKey(binding.position);
|
|
1105
|
+
const known = imports.get(key);
|
|
1106
|
+
imports.set(key, known === undefined ? binding : { ...known, used: known.used || binding.used });
|
|
1107
|
+
}
|
|
842
1108
|
const calls = new Map();
|
|
843
1109
|
const specifiers = new Set();
|
|
844
1110
|
for (const one of projects) {
|
|
@@ -885,5 +1151,11 @@ export function intraFunctionFacts(projects, references, exempted) {
|
|
|
885
1151
|
const discardedEverywhere = new Set([...called]
|
|
886
1152
|
.filter(([id, all]) => all && !valued.has(id) && !sequenced.has(id))
|
|
887
1153
|
.map(([id]) => id));
|
|
888
|
-
return {
|
|
1154
|
+
return {
|
|
1155
|
+
parts: [...parts.values()],
|
|
1156
|
+
imports: [...imports.values()],
|
|
1157
|
+
free,
|
|
1158
|
+
freeButValued,
|
|
1159
|
+
discardedEverywhere,
|
|
1160
|
+
};
|
|
889
1161
|
}
|