@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,182 @@
1
+ /**
2
+ * The one walk of a project's own files every detector shares: its calls and the class
3
+ * `this` stands for at each, its classes, the values it hands to positions typed by
4
+ * their context, its functions' parameters, its decorated declarations and its
5
+ * assignments to properties. The walk reads syntax only; what a call invokes through a
6
+ * method of `Function.prototype` asks the checker, once per project, at the first
7
+ * detector that needs it.
8
+ */
9
+ import { isArrowFunction, isBinaryExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isElementAccessExpression, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyDeclaration, isSetAccessorDeclaration, isStringLiteral, isVariableDeclaration, ModifierFlags, SyntaxKind, } from "@typescript/native/ast";
10
+ import { handedOn, valuesOf } from "../index/values.js";
11
+ import { isParameterProperty, nodeKey } from "../inventory.js";
12
+ import { parenthesesRemoved } from "../unwrap.js";
13
+ import { decoratorsOf, invocations } from "./calls.js";
14
+ /** The property one assignment's left side names on its receiver, where a written key names it. */
15
+ function storeOf(node) {
16
+ if (!isBinaryExpression(node) || node.operatorToken.kind !== SyntaxKind.EqualsToken) {
17
+ return undefined;
18
+ }
19
+ const left = parenthesesRemoved(node.left);
20
+ if (isPropertyAccessExpression(left) && isIdentifier(left.name)) {
21
+ return { store: node, receiver: left.expression, key: left.name.text, value: node.right };
22
+ }
23
+ if (isElementAccessExpression(left) &&
24
+ (isStringLiteral(left.argumentExpression) ||
25
+ isNoSubstitutionTemplateLiteral(left.argumentExpression))) {
26
+ return {
27
+ store: node,
28
+ receiver: left.expression,
29
+ key: left.argumentExpression.text,
30
+ value: node.right,
31
+ };
32
+ }
33
+ return undefined;
34
+ }
35
+ /** Whether one node declares a static member, whose `this` is the class itself. */
36
+ function isStatic(node) {
37
+ const flags = node.modifierFlags ?? 0;
38
+ return (flags & ModifierFlags.Static) !== 0;
39
+ }
40
+ /**
41
+ * Whether one node opens a body whose `this` is no instance of the enclosing class: a
42
+ * function that is not an arrow, a static block, a static member, and a method or an
43
+ * accessor of an object literal, whose `this` is the literal.
44
+ */
45
+ function resetsThis(node) {
46
+ return (isFunctionDeclaration(node) ||
47
+ isFunctionExpression(node) ||
48
+ isClassStaticBlockDeclaration(node) ||
49
+ isStatic(node) ||
50
+ ((isMethodDeclaration(node) ||
51
+ isGetAccessorDeclaration(node) ||
52
+ isSetAccessorDeclaration(node)) &&
53
+ isObjectLiteralExpression(node.parent)));
54
+ }
55
+ /** The parameters and declaring node of one function-like declaration of the project. */
56
+ function functionOf(node) {
57
+ if (isFunctionDeclaration(node) ||
58
+ (isMethodDeclaration(node) && !isObjectLiteralExpression(node.parent))) {
59
+ return { declared: node, parameters: node.parameters };
60
+ }
61
+ if (isVariableDeclaration(node) &&
62
+ node.initializer !== undefined &&
63
+ (isArrowFunction(node.initializer) || isFunctionExpression(node.initializer))) {
64
+ return { declared: node, parameters: node.initializer.parameters };
65
+ }
66
+ return undefined;
67
+ }
68
+ function walk(project, held) {
69
+ const calls = [];
70
+ const classes = [];
71
+ const flows = [];
72
+ const parameters = [];
73
+ const decorated = [];
74
+ const stores = [];
75
+ const parameterProperties = new Set();
76
+ const initialized = new Set();
77
+ for (const file of project.ownSourceFiles()) {
78
+ const visit = (node, self) => {
79
+ const id = held.declarations.get(nodeKey(file, node));
80
+ let inner = self;
81
+ if (isClassDeclaration(node) || isClassExpression(node)) {
82
+ inner = id;
83
+ if (id !== undefined) {
84
+ classes.push({ id, node });
85
+ }
86
+ }
87
+ else if (resetsThis(node)) {
88
+ inner = undefined;
89
+ }
90
+ if (id !== undefined) {
91
+ if (isParameterProperty(node)) {
92
+ parameterProperties.add(id);
93
+ }
94
+ if (decoratorsOf(node).length > 0) {
95
+ decorated.push({ id, node });
96
+ }
97
+ if (isPropertyDeclaration(node) && node.initializer !== undefined) {
98
+ initialized.add(id);
99
+ }
100
+ }
101
+ if (node.kind === SyntaxKind.CallExpression) {
102
+ calls.push({ call: node, self: inner });
103
+ }
104
+ const store = storeOf(node);
105
+ if (store !== undefined) {
106
+ stores.push(store);
107
+ }
108
+ for (const written of handedOn(node)) {
109
+ for (const value of valuesOf(written)) {
110
+ flows.push({ value, self: value.kind === SyntaxKind.ThisKeyword ? inner : undefined });
111
+ }
112
+ }
113
+ const fn = functionOf(node);
114
+ const fnId = fn === undefined ? undefined : held.declarations.get(nodeKey(file, fn.declared));
115
+ if (fn !== undefined && fnId !== undefined) {
116
+ fn.parameters
117
+ .filter((parameter) => !(isIdentifier(parameter.name) && parameter.name.text === "this"))
118
+ .forEach((parameter, index) => {
119
+ if (isIdentifier(parameter.name)) {
120
+ parameters.push({
121
+ fn: fnId,
122
+ index,
123
+ rest: parameter.dotDotDotToken !== undefined,
124
+ name: parameter.name,
125
+ });
126
+ }
127
+ });
128
+ }
129
+ node.forEachChild((child) => {
130
+ visit(child, inner);
131
+ });
132
+ };
133
+ file.forEachChild((child) => {
134
+ visit(child, undefined);
135
+ });
136
+ }
137
+ return {
138
+ calls,
139
+ classes,
140
+ flows,
141
+ parameters,
142
+ decorated,
143
+ stores,
144
+ parameterProperties,
145
+ initialized,
146
+ };
147
+ }
148
+ /** The sites of one project's own files, each walk and each checker question made once. */
149
+ export function projectSites(project, held) {
150
+ let walked;
151
+ let invoked;
152
+ const sites = () => (walked ??= walk(project, held));
153
+ return {
154
+ get calls() {
155
+ return sites().calls;
156
+ },
157
+ get classes() {
158
+ return sites().classes;
159
+ },
160
+ get flows() {
161
+ return sites().flows;
162
+ },
163
+ get parameters() {
164
+ return sites().parameters;
165
+ },
166
+ get decorated() {
167
+ return sites().decorated;
168
+ },
169
+ get stores() {
170
+ return sites().stores;
171
+ },
172
+ get parameterProperties() {
173
+ return sites().parameterProperties;
174
+ },
175
+ get initialized() {
176
+ return sites().initialized;
177
+ },
178
+ get invocations() {
179
+ return (invoked ??= invocations(project, sites().calls.map((one) => one.call)));
180
+ },
181
+ };
182
+ }
@@ -11,13 +11,13 @@
11
11
  * instance members of classes and the members of interfaces and object types, on any
12
12
  * type, because nothing ties the name to a type.
13
13
  */
14
- import { readComponent } from "./component-files.js";
15
- import { memberNames } from "./member-names.js";
16
- import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.js";
17
- import { isComponentFile } from "./position.js";
18
- import { bytewise } from "./text/compare.js";
19
- import { filesBelow } from "./fs/tree.js";
20
- import { UsageError } from "./usage.js";
14
+ import { readComponent } from "../component-files.js";
15
+ import { memberNames } from "../member-names.js";
16
+ import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "../paths.js";
17
+ import { isComponentFile } from "../position.js";
18
+ import { bytewise } from "../text/compare.js";
19
+ import { filesBelow } from "../fs/tree.js";
20
+ import { UsageError } from "../usage.js";
21
21
  /** The kinds of declaration an action can name. */
22
22
  const READ_BY_A_TEMPLATE = new Set([
23
23
  "method",
@@ -2,14 +2,15 @@
2
2
  * The classes of the target a value of one type carries, and the members of the other
3
3
  * object types the target writes that it carries, as a serializer walking the value reaches
4
4
  * them: through constituents, array and tuple elements, the data members and index
5
- * signatures of the types the target writes, and the values `stored` gives for a type where
6
- * it erases them. The walk stops at `any`, `unknown`, a type parameter, a function and an
7
- * interface or class of another program, so an instance held only there is missed.
5
+ * signatures of the types the target writes, a class's polymorphic `this` type, and the values
6
+ * `stored` gives for a type where it erases them. The walk stops at `any`, `unknown`, any
7
+ * other type parameter, a function and an interface or class of another program, so an
8
+ * instance held only there is missed.
8
9
  */
9
10
  import { isAsExpression, isParenthesizedExpression, isSatisfiesExpression, isTypeAssertion, } from "@typescript/native/ast";
10
- import { ObjectFlags, SymbolFlags, } from "@typescript/native/sync";
11
- import { classNames, typesAt } from "./class-members.js";
12
- import { must } from "./query.js";
11
+ import { ObjectFlags, SymbolFlags, TypeFlags, } from "@typescript/native/sync";
12
+ import { classNames, typesAt } from "../class-members.js";
13
+ import { must } from "../query.js";
13
14
  /**
14
15
  * The object types whose members the walk reads whatever declares them. A reverse-mapped
15
16
  * type has no symbol: it is the shape the checker infers from an argument the target
@@ -19,7 +20,9 @@ const WRITTEN = ObjectFlags.ObjectLiteral | ObjectFlags.Mapped | ObjectFlags.Rev
19
20
  /** The symbols of an anonymous object type the target writes as a type. */
20
21
  const LITERAL_SYMBOLS = SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral;
21
22
  /** The two conversion methods a serializer or a formatter resolves on a value's own shape. */
22
- const CONVERSION_NAMES = new Set(["toJSON", "toString"]);
23
+ export const CONVERSION_NAMES = new Set(["toJSON", "toString"]);
24
+ /** The types that keep nothing of a value they are given, or that a decode gives a value. */
25
+ export const ERASED = TypeFlags.Any | TypeFlags.Unknown;
23
26
  /**
24
27
  * The members that can answer a conversion: a serializer reads the name with `[[Get]]`
25
28
  * and calls what it answers, so a getter answers it as a method does:
@@ -140,6 +143,10 @@ export function valueReach(project, held, stored = () => []) {
140
143
  if (type.isUnionType() || type.isIntersectionType()) {
141
144
  found = must(queries.constituents(type));
142
145
  }
146
+ else if (type.isTypeParameter() && type.isThisType === true) {
147
+ // A value of a class's polymorphic `this` type is an instance of the class.
148
+ found = [must(queries.constraintOf(type))].filter((one) => one !== undefined);
149
+ }
143
150
  else if (type.isObjectType()) {
144
151
  const target = targetOf(type);
145
152
  const isList = type.isTypeReference() &&
@@ -1,8 +1,21 @@
1
1
  import { CODE } from "../contract/contract.generated.js";
2
2
  import { KINDS } from "../kinds.js";
3
3
  import { parameterRef } from "../ref.js";
4
- import {} from "../intra-function-parts.js";
5
4
  import { reachedWithTheTests } from "../liveness/sweep.js";
5
+ /** The code each part kind is reported under, and what its finding says before the overlap clause. */
6
+ const PART_KINDS = {
7
+ parameter: {
8
+ code: CODE.unusedParameter,
9
+ says: (name) => `parameter ${name} is never read in the body`,
10
+ },
11
+ result: { code: CODE.unusedResult, says: () => "result is discarded at every call site" },
12
+ store: { code: CODE.deadStore, says: (name) => `value written to ${name} is never read` },
13
+ statement: { code: CODE.unreachableStatement, says: () => "statement is unreachable" },
14
+ case: {
15
+ code: CODE.unreachableCase,
16
+ says: (name) => `case ${name} is covered by an earlier case of the same switch`,
17
+ },
18
+ };
6
19
  /** A list of rules as a sentence names them. */
7
20
  function spelled(rules) {
8
21
  if (rules.length < 2) {
@@ -25,17 +38,24 @@ function overlapClause(overlap, part) {
25
38
  : `also reported by ${spelled(rules)}`;
26
39
  }
27
40
  /**
28
- * Whether one unread parameter is reported, and how. Of a free signature it is deleted. A
29
- * caller may pass a function used as a value more arguments than it declares, so there one
30
- * a read parameter follows is renamed to an underscore name, which the compilers read as
31
- * declared unread, and one no read parameter follows is deleted: kept where the call's
32
- * inference may read its annotation, at `probable` where nothing reads the annotation or
33
- * where it is a name a contextually typed object pattern binds.
41
+ * Whether one unread parameter is reported, and how (DS1401's precondition). Of a free
42
+ * signature it is deleted. Of a function used as a value, one a read parameter follows is
43
+ * renamed to an underscore name, and any other is deleted, kept where the call's inference
44
+ * may read its annotation. A deletion is `probable` where an unread annotation or a
45
+ * contextual pattern may matter to the caller, or where `length` counts the parameter of
46
+ * a function used as a value or of a root, whose holder may count it.
34
47
  */
35
- function parameterVerdict(part, free, freeButValued) {
48
+ function parameterVerdict(part, free, freeButValued, rooted) {
36
49
  const place = part.parameter;
50
+ const counted = place?.counted === true;
37
51
  if (place?.inValue !== true && free.has(part.declaration)) {
38
- return place?.annotated === undefined ? { fix: "delete", confidence: "certain" } : undefined;
52
+ if (place?.annotated !== undefined) {
53
+ return undefined;
54
+ }
55
+ return {
56
+ fix: "delete",
57
+ confidence: counted && rooted.has(part.declaration) ? "probable" : "certain",
58
+ };
39
59
  }
40
60
  const valued = place?.inValue === true || freeButValued.has(part.declaration);
41
61
  if (!valued) {
@@ -47,7 +67,7 @@ function parameterVerdict(part, free, freeButValued) {
47
67
  if (place?.annotated === "steers") {
48
68
  return undefined;
49
69
  }
50
- const unseen = place?.annotated === "free" || place?.contextualPattern === true;
70
+ const unseen = place?.annotated === "free" || place?.contextualPattern === true || counted;
51
71
  return { fix: "delete", confidence: unseen ? "probable" : "certain" };
52
72
  }
53
73
  /**
@@ -57,15 +77,12 @@ function parameterVerdict(part, free, freeButValued) {
57
77
  * exports to callers the run cannot see has no result to report. Every other part is
58
78
  * reported wherever no project found it in use.
59
79
  */
60
- function reported(input, part, callersUnknown) {
61
- if (part.used) {
62
- return false;
63
- }
80
+ function reported(input, part, rooted, callersUnknown) {
64
81
  const { free, freeButValued, discardedEverywhere } = input.intraFunction;
65
- switch (part.code) {
66
- case CODE.unusedParameter:
67
- return parameterVerdict(part, free, freeButValued) !== undefined;
68
- case CODE.unusedResult:
82
+ switch (part.kind) {
83
+ case "parameter":
84
+ return parameterVerdict(part, free, freeButValued, rooted) !== undefined;
85
+ case "result":
69
86
  return (free.has(part.declaration) &&
70
87
  discardedEverywhere.has(part.declaration) &&
71
88
  !callersUnknown.has(part.declaration));
@@ -93,43 +110,39 @@ export const intraFunction = (input) => {
93
110
  const testCode = union.test[at] === true || union.support[at] === true;
94
111
  return !testCode || (reached[at] === true && !testsOfDeadCode.has(id));
95
112
  };
96
- const unknown = new Set(union.rooted.flatMap((root) => {
113
+ const rooted = new Set(union.rooted.flatMap((root) => {
97
114
  const symbol = union.symbols[root.at];
98
115
  return symbol === undefined ? [] : [symbol.id];
99
116
  }));
117
+ const unknown = new Set(rooted);
100
118
  const { declared, loaded } = input.boundary.consumers;
101
119
  const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
102
- const published = (id) => {
103
- const symbol = byId.get(id);
104
- return (symbol !== undefined &&
105
- symbol.kind !== "file" &&
106
- (symbol.exported || published(symbol.parent)));
107
- };
108
120
  if (!closed) {
109
- for (const part of input.intraFunction.parts) {
110
- if (published(part.declaration)) {
111
- unknown.add(part.declaration);
112
- }
121
+ for (const id of input.intraFunction.published) {
122
+ unknown.add(id);
113
123
  }
114
124
  }
115
125
  const found = [];
116
126
  for (const part of input.intraFunction.parts) {
117
127
  const declaration = byId.get(part.declaration);
118
- const overlap = KINDS.get(part.code)?.overlap?.ts ?? [];
119
- if (declaration === undefined || !judged(declaration.id) || !reported(input, part, unknown)) {
128
+ const { code, says } = PART_KINDS[part.kind];
129
+ const overlap = KINDS.get(code)?.overlap?.ts ?? [];
130
+ if (declaration === undefined ||
131
+ !judged(declaration.id) ||
132
+ !reported(input, part, rooted, unknown)) {
120
133
  continue;
121
134
  }
122
- const verdict = part.code === CODE.unusedParameter
123
- ? parameterVerdict(part, input.intraFunction.free, input.intraFunction.freeButValued)
135
+ const verdict = part.kind === "parameter"
136
+ ? parameterVerdict(part, input.intraFunction.free, input.intraFunction.freeButValued, rooted)
124
137
  : undefined;
125
138
  const message = verdict?.fix === "rename"
126
- ? `${part.message}, and a read parameter follows it, so rename it to _${part.name}`
127
- : part.message;
139
+ ? `${says(part.name)}, and a read parameter follows it, so rename it to _${part.name}`
140
+ : says(part.name);
128
141
  found.push({
129
- code: part.code,
142
+ code,
130
143
  position: part.position,
131
144
  symbol: {
132
- ...(part.code === CODE.unusedParameter
145
+ ...(part.kind === "parameter"
133
146
  ? { ref: parameterRef(declaration.ref, part.name), holder: declaration.ref }
134
147
  : { ref: declaration.ref }),
135
148
  kind: part.kind,
@@ -15,7 +15,7 @@ import { writtenName } from "../platform-calls.js";
15
15
  import { isLoadCall } from "../roots/unseen-loads.js";
16
16
  import { keyNameOf } from "../symbol-keys.js";
17
17
  import { parenthesesRemoved } from "../unwrap.js";
18
- import { handedOn, valuesOf } from "../value-flow.js";
18
+ import { handedOn, valuesOf } from "./values.js";
19
19
  /**
20
20
  * The operators whose left side one assignment stores into. A compound assignment is
21
21
  * among them: where its value is discarded it reads its target only to write the result
@@ -0,0 +1,124 @@
1
+ /**
2
+ * What a written expression evaluates to, the one definition every pass shares: the
3
+ * values it can be, the values a call's argument carries inside the literals it writes,
4
+ * and the positions a program hands a value to that the checker types from their context.
5
+ */
6
+ import { isArrayLiteralExpression, isArrowFunction, isAsExpression, isBinaryExpression, isCallExpression, isConditionalExpression, isIdentifier, isNewExpression, isNonNullExpression, isObjectLiteralExpression, isOmittedExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isPropertyDeclaration, isReturnStatement, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
7
+ /** The operators of an assignment whose right side is stored into the left side's type. */
8
+ const STORES = new Set([
9
+ SyntaxKind.EqualsToken,
10
+ SyntaxKind.QuestionQuestionEqualsToken,
11
+ SyntaxKind.BarBarEqualsToken,
12
+ SyntaxKind.AmpersandAmpersandEqualsToken,
13
+ ]);
14
+ /** Whether a declaration writes its type, so its initializer is read in that type's context. */
15
+ function writesType(node) {
16
+ return node.type !== undefined;
17
+ }
18
+ /**
19
+ * The expressions one written value can evaluate to, each read alone because the checker
20
+ * reduces a union by subtype, so the type at the whole drops a class another branch's
21
+ * extends: the operand of a spread, of parentheses and of `!`, each branch of a
22
+ * conditional, each operand of `??` and `||`, and the right operand of `&&` and of a
23
+ * comma. The left operand of `&&` is its value only where it is falsy, which no object
24
+ * is. An assertion is a value of its own, whose type its reader decides.
25
+ */
26
+ export function valuesOf(expression) {
27
+ if (isSpreadElement(expression) || isParenthesizedExpression(expression)) {
28
+ return valuesOf(expression.expression);
29
+ }
30
+ if (isNonNullExpression(expression)) {
31
+ return valuesOf(expression.expression);
32
+ }
33
+ if (isConditionalExpression(expression)) {
34
+ return [...valuesOf(expression.whenTrue), ...valuesOf(expression.whenFalse)];
35
+ }
36
+ if (isBinaryExpression(expression)) {
37
+ const operator = expression.operatorToken.kind;
38
+ if (operator === SyntaxKind.QuestionQuestionToken || operator === SyntaxKind.BarBarToken) {
39
+ return [...valuesOf(expression.left), ...valuesOf(expression.right)];
40
+ }
41
+ if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.CommaToken) {
42
+ return valuesOf(expression.right);
43
+ }
44
+ }
45
+ return [expression];
46
+ }
47
+ /** The name node a callee, a decorator expression or a value resolves through, where it has one. */
48
+ export function nameOf(expression) {
49
+ if (isParenthesizedExpression(expression)) {
50
+ return nameOf(expression.expression);
51
+ }
52
+ if (isPropertyAccessExpression(expression)) {
53
+ return expression.name;
54
+ }
55
+ return isIdentifier(expression) ? expression : undefined;
56
+ }
57
+ /**
58
+ * The values one argument carries: each value it can be ({@link valuesOf}), or what an
59
+ * array or object literal it can be holds, at any depth, through spreads and shorthand
60
+ * properties. Each value is read inside every assertion around it.
61
+ */
62
+ export function carriedValues(argument) {
63
+ return valuesOf(argument).flatMap((value) => {
64
+ let at = value;
65
+ while (isAsExpression(at) || isSatisfiesExpression(at)) {
66
+ at = at.expression;
67
+ }
68
+ if (at !== value) {
69
+ return carriedValues(at);
70
+ }
71
+ if (isArrayLiteralExpression(value)) {
72
+ return value.elements.flatMap(carriedValues);
73
+ }
74
+ if (isObjectLiteralExpression(value)) {
75
+ return value.properties.flatMap((property) => {
76
+ if (isPropertyAssignment(property)) {
77
+ return carriedValues(property.initializer);
78
+ }
79
+ if (isSpreadAssignment(property)) {
80
+ return carriedValues(property.expression);
81
+ }
82
+ return isShorthandPropertyAssignment(property)
83
+ ? [{ site: property.name, name: property, shorthand: true }]
84
+ : [];
85
+ });
86
+ }
87
+ return isOmittedExpression(value)
88
+ ? []
89
+ : [{ site: value, name: nameOf(value), shorthand: false }];
90
+ });
91
+ }
92
+ /** The expressions one node hands to a position typed by its context. */
93
+ export function handedOn(node) {
94
+ if ((isVariableDeclaration(node) || isPropertyDeclaration(node) || isParameterDeclaration(node)) &&
95
+ node.initializer !== undefined &&
96
+ writesType(node)) {
97
+ return [node.initializer];
98
+ }
99
+ if (isBinaryExpression(node) && STORES.has(node.operatorToken.kind)) {
100
+ return [node.right];
101
+ }
102
+ if (isCallExpression(node) || isNewExpression(node)) {
103
+ return (node.arguments ?? []).filter((argument) => !isSpreadElement(argument));
104
+ }
105
+ if (isReturnStatement(node)) {
106
+ return node.expression === undefined ? [] : [node.expression];
107
+ }
108
+ if (isArrowFunction(node)) {
109
+ return node.body.kind === SyntaxKind.Block ? [] : [node.body];
110
+ }
111
+ if (isArrayLiteralExpression(node)) {
112
+ return node.elements.filter((element) => !isSpreadElement(element) && element.kind !== SyntaxKind.OmittedExpression);
113
+ }
114
+ if (isPropertyAssignment(node)) {
115
+ return [node.initializer];
116
+ }
117
+ if (isShorthandPropertyAssignment(node)) {
118
+ return isIdentifier(node.name) ? [node.name] : [];
119
+ }
120
+ if (isAsExpression(node) || isSatisfiesExpression(node)) {
121
+ return [node.expression];
122
+ }
123
+ return [];
124
+ }