@cplieger/deadset-ts 5.0.0 → 5.1.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 (55) hide show
  1. package/dist/src/analysis.js +77 -10
  2. package/dist/src/configuration-files.js +107 -3
  3. package/dist/src/conformance-record.js +3 -9
  4. package/dist/src/contextual-uses.js +240 -0
  5. package/dist/src/convention-rows.js +6 -0
  6. package/dist/src/conventions.js +123 -14
  7. package/dist/src/dependencies.js +8 -2
  8. package/dist/src/destructuring.js +6 -6
  9. package/dist/src/entry-points.js +8 -5
  10. package/dist/src/exempt-classes.js +6 -1
  11. package/dist/src/exempt.js +21 -17
  12. package/dist/src/findings/completion.js +27 -3
  13. package/dist/src/findings/interfaces.js +6 -2
  14. package/dist/src/findings/reads-and-writes.js +53 -9
  15. package/dist/src/findings/unused-declarations.js +40 -1
  16. package/dist/src/generated-file.js +24 -0
  17. package/dist/src/interface-satisfaction.js +2 -68
  18. package/dist/src/inventory.js +4 -1
  19. package/dist/src/kinds.js +4 -3
  20. package/dist/src/query.js +15 -0
  21. package/dist/src/references.js +199 -10
  22. package/dist/src/resolve.js +12 -11
  23. package/dist/src/roots.js +1 -1
  24. package/dist/src/stores.js +3 -0
  25. package/dist/src/value-flow.js +74 -0
  26. package/dist/src/version.js +1 -1
  27. package/jsr.json +1 -1
  28. package/package.json +2 -2
  29. package/src/analysis.ts +92 -13
  30. package/src/configuration-files.ts +136 -1
  31. package/src/conformance-record.ts +3 -10
  32. package/src/contextual-uses.ts +354 -0
  33. package/src/convention-rows.ts +8 -0
  34. package/src/conventions.ts +161 -13
  35. package/src/dependencies.ts +14 -1
  36. package/src/destructuring.ts +16 -6
  37. package/src/entry-points.ts +16 -3
  38. package/src/exempt-classes.ts +6 -1
  39. package/src/exempt.ts +48 -17
  40. package/src/file-facts.ts +2 -0
  41. package/src/findings/completion.ts +31 -3
  42. package/src/findings/interfaces.ts +9 -2
  43. package/src/findings/reads-and-writes.ts +70 -9
  44. package/src/findings/unused-declarations.ts +51 -1
  45. package/src/generated-file.ts +27 -0
  46. package/src/interface-satisfaction.ts +1 -92
  47. package/src/inventory.ts +4 -1
  48. package/src/kinds.ts +5 -3
  49. package/src/query.ts +26 -0
  50. package/src/references.ts +254 -21
  51. package/src/resolve.ts +14 -13
  52. package/src/roots.ts +11 -1
  53. package/src/stores.ts +9 -0
  54. package/src/value-flow.ts +104 -0
  55. package/src/version.ts +1 -1
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The values a program hands to a position the checker types from its context: the
3
+ * initializer of a declaration whose type is written, an assignment's right side, an
4
+ * argument, a returned value, an arrow function's expression body, an array element, an
5
+ * object property, and the operand of `as` or `satisfies`.
6
+ */
7
+ import { isArrayLiteralExpression, isArrowFunction, isAsExpression, isBinaryExpression, isCallExpression, isConditionalExpression, isIdentifier, isNewExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAssignment, isPropertyDeclaration, isReturnStatement, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadElement, isVariableDeclaration, SyntaxKind, } from "@typescript/native/unstable/ast";
8
+ /** The operators of an assignment whose right side is stored into the left side's type. */
9
+ const STORES = new Set([
10
+ SyntaxKind.EqualsToken,
11
+ SyntaxKind.QuestionQuestionEqualsToken,
12
+ SyntaxKind.BarBarEqualsToken,
13
+ SyntaxKind.AmpersandAmpersandEqualsToken,
14
+ ]);
15
+ /** Whether a declaration writes its type, so its initializer is read in that type's context. */
16
+ function writesType(node) {
17
+ return node.type !== undefined;
18
+ }
19
+ /**
20
+ * The values one written expression hands on: the expression itself, or what it
21
+ * evaluates to through parentheses, a conditional and the operators whose result is
22
+ * one of their operands.
23
+ */
24
+ export function valuesOf(expression) {
25
+ if (isParenthesizedExpression(expression)) {
26
+ return valuesOf(expression.expression);
27
+ }
28
+ if (isConditionalExpression(expression)) {
29
+ return [...valuesOf(expression.whenTrue), ...valuesOf(expression.whenFalse)];
30
+ }
31
+ if (isBinaryExpression(expression)) {
32
+ const operator = expression.operatorToken.kind;
33
+ if (operator === SyntaxKind.QuestionQuestionToken || operator === SyntaxKind.BarBarToken) {
34
+ return [...valuesOf(expression.left), ...valuesOf(expression.right)];
35
+ }
36
+ if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.CommaToken) {
37
+ return valuesOf(expression.right);
38
+ }
39
+ }
40
+ return [expression];
41
+ }
42
+ /** The expressions one node hands to a position typed by its context. */
43
+ export function handedOn(node) {
44
+ if ((isVariableDeclaration(node) || isPropertyDeclaration(node) || isParameterDeclaration(node)) &&
45
+ node.initializer !== undefined &&
46
+ writesType(node)) {
47
+ return [node.initializer];
48
+ }
49
+ if (isBinaryExpression(node) && STORES.has(node.operatorToken.kind)) {
50
+ return [node.right];
51
+ }
52
+ if (isCallExpression(node) || isNewExpression(node)) {
53
+ return (node.arguments ?? []).filter((argument) => !isSpreadElement(argument));
54
+ }
55
+ if (isReturnStatement(node)) {
56
+ return node.expression === undefined ? [] : [node.expression];
57
+ }
58
+ if (isArrowFunction(node)) {
59
+ return node.body.kind === SyntaxKind.Block ? [] : [node.body];
60
+ }
61
+ if (isArrayLiteralExpression(node)) {
62
+ return node.elements.filter((element) => !isSpreadElement(element) && element.kind !== SyntaxKind.OmittedExpression);
63
+ }
64
+ if (isPropertyAssignment(node)) {
65
+ return [node.initializer];
66
+ }
67
+ if (isShorthandPropertyAssignment(node)) {
68
+ return isIdentifier(node.name) ? [node.name] : [];
69
+ }
70
+ if (isAsExpression(node) || isSatisfiesExpression(node)) {
71
+ return [node.expression];
72
+ }
73
+ return [];
74
+ }
@@ -4,4 +4,4 @@
4
4
  * independently of the analyzer's own version, which is the one the host answers
5
5
  * from the package the analyzer was installed from.
6
6
  */
7
- export const CONTRACT_VERSION = "5.1.0";
7
+ export const CONTRACT_VERSION = "5.2.0";
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "5.0.0",
3
+ "version": "5.1.0",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "5.0.0",
3
+ "version": "5.1.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Deterministic dead-code analysis for TypeScript, class and type members included",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@eslint/js": "10.0.1",
36
- "@types/node": "24.19.0",
36
+ "@types/node": "24.19.1",
37
37
  "eslint": "10.11.0",
38
38
  "fast-check": "4.10.2",
39
39
  "prettier": "3.9.9",
package/src/analysis.ts CHANGED
@@ -36,14 +36,17 @@ import {
36
36
  } from "./discover.ts";
37
37
  import {
38
38
  conventionFailuresFor,
39
+ generatedFiles,
39
40
  readConventions,
40
41
  type AppliedConvention,
41
42
  type ConventionManifest,
42
43
  } from "./conventions.ts";
43
44
  import { readEdgeSides } from "./edges.ts";
44
45
  import { enumGroup } from "./enum-group.ts";
46
+ import { generatedFile } from "./generated-file.ts";
47
+ import { sourceFilesOf } from "./source-files.ts";
45
48
  import {
46
- computeExemptions,
49
+ detectExemptions,
47
50
  disabledClasses,
48
51
  exemptionsOf,
49
52
  holdingWhile,
@@ -185,6 +188,8 @@ interface ProjectRead {
185
188
  readonly typeErrorSkips: readonly TypeErrorSkip[];
186
189
  /** The lines each of those skips withholds every finding on. */
187
190
  readonly skipped: readonly SkippedUnit[];
191
+ /** The project's generated files, by path below the target root, each with the row naming it. */
192
+ readonly generated: ReadonlyMap<string, string>;
188
193
  }
189
194
 
190
195
  /** One target project the run analyzes, as its stage is handed it. */
@@ -284,6 +289,7 @@ function readProjects<Answer>(
284
289
  entryFiles: config.ts.entryFiles,
285
290
  conventions,
286
291
  testFiles: config.ts.testFiles,
292
+ parse: (fileName: string, text: string) => engine.parseSourceFile(fileName, text),
287
293
  publishedAPI: config.targetKind === "library",
288
294
  };
289
295
  const notBuilt: NotBuilt[] = [...discovered.notBuilt];
@@ -425,6 +431,7 @@ function readProjects<Answer>(
425
431
  .ownSourceFiles()
426
432
  .filter((file) => referenceOnly(file.fileName))
427
433
  .flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
434
+ generated: generatedFiles(conventions, sourceFilesOf(project, targetRoot)),
428
435
  };
429
436
  };
430
437
  return stage(project, { configuration, read });
@@ -599,6 +606,7 @@ export interface ComponentWarning {
599
606
  const DETECTORS: Detectors = new Map<TSExemptionClass, Detector>([
600
607
  ["interface-satisfaction", interfaceSatisfaction],
601
608
  ["enum-group", enumGroup],
609
+ ["generated-file", generatedFile],
602
610
  ["template-field", templateField],
603
611
  ["reflective-lookup", reflectiveLookup],
604
612
  ["decorator", decorator],
@@ -677,11 +685,31 @@ function readRun<Extra>(
677
685
  projectRead.rooted.liveUnderReachability,
678
686
  resolved.testFilePaths,
679
687
  );
688
+ const detected = detectExemptions(
689
+ {
690
+ project,
691
+ held: projectRead.held,
692
+ targetRoot,
693
+ templates,
694
+ ts: config.ts,
695
+ consumers,
696
+ ...(config.analysis.generatedFiles === "include"
697
+ ? {}
698
+ : { generated: projectRead.generated }),
699
+ },
700
+ detectors,
701
+ {
702
+ disabled,
703
+ mode,
704
+ testFiles: new Set([...resolved.testFilePaths, ...support]),
705
+ },
706
+ );
707
+ const exempt = detected.records;
680
708
  const made = supportReferences(resolved.references, support);
681
709
  const configured: Configured = {
682
710
  configuration: projectRead.configuration,
683
711
  symbols: projectRead.held.symbols,
684
- references: made,
712
+ references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
685
713
  roots: projectRead.rooted.liveUnderReachability,
686
714
  testFiles: resolved.testFilePaths,
687
715
  ...(support.size === 0 ? {} : { supportFiles: [...support].sort() }),
@@ -689,15 +717,6 @@ function readRun<Extra>(
689
717
  ? {}
690
718
  : { referenceOnly: projectRead.referenceOnly }),
691
719
  };
692
- const exempt = computeExemptions(
693
- { project, held: projectRead.held, targetRoot, templates, ts: config.ts, consumers },
694
- detectors,
695
- {
696
- disabled,
697
- mode,
698
- testFiles: new Set([...resolved.testFilePaths, ...support]),
699
- },
700
- );
701
720
  const unanswered = [
702
721
  ...projectRead.held.unanswered,
703
722
  ...projectRead.rooted.unanswered,
@@ -784,6 +803,61 @@ function holdingSweep<Extra>(
784
803
  }
785
804
  }
786
805
 
806
+ /**
807
+ * The test references exemption evidence written in a test file makes: each is a read of
808
+ * the declaration it would retain, made by the test file at the evidence's site.
809
+ */
810
+ function evidenceReferences(
811
+ symbols: readonly InventorySymbol[],
812
+ inTestFiles: readonly Exemption[],
813
+ ): readonly Reference[] {
814
+ if (inTestFiles.length === 0) {
815
+ return [];
816
+ }
817
+ const files = new Map(
818
+ symbols
819
+ .filter((symbol) => symbol.kind === "file")
820
+ .map((symbol) => [symbol.position.path, symbol.id]),
821
+ );
822
+ return inTestFiles.flatMap((record) => {
823
+ const from = files.get(record.site.path);
824
+ return from === undefined
825
+ ? []
826
+ : [
827
+ {
828
+ from,
829
+ to: record.id,
830
+ position: record.site,
831
+ use: "read",
832
+ resolution: "syntax",
833
+ test: true,
834
+ },
835
+ ];
836
+ });
837
+ }
838
+
839
+ /**
840
+ * The declarations every reference the mode counts stores into, a loaded consumer's
841
+ * reference counting as a read: what the write-only kind would report but for a record.
842
+ */
843
+ function writtenOnly<Extra>(read: ReadRun<Extra>, mode: Mode): ReadonlySet<string> {
844
+ const written = new Set<string>();
845
+ const readSome = new Set<string>();
846
+ for (const { configured } of read.projects) {
847
+ for (const reference of configured.references) {
848
+ if (mode.production && reference.test) {
849
+ continue;
850
+ }
851
+ if (reference.use === "write" && reference.consumer === undefined) {
852
+ written.add(reference.to);
853
+ } else {
854
+ readSome.add(reference.to);
855
+ }
856
+ }
857
+ }
858
+ return new Set([...written].filter((id) => !readSome.has(id)));
859
+ }
860
+
787
861
  /**
788
862
  * The sweep of a run read once, under one set of marks. Where a question went unanswered,
789
863
  * the run is swept once more without what that question could have kept live and without
@@ -834,7 +908,7 @@ function sweptOf<Extra>(
834
908
  matrix: read.matrix,
835
909
  sweep: result,
836
910
  exempt,
837
- retained: retainedIn(read.matrix, swept),
911
+ retained: retainedIn(read.matrix, swept, writtenOnly(read, input.mode)),
838
912
  notBuilt: read.notBuilt,
839
913
  unanswered: read.unanswered,
840
914
  heldByUnanswered: [...held],
@@ -874,6 +948,7 @@ interface EmitterExtra {
874
948
  readonly needs: ReturnType<typeof projectNeeds>;
875
949
  readonly implementations: ReturnType<typeof implementations>;
876
950
  readonly heldByInclusion: readonly string[];
951
+ readonly generated: readonly string[];
877
952
  readonly rooted: Roots;
878
953
  readonly directives: readonly InlineDirective[];
879
954
  readonly declarationsNamed: readonly boolean[];
@@ -900,9 +975,12 @@ function readEmitterRun(
900
975
  (project, read, references) => ({
901
976
  deprecation: deprecatedDeclarations(project, read.held),
902
977
  accessors: accessorsOf(project, read.held),
903
- needs: projectNeeds(project, read.held, host, targetRoot),
978
+ needs: projectNeeds(project, read.held, host, targetRoot, (fileName, text) =>
979
+ engine.parseSourceFile(fileName, text),
980
+ ),
904
981
  implementations: implementations(project, read.held),
905
982
  heldByInclusion: heldByInclusion(project, targetRoot),
983
+ generated: [...read.generated.keys()],
906
984
  rooted: read.rooted,
907
985
  directives: inlineDirectives(project, read.held, targetRoot).filter(
908
986
  (directive) => !read.referenceOnly.includes(directive.site.path),
@@ -956,6 +1034,7 @@ function emitterInputOver(
956
1034
  files: {
957
1035
  tree: sourceTree(host, targetRoot),
958
1036
  heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
1037
+ generated: new Set(projects.flatMap((one) => one.extra.generated)),
959
1038
  },
960
1039
  boundary: {
961
1040
  consumers: {
@@ -8,6 +8,8 @@
8
8
  */
9
9
 
10
10
  import {
11
+ isExportDeclaration,
12
+ isImportDeclaration,
11
13
  isNoSubstitutionTemplateLiteral,
12
14
  isStringLiteral,
13
15
  type Node,
@@ -43,6 +45,17 @@ const DEPENDENCY_MEMBERS = [
43
45
  /** The prefixes a string names a file with, relative to the file that writes it. */
44
46
  const RELATIVE_PREFIXES = ["./", "../"] as const;
45
47
 
48
+ /** The JavaScript extensions a relative specifier from a configuration file stands for TypeScript under. */
49
+ const COUNTERPARTS: readonly (readonly [string, readonly string[]])[] = [
50
+ [".js", [".ts", ".tsx"]],
51
+ [".jsx", [".tsx", ".ts"]],
52
+ [".mjs", [".mts"]],
53
+ [".cjs", [".cts"]],
54
+ ];
55
+
56
+ /** The extensions appended to a relative specifier from a configuration file, in order. */
57
+ const APPENDED = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"] as const;
58
+
46
59
  /** The extensions the program's module resolution appends to a specifier written without one. */
47
60
  const RESOLVED_EXTENSIONS = [
48
61
  ".ts",
@@ -83,10 +96,27 @@ interface ConfigurationDocument {
83
96
  readonly strings: readonly string[];
84
97
  }
85
98
 
99
+ /** Parses one file the program does not hold, with no type information. */
100
+ export type ParseFile = (fileName: string, text: string) => SourceFile;
101
+
102
+ /** One file no program holds that a configuration module outside the program leads to. */
103
+ export interface OutsideModule {
104
+ /** The file, parsed alone. */
105
+ readonly file: SourceFile;
106
+ /** The directory of the configuration module the file was reached from, which holds its manifest. */
107
+ readonly manifestDir: string;
108
+ }
109
+
86
110
  /** The configuration files one project holds and sits beside. */
87
111
  interface ConfigurationFiles {
88
112
  /** Every own file that is a configuration module, with the form its name has. */
89
113
  readonly modules: readonly { readonly file: SourceFile; readonly form: string }[];
114
+ /**
115
+ * Every configuration module in a directory of the project that the program does not
116
+ * hold, and every file such a file imports or re-exports by a relative specifier that
117
+ * the program does not hold either, read where a parser is given.
118
+ */
119
+ readonly outside: readonly OutsideModule[];
90
120
  /** Every JSON configuration file in a directory of the project's files or above one. */
91
121
  readonly documents: readonly ConfigurationDocument[];
92
122
  }
@@ -128,6 +158,7 @@ export function configurationFiles(
128
158
  host: Host,
129
159
  targetRoot: string,
130
160
  files: SourceFiles,
161
+ parse?: ParseFile,
131
162
  ): ConfigurationFiles {
132
163
  const root = resolvePath(host.workingDirectory(), targetRoot);
133
164
  const manifests = new Map<string, boolean>();
@@ -182,7 +213,111 @@ export function configurationFiles(
182
213
  documents.push({ path, strings: documentStrings(value) });
183
214
  }
184
215
  }
185
- return { modules, documents };
216
+ return {
217
+ modules,
218
+ documents,
219
+ outside:
220
+ parse === undefined ? [] : outsideModules(host, [...dirs], files, holdsManifest, parse),
221
+ };
222
+ }
223
+
224
+ /** The relative specifiers one file's imports and re-exports name. */
225
+ function relativeSpecifiers(file: SourceFile): readonly string[] {
226
+ return file.statements.flatMap((statement) => {
227
+ const specifier =
228
+ isImportDeclaration(statement) || isExportDeclaration(statement)
229
+ ? statement.moduleSpecifier
230
+ : undefined;
231
+ return specifier !== undefined &&
232
+ isStringLiteral(specifier) &&
233
+ RELATIVE_PREFIXES.some((prefix) => specifier.text.startsWith(prefix))
234
+ ? [specifier.text]
235
+ : [];
236
+ });
237
+ }
238
+
239
+ /**
240
+ * The module one relative specifier from a configuration file reaches: the path it names,
241
+ * that path with its JavaScript extension replaced by the TypeScript counterparts, that
242
+ * path with an extension appended, and a directory's `index` with one appended.
243
+ */
244
+ function reachedBy(host: Host, dir: string, specifier: string): string | undefined {
245
+ const path = resolvePath(dir, specifier);
246
+ const isFile = (candidate: string): boolean => host.kindOf(candidate) === "file";
247
+ // A data file such as `./package.json` is no module, so its strings use no dependency.
248
+ if (isFile(path) && APPENDED.some((extension) => path.endsWith(extension))) {
249
+ return path;
250
+ }
251
+ for (const [extension, counterparts] of COUNTERPARTS) {
252
+ if (path.endsWith(extension)) {
253
+ const stem = path.slice(0, -extension.length);
254
+ const found = counterparts.map((one) => stem + one).find(isFile);
255
+ if (found !== undefined) {
256
+ return found;
257
+ }
258
+ }
259
+ }
260
+ const appended = APPENDED.map((extension) => path + extension).find(isFile);
261
+ if (appended !== undefined) {
262
+ return appended;
263
+ }
264
+ return host.kindOf(path) === "directory"
265
+ ? APPENDED.map((extension) => joinPath(path, `index${extension}`)).find(isFile)
266
+ : undefined;
267
+ }
268
+
269
+ /**
270
+ * The configuration modules of the directories that hold a manifest which the program
271
+ * does not hold, and the files they import by a relative specifier, until no further
272
+ * file joins. A file that cannot be read is skipped.
273
+ */
274
+ function outsideModules(
275
+ host: Host,
276
+ dirs: readonly string[],
277
+ files: SourceFiles,
278
+ holdsManifest: (dir: string) => boolean,
279
+ parse: ParseFile,
280
+ ): readonly OutsideModule[] {
281
+ const pending: { path: string; manifestDir: string }[] = [];
282
+ for (const dir of [...dirs].sort()) {
283
+ if (!holdsManifest(dir)) {
284
+ continue;
285
+ }
286
+ let names: readonly string[];
287
+ try {
288
+ names = host
289
+ .readDirectory(dir)
290
+ .filter((entry) => !entry.directory && configurationModuleForm(entry.name) !== undefined)
291
+ .map((entry) => entry.name)
292
+ .sort();
293
+ } catch {
294
+ continue;
295
+ }
296
+ pending.push(...names.map((name) => ({ path: joinPath(dir, name), manifestDir: dir })));
297
+ }
298
+ const found: OutsideModule[] = [];
299
+ const seen = new Set<string>();
300
+ for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
301
+ if (seen.has(next.path) || files.byName.has(next.path)) {
302
+ continue;
303
+ }
304
+ seen.add(next.path);
305
+ let text: string;
306
+ try {
307
+ text = host.readFile(next.path);
308
+ } catch {
309
+ continue;
310
+ }
311
+ const file = parse(next.path, text);
312
+ found.push({ file, manifestDir: next.manifestDir });
313
+ for (const specifier of relativeSpecifiers(file)) {
314
+ const reached = reachedBy(host, dirnamePath(next.path), specifier);
315
+ if (reached !== undefined) {
316
+ pending.push({ path: reached, manifestDir: next.manifestDir });
317
+ }
318
+ }
319
+ }
320
+ return found;
186
321
  }
187
322
 
188
323
  /** Every dependency the manifest in one directory declares, or none where it holds none to read. */
@@ -2,16 +2,9 @@
2
2
  // Regenerate it with `UPDATE_GOLDEN=1 npx vitest --run src/conformance.corpus.test.ts`.
3
3
  export const RECORD = {
4
4
  conformance: {
5
- corpusVersion: "2.1.1",
5
+ corpusVersion: "2.2.0",
6
6
  result: "pass",
7
- digest: "sha256:e5e456bf3a706f1fa9f906ae71e10ea98a78da52d7d99a5ed59dca8a72bbe927",
7
+ digest: "sha256:ce7c9087ed7b2baf3de9fa2df436b500706f4f9e94fd9e0fed633054305f06a4",
8
8
  },
9
- gaps: [
10
- {
11
- fixture: "private-member-unread",
12
- capability: "reflective-lookup",
13
- reason:
14
- "The reference pass resolves a string-literal element access to the member it names, so the member is live by that reference and the retained listing names no class for it.",
15
- },
16
- ],
9
+ gaps: [],
17
10
  } as const;