@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
package/src/resolve.ts CHANGED
@@ -262,18 +262,19 @@ function readStrings<T extends string>(
262
262
  );
263
263
  }
264
264
  const seen = new Set<string>();
265
- for (const entry of value as unknown[]) {
265
+ for (const [index, entry] of (value as unknown[]).entries()) {
266
+ const at = `${path}[${String(index)}]`;
266
267
  if (typeof entry !== "string") {
267
- throw malformed(label, path, "holds an entry that is not a string");
268
+ throw malformed(label, at, "is not a string");
268
269
  }
269
270
  if (entry === "") {
270
- throw malformed(label, path, "holds an empty entry");
271
+ throw malformed(label, at, "is empty");
271
272
  }
272
273
  if (seen.has(entry)) {
273
- throw malformed(label, path, `names ${JSON.stringify(entry)} twice`);
274
+ throw malformed(label, at, `names ${JSON.stringify(entry)} again`);
274
275
  }
275
276
  if (allowed !== undefined && !(allowed as readonly string[]).includes(entry)) {
276
- throw malformed(label, path, `${JSON.stringify(entry)} is not one of ${spell(allowed)}`);
277
+ throw malformed(label, at, `${JSON.stringify(entry)} is not one of ${spell(allowed)}`);
277
278
  }
278
279
  seen.add(entry);
279
280
  }
@@ -288,12 +289,12 @@ function readComponentExtensions(
288
289
  label: string,
289
290
  ): readonly string[] | undefined {
290
291
  const extensions = readStrings(ts, "component_extensions", "ts.component_extensions", label, 0);
291
- const wrong = extensions?.find((one) => !COMPONENT_EXTENSION.test(one));
292
- if (wrong !== undefined) {
292
+ const wrong = extensions?.findIndex((one) => !COMPONENT_EXTENSION.test(one)) ?? -1;
293
+ if (extensions !== undefined && wrong !== -1) {
293
294
  throw malformed(
294
295
  label,
295
- "ts.component_extensions",
296
- `${JSON.stringify(wrong)} is not a full stop followed by letters and digits`,
296
+ `ts.component_extensions[${String(wrong)}]`,
297
+ `${JSON.stringify(extensions[wrong])} is not a full stop followed by letters and digits`,
297
298
  );
298
299
  }
299
300
  return extensions;
@@ -307,12 +308,12 @@ function readDisabledConventions(
307
308
  label: string,
308
309
  ): readonly string[] | undefined {
309
310
  const names = readStrings(ts, "disabled_conventions", "ts.disabled_conventions", label, 0);
310
- const unknown = names?.find((one) => !CONVENTION_NAMES.has(one));
311
- if (unknown !== undefined) {
311
+ const unknown = names?.findIndex((one) => !CONVENTION_NAMES.has(one)) ?? -1;
312
+ if (names !== undefined && unknown !== -1) {
312
313
  throw malformed(
313
314
  label,
314
- "ts.disabled_conventions",
315
- `${JSON.stringify(unknown)} names no convention row this analyzer carries`,
315
+ `ts.disabled_conventions[${String(unknown)}]`,
316
+ `${JSON.stringify(names[unknown])} names no convention row this analyzer carries`,
316
317
  );
317
318
  }
318
319
  return names;
package/src/roots.ts CHANGED
@@ -10,6 +10,7 @@
10
10
 
11
11
  import type { SourceFile } from "@typescript/native/unstable/ast";
12
12
  import { aliasChains } from "./alias-chain.ts";
13
+ import type { ParseFile } from "./configuration-files.ts";
13
14
  import { conventionEntries, type Conventions } from "./conventions.ts";
14
15
  import { entryPoints, type EntryRule } from "./entry-points.ts";
15
16
  import { globExpression } from "./glob.ts";
@@ -112,6 +113,8 @@ export interface RootOptions {
112
113
  readonly conventions?: Conventions | undefined;
113
114
  /** `ts.test_files` from the resolved configuration. */
114
115
  readonly testFiles: readonly string[];
116
+ /** The parser the configuration files no program holds are read with, where they are read. */
117
+ readonly parse?: ParseFile | undefined;
115
118
  /** Whether the target is a library, so a consumer outside it reaches its published API. */
116
119
  readonly publishedAPI: boolean;
117
120
  }
@@ -268,7 +271,14 @@ export function roots<Brand>(
268
271
  : conventionEntries(options.conventions, files)) {
269
272
  enter(file, "convention", row, true);
270
273
  }
271
- for (const point of entryPoints(project, files, options.testFiles, options.host, targetRoot)) {
274
+ for (const point of entryPoints(
275
+ project,
276
+ files,
277
+ options.testFiles,
278
+ options.host,
279
+ targetRoot,
280
+ options.parse,
281
+ )) {
272
282
  enter(point.file, point.rule, point.source, point.exports);
273
283
  }
274
284
  for (const file of files.byPath.values()) {
package/src/stores.ts CHANGED
@@ -22,6 +22,8 @@ export interface Stores {
22
22
  * configuration. A production mode counts none a test file made.
23
23
  */
24
24
  readonly references: readonly Reference[];
25
+ /** The declarations a test file reads, which a production mode counts no read of. */
26
+ readonly readInTests: ReadonlySet<string>;
25
27
  /**
26
28
  * The class members declared by a getter or a setter. A store into one runs the
27
29
  * setter, which is code rather than state.
@@ -71,6 +73,13 @@ export function storesOf(
71
73
  references: per.flatMap((one) =>
72
74
  one.references.filter((reference) => !mode.production || !reference.test),
73
75
  ),
76
+ readInTests: new Set(
77
+ per.flatMap((one) =>
78
+ one.references
79
+ .filter((reference) => reference.test && reference.use !== "write")
80
+ .map((reference) => reference.to),
81
+ ),
82
+ ),
74
83
  accessors: new Set(per.flatMap((one) => one.accessors)),
75
84
  exempt: new Set(exempt.map((record) => record.id)),
76
85
  };
@@ -0,0 +1,104 @@
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/unstable/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
+ }
package/src/version.ts CHANGED
@@ -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";