@cplieger/deadset-ts 3.5.0 → 3.6.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.
Files changed (43) hide show
  1. package/dist/src/analysis.js +70 -14
  2. package/dist/src/components.js +5 -1
  3. package/dist/src/dependencies.js +493 -0
  4. package/dist/src/deprecation.js +83 -0
  5. package/dist/src/discover.js +1 -1
  6. package/dist/src/enum-group.js +217 -0
  7. package/dist/src/file-facts.js +101 -0
  8. package/dist/src/findings/boundary.js +1 -0
  9. package/dist/src/findings/completion.js +99 -0
  10. package/dist/src/findings/dependencies-and-module-machinery.js +65 -1
  11. package/dist/src/findings/emitters.js +8 -0
  12. package/dist/src/findings/interfaces.js +95 -2
  13. package/dist/src/findings/non-code-artifacts.js +97 -1
  14. package/dist/src/findings/reads-and-writes.js +199 -2
  15. package/dist/src/findings/unused-declarations.js +182 -1
  16. package/dist/src/findings/visibility-narrowing.js +239 -2
  17. package/dist/src/implementations.js +210 -0
  18. package/dist/src/inventory.js +2 -2
  19. package/dist/src/resolve.js +1 -1
  20. package/dist/src/stores.js +32 -0
  21. package/jsr.json +1 -1
  22. package/package.json +1 -1
  23. package/src/analysis.ts +113 -16
  24. package/src/components.ts +6 -1
  25. package/src/dependencies.ts +617 -0
  26. package/src/deprecation.ts +101 -0
  27. package/src/discover.ts +1 -1
  28. package/src/enum-group.ts +265 -0
  29. package/src/file-facts.ts +143 -0
  30. package/src/findings/boundary.ts +39 -0
  31. package/src/findings/completion.ts +151 -0
  32. package/src/findings/dependencies-and-module-machinery.ts +79 -1
  33. package/src/findings/emitter.ts +21 -1
  34. package/src/findings/emitters.ts +13 -1
  35. package/src/findings/interfaces.ts +149 -3
  36. package/src/findings/non-code-artifacts.ts +124 -2
  37. package/src/findings/reads-and-writes.ts +310 -3
  38. package/src/findings/unused-declarations.ts +223 -2
  39. package/src/findings/visibility-narrowing.ts +324 -3
  40. package/src/implementations.ts +286 -0
  41. package/src/inventory.ts +2 -2
  42. package/src/resolve.ts +1 -1
  43. package/src/stores.ts +77 -0
@@ -1,4 +1,311 @@
1
- import type { Emitter } from "./emitter.ts";
1
+ import type { Confidence, Severity } from "../config.ts";
2
+ import type { Finding, FindingPosition } from "../finding.ts";
3
+ import type { InventorySymbol } from "../inventory.ts";
4
+ import type { MatrixCandidate } from "../matrix.ts";
5
+ 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
+ import type { Emitter, EmitterInput } from "./emitter.ts";
2
15
 
3
- /** The findings of the reads-and-writes family, `DS1300` to `DS1399`. */
4
- export const readsAndWrites: Emitter = () => [];
16
+ /** The codes of this family. */
17
+ export type ReadsAndWritesCode = "DS1301" | "DS1302" | "DS1303";
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
+ }
54
+
55
+ /** The containers whose type parameters `DS1303` reports: a function and a class method. */
56
+ const SIGNATURES: ReadonlySet<string> = new Set(["function", "method"]);
57
+
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
+ /** What the family reads of one run, indexed by declaration. */
64
+ interface Run {
65
+ readonly input: EmitterInput;
66
+ readonly symbols: ReadonlyMap<string, InventorySymbol>;
67
+ readonly candidates: ReadonlyMap<string, MatrixCandidate>;
68
+ readonly componentOf: (id: string) => FindingComponent | undefined;
69
+ readonly classOf: (id: string) => Confidence;
70
+ }
71
+
72
+ function indexed(input: EmitterInput): Run {
73
+ const { matrix, sweep } = input.swept;
74
+ return {
75
+ input,
76
+ symbols: new Map(matrix.union.symbols.map((symbol) => [symbol.id, symbol])),
77
+ candidates: new Map(sweep.candidates.map((candidate) => [candidate.id, candidate])),
78
+ componentOf: findingComponents(input.swept),
79
+ classOf: reachabilityClasses(input.swept),
80
+ };
81
+ }
82
+
83
+ /** Where one declaration is, as a finding states it. */
84
+ function positionOf(symbol: InventorySymbol): FindingPosition {
85
+ return {
86
+ path: symbol.position.path,
87
+ line: symbol.position.line,
88
+ column: symbol.position.column,
89
+ endLine: Math.max(symbol.position.line, symbol.endLine),
90
+ };
91
+ }
92
+
93
+ /** The finding about one declaration, every member the family decides filled in. */
94
+ function findingAbout(
95
+ run: Run,
96
+ symbol: InventorySymbol,
97
+ code: ReadsAndWritesCode,
98
+ message: string,
99
+ facts: {
100
+ readonly livenessRelation?: Relation;
101
+ readonly testOnly: boolean;
102
+ readonly configurations: readonly string[];
103
+ readonly details: ReadsAndWritesDetails;
104
+ },
105
+ ): ReadsAndWritesFinding {
106
+ const position = positionOf(symbol);
107
+ const reachabilityClass = run.classOf(symbol.id);
108
+ const component = run.componentOf(symbol.id);
109
+ return {
110
+ code,
111
+ position,
112
+ symbol: {
113
+ ref: symbol.ref,
114
+ kind: symbol.kind,
115
+ name: symbol.name,
116
+ sizeLines: position.endLine - position.line + 1,
117
+ },
118
+ message,
119
+ reachabilityClass,
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,
127
+ };
128
+ }
129
+
130
+ /** How one declaration's counted references divide for the write-only kind. */
131
+ interface Uses {
132
+ readonly writes: FindingPosition[];
133
+ reads: number;
134
+ production: number;
135
+ test: number;
136
+ }
137
+
138
+ /**
139
+ * Whether one declaration is a subject of the write-only kind: a module-level variable,
140
+ * or a class member that is state rather than code. A getter or a setter is code, so a
141
+ * store into one runs it.
142
+ */
143
+ function writable(run: Run, symbol: InventorySymbol): boolean {
144
+ if (symbol.kind === "variable") {
145
+ return true;
146
+ }
147
+ return symbol.kind === "class-member" && !run.input.stores.accessors.has(symbol.id);
148
+ }
149
+
150
+ /**
151
+ * `DS1301`: a module-level variable or a class member the counted references store into
152
+ * and never read, named with every position written.
153
+ *
154
+ * The subject is a live declaration: a store is a reference, so the sweep holds a
155
+ * written one live and its candidates are never this kind's. Every reference that is
156
+ * not a store reads, and so does an exemption record naming the declaration.
157
+ */
158
+ function writeOnlySymbols(run: Run): readonly ReadsAndWritesFinding[] {
159
+ const { stores } = run.input;
160
+ const { matrix } = run.input.swept;
161
+ const uses = new Map<string, Uses>();
162
+ for (const reference of stores.references) {
163
+ let held = uses.get(reference.to);
164
+ if (held === undefined) {
165
+ held = { writes: [], reads: 0, production: 0, test: 0 };
166
+ uses.set(reference.to, held);
167
+ }
168
+ if (reference.test) {
169
+ held.test += 1;
170
+ } else {
171
+ held.production += 1;
172
+ }
173
+ if (reference.use !== "write") {
174
+ held.reads += 1;
175
+ continue;
176
+ }
177
+ const at = reference.position;
178
+ const key = positionKey(at);
179
+ if (!held.writes.some((written) => positionKey(written) === key)) {
180
+ held.writes.push({ path: at.path, line: at.line, column: at.column, endLine: at.line });
181
+ }
182
+ }
183
+
184
+ const found: ReadsAndWritesFinding[] = [];
185
+ matrix.union.symbols.forEach((symbol, at) => {
186
+ const held = uses.get(symbol.id);
187
+ if (
188
+ held === undefined ||
189
+ held.writes.length === 0 ||
190
+ held.reads > 0 ||
191
+ !writable(run, symbol) ||
192
+ stores.exempt.has(symbol.id) ||
193
+ run.candidates.has(symbol.id)
194
+ ) {
195
+ return;
196
+ }
197
+ const writes = held.writes.length === 1 ? "once" : `at ${String(held.writes.length)} positions`;
198
+ const word = symbol.kind === "variable" ? "variable" : "member";
199
+ found.push(
200
+ findingAbout(
201
+ run,
202
+ symbol,
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
+ ),
211
+ );
212
+ });
213
+ return found;
214
+ }
215
+
216
+ /**
217
+ * The candidates no reference of any file names whose container is not itself dead, of
218
+ * one kind. A reference from a test file alone leaves a candidate to the test-only kind,
219
+ * and a member of a dead container falls with it and is reported inside its component.
220
+ */
221
+ function unnamed(
222
+ run: Run,
223
+ kind: InventorySymbol["kind"],
224
+ ): readonly [MatrixCandidate, InventorySymbol][] {
225
+ return run.input.swept.sweep.candidates.flatMap(
226
+ (candidate): [MatrixCandidate, InventorySymbol][] => {
227
+ const symbol = run.symbols.get(candidate.id);
228
+ if (
229
+ symbol?.kind !== kind ||
230
+ candidate.productionRefs > 0 ||
231
+ candidate.testRefs > 0 ||
232
+ run.candidates.has(symbol.parent)
233
+ ) {
234
+ return [];
235
+ }
236
+ return [[candidate, symbol]];
237
+ },
238
+ );
239
+ }
240
+
241
+ /**
242
+ * `DS1302`: a member of an enum declaration that nothing names. A member an enum-group
243
+ * record holds back is no candidate, so a member a conversion can produce never reaches
244
+ * this kind.
245
+ */
246
+ function unusedEnumMembers(run: Run): readonly ReadsAndWritesFinding[] {
247
+ return unnamed(run, "enum-member").map(([candidate, symbol]) =>
248
+ findingAbout(run, symbol, "DS1302", `enum member ${symbol.name} is named nowhere`, {
249
+ livenessRelation: candidate.relation,
250
+ testOnly: false,
251
+ configurations: candidate.configurations,
252
+ details: {},
253
+ }),
254
+ );
255
+ }
256
+
257
+ /**
258
+ * `DS1303`: a type parameter of a function or a class method that neither the
259
+ * declaration's signature nor its body names. A type parameter of a class, an interface,
260
+ * a type alias or a method signature belongs to a type declaration and is never
261
+ * reported, because a phantom parameter makes two instantiations distinct types.
262
+ */
263
+ function unusedTypeParameters(run: Run): readonly ReadsAndWritesFinding[] {
264
+ return unnamed(run, "type-parameter")
265
+ .filter(([, symbol]) => SIGNATURES.has(run.symbols.get(symbol.parent)?.kind ?? ""))
266
+ .map(([candidate, symbol]) =>
267
+ findingAbout(
268
+ run,
269
+ symbol,
270
+ "DS1303",
271
+ `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
+ ),
279
+ );
280
+ }
281
+
282
+ /**
283
+ * The references of the declarations the write-only kind reports, whatever the
284
+ * severity: a narrowing kind yields each to it, since deleting the declaration
285
+ * supersedes narrowing it.
286
+ */
287
+ export function writeOnlyDeclarations(input: EmitterInput): ReadonlySet<string> {
288
+ return new Set(writeOnlySymbols(indexed(input)).map((finding) => finding.symbol.ref));
289
+ }
290
+
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
+ /**
299
+ * The findings of the reads-and-writes family, `DS1300` to `DS1399`, each code's in site
300
+ * order. A code the configuration sets to `allow` reports nothing, and a finding whose
301
+ * confidence is below the configured minimum is withheld.
302
+ */
303
+ export const readsAndWrites: Emitter = (input) => {
304
+ const run = indexed(input);
305
+ const least = RANK[input.config.analysis.minConfidence];
306
+ return RULES.flatMap(([code, rule]) =>
307
+ severityIn(input, code) === "allow"
308
+ ? []
309
+ : rule(run).filter((finding) => RANK[finding.confidence] >= least),
310
+ );
311
+ };
@@ -1,4 +1,225 @@
1
- import type { Emitter } from "./emitter.ts";
1
+ import type { Confidence, Severity } from "../config.ts";
2
+ import type { InventorySymbol, SymbolKind } from "../inventory.ts";
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";
13
+ import { unreachableExports } from "./visibility-narrowing.ts";
14
+
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
+ /** The kinds of declaration that are members of a class, an interface, a type or an enum. */
32
+ const MEMBERS: ReadonlySet<SymbolKind> = new Set([
33
+ "method",
34
+ "class-member",
35
+ "type-member",
36
+ "enum-member",
37
+ ]);
38
+
39
+ /**
40
+ * The kinds of declaration no code of this family reports: an interface and a method an
41
+ * interface declares are the interface kinds' subjects, and a type parameter is the
42
+ * subject of the unused-type-parameter kind or of none.
43
+ */
44
+ const NOT_THIS_FAMILY: ReadonlySet<SymbolKind> = new Set([
45
+ "file",
46
+ "interface",
47
+ "interface-method",
48
+ "type-parameter",
49
+ ]);
50
+
51
+ /** The reader's word for each kind of declaration a finding of the family names. */
52
+ const WORDS: Readonly<Record<SymbolKind, string>> = {
53
+ file: "file",
54
+ function: "function",
55
+ class: "class",
56
+ interface: "interface",
57
+ type: "type alias",
58
+ enum: "enum",
59
+ namespace: "namespace",
60
+ variable: "variable",
61
+ "export-alias": "re-export",
62
+ method: "method",
63
+ "class-member": "class member",
64
+ "interface-method": "interface method",
65
+ "type-member": "type member",
66
+ "enum-member": "enum member",
67
+ "type-parameter": "type parameter",
68
+ };
69
+
70
+ /** What the test-of-dead-code kind says, which states the rule it applies. */
71
+ const TEST_OF_DEAD_CODE = "every target symbol this test references is reported dead";
72
+
73
+ /** What the run holds about one candidate that decides its code. */
74
+ interface Judged {
75
+ readonly candidate: MatrixCandidate;
76
+ readonly symbol: InventorySymbol;
77
+ /** Whether a test file declares it. */
78
+ readonly test: boolean;
79
+ /** Whether its container is itself dead, so it falls with the container. */
80
+ readonly containerDead: boolean;
81
+ readonly deprecated: boolean;
82
+ /** Whether the unreachable-export kind reports it. */
83
+ readonly unreachable: boolean;
84
+ }
85
+
86
+ /**
87
+ * The code one candidate is reported under, or none, each symbol once under the most
88
+ * specific code: a test of dead code first; no other test-file declaration, member of a
89
+ * dead container or enum member nothing names; then deprecation, a test reference, and
90
+ * the unreferenced codes, an unimportable unused export being the unreachable kind's.
91
+ */
92
+ function codeOf(judged: Judged): string | undefined {
93
+ const { candidate, symbol } = judged;
94
+ if (candidate.testOfDeadCode) {
95
+ return "DS1005";
96
+ }
97
+ if (judged.test || judged.containerDead || NOT_THIS_FAMILY.has(symbol.kind)) {
98
+ return undefined;
99
+ }
100
+ const referenced = candidate.productionRefs + candidate.testRefs > 0;
101
+ if (symbol.kind === "enum-member" && !referenced) {
102
+ return undefined;
103
+ }
104
+ if (candidate.productionRefs === 0 && judged.deprecated) {
105
+ return "DS1006";
106
+ }
107
+ if (candidate.productionRefs === 0 && candidate.testRefs > 0) {
108
+ return "DS1004";
109
+ }
110
+ if (MEMBERS.has(symbol.kind)) {
111
+ return "DS1003";
112
+ }
113
+ if (!symbol.exported) {
114
+ return "DS1002";
115
+ }
116
+ return judged.unreachable ? undefined : "DS1001";
117
+ }
118
+
119
+ /** The visibility a member's message names, where it narrows the member's reach. */
120
+ function visibilityWord(symbol: InventorySymbol): string {
121
+ switch (symbol.visibility) {
122
+ case "private":
123
+ case "private-name":
124
+ return "private ";
125
+ case "protected":
126
+ return "protected ";
127
+ case "public":
128
+ return "";
129
+ }
130
+ }
131
+
132
+ /**
133
+ * What one finding says: what the subject is, and what the analysis found about its
134
+ * references. A candidate reachability found has references, each from a declaration
135
+ * that is itself dead, so saying it has none would be false.
136
+ */
137
+ function messageOf(code: string, judged: Judged): string {
138
+ const { candidate, symbol } = judged;
139
+ const word = WORDS[symbol.kind];
140
+ switch (code) {
141
+ case "DS1005":
142
+ return TEST_OF_DEAD_CODE;
143
+ case "DS1006":
144
+ return `deprecated ${word} has no production reference`;
145
+ case "DS1004":
146
+ return `${word} is referenced only from test files and never from production code`;
147
+ }
148
+ const subject =
149
+ code === "DS1003"
150
+ ? `${visibilityWord(symbol)}${word}`
151
+ : symbol.kind === "export-alias"
152
+ ? word
153
+ : `${symbol.exported ? "exported" : "unexported"} ${word}`;
154
+ if (candidate.relation === "reachability") {
155
+ return `${subject} is referenced only from declarations that are themselves dead`;
156
+ }
157
+ if (code === "DS1001") {
158
+ return `${subject} has no reference in the target and none from any loaded consumer`;
159
+ }
160
+ return `${subject} has no reference in the target`;
161
+ }
162
+
163
+ /**
164
+ * The findings of the unused-declarations family, `DS1000` to `DS1099`, completed and
165
+ * with what the dials withhold left out.
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);
173
+ const unreachable = unreachableExports(input);
174
+
175
+ const found: CompletedFinding[] = [];
176
+ for (const candidate of swept.sweep.candidates) {
177
+ const at = union.at(candidate.id);
178
+ const symbol = union.symbols[at];
179
+ if (symbol === undefined) {
180
+ continue;
181
+ }
182
+ const judged: Judged = {
183
+ candidate,
184
+ symbol,
185
+ test: union.test[at] === true,
186
+ containerDead: dead.has(symbol.parent),
187
+ deprecated: input.deprecated.has(symbol.id),
188
+ unreachable: unreachable.has(symbol.id),
189
+ };
190
+ const code = codeOf(judged);
191
+ const row = code === undefined ? undefined : UNUSED_DECLARATION_KINDS.get(code);
192
+ if (code === undefined || row === undefined) {
193
+ continue;
194
+ }
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
+ const endLine = Math.max(symbol.position.line, symbol.endLine);
200
+ const reachabilityClass = classOf(symbol.id);
201
+ found.push({
202
+ code,
203
+ position: { ...symbol.position, endLine },
204
+ symbol: {
205
+ ref: symbol.ref,
206
+ kind: symbol.kind,
207
+ name: symbol.name,
208
+ sizeLines: endLine - symbol.position.line + 1,
209
+ },
210
+ 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
+ });
220
+ }
221
+ return reportable(found, config.analysis.minConfidence);
222
+ }
2
223
 
3
224
  /** The findings of the unused-declarations family, `DS1000` to `DS1099`. */
4
- export const unusedDeclarations: Emitter = () => [];
225
+ export const unusedDeclarations: Emitter = unusedDeclarationFindings;