@cplieger/deadset-ts 8.0.0-dev.17 → 8.0.0-dev.18

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 (60) hide show
  1. package/dist/src/analysis.js +26 -71
  2. package/dist/src/augmentations.js +1 -1
  3. package/dist/src/calls.js +267 -48
  4. package/dist/src/conformance-record.js +1 -1
  5. package/dist/src/contract/contract.generated.js +2 -2
  6. package/dist/src/exempt.js +0 -26
  7. package/dist/src/exposure.js +400 -0
  8. package/dist/src/findings/completion.js +4 -17
  9. package/dist/src/findings/emitters.js +22 -33
  10. package/dist/src/findings/intra-function.js +3 -3
  11. package/dist/src/findings/reads-and-writes.js +71 -15
  12. package/dist/src/findings/unused-declarations.js +2 -22
  13. package/dist/src/findings/visibility-narrowing.js +14 -20
  14. package/dist/src/inventory.js +18 -0
  15. package/dist/src/{components.js → liveness/components.js} +1 -1
  16. package/dist/src/{graph.js → liveness/graph.js} +38 -21
  17. package/dist/src/liveness/matrix.js +99 -0
  18. package/dist/src/liveness/sweep.js +558 -0
  19. package/dist/src/query.js +46 -0
  20. package/dist/src/references/sink.js +87 -1
  21. package/dist/src/references.js +1 -1
  22. package/dist/src/roots/published-reach.js +4 -4
  23. package/dist/src/verbs/explain.js +73 -152
  24. package/dist/src/verbs/print-retained.js +1 -1
  25. package/jsr.json +1 -1
  26. package/package.json +1 -1
  27. package/src/analysis.ts +41 -107
  28. package/src/augmentations.ts +1 -1
  29. package/src/calls.ts +326 -52
  30. package/src/conformance-record.ts +1 -1
  31. package/src/contract/contract.generated.ts +2 -2
  32. package/src/exempt.ts +31 -46
  33. package/src/exposure.ts +492 -0
  34. package/src/finding.ts +1 -1
  35. package/src/findings/boundary.ts +7 -0
  36. package/src/findings/completion.ts +5 -19
  37. package/src/findings/emitters.ts +23 -41
  38. package/src/findings/interfaces.ts +3 -3
  39. package/src/findings/intra-function.ts +3 -3
  40. package/src/findings/reads-and-writes.ts +99 -26
  41. package/src/findings/unused-declarations.ts +4 -28
  42. package/src/findings/visibility-narrowing.ts +25 -26
  43. package/src/interface-satisfaction.ts +1 -1
  44. package/src/inventory.ts +19 -0
  45. package/src/{components.ts → liveness/components.ts} +3 -3
  46. package/src/{graph.ts → liveness/graph.ts} +74 -26
  47. package/src/liveness/matrix.ts +162 -0
  48. package/src/liveness/sweep.ts +855 -0
  49. package/src/query.ts +81 -0
  50. package/src/references/sink.ts +101 -0
  51. package/src/references.ts +8 -1
  52. package/src/roots/published-reach.ts +9 -4
  53. package/src/stores.ts +2 -1
  54. package/src/tests/file-class.ts +19 -2
  55. package/src/verbs/explain.ts +81 -199
  56. package/src/verbs/print-retained.ts +1 -1
  57. package/dist/src/matrix.js +0 -275
  58. package/dist/src/sweep.js +0 -234
  59. package/src/matrix.ts +0 -466
  60. package/src/sweep.ts +0 -384
package/src/analysis.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  } from "./roots/configured-declarations.ts";
18
18
  import { decorator } from "./decorator.ts";
19
19
  import { configurationNeeds, dependenciesOf, projectNeeds } from "./dependencies.ts";
20
+ import { carriersOf, NO_CARRIERS, type Carriers } from "./exposure.ts";
20
21
  import { deprecatedDeclarations, type Deprecation } from "./deprecation.ts";
21
22
  import { moduleIdentity, type LoadedConsumer, type NotBuilt } from "./load/discover.ts";
22
23
  import type { LibraryUnfound } from "./load/classify.ts";
@@ -35,9 +36,7 @@ import {
35
36
  heldRecords,
36
37
  disabledClasses,
37
38
  exemptionsOf,
38
- holdingWhile,
39
39
  isUnansweredRecord,
40
- retainedIn,
41
40
  writtenRecords,
42
41
  type Detector,
43
42
  type Detectors,
@@ -57,15 +56,7 @@ import { intraFunctionFacts, projectParts, type ProjectParts } from "./intra-fun
57
56
  import { type Inventory, type InventorySymbol } from "./inventory.ts";
58
57
  import { inventory } from "./inventory/enumerate.ts";
59
58
  import { readManifest, type ManifestEntry } from "./roots/manifest.ts";
60
- import {
61
- liveFilesAt,
62
- matrixOf,
63
- referenceKey,
64
- sweepMatrix,
65
- type Configured,
66
- type Matrix,
67
- type SweepResult,
68
- } from "./matrix.ts";
59
+ import { matrixOf, referenceKey, type Configured, type Matrix } from "./liveness/matrix.ts";
69
60
  import {
70
61
  ancestors,
71
62
  dirnamePath,
@@ -108,7 +99,9 @@ import { accessorsOf, storesOf } from "./stores.ts";
108
99
  import { isPartKind, type SuppressionRecord, type Suppressions, type Verdict } from "./suppress.ts";
109
100
  import { bindRows, readBaseline, readIgnoreFile, type Recorded } from "./suppress-file.ts";
110
101
  import { inlineDirectives, inlineSuppressions, type InlineDirective } from "./suppress-inline.ts";
111
- import type { Exemption, Mode, SweepInput } from "./sweep.ts";
102
+ import { sweep, type Sweep, type SweepPolicy } from "./liveness/sweep.ts";
103
+ import type { Exemption } from "./exempt.ts";
104
+ import type { Mode } from "./tests/file-class.ts";
112
105
  import { readTemplates, templateField } from "./template-field.ts";
113
106
  import { bytewise } from "./text/compare.ts";
114
107
  import { holdsManifest, manifestAt } from "./fs/manifest.ts";
@@ -434,27 +427,13 @@ export function productionMode(config: PartialConfig): Mode {
434
427
  /** The run's sweep, beside the matrix it judged. */
435
428
  export interface RunSweep {
436
429
  readonly matrix: Matrix;
437
- readonly sweep: SweepResult;
438
- /** The exemption records that hold under the sweep. */
439
- readonly exempt: readonly Exemption[];
440
- /**
441
- * The exemption records that held back a declaration some configuration of the run
442
- * would otherwise judge dead, in the declarations' site order.
443
- */
444
- readonly retained: readonly Exemption[];
430
+ readonly sweep: Sweep;
445
431
  /** The derived configurations the run dropped. */
446
432
  readonly notBuilt: readonly NotBuilt[];
447
433
  /** Every question the checker could not answer, each once. */
448
434
  readonly unanswered: readonly UnansweredQuestion[];
449
- /**
450
- * Every declaration an unanswered question could have kept live, and every one only
451
- * such a declaration keeps live. The run reports none of them.
452
- */
453
- readonly heldByUnanswered: readonly string[];
454
435
  /** The lines of the units type errors skipped, on which the run reports nothing. */
455
436
  readonly skipped: readonly SkippedUnit[];
456
- /** The component files the run read, by path below the target root, each once. */
457
- readonly componentFiles: readonly string[];
458
437
  }
459
438
 
460
439
  /** One `<script` start tag of a component file the run read no block for. */
@@ -527,6 +506,8 @@ interface ReadRun<Extra> {
527
506
  readonly projects: readonly SweptProject<Extra>[];
528
507
  /** Every declared consumer, each loaded, in the scope's order. */
529
508
  readonly consumers: readonly LoadedConsumer[];
509
+ /** Whether the scope declares a consumer, loaded or not. */
510
+ readonly declaresConsumers: boolean;
530
511
  /** The derived configurations the run dropped. */
531
512
  readonly notBuilt: readonly NotBuilt[];
532
513
  /** Every question the checker could not answer, each once. */
@@ -826,6 +807,7 @@ function readRun<Extra>(
826
807
  ],
827
808
  projects,
828
809
  consumers: read.plan.discovery.consumers,
810
+ declaresConsumers: scope.consumers.length > 0,
829
811
  notBuilt: read.classified.notBuilt,
830
812
  unanswered: read.unanswered,
831
813
  componentsUnread: read.plan.workspace?.componentsUnread ?? [],
@@ -840,33 +822,6 @@ function readRun<Extra>(
840
822
  };
841
823
  }
842
824
 
843
- /**
844
- * The run swept under one set of marks with the records that hold. A record a component
845
- * file's markup holds counts only while the file is live, and what it holds may be what
846
- * keeps the file live, so each file's markup joins only once a sweep without it finds the
847
- * file live, until a sweep finds no further file live.
848
- */
849
- function holdingSweep<Extra>(
850
- read: ReadRun<Extra>,
851
- input: Pick<SweepInput, "marked" | "mode">,
852
- ): { readonly swept: SweepInput; readonly result: SweepResult } {
853
- const paths = new Set(read.exempt.flatMap((record) => record.whileLive ?? []));
854
- let live = new Set<string>();
855
- for (;;) {
856
- const swept: SweepInput = {
857
- ...input,
858
- exempt: holdingWhile(read.exempt, live),
859
- unanswered: read.unansweredHeld,
860
- };
861
- const result = sweepMatrix(read.matrix, swept);
862
- const next = new Set(liveFilesAt(read.matrix, result, paths).map((one) => one.position.path));
863
- if (next.size === live.size) {
864
- return { swept, result };
865
- }
866
- live = next;
867
- }
868
- }
869
-
870
825
  /**
871
826
  * The test references exemption evidence written in a test file makes: each is a read of
872
827
  * the declaration it would retain, made by the test file at the evidence's site.
@@ -939,62 +894,27 @@ function writtenOnly<Extra>(read: ReadRun<Extra>, mode: Mode): ReadonlySet<strin
939
894
  return new Set([...written].filter((id) => !readSome.has(id)));
940
895
  }
941
896
 
942
- /**
943
- * The sweep of a run read once, under one set of marks. Where a question went unanswered,
944
- * the run is swept once more without what that question could have kept live and without
945
- * the references that stood in for its answer, and every declaration that sweep judges
946
- * dead and this one judges live, or dead by another rule, is held by the gap too, as is
947
- * every declaration that makes such a reference.
948
- */
897
+ /** The sweep of a run read once, under one set of marks. */
949
898
  function sweptOf<Extra>(
950
899
  read: ReadRun<Extra>,
951
- input: Pick<SweepInput, "marked" | "mode">,
900
+ input: Pick<SweepPolicy, "marked" | "mode">,
952
901
  ): RunSweep {
953
- const { swept, result } = holdingSweep(read, input);
954
- const exempt = swept.exempt ?? [];
955
- const held = new Set(read.unansweredHeld);
956
- if (held.size > 0) {
957
- // What a declaration reads where the checker did not answer is unknown, so a rule
958
- // over what it reads, the test of dead code among them, cannot judge it.
959
- for (const { configured } of read.projects) {
960
- for (const reference of configured.references) {
961
- if (reference.guessed === true) {
962
- held.add(reference.from);
963
- }
964
- }
965
- }
966
- const dead = new Map(result.candidates.map((candidate) => [candidate.id, candidate]));
967
- const answered = matrixOf(
968
- read.projects.map(({ configured }) => ({
969
- ...configured,
970
- references: configured.references.filter((one) => one.guessed !== true),
971
- })),
972
- );
973
- const bare: SweepInput = {
974
- ...swept,
975
- exempt: exempt.filter((record) => !isUnansweredRecord(record)),
976
- unanswered: [],
977
- };
978
- for (const candidate of sweepMatrix(answered, bare).candidates) {
979
- const kept = dead.get(candidate.id);
980
- if (
981
- kept?.testOfDeadCode !== candidate.testOfDeadCode ||
982
- kept.relation !== candidate.relation
983
- ) {
984
- held.add(candidate.id);
985
- }
986
- }
987
- }
902
+ const componentFiles = [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(
903
+ bytewise,
904
+ );
988
905
  return {
989
906
  matrix: read.matrix,
990
- sweep: result,
991
- exempt,
992
- retained: retainedIn(read.matrix, swept, writtenOnly(read, input.mode)),
907
+ sweep: sweep(read.matrix, {
908
+ ...input,
909
+ exempt: read.exempt,
910
+ unanswered: read.unansweredHeld,
911
+ componentFiles,
912
+ unseenCallers: !read.declaresConsumers,
913
+ writeOnly: writtenOnly(read, input.mode),
914
+ }),
993
915
  notBuilt: read.notBuilt,
994
916
  unanswered: read.unanswered,
995
- heldByUnanswered: [...held],
996
917
  skipped: read.projects.flatMap((one) => one.skipped),
997
- componentFiles: [...new Set(read.projects.flatMap((one) => one.componentFiles))].sort(bytewise),
998
918
  };
999
919
  }
1000
920
 
@@ -1011,7 +931,7 @@ export function runSweep(
1011
931
  host: Host,
1012
932
  scope: Scope,
1013
933
  config: ResolvedConfig,
1014
- input: Pick<SweepInput, "marked" | "mode">,
934
+ input: Pick<SweepPolicy, "marked" | "mode">,
1015
935
  detectors: Detectors = DETECTORS,
1016
936
  ): RunSweep {
1017
937
  return sweptOf(
@@ -1035,6 +955,7 @@ interface EmitterExtra {
1035
955
  readonly declarationsNamed: readonly boolean[];
1036
956
  readonly parts: ProjectParts;
1037
957
  readonly unseenLoads: readonly UnseenLoad[];
958
+ readonly carriers: Carriers;
1038
959
  }
1039
960
 
1040
961
  /** The project's own files below the target root that are no test file. */
@@ -1090,6 +1011,8 @@ export function readEmitterRun(
1090
1011
  declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
1091
1012
  parts: projectParts(project, read.held, references, targetRoot),
1092
1013
  unseenLoads,
1014
+ // Only a closed world reports an unnecessary export, which is all the carriers decide.
1015
+ carriers: config.consumers.complete ? carriersOf(project, read.held) : NO_CARRIERS,
1093
1016
  }),
1094
1017
  );
1095
1018
  // Whether a declaration carries the marker decides its code, so one whose answer went
@@ -1156,7 +1079,7 @@ export function emitterInputOver(
1156
1079
  scope: Scope,
1157
1080
  config: ResolvedConfig,
1158
1081
  read: ReadRun<EmitterExtra>,
1159
- input: Pick<SweepInput, "marked" | "mode">,
1082
+ input: Pick<SweepPolicy, "marked" | "mode">,
1160
1083
  selfCheck: SelfCheckFacts,
1161
1084
  ): EmitterInput {
1162
1085
  const targetRoot = scope.target.path;
@@ -1173,7 +1096,7 @@ export function emitterInputOver(
1173
1096
  support: new Set(one.configured.supportFiles ?? []),
1174
1097
  accessors: one.extra.accessors,
1175
1098
  })),
1176
- swept.exempt,
1099
+ swept.sweep.exempt,
1177
1100
  input.mode,
1178
1101
  ),
1179
1102
  dependencies: dependenciesOf(
@@ -1203,12 +1126,23 @@ export function emitterInputOver(
1203
1126
  edges: readEdgeSides(host, targetRoot),
1204
1127
  filledOutside: new Set(projects.flatMap((one) => one.filledOutside)),
1205
1128
  possiblyLoaded: new Set(projects.flatMap((one) => one.possiblyLoaded)),
1129
+ carriers: {
1130
+ named: new Map(
1131
+ [
1132
+ ...Map.groupBy(
1133
+ projects.flatMap((one) => [...one.extra.carriers.named]),
1134
+ ([id]) => id,
1135
+ ),
1136
+ ].map(([id, entries]) => [id, [...new Set(entries.flatMap(([, named]) => named))]]),
1137
+ ),
1138
+ unread: new Set(projects.flatMap((one) => [...one.extra.carriers.unread])),
1139
+ },
1206
1140
  },
1207
1141
  selfCheck,
1208
1142
  intraFunction: intraFunctionFacts(
1209
1143
  projects.map((one) => one.extra.parts),
1210
1144
  projects.flatMap((one) => one.configured.references),
1211
- new Set([...swept.exempt.map((one) => one.id), ...swept.heldByUnanswered]),
1145
+ new Set([...swept.sweep.exempt.map((one) => one.id), ...swept.sweep.held]),
1212
1146
  ),
1213
1147
  };
1214
1148
  }
@@ -1448,7 +1382,7 @@ export function runAnalysis(
1448
1382
  * declarations of its own the run holds for them.
1449
1383
  */
1450
1384
  export function unansweredCounts(analysis: RunAnalysis): readonly UnansweredCount[] {
1451
- const held = new Set(analysis.swept.heldByUnanswered);
1385
+ const held = new Set(analysis.swept.sweep.held);
1452
1386
  return analysis.run.projects.flatMap((project) => {
1453
1387
  const questions =
1454
1388
  analysis.run.unanswered.filter((one) => one.configFile === project.configFile).length +
@@ -14,7 +14,7 @@ import {
14
14
  type Node,
15
15
  } from "@typescript/native/ast";
16
16
  import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
17
- import { referenceKey } from "./matrix.ts";
17
+ import { referenceKey } from "./liveness/matrix.ts";
18
18
  import type { Reference } from "./references.ts";
19
19
  import type { ProjectView } from "./session.ts";
20
20
  import type { SourceFiles } from "./source-files.ts";