@cplieger/deadset-ts 3.6.0 → 3.8.0
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/analysis.js +92 -23
- package/dist/src/edges.js +190 -0
- package/dist/src/finding.js +0 -11
- package/dist/src/findings/completion.js +92 -35
- package/dist/src/findings/dependencies-and-module-machinery.js +3 -11
- package/dist/src/findings/emitters.js +42 -4
- package/dist/src/findings/reads-and-writes.js +16 -69
- package/dist/src/findings/self-check.js +127 -2
- package/dist/src/findings/unused-declarations.js +8 -40
- package/dist/src/findings/visibility-narrowing.js +9 -40
- package/dist/src/findings-pass.js +97 -0
- package/dist/src/json-document.js +113 -0
- package/dist/src/kinds.js +47 -45
- package/dist/src/roots.js +0 -34
- package/dist/src/suppress-file.js +196 -0
- package/dist/src/suppress-inline.js +259 -0
- package/dist/src/suppress.js +148 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +176 -32
- package/src/edges.ts +241 -0
- package/src/finding.ts +76 -10
- package/src/findings/boundary.ts +0 -11
- package/src/findings/completion.ts +117 -71
- package/src/findings/dependencies-and-module-machinery.ts +4 -19
- package/src/findings/emitter.ts +6 -2
- package/src/findings/emitters.ts +81 -7
- package/src/findings/interfaces.ts +5 -17
- package/src/findings/reads-and-writes.ts +37 -151
- package/src/findings/self-check.ts +205 -2
- package/src/findings/unused-declarations.ts +11 -58
- package/src/findings/visibility-narrowing.ts +11 -58
- package/src/findings-pass.ts +145 -0
- package/src/json-document.ts +130 -0
- package/src/kinds.ts +79 -45
- package/src/roots.ts +0 -39
- package/src/suppress-file.ts +283 -0
- package/src/suppress-inline.ts +318 -0
- package/src/suppress.ts +262 -0
package/src/findings/emitter.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { Finding } from "../finding.ts";
|
|
|
6
6
|
import type { Implementations } from "../implementations.ts";
|
|
7
7
|
import type { Stores } from "../stores.ts";
|
|
8
8
|
import type { Boundary } from "./boundary.ts";
|
|
9
|
+
import type { SelfCheckFacts } from "./self-check.ts";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* What every emitter reads: the configuration the run resolved, the run's sweep, and
|
|
@@ -27,11 +28,14 @@ export interface EmitterInput {
|
|
|
27
28
|
readonly files: FileFacts;
|
|
28
29
|
/** What can reach the target from outside it: consumers, declared edges, and `exports`. */
|
|
29
30
|
readonly boundary: Boundary;
|
|
31
|
+
/** The run's suppression outcomes and configured roots, which the self-check family reports. */
|
|
32
|
+
readonly selfCheck: SelfCheckFacts;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
|
-
* One kind family's rule over one run:
|
|
34
|
-
* of the Contract's code space,
|
|
36
|
+
* One kind family's rule over one run: every finding of the codes in its family's range
|
|
37
|
+
* of the Contract's code space, whatever severity and minimum confidence the
|
|
38
|
+
* configuration sets, in no particular order.
|
|
35
39
|
*/
|
|
36
40
|
export type Emitter = (input: EmitterInput) => readonly Finding[];
|
|
37
41
|
|
package/src/findings/emitters.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { evaluateEdges, type EdgeEvaluation } from "../edges.ts";
|
|
2
|
+
import type { CompletedFinding } from "../finding.ts";
|
|
3
|
+
import { ledgerOf, type Dials, type Ledger, type SuppressionRecord } from "../suppress.ts";
|
|
4
|
+
import { completed, dialsOf, reportable } from "./completion.ts";
|
|
1
5
|
import { dependenciesAndModuleMachinery } from "./dependencies-and-module-machinery.ts";
|
|
2
|
-
import { placed, type PlacedFinding } from "./completion.ts";
|
|
3
6
|
import type { Emitter, EmitterInput, Emitters } from "./emitter.ts";
|
|
4
7
|
import { interfaces } from "./interfaces.ts";
|
|
5
8
|
import { intraFunction } from "./intra-function.ts";
|
|
@@ -21,13 +24,84 @@ export const EMITTERS: Emitters = new Map<string, Emitter>([
|
|
|
21
24
|
["intra-function", intraFunction],
|
|
22
25
|
]);
|
|
23
26
|
|
|
27
|
+
/** What the suppressions of a run swept under their marks are decided against. */
|
|
28
|
+
export interface Suppressed {
|
|
29
|
+
/** The same run swept with no mark, which is where a kind about liveness reports a marked declaration. */
|
|
30
|
+
readonly unmarked: EmitterInput;
|
|
31
|
+
/** The run's suppression records, in reading order. */
|
|
32
|
+
readonly records: readonly SuppressionRecord[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** What the emitter table answers about one run. */
|
|
36
|
+
export interface DecidedFindings {
|
|
37
|
+
/** Every finding the run reports, each family's in table order. */
|
|
38
|
+
readonly findings: readonly CompletedFinding[];
|
|
39
|
+
/** One evaluation per declared edge side of this language, ordered by edge, then by side. */
|
|
40
|
+
readonly evaluations: readonly EdgeEvaluation<CompletedFinding>[];
|
|
41
|
+
/** What every suppression record did, in reading order. */
|
|
42
|
+
readonly ledger: Ledger<CompletedFinding>;
|
|
43
|
+
/** Every finding a record could name: the run's with no mark, then the run's as swept. */
|
|
44
|
+
readonly would: readonly CompletedFinding[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Every family's findings over one sweep, completed by one step. */
|
|
48
|
+
function completedOver(input: EmitterInput): readonly CompletedFinding[] {
|
|
49
|
+
return completed(
|
|
50
|
+
input,
|
|
51
|
+
[...EMITTERS.values()].flatMap((emit) => emit(input)),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
56
|
+
* The dials over the findings a record could name, each read against the sweep that
|
|
57
|
+
* produced it, so a component one sweep numbers withholds nothing of the other's.
|
|
27
58
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
59
|
+
function dialsOver(
|
|
60
|
+
input: EmitterInput,
|
|
61
|
+
found: readonly CompletedFinding[],
|
|
62
|
+
suppressed: Suppressed,
|
|
63
|
+
unmarked: readonly CompletedFinding[],
|
|
64
|
+
): Dials<CompletedFinding> {
|
|
65
|
+
const marked = dialsOf(input, found);
|
|
66
|
+
const before = dialsOf(suppressed.unmarked, unmarked);
|
|
67
|
+
const fromUnmarked = new Set(unmarked);
|
|
68
|
+
return {
|
|
69
|
+
withholdsCode: marked.withholdsCode,
|
|
70
|
+
withholds: (finding) =>
|
|
71
|
+
fromUnmarked.has(finding) ? before.withholds(finding) : marked.withholds(finding),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Every finding of one run swept under its suppressions' marks, decided in one pass: every
|
|
77
|
+
* family's findings completed by one step, so every component number is minted once, a
|
|
78
|
+
* pending finding's among them; the finding each record claims withheld, whatever its
|
|
79
|
+
* kind; the rest withheld by the dials, so a root one family reports withholds what
|
|
80
|
+
* another family reports in its component; and every declared edge evaluated over what
|
|
81
|
+
* remains, so a finding it holds pending is in its evaluation and nowhere else.
|
|
82
|
+
*/
|
|
83
|
+
export function decidedFindings(
|
|
84
|
+
input: EmitterInput,
|
|
85
|
+
suppressed: Suppressed = { unmarked: input, records: [] },
|
|
86
|
+
): DecidedFindings {
|
|
87
|
+
const found = completedOver(input);
|
|
88
|
+
const unmarked = suppressed.records.length === 0 ? [] : completedOver(suppressed.unmarked);
|
|
89
|
+
const would = [...unmarked, ...found];
|
|
90
|
+
const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
|
|
91
|
+
const kept = reportable(
|
|
92
|
+
input,
|
|
93
|
+
found.filter((finding) => !ledger.withheld(finding)),
|
|
32
94
|
);
|
|
95
|
+
const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
|
|
96
|
+
return {
|
|
97
|
+
findings: evaluated.findings,
|
|
98
|
+
evaluations: evaluated.evaluations,
|
|
99
|
+
ledger,
|
|
100
|
+
would,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Every finding the run reports, each family's in table order. */
|
|
105
|
+
export function findingsOf(input: EmitterInput): readonly CompletedFinding[] {
|
|
106
|
+
return decidedFindings(input).findings;
|
|
33
107
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Finding, FindingPosition } from "../finding.ts";
|
|
1
|
+
import type { Finding, FindingPosition, PositionedSymbol } from "../finding.ts";
|
|
2
2
|
import type { InventorySymbol } from "../inventory.ts";
|
|
3
3
|
import { byPosition } from "../position.ts";
|
|
4
4
|
import type { Candidate } from "../sweep.ts";
|
|
@@ -14,18 +14,6 @@ const UNUSED_DEAD_MESSAGE =
|
|
|
14
14
|
const UNUSED_TEST_MESSAGE = "the interface is named as a type only from test files";
|
|
15
15
|
const UNCALLED_MESSAGE = "no call site invokes or selects the method through the interface";
|
|
16
16
|
|
|
17
|
-
/** One class a finding names as an implementation of its interface. */
|
|
18
|
-
interface Implementation {
|
|
19
|
-
readonly ref: string;
|
|
20
|
-
readonly name: string;
|
|
21
|
-
readonly position: FindingPosition;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** A finding of this family, with the implementations its details carry. */
|
|
25
|
-
interface InterfaceFinding extends Finding {
|
|
26
|
-
readonly details: { readonly implementations: readonly Implementation[] };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
17
|
/** Where one declaration is written, as a finding carries it. */
|
|
30
18
|
function positionOf(symbol: InventorySymbol): FindingPosition {
|
|
31
19
|
return { ...symbol.position, endLine: Math.max(symbol.position.line, symbol.endLine) };
|
|
@@ -44,7 +32,7 @@ function indexed(input: EmitterInput): {
|
|
|
44
32
|
readonly symbolOf: (id: string) => InventorySymbol | undefined;
|
|
45
33
|
readonly inTest: (id: string) => boolean;
|
|
46
34
|
readonly candidateOf: (id: string) => Candidate | undefined;
|
|
47
|
-
readonly implementationsOf: (id: string) => readonly
|
|
35
|
+
readonly implementationsOf: (id: string) => readonly PositionedSymbol[];
|
|
48
36
|
} {
|
|
49
37
|
const union = input.swept.matrix.union;
|
|
50
38
|
const symbols = new Map(union.symbols.map((symbol) => [symbol.id, symbol]));
|
|
@@ -70,8 +58,8 @@ function findingOf(
|
|
|
70
58
|
code: string,
|
|
71
59
|
symbol: InventorySymbol,
|
|
72
60
|
message: string,
|
|
73
|
-
implementations: readonly
|
|
74
|
-
):
|
|
61
|
+
implementations: readonly PositionedSymbol[],
|
|
62
|
+
): Finding {
|
|
75
63
|
const position = positionOf(symbol);
|
|
76
64
|
return {
|
|
77
65
|
code,
|
|
@@ -96,7 +84,7 @@ function findingOf(
|
|
|
96
84
|
*/
|
|
97
85
|
export const interfaces: Emitter = (input) => {
|
|
98
86
|
const run = indexed(input);
|
|
99
|
-
const found:
|
|
87
|
+
const found: Finding[] = [];
|
|
100
88
|
for (const candidate of input.swept.sweep.candidates) {
|
|
101
89
|
const symbol = run.symbolOf(candidate.id);
|
|
102
90
|
if (symbol === undefined || run.inTest(candidate.id)) {
|
|
@@ -1,72 +1,20 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { Finding, FindingPosition } from "../finding.ts";
|
|
1
|
+
import type { Finding, FindingDetails, FindingPosition } from "../finding.ts";
|
|
3
2
|
import type { InventorySymbol } from "../inventory.ts";
|
|
4
3
|
import type { MatrixCandidate } from "../matrix.ts";
|
|
5
4
|
import { positionKey } from "../position.ts";
|
|
6
|
-
import type { Relation } from "../sweep.ts";
|
|
7
|
-
import {
|
|
8
|
-
findingComponents,
|
|
9
|
-
RANK,
|
|
10
|
-
reachabilityClasses,
|
|
11
|
-
severityOf,
|
|
12
|
-
type FindingComponent,
|
|
13
|
-
} from "./completion.ts";
|
|
14
5
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
15
6
|
|
|
16
7
|
/** The codes of this family. */
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Each code's severity where the configuration sets none, as the issue-kind vocabulary
|
|
21
|
-
* states it.
|
|
22
|
-
*/
|
|
23
|
-
export const DEFAULT_SEVERITY: Readonly<Record<ReadsAndWritesCode, Severity>> = {
|
|
24
|
-
DS1301: "deny",
|
|
25
|
-
DS1302: "deny",
|
|
26
|
-
DS1303: "deny",
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/** What only some codes carry. */
|
|
30
|
-
export interface ReadsAndWritesDetails {
|
|
31
|
-
/** Every position the subject is written at, on `DS1301` alone. */
|
|
32
|
-
readonly writePositions?: readonly FindingPosition[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** One finding of this family, with every member the family decides. */
|
|
36
|
-
export interface ReadsAndWritesFinding extends Finding {
|
|
37
|
-
readonly code: ReadsAndWritesCode;
|
|
38
|
-
readonly reachabilityClass: Confidence;
|
|
39
|
-
readonly confidence: Confidence;
|
|
40
|
-
/** The relation that decided the subject, absent on `DS1301`, whose subject is live. */
|
|
41
|
-
readonly livenessRelation?: Relation;
|
|
42
|
-
/** Whether every reference the run counts to the subject comes from a test file. */
|
|
43
|
-
readonly testOnly: boolean;
|
|
44
|
-
/**
|
|
45
|
-
* The dead component the subject belongs to, absent where it belongs to none: a live
|
|
46
|
-
* subject falls with nothing, and the report gives it a component of its own.
|
|
47
|
-
*/
|
|
48
|
-
readonly component?: FindingComponent;
|
|
49
|
-
/** The configurations the finding holds in, in the run's order. */
|
|
50
|
-
readonly configurations: readonly string[];
|
|
51
|
-
readonly severity: Severity;
|
|
52
|
-
readonly details: ReadsAndWritesDetails;
|
|
53
|
-
}
|
|
8
|
+
type ReadsAndWritesCode = "DS1301" | "DS1302" | "DS1303";
|
|
54
9
|
|
|
55
10
|
/** The containers whose type parameters `DS1303` reports: a function and a class method. */
|
|
56
11
|
const SIGNATURES: ReadonlySet<string> = new Set(["function", "method"]);
|
|
57
12
|
|
|
58
|
-
/** The severity the configuration gives one code of the family. */
|
|
59
|
-
function severityIn(input: EmitterInput, code: ReadsAndWritesCode): Severity {
|
|
60
|
-
return severityOf(input.config, code, DEFAULT_SEVERITY[code]);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
13
|
/** What the family reads of one run, indexed by declaration. */
|
|
64
14
|
interface Run {
|
|
65
15
|
readonly input: EmitterInput;
|
|
66
16
|
readonly symbols: ReadonlyMap<string, InventorySymbol>;
|
|
67
17
|
readonly candidates: ReadonlyMap<string, MatrixCandidate>;
|
|
68
|
-
readonly componentOf: (id: string) => FindingComponent | undefined;
|
|
69
|
-
readonly classOf: (id: string) => Confidence;
|
|
70
18
|
}
|
|
71
19
|
|
|
72
20
|
function indexed(input: EmitterInput): Run {
|
|
@@ -75,8 +23,6 @@ function indexed(input: EmitterInput): Run {
|
|
|
75
23
|
input,
|
|
76
24
|
symbols: new Map(matrix.union.symbols.map((symbol) => [symbol.id, symbol])),
|
|
77
25
|
candidates: new Map(sweep.candidates.map((candidate) => [candidate.id, candidate])),
|
|
78
|
-
componentOf: findingComponents(input.swept),
|
|
79
|
-
classOf: reachabilityClasses(input.swept),
|
|
80
26
|
};
|
|
81
27
|
}
|
|
82
28
|
|
|
@@ -90,22 +36,14 @@ function positionOf(symbol: InventorySymbol): FindingPosition {
|
|
|
90
36
|
};
|
|
91
37
|
}
|
|
92
38
|
|
|
93
|
-
/** The finding
|
|
39
|
+
/** The finding one code makes about one declaration. */
|
|
94
40
|
function findingAbout(
|
|
95
|
-
run: Run,
|
|
96
41
|
symbol: InventorySymbol,
|
|
97
42
|
code: ReadsAndWritesCode,
|
|
98
43
|
message: string,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
readonly testOnly: boolean;
|
|
102
|
-
readonly configurations: readonly string[];
|
|
103
|
-
readonly details: ReadsAndWritesDetails;
|
|
104
|
-
},
|
|
105
|
-
): ReadsAndWritesFinding {
|
|
44
|
+
details?: FindingDetails,
|
|
45
|
+
): Finding {
|
|
106
46
|
const position = positionOf(symbol);
|
|
107
|
-
const reachabilityClass = run.classOf(symbol.id);
|
|
108
|
-
const component = run.componentOf(symbol.id);
|
|
109
47
|
return {
|
|
110
48
|
code,
|
|
111
49
|
position,
|
|
@@ -116,14 +54,7 @@ function findingAbout(
|
|
|
116
54
|
sizeLines: position.endLine - position.line + 1,
|
|
117
55
|
},
|
|
118
56
|
message,
|
|
119
|
-
|
|
120
|
-
confidence: reachabilityClass,
|
|
121
|
-
...(facts.livenessRelation === undefined ? {} : { livenessRelation: facts.livenessRelation }),
|
|
122
|
-
testOnly: facts.testOnly,
|
|
123
|
-
...(component === undefined ? {} : { component }),
|
|
124
|
-
configurations: facts.configurations,
|
|
125
|
-
severity: severityIn(run.input, code),
|
|
126
|
-
details: facts.details,
|
|
57
|
+
...(details === undefined ? {} : { details }),
|
|
127
58
|
};
|
|
128
59
|
}
|
|
129
60
|
|
|
@@ -131,8 +62,6 @@ function findingAbout(
|
|
|
131
62
|
interface Uses {
|
|
132
63
|
readonly writes: FindingPosition[];
|
|
133
64
|
reads: number;
|
|
134
|
-
production: number;
|
|
135
|
-
test: number;
|
|
136
65
|
}
|
|
137
66
|
|
|
138
67
|
/**
|
|
@@ -155,21 +84,16 @@ function writable(run: Run, symbol: InventorySymbol): boolean {
|
|
|
155
84
|
* written one live and its candidates are never this kind's. Every reference that is
|
|
156
85
|
* not a store reads, and so does an exemption record naming the declaration.
|
|
157
86
|
*/
|
|
158
|
-
function writeOnlySymbols(run: Run): readonly
|
|
87
|
+
function writeOnlySymbols(run: Run): readonly Finding[] {
|
|
159
88
|
const { stores } = run.input;
|
|
160
89
|
const { matrix } = run.input.swept;
|
|
161
90
|
const uses = new Map<string, Uses>();
|
|
162
91
|
for (const reference of stores.references) {
|
|
163
92
|
let held = uses.get(reference.to);
|
|
164
93
|
if (held === undefined) {
|
|
165
|
-
held = { writes: [], reads: 0
|
|
94
|
+
held = { writes: [], reads: 0 };
|
|
166
95
|
uses.set(reference.to, held);
|
|
167
96
|
}
|
|
168
|
-
if (reference.test) {
|
|
169
|
-
held.test += 1;
|
|
170
|
-
} else {
|
|
171
|
-
held.production += 1;
|
|
172
|
-
}
|
|
173
97
|
if (reference.use !== "write") {
|
|
174
98
|
held.reads += 1;
|
|
175
99
|
continue;
|
|
@@ -181,8 +105,8 @@ function writeOnlySymbols(run: Run): readonly ReadsAndWritesFinding[] {
|
|
|
181
105
|
}
|
|
182
106
|
}
|
|
183
107
|
|
|
184
|
-
const found:
|
|
185
|
-
matrix.union.symbols
|
|
108
|
+
const found: Finding[] = [];
|
|
109
|
+
for (const symbol of matrix.union.symbols) {
|
|
186
110
|
const held = uses.get(symbol.id);
|
|
187
111
|
if (
|
|
188
112
|
held === undefined ||
|
|
@@ -192,24 +116,16 @@ function writeOnlySymbols(run: Run): readonly ReadsAndWritesFinding[] {
|
|
|
192
116
|
stores.exempt.has(symbol.id) ||
|
|
193
117
|
run.candidates.has(symbol.id)
|
|
194
118
|
) {
|
|
195
|
-
|
|
119
|
+
continue;
|
|
196
120
|
}
|
|
197
121
|
const writes = held.writes.length === 1 ? "once" : `at ${String(held.writes.length)} positions`;
|
|
198
122
|
const word = symbol.kind === "variable" ? "variable" : "member";
|
|
199
123
|
found.push(
|
|
200
|
-
findingAbout(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
"DS1301",
|
|
204
|
-
`${word} ${symbol.name} is written ${writes} and never read`,
|
|
205
|
-
{
|
|
206
|
-
testOnly: held.production === 0 && held.test > 0,
|
|
207
|
-
configurations: matrix.heldIn[at] ?? [],
|
|
208
|
-
details: { writePositions: held.writes },
|
|
209
|
-
},
|
|
210
|
-
),
|
|
124
|
+
findingAbout(symbol, "DS1301", `${word} ${symbol.name} is written ${writes} and never read`, {
|
|
125
|
+
writePositions: held.writes,
|
|
126
|
+
}),
|
|
211
127
|
);
|
|
212
|
-
}
|
|
128
|
+
}
|
|
213
129
|
return found;
|
|
214
130
|
}
|
|
215
131
|
|
|
@@ -218,24 +134,19 @@ function writeOnlySymbols(run: Run): readonly ReadsAndWritesFinding[] {
|
|
|
218
134
|
* one kind. A reference from a test file alone leaves a candidate to the test-only kind,
|
|
219
135
|
* and a member of a dead container falls with it and is reported inside its component.
|
|
220
136
|
*/
|
|
221
|
-
function unnamed(
|
|
222
|
-
run:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return [];
|
|
235
|
-
}
|
|
236
|
-
return [[candidate, symbol]];
|
|
237
|
-
},
|
|
238
|
-
);
|
|
137
|
+
function unnamed(run: Run, kind: InventorySymbol["kind"]): readonly InventorySymbol[] {
|
|
138
|
+
return run.input.swept.sweep.candidates.flatMap((candidate): InventorySymbol[] => {
|
|
139
|
+
const symbol = run.symbols.get(candidate.id);
|
|
140
|
+
if (
|
|
141
|
+
symbol?.kind !== kind ||
|
|
142
|
+
candidate.productionRefs > 0 ||
|
|
143
|
+
candidate.testRefs > 0 ||
|
|
144
|
+
run.candidates.has(symbol.parent)
|
|
145
|
+
) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
return [symbol];
|
|
149
|
+
});
|
|
239
150
|
}
|
|
240
151
|
|
|
241
152
|
/**
|
|
@@ -243,14 +154,9 @@ function unnamed(
|
|
|
243
154
|
* record holds back is no candidate, so a member a conversion can produce never reaches
|
|
244
155
|
* this kind.
|
|
245
156
|
*/
|
|
246
|
-
function unusedEnumMembers(run: Run): readonly
|
|
247
|
-
return unnamed(run, "enum-member").map((
|
|
248
|
-
findingAbout(
|
|
249
|
-
livenessRelation: candidate.relation,
|
|
250
|
-
testOnly: false,
|
|
251
|
-
configurations: candidate.configurations,
|
|
252
|
-
details: {},
|
|
253
|
-
}),
|
|
157
|
+
function unusedEnumMembers(run: Run): readonly Finding[] {
|
|
158
|
+
return unnamed(run, "enum-member").map((symbol) =>
|
|
159
|
+
findingAbout(symbol, "DS1302", `enum member ${symbol.name} is named nowhere`),
|
|
254
160
|
);
|
|
255
161
|
}
|
|
256
162
|
|
|
@@ -260,21 +166,14 @@ function unusedEnumMembers(run: Run): readonly ReadsAndWritesFinding[] {
|
|
|
260
166
|
* a type alias or a method signature belongs to a type declaration and is never
|
|
261
167
|
* reported, because a phantom parameter makes two instantiations distinct types.
|
|
262
168
|
*/
|
|
263
|
-
function unusedTypeParameters(run: Run): readonly
|
|
169
|
+
function unusedTypeParameters(run: Run): readonly Finding[] {
|
|
264
170
|
return unnamed(run, "type-parameter")
|
|
265
|
-
.filter((
|
|
266
|
-
.map((
|
|
171
|
+
.filter((symbol) => SIGNATURES.has(run.symbols.get(symbol.parent)?.kind ?? ""))
|
|
172
|
+
.map((symbol) =>
|
|
267
173
|
findingAbout(
|
|
268
|
-
run,
|
|
269
174
|
symbol,
|
|
270
175
|
"DS1303",
|
|
271
176
|
`type parameter ${symbol.name} is named in neither its signature nor its body`,
|
|
272
|
-
{
|
|
273
|
-
livenessRelation: candidate.relation,
|
|
274
|
-
testOnly: false,
|
|
275
|
-
configurations: candidate.configurations,
|
|
276
|
-
details: {},
|
|
277
|
-
},
|
|
278
177
|
),
|
|
279
178
|
);
|
|
280
179
|
}
|
|
@@ -288,24 +187,11 @@ export function writeOnlyDeclarations(input: EmitterInput): ReadonlySet<string>
|
|
|
288
187
|
return new Set(writeOnlySymbols(indexed(input)).map((finding) => finding.symbol.ref));
|
|
289
188
|
}
|
|
290
189
|
|
|
291
|
-
/** Each code's rule, in code order. */
|
|
292
|
-
const RULES: readonly [ReadsAndWritesCode, (run: Run) => readonly ReadsAndWritesFinding[]][] = [
|
|
293
|
-
["DS1301", writeOnlySymbols],
|
|
294
|
-
["DS1302", unusedEnumMembers],
|
|
295
|
-
["DS1303", unusedTypeParameters],
|
|
296
|
-
];
|
|
297
|
-
|
|
298
190
|
/**
|
|
299
|
-
* The findings of the reads-and-writes family, `DS1300` to `DS1399`,
|
|
300
|
-
*
|
|
301
|
-
* confidence is below the configured minimum is withheld.
|
|
191
|
+
* The findings of the reads-and-writes family, `DS1300` to `DS1399`, in code order and
|
|
192
|
+
* each code's in site order.
|
|
302
193
|
*/
|
|
303
194
|
export const readsAndWrites: Emitter = (input) => {
|
|
304
195
|
const run = indexed(input);
|
|
305
|
-
|
|
306
|
-
return RULES.flatMap(([code, rule]) =>
|
|
307
|
-
severityIn(input, code) === "allow"
|
|
308
|
-
? []
|
|
309
|
-
: rule(run).filter((finding) => RANK[finding.confidence] >= least),
|
|
310
|
-
);
|
|
196
|
+
return [...writeOnlySymbols(run), ...unusedEnumMembers(run), ...unusedTypeParameters(run)];
|
|
311
197
|
};
|