@cplieger/deadset-ts 3.6.0 → 3.7.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/finding.js +0 -11
- package/dist/src/findings/completion.js +62 -26
- package/dist/src/findings/dependencies-and-module-machinery.js +3 -11
- package/dist/src/findings/emitters.js +6 -4
- package/dist/src/findings/reads-and-writes.js +16 -69
- package/dist/src/findings/unused-declarations.js +8 -40
- package/dist/src/kinds.js +47 -45
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/finding.ts +76 -10
- package/src/findings/completion.ts +71 -59
- package/src/findings/dependencies-and-module-machinery.ts +4 -19
- package/src/findings/emitter.ts +3 -2
- package/src/findings/emitters.ts +8 -8
- package/src/findings/interfaces.ts +5 -17
- package/src/findings/reads-and-writes.ts +37 -151
- package/src/findings/unused-declarations.ts +11 -58
- package/src/findings/visibility-narrowing.ts +1 -6
- package/src/kinds.ts +79 -45
|
@@ -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
|
};
|
|
@@ -1,33 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Finding } from "../finding.ts";
|
|
2
2
|
import type { InventorySymbol, SymbolKind } from "../inventory.ts";
|
|
3
3
|
import type { MatrixCandidate } from "../matrix.ts";
|
|
4
|
-
import {
|
|
5
|
-
capped,
|
|
6
|
-
findingComponents,
|
|
7
|
-
reachabilityClasses,
|
|
8
|
-
reportable,
|
|
9
|
-
severityOf,
|
|
10
|
-
type CompletedFinding,
|
|
11
|
-
} from "./completion.ts";
|
|
12
|
-
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
4
|
+
import type { Emitter } from "./emitter.ts";
|
|
13
5
|
import { unreachableExports } from "./visibility-narrowing.ts";
|
|
14
6
|
|
|
15
|
-
/** What the issue-kind vocabulary states about one code of the family. */
|
|
16
|
-
interface KindRow {
|
|
17
|
-
readonly defaultSeverity: Severity;
|
|
18
|
-
readonly maxClass: Confidence;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** The codes of the unused-declarations family, each by its row of the vocabulary. */
|
|
22
|
-
export const UNUSED_DECLARATION_KINDS: ReadonlyMap<string, KindRow> = new Map([
|
|
23
|
-
["DS1001", { defaultSeverity: "deny", maxClass: "certain" }],
|
|
24
|
-
["DS1002", { defaultSeverity: "deny", maxClass: "certain" }],
|
|
25
|
-
["DS1003", { defaultSeverity: "deny", maxClass: "certain" }],
|
|
26
|
-
["DS1004", { defaultSeverity: "deny", maxClass: "certain" }],
|
|
27
|
-
["DS1005", { defaultSeverity: "deny", maxClass: "certain" }],
|
|
28
|
-
["DS1006", { defaultSeverity: "deny", maxClass: "certain" }],
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
7
|
/** The kinds of declaration that are members of a class, an interface, a type or an enum. */
|
|
32
8
|
const MEMBERS: ReadonlySet<SymbolKind> = new Set([
|
|
33
9
|
"method",
|
|
@@ -160,20 +136,14 @@ function messageOf(code: string, judged: Judged): string {
|
|
|
160
136
|
return `${subject} has no reference in the target`;
|
|
161
137
|
}
|
|
162
138
|
|
|
163
|
-
/**
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
export function unusedDeclarationFindings(input: EmitterInput): readonly CompletedFinding[] {
|
|
168
|
-
const { config, swept } = input;
|
|
169
|
-
const union = swept.matrix.union;
|
|
170
|
-
const dead = new Set(swept.sweep.candidates.map((candidate) => candidate.id));
|
|
171
|
-
const classOf = reachabilityClasses(swept);
|
|
172
|
-
const componentOf = findingComponents(swept);
|
|
139
|
+
/** The findings of the unused-declarations family, `DS1000` to `DS1099`. */
|
|
140
|
+
export const unusedDeclarations: Emitter = (input) => {
|
|
141
|
+
const union = input.swept.matrix.union;
|
|
142
|
+
const dead = new Set(input.swept.sweep.candidates.map((candidate) => candidate.id));
|
|
173
143
|
const unreachable = unreachableExports(input);
|
|
174
144
|
|
|
175
|
-
const found:
|
|
176
|
-
for (const candidate of swept.sweep.candidates) {
|
|
145
|
+
const found: Finding[] = [];
|
|
146
|
+
for (const candidate of input.swept.sweep.candidates) {
|
|
177
147
|
const at = union.at(candidate.id);
|
|
178
148
|
const symbol = union.symbols[at];
|
|
179
149
|
if (symbol === undefined) {
|
|
@@ -188,16 +158,10 @@ export function unusedDeclarationFindings(input: EmitterInput): readonly Complet
|
|
|
188
158
|
unreachable: unreachable.has(symbol.id),
|
|
189
159
|
};
|
|
190
160
|
const code = codeOf(judged);
|
|
191
|
-
|
|
192
|
-
if (code === undefined || row === undefined) {
|
|
161
|
+
if (code === undefined) {
|
|
193
162
|
continue;
|
|
194
163
|
}
|
|
195
|
-
const component = componentOf(symbol.id);
|
|
196
|
-
if (component === undefined) {
|
|
197
|
-
throw new Error(`${symbol.id} is reported dead and falls in no dead component`);
|
|
198
|
-
}
|
|
199
164
|
const endLine = Math.max(symbol.position.line, symbol.endLine);
|
|
200
|
-
const reachabilityClass = classOf(symbol.id);
|
|
201
165
|
found.push({
|
|
202
166
|
code,
|
|
203
167
|
position: { ...symbol.position, endLine },
|
|
@@ -208,18 +172,7 @@ export function unusedDeclarationFindings(input: EmitterInput): readonly Complet
|
|
|
208
172
|
sizeLines: endLine - symbol.position.line + 1,
|
|
209
173
|
},
|
|
210
174
|
message: messageOf(code, judged),
|
|
211
|
-
reachabilityClass,
|
|
212
|
-
confidence: capped(reachabilityClass, row.maxClass),
|
|
213
|
-
livenessRelation: candidate.relation,
|
|
214
|
-
testOnly: candidate.productionRefs === 0 && candidate.testRefs > 0,
|
|
215
|
-
component,
|
|
216
|
-
configurations: candidate.configurations,
|
|
217
|
-
severity: severityOf(config, code, row.defaultSeverity),
|
|
218
|
-
details: {},
|
|
219
175
|
});
|
|
220
176
|
}
|
|
221
|
-
return
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/** The findings of the unused-declarations family, `DS1000` to `DS1099`. */
|
|
225
|
-
export const unusedDeclarations: Emitter = unusedDeclarationFindings;
|
|
177
|
+
return found;
|
|
178
|
+
};
|
|
@@ -126,18 +126,13 @@ function wordOf(symbol: InventorySymbol): string {
|
|
|
126
126
|
return WORDS[symbol.kind] ?? symbol.kind;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
/** A finding of this family, with the visibility a narrowing would give its subject. */
|
|
130
|
-
interface NarrowingFinding extends Finding {
|
|
131
|
-
readonly details: { readonly narrowerVisibility?: "file" | "package" };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
129
|
/** The visibility each narrowing code names, which only the narrowing codes carry. */
|
|
135
130
|
const NARROWER: Readonly<Record<string, "file" | "package">> = {
|
|
136
131
|
[REDUNDANT_EXPORT_KEYWORD]: "file",
|
|
137
132
|
[UNNECESSARY_EXPORT]: "package",
|
|
138
133
|
};
|
|
139
134
|
|
|
140
|
-
function findingOf(symbol: InventorySymbol, code: string, message: string):
|
|
135
|
+
function findingOf(symbol: InventorySymbol, code: string, message: string): Finding {
|
|
141
136
|
const { path, line, column } = symbol.position;
|
|
142
137
|
const narrower = Object.hasOwn(NARROWER, code) ? NARROWER[code] : undefined;
|
|
143
138
|
const endLine = Math.max(line, symbol.endLine);
|
package/src/kinds.ts
CHANGED
|
@@ -1,53 +1,87 @@
|
|
|
1
|
+
import type { Confidence, Severity } from "./config.ts";
|
|
2
|
+
|
|
3
|
+
/** What a mechanical edit may do with a finding of one kind. */
|
|
4
|
+
export type Fixability = "deletable" | "narrowable" | "manual" | "none";
|
|
5
|
+
|
|
6
|
+
/** One live row of the issue-kind vocabulary, as far as an analyzer reads it. */
|
|
7
|
+
export interface KindRow {
|
|
8
|
+
readonly code: string;
|
|
9
|
+
/** The kind's name, which a finding carries as its `kind`. */
|
|
10
|
+
readonly name: string;
|
|
11
|
+
/** The severity a finding carries where no severity key names its code or its family. */
|
|
12
|
+
readonly defaultSeverity: Severity;
|
|
13
|
+
/** The highest confidence a finding of the kind carries. */
|
|
14
|
+
readonly maxClass: Confidence;
|
|
15
|
+
readonly fixability: Fixability;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the Contract fixes the kind on at its default severity, so a severity key
|
|
18
|
+
* naming the code, or a family prefix whose range holds it, is an unimplemented key.
|
|
19
|
+
*/
|
|
20
|
+
readonly fixed: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
1
23
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* The codes are written out rather than read from the Contract at run time: the
|
|
7
|
-
* Contract is data a reader outside this program consults, and a product that
|
|
8
|
-
* read it while running would need it installed beside itself. A test compares
|
|
9
|
-
* this list against the Contract release the analyzer is written against, so a
|
|
10
|
-
* code the Contract fixes arrives here as a failing test.
|
|
24
|
+
* Every live row of the issue-kind vocabulary, in ascending code order, written out
|
|
25
|
+
* rather than read at run time, because a product that read the Contract while running
|
|
26
|
+
* would need it installed beside itself. A test compares this table against the
|
|
27
|
+
* Contract release the analyzer is written against.
|
|
11
28
|
*/
|
|
12
|
-
|
|
29
|
+
const ROWS: readonly KindRow[] = [
|
|
30
|
+
row("DS1001", "unused-exported", "deny", "deletable"),
|
|
31
|
+
row("DS1002", "unused-unexported", "deny", "deletable"),
|
|
32
|
+
row("DS1003", "unused-member", "deny", "deletable"),
|
|
33
|
+
row("DS1004", "test-only-use", "deny", "deletable"),
|
|
34
|
+
row("DS1005", "test-of-dead-code", "deny", "deletable"),
|
|
35
|
+
row("DS1006", "deprecated-unused", "deny", "deletable"),
|
|
36
|
+
row("DS1101", "unnecessary-export", "warn", "narrowable"),
|
|
37
|
+
row("DS1102", "unnecessary-exposure", "warn", "narrowable"),
|
|
38
|
+
row("DS1103", "unreachable-export", "deny", "deletable"),
|
|
39
|
+
row("DS1104", "redundant-export-keyword", "warn", "narrowable"),
|
|
40
|
+
row("DS1201", "unused-interface", "deny", "deletable"),
|
|
41
|
+
row("DS1203", "uncalled-interface-method", "warn", "manual"),
|
|
42
|
+
row("DS1204", "unused-satisfaction-assertion", "deny", "deletable"),
|
|
43
|
+
row("DS1301", "write-only-symbol", "deny", "deletable"),
|
|
44
|
+
row("DS1302", "unused-enum-member", "deny", "deletable"),
|
|
45
|
+
row("DS1303", "unused-type-parameter", "deny", "deletable"),
|
|
46
|
+
row("DS1501", "file-never-built", "deny", "deletable"),
|
|
47
|
+
row("DS1502", "file-never-imported", "deny", "deletable"),
|
|
48
|
+
row("DS1601", "unused-dependency", "deny", "manual"),
|
|
49
|
+
row("DS1605", "unused-module-directive", "warn", "deletable"),
|
|
50
|
+
row("DS1701", "suppression-without-reason", "deny", "none"),
|
|
51
|
+
row("DS1702", "unscoped-ignore-entry", "deny", "none"),
|
|
52
|
+
{ ...row("DS1703", "stale-suppression", "deny", "none"), fixed: true },
|
|
53
|
+
{ ...row("DS1704", "unmatched-root", "deny", "none"), fixed: true },
|
|
54
|
+
row("DS1705", "stale-cross-language-edge", "deny", "none"),
|
|
55
|
+
row("DS1801", "unused-parameter", "warn", "manual"),
|
|
56
|
+
row("DS1802", "unused-receiver", "warn", "deletable"),
|
|
57
|
+
row("DS1803", "unused-result", "warn", "manual"),
|
|
58
|
+
row("DS1805", "unreachable-statement", "deny", "deletable"),
|
|
59
|
+
row("DS1807", "dead-store", "deny", "deletable"),
|
|
60
|
+
row("DS1809", "unreachable-case", "deny", "deletable"),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/** A row at the `certain` ceiling whose severity a configuration may set. */
|
|
64
|
+
function row(
|
|
65
|
+
code: string,
|
|
66
|
+
name: string,
|
|
67
|
+
defaultSeverity: Severity,
|
|
68
|
+
fixability: Fixability,
|
|
69
|
+
): KindRow {
|
|
70
|
+
return { code, name, defaultSeverity, maxClass: "certain", fixability, fixed: false };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Every live row of the issue-kind vocabulary, by its code, in ascending code order. */
|
|
74
|
+
export const KINDS: ReadonlyMap<string, KindRow> = new Map(ROWS.map((one) => [one.code, one]));
|
|
13
75
|
|
|
14
76
|
/**
|
|
15
77
|
* The code of every live issue kind, in ascending order. A severity key names one of
|
|
16
78
|
* them or a family prefix whose range holds one; a key naming a retired code, an
|
|
17
79
|
* unassigned code or a range holding no live kind is an unimplemented key, so every
|
|
18
|
-
* severity a configuration sets is one some analyzer reports under.
|
|
19
|
-
* this list against the Contract release the analyzer is written against.
|
|
80
|
+
* severity a configuration sets is one some analyzer reports under.
|
|
20
81
|
*/
|
|
21
|
-
export const LIVE_CODES: readonly string[] =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"DS1006",
|
|
28
|
-
"DS1101",
|
|
29
|
-
"DS1102",
|
|
30
|
-
"DS1103",
|
|
31
|
-
"DS1104",
|
|
32
|
-
"DS1201",
|
|
33
|
-
"DS1203",
|
|
34
|
-
"DS1204",
|
|
35
|
-
"DS1301",
|
|
36
|
-
"DS1302",
|
|
37
|
-
"DS1303",
|
|
38
|
-
"DS1501",
|
|
39
|
-
"DS1502",
|
|
40
|
-
"DS1601",
|
|
41
|
-
"DS1605",
|
|
42
|
-
"DS1701",
|
|
43
|
-
"DS1702",
|
|
44
|
-
"DS1703",
|
|
45
|
-
"DS1704",
|
|
46
|
-
"DS1705",
|
|
47
|
-
"DS1801",
|
|
48
|
-
"DS1802",
|
|
49
|
-
"DS1803",
|
|
50
|
-
"DS1805",
|
|
51
|
-
"DS1807",
|
|
52
|
-
"DS1809",
|
|
53
|
-
];
|
|
82
|
+
export const LIVE_CODES: readonly string[] = ROWS.map((one) => one.code);
|
|
83
|
+
|
|
84
|
+
/** The issue-kind codes whose severity the Contract fixes, in ascending order. */
|
|
85
|
+
export const FIXED_SEVERITY_CODES: readonly string[] = ROWS.filter((one) => one.fixed).map(
|
|
86
|
+
(one) => one.code,
|
|
87
|
+
);
|