@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.
@@ -4,7 +4,7 @@ export const RECORD = {
4
4
  conformance: {
5
5
  corpusVersion: "6.1.0",
6
6
  result: "pass",
7
- digest: "sha256:352b870acbc05213d4869bc18611137d0782cc0c9a8379e7a998a3d9cea2d966",
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 signature it is deleted. A
29
- * caller may pass a function used as a value more arguments than it declares, so there one
30
- * a read parameter follows is renamed to an underscore name, which the compilers read as
31
- * declared unread, and one no read parameter follows is deleted: kept where the call's
32
- * inference may read its annotation, at `probable` where nothing reads the annotation or
33
- * where it is a name a contextually typed object pattern binds.
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
- return place?.annotated === undefined ? { fix: "delete", confidence: "certain" } : undefined;
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.code) {
66
- case CODE.unusedParameter:
67
- return parameterVerdict(part, free, freeButValued) !== undefined;
68
- case CODE.unusedResult:
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 unknown = new Set(union.rooted.flatMap((root) => {
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 part of input.intraFunction.parts) {
110
- if (published(part.declaration)) {
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 overlap = KINDS.get(part.code)?.overlap?.ts ?? [];
119
- if (declaration === undefined || !judged(declaration.id) || !reported(input, part, unknown)) {
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.code === CODE.unusedParameter
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.message}, and a read parameter follows it, so rename it to _${part.name}`
127
- : part.message;
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: part.code,
142
+ code,
130
143
  position: part.position,
131
144
  symbol: {
132
- ...(part.code === CODE.unusedParameter
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,