@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.
Files changed (39) hide show
  1. package/dist/src/analysis.js +92 -23
  2. package/dist/src/edges.js +190 -0
  3. package/dist/src/finding.js +0 -11
  4. package/dist/src/findings/completion.js +92 -35
  5. package/dist/src/findings/dependencies-and-module-machinery.js +3 -11
  6. package/dist/src/findings/emitters.js +42 -4
  7. package/dist/src/findings/reads-and-writes.js +16 -69
  8. package/dist/src/findings/self-check.js +127 -2
  9. package/dist/src/findings/unused-declarations.js +8 -40
  10. package/dist/src/findings/visibility-narrowing.js +9 -40
  11. package/dist/src/findings-pass.js +97 -0
  12. package/dist/src/json-document.js +113 -0
  13. package/dist/src/kinds.js +47 -45
  14. package/dist/src/roots.js +0 -34
  15. package/dist/src/suppress-file.js +196 -0
  16. package/dist/src/suppress-inline.js +259 -0
  17. package/dist/src/suppress.js +148 -0
  18. package/jsr.json +1 -1
  19. package/package.json +1 -1
  20. package/src/analysis.ts +176 -32
  21. package/src/edges.ts +241 -0
  22. package/src/finding.ts +76 -10
  23. package/src/findings/boundary.ts +0 -11
  24. package/src/findings/completion.ts +117 -71
  25. package/src/findings/dependencies-and-module-machinery.ts +4 -19
  26. package/src/findings/emitter.ts +6 -2
  27. package/src/findings/emitters.ts +81 -7
  28. package/src/findings/interfaces.ts +5 -17
  29. package/src/findings/reads-and-writes.ts +37 -151
  30. package/src/findings/self-check.ts +205 -2
  31. package/src/findings/unused-declarations.ts +11 -58
  32. package/src/findings/visibility-narrowing.ts +11 -58
  33. package/src/findings-pass.ts +145 -0
  34. package/src/json-document.ts +130 -0
  35. package/src/kinds.ts +79 -45
  36. package/src/roots.ts +0 -39
  37. package/src/suppress-file.ts +283 -0
  38. package/src/suppress-inline.ts +318 -0
  39. package/src/suppress.ts +262 -0
@@ -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: the findings of the codes in its family's range
34
- * of the Contract's code space, in no particular order.
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
 
@@ -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
- * Every finding of the run, each family's in table order, each placed in its component
26
- * by the one step that numbers the components minted for subjects that fall with none.
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
- export function findingsOf(input: EmitterInput): readonly PlacedFinding[] {
29
- return placed(
30
- input.swept,
31
- [...EMITTERS.values()].flatMap((emit) => emit(input)),
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 Implementation[];
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 Implementation[],
74
- ): InterfaceFinding {
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: InterfaceFinding[] = [];
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 { Confidence, Severity } from "../config.ts";
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
- 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
- }
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 about one declaration, every member the family decides filled in. */
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
- facts: {
100
- readonly livenessRelation?: Relation;
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
- 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,
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 ReadsAndWritesFinding[] {
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, production: 0, test: 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: ReadsAndWritesFinding[] = [];
185
- matrix.union.symbols.forEach((symbol, at) => {
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
- return;
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
- 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
- ),
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: 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
- );
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 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
- }),
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 ReadsAndWritesFinding[] {
169
+ function unusedTypeParameters(run: Run): readonly Finding[] {
264
170
  return unnamed(run, "type-parameter")
265
- .filter(([, symbol]) => SIGNATURES.has(run.symbols.get(symbol.parent)?.kind ?? ""))
266
- .map(([candidate, symbol]) =>
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`, 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.
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
- 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
- );
196
+ return [...writeOnlySymbols(run), ...unusedEnumMembers(run), ...unusedTypeParameters(run)];
311
197
  };