@cplieger/deadset-ts 8.0.0-dev.20 → 8.0.0-dev.22

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 (72) hide show
  1. package/dist/src/alias-chain.js +33 -2
  2. package/dist/src/analysis.js +18 -77
  3. package/dist/src/conformance-record.js +2 -2
  4. package/dist/src/contract/contract.generated.js +5 -4
  5. package/dist/src/dependencies.js +1 -1
  6. package/dist/src/exemptions/argument-places.js +131 -0
  7. package/dist/src/{calls.js → exemptions/calls.js} +126 -179
  8. package/dist/src/{exempt-classes.js → exemptions/classes.js} +1 -1
  9. package/dist/src/{decorator.js → exemptions/decorator.js} +19 -30
  10. package/dist/src/exemptions/detectors.js +48 -0
  11. package/dist/src/{enum-group.js → exemptions/enum-group.js} +7 -7
  12. package/dist/src/{erased-stores.js → exemptions/erased-stores.js} +4 -6
  13. package/dist/src/{framework-lifecycle.js → exemptions/framework-lifecycle.js} +31 -36
  14. package/dist/src/{generated-file.js → exemptions/generated-file.js} +4 -4
  15. package/dist/src/{injection-container.js → exemptions/injection-container.js} +19 -25
  16. package/dist/src/exemptions/installed-globals.js +173 -0
  17. package/dist/src/{interface-satisfaction.js → exemptions/interface-satisfaction.js} +52 -121
  18. package/dist/src/exemptions/lifecycle.js +150 -0
  19. package/dist/src/{reflective-lookup.js → exemptions/reflective-lookup.js} +3 -3
  20. package/dist/src/{serialization-contract.js → exemptions/serialization-contract.js} +415 -329
  21. package/dist/src/exemptions/sites.js +182 -0
  22. package/dist/src/{template-field.js → exemptions/template-field.js} +7 -7
  23. package/dist/src/{value-reach.js → exemptions/value-reach.js} +14 -7
  24. package/dist/src/findings/intra-function.js +50 -37
  25. package/dist/src/index/sites.js +1 -1
  26. package/dist/src/index/values.js +124 -0
  27. package/dist/src/intra-function-parts.js +1149 -222
  28. package/dist/src/liveness/sweep.js +2 -2
  29. package/dist/src/query.js +48 -0
  30. package/dist/src/roots/configured-declarations.js +1 -1
  31. package/dist/src/verbs/print-retained.js +23 -1
  32. package/jsr.json +1 -1
  33. package/package.json +2 -2
  34. package/src/alias-chain.ts +38 -2
  35. package/src/analysis.ts +19 -100
  36. package/src/bindings.ts +1 -1
  37. package/src/conformance-record.ts +2 -2
  38. package/src/contract/contract.generated.ts +5 -4
  39. package/src/dependencies.ts +1 -1
  40. package/src/exemptions/argument-places.ts +198 -0
  41. package/src/{calls.ts → exemptions/calls.ts} +190 -224
  42. package/src/{exempt-classes.ts → exemptions/classes.ts} +1 -1
  43. package/src/exemptions/decorator.ts +56 -0
  44. package/src/exemptions/detectors.ts +75 -0
  45. package/src/{enum-group.ts → exemptions/enum-group.ts} +9 -9
  46. package/src/{erased-stores.ts → exemptions/erased-stores.ts} +5 -8
  47. package/src/{framework-lifecycle.ts → exemptions/framework-lifecycle.ts} +39 -48
  48. package/src/{generated-file.ts → exemptions/generated-file.ts} +6 -6
  49. package/src/{injection-container.ts → exemptions/injection-container.ts} +28 -33
  50. package/src/exemptions/installed-globals.ts +255 -0
  51. package/src/{interface-satisfaction.ts → exemptions/interface-satisfaction.ts} +69 -160
  52. package/src/exemptions/lifecycle.ts +286 -0
  53. package/src/{reflective-lookup.ts → exemptions/reflective-lookup.ts} +5 -5
  54. package/src/{serialization-contract.ts → exemptions/serialization-contract.ts} +563 -429
  55. package/src/exemptions/sites.ts +293 -0
  56. package/src/{template-field.ts → exemptions/template-field.ts} +11 -11
  57. package/src/{value-reach.ts → exemptions/value-reach.ts} +16 -8
  58. package/src/facts.ts +1 -1
  59. package/src/findings/intra-function.ts +59 -37
  60. package/src/index/sites.ts +1 -1
  61. package/src/index/values.ts +170 -0
  62. package/src/intra-function-parts.ts +1435 -247
  63. package/src/liveness/sweep.ts +2 -2
  64. package/src/query.ts +109 -10
  65. package/src/roots/configured-declarations.ts +1 -2
  66. package/src/stores.ts +1 -1
  67. package/src/verbs/print-retained.ts +29 -1
  68. package/dist/src/exempt.js +0 -164
  69. package/dist/src/value-flow.js +0 -74
  70. package/src/decorator.ts +0 -78
  71. package/src/exempt.ts +0 -295
  72. package/src/value-flow.ts +0 -104
@@ -0,0 +1,150 @@
1
+ /**
2
+ * The exemption lifecycle: the classes of reason for which a declaration the reference
3
+ * graph alone would report is live all the same. A class is policy over what one
4
+ * project's checker answers, and its detector names the declarations it holds back. This
5
+ * module runs the detectors over one project, then settles every project's records
6
+ * against the run: the writes they wait for, the interface methods they wait to be
7
+ * called, and the mode and test files that decide which of them hold. It names no class:
8
+ * the composition hands it the table of the classes it has.
9
+ */
10
+ import { EXEMPTION_CLASSES } from "../contract/contract.generated.js";
11
+ import { byPosition } from "../position.js";
12
+ import { Unanswerable } from "../query.js";
13
+ import { bytewise } from "../text/compare.js";
14
+ import { TS_EXEMPTION_CLASSES, typescriptVisibilityOf, } from "./classes.js";
15
+ import { projectSites } from "./sites.js";
16
+ /**
17
+ * Whether a class whose row states `allowed` may retain a member of one visibility. A
18
+ * declaration the inventory does not hold has no visibility to refuse it by.
19
+ */
20
+ function retainable(allowed, visibility) {
21
+ if (visibility === "private") {
22
+ return allowed.private;
23
+ }
24
+ if (visibility === "private-name") {
25
+ return allowed.privateName;
26
+ }
27
+ return true;
28
+ }
29
+ /** The kinds of declaration each class can hold back, which a detector that stops holds back whole. */
30
+ const RETAINABLE = new Map(EXEMPTION_CLASSES.flatMap((row) => "ts" in row.retainsKinds ? [[row.class, new Set(row.retainsKinds.ts)]] : []));
31
+ /**
32
+ * What one detector holds back over one project. A detector that stops because the
33
+ * checker left a question it needs unanswered holds back every declaration of the
34
+ * project its class could hold back, each recorded at its own position, so the gap
35
+ * never yields a finding.
36
+ */
37
+ function detected(exemptionClass, detect, input) {
38
+ try {
39
+ return detect(input);
40
+ }
41
+ catch (error) {
42
+ if (!(error instanceof Unanswerable)) {
43
+ throw error;
44
+ }
45
+ const kinds = RETAINABLE.get(exemptionClass);
46
+ return input.held.symbols
47
+ .filter((symbol) => kinds?.has(symbol.kind) === true)
48
+ .map((symbol) => ({
49
+ id: symbol.id,
50
+ detail: "kept live by a question the checker did not answer",
51
+ site: symbol.position,
52
+ unanswered: true,
53
+ }));
54
+ }
55
+ }
56
+ /**
57
+ * Every record one project's detectors found, one per piece of evidence, before the run
58
+ * settles which of them hold. Classes run in the vocabulary's order and a switched-off
59
+ * class runs not at all; evidence on a private member the class's row says it cannot
60
+ * retain is dropped, whatever the detector found.
61
+ */
62
+ export function detect(input, detectors, disabled) {
63
+ const visibility = new Map(input.held.symbols.map((symbol) => [symbol.id, symbol.visibility]));
64
+ const sited = { ...input, sites: projectSites(input.project, input.held) };
65
+ return TS_EXEMPTION_CLASSES.flatMap((exemptionClass) => {
66
+ const run = detectors.get(exemptionClass);
67
+ if (run === undefined || disabled.has(exemptionClass)) {
68
+ return [];
69
+ }
70
+ const allowed = typescriptVisibilityOf(exemptionClass);
71
+ return detected(exemptionClass, run, sited).flatMap((evidence) => retainable(allowed, visibility.get(evidence.id))
72
+ ? [{ ...evidence, class: exemptionClass }]
73
+ : []);
74
+ });
75
+ }
76
+ /**
77
+ * Every project's records settled against the run, in three steps. A record that holds
78
+ * only while written holds where a write the mode counts stores into its declaration.
79
+ * A record whose member implements only interface methods nothing calls is dropped,
80
+ * unless every implementation of such a method is empty. Under a production mode a
81
+ * record whose evidence a test file writes holds nothing, and is a test reference to its
82
+ * declaration, as a reference the test file wrote would be.
83
+ */
84
+ export function settle(projects, run) {
85
+ const written = projects.map((one) => one.records.flatMap(({ whileWritten, ...record }) => whileWritten === undefined || run.written.has(record.id) ? [record] : []));
86
+ const methods = new Set(projects.flatMap((one) => one.held.symbols.filter((symbol) => symbol.kind === "interface-method").map((one) => one.id)));
87
+ const called = new Set(run.references.map((reference) => reference.to));
88
+ const bodies = new Map();
89
+ for (const record of written.flat()) {
90
+ for (const method of record.implementing?.methods ?? []) {
91
+ bodies.set(method, (bodies.get(method) ?? true) && record.implementing?.empty === true);
92
+ }
93
+ }
94
+ const uncalled = (method) => methods.has(method) && !called.has(method) && bodies.get(method) !== true;
95
+ return projects.map((project, index) => {
96
+ const files = new Map(project.held.symbols
97
+ .filter((symbol) => symbol.kind === "file")
98
+ .map((symbol) => [symbol.position.path, symbol.id]));
99
+ const kept = [];
100
+ const references = [];
101
+ const tested = [];
102
+ for (const record of written[index] ?? []) {
103
+ const implemented = record.implementing?.methods ?? [];
104
+ if (implemented.length > 0 && implemented.every(uncalled)) {
105
+ references.push(...implemented.map((method) => ({
106
+ from: method,
107
+ to: record.id,
108
+ position: record.site,
109
+ use: "read",
110
+ resolution: "override",
111
+ test: false,
112
+ })));
113
+ }
114
+ else if (run.mode.production && project.testFiles.has(record.site.path)) {
115
+ const from = files.get(record.site.path);
116
+ if (from !== undefined) {
117
+ tested.push({
118
+ from,
119
+ to: record.id,
120
+ position: record.site,
121
+ use: "read",
122
+ resolution: "syntax",
123
+ test: true,
124
+ });
125
+ }
126
+ }
127
+ else {
128
+ kept.push(record);
129
+ }
130
+ }
131
+ return { records: exemptionsOf(kept), references: [...references, ...tested] };
132
+ });
133
+ }
134
+ /**
135
+ * Records as a run holds them: ordered by site, then class, then declaration, and one
136
+ * per declaration, class, detail and file it holds while live, the one at the first
137
+ * site. A fact found at many sites, or by several projects, is one record.
138
+ */
139
+ export function exemptionsOf(found) {
140
+ const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) || bytewise(a.class, b.class) || bytewise(a.id, b.id));
141
+ const seen = new Set();
142
+ return ordered.filter((record) => {
143
+ const fact = [record.id, record.class, record.detail, record.whileLive ?? ""].join("\u0000");
144
+ if (seen.has(fact)) {
145
+ return false;
146
+ }
147
+ seen.add(fact);
148
+ return true;
149
+ });
150
+ }
@@ -14,9 +14,9 @@
14
14
  */
15
15
  import { isCallExpression, isElementAccessExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/ast";
16
16
  import { TypeFlags } from "@typescript/native/sync";
17
- import { memberNames } from "./member-names.js";
18
- import { renderPosition } from "./position.js";
19
- import { isAnswered, UNANSWERED } from "./query.js";
17
+ import { memberNames } from "../member-names.js";
18
+ import { renderPosition } from "../position.js";
19
+ import { isAnswered, UNANSWERED } from "../query.js";
20
20
  /** The `Reflect` methods whose second argument is a property key. */
21
21
  const REFLECT_KEYED = new Set(["get", "set", "has"]);
22
22
  /** The text one key denotes, where it is literal text. */