@eslint-react/var 5.20.0 → 5.20.2

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 (3) hide show
  1. package/dist/index.d.ts +128 -20
  2. package/dist/index.js +607 -27
  3. package/package.json +4 -4
package/dist/index.d.ts CHANGED
@@ -1,11 +1,120 @@
1
1
  import { TSESTree } from "@typescript-eslint/types";
2
2
  import { Scope } from "@typescript-eslint/scope-manager";
3
3
  import { RuleContext } from "@eslint-react/eslint";
4
+ //#region src/create-import-lookup.d.ts
5
+ /**
6
+ * A single local import binding, modeled after the specification's ImportEntry.
7
+ *
8
+ * Entries are the ground truth of the lookup: one is recorded per import
9
+ * specifier (or pre-registered builtin namespace), and every query on
10
+ * {@link ImportLookup} is answered from them.
11
+ */
12
+ export interface ImportEntry {
13
+ /**
14
+ * The import form.
15
+ *
16
+ * `"named"` covers both named imports and default imports; a default import
17
+ * is distinguished by its {@link ImportEntry.name} being `"default"`.
18
+ */
19
+ kind: "named" | "namespace";
20
+ /**
21
+ * The name of the imported export.
22
+ *
23
+ * `"default"` for default imports and `""` for namespace imports, which
24
+ * bind the module as a whole rather than a single export.
25
+ */
26
+ name: string;
27
+ /** The local name the import is bound to (the alias when one is used). */
28
+ local: string;
29
+ /** The module specifier as written in the source, e.g. `"react-dom/client"`. */
30
+ specifier: string;
31
+ }
32
+ /**
33
+ * A read-only index over the local import bindings of a program.
34
+ *
35
+ * All queries are O(1) or O(k) lookups over indexes built once at creation
36
+ * time, and results preserve source order.
37
+ */
38
+ export interface ImportLookup {
39
+ /**
40
+ * Look up the import entry a local name is bound to.
41
+ *
42
+ * @param local - The local binding name.
43
+ * @returns The matching entry, or `undefined` if the name is not imported.
44
+ */
45
+ binding(local: string): ImportEntry | undefined;
46
+ /**
47
+ * Look up all local bindings of a given imported export name.
48
+ *
49
+ * @param name - The imported export name (`"default"` for default imports).
50
+ * @returns The matching entries in source order, possibly empty.
51
+ */
52
+ bindingsOf(name: string): readonly ImportEntry[];
53
+ /** Return every import entry, in source order. */
54
+ all(): readonly ImportEntry[];
55
+ /**
56
+ * Check whether a local name is bound to a specific imported export.
57
+ *
58
+ * @param local - The local binding name.
59
+ * @param name - The imported export name.
60
+ */
61
+ has(local: string, name: string): boolean;
62
+ /**
63
+ * Check whether a local name is a default or namespace binding, i.e. one
64
+ * that refers to the module as a whole rather than to a single named export.
65
+ *
66
+ * @param local - The local binding name.
67
+ */
68
+ hasNamespace(local: string): boolean;
69
+ }
70
+ /** Options for {@link createImportLookup}. */
71
+ export interface ImportLookupOptions {
72
+ /**
73
+ * The base import source to track, e.g. `"react-dom"`.
74
+ *
75
+ * Subpath imports are grouped under their base, so `"react-dom/client"`
76
+ * matches a source of `"react-dom"`.
77
+ */
78
+ source: string;
79
+ /**
80
+ * Local names to pre-register as namespace bindings without an import
81
+ * statement, e.g. a `ReactDOM` global provided by the environment.
82
+ */
83
+ builtinNamespaces?: readonly string[];
84
+ }
85
+ /**
86
+ * Create a lookup of local import bindings from a source by scanning the
87
+ * top-level imports of a program.
88
+ *
89
+ * Entries are kept in source order, and both indexes (by local name and by
90
+ * imported name) are built during the same scan, so every query works
91
+ * immediately after creation. Aliases (`import { flushSync as fs }`) are
92
+ * handled naturally: the alias is the entry's local name.
93
+ *
94
+ * @param program - The program whose top-level import declarations to scan.
95
+ * @param options - The source to track and optional builtin namespaces.
96
+ * @returns An {@link ImportLookup} over the matching import bindings.
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const imports = createImportLookup(context.sourceCode.ast, { source: "react-dom" });
101
+ * return {
102
+ * CallExpression(node) {
103
+ * const callee = Extract.unwrap(node.callee);
104
+ * if (Check.isIdentifier(callee) && imports.has(callee.name, "flushSync")) {
105
+ * // ...
106
+ * }
107
+ * },
108
+ * };
109
+ * ```
110
+ */
111
+ export declare function createImportLookup(program: TSESTree.Program, options: ImportLookupOptions): ImportLookup;
112
+ //#endregion
4
113
  //#region src/get-require-expression-arguments.d.ts
5
114
  /**
6
115
  * Get the arguments of a require expression.
7
- * @param node The node to match.
8
- * @returns The require expression arguments or `null` if the node is not a require expression.
116
+ * @param node The node to check.
117
+ * @returns The require expression arguments, or `null` when the node is not a require expression.
9
118
  * @internal
10
119
  */
11
120
  export declare function getRequireExpressionArguments(node: TSESTree.Node): TSESTree.CallExpressionArgument[] | null;
@@ -13,7 +122,7 @@ export declare function getRequireExpressionArguments(node: TSESTree.Node): TSES
13
122
  //#region src/is-assignment-target-equal.d.ts
14
123
  /**
15
124
  * Check if two assignment targets are equal, either directly or by their values.
16
- * @param context The rule context.
125
+ * @param context The ESLint rule context.
17
126
  * @param a The first node to compare.
18
127
  * @param b The second node to compare.
19
128
  * @returns `true` if the assignment targets are equal.
@@ -38,18 +147,18 @@ export declare function isInitializedFromReact(name: string, initialScope: Scope
38
147
  * @param name The variable name.
39
148
  * @param initialScope The initial scope.
40
149
  * @param importSource Alternative import source of React Native (ex: "react-native-web").
41
- * @returns `true` if the variable is initialized from a React Native import.
150
+ * @returns `true` if the variable is initialized or derived from a React Native import.
42
151
  * @internal
43
152
  */
44
153
  export declare function isInitializedFromReactNative(name: string, initialScope: Scope, importSource?: string): boolean;
45
154
  //#endregion
46
155
  //#region src/is-value-equal.d.ts
47
156
  /**
48
- * Check if the value of a node equals the value of another node.
49
- * @param context The rule context.
157
+ * Check if two nodes have equal values.
158
+ * @param context The ESLint rule context.
50
159
  * @param a The first node to compare.
51
160
  * @param b The second node to compare.
52
- * @returns `true` if the node values are equal.
161
+ * @returns `true` if the two nodes have equal values.
53
162
  */
54
163
  export declare function isValueEqual(context: RuleContext, a: TSESTree.Node, b: TSESTree.Node): boolean;
55
164
  //#endregion
@@ -74,13 +183,13 @@ export declare function isValueEqual(context: RuleContext, a: TSESTree.Node, b:
74
183
  * | `Type` | type alias node | `null` |
75
184
  * | `ImplicitGlobalVariable` | any node | `null` |
76
185
  *
77
- * @param context The ESLint rule context used for scope lookup.
186
+ * @param context The ESLint rule context.
78
187
  * @param node The identifier to resolve.
79
188
  * @param options Optional settings:
80
189
  * - `at`: Index of the definition to resolve (default: `0` for the first definition).
81
190
  * - `localOnly`: If `true`, only consider variables declared in the same scope as the identifier
82
- * will miss variables declared in an outer scope). When `false` (default), traverse the scope
83
- * chain upward via `findVariable` so that references to outer-scope bindings are resolved
191
+ * (this will miss variables declared in an outer scope). When `false` (default), traverse the
192
+ * scope chain upward via `findVariable` so that references to outer-scope bindings are resolved
84
193
  * correctly.
85
194
  * @returns The resolved node, or `null` if the identifier cannot be resolved to a value node.
86
195
  */
@@ -90,15 +199,14 @@ export declare function resolve(context: RuleContext, node: TSESTree.Identifier,
90
199
  }>): TSESTree.Node | null;
91
200
  //#endregion
92
201
  //#region src/resolve-enclosing-assignment-target.d.ts
202
+ /** The possible assignment targets returned by {@link resolveEnclosingAssignmentTarget}. */
203
+ export type AssignmentTarget = ReturnType<typeof resolveEnclosingAssignmentTarget>;
93
204
  /**
94
- * Resolve the enclosing assignment target (variable, property, etc.) of a node.
95
- *
96
- * @param node The starting node.
97
- * @returns The enclosing assignment target node, or `null` if not found.
205
+ * Resolve the enclosing assignment target (variable, property, etc.) of the node.
206
+ * @param node The starting node for the upward search.
207
+ * @returns The enclosing assignment target node, or `null` when not found.
98
208
  */
99
209
  export declare function resolveEnclosingAssignmentTarget(node: TSESTree.Node): TSESTree.ArrayExpression | TSESTree.ArrayPattern | TSESTree.ArrowFunctionExpressionWithBlockBody | TSESTree.ArrowFunctionExpressionWithExpressionBody | TSESTree.AssignmentExpression | TSESTree.AwaitExpression | TSESTree.PrivateInExpression | TSESTree.SymmetricBinaryExpression | TSESTree.CallExpression | TSESTree.ChainExpression | TSESTree.ClassDeclarationWithOptionalName | TSESTree.ClassExpression | TSESTree.ConditionalExpression | TSESTree.FunctionDeclarationWithName | TSESTree.FunctionDeclarationWithOptionalName | TSESTree.FunctionExpression | TSESTree.Identifier | TSESTree.ImportExpression | TSESTree.JSXElement | TSESTree.JSXFragment | TSESTree.BigIntLiteral | TSESTree.BooleanLiteral | TSESTree.NullLiteral | TSESTree.NumberLiteral | TSESTree.RegExpLiteral | TSESTree.StringLiteral | TSESTree.LogicalExpression | TSESTree.MemberExpressionComputedName | TSESTree.MemberExpressionNonComputedName | TSESTree.MetaProperty | TSESTree.NewExpression | TSESTree.ObjectExpression | TSESTree.ObjectPattern | TSESTree.PrivateIdentifier | TSESTree.SequenceExpression | TSESTree.Super | TSESTree.TaggedTemplateExpression | TSESTree.TemplateLiteral | TSESTree.ThisExpression | TSESTree.TSAsExpression | TSESTree.TSDeclareFunctionNoDeclare | TSESTree.TSDeclareFunctionWithDeclare | TSESTree.TSEnumDeclaration | TSESTree.TSInstantiationExpression | TSESTree.TSInterfaceDeclaration | TSESTree.TSModuleDeclarationGlobal | TSESTree.TSModuleDeclarationModuleWithIdentifierId | TSESTree.TSModuleDeclarationModuleWithStringIdDeclared | TSESTree.TSModuleDeclarationModuleWithStringIdNotDeclared | TSESTree.TSModuleDeclarationNamespace | TSESTree.TSNonNullExpression | TSESTree.TSSatisfiesExpression | TSESTree.TSTypeAliasDeclaration | TSESTree.TSTypeAssertion | TSESTree.UnaryExpressionBitwiseNot | TSESTree.UnaryExpressionDelete | TSESTree.UnaryExpressionMinus | TSESTree.UnaryExpressionNot | TSESTree.UnaryExpressionPlus | TSESTree.UnaryExpressionTypeof | TSESTree.UnaryExpressionVoid | TSESTree.UpdateExpression | TSESTree.ConstDeclaration | TSESTree.LetOrVarDeclaredDeclaration | TSESTree.LetOrVarNonDeclaredDeclaration | TSESTree.UsingInForOfDeclaration | TSESTree.UsingInNormalContextDeclaration | TSESTree.YieldNoStarExpression | TSESTree.YieldStarExpression | null;
100
- /** The possible assignment targets returned by {@link resolveEnclosingAssignmentTarget}. */
101
- export type AssignmentTarget = ReturnType<typeof resolveEnclosingAssignmentTarget>;
102
210
  //#endregion
103
211
  //#region src/resolve-import-source.d.ts
104
212
  /**
@@ -106,7 +214,7 @@ export type AssignmentTarget = ReturnType<typeof resolveEnclosingAssignmentTarge
106
214
  * @param name The variable name.
107
215
  * @param initialScope The initial scope.
108
216
  * @param seen The set of already visited variable names (for cycle detection).
109
- * @returns The import source, or `null` if it cannot be resolved.
217
+ * @returns The import source, or `null` when it cannot be resolved.
110
218
  */
111
219
  export declare function resolveImportSource(name: string, initialScope: Scope, seen?: Set<string>): string | null;
112
220
  //#endregion
@@ -141,10 +249,10 @@ export type ObjectType = {
141
249
  reason?: string;
142
250
  };
143
251
  /**
144
- * Resolve the object type of the given node.
145
- * @param context The rule context.
252
+ * Resolve the object type of the node.
253
+ * @param context The ESLint rule context.
146
254
  * @param node The node to resolve.
147
- * @returns The object type of the node, or `null` if it cannot be resolved.
255
+ * @returns The object type of the node, or `null` when it cannot be resolved.
148
256
  */
149
257
  export declare function resolveObjectType(context: RuleContext, node: TSESTree.Node | null): ObjectType | null;
150
258
  //#endregion
package/dist/index.js CHANGED
@@ -1,14 +1,593 @@
1
- import { Check, Compare, Extract, Traverse } from "@eslint-react/ast";
2
1
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
2
+ import { Check, Compare, Extract, Traverse } from "@eslint-react/ast";
3
3
  import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
4
4
  import { DefinitionType } from "@typescript-eslint/scope-manager";
5
5
  import { P, isMatching } from "ts-pattern";
6
6
 
7
+ //#region ../../.pkgs/eff/dist/index.js
8
+ /**
9
+ * Applies a `pipe` method's variadic arguments to an initial value from left
10
+ * to right.
11
+ *
12
+ * **When to use**
13
+ *
14
+ * Use to implement a custom `.pipe(...)` method from JavaScript's `arguments`
15
+ * object.
16
+ *
17
+ * **Details**
18
+ *
19
+ * This helper is intended for implementing `Pipeable.pipe` methods that
20
+ * receive JavaScript's `arguments` object. With no functions it returns the
21
+ * original value; otherwise it feeds each result into the next function.
22
+ *
23
+ * **Example** (Implementing a pipe method)
24
+ *
25
+ * ```ts
26
+ * import { Pipeable } from "effect"
27
+ *
28
+ * class NumberBox {
29
+ * constructor(readonly value: number) {}
30
+ *
31
+ * pipe(..._fns: ReadonlyArray<(value: number) => number>): number {
32
+ * return Pipeable.pipeArguments(this.value, arguments) as number
33
+ * }
34
+ * }
35
+ *
36
+ * const result = new NumberBox(5).pipe(
37
+ * (n) => n + 2,
38
+ * (n) => n * 3
39
+ * )
40
+ * console.log(result) // 21
41
+ * ```
42
+ *
43
+ * @category combinators
44
+ * @since 2.0.0
45
+ */
46
+ const pipeArguments = (self, args) => {
47
+ switch (args.length) {
48
+ case 0: return self;
49
+ case 1: return args[0](self);
50
+ case 2: return args[1](args[0](self));
51
+ case 3: return args[2](args[1](args[0](self)));
52
+ case 4: return args[3](args[2](args[1](args[0](self))));
53
+ case 5: return args[4](args[3](args[2](args[1](args[0](self)))));
54
+ case 6: return args[5](args[4](args[3](args[2](args[1](args[0](self))))));
55
+ case 7: return args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))));
56
+ case 8: return args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self))))))));
57
+ case 9: return args[8](args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))))));
58
+ default: {
59
+ let ret = self;
60
+ for (let i = 0, len = args.length; i < len; i++) ret = args[i](ret);
61
+ return ret;
62
+ }
63
+ }
64
+ };
65
+ /**
66
+ * Reusable prototype that implements `Pipeable.pipe`.
67
+ *
68
+ * **When to use**
69
+ *
70
+ * Use when classes or object prototypes can reuse this value when they need the
71
+ * standard pipe implementation backed by `pipeArguments`.
72
+ *
73
+ * @category prototypes
74
+ * @since 3.15.0
75
+ */
76
+ const Prototype = { pipe() {
77
+ return pipeArguments(this, arguments);
78
+ } };
79
+ /**
80
+ * Provides a base constructor whose instances implement the standard `Pipeable.pipe`
81
+ * method.
82
+ *
83
+ * **When to use**
84
+ *
85
+ * Use when you need to define a class that supports Effect-style method
86
+ * chaining through `.pipe(...)`.
87
+ *
88
+ * @category constructors
89
+ * @since 3.15.0
90
+ */
91
+ const Class = (function() {
92
+ function PipeableBase() {}
93
+ PipeableBase.prototype = Prototype;
94
+ return PipeableBase;
95
+ })();
96
+ /**
97
+ * Provides small helpers for defining and reusing TypeScript functions.
98
+ *
99
+ * The main helpers are `pipe` and `flow` for left-to-right composition and
100
+ * `dual` for APIs that support both direct and pipe-friendly call styles. The
101
+ * module also contains small identity, constant, tuple, type-level, and
102
+ * memoization helpers used across the library.
103
+ *
104
+ * @since 2.0.0
105
+ */
106
+ /**
107
+ * Creates a function that can be called in data-first style or data-last
108
+ * (`pipe`-friendly) style.
109
+ *
110
+ * **When to use**
111
+ *
112
+ * Use to expose one implementation through both direct and `pipe`-friendly
113
+ * call styles.
114
+ *
115
+ * **Details**
116
+ *
117
+ * Pass either the arity of the uncurried function or a predicate that decides
118
+ * whether the current call is data-first. Arity is the common case. Use a
119
+ * predicate when optional arguments make arity ambiguous.
120
+ *
121
+ * **Example** (Selecting data-first or data-last style by arity)
122
+ *
123
+ * ```ts
124
+ * import { Function, pipe } from "effect"
125
+ *
126
+ * const sum = Function.dual<
127
+ * (that: number) => (self: number) => number,
128
+ * (self: number, that: number) => number
129
+ * >(2, (self, that) => self + that)
130
+ *
131
+ * console.log(sum(2, 3)) // 5
132
+ * console.log(pipe(2, sum(3))) // 5
133
+ * ```
134
+ *
135
+ * **Example** (Defining overloads with call signatures)
136
+ *
137
+ * ```ts
138
+ * import { Function, pipe } from "effect"
139
+ *
140
+ * const sum: {
141
+ * (that: number): (self: number) => number
142
+ * (self: number, that: number): number
143
+ * } = Function.dual(2, (self: number, that: number): number => self + that)
144
+ *
145
+ * console.log(sum(2, 3)) // 5
146
+ * console.log(pipe(2, sum(3))) // 5
147
+ * ```
148
+ *
149
+ * **Example** (Selecting data-first or data-last style with a predicate)
150
+ *
151
+ * ```ts
152
+ * import { Function, pipe } from "effect"
153
+ *
154
+ * const sum = Function.dual<
155
+ * (that: number) => (self: number) => number,
156
+ * (self: number, that: number) => number
157
+ * >(
158
+ * (args) => args.length === 2,
159
+ * (self, that) => self + that
160
+ * )
161
+ *
162
+ * console.log(sum(2, 3)) // 5
163
+ * console.log(pipe(2, sum(3))) // 5
164
+ * ```
165
+ *
166
+ * @category combinators
167
+ * @since 2.0.0
168
+ */
169
+ const dual = function(arity, body) {
170
+ if (typeof arity === "function") return function() {
171
+ return arity(arguments) ? body.apply(this, arguments) : ((self) => body(self, ...arguments));
172
+ };
173
+ switch (arity) {
174
+ case 0:
175
+ case 1: throw new RangeError(`Invalid arity ${arity}`);
176
+ case 2: return function(a, b) {
177
+ if (arguments.length >= 2) return body(a, b);
178
+ return function(self) {
179
+ return body(self, a);
180
+ };
181
+ };
182
+ case 3: return function(a, b, c) {
183
+ if (arguments.length >= 3) return body(a, b, c);
184
+ return function(self) {
185
+ return body(self, a, b);
186
+ };
187
+ };
188
+ default: return function() {
189
+ if (arguments.length >= arity) return body.apply(this, arguments);
190
+ const args = arguments;
191
+ return function(self) {
192
+ return body(self, ...args);
193
+ };
194
+ };
195
+ }
196
+ };
197
+ /**
198
+ * Returns its input argument unchanged.
199
+ *
200
+ * **When to use**
201
+ *
202
+ * Use to return a value unchanged where a function is required.
203
+ *
204
+ * **Example** (Returning the same value)
205
+ *
206
+ * ```ts
207
+ * import { identity } from "effect"
208
+ * import * as assert from "node:assert"
209
+ *
210
+ * assert.deepStrictEqual(identity(5), 5)
211
+ * ```
212
+ *
213
+ * @category combinators
214
+ * @since 2.0.0
215
+ */
216
+ const identity = (a) => a;
217
+ /**
218
+ * Returns the input value with a different static type.
219
+ *
220
+ * **When to use**
221
+ *
222
+ * Use when you need an explicit type-level cast and accept that the value is
223
+ * returned unchanged at runtime.
224
+ *
225
+ * **Gotchas**
226
+ *
227
+ * This is a type-level cast only; it performs no runtime validation or
228
+ * conversion.
229
+ *
230
+ * @see {@link satisfies} for checking assignability without changing the resulting type
231
+ *
232
+ * @category utility types
233
+ * @since 4.0.0
234
+ */
235
+ const cast = identity;
236
+ /**
237
+ * Creates a zero-argument function that always returns the provided value.
238
+ *
239
+ * **When to use**
240
+ *
241
+ * Use when you need a thunk or callback that returns the same value on every
242
+ * invocation.
243
+ *
244
+ * **Example** (Creating a constant thunk)
245
+ *
246
+ * ```ts
247
+ * import { Function } from "effect"
248
+ * import * as assert from "node:assert"
249
+ *
250
+ * const constNull = Function.constant(null)
251
+ *
252
+ * assert.deepStrictEqual(constNull(), null)
253
+ * assert.deepStrictEqual(constNull(), null)
254
+ * ```
255
+ *
256
+ * @category constructors
257
+ * @since 2.0.0
258
+ */
259
+ const constant = (value) => () => value;
260
+ /**
261
+ * Returns `true` when called.
262
+ *
263
+ * **When to use**
264
+ *
265
+ * Use when you need a thunk that returns `true` on every invocation.
266
+ *
267
+ * **Example** (Returning true from a thunk)
268
+ *
269
+ * ```ts
270
+ * import { Function } from "effect"
271
+ * import * as assert from "node:assert"
272
+ *
273
+ * assert.deepStrictEqual(Function.constTrue(), true)
274
+ * ```
275
+ *
276
+ * @category constants
277
+ * @since 2.0.0
278
+ */
279
+ const constTrue = constant(true);
280
+ /**
281
+ * Returns `false` when called.
282
+ *
283
+ * **When to use**
284
+ *
285
+ * Use when you need a thunk that returns `false` on every invocation.
286
+ *
287
+ * **Example** (Returning false from a thunk)
288
+ *
289
+ * ```ts
290
+ * import { Function } from "effect"
291
+ * import * as assert from "node:assert"
292
+ *
293
+ * assert.deepStrictEqual(Function.constFalse(), false)
294
+ * ```
295
+ *
296
+ * @category constants
297
+ * @since 2.0.0
298
+ */
299
+ const constFalse = constant(false);
300
+ /**
301
+ * Returns `null` when called.
302
+ *
303
+ * **When to use**
304
+ *
305
+ * Use when you need a thunk that returns `null` on every invocation.
306
+ *
307
+ * **Example** (Returning null from a thunk)
308
+ *
309
+ * ```ts
310
+ * import { Function } from "effect"
311
+ * import * as assert from "node:assert"
312
+ *
313
+ * assert.deepStrictEqual(Function.constNull(), null)
314
+ * ```
315
+ *
316
+ * @category constants
317
+ * @since 2.0.0
318
+ */
319
+ const constNull = constant(null);
320
+ /**
321
+ * Returns `undefined` when called.
322
+ *
323
+ * **When to use**
324
+ *
325
+ * Use when you need a thunk that returns `undefined` on every invocation.
326
+ *
327
+ * **Example** (Returning undefined from a thunk)
328
+ *
329
+ * ```ts
330
+ * import { Function } from "effect"
331
+ * import * as assert from "node:assert"
332
+ *
333
+ * assert.deepStrictEqual(Function.constUndefined(), undefined)
334
+ * ```
335
+ *
336
+ * @category constants
337
+ * @since 2.0.0
338
+ */
339
+ const constUndefined = constant(void 0);
340
+ /**
341
+ * Composes two functions, `ab` and `bc` into a single function that takes in an argument `a` of type `A` and returns a result of type `C`.
342
+ * The result is obtained by first applying the `ab` function to `a` and then applying the `bc` function to the result of `ab`.
343
+ *
344
+ * **When to use**
345
+ *
346
+ * Use to compose exactly two unary functions into a reusable unary function.
347
+ *
348
+ * **Example** (Composing two functions)
349
+ *
350
+ * ```ts
351
+ * import { Function } from "effect"
352
+ * import * as assert from "node:assert"
353
+ *
354
+ * const increment = (n: number) => n + 1
355
+ * const square = (n: number) => n * n
356
+ *
357
+ * assert.strictEqual(Function.compose(increment, square)(2), 9)
358
+ * ```
359
+ *
360
+ * @see {@link flow} for composing a left-to-right sequence of functions
361
+ * @see {@link pipe} for applying a value through a left-to-right sequence immediately
362
+ *
363
+ * @category combinators
364
+ * @since 2.0.0
365
+ */
366
+ const compose = dual(2, (ab, bc) => (a) => bc(ab(a)));
367
+ /**
368
+ * Marks an impossible branch by accepting a `never` value and returning any
369
+ * type.
370
+ *
371
+ * **When to use**
372
+ *
373
+ * Use when you need a return value in a branch that exhaustive checks prove
374
+ * cannot be reached.
375
+ *
376
+ * **Gotchas**
377
+ *
378
+ * Calling `absurd` throws, because a value of type `never` should be
379
+ * impossible at runtime.
380
+ *
381
+ * **Example** (Handling impossible values)
382
+ *
383
+ * ```ts
384
+ * import { absurd } from "effect"
385
+ *
386
+ * const handleNever = (value: never) => {
387
+ * return absurd(value) // This will throw an error if called
388
+ * }
389
+ * ```
390
+ *
391
+ * @category utility types
392
+ * @since 2.0.0
393
+ */
394
+ const absurd = (_) => {
395
+ throw new Error("Called `absurd` function which should be uncallable");
396
+ };
397
+ /**
398
+ * Creates a compile-time placeholder for a value of any type.
399
+ *
400
+ * **When to use**
401
+ *
402
+ * Use as a temporary typed placeholder while developing incomplete code.
403
+ *
404
+ * **Gotchas**
405
+ *
406
+ * `hole` is intended for temporary development use. If the placeholder is
407
+ * evaluated at runtime, it throws.
408
+ *
409
+ * **Example** (Creating a development placeholder)
410
+ *
411
+ * ```ts
412
+ * import { hole } from "effect"
413
+ *
414
+ * // Intentionally not called: `hole` throws if the placeholder is evaluated.
415
+ * const buildUser = (id: number): { readonly id: number; readonly name: string } => ({
416
+ * id,
417
+ * name: hole<string>()
418
+ * })
419
+ *
420
+ * console.log(typeof buildUser) // "function"
421
+ * ```
422
+ *
423
+ * @category utility types
424
+ * @since 2.0.0
425
+ */
426
+ const hole = cast(absurd);
427
+ function getOrInsertComputed(map, key, callback) {
428
+ if (map.has(key)) return map.get(key);
429
+ const value = callback(key);
430
+ map.set(key, value);
431
+ return value;
432
+ }
433
+ /**
434
+ * Drops the longest prefix of elements from an array that satisfy the given predicate.
435
+ *
436
+ * Supports both data-first and data-last (`pipe`-friendly) call styles.
437
+ *
438
+ * @param pred - The predicate to test each element with.
439
+ * @returns A new array without the matching prefix.
440
+ * @example
441
+ * ```ts
442
+ * import * as assert from "node:assert"
443
+ * import { dropWhile, pipe } from "@local/eff"
444
+ *
445
+ * // data-first
446
+ * assert.deepStrictEqual(dropWhile([1, 2, 3, 2, 1], (n: number) => n < 3), [3, 2, 1])
447
+ *
448
+ * // data-last
449
+ * assert.deepStrictEqual(pipe([1, 2, 3, 2, 1], dropWhile((n: number) => n < 3)), [3, 2, 1])
450
+ * ```
451
+ * @category array
452
+ */
453
+ const dropWhile = dual(2, (xs, pred) => {
454
+ const len = xs.length;
455
+ let idx = 0;
456
+ while (idx < len && pred(xs[idx])) idx++;
457
+ return xs.slice(idx);
458
+ });
459
+ /**
460
+ * Takes the longest prefix of elements from an array that satisfy the given predicate.
461
+ *
462
+ * Supports both data-first and data-last (`pipe`-friendly) call styles.
463
+ *
464
+ * @param pred - The predicate to test each element with.
465
+ * @returns A new array containing only the matching prefix.
466
+ * @example
467
+ * ```ts
468
+ * import * as assert from "node:assert"
469
+ * import { pipe, takeWhile } from "@local/eff"
470
+ *
471
+ * // data-first
472
+ * assert.deepStrictEqual(takeWhile([1, 2, 3, 2, 1], (n: number) => n < 3), [1, 2])
473
+ *
474
+ * // data-last
475
+ * assert.deepStrictEqual(pipe([1, 2, 3, 2, 1], takeWhile((n: number) => n < 3)), [1, 2])
476
+ * ```
477
+ * @category array
478
+ */
479
+ const takeWhile = dual(2, (xs, pred) => {
480
+ const len = xs.length;
481
+ let idx = 0;
482
+ while (idx < len && pred(xs[idx])) idx++;
483
+ return xs.slice(0, idx);
484
+ });
485
+
486
+ //#endregion
487
+ //#region src/create-import-lookup.ts
488
+ /**
489
+ * Create a lookup of local import bindings from a source by scanning the
490
+ * top-level imports of a program.
491
+ *
492
+ * Entries are kept in source order, and both indexes (by local name and by
493
+ * imported name) are built during the same scan, so every query works
494
+ * immediately after creation. Aliases (`import { flushSync as fs }`) are
495
+ * handled naturally: the alias is the entry's local name.
496
+ *
497
+ * @param program - The program whose top-level import declarations to scan.
498
+ * @param options - The source to track and optional builtin namespaces.
499
+ * @returns An {@link ImportLookup} over the matching import bindings.
500
+ *
501
+ * @example
502
+ * ```typescript
503
+ * const imports = createImportLookup(context.sourceCode.ast, { source: "react-dom" });
504
+ * return {
505
+ * CallExpression(node) {
506
+ * const callee = Extract.unwrap(node.callee);
507
+ * if (Check.isIdentifier(callee) && imports.has(callee.name, "flushSync")) {
508
+ * // ...
509
+ * }
510
+ * },
511
+ * };
512
+ * ```
513
+ */
514
+ function createImportLookup(program, options) {
515
+ const { source, builtinNamespaces = [] } = options;
516
+ const entries = [];
517
+ const byLocal = /* @__PURE__ */ new Map();
518
+ const byName = /* @__PURE__ */ new Map();
519
+ const add = (entry) => {
520
+ entries.push(entry);
521
+ byLocal.set(entry.local, entry);
522
+ getOrInsertComputed(byName, entry.name, () => []).push(entry);
523
+ };
524
+ for (const local of builtinNamespaces) add({
525
+ kind: "namespace",
526
+ name: "",
527
+ local,
528
+ specifier: source
529
+ });
530
+ for (const statement of program.body) {
531
+ if (statement.type !== AST_NODE_TYPES.ImportDeclaration) continue;
532
+ const specifier = statement.source.value;
533
+ const [baseSource = ""] = specifier.split("/");
534
+ if (baseSource !== source) continue;
535
+ for (const specifierNode of statement.specifiers) switch (specifierNode.type) {
536
+ case AST_NODE_TYPES.ImportSpecifier: {
537
+ const { imported, local } = specifierNode;
538
+ if (imported.type !== AST_NODE_TYPES.Identifier) continue;
539
+ add({
540
+ kind: "named",
541
+ name: imported.name,
542
+ local: local.name,
543
+ specifier
544
+ });
545
+ continue;
546
+ }
547
+ case AST_NODE_TYPES.ImportDefaultSpecifier:
548
+ add({
549
+ kind: "named",
550
+ name: "default",
551
+ local: specifierNode.local.name,
552
+ specifier
553
+ });
554
+ continue;
555
+ case AST_NODE_TYPES.ImportNamespaceSpecifier:
556
+ add({
557
+ kind: "namespace",
558
+ name: "",
559
+ local: specifierNode.local.name,
560
+ specifier
561
+ });
562
+ continue;
563
+ }
564
+ }
565
+ return {
566
+ binding(local) {
567
+ return byLocal.get(local);
568
+ },
569
+ bindingsOf(name) {
570
+ return byName.get(name) ?? [];
571
+ },
572
+ all() {
573
+ return entries;
574
+ },
575
+ has(local, name) {
576
+ return byLocal.get(local)?.name === name;
577
+ },
578
+ hasNamespace(local) {
579
+ const entry = byLocal.get(local);
580
+ return entry != null && (entry.kind === "namespace" || entry.name === "default");
581
+ }
582
+ };
583
+ }
584
+
585
+ //#endregion
7
586
  //#region src/get-require-expression-arguments.ts
8
587
  /**
9
588
  * Get the arguments of a require expression.
10
- * @param node The node to match.
11
- * @returns The require expression arguments or `null` if the node is not a require expression.
589
+ * @param node The node to check.
590
+ * @returns The require expression arguments, or `null` when the node is not a require expression.
12
591
  * @internal
13
592
  */
14
593
  function getRequireExpressionArguments(node) {
@@ -16,6 +595,7 @@ function getRequireExpressionArguments(node) {
16
595
  if (unwrapped.type === AST_NODE_TYPES.CallExpression) {
17
596
  const callee = Extract.unwrap(unwrapped.callee);
18
597
  if (Check.isIdentifier(callee, "require")) return unwrapped.arguments;
598
+ return null;
19
599
  }
20
600
  if (unwrapped.type === AST_NODE_TYPES.MemberExpression) return getRequireExpressionArguments(unwrapped.object);
21
601
  return null;
@@ -43,13 +623,13 @@ function getRequireExpressionArguments(node) {
43
623
  * | `Type` | type alias node | `null` |
44
624
  * | `ImplicitGlobalVariable` | any node | `null` |
45
625
  *
46
- * @param context The ESLint rule context used for scope lookup.
626
+ * @param context The ESLint rule context.
47
627
  * @param node The identifier to resolve.
48
628
  * @param options Optional settings:
49
629
  * - `at`: Index of the definition to resolve (default: `0` for the first definition).
50
630
  * - `localOnly`: If `true`, only consider variables declared in the same scope as the identifier
51
- * will miss variables declared in an outer scope). When `false` (default), traverse the scope
52
- * chain upward via `findVariable` so that references to outer-scope bindings are resolved
631
+ * (this will miss variables declared in an outer scope). When `false` (default), traverse the
632
+ * scope chain upward via `findVariable` so that references to outer-scope bindings are resolved
53
633
  * correctly.
54
634
  * @returns The resolved node, or `null` if the identifier cannot be resolved to a value node.
55
635
  */
@@ -90,11 +670,11 @@ const thisBlockTypes = [
90
670
  AST_NODE_TYPES.Program
91
671
  ];
92
672
  /**
93
- * Check if the value of a node equals the value of another node.
94
- * @param context The rule context.
673
+ * Check if two nodes have equal values.
674
+ * @param context The ESLint rule context.
95
675
  * @param a The first node to compare.
96
676
  * @param b The second node to compare.
97
- * @returns `true` if the node values are equal.
677
+ * @returns `true` if the two nodes have equal values.
98
678
  */
99
679
  function isValueEqual(context, a, b) {
100
680
  a = Check.isTypeExpression(a) ? Extract.unwrap(a) : a;
@@ -153,7 +733,7 @@ function isValueEqual(context, a, b) {
153
733
  //#region src/is-assignment-target-equal.ts
154
734
  /**
155
735
  * Check if two assignment targets are equal, either directly or by their values.
156
- * @param context The rule context.
736
+ * @param context The ESLint rule context.
157
737
  * @param a The first node to compare.
158
738
  * @param b The second node to compare.
159
739
  * @returns `true` if the assignment targets are equal.
@@ -170,7 +750,7 @@ function isAssignmentTargetEqual(context, a, b) {
170
750
  * @param name The variable name.
171
751
  * @param initialScope The initial scope.
172
752
  * @param seen The set of already visited variable names (for cycle detection).
173
- * @returns The import source, or `null` if it cannot be resolved.
753
+ * @returns The import source, or `null` when it cannot be resolved.
174
754
  */
175
755
  function resolveImportSource(name, initialScope, seen = /* @__PURE__ */ new Set()) {
176
756
  if (seen.has(name)) return null;
@@ -214,7 +794,7 @@ function isInitializedFromReact(name, initialScope, importSource = "react") {
214
794
  * @param name The variable name.
215
795
  * @param initialScope The initial scope.
216
796
  * @param importSource Alternative import source of React Native (ex: "react-native-web").
217
- * @returns `true` if the variable is initialized from a React Native import.
797
+ * @returns `true` if the variable is initialized or derived from a React Native import.
218
798
  * @internal
219
799
  */
220
800
  function isInitializedFromReactNative(name, initialScope, importSource = "react-native") {
@@ -228,18 +808,18 @@ function isInitializedFromReactNative(name, initialScope, importSource = "react-
228
808
  //#endregion
229
809
  //#region src/resolve-enclosing-assignment-target.ts
230
810
  /**
231
- * Resolve the enclosing assignment target (variable, property, etc.) of a node.
232
- *
233
- * @param node The starting node.
234
- * @returns The enclosing assignment target node, or `null` if not found.
811
+ * Resolve the enclosing assignment target (variable, property, etc.) of the node.
812
+ * @param node The starting node for the upward search.
813
+ * @returns The enclosing assignment target node, or `null` when not found.
235
814
  */
236
815
  function resolveEnclosingAssignmentTarget(node) {
237
- switch (true) {
238
- case node.type === AST_NODE_TYPES.VariableDeclarator: return node.id;
239
- case node.type === AST_NODE_TYPES.AssignmentExpression: return node.left;
240
- case node.type === AST_NODE_TYPES.PropertyDefinition: return node.key;
241
- case node.type === AST_NODE_TYPES.ExportDefaultDeclaration: return node.declaration;
242
- case node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program: return null;
816
+ switch (node.type) {
817
+ case AST_NODE_TYPES.VariableDeclarator: return node.id;
818
+ case AST_NODE_TYPES.AssignmentExpression: return node.left;
819
+ case AST_NODE_TYPES.PropertyDefinition: return node.key;
820
+ case AST_NODE_TYPES.ExportDefaultDeclaration: return node.declaration;
821
+ case AST_NODE_TYPES.BlockStatement:
822
+ case AST_NODE_TYPES.Program: return null;
243
823
  default: return resolveEnclosingAssignmentTarget(node.parent);
244
824
  }
245
825
  }
@@ -247,10 +827,10 @@ function resolveEnclosingAssignmentTarget(node) {
247
827
  //#endregion
248
828
  //#region src/resolve-object-type.ts
249
829
  /**
250
- * Resolve the object type of the given node.
251
- * @param context The rule context.
830
+ * Resolve the object type of the node.
831
+ * @param context The ESLint rule context.
252
832
  * @param node The node to resolve.
253
- * @returns The object type of the node, or `null` if it cannot be resolved.
833
+ * @returns The object type of the node, or `null` when it cannot be resolved.
254
834
  */
255
835
  function resolveObjectType(context, node) {
256
836
  if (node == null) return null;
@@ -305,7 +885,7 @@ function resolveObjectType(context, node) {
305
885
  case AST_NODE_TYPES.ConditionalExpression: return resolveObjectType(context, node.consequent) ?? resolveObjectType(context, node.alternate);
306
886
  case AST_NODE_TYPES.SequenceExpression:
307
887
  if (node.expressions.length === 0) return null;
308
- return resolveObjectType(context, node.expressions[node.expressions.length - 1] ?? null);
888
+ return resolveObjectType(context, node.expressions.at(-1) ?? null);
309
889
  case AST_NODE_TYPES.CallExpression: {
310
890
  const callee = Extract.unwrap(node.callee);
311
891
  switch (true) {
@@ -354,4 +934,4 @@ function resolveObjectType(context, node) {
354
934
  }
355
935
 
356
936
  //#endregion
357
- export { getRequireExpressionArguments, isAssignmentTargetEqual, isInitializedFromReact, isInitializedFromReactNative, isValueEqual, resolve, resolveEnclosingAssignmentTarget, resolveImportSource, resolveObjectType };
937
+ export { createImportLookup, getRequireExpressionArguments, isAssignmentTargetEqual, isInitializedFromReact, isInitializedFromReactNative, isValueEqual, resolve, resolveEnclosingAssignmentTarget, resolveImportSource, resolveObjectType };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-react/var",
3
- "version": "5.20.0",
3
+ "version": "5.20.2",
4
4
  "description": "ESLint React's TSESTree AST utility module for static analysis of variables.",
5
5
  "homepage": "https://github.com/Rel1cx/eslint-react",
6
6
  "bugs": {
@@ -29,8 +29,8 @@
29
29
  "dist"
30
30
  ],
31
31
  "dependencies": {
32
- "@eslint-react/ast": "5.20.0",
33
- "@eslint-react/eslint": "5.20.0",
32
+ "@eslint-react/ast": "5.20.2",
33
+ "@eslint-react/eslint": "5.20.2",
34
34
  "@typescript-eslint/scope-manager": "^8.70.0",
35
35
  "@typescript-eslint/types": "^8.70.0",
36
36
  "@typescript-eslint/utils": "^8.70.0",
@@ -42,7 +42,7 @@
42
42
  "@local/testkit": "0.0.0",
43
43
  "@typescript-eslint/parser": "^8.70.0",
44
44
  "@typescript-eslint/typescript-estree": "^8.70.0",
45
- "eslint": "^10.10.0",
45
+ "eslint": "^10.11.0",
46
46
  "tsdown": "^0.23.0",
47
47
  "typescript": "6.0.3",
48
48
  "vitest": "^5.0.1"