@cplieger/deadset-ts 7.0.0-dev.1 → 7.0.0-dev.3

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 (113) hide show
  1. package/dist/src/analysis.js +95 -60
  2. package/dist/src/augmentations.js +14 -12
  3. package/dist/src/component-layer.js +1 -1
  4. package/dist/src/components.js +62 -20
  5. package/dist/src/configuration-files.js +230 -30
  6. package/dist/src/conformance-record.js +1 -1
  7. package/dist/src/contextual-uses.js +104 -13
  8. package/dist/src/convention-rows.js +10 -0
  9. package/dist/src/conventions.js +35 -1
  10. package/dist/src/dependencies.js +90 -32
  11. package/dist/src/discover.js +1 -1
  12. package/dist/src/edges.js +1 -1
  13. package/dist/src/element-access.js +341 -0
  14. package/dist/src/erased-stores.js +317 -0
  15. package/dist/src/exempt-classes.js +9 -22
  16. package/dist/src/exempt.js +3 -15
  17. package/dist/src/findings/completion.js +78 -16
  18. package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
  19. package/dist/src/findings/emitters.js +1 -5
  20. package/dist/src/findings/self-check.js +27 -10
  21. package/dist/src/findings-pass.js +6 -5
  22. package/dist/src/in-operands.js +30 -0
  23. package/dist/src/indexed-access.js +264 -119
  24. package/dist/src/intra-function-parts.js +25 -11
  25. package/dist/src/inventory.js +28 -12
  26. package/dist/src/keyof-comparisons.js +170 -0
  27. package/dist/src/kinds.js +17 -7
  28. package/dist/src/manifest.js +6 -16
  29. package/dist/src/matrix.js +20 -2
  30. package/dist/src/query.js +3 -0
  31. package/dist/src/references.js +14 -3
  32. package/dist/src/report.js +4 -1
  33. package/dist/src/reporters/sarif.js +4 -0
  34. package/dist/src/roots.js +0 -1
  35. package/dist/src/run.js +0 -2
  36. package/dist/src/schema.js +1 -1
  37. package/dist/src/scope.js +2 -2
  38. package/dist/src/serialization-contract.js +43 -14
  39. package/dist/src/session.js +12 -8
  40. package/dist/src/suppress-file.js +58 -6
  41. package/dist/src/suppress-inline.js +14 -5
  42. package/dist/src/suppress.js +64 -35
  43. package/dist/src/unimplemented-callees.js +312 -0
  44. package/dist/src/value-reach.js +7 -7
  45. package/dist/src/verbs/analyze.js +11 -9
  46. package/dist/src/workspace-programs.js +0 -3
  47. package/dist/src/workspace-resolution.js +0 -2
  48. package/dist/src/workspace.js +0 -3
  49. package/jsr.json +1 -1
  50. package/package.json +3 -2
  51. package/src/analysis.ts +145 -82
  52. package/src/augmentations.ts +14 -11
  53. package/src/calls.ts +3 -3
  54. package/src/component-files.ts +7 -7
  55. package/src/component-layer.ts +2 -2
  56. package/src/components.ts +104 -22
  57. package/src/config.ts +4 -4
  58. package/src/configuration-files.ts +250 -31
  59. package/src/conformance-record.ts +1 -1
  60. package/src/contextual-uses.ts +135 -17
  61. package/src/convention-rows.ts +23 -0
  62. package/src/conventions.ts +53 -1
  63. package/src/dependencies.ts +119 -38
  64. package/src/discover.ts +3 -3
  65. package/src/edges.ts +3 -3
  66. package/src/element-access.ts +446 -0
  67. package/src/entry-points.ts +1 -1
  68. package/src/erased-stores.ts +413 -0
  69. package/src/exempt-classes.ts +10 -24
  70. package/src/exempt.ts +5 -29
  71. package/src/file-facts.ts +3 -1
  72. package/src/finding.ts +3 -1
  73. package/src/findings/completion.ts +97 -21
  74. package/src/findings/dependencies-and-module-machinery.ts +1 -1
  75. package/src/findings/emitters.ts +4 -9
  76. package/src/findings/self-check.ts +31 -14
  77. package/src/findings-pass.ts +14 -7
  78. package/src/graph.ts +2 -2
  79. package/src/html-entries.ts +1 -1
  80. package/src/in-operands.ts +45 -0
  81. package/src/indexed-access.ts +303 -137
  82. package/src/intra-function-parts.ts +41 -16
  83. package/src/inventory.ts +37 -33
  84. package/src/json-document.ts +1 -1
  85. package/src/keyof-comparisons.ts +211 -0
  86. package/src/kinds.ts +20 -7
  87. package/src/manifest.ts +7 -22
  88. package/src/matrix.ts +34 -2
  89. package/src/namespace-objects.ts +1 -1
  90. package/src/pnpm-workspace.ts +1 -1
  91. package/src/query.ts +10 -0
  92. package/src/references.ts +30 -7
  93. package/src/report.ts +11 -5
  94. package/src/reporters/sarif.ts +4 -0
  95. package/src/roots.ts +0 -3
  96. package/src/run.ts +0 -2
  97. package/src/schema.ts +2 -2
  98. package/src/scope.ts +2 -2
  99. package/src/serialization-contract.ts +53 -14
  100. package/src/session.ts +34 -31
  101. package/src/stores.ts +1 -1
  102. package/src/suppress-file.ts +68 -5
  103. package/src/suppress-inline.ts +17 -7
  104. package/src/suppress.ts +79 -47
  105. package/src/template-field.ts +1 -1
  106. package/src/unimplemented-callees.ts +411 -0
  107. package/src/value-reach.ts +12 -8
  108. package/src/verbs/analyze.ts +15 -9
  109. package/src/verbs/verb.ts +1 -4
  110. package/src/workspace-programs.ts +2 -9
  111. package/src/workspace-resolution.ts +0 -3
  112. package/src/workspace-run.ts +1 -1
  113. package/src/workspace.ts +1 -9
@@ -0,0 +1,413 @@
1
+ /**
2
+ * The values the program stores in a slot of a container: a property, an index signature,
3
+ * an array element, and a variable. A serializer walking the container emits the value the
4
+ * slot holds, so it walks each stored value at its own static type, a subtype stored where
5
+ * the slot's type names a supertype, `object`, `unknown` or `any` included. A container is
6
+ * identified by the checker's type identity, so two containers of one type share what
7
+ * either stores. A store into an erased property of a type the analyzed program does not
8
+ * declare hands the value to the code that declares the type.
9
+ */
10
+
11
+ import {
12
+ isArrayLiteralExpression,
13
+ isBinaryExpression,
14
+ isCallExpression,
15
+ isElementAccessExpression,
16
+ isIdentifier,
17
+ isNoSubstitutionTemplateLiteral,
18
+ isNumericLiteral,
19
+ isObjectLiteralExpression,
20
+ isParenthesizedExpression,
21
+ isPropertyAccessExpression,
22
+ isPropertyAssignment,
23
+ isShorthandPropertyAssignment,
24
+ isSpreadElement,
25
+ isStringLiteral,
26
+ isVariableDeclaration,
27
+ SyntaxKind,
28
+ type Expression,
29
+ type Node,
30
+ } from "@typescript/native/unstable/ast";
31
+ import { SymbolFlags, TypeFlags, type Type } from "@typescript/native/unstable/sync";
32
+ import { must } from "./query.ts";
33
+ import type { ProjectView } from "./session.ts";
34
+
35
+ /** The types that keep nothing of the value they are given. */
36
+ const ERASED = TypeFlags.Any | TypeFlags.Unknown;
37
+
38
+ /** The array methods whose arguments become elements of the array they are called on. */
39
+ const ELEMENT_STORES: ReadonlySet<string> = new Set(["push", "unshift"]);
40
+
41
+ /** One step into a value: a property's name, or an element's place. */
42
+ type Step = string | number;
43
+
44
+ /**
45
+ * What a store puts in its slot: the value of `value`, or, where a destructuring pattern
46
+ * takes it apart, the part of it `path` names.
47
+ */
48
+ interface Stored {
49
+ readonly value: Expression;
50
+ readonly path: readonly Step[];
51
+ }
52
+
53
+ /** One store into a slot of a container: the property it names, or none for an element or an index. */
54
+ interface Store extends Stored {
55
+ /** The expression whose type is the container, or the literal whose contextual type is. */
56
+ readonly container: Expression;
57
+ readonly contextual: boolean;
58
+ readonly name: string | undefined;
59
+ }
60
+
61
+ /** A store into an erased property of a type no file of the project declares. */
62
+ interface OutsideStore {
63
+ readonly value: Expression;
64
+ readonly type: Type;
65
+ readonly property: string;
66
+ }
67
+
68
+ /** What the program stores where a type erases it. */
69
+ export interface ErasedStores {
70
+ /** The static types of the values stored in a slot of a value of one type. */
71
+ readonly inside: (type: Type) => readonly Type[];
72
+ /** The static types stored in the variable one identifier names, if it names one the program stores into. */
73
+ readonly ofVariable: (node: Node) => readonly Type[] | undefined;
74
+ readonly outside: readonly OutsideStore[];
75
+ }
76
+
77
+ /** The name an element access's literal index spells, or none. */
78
+ function literalKey(node: Expression): string | undefined {
79
+ return isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node) || isNumericLiteral(node)
80
+ ? node.text
81
+ : undefined;
82
+ }
83
+
84
+ function unwrapped(node: Expression): Expression {
85
+ return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
86
+ }
87
+
88
+ /**
89
+ * Whether a variable declaration's type can differ from what is stored in it: it carries an
90
+ * annotation, or it has neither an annotation nor an initializer, so its type evolves.
91
+ */
92
+ function declaresSlot(node: Node): boolean {
93
+ const held = node as { readonly type?: Node; readonly initializer?: Node };
94
+ return held.type !== undefined || held.initializer === undefined;
95
+ }
96
+
97
+ /** One target a destructuring assignment writes, and what it receives. */
98
+ interface Target extends Stored {
99
+ readonly target: Expression;
100
+ }
101
+
102
+ /** The element of an array literal at one place, where no spread before it moves the places. */
103
+ function elementAt(source: Expression, at: number): Expression | undefined {
104
+ if (!isArrayLiteralExpression(source)) {
105
+ return undefined;
106
+ }
107
+ const before = source.elements.slice(0, at + 1);
108
+ return before.some((one) => isSpreadElement(one)) ? undefined : source.elements[at];
109
+ }
110
+
111
+ /** The name a property spells, and none for a computed one. */
112
+ function propertyName(name: Node): string | undefined {
113
+ return isIdentifier(name) || isStringLiteral(name) || isNumericLiteral(name)
114
+ ? name.text
115
+ : undefined;
116
+ }
117
+
118
+ /** The name one property of an object literal spells and the expression it holds, or none. */
119
+ function propertyParts(
120
+ property: Node,
121
+ ): { readonly name: string; readonly value: Expression } | undefined {
122
+ if (isPropertyAssignment(property)) {
123
+ const name = propertyName(property.name);
124
+ return name === undefined ? undefined : { name, value: property.initializer };
125
+ }
126
+ if (isShorthandPropertyAssignment(property) && isIdentifier(property.name)) {
127
+ return { name: property.name.text, value: property.name };
128
+ }
129
+ return undefined;
130
+ }
131
+
132
+ /** The value an object literal gives one property, where it gives it by a plain or a shorthand property. */
133
+ function propertyAt(source: Expression, name: string): Expression | undefined {
134
+ if (!isObjectLiteralExpression(source)) {
135
+ return undefined;
136
+ }
137
+ for (const property of source.properties) {
138
+ const parts = propertyParts(property);
139
+ if (parts?.name === name) {
140
+ return parts.value;
141
+ }
142
+ }
143
+ return undefined;
144
+ }
145
+
146
+ /**
147
+ * Every target one destructuring pattern writes at any depth, each with the expression
148
+ * whose value it receives: the matching element or property of a literal on the right,
149
+ * else the right side with the path into it. A rest element copies, so it is no target
150
+ * here, and a default value is stored in its target too.
151
+ */
152
+ function patternTargets(pattern: Expression, from: Stored, out: Target[]): void {
153
+ const target = unwrapped(pattern);
154
+ const source = from.path.length === 0 ? unwrapped(from.value) : from.value;
155
+ const into = (element: Expression, step: Step, literal: Expression | undefined): void => {
156
+ patternTargets(
157
+ element,
158
+ literal === undefined
159
+ ? { value: from.value, path: [...from.path, step] }
160
+ : { value: literal, path: [] },
161
+ out,
162
+ );
163
+ };
164
+ if (isArrayLiteralExpression(target)) {
165
+ target.elements.forEach((element, at) => {
166
+ if (!isSpreadElement(element) && element.kind !== SyntaxKind.OmittedExpression) {
167
+ into(element, at, from.path.length === 0 ? elementAt(source, at) : undefined);
168
+ }
169
+ });
170
+ return;
171
+ }
172
+ if (isObjectLiteralExpression(target)) {
173
+ for (const property of target.properties) {
174
+ const parts = propertyParts(property);
175
+ if (parts !== undefined) {
176
+ into(
177
+ parts.value,
178
+ parts.name,
179
+ from.path.length === 0 ? propertyAt(source, parts.name) : undefined,
180
+ );
181
+ }
182
+ if (
183
+ parts !== undefined &&
184
+ isShorthandPropertyAssignment(property) &&
185
+ property.objectAssignmentInitializer !== undefined
186
+ ) {
187
+ out.push({ target: parts.value, value: property.objectAssignmentInitializer, path: [] });
188
+ }
189
+ }
190
+ return;
191
+ }
192
+ if (isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.EqualsToken) {
193
+ patternTargets(target.left, from, out);
194
+ out.push({ target: target.left, value: target.right, path: [] });
195
+ return;
196
+ }
197
+ out.push({ target, ...from });
198
+ }
199
+
200
+ /** Every store one project's own files write, and the stores into variables declared erased. */
201
+ export function erasedStores<Brand>(project: ProjectView<Brand>): ErasedStores {
202
+ const queries = project.queries;
203
+ const own = project.ownPaths();
204
+ const stores: Store[] = [];
205
+ const assigned: (Stored & { readonly target: Node })[] = [];
206
+ const slotVariables: Node[] = [];
207
+ for (const file of project.ownSourceFiles()) {
208
+ const visit = (node: Node): void => {
209
+ if (isObjectLiteralExpression(node)) {
210
+ for (const property of node.properties) {
211
+ if (isShorthandPropertyAssignment(property) && isIdentifier(property.name)) {
212
+ const { name } = property;
213
+ stores.push({
214
+ container: node,
215
+ contextual: true,
216
+ name: name.text,
217
+ value: name,
218
+ path: [],
219
+ });
220
+ } else if (
221
+ isPropertyAssignment(property) &&
222
+ (isIdentifier(property.name) || isStringLiteral(property.name))
223
+ ) {
224
+ const value = property.initializer;
225
+ stores.push({
226
+ container: node,
227
+ contextual: true,
228
+ name: property.name.text,
229
+ value,
230
+ path: [],
231
+ });
232
+ }
233
+ }
234
+ } else if (isArrayLiteralExpression(node)) {
235
+ for (const element of node.elements) {
236
+ if (!isSpreadElement(element)) {
237
+ stores.push({
238
+ container: node,
239
+ contextual: true,
240
+ name: undefined,
241
+ value: element,
242
+ path: [],
243
+ });
244
+ }
245
+ }
246
+ } else if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.EqualsToken) {
247
+ const targets: Target[] = [];
248
+ patternTargets(node.left, { value: node.right, path: [] }, targets);
249
+ for (const { target, value, path } of targets) {
250
+ if (isPropertyAccessExpression(target)) {
251
+ stores.push({
252
+ container: target.expression,
253
+ contextual: false,
254
+ name: target.name.text,
255
+ value,
256
+ path,
257
+ });
258
+ } else if (isElementAccessExpression(target)) {
259
+ stores.push({
260
+ container: target.expression,
261
+ contextual: false,
262
+ name: literalKey(target.argumentExpression),
263
+ value,
264
+ path,
265
+ });
266
+ } else if (isIdentifier(target)) {
267
+ assigned.push({ target, value, path });
268
+ }
269
+ }
270
+ } else if (
271
+ isCallExpression(node) &&
272
+ isPropertyAccessExpression(node.expression) &&
273
+ ELEMENT_STORES.has(node.expression.name.text)
274
+ ) {
275
+ for (const argument of node.arguments) {
276
+ stores.push({
277
+ container: node.expression.expression,
278
+ contextual: false,
279
+ name: undefined,
280
+ value: argument,
281
+ path: [],
282
+ });
283
+ }
284
+ } else if (isVariableDeclaration(node) && declaresSlot(node)) {
285
+ slotVariables.push(node);
286
+ if (node.initializer !== undefined) {
287
+ assigned.push({ target: node, value: node.initializer, path: [] });
288
+ }
289
+ }
290
+ node.forEachChild(visit);
291
+ };
292
+ file.forEachChild(visit);
293
+ }
294
+
295
+ const contextual = stores.filter((one) => one.contextual);
296
+ const contexts = queries.contextualTypes(contextual.map((one) => one.container)).map(must);
297
+ const containerOf = new Map<Store, Type | undefined>(
298
+ contextual.map((one, at) => [one, contexts[at]]),
299
+ );
300
+ const typed = stores.filter((one) => !one.contextual);
301
+ queries
302
+ .typesAt(typed.map((one) => one.container))
303
+ .map(must)
304
+ .forEach((type, at) => {
305
+ const one = typed[at];
306
+ if (one !== undefined) {
307
+ containerOf.set(one, type);
308
+ }
309
+ });
310
+
311
+ const slotTypes = new Map<string, Type | undefined>();
312
+ /** The type a slot of one object type holds: a property's, else the string index's, else an element's. */
313
+ const slotOf = (type: Type, name: string | undefined): Type | undefined => {
314
+ const key = `${String(type.id)}\u0000${name ?? ""}`;
315
+ if (slotTypes.has(key)) {
316
+ return slotTypes.get(key);
317
+ }
318
+ let found: Type | undefined;
319
+ const property =
320
+ name === undefined
321
+ ? undefined
322
+ : must(queries.propertiesOf(type)).find((one) => one.name === name);
323
+ if (property !== undefined) {
324
+ found =
325
+ (property.flags & SymbolFlags.Property) !== 0
326
+ ? must(queries.typeOfSymbol(property))
327
+ : undefined;
328
+ } else if (type.isTypeReference() && must(queries.isArray(type))) {
329
+ found = must(queries.typeArguments(type))[0];
330
+ } else {
331
+ found = must(queries.indexInfosOf(type))[0]?.valueType;
332
+ }
333
+ slotTypes.set(key, found);
334
+ return found;
335
+ };
336
+ const declaredOutside = (type: Type): boolean => {
337
+ const symbol = must(queries.symbolOfType(type));
338
+ return (
339
+ symbol !== undefined &&
340
+ symbol.declarations.length > 0 &&
341
+ symbol.declarations.every((handle) => !own.has(handle.path))
342
+ );
343
+ };
344
+
345
+ /** The type of the part of a stored value its path names, a step at a time. */
346
+ const partOf = (type: Type | undefined, path: readonly Step[]): Type | undefined =>
347
+ path.reduce<Type | undefined>(
348
+ (at, step) => (at === undefined ? undefined : slotOf(at, String(step))),
349
+ type,
350
+ );
351
+ const valueTypes = queries
352
+ .typesAt(stores.map((one) => one.value))
353
+ .map((type, at) => partOf(must(type), stores[at]?.path ?? []));
354
+ const inside = new Map<number, Type[]>();
355
+ const outside: OutsideStore[] = [];
356
+ stores.forEach((store, at) => {
357
+ const container = containerOf.get(store);
358
+ const value = valueTypes[at];
359
+ if (container === undefined || value === undefined) {
360
+ return;
361
+ }
362
+ const parts =
363
+ container.isUnionType() || container.isIntersectionType()
364
+ ? must(queries.constituents(container))
365
+ : [container];
366
+ for (const part of parts) {
367
+ if (!part.isObjectType()) {
368
+ continue;
369
+ }
370
+ const { name } = store;
371
+ const erased = name !== undefined && ((slotOf(part, name)?.flags ?? 0) & ERASED) !== 0;
372
+ if (erased && declaredOutside(part)) {
373
+ outside.push({ value: store.value, type: value, property: name });
374
+ } else {
375
+ inside.set(part.id, [...(inside.get(part.id) ?? []), value]);
376
+ }
377
+ }
378
+ });
379
+
380
+ const targets = assigned.map((one) =>
381
+ isVariableDeclaration(one.target) ? one.target.name : one.target,
382
+ );
383
+ const symbols = queries.symbolsAt(targets).map(must);
384
+ const slots = new Set(slotVariables);
385
+ const declarationOf = (symbol: (typeof symbols)[number]): Node | undefined => {
386
+ const handle = symbol?.valueDeclaration ?? symbol?.declarations[0];
387
+ return handle === undefined ? undefined : project.declarationAt(handle)?.node;
388
+ };
389
+ const assignedTypes = queries
390
+ .typesAt(assigned.map((one) => one.value))
391
+ .map((type, at) => partOf(must(type), assigned[at]?.path ?? []));
392
+ const byVariable = new Map<Node, Type[]>();
393
+ assigned.forEach((_one, at) => {
394
+ const declaration = declarationOf(symbols[at]);
395
+ const type = assignedTypes[at];
396
+ if (declaration !== undefined && slots.has(declaration) && type !== undefined) {
397
+ byVariable.set(declaration, [...(byVariable.get(declaration) ?? []), type]);
398
+ }
399
+ });
400
+
401
+ return {
402
+ inside: (type) => inside.get(type.id) ?? [],
403
+ ofVariable: (node) => {
404
+ if (byVariable.size === 0 || !isIdentifier(node)) {
405
+ return undefined;
406
+ }
407
+ const [symbol] = queries.symbolsAt([node]).map(must);
408
+ const declaration = declarationOf(symbol);
409
+ return declaration === undefined ? undefined : byVariable.get(declaration);
410
+ },
411
+ outside,
412
+ };
413
+ }
@@ -1,16 +1,12 @@
1
1
  /**
2
- * The exemption classes of the Contract's vocabulary, in its order: each class's name,
3
- * the languages it runs on, the confidence it is recorded at and, for a class that runs
4
- * on TypeScript, which private members it may retain.
5
- *
6
- * The rows are written out rather than read from the Contract at run time, for the
7
- * reason the fixed severity codes are, and a test holds them equal to the Contract
8
- * release this analyzer implements. A class name is a type, so a detector table or a
9
- * record naming a class the vocabulary does not hold, or one that runs on another
10
- * language only, does not compile.
2
+ * The exemption classes of the Contract's vocabulary, in its order: each class's name, its
3
+ * languages and, for a TypeScript class, which private members it may retain. The rows are
4
+ * written out, as the fixed severity codes are, and a test holds them equal to the Contract.
5
+ * A class name is a type, so a table or a record naming a class outside the vocabulary, or
6
+ * one of another language only, does not compile.
11
7
  */
12
8
 
13
- import type { Confidence, Language } from "./config.ts";
9
+ import type { Language } from "./config.ts";
14
10
 
15
11
  /**
16
12
  * Whether a class may retain a member declared with the `private` modifier, and one
@@ -27,7 +23,6 @@ export interface TypeScriptVisibility {
27
23
  interface ExemptionClassRow {
28
24
  readonly class: string;
29
25
  readonly languages: readonly Language[];
30
- readonly confidence: Confidence;
31
26
  /** Present exactly on a class that runs on TypeScript. */
32
27
  readonly typescriptVisibility?: TypeScriptVisibility;
33
28
  }
@@ -37,59 +32,50 @@ export const EXEMPTION_CLASSES = [
37
32
  {
38
33
  class: "interface-satisfaction",
39
34
  languages: ["go", "ts"],
40
- confidence: "certain",
41
35
  typescriptVisibility: { private: false, privateName: false },
42
36
  },
43
- { class: "encoding-reflection", languages: ["go"], confidence: "certain" },
44
- { class: "format-verb-contract", languages: ["go"], confidence: "certain" },
45
- { class: "errors-duck-typing", languages: ["go"], confidence: "certain" },
37
+ { class: "encoding-reflection", languages: ["go"] },
38
+ { class: "format-verb-contract", languages: ["go"] },
39
+ { class: "errors-duck-typing", languages: ["go"] },
46
40
  {
47
41
  class: "enum-group",
48
42
  languages: ["go", "ts"],
49
- confidence: "certain",
50
43
  typescriptVisibility: { private: false, privateName: false },
51
44
  },
52
45
  {
53
46
  class: "generated-file",
54
47
  languages: ["go", "ts"],
55
- confidence: "certain",
56
48
  typescriptVisibility: { private: true, privateName: false },
57
49
  },
58
- { class: "linkname-cgo-asm-plugin", languages: ["go"], confidence: "certain" },
50
+ { class: "linkname-cgo-asm-plugin", languages: ["go"] },
59
51
  {
60
52
  class: "template-field",
61
53
  languages: ["go", "ts"],
62
- confidence: "possible",
63
54
  typescriptVisibility: { private: true, privateName: false },
64
55
  },
65
56
  {
66
57
  class: "reflective-lookup",
67
58
  languages: ["go", "ts"],
68
- confidence: "possible",
69
59
  typescriptVisibility: { private: true, privateName: false },
70
60
  },
71
61
  {
72
62
  class: "decorator",
73
63
  languages: ["ts"],
74
- confidence: "certain",
75
64
  typescriptVisibility: { private: true, privateName: false },
76
65
  },
77
66
  {
78
67
  class: "injection-container",
79
68
  languages: ["ts"],
80
- confidence: "certain",
81
69
  typescriptVisibility: { private: true, privateName: false },
82
70
  },
83
71
  {
84
72
  class: "framework-lifecycle",
85
73
  languages: ["ts"],
86
- confidence: "certain",
87
74
  typescriptVisibility: { private: true, privateName: false },
88
75
  },
89
76
  {
90
77
  class: "serialization-contract",
91
78
  languages: ["ts"],
92
- confidence: "certain",
93
79
  typescriptVisibility: { private: true, privateName: false },
94
80
  },
95
81
  ] as const satisfies readonly ExemptionClassRow[];
package/src/exempt.ts CHANGED
@@ -64,9 +64,7 @@ export type Detector = <Brand>(input: DetectorInput<Brand>) => readonly Evidence
64
64
  export type Detectors = ReadonlyMap<TSExemptionClass, Detector>;
65
65
 
66
66
  /** What decides which records of one project stand. */
67
- export interface Holding {
68
- /** The classes the configuration switches off, whose detectors do not run. */
69
- readonly disabled: ReadonlySet<ExemptionClass>;
67
+ interface Holding {
70
68
  readonly mode: Mode;
71
69
  /** The paths the reference pass classified as test files. */
72
70
  readonly testFiles: ReadonlySet<string>;
@@ -154,23 +152,8 @@ function detected<Brand>(
154
152
  }
155
153
  }
156
154
 
157
- /**
158
- * The records one project's detectors found, each class in the vocabulary's order and
159
- * none of a switched-off class. Evidence on a private member the class's row says it
160
- * cannot retain is dropped first, whatever the detector found. Under a production mode
161
- * a record whose evidence is written in a test file holds nothing, as a reference a
162
- * test file makes is none there.
163
- */
164
- export function computeExemptions<Brand>(
165
- input: DetectorInput<Brand>,
166
- detectors: Detectors,
167
- holding: Holding,
168
- ): readonly Exemption[] {
169
- return detectExemptions(input, detectors, holding).records;
170
- }
171
-
172
155
  /** One project's records, and the evidence a production mode found in a test file. */
173
- export interface Detected {
156
+ interface Detected {
174
157
  readonly records: readonly Exemption[];
175
158
  /**
176
159
  * Under a production mode, each record whose evidence is written in a test file. It
@@ -180,18 +163,11 @@ export interface Detected {
180
163
  readonly inTestFiles: readonly Exemption[];
181
164
  }
182
165
 
183
- /** The records of {@link computeExemptions}, beside the test-file evidence it set aside. */
184
- export function detectExemptions<Brand>(
185
- input: DetectorInput<Brand>,
186
- detectors: Detectors,
187
- holding: Holding,
188
- ): Detected {
189
- return heldRecords(evidenceRecords(input, detectors, holding.disabled), holding);
190
- }
191
-
192
166
  /**
193
167
  * Every record one project's detectors found, one per piece of evidence, before the
194
- * mode decides which of them hold: what {@link heldRecords} reads.
168
+ * mode decides which of them hold: what {@link heldRecords} reads. Classes run in the
169
+ * vocabulary's order and a switched-off class runs not at all; evidence on a private
170
+ * member the class's row says it cannot retain is dropped, whatever the detector found.
195
171
  */
196
172
  export function evidenceRecords<Brand>(
197
173
  input: DetectorInput<Brand>,
package/src/file-facts.ts CHANGED
@@ -22,7 +22,7 @@ const DECLARATION_FILE = /\.d\.(?:[^./]+\.)?[mc]?ts$/u;
22
22
  const LINE_BREAK = /\r\n|[\n\r\u2028\u2029]/gu;
23
23
 
24
24
  /** One source file below the target root. */
25
- export interface TreeFile {
25
+ interface TreeFile {
26
26
  /** The path below the target root, with the solidus as separator. */
27
27
  readonly path: string;
28
28
  /** The stable reference of the file's module. */
@@ -49,6 +49,8 @@ export interface FileFacts {
49
49
  readonly heldByInclusion: ReadonlySet<string>;
50
50
  /** The paths below the target root of the generated files. */
51
51
  readonly generated?: ReadonlySet<string>;
52
+ /** The property names an `in` expression of a production file tests by a literal. */
53
+ readonly inOperands?: ReadonlySet<string>;
52
54
  }
53
55
 
54
56
  /** The line of the last character of one text, counted from one; an empty text is one line. */
package/src/finding.ts CHANGED
@@ -25,7 +25,7 @@ export interface FindingPosition {
25
25
  }
26
26
 
27
27
  /** What a finding is about. */
28
- export interface FindingSubject {
28
+ interface FindingSubject {
29
29
  /** The stable symbol reference, or for a configured root the root as written. */
30
30
  readonly ref: string;
31
31
  /** The subject kind, from the closed vocabulary of the finding schema. */
@@ -65,6 +65,8 @@ export interface FindingDetails {
65
65
  readonly entry?: SuppressionEntry;
66
66
  /** On the intra-function kinds: the external rules that report the same kind, as the vocabulary lists them. */
67
67
  readonly overlap?: readonly string[];
68
+ /** On a deletable finding: the dependencies whose last use deleting the subject removes. */
69
+ readonly removesLastUseOf?: readonly string[];
68
70
  }
69
71
 
70
72
  /** One suppression in the four-member form the ignore file and the baseline share. */