@cplieger/deadset-ts 8.0.0-dev.21 → 8.0.0-dev.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contract/contract.generated.js +1 -1
- package/dist/src/findings/intra-function.js +50 -37
- package/dist/src/intra-function-parts.js +1149 -222
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/conformance-record.ts +1 -1
- package/src/contract/contract.generated.ts +1 -1
- package/src/findings/intra-function.ts +59 -37
- package/src/intra-function-parts.ts +1435 -247
|
@@ -4,7 +4,7 @@ export const RECORD = {
|
|
|
4
4
|
conformance: {
|
|
5
5
|
corpusVersion: "6.1.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:fcfdf88a7d45e1ac96545ef10c8427462260f4e81641d4d808b80086256bb6b7",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
};
|
|
@@ -266,7 +266,7 @@ export const KIND_ROWS = [
|
|
|
266
266
|
subject: "part",
|
|
267
267
|
holdsLive: false,
|
|
268
268
|
rule: "A parameter with no reference inside its function body, on a function whose signature is free to change.",
|
|
269
|
-
precondition: "The signature must be free, defined as follows: the function is not a method retained by interface satisfaction, is not a method whose name and signature are those of a method of an exported interface type declared by a package outside the target that the import closure holds, is not used as a value, is not a go:linkname or cgo target, is not a go-fuzz entry point, is not a function the Go test driver runs (a test, benchmark or fuzz test of a test file, or TestMain), and is not a stub whose body is empty or only panics. A function literal or function expression that initializes a variable the program only calls, never passing, storing, returning or assigning it, is called by name and is not used as a value. In Go a function literal that is the callee of the call it is written in, the call of a `go` or a `defer` statement included, is not used as a value, wherever the literal is written. A parameter its body never names is dead whatever the callers, so a published declaration of a library is reported too, with the fixability the vocabulary gives the kind: the signature change is a breaking change. In TypeScript a caller may pass a function used as a value more arguments than it declares parameters, so in such a function whose signature is free but for that use, a parameter its body never reads is reported for deletion when its body reads no parameter after it, and an unread parameter before the last read one is reported for a rename to its name with a leading underscore, which keeps every argument in place and which the compilers' unused-parameter rules read as declared unread, unless its name already starts with one. A parameter of a TypeScript function expression or arrow function that the compiler gives a contextual type, and that carries a type annotation, is not reported for deletion where the inference of the call holding the function may read the annotation, being where the callee is generic and its parameter type at the function's place names a type parameter at the parameter's place, or where the function is written inside an object literal such a call is passed, and it is reported for deletion at `probable` where it carries an annotation no such inference reads, because nothing in the program shows that an outside reader ignores it. A name an object binding pattern of a parameter of such a function binds, annotated or not, that the body never reads is reported for deletion at `probable`, because the pattern reads the property it names from the argument and the code that hands the argument may act on that read, which nothing in the program shows. Any other TypeScript parameter that is a binding pattern is judged by the names it binds: each name the body never reads is reported wherever the pattern stands, because removing it from the pattern moves no argument, except a name beside a rest element of the same object pattern, whose removal changes what that element holds, and the pattern counts as a read parameter when the body reads any name it binds. A rest parameter is judged as the last parameter. A TypeScript `this` parameter declares the type of `this` and receives no argument, so it is never reported. A TypeScript constructor is used as a value where its class is used as a value other than as the callee of a `new` expression or the class an `extends` clause names, and its signature is not free where its class carries a decorator or a parameter of the constructor carries one, because the code that decorator or a container runs may construct the class. A parameter property is read by the write to its member the constructor makes, so it is never reported under this kind, and its member is judged under DS1301. A TypeScript name a parameter binds, whole or through a binding pattern, that starts with an underscore is never reported, because a leading underscore is the spelling unused-parameter rules read as declared unread, and no other name exempts a parameter. A Go parameter named by the blank identifier declares no name a body could read and is never reported.",
|
|
269
|
+
precondition: "The signature must be free, defined as follows: the function is not a method retained by interface satisfaction, is not a method whose name and signature are those of a method of an exported interface type declared by a package outside the target that the import closure holds, is not used as a value, is not a go:linkname or cgo target, is not a go-fuzz entry point, is not a function the Go test driver runs (a test, benchmark or fuzz test of a test file, or TestMain), and is not a stub whose body is empty or only panics. A function literal or function expression that initializes a variable the program only calls, never passing, storing, returning or assigning it, is called by name and is not used as a value. In Go a function literal that is the callee of the call it is written in, the call of a `go` or a `defer` statement included, is not used as a value, wherever the literal is written. A parameter its body never names is dead whatever the callers, so a published declaration of a library is reported too, with the fixability the vocabulary gives the kind: the signature change is a breaking change. In TypeScript a caller may pass a function used as a value more arguments than it declares parameters, so in such a function whose signature is free but for that use, a parameter its body never reads is reported for deletion when its body reads no parameter after it, and an unread parameter before the last read one is reported for a rename to its name with a leading underscore, which keeps every argument in place and which the compilers' unused-parameter rules read as declared unread, unless its name already starts with one. A function's `length` counts each parameter before the first one with a default or a rest, and code holding the function may read it, so in TypeScript an unread parameter that `length` counts is reported at `probable` where the function is used as a value or is a root, which a library's published API is and which code outside the program calls, and a function whose `length` the program reads through its own name reports no parameter. A parameter of a TypeScript function expression or arrow function that the compiler gives a contextual type, and that carries a type annotation, is not reported for deletion where the inference of the call holding the function may read the annotation, being where the callee is generic and its parameter type at the function's place names a type parameter at the parameter's place, or where the function is written inside an object literal such a call is passed, and it is reported for deletion at `probable` where it carries an annotation no such inference reads, because nothing in the program shows that an outside reader ignores it. A name an object binding pattern of a parameter of such a function binds, annotated or not, that the body never reads is reported for deletion at `probable`, because the pattern reads the property it names from the argument and the code that hands the argument may act on that read, which nothing in the program shows. Any other TypeScript parameter that is a binding pattern is judged by the names it binds: each name the body never reads is reported wherever the pattern stands, because removing it from the pattern moves no argument, except a name beside a rest element of the same object pattern, whose removal changes what that element holds, and the pattern counts as a read parameter when the body reads any name it binds. A rest parameter is judged as the last parameter. A TypeScript `this` parameter declares the type of `this` and receives no argument, so it is never reported. A TypeScript constructor is used as a value where its class is used as a value other than as the callee of a `new` expression or the class an `extends` clause names, and its signature is not free where its class carries a decorator or a parameter of the constructor carries one, because the code that decorator or a container runs may construct the class. A parameter property is read by the write to its member the constructor makes, so it is never reported under this kind, and its member is judged under DS1301. A TypeScript name a parameter binds, whole or through a binding pattern, that starts with an underscore is never reported, because a leading underscore is the spelling unused-parameter rules read as declared unread, and no other name exempts a parameter. A Go parameter named by the blank identifier declares no name a body could read and is never reported.",
|
|
270
270
|
details: { overlap: "required" },
|
|
271
271
|
defaultEnabled: true,
|
|
272
272
|
defaultSeverity: "warn",
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { CODE } from "../contract/contract.generated.js";
|
|
2
2
|
import { KINDS } from "../kinds.js";
|
|
3
3
|
import { parameterRef } from "../ref.js";
|
|
4
|
-
import {} from "../intra-function-parts.js";
|
|
5
4
|
import { reachedWithTheTests } from "../liveness/sweep.js";
|
|
5
|
+
/** The code each part kind is reported under, and what its finding says before the overlap clause. */
|
|
6
|
+
const PART_KINDS = {
|
|
7
|
+
parameter: {
|
|
8
|
+
code: CODE.unusedParameter,
|
|
9
|
+
says: (name) => `parameter ${name} is never read in the body`,
|
|
10
|
+
},
|
|
11
|
+
result: { code: CODE.unusedResult, says: () => "result is discarded at every call site" },
|
|
12
|
+
store: { code: CODE.deadStore, says: (name) => `value written to ${name} is never read` },
|
|
13
|
+
statement: { code: CODE.unreachableStatement, says: () => "statement is unreachable" },
|
|
14
|
+
case: {
|
|
15
|
+
code: CODE.unreachableCase,
|
|
16
|
+
says: (name) => `case ${name} is covered by an earlier case of the same switch`,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
6
19
|
/** A list of rules as a sentence names them. */
|
|
7
20
|
function spelled(rules) {
|
|
8
21
|
if (rules.length < 2) {
|
|
@@ -25,17 +38,24 @@ function overlapClause(overlap, part) {
|
|
|
25
38
|
: `also reported by ${spelled(rules)}`;
|
|
26
39
|
}
|
|
27
40
|
/**
|
|
28
|
-
* Whether one unread parameter is reported, and how. Of a free
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
41
|
+
* Whether one unread parameter is reported, and how (DS1401's precondition). Of a free
|
|
42
|
+
* signature it is deleted. Of a function used as a value, one a read parameter follows is
|
|
43
|
+
* renamed to an underscore name, and any other is deleted, kept where the call's inference
|
|
44
|
+
* may read its annotation. A deletion is `probable` where an unread annotation or a
|
|
45
|
+
* contextual pattern may matter to the caller, or where `length` counts the parameter of
|
|
46
|
+
* a function used as a value or of a root, whose holder may count it.
|
|
34
47
|
*/
|
|
35
|
-
function parameterVerdict(part, free, freeButValued) {
|
|
48
|
+
function parameterVerdict(part, free, freeButValued, rooted) {
|
|
36
49
|
const place = part.parameter;
|
|
50
|
+
const counted = place?.counted === true;
|
|
37
51
|
if (place?.inValue !== true && free.has(part.declaration)) {
|
|
38
|
-
|
|
52
|
+
if (place?.annotated !== undefined) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
fix: "delete",
|
|
57
|
+
confidence: counted && rooted.has(part.declaration) ? "probable" : "certain",
|
|
58
|
+
};
|
|
39
59
|
}
|
|
40
60
|
const valued = place?.inValue === true || freeButValued.has(part.declaration);
|
|
41
61
|
if (!valued) {
|
|
@@ -47,7 +67,7 @@ function parameterVerdict(part, free, freeButValued) {
|
|
|
47
67
|
if (place?.annotated === "steers") {
|
|
48
68
|
return undefined;
|
|
49
69
|
}
|
|
50
|
-
const unseen = place?.annotated === "free" || place?.contextualPattern === true;
|
|
70
|
+
const unseen = place?.annotated === "free" || place?.contextualPattern === true || counted;
|
|
51
71
|
return { fix: "delete", confidence: unseen ? "probable" : "certain" };
|
|
52
72
|
}
|
|
53
73
|
/**
|
|
@@ -57,15 +77,12 @@ function parameterVerdict(part, free, freeButValued) {
|
|
|
57
77
|
* exports to callers the run cannot see has no result to report. Every other part is
|
|
58
78
|
* reported wherever no project found it in use.
|
|
59
79
|
*/
|
|
60
|
-
function reported(input, part, callersUnknown) {
|
|
61
|
-
if (part.used) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
80
|
+
function reported(input, part, rooted, callersUnknown) {
|
|
64
81
|
const { free, freeButValued, discardedEverywhere } = input.intraFunction;
|
|
65
|
-
switch (part.
|
|
66
|
-
case
|
|
67
|
-
return parameterVerdict(part, free, freeButValued) !== undefined;
|
|
68
|
-
case
|
|
82
|
+
switch (part.kind) {
|
|
83
|
+
case "parameter":
|
|
84
|
+
return parameterVerdict(part, free, freeButValued, rooted) !== undefined;
|
|
85
|
+
case "result":
|
|
69
86
|
return (free.has(part.declaration) &&
|
|
70
87
|
discardedEverywhere.has(part.declaration) &&
|
|
71
88
|
!callersUnknown.has(part.declaration));
|
|
@@ -93,43 +110,39 @@ export const intraFunction = (input) => {
|
|
|
93
110
|
const testCode = union.test[at] === true || union.support[at] === true;
|
|
94
111
|
return !testCode || (reached[at] === true && !testsOfDeadCode.has(id));
|
|
95
112
|
};
|
|
96
|
-
const
|
|
113
|
+
const rooted = new Set(union.rooted.flatMap((root) => {
|
|
97
114
|
const symbol = union.symbols[root.at];
|
|
98
115
|
return symbol === undefined ? [] : [symbol.id];
|
|
99
116
|
}));
|
|
117
|
+
const unknown = new Set(rooted);
|
|
100
118
|
const { declared, loaded } = input.boundary.consumers;
|
|
101
119
|
const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
|
|
102
|
-
const published = (id) => {
|
|
103
|
-
const symbol = byId.get(id);
|
|
104
|
-
return (symbol !== undefined &&
|
|
105
|
-
symbol.kind !== "file" &&
|
|
106
|
-
(symbol.exported || published(symbol.parent)));
|
|
107
|
-
};
|
|
108
120
|
if (!closed) {
|
|
109
|
-
for (const
|
|
110
|
-
|
|
111
|
-
unknown.add(part.declaration);
|
|
112
|
-
}
|
|
121
|
+
for (const id of input.intraFunction.published) {
|
|
122
|
+
unknown.add(id);
|
|
113
123
|
}
|
|
114
124
|
}
|
|
115
125
|
const found = [];
|
|
116
126
|
for (const part of input.intraFunction.parts) {
|
|
117
127
|
const declaration = byId.get(part.declaration);
|
|
118
|
-
const
|
|
119
|
-
|
|
128
|
+
const { code, says } = PART_KINDS[part.kind];
|
|
129
|
+
const overlap = KINDS.get(code)?.overlap?.ts ?? [];
|
|
130
|
+
if (declaration === undefined ||
|
|
131
|
+
!judged(declaration.id) ||
|
|
132
|
+
!reported(input, part, rooted, unknown)) {
|
|
120
133
|
continue;
|
|
121
134
|
}
|
|
122
|
-
const verdict = part.
|
|
123
|
-
? parameterVerdict(part, input.intraFunction.free, input.intraFunction.freeButValued)
|
|
135
|
+
const verdict = part.kind === "parameter"
|
|
136
|
+
? parameterVerdict(part, input.intraFunction.free, input.intraFunction.freeButValued, rooted)
|
|
124
137
|
: undefined;
|
|
125
138
|
const message = verdict?.fix === "rename"
|
|
126
|
-
? `${part.
|
|
127
|
-
: part.
|
|
139
|
+
? `${says(part.name)}, and a read parameter follows it, so rename it to _${part.name}`
|
|
140
|
+
: says(part.name);
|
|
128
141
|
found.push({
|
|
129
|
-
code
|
|
142
|
+
code,
|
|
130
143
|
position: part.position,
|
|
131
144
|
symbol: {
|
|
132
|
-
...(part.
|
|
145
|
+
...(part.kind === "parameter"
|
|
133
146
|
? { ref: parameterRef(declaration.ref, part.name), holder: declaration.ref }
|
|
134
147
|
: { ref: declaration.ref }),
|
|
135
148
|
kind: part.kind,
|