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

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 (68) 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 +4 -3
  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/index/sites.js +1 -1
  25. package/dist/src/index/values.js +124 -0
  26. package/dist/src/liveness/sweep.js +2 -2
  27. package/dist/src/query.js +48 -0
  28. package/dist/src/roots/configured-declarations.js +1 -1
  29. package/dist/src/verbs/print-retained.js +23 -1
  30. package/jsr.json +1 -1
  31. package/package.json +1 -1
  32. package/src/alias-chain.ts +38 -2
  33. package/src/analysis.ts +19 -100
  34. package/src/bindings.ts +1 -1
  35. package/src/conformance-record.ts +2 -2
  36. package/src/contract/contract.generated.ts +4 -3
  37. package/src/dependencies.ts +1 -1
  38. package/src/exemptions/argument-places.ts +198 -0
  39. package/src/{calls.ts → exemptions/calls.ts} +190 -224
  40. package/src/{exempt-classes.ts → exemptions/classes.ts} +1 -1
  41. package/src/exemptions/decorator.ts +56 -0
  42. package/src/exemptions/detectors.ts +75 -0
  43. package/src/{enum-group.ts → exemptions/enum-group.ts} +9 -9
  44. package/src/{erased-stores.ts → exemptions/erased-stores.ts} +5 -8
  45. package/src/{framework-lifecycle.ts → exemptions/framework-lifecycle.ts} +39 -48
  46. package/src/{generated-file.ts → exemptions/generated-file.ts} +6 -6
  47. package/src/{injection-container.ts → exemptions/injection-container.ts} +28 -33
  48. package/src/exemptions/installed-globals.ts +255 -0
  49. package/src/{interface-satisfaction.ts → exemptions/interface-satisfaction.ts} +69 -160
  50. package/src/exemptions/lifecycle.ts +286 -0
  51. package/src/{reflective-lookup.ts → exemptions/reflective-lookup.ts} +5 -5
  52. package/src/{serialization-contract.ts → exemptions/serialization-contract.ts} +563 -429
  53. package/src/exemptions/sites.ts +293 -0
  54. package/src/{template-field.ts → exemptions/template-field.ts} +11 -11
  55. package/src/{value-reach.ts → exemptions/value-reach.ts} +16 -8
  56. package/src/facts.ts +1 -1
  57. package/src/index/sites.ts +1 -1
  58. package/src/index/values.ts +170 -0
  59. package/src/liveness/sweep.ts +2 -2
  60. package/src/query.ts +109 -10
  61. package/src/roots/configured-declarations.ts +1 -2
  62. package/src/stores.ts +1 -1
  63. package/src/verbs/print-retained.ts +29 -1
  64. package/dist/src/exempt.js +0 -164
  65. package/dist/src/value-flow.js +0 -74
  66. package/src/decorator.ts +0 -78
  67. package/src/exempt.ts +0 -295
  68. package/src/value-flow.ts +0 -104
package/src/exempt.ts DELETED
@@ -1,295 +0,0 @@
1
- /**
2
- * The exemption framework: 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;
5
- * this module runs the detectors, decides which of their records stand, and answers
6
- * which records held something back. It names no class's detector: the composition
7
- * hands it the table of the classes it has.
8
- */
9
-
10
- import { EXEMPTION_CLASSES } from "./contract/contract.generated.ts";
11
- import type { GeneratedFact } from "./generated-file.ts";
12
- import type { ResolvedConfig } from "./config.ts";
13
- import {
14
- TS_EXEMPTION_CLASSES,
15
- typescriptVisibilityOf,
16
- type ExemptionClass,
17
- type TSExemptionClass,
18
- type TypeScriptVisibility,
19
- } from "./exempt-classes.ts";
20
- import type { Inventory, InventorySymbol, Visibility } from "./inventory.ts";
21
- import { byPosition, positionKey, type Position } from "./position.ts";
22
- import { Unanswerable } from "./query.ts";
23
- import type { ProjectView } from "./session.ts";
24
- import type { Templates } from "./template-field.ts";
25
- import type { Mode } from "./tests/file-class.ts";
26
- import { bytewise } from "./text/compare.ts";
27
-
28
- /** One declaration an exemption class holds back, and the evidence it was found by. */
29
- export interface Exemption {
30
- /** The identifier of the declaration held back. */
31
- readonly id: string;
32
- readonly class: TSExemptionClass;
33
- /** One clause naming the relation, then the thing it relates to: `passed to JSON.stringify`. */
34
- readonly detail: string;
35
- /** Where the evidence is written. */
36
- readonly site: Position;
37
- /**
38
- * The component file, below the target root, whose markup holds the evidence. The
39
- * record holds only while that file is live.
40
- */
41
- readonly whileLive?: string;
42
- /**
43
- * The interface methods the retained member implements, and whether its body is empty.
44
- * The record holds only while one of them is called through its interface.
45
- */
46
- readonly implementing?: Implementing;
47
- /** Whether the record holds only where a write the mode counts stores into the declaration. */
48
- readonly whileWritten?: true;
49
- }
50
-
51
- /** The interface methods one retained class member implements, and whether its body is empty. */
52
- interface Implementing {
53
- /** The declarations of the interface's methods the member answers, by identifier. */
54
- readonly methods: readonly string[];
55
- readonly empty: boolean;
56
- }
57
-
58
- /** One declaration a detector holds back, and where and why. */
59
- export interface Evidence {
60
- /** The identifier of the declaration held back. */
61
- readonly id: string;
62
- /** One clause naming the relation, then the thing it relates to. */
63
- readonly detail: string;
64
- readonly site: Position;
65
- /** The component file, below the target root, whose markup holds the evidence. */
66
- readonly whileLive?: string;
67
- /** Where the evidence is an implementation of interface methods, which and how. */
68
- readonly implementing?: Implementing;
69
- /** Whether the evidence holds only where a write the mode counts stores into the declaration. */
70
- readonly whileWritten?: true;
71
- }
72
-
73
- /** What a detector reads of one project, while the project's view is open. */
74
- export interface DetectorInput<Brand> {
75
- readonly project: ProjectView<Brand>;
76
- readonly held: Inventory;
77
- readonly targetRoot: string;
78
- /** The configured templates, read once for the run. */
79
- readonly templates: Templates;
80
- /** The `ts` section, whose declarations the configured classes read. */
81
- readonly ts: ResolvedConfig["ts"];
82
- /** The absolute path of each declared consumer, whose modules are part of the analysis. */
83
- readonly consumers: readonly string[];
84
- /** The project's generated files, by path below the target root, each with what decided it. */
85
- readonly generated?: ReadonlyMap<string, GeneratedFact>;
86
- }
87
-
88
- /**
89
- * One class's detection over one project. The framework records each piece of evidence
90
- * under the class the table names the detector by, so a detector names no class.
91
- */
92
- export type Detector = <Brand>(input: DetectorInput<Brand>) => readonly Evidence[];
93
-
94
- /** The detector of each class one composition implements. */
95
- export type Detectors = ReadonlyMap<TSExemptionClass, Detector>;
96
-
97
- /** What decides which records of one project stand. */
98
- interface Holding {
99
- readonly mode: Mode;
100
- /** The paths the reference pass classified as test files. */
101
- readonly testFiles: ReadonlySet<string>;
102
- }
103
-
104
- /**
105
- * The classes `exemptions.disabled` switches off. A class that runs on another language
106
- * only switches nothing off here, so one configuration serves every analyzer.
107
- */
108
- export function disabledClasses(config: ResolvedConfig): ReadonlySet<ExemptionClass> {
109
- return new Set(config.exemptions.disabled);
110
- }
111
-
112
- /**
113
- * Whether a class whose row states `allowed` may retain a member of one visibility. A
114
- * declaration the inventory does not hold has no visibility to refuse it by.
115
- */
116
- function retainable(allowed: TypeScriptVisibility, visibility: Visibility | undefined): boolean {
117
- if (visibility === "private") {
118
- return allowed.private;
119
- }
120
- if (visibility === "private-name") {
121
- return allowed.privateName;
122
- }
123
- return true;
124
- }
125
-
126
- /** The kinds of declaration each class can hold back, which a detector that stops holds back whole. */
127
- const RETAINABLE: ReadonlyMap<string, ReadonlySet<string>> = new Map(
128
- EXEMPTION_CLASSES.flatMap((row) =>
129
- "ts" in row.retainsKinds ? [[row.class, new Set<string>(row.retainsKinds.ts)] as const] : [],
130
- ),
131
- );
132
-
133
- /** The detail of a record a detector that could not finish holds. */
134
- const UNANSWERED_DETAIL = "kept live by a question the checker did not answer";
135
-
136
- /** Whether one record is held by a detector that could not finish. */
137
- export function isUnansweredRecord(record: Exemption): boolean {
138
- return record.detail === UNANSWERED_DETAIL;
139
- }
140
-
141
- /**
142
- * What one detector holds back over one project. A detector that stops because the
143
- * checker left a question it needs unanswered holds back every declaration of the
144
- * project its class could hold back, each recorded at its own position, so the gap
145
- * never yields a finding.
146
- */
147
- function detected<Brand>(
148
- exemptionClass: TSExemptionClass,
149
- detect: Detector,
150
- input: DetectorInput<Brand>,
151
- ): readonly Evidence[] {
152
- try {
153
- return detect(input);
154
- } catch (error: unknown) {
155
- if (!(error instanceof Unanswerable)) {
156
- throw error;
157
- }
158
- const kinds = RETAINABLE.get(exemptionClass);
159
- return input.held.symbols
160
- .filter((symbol) => kinds?.has(symbol.kind) === true)
161
- .map((symbol) => ({ id: symbol.id, detail: UNANSWERED_DETAIL, site: symbol.position }));
162
- }
163
- }
164
-
165
- /** One project's records, and the evidence a production mode found in a test file. */
166
- interface Detected {
167
- readonly records: readonly Exemption[];
168
- /**
169
- * Under a production mode, each record whose evidence is written in a test file. It
170
- * holds nothing, and it is a test reference to its declaration, as a reference the
171
- * test file wrote would be.
172
- */
173
- readonly inTestFiles: readonly Exemption[];
174
- }
175
-
176
- /**
177
- * Every record one project's detectors found, one per piece of evidence, before the
178
- * mode decides which of them hold: what {@link heldRecords} reads. Classes run in the
179
- * vocabulary's order and a switched-off class runs not at all; evidence on a private
180
- * member the class's row says it cannot retain is dropped, whatever the detector found.
181
- */
182
- export function evidenceRecords<Brand>(
183
- input: DetectorInput<Brand>,
184
- detectors: Detectors,
185
- disabled: ReadonlySet<ExemptionClass>,
186
- ): readonly Exemption[] {
187
- const visibility = new Map(input.held.symbols.map((symbol) => [symbol.id, symbol.visibility]));
188
- const found: Exemption[] = [];
189
- for (const exemptionClass of TS_EXEMPTION_CLASSES) {
190
- const detect = detectors.get(exemptionClass);
191
- if (detect === undefined || disabled.has(exemptionClass)) {
192
- continue;
193
- }
194
- const allowed = typescriptVisibilityOf(exemptionClass);
195
- for (const evidence of detected(exemptionClass, detect, input)) {
196
- if (!retainable(allowed, visibility.get(evidence.id))) {
197
- continue;
198
- }
199
- found.push({
200
- id: evidence.id,
201
- class: exemptionClass,
202
- detail: evidence.detail,
203
- site: evidence.site,
204
- ...(evidence.whileLive === undefined ? {} : { whileLive: evidence.whileLive }),
205
- ...(evidence.implementing === undefined ? {} : { implementing: evidence.implementing }),
206
- ...(evidence.whileWritten === undefined ? {} : { whileWritten: evidence.whileWritten }),
207
- });
208
- }
209
- }
210
- return found;
211
- }
212
-
213
- /**
214
- * The records that hold given the declarations a write the mode counts stores into: one
215
- * that holds only while written holds where its declaration is among them.
216
- */
217
- export function writtenRecords(
218
- records: readonly Exemption[],
219
- written: ReadonlySet<string>,
220
- ): readonly Exemption[] {
221
- return records.flatMap(({ whileWritten, ...record }) =>
222
- whileWritten === undefined || written.has(record.id) ? [record] : [],
223
- );
224
- }
225
-
226
- /** The records that hold under one mode, beside the evidence a production mode found in a test file. */
227
- export function heldRecords(records: readonly Exemption[], holding: Holding): Detected {
228
- const found: Exemption[] = [];
229
- const inTestFiles: Exemption[] = [];
230
- for (const record of records) {
231
- if (holding.mode.production && holding.testFiles.has(record.site.path)) {
232
- inTestFiles.push(record);
233
- } else {
234
- found.push(record);
235
- }
236
- }
237
- return { records: exemptionsOf(found), inTestFiles };
238
- }
239
-
240
- /**
241
- * Records as a run holds them: ordered by site, then class, then declaration, and one
242
- * per declaration, class, detail and file it holds while live, the one at the first
243
- * site. A fact found at many sites, or by several projects, is one record.
244
- */
245
- export function exemptionsOf(found: readonly Exemption[]): readonly Exemption[] {
246
- const ordered = [...found].sort(
247
- (a, b) => byPosition(a.site, b.site) || bytewise(a.class, b.class) || bytewise(a.id, b.id),
248
- );
249
- const seen = new Set<string>();
250
- return ordered.filter((record) => {
251
- const fact = [record.id, record.class, record.detail, record.whileLive ?? ""].join("\u0000");
252
- if (seen.has(fact)) {
253
- return false;
254
- }
255
- seen.add(fact);
256
- return true;
257
- });
258
- }
259
-
260
- /** The records per declaration, each declaration's in the order given. */
261
- function byDeclaration(records: readonly Exemption[]): ReadonlyMap<string, readonly Exemption[]> {
262
- const grouped = new Map<string, Exemption[]>();
263
- for (const record of records) {
264
- const held = grouped.get(record.id);
265
- if (held === undefined) {
266
- grouped.set(record.id, [record]);
267
- } else {
268
- held.push(record);
269
- }
270
- }
271
- return grouped;
272
- }
273
-
274
- /**
275
- * One line per held-back declaration, in site order: its reference and position, then
276
- * each record's class, site and detail, all separated by tabs.
277
- */
278
- export function retainedLines(
279
- symbols: readonly InventorySymbol[],
280
- retained: readonly Exemption[],
281
- ): readonly string[] {
282
- const held = byDeclaration(retained);
283
- return symbols.flatMap((symbol) => {
284
- const records = held.get(symbol.id);
285
- if (records === undefined) {
286
- return [];
287
- }
288
- const fields = records.flatMap((record) => [
289
- record.class,
290
- positionKey(record.site),
291
- record.detail,
292
- ]);
293
- return [[symbol.ref, positionKey(symbol.position), ...fields].join("\t")];
294
- });
295
- }
package/src/value-flow.ts DELETED
@@ -1,104 +0,0 @@
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
-
8
- import {
9
- isArrayLiteralExpression,
10
- isArrowFunction,
11
- isAsExpression,
12
- isBinaryExpression,
13
- isCallExpression,
14
- isConditionalExpression,
15
- isIdentifier,
16
- isNewExpression,
17
- isParameterDeclaration,
18
- isParenthesizedExpression,
19
- isPropertyAssignment,
20
- isPropertyDeclaration,
21
- isReturnStatement,
22
- isSatisfiesExpression,
23
- isShorthandPropertyAssignment,
24
- isSpreadElement,
25
- isVariableDeclaration,
26
- SyntaxKind,
27
- type Expression,
28
- type Node,
29
- } from "@typescript/native/ast";
30
-
31
- /** The operators of an assignment whose right side is stored into the left side's type. */
32
- const STORES: ReadonlySet<SyntaxKind> = new Set([
33
- SyntaxKind.EqualsToken,
34
- SyntaxKind.QuestionQuestionEqualsToken,
35
- SyntaxKind.BarBarEqualsToken,
36
- SyntaxKind.AmpersandAmpersandEqualsToken,
37
- ]);
38
-
39
- /** Whether a declaration writes its type, so its initializer is read in that type's context. */
40
- function writesType(node: Node): boolean {
41
- return (node as { readonly type?: Node }).type !== undefined;
42
- }
43
-
44
- /**
45
- * The values one written expression hands on: the expression itself, or what it
46
- * evaluates to through parentheses, a conditional and the operators whose result is
47
- * one of their operands.
48
- */
49
- export function valuesOf(expression: Expression): readonly Expression[] {
50
- if (isParenthesizedExpression(expression)) {
51
- return valuesOf(expression.expression);
52
- }
53
- if (isConditionalExpression(expression)) {
54
- return [...valuesOf(expression.whenTrue), ...valuesOf(expression.whenFalse)];
55
- }
56
- if (isBinaryExpression(expression)) {
57
- const operator = expression.operatorToken.kind;
58
- if (operator === SyntaxKind.QuestionQuestionToken || operator === SyntaxKind.BarBarToken) {
59
- return [...valuesOf(expression.left), ...valuesOf(expression.right)];
60
- }
61
- if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.CommaToken) {
62
- return valuesOf(expression.right);
63
- }
64
- }
65
- return [expression];
66
- }
67
-
68
- /** The expressions one node hands to a position typed by its context. */
69
- export function handedOn(node: Node): readonly Expression[] {
70
- if (
71
- (isVariableDeclaration(node) || isPropertyDeclaration(node) || isParameterDeclaration(node)) &&
72
- node.initializer !== undefined &&
73
- writesType(node)
74
- ) {
75
- return [node.initializer];
76
- }
77
- if (isBinaryExpression(node) && STORES.has(node.operatorToken.kind)) {
78
- return [node.right];
79
- }
80
- if (isCallExpression(node) || isNewExpression(node)) {
81
- return (node.arguments ?? []).filter((argument) => !isSpreadElement(argument));
82
- }
83
- if (isReturnStatement(node)) {
84
- return node.expression === undefined ? [] : [node.expression];
85
- }
86
- if (isArrowFunction(node)) {
87
- return node.body.kind === SyntaxKind.Block ? [] : [node.body];
88
- }
89
- if (isArrayLiteralExpression(node)) {
90
- return node.elements.filter(
91
- (element) => !isSpreadElement(element) && element.kind !== SyntaxKind.OmittedExpression,
92
- );
93
- }
94
- if (isPropertyAssignment(node)) {
95
- return [node.initializer];
96
- }
97
- if (isShorthandPropertyAssignment(node)) {
98
- return isIdentifier(node.name) ? [node.name] : [];
99
- }
100
- if (isAsExpression(node) || isSatisfiesExpression(node)) {
101
- return [node.expression];
102
- }
103
- return [];
104
- }