@eslint-react/core 5.24.0 → 5.24.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.
- package/dist/index.js +104 -1020
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -7,11 +7,6 @@ import { P, isMatching, match } from "ts-pattern";
|
|
|
7
7
|
import ts from "typescript";
|
|
8
8
|
|
|
9
9
|
//#region src/api.ts
|
|
10
|
-
/**
|
|
11
|
-
* Check if the node is a React API identifier or member expression.
|
|
12
|
-
* @param api The React API name to check against (ex: "useState", "React.memo").
|
|
13
|
-
* @returns A predicate function to check if a node matches the API.
|
|
14
|
-
*/
|
|
15
10
|
function isAPI(api) {
|
|
16
11
|
const func = (context, node) => {
|
|
17
12
|
if (node == null) return false;
|
|
@@ -25,11 +20,6 @@ function isAPI(api) {
|
|
|
25
20
|
}
|
|
26
21
|
return dual;
|
|
27
22
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Check if the node is a call expression to a specific React API.
|
|
30
|
-
* @param api The React API name to check against.
|
|
31
|
-
* @returns A predicate function to check if a node is a call to the API.
|
|
32
|
-
*/
|
|
33
23
|
function isAPICall(api) {
|
|
34
24
|
const func = (context, node) => {
|
|
35
25
|
if (node == null) return false;
|
|
@@ -41,157 +31,181 @@ function isAPICall(api) {
|
|
|
41
31
|
}
|
|
42
32
|
return dual;
|
|
43
33
|
}
|
|
44
|
-
/** Check if the node is a React `captureOwnerStack` API identifier or member expression. */
|
|
45
34
|
const isCaptureOwnerStack = isAPI("captureOwnerStack");
|
|
46
|
-
/** Check if the node is a React `Children.count` API identifier or member expression. */
|
|
47
35
|
const isChildrenCount = isAPI("Children.count");
|
|
48
|
-
/** Check if the node is a React `Children.forEach` API identifier or member expression. */
|
|
49
36
|
const isChildrenForEach = isAPI("Children.forEach");
|
|
50
|
-
/** Check if the node is a React `Children.map` API identifier or member expression. */
|
|
51
37
|
const isChildrenMap = isAPI("Children.map");
|
|
52
|
-
/** Check if the node is a React `Children.only` API identifier or member expression. */
|
|
53
38
|
const isChildrenOnly = isAPI("Children.only");
|
|
54
|
-
/** Check if the node is a React `Children.toArray` API identifier or member expression. */
|
|
55
39
|
const isChildrenToArray = isAPI("Children.toArray");
|
|
56
|
-
/** Check if the node is a React `cloneElement` API identifier or member expression. */
|
|
57
40
|
const isCloneElement = isAPI("cloneElement");
|
|
58
|
-
/** Check if the node is a React `createContext` API identifier or member expression. */
|
|
59
41
|
const isCreateContext = isAPI("createContext");
|
|
60
|
-
/** Check if the node is a React `createElement` API identifier or member expression. */
|
|
61
42
|
const isCreateElement = isAPI("createElement");
|
|
62
|
-
/** Check if the node is a React `createRef` API identifier or member expression. */
|
|
63
43
|
const isCreateRef = isAPI("createRef");
|
|
64
|
-
/** Check if the node is a React `forwardRef` API identifier or member expression. */
|
|
65
44
|
const isForwardRef = isAPI("forwardRef");
|
|
66
|
-
/** Check if the node is a React `memo` API identifier or member expression. */
|
|
67
45
|
const isMemo = isAPI("memo");
|
|
68
|
-
/** Check if the node is a React `lazy` API identifier or member expression. */
|
|
69
46
|
const isLazy = isAPI("lazy");
|
|
70
|
-
/** Check if the node is a call expression to the React `captureOwnerStack` API. */
|
|
71
47
|
const isCaptureOwnerStackCall = isAPICall("captureOwnerStack");
|
|
72
|
-
/** Check if the node is a call expression to the React `Children.count` API. */
|
|
73
48
|
const isChildrenCountCall = isAPICall("Children.count");
|
|
74
|
-
/** Check if the node is a call expression to the React `Children.forEach` API. */
|
|
75
49
|
const isChildrenForEachCall = isAPICall("Children.forEach");
|
|
76
|
-
/** Check if the node is a call expression to the React `Children.map` API. */
|
|
77
50
|
const isChildrenMapCall = isAPICall("Children.map");
|
|
78
|
-
/** Check if the node is a call expression to the React `Children.only` API. */
|
|
79
51
|
const isChildrenOnlyCall = isAPICall("Children.only");
|
|
80
|
-
/** Check if the node is a call expression to the React `Children.toArray` API. */
|
|
81
52
|
const isChildrenToArrayCall = isAPICall("Children.toArray");
|
|
82
|
-
/** Check if the node is a call expression to the React `cloneElement` API. */
|
|
83
53
|
const isCloneElementCall = isAPICall("cloneElement");
|
|
84
|
-
/** Check if the node is a call expression to the React `createContext` API. */
|
|
85
54
|
const isCreateContextCall = isAPICall("createContext");
|
|
86
|
-
/** Check if the node is a call expression to the React `createElement` API. */
|
|
87
55
|
const isCreateElementCall = isAPICall("createElement");
|
|
88
|
-
/** Check if the node is a call expression to the React `createRef` API. */
|
|
89
56
|
const isCreateRefCall = isAPICall("createRef");
|
|
90
|
-
/** Check if the node is a call expression to the React `forwardRef` API. */
|
|
91
57
|
const isForwardRefCall = isAPICall("forwardRef");
|
|
92
|
-
/** Check if the node is a call expression to the React `memo` API. */
|
|
93
58
|
const isMemoCall = isAPICall("memo");
|
|
94
|
-
/** Check if the node is a call expression to the React `lazy` API. */
|
|
95
59
|
const isLazyCall = isAPICall("lazy");
|
|
96
|
-
/** Check if the node is a React `use` API identifier or member expression. */
|
|
97
60
|
const isUse = isAPI("use");
|
|
98
|
-
/** Check if the node is a React `useActionState` API identifier or member expression. */
|
|
99
61
|
const isUseActionState = isAPI("useActionState");
|
|
100
|
-
/** Check if the node is a React `useCallback` API identifier or member expression. */
|
|
101
62
|
const isUseCallback = isAPI("useCallback");
|
|
102
|
-
/** Check if the node is a React `useContext` API identifier or member expression. */
|
|
103
63
|
const isUseContext = isAPI("useContext");
|
|
104
|
-
/** Check if the node is a React `useDebugValue` API identifier or member expression. */
|
|
105
64
|
const isUseDebugValue = isAPI("useDebugValue");
|
|
106
|
-
/** Check if the node is a React `useDeferredValue` API identifier or member expression. */
|
|
107
65
|
const isUseDeferredValue = isAPI("useDeferredValue");
|
|
108
|
-
/** Check if the node is a React `useEffect` API identifier or member expression. */
|
|
109
66
|
const isUseEffect = isAPI("useEffect");
|
|
110
|
-
/** Check if the node is a React `useFormStatus` API identifier or member expression. */
|
|
111
67
|
const isUseFormStatus = isAPI("useFormStatus");
|
|
112
|
-
/** Check if the node is a React `useId` API identifier or member expression. */
|
|
113
68
|
const isUseId = isAPI("useId");
|
|
114
|
-
/** Check if the node is a React `useImperativeHandle` API identifier or member expression. */
|
|
115
69
|
const isUseImperativeHandle = isAPI("useImperativeHandle");
|
|
116
|
-
/** Check if the node is a React `useInsertionEffect` API identifier or member expression. */
|
|
117
70
|
const isUseInsertionEffect = isAPI("useInsertionEffect");
|
|
118
|
-
/** Check if the node is a React `useLayoutEffect` API identifier or member expression. */
|
|
119
71
|
const isUseLayoutEffect = isAPI("useLayoutEffect");
|
|
120
|
-
/** Check if the node is a React `useMemo` API identifier or member expression. */
|
|
121
72
|
const isUseMemo = isAPI("useMemo");
|
|
122
|
-
/** Check if the node is a React `useOptimistic` API identifier or member expression. */
|
|
123
73
|
const isUseOptimistic = isAPI("useOptimistic");
|
|
124
|
-
/** Check if the node is a React `useReducer` API identifier or member expression. */
|
|
125
74
|
const isUseReducer = isAPI("useReducer");
|
|
126
|
-
/** Check if the node is a React `useRef` API identifier or member expression. */
|
|
127
75
|
const isUseRef = isAPI("useRef");
|
|
128
|
-
/** Check if the node is a React `useState` API identifier or member expression. */
|
|
129
76
|
const isUseState = isAPI("useState");
|
|
130
|
-
/** Check if the node is a React `useSyncExternalStore` API identifier or member expression. */
|
|
131
77
|
const isUseSyncExternalStore = isAPI("useSyncExternalStore");
|
|
132
|
-
/** Check if the node is a React `useTransition` API identifier or member expression. */
|
|
133
78
|
const isUseTransition = isAPI("useTransition");
|
|
134
|
-
/** Check if the node is a call expression to the React `use` API. */
|
|
135
79
|
const isUseCall = isAPICall("use");
|
|
136
|
-
/** Check if the node is a call expression to the React `useActionState` API. */
|
|
137
80
|
const isUseActionStateCall = isAPICall("useActionState");
|
|
138
|
-
/** Check if the node is a call expression to the React `useCallback` API. */
|
|
139
81
|
const isUseCallbackCall = isAPICall("useCallback");
|
|
140
|
-
/** Check if the node is a call expression to the React `useContext` API. */
|
|
141
82
|
const isUseContextCall = isAPICall("useContext");
|
|
142
|
-
/** Check if the node is a call expression to the React `useDebugValue` API. */
|
|
143
83
|
const isUseDebugValueCall = isAPICall("useDebugValue");
|
|
144
|
-
/** Check if the node is a call expression to the React `useDeferredValue` API. */
|
|
145
84
|
const isUseDeferredValueCall = isAPICall("useDeferredValue");
|
|
146
|
-
/** Check if the node is a call expression to the React `useEffect` API. */
|
|
147
85
|
const isUseEffectCall = isAPICall("useEffect");
|
|
148
|
-
/** Check if the node is a call expression to the React `useFormStatus` API. */
|
|
149
86
|
const isUseFormStatusCall = isAPICall("useFormStatus");
|
|
150
|
-
/** Check if the node is a call expression to the React `useId` API. */
|
|
151
87
|
const isUseIdCall = isAPICall("useId");
|
|
152
|
-
/** Check if the node is a call expression to the React `useImperativeHandle` API. */
|
|
153
88
|
const isUseImperativeHandleCall = isAPICall("useImperativeHandle");
|
|
154
|
-
/** Check if the node is a call expression to the React `useInsertionEffect` API. */
|
|
155
89
|
const isUseInsertionEffectCall = isAPICall("useInsertionEffect");
|
|
156
|
-
/** Check if the node is a call expression to the React `useLayoutEffect` API. */
|
|
157
90
|
const isUseLayoutEffectCall = isAPICall("useLayoutEffect");
|
|
158
|
-
/** Check if the node is a call expression to the React `useMemo` API. */
|
|
159
91
|
const isUseMemoCall = isAPICall("useMemo");
|
|
160
|
-
/** Check if the node is a call expression to the React `useOptimistic` API. */
|
|
161
92
|
const isUseOptimisticCall = isAPICall("useOptimistic");
|
|
162
|
-
/** Check if the node is a call expression to the React `useReducer` API. */
|
|
163
93
|
const isUseReducerCall = isAPICall("useReducer");
|
|
164
|
-
/** Check if the node is a call expression to the React `useRef` API. */
|
|
165
94
|
const isUseRefCall = isAPICall("useRef");
|
|
166
|
-
/** Check if the node is a call expression to the React `useState` API. */
|
|
167
95
|
const isUseStateCall = isAPICall("useState");
|
|
168
|
-
/** Check if the node is a call expression to the React `useSyncExternalStore` API. */
|
|
169
96
|
const isUseSyncExternalStoreCall = isAPICall("useSyncExternalStore");
|
|
170
|
-
/** Check if the node is a call expression to the React `useTransition` API. */
|
|
171
97
|
const isUseTransitionCall = isAPICall("useTransition");
|
|
172
98
|
|
|
173
99
|
//#endregion
|
|
174
100
|
//#region src/class.ts
|
|
175
|
-
/**
|
|
176
|
-
* Get the class identifier of a class node.
|
|
177
|
-
* @param node The class node to get the identifier from.
|
|
178
|
-
* @returns The class identifier or `null` if not found.
|
|
179
|
-
*/
|
|
180
101
|
function getClassId(node) {
|
|
181
102
|
if (node.id != null) return node.id;
|
|
182
103
|
if (node.parent.type === AST_NODE_TYPES.VariableDeclarator) return node.parent.id;
|
|
183
104
|
return null;
|
|
184
105
|
}
|
|
185
106
|
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region ../../.pkgs/eff/dist/index.js
|
|
109
|
+
const pipeArguments = (self, args) => {
|
|
110
|
+
switch (args.length) {
|
|
111
|
+
case 0: return self;
|
|
112
|
+
case 1: return args[0](self);
|
|
113
|
+
case 2: return args[1](args[0](self));
|
|
114
|
+
case 3: return args[2](args[1](args[0](self)));
|
|
115
|
+
case 4: return args[3](args[2](args[1](args[0](self))));
|
|
116
|
+
case 5: return args[4](args[3](args[2](args[1](args[0](self)))));
|
|
117
|
+
case 6: return args[5](args[4](args[3](args[2](args[1](args[0](self))))));
|
|
118
|
+
case 7: return args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))));
|
|
119
|
+
case 8: return args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self))))))));
|
|
120
|
+
case 9: return args[8](args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))))));
|
|
121
|
+
default: {
|
|
122
|
+
let ret = self;
|
|
123
|
+
for (let i = 0, len = args.length; i < len; i++) ret = args[i](ret);
|
|
124
|
+
return ret;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const Prototype = { pipe() {
|
|
129
|
+
return pipeArguments(this, arguments);
|
|
130
|
+
} };
|
|
131
|
+
const Class = (function() {
|
|
132
|
+
function PipeableBase() {}
|
|
133
|
+
PipeableBase.prototype = Prototype;
|
|
134
|
+
return PipeableBase;
|
|
135
|
+
})();
|
|
136
|
+
const dual = function(arity, body) {
|
|
137
|
+
if (typeof arity === "function") return function() {
|
|
138
|
+
return arity(arguments) ? body.apply(this, arguments) : ((self) => body(self, ...arguments));
|
|
139
|
+
};
|
|
140
|
+
switch (arity) {
|
|
141
|
+
case 0:
|
|
142
|
+
case 1: throw new RangeError(`Invalid arity ${arity}`);
|
|
143
|
+
case 2: return function(a, b) {
|
|
144
|
+
if (arguments.length >= 2) return body(a, b);
|
|
145
|
+
return function(self) {
|
|
146
|
+
return body(self, a);
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
case 3: return function(a, b, c) {
|
|
150
|
+
if (arguments.length >= 3) return body(a, b, c);
|
|
151
|
+
return function(self) {
|
|
152
|
+
return body(self, a, b);
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
default: return function() {
|
|
156
|
+
if (arguments.length >= arity) return body.apply(this, arguments);
|
|
157
|
+
const args = arguments;
|
|
158
|
+
return function(self) {
|
|
159
|
+
return body(self, ...args);
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const identity = (a) => a;
|
|
165
|
+
const cast = identity;
|
|
166
|
+
const constant = (value) => () => value;
|
|
167
|
+
const constTrue = constant(true);
|
|
168
|
+
const constFalse = constant(false);
|
|
169
|
+
const constNull = constant(null);
|
|
170
|
+
const constUndefined = constant(void 0);
|
|
171
|
+
const compose = dual(2, (ab, bc) => (a) => bc(ab(a)));
|
|
172
|
+
const absurd = (_) => {
|
|
173
|
+
throw new Error("Called `absurd` function which should be uncallable");
|
|
174
|
+
};
|
|
175
|
+
const hole = cast(absurd);
|
|
176
|
+
const and = dual(2, (a, b) => (data) => a(data) && b(data));
|
|
177
|
+
const or = dual(2, (a, b) => (data) => a(data) || b(data));
|
|
178
|
+
const xor = dual(2, (a, b) => (data) => a(data) !== b(data));
|
|
179
|
+
const eqv = dual(2, (a, b) => (data) => a(data) === b(data));
|
|
180
|
+
const implies = dual(2, (antecedent, consequent) => (data) => !antecedent(data) || consequent(data));
|
|
181
|
+
const nor = dual(2, (a, b) => (data) => !a(data) && !b(data));
|
|
182
|
+
const nand = dual(2, (a, b) => (data) => !a(data) || !b(data));
|
|
183
|
+
function isFunction(input) {
|
|
184
|
+
return typeof input === "function";
|
|
185
|
+
}
|
|
186
|
+
function isObjectKeyword(input) {
|
|
187
|
+
return typeof input === "object" && input !== null || isFunction(input);
|
|
188
|
+
}
|
|
189
|
+
const hasProperty = dual(2, (data, property) => isObjectKeyword(data) && property in data);
|
|
190
|
+
const dropWhile = dual(2, (self, predicate) => {
|
|
191
|
+
const input = Array.isArray(self) ? self : Array.from(self);
|
|
192
|
+
const len = input.length;
|
|
193
|
+
let idx = 0;
|
|
194
|
+
while (idx < len && predicate(input[idx], idx)) idx++;
|
|
195
|
+
return input.slice(idx);
|
|
196
|
+
});
|
|
197
|
+
const takeWhile = dual(2, (self, predicate) => {
|
|
198
|
+
const input = Array.isArray(self) ? self : Array.from(self);
|
|
199
|
+
const len = input.length;
|
|
200
|
+
let idx = 0;
|
|
201
|
+
while (idx < len && predicate(input[idx], idx)) idx++;
|
|
202
|
+
return input.slice(0, idx);
|
|
203
|
+
});
|
|
204
|
+
|
|
186
205
|
//#endregion
|
|
187
206
|
//#region src/class-component.ts
|
|
188
|
-
/**
|
|
189
|
-
* Check if the node is a class component (extends `Component` or `PureComponent`).
|
|
190
|
-
* @param node The node to check.
|
|
191
|
-
* @returns `true` if the node is a class component.
|
|
192
|
-
*/
|
|
193
207
|
function isClassComponent(node) {
|
|
194
|
-
if ("superClass"
|
|
208
|
+
if (hasProperty(node, "superClass") && node.superClass != null) {
|
|
195
209
|
const re = /^(?:Pure)?Component$/u;
|
|
196
210
|
switch (true) {
|
|
197
211
|
case Check.isIdentifier(node.superClass): return re.test(node.superClass.name);
|
|
@@ -200,14 +214,8 @@ function isClassComponent(node) {
|
|
|
200
214
|
}
|
|
201
215
|
return false;
|
|
202
216
|
}
|
|
203
|
-
/**
|
|
204
|
-
* Check if the node is a pure component (extends `PureComponent`).
|
|
205
|
-
* @param node The AST node to check.
|
|
206
|
-
* @returns `true` if the node is a pure component.
|
|
207
|
-
* @deprecated Class components are legacy. This function exists only to support legacy rules.
|
|
208
|
-
*/
|
|
209
217
|
function isPureComponent(node) {
|
|
210
|
-
if ("superClass"
|
|
218
|
+
if (hasProperty(node, "superClass") && node.superClass != null) {
|
|
211
219
|
const re = /^PureComponent$/u;
|
|
212
220
|
switch (true) {
|
|
213
221
|
case Check.isIdentifier(node.superClass): return re.test(node.superClass.name);
|
|
@@ -219,68 +227,33 @@ function isPureComponent(node) {
|
|
|
219
227
|
function createLifecycleChecker(methodName, isStatic = false) {
|
|
220
228
|
return (node) => Check.isPropertyOrMethod(node) && node.static === isStatic && Check.isIdentifier(node.key, methodName);
|
|
221
229
|
}
|
|
222
|
-
/** @deprecated Class components are legacy. */
|
|
223
230
|
const isRender = createLifecycleChecker("render");
|
|
224
|
-
/** @deprecated Class components are legacy. */
|
|
225
231
|
const isComponentDidCatch = createLifecycleChecker("componentDidCatch");
|
|
226
|
-
/** @deprecated Class components are legacy. */
|
|
227
232
|
const isComponentDidMount = createLifecycleChecker("componentDidMount");
|
|
228
|
-
/** @deprecated Class components are legacy. */
|
|
229
233
|
const isComponentDidUpdate = createLifecycleChecker("componentDidUpdate");
|
|
230
|
-
/** @deprecated Class components are legacy. */
|
|
231
234
|
const isComponentWillMount = createLifecycleChecker("componentWillMount");
|
|
232
|
-
/** @deprecated Class components are legacy. */
|
|
233
235
|
const isComponentWillReceiveProps = createLifecycleChecker("componentWillReceiveProps");
|
|
234
|
-
/** @deprecated Class components are legacy. */
|
|
235
236
|
const isComponentWillUnmount = createLifecycleChecker("componentWillUnmount");
|
|
236
|
-
/** @deprecated Class components are legacy. */
|
|
237
237
|
const isComponentWillUpdate = createLifecycleChecker("componentWillUpdate");
|
|
238
|
-
/** @deprecated Class components are legacy. */
|
|
239
238
|
const isGetChildContext = createLifecycleChecker("getChildContext");
|
|
240
|
-
/** @deprecated Class components are legacy. */
|
|
241
239
|
const isGetInitialState = createLifecycleChecker("getInitialState");
|
|
242
|
-
/** @deprecated Class components are legacy. */
|
|
243
240
|
const isGetSnapshotBeforeUpdate = createLifecycleChecker("getSnapshotBeforeUpdate");
|
|
244
|
-
/** @deprecated Class components are legacy. */
|
|
245
241
|
const isShouldComponentUpdate = createLifecycleChecker("shouldComponentUpdate");
|
|
246
|
-
/** @deprecated Class components are legacy. */
|
|
247
242
|
const isUnsafeComponentWillMount = createLifecycleChecker("UNSAFE_componentWillMount");
|
|
248
|
-
/** @deprecated Class components are legacy. */
|
|
249
243
|
const isUnsafeComponentWillReceiveProps = createLifecycleChecker("UNSAFE_componentWillReceiveProps");
|
|
250
|
-
/** @deprecated Class components are legacy. */
|
|
251
244
|
const isUnsafeComponentWillUpdate = createLifecycleChecker("UNSAFE_componentWillUpdate");
|
|
252
|
-
/** @deprecated Class components are legacy. */
|
|
253
245
|
const isGetDefaultProps = createLifecycleChecker("getDefaultProps", true);
|
|
254
|
-
/** @deprecated Class components are legacy. */
|
|
255
246
|
const isGetDerivedStateFromProps = createLifecycleChecker("getDerivedStateFromProps", true);
|
|
256
|
-
/** @deprecated Class components are legacy. */
|
|
257
247
|
const isGetDerivedStateFromError = createLifecycleChecker("getDerivedStateFromError", true);
|
|
258
|
-
/**
|
|
259
|
-
* Check if the node is a render-like method of a class component.
|
|
260
|
-
* @param node The AST node to check.
|
|
261
|
-
* @returns `true` if the node is a render-like method.
|
|
262
|
-
* @deprecated Class components are legacy. This function exists only to support legacy rules.
|
|
263
|
-
*/
|
|
264
248
|
function isRenderMethodLike(node) {
|
|
265
249
|
return Check.isPropertyOrMethod(node) && Check.isIdentifier(node.key) && node.key.name.startsWith("render") && Check.isOneOf([AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.ClassExpression])(node.parent.parent);
|
|
266
250
|
}
|
|
267
|
-
/**
|
|
268
|
-
* Check if the function is a callback passed to a class component's render method.
|
|
269
|
-
* @param node The function node to check.
|
|
270
|
-
* @returns `true` if the function is a render method callback.
|
|
271
|
-
*/
|
|
272
251
|
function isRenderMethodCallback(node) {
|
|
273
252
|
const parent = node.parent;
|
|
274
253
|
const greatGrandparent = parent.parent?.parent;
|
|
275
254
|
if (greatGrandparent == null) return false;
|
|
276
255
|
return isRenderMethodLike(parent) && isClassComponent(greatGrandparent);
|
|
277
256
|
}
|
|
278
|
-
/**
|
|
279
|
-
* Check if the call expression is a `this.setState(...)` call.
|
|
280
|
-
* @param node The call expression node to check.
|
|
281
|
-
* @returns `true` if the node is a `this.setState(...)` call.
|
|
282
|
-
* @deprecated Class components are legacy. This function exists only to support legacy rules.
|
|
283
|
-
*/
|
|
284
257
|
function isThisSetStateCall(node) {
|
|
285
258
|
const callee = Extract.unwrap(node.callee);
|
|
286
259
|
return callee.type === AST_NODE_TYPES.MemberExpression && Extract.unwrap(callee.object).type === AST_NODE_TYPES.ThisExpression && Extract.getCalleeName(node) === "setState";
|
|
@@ -288,11 +261,6 @@ function isThisSetStateCall(node) {
|
|
|
288
261
|
|
|
289
262
|
//#endregion
|
|
290
263
|
//#region src/class-component-collector.ts
|
|
291
|
-
/**
|
|
292
|
-
* Get an api and visitor object for the rule to collect class components.
|
|
293
|
-
* @param context The rule context.
|
|
294
|
-
* @deprecated Class components are legacy. This function exists only to support legacy rules.
|
|
295
|
-
*/
|
|
296
264
|
function getClassComponentCollector(context) {
|
|
297
265
|
const components = /* @__PURE__ */ new Map();
|
|
298
266
|
const getText = (n) => context.sourceCode.getText(n);
|
|
@@ -326,27 +294,10 @@ function getClassComponentCollector(context) {
|
|
|
326
294
|
|
|
327
295
|
//#endregion
|
|
328
296
|
//#region src/create-element.ts
|
|
329
|
-
/**
|
|
330
|
-
* Get the type argument (the first argument) of a `createElement` call.
|
|
331
|
-
* @param context The ESLint rule context.
|
|
332
|
-
* @param node The node to inspect.
|
|
333
|
-
* @returns The type argument, or `null` when the node is not a `createElement` call or has no arguments.
|
|
334
|
-
*/
|
|
335
297
|
function getCreateElementTypeArgument(context, node) {
|
|
336
298
|
if (!isCreateElementCall(context, node)) return null;
|
|
337
299
|
return node.arguments[0] ?? null;
|
|
338
300
|
}
|
|
339
|
-
/**
|
|
340
|
-
* Get the props object (the second argument) of a `createElement` call.
|
|
341
|
-
*
|
|
342
|
-
* Type expressions and chain expressions wrapping the argument are unwrapped
|
|
343
|
-
* before the object check; `null`, spread, or otherwise non-object props
|
|
344
|
-
* arguments yield `null`.
|
|
345
|
-
*
|
|
346
|
-
* @param context The ESLint rule context.
|
|
347
|
-
* @param node The node to inspect.
|
|
348
|
-
* @returns The props `ObjectExpression`, or `null` when absent or not statically an object literal.
|
|
349
|
-
*/
|
|
350
301
|
function getCreateElementPropsObject(context, node) {
|
|
351
302
|
if (!isCreateElementCall(context, node)) return null;
|
|
352
303
|
const propsArg = node.arguments[1];
|
|
@@ -354,57 +305,21 @@ function getCreateElementPropsObject(context, node) {
|
|
|
354
305
|
const expr = Extract.unwrap(propsArg);
|
|
355
306
|
return expr.type === AST_NODE_TYPES.ObjectExpression ? expr : null;
|
|
356
307
|
}
|
|
357
|
-
/**
|
|
358
|
-
* Get the children arguments (the arguments after the props object) of a `createElement` call.
|
|
359
|
-
* @param context The ESLint rule context.
|
|
360
|
-
* @param node The node to inspect.
|
|
361
|
-
* @returns The children arguments, or an empty array when the node is not a `createElement` call.
|
|
362
|
-
*/
|
|
363
308
|
function getCreateElementChildrenArguments(context, node) {
|
|
364
309
|
if (!isCreateElementCall(context, node)) return [];
|
|
365
310
|
return node.arguments.slice(2);
|
|
366
311
|
}
|
|
367
|
-
/**
|
|
368
|
-
* Find a statically named property in the props object of a `createElement` call.
|
|
369
|
-
*
|
|
370
|
-
* Statically resolvable names include plain identifier keys as well as
|
|
371
|
-
* string-literal and simple template-literal keys (computed or not).
|
|
372
|
-
* @param context The ESLint rule context.
|
|
373
|
-
* @param node The node to inspect.
|
|
374
|
-
* @param name The property name to look for (ex: `"children"`, `"key"`).
|
|
375
|
-
* @returns The matching `Property` node, or `null` when the call has no static property with that name.
|
|
376
|
-
*
|
|
377
|
-
* @example
|
|
378
|
-
* ```ts
|
|
379
|
-
* import { getCreateElementProp } from "@eslint-react/core";
|
|
380
|
-
*
|
|
381
|
-
* const childrenProp = getCreateElementProp(context, node, "children");
|
|
382
|
-
* ```
|
|
383
|
-
*/
|
|
384
312
|
function getCreateElementProp(context, node, name) {
|
|
385
313
|
const propsObject = getCreateElementPropsObject(context, node);
|
|
386
314
|
if (propsObject == null) return null;
|
|
387
315
|
for (const prop of propsObject.properties) if (prop.type === AST_NODE_TYPES.Property && Extract.getPropertyName(prop, "max") === name) return prop;
|
|
388
316
|
return null;
|
|
389
317
|
}
|
|
390
|
-
/**
|
|
391
|
-
* Check if the node is passed as a children argument (the third argument or
|
|
392
|
-
* later) of a `createElement` call.
|
|
393
|
-
* @param context The ESLint rule context.
|
|
394
|
-
* @param node The node to check.
|
|
395
|
-
* @returns `true` if the node is a direct children argument of a `createElement` call.
|
|
396
|
-
*/
|
|
397
318
|
function isCreateElementChildrenArgument(context, node) {
|
|
398
319
|
let parent = node.parent;
|
|
399
320
|
while (Check.isTypeExpression(parent)) parent = parent.parent;
|
|
400
321
|
return parent?.type === AST_NODE_TYPES.CallExpression && isCreateElementCall(context, parent) && parent.arguments.slice(2).some((arg) => Extract.unwrap(arg) === node);
|
|
401
322
|
}
|
|
402
|
-
/**
|
|
403
|
-
* Check if the node is inside the props object (the second argument) of a `createElement` call.
|
|
404
|
-
* @param context The ESLint rule context.
|
|
405
|
-
* @param node The node to check.
|
|
406
|
-
* @returns `true` if the node is inside `createElement`'s props object.
|
|
407
|
-
*/
|
|
408
323
|
function isInsideCreateElementProps(context, node) {
|
|
409
324
|
const call = Traverse.findParent(node, isCreateElementCall(context));
|
|
410
325
|
if (call == null) return false;
|
|
@@ -415,23 +330,9 @@ function isInsideCreateElementProps(context, node) {
|
|
|
415
330
|
|
|
416
331
|
//#endregion
|
|
417
332
|
//#region src/function.ts
|
|
418
|
-
/**
|
|
419
|
-
* Get the static identifier of a function AST node.
|
|
420
|
-
*
|
|
421
|
-
* @remarks
|
|
422
|
-
* For function declarations this is straightforward. For anonymous function
|
|
423
|
-
* expressions it is more complex. This function roughly detects the same AST
|
|
424
|
-
* nodes as the ECMAScript spec's `IsAnonymousFunctionDefinition()` with some
|
|
425
|
-
* exceptions to better fit our use case.
|
|
426
|
-
*
|
|
427
|
-
* Ported from {@link https://github.com/facebook/react/blob/bb8a76c6cc77ea2976d690ea09f5a1b3d9b1792a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts#L860 | RulesOfHooks.ts}
|
|
428
|
-
*
|
|
429
|
-
* @param node The function node to analyze.
|
|
430
|
-
* @returns The identifier node if found, `null` otherwise.
|
|
431
|
-
*/
|
|
432
333
|
function getFunctionId(node) {
|
|
433
334
|
switch (true) {
|
|
434
|
-
case "id"
|
|
335
|
+
case hasProperty(node, "id") && node.id != null: return node.id;
|
|
435
336
|
case node.parent.type === AST_NODE_TYPES.VariableDeclarator && node.parent.init === node: return node.parent.id;
|
|
436
337
|
case node.parent.type === AST_NODE_TYPES.AssignmentExpression && node.parent.right === node && node.parent.operator === "=": return node.parent.left;
|
|
437
338
|
case node.parent.type === AST_NODE_TYPES.Property && node.parent.value === node && !node.parent.computed: return node.parent.key;
|
|
@@ -442,12 +343,6 @@ function getFunctionId(node) {
|
|
|
442
343
|
}
|
|
443
344
|
return null;
|
|
444
345
|
}
|
|
445
|
-
/**
|
|
446
|
-
* Get the initialization path of a function node in the AST.
|
|
447
|
-
*
|
|
448
|
-
* @param node The function node to analyze.
|
|
449
|
-
* @returns The function initialization path or `null` if not identifiable.
|
|
450
|
-
*/
|
|
451
346
|
function getFunctionInitPath(node) {
|
|
452
347
|
if (node.type === AST_NODE_TYPES.FunctionDeclaration) return [node];
|
|
453
348
|
let parent = node.parent;
|
|
@@ -493,36 +388,18 @@ function getFunctionInitPath(node) {
|
|
|
493
388
|
}
|
|
494
389
|
return null;
|
|
495
390
|
}
|
|
496
|
-
/**
|
|
497
|
-
* Check if a specific function call exists in the function initialization path.
|
|
498
|
-
*
|
|
499
|
-
* @param callName The name of the call to check for (e.g., "memo", "forwardRef").
|
|
500
|
-
* @param initPath The function initialization path to search in.
|
|
501
|
-
* @returns `true` if the call exists in the path, `false` otherwise.
|
|
502
|
-
*/
|
|
503
391
|
function isFunctionHasCallInInitPath(callName, initPath) {
|
|
504
392
|
return initPath.some((node) => {
|
|
505
393
|
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
506
394
|
const callee = Extract.unwrap(node.callee);
|
|
507
395
|
if (Check.isIdentifier(callee)) return callee.name === callName;
|
|
508
|
-
if (callee.type === AST_NODE_TYPES.MemberExpression && "name"
|
|
396
|
+
if (callee.type === AST_NODE_TYPES.MemberExpression && hasProperty(callee.property, "name")) return callee.property.name === callName;
|
|
509
397
|
return false;
|
|
510
398
|
});
|
|
511
399
|
}
|
|
512
|
-
/**
|
|
513
|
-
* Check if a function is empty.
|
|
514
|
-
*
|
|
515
|
-
* @param node The function node to check.
|
|
516
|
-
* @returns `true` if the function is empty, `false` otherwise.
|
|
517
|
-
*/
|
|
518
400
|
function isFunctionEmpty(node) {
|
|
519
401
|
return node.body.type === AST_NODE_TYPES.BlockStatement && node.body.body.length === 0;
|
|
520
402
|
}
|
|
521
|
-
/**
|
|
522
|
-
* Get the directives of a function (ex: "use strict", "use client", "use server").
|
|
523
|
-
* @param node The function node to get the directives from.
|
|
524
|
-
* @returns The directives of the function.
|
|
525
|
-
*/
|
|
526
403
|
function getFunctionDirectives(node) {
|
|
527
404
|
const directives = [];
|
|
528
405
|
if (node.body.type !== AST_NODE_TYPES.BlockStatement) return directives;
|
|
@@ -535,17 +412,9 @@ function getFunctionDirectives(node) {
|
|
|
535
412
|
}
|
|
536
413
|
return directives;
|
|
537
414
|
}
|
|
538
|
-
/**
|
|
539
|
-
* Check if a directive with the given name exists in the function directives.
|
|
540
|
-
*
|
|
541
|
-
* @param node The function AST node.
|
|
542
|
-
* @param name The directive name to check (e.g., "use memo", "use no memo").
|
|
543
|
-
* @returns `true` if the directive exists, `false` otherwise.
|
|
544
|
-
*/
|
|
545
415
|
function isFunctionHasDirective(node, name) {
|
|
546
416
|
return getFunctionDirectives(node).some((d) => d.directive === name);
|
|
547
417
|
}
|
|
548
|
-
/** The esquery selector matching `displayName` assignment expressions. */
|
|
549
418
|
const SEL_FUNCTION_DISPLAY_NAME_ASSIGNMENT = [
|
|
550
419
|
"AssignmentExpression",
|
|
551
420
|
"[operator='=']",
|
|
@@ -555,67 +424,21 @@ const SEL_FUNCTION_DISPLAY_NAME_ASSIGNMENT = [
|
|
|
555
424
|
|
|
556
425
|
//#endregion
|
|
557
426
|
//#region src/jsx.ts
|
|
558
|
-
/**
|
|
559
|
-
* Hints for JSX detection.
|
|
560
|
-
*/
|
|
561
427
|
const JsxDetectionHint = {
|
|
562
|
-
/** No hints set. */
|
|
563
428
|
None: 0n,
|
|
564
|
-
/** Do not treat `null` values as JSX-like. */
|
|
565
429
|
DoNotIncludeJsxWithNullValue: 1n << 0n,
|
|
566
|
-
/** Do not treat number values as JSX-like. */
|
|
567
430
|
DoNotIncludeJsxWithNumberValue: 1n << 1n,
|
|
568
|
-
/** Do not treat bigint values as JSX-like. */
|
|
569
431
|
DoNotIncludeJsxWithBigIntValue: 1n << 2n,
|
|
570
|
-
/** Do not treat string values as JSX-like. */
|
|
571
432
|
DoNotIncludeJsxWithStringValue: 1n << 3n,
|
|
572
|
-
/** Do not treat boolean values as JSX-like. */
|
|
573
433
|
DoNotIncludeJsxWithBooleanValue: 1n << 4n,
|
|
574
|
-
/** Do not treat undefined values as JSX-like. */
|
|
575
434
|
DoNotIncludeJsxWithUndefinedValue: 1n << 5n,
|
|
576
|
-
/** Do not treat empty array values as JSX-like. */
|
|
577
435
|
DoNotIncludeJsxWithEmptyArrayValue: 1n << 6n,
|
|
578
|
-
/** Do not treat `createElement` calls as JSX-like. */
|
|
579
436
|
DoNotIncludeJsxWithCreateElementValue: 1n << 7n,
|
|
580
|
-
/** Require all array elements to be JSX-like for the array to be JSX-like. */
|
|
581
437
|
RequireAllArrayElementsToBeJsx: 1n << 8n,
|
|
582
|
-
/** Require both sides of a logical expression to be JSX-like. */
|
|
583
438
|
RequireBothSidesOfLogicalExpressionToBeJsx: 1n << 9n,
|
|
584
|
-
/** Require both branches of a conditional expression to be JSX-like. */
|
|
585
439
|
RequireBothBranchesOfConditionalExpressionToBeJsx: 1n << 10n
|
|
586
440
|
};
|
|
587
|
-
/**
|
|
588
|
-
* Default JSX detection hint.
|
|
589
|
-
*
|
|
590
|
-
* Skips number, bigint, boolean, string, and undefined literals,
|
|
591
|
-
* the value types that are commonly returned alongside JSX in React
|
|
592
|
-
* components but are not themselves renderable elements.
|
|
593
|
-
*/
|
|
594
441
|
const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue;
|
|
595
|
-
/**
|
|
596
|
-
* Check if the node represents JSX-like content based on heuristics.
|
|
597
|
-
*
|
|
598
|
-
* The detection behavior is configurable through {@link JsxDetectionHint}
|
|
599
|
-
* bit-flags so that callers can opt individual value kinds in or out.
|
|
600
|
-
*
|
|
601
|
-
* Identifiers are resolved to their definitions via scope analysis;
|
|
602
|
-
* circular definitions (e.g. `var a = b; var b = a;`) are detected and
|
|
603
|
-
* treated as not JSX-like instead of recursing indefinitely.
|
|
604
|
-
*
|
|
605
|
-
* @param context The ESLint rule context (needed for variable resolution).
|
|
606
|
-
* @param node The AST node to analyze.
|
|
607
|
-
* @param hint Optional bit-flags to adjust detection behavior. Defaults to {@link DEFAULT_JSX_DETECTION_HINT}.
|
|
608
|
-
* @returns Whether the node is considered JSX-like.
|
|
609
|
-
*
|
|
610
|
-
* @example
|
|
611
|
-
* ```ts
|
|
612
|
-
* import { isJsxLike } from "@eslint-react/core";
|
|
613
|
-
*
|
|
614
|
-
* if (isJsxLike(context, node)) {
|
|
615
|
-
* // node looks like it evaluates to a React element
|
|
616
|
-
* }
|
|
617
|
-
* ```
|
|
618
|
-
*/
|
|
619
442
|
function isJsxLike(context, node, hint = DEFAULT_JSX_DETECTION_HINT) {
|
|
620
443
|
const seen = /* @__PURE__ */ new Set();
|
|
621
444
|
function visit(node) {
|
|
@@ -655,49 +478,23 @@ function isJsxLike(context, node, hint = DEFAULT_JSX_DETECTION_HINT) {
|
|
|
655
478
|
|
|
656
479
|
//#endregion
|
|
657
480
|
//#region src/function-component.ts
|
|
658
|
-
/**
|
|
659
|
-
* Component flag constants.
|
|
660
|
-
*/
|
|
661
481
|
const FunctionComponentFlag = {
|
|
662
|
-
/** No flags set. */
|
|
663
482
|
None: 0n,
|
|
664
|
-
/** Indicates the component is a pure component (ex: extends PureComponent). */
|
|
665
483
|
PureComponent: 1n << 0n,
|
|
666
|
-
/** Indicates the component creates elements using `createElement` instead of JSX. */
|
|
667
484
|
CreateElement: 1n << 1n,
|
|
668
|
-
/** Indicates the component is memoized (ex: React.memo). */
|
|
669
485
|
Memo: 1n << 2n,
|
|
670
|
-
/** Indicates the component forwards a ref (ex: React.forwardRef). */
|
|
671
486
|
ForwardRef: 1n << 3n
|
|
672
487
|
};
|
|
673
|
-
/**
|
|
674
|
-
* Get component flag from init path.
|
|
675
|
-
* @param initPath The init path of the function component.
|
|
676
|
-
* @returns The component flag.
|
|
677
|
-
* @internal
|
|
678
|
-
*/
|
|
679
488
|
function getFunctionComponentFlagFromInitPath(initPath) {
|
|
680
489
|
let flag = FunctionComponentFlag.None;
|
|
681
490
|
if (initPath != null && isFunctionHasCallInInitPath("memo", initPath)) flag |= FunctionComponentFlag.Memo;
|
|
682
491
|
if (initPath != null && isFunctionHasCallInInitPath("forwardRef", initPath)) flag |= FunctionComponentFlag.ForwardRef;
|
|
683
492
|
return flag;
|
|
684
493
|
}
|
|
685
|
-
/**
|
|
686
|
-
* Check if the node is a call expression for a component wrapper.
|
|
687
|
-
* @param context The ESLint rule context.
|
|
688
|
-
* @param node The node to check.
|
|
689
|
-
* @returns `true` if the node is a call expression for a component wrapper.
|
|
690
|
-
*/
|
|
691
494
|
function isFunctionComponentWrapperCall(context, node) {
|
|
692
495
|
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
693
496
|
return isMemoCall(context, node) || isForwardRefCall(context, node);
|
|
694
497
|
}
|
|
695
|
-
/**
|
|
696
|
-
* Check if the node is a callback function passed to a component wrapper.
|
|
697
|
-
* @param context The ESLint rule context.
|
|
698
|
-
* @param node The node to check.
|
|
699
|
-
* @returns `true` if the node is a callback function passed to a component wrapper.
|
|
700
|
-
*/
|
|
701
498
|
function isFunctionComponentWrapperCallback(context, node) {
|
|
702
499
|
if (!Check.isFunction(node)) return false;
|
|
703
500
|
let parent = node.parent;
|
|
@@ -705,12 +502,6 @@ function isFunctionComponentWrapperCallback(context, node) {
|
|
|
705
502
|
if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
706
503
|
return isFunctionComponentWrapperCall(context, parent);
|
|
707
504
|
}
|
|
708
|
-
/**
|
|
709
|
-
* Get function component identifier from `const Component = memo(() => {});`.
|
|
710
|
-
* @param context The rule context.
|
|
711
|
-
* @param node The AST node to get the function component identifier from.
|
|
712
|
-
* @internal
|
|
713
|
-
*/
|
|
714
505
|
function getFunctionComponentId(context, node) {
|
|
715
506
|
const functionId = getFunctionId(node);
|
|
716
507
|
if (functionId != null) return functionId;
|
|
@@ -722,29 +513,12 @@ function getFunctionComponentId(context, node) {
|
|
|
722
513
|
default: return null;
|
|
723
514
|
}
|
|
724
515
|
}
|
|
725
|
-
/**
|
|
726
|
-
* Check if a string matches the strict component name pattern.
|
|
727
|
-
* @param name The name to check.
|
|
728
|
-
* @returns `true` if the name matches the strict component name pattern.
|
|
729
|
-
*/
|
|
730
516
|
function isFunctionComponentName(name) {
|
|
731
517
|
return RE_COMPONENT_NAME.test(name);
|
|
732
518
|
}
|
|
733
|
-
/**
|
|
734
|
-
* Check if a string matches the loose component name pattern.
|
|
735
|
-
* @param name The name to check.
|
|
736
|
-
* @returns `true` if the name matches the loose component name pattern.
|
|
737
|
-
*/
|
|
738
519
|
function isFunctionComponentNameLoose(name) {
|
|
739
520
|
return RE_COMPONENT_NAME_LOOSE.test(name);
|
|
740
521
|
}
|
|
741
|
-
/**
|
|
742
|
-
* Check if a function has a loose component name.
|
|
743
|
-
* @param context The rule context.
|
|
744
|
-
* @param fn The function to check.
|
|
745
|
-
* @param allowNone Whether to allow no name.
|
|
746
|
-
* @returns `true` if the function has a loose component name.
|
|
747
|
-
*/
|
|
748
522
|
function isFunctionWithLooseComponentName(context, fn, allowNone = false) {
|
|
749
523
|
const id = getFunctionComponentId(context, fn);
|
|
750
524
|
if (id == null) return allowNone;
|
|
@@ -752,40 +526,18 @@ function isFunctionWithLooseComponentName(context, fn, allowNone = false) {
|
|
|
752
526
|
if (id.type === AST_NODE_TYPES.MemberExpression && Check.isIdentifier(id.property)) return isFunctionComponentNameLoose(id.property.name);
|
|
753
527
|
return false;
|
|
754
528
|
}
|
|
755
|
-
/**
|
|
756
|
-
* Hints for component collector.
|
|
757
|
-
*/
|
|
758
529
|
const FunctionComponentDetectionHint = {
|
|
759
530
|
...JsxDetectionHint,
|
|
760
|
-
/** Exclude functions defined as class methods from component detection. */
|
|
761
531
|
DoNotIncludeFunctionDefinedAsClassMethod: 1n << 11n,
|
|
762
|
-
/** Exclude functions defined as class properties from component detection. */
|
|
763
532
|
DoNotIncludeFunctionDefinedAsClassProperty: 1n << 12n,
|
|
764
|
-
/** Exclude functions defined as object methods from component detection. */
|
|
765
533
|
DoNotIncludeFunctionDefinedAsObjectMethod: 1n << 13n,
|
|
766
|
-
/** Exclude functions defined as array expression elements from component detection. */
|
|
767
534
|
DoNotIncludeFunctionDefinedAsArrayExpressionElement: 1n << 14n,
|
|
768
|
-
/** Exclude functions defined as array pattern elements from component detection. */
|
|
769
535
|
DoNotIncludeFunctionDefinedAsArrayPatternElement: 1n << 15n,
|
|
770
|
-
/** Exclude functions defined as array flatMap callbacks from component detection. */
|
|
771
536
|
DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: 1n << 16n,
|
|
772
|
-
/** Exclude functions defined as array map callbacks from component detection. */
|
|
773
537
|
DoNotIncludeFunctionDefinedAsArrayMapCallback: 1n << 17n,
|
|
774
|
-
/** Exclude functions defined as arbitrary call expression callbacks from component detection. */
|
|
775
538
|
DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback: 1n << 18n
|
|
776
539
|
};
|
|
777
|
-
/**
|
|
778
|
-
* Default component detection hint.
|
|
779
|
-
*/
|
|
780
540
|
const DEFAULT_COMPONENT_DETECTION_HINT = 0n | FunctionComponentDetectionHint.DoNotIncludeJsxWithBigIntValue | FunctionComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | FunctionComponentDetectionHint.DoNotIncludeJsxWithNumberValue | FunctionComponentDetectionHint.DoNotIncludeJsxWithStringValue | FunctionComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback | FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback | FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | FunctionComponentDetectionHint.RequireAllArrayElementsToBeJsx | FunctionComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | FunctionComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx;
|
|
781
|
-
/**
|
|
782
|
-
* Check if the function node is a valid React component definition.
|
|
783
|
-
*
|
|
784
|
-
* @param context The rule context.
|
|
785
|
-
* @param node The function node to analyze.
|
|
786
|
-
* @param hint Component detection hints (bit flags) to customize detection logic.
|
|
787
|
-
* @returns `true` if the node is considered a component definition.
|
|
788
|
-
*/
|
|
789
541
|
function isFunctionComponentDefinition(context, node, hint) {
|
|
790
542
|
if (!isFunctionWithLooseComponentName(context, node, true)) return false;
|
|
791
543
|
let parent = node.parent;
|
|
@@ -828,483 +580,8 @@ function isFunctionComponentDefinition(context, node, hint) {
|
|
|
828
580
|
return significantParent.type !== AST_NODE_TYPES.JSXExpressionContainer;
|
|
829
581
|
}
|
|
830
582
|
|
|
831
|
-
//#endregion
|
|
832
|
-
//#region ../../.pkgs/eff/dist/index.js
|
|
833
|
-
/**
|
|
834
|
-
* Applies a `pipe` method's variadic arguments to an initial value from left
|
|
835
|
-
* to right.
|
|
836
|
-
*
|
|
837
|
-
* **When to use**
|
|
838
|
-
*
|
|
839
|
-
* Use to implement a custom `.pipe(...)` method from JavaScript's `arguments`
|
|
840
|
-
* object.
|
|
841
|
-
*
|
|
842
|
-
* **Details**
|
|
843
|
-
*
|
|
844
|
-
* This helper is intended for implementing `Pipeable.pipe` methods that
|
|
845
|
-
* receive JavaScript's `arguments` object. With no functions it returns the
|
|
846
|
-
* original value; otherwise it feeds each result into the next function.
|
|
847
|
-
*
|
|
848
|
-
* **Example** (Implementing a pipe method)
|
|
849
|
-
*
|
|
850
|
-
* ```ts
|
|
851
|
-
* import { Pipeable } from "effect"
|
|
852
|
-
*
|
|
853
|
-
* class NumberBox {
|
|
854
|
-
* constructor(readonly value: number) {}
|
|
855
|
-
*
|
|
856
|
-
* pipe(..._fns: ReadonlyArray<(value: number) => number>): number {
|
|
857
|
-
* return Pipeable.pipeArguments(this.value, arguments) as number
|
|
858
|
-
* }
|
|
859
|
-
* }
|
|
860
|
-
*
|
|
861
|
-
* const result = new NumberBox(5).pipe(
|
|
862
|
-
* (n) => n + 2,
|
|
863
|
-
* (n) => n * 3
|
|
864
|
-
* )
|
|
865
|
-
* console.log(result) // 21
|
|
866
|
-
* ```
|
|
867
|
-
*
|
|
868
|
-
* @category combinators
|
|
869
|
-
* @since 2.0.0
|
|
870
|
-
*/
|
|
871
|
-
const pipeArguments = (self, args) => {
|
|
872
|
-
switch (args.length) {
|
|
873
|
-
case 0: return self;
|
|
874
|
-
case 1: return args[0](self);
|
|
875
|
-
case 2: return args[1](args[0](self));
|
|
876
|
-
case 3: return args[2](args[1](args[0](self)));
|
|
877
|
-
case 4: return args[3](args[2](args[1](args[0](self))));
|
|
878
|
-
case 5: return args[4](args[3](args[2](args[1](args[0](self)))));
|
|
879
|
-
case 6: return args[5](args[4](args[3](args[2](args[1](args[0](self))))));
|
|
880
|
-
case 7: return args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))));
|
|
881
|
-
case 8: return args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self))))))));
|
|
882
|
-
case 9: return args[8](args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))))));
|
|
883
|
-
default: {
|
|
884
|
-
let ret = self;
|
|
885
|
-
for (let i = 0, len = args.length; i < len; i++) ret = args[i](ret);
|
|
886
|
-
return ret;
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
};
|
|
890
|
-
/**
|
|
891
|
-
* Reusable prototype that implements `Pipeable.pipe`.
|
|
892
|
-
*
|
|
893
|
-
* **When to use**
|
|
894
|
-
*
|
|
895
|
-
* Use when classes or object prototypes can reuse this value when they need the
|
|
896
|
-
* standard pipe implementation backed by `pipeArguments`.
|
|
897
|
-
*
|
|
898
|
-
* @category prototypes
|
|
899
|
-
* @since 3.15.0
|
|
900
|
-
*/
|
|
901
|
-
const Prototype = { pipe() {
|
|
902
|
-
return pipeArguments(this, arguments);
|
|
903
|
-
} };
|
|
904
|
-
/**
|
|
905
|
-
* Provides a base constructor whose instances implement the standard `Pipeable.pipe`
|
|
906
|
-
* method.
|
|
907
|
-
*
|
|
908
|
-
* **When to use**
|
|
909
|
-
*
|
|
910
|
-
* Use when you need to define a class that supports Effect-style method
|
|
911
|
-
* chaining through `.pipe(...)`.
|
|
912
|
-
*
|
|
913
|
-
* @category constructors
|
|
914
|
-
* @since 3.15.0
|
|
915
|
-
*/
|
|
916
|
-
const Class = (function() {
|
|
917
|
-
function PipeableBase() {}
|
|
918
|
-
PipeableBase.prototype = Prototype;
|
|
919
|
-
return PipeableBase;
|
|
920
|
-
})();
|
|
921
|
-
/**
|
|
922
|
-
* Provides small helpers for defining and reusing TypeScript functions.
|
|
923
|
-
*
|
|
924
|
-
* The main helpers are `pipe` and `flow` for left-to-right composition and
|
|
925
|
-
* `dual` for APIs that support both direct and pipe-friendly call styles. The
|
|
926
|
-
* module also contains small identity, constant, tuple, type-level, and
|
|
927
|
-
* memoization helpers used across the library.
|
|
928
|
-
*
|
|
929
|
-
* @since 2.0.0
|
|
930
|
-
*/
|
|
931
|
-
/**
|
|
932
|
-
* Creates a function that can be called in data-first style or data-last
|
|
933
|
-
* (`pipe`-friendly) style.
|
|
934
|
-
*
|
|
935
|
-
* **When to use**
|
|
936
|
-
*
|
|
937
|
-
* Use to expose one implementation through both direct and `pipe`-friendly
|
|
938
|
-
* call styles.
|
|
939
|
-
*
|
|
940
|
-
* **Details**
|
|
941
|
-
*
|
|
942
|
-
* Pass either the arity of the uncurried function or a predicate that decides
|
|
943
|
-
* whether the current call is data-first. Arity is the common case. Use a
|
|
944
|
-
* predicate when optional arguments make arity ambiguous.
|
|
945
|
-
*
|
|
946
|
-
* **Example** (Selecting data-first or data-last style by arity)
|
|
947
|
-
*
|
|
948
|
-
* ```ts
|
|
949
|
-
* import { Function, pipe } from "effect"
|
|
950
|
-
*
|
|
951
|
-
* const sum = Function.dual<
|
|
952
|
-
* (that: number) => (self: number) => number,
|
|
953
|
-
* (self: number, that: number) => number
|
|
954
|
-
* >(2, (self, that) => self + that)
|
|
955
|
-
*
|
|
956
|
-
* console.log(sum(2, 3)) // 5
|
|
957
|
-
* console.log(pipe(2, sum(3))) // 5
|
|
958
|
-
* ```
|
|
959
|
-
*
|
|
960
|
-
* **Example** (Defining overloads with call signatures)
|
|
961
|
-
*
|
|
962
|
-
* ```ts
|
|
963
|
-
* import { Function, pipe } from "effect"
|
|
964
|
-
*
|
|
965
|
-
* const sum: {
|
|
966
|
-
* (that: number): (self: number) => number
|
|
967
|
-
* (self: number, that: number): number
|
|
968
|
-
* } = Function.dual(2, (self: number, that: number): number => self + that)
|
|
969
|
-
*
|
|
970
|
-
* console.log(sum(2, 3)) // 5
|
|
971
|
-
* console.log(pipe(2, sum(3))) // 5
|
|
972
|
-
* ```
|
|
973
|
-
*
|
|
974
|
-
* **Example** (Selecting data-first or data-last style with a predicate)
|
|
975
|
-
*
|
|
976
|
-
* ```ts
|
|
977
|
-
* import { Function, pipe } from "effect"
|
|
978
|
-
*
|
|
979
|
-
* const sum = Function.dual<
|
|
980
|
-
* (that: number) => (self: number) => number,
|
|
981
|
-
* (self: number, that: number) => number
|
|
982
|
-
* >(
|
|
983
|
-
* (args) => args.length === 2,
|
|
984
|
-
* (self, that) => self + that
|
|
985
|
-
* )
|
|
986
|
-
*
|
|
987
|
-
* console.log(sum(2, 3)) // 5
|
|
988
|
-
* console.log(pipe(2, sum(3))) // 5
|
|
989
|
-
* ```
|
|
990
|
-
*
|
|
991
|
-
* @category combinators
|
|
992
|
-
* @since 2.0.0
|
|
993
|
-
*/
|
|
994
|
-
const dual = function(arity, body) {
|
|
995
|
-
if (typeof arity === "function") return function() {
|
|
996
|
-
return arity(arguments) ? body.apply(this, arguments) : ((self) => body(self, ...arguments));
|
|
997
|
-
};
|
|
998
|
-
switch (arity) {
|
|
999
|
-
case 0:
|
|
1000
|
-
case 1: throw new RangeError(`Invalid arity ${arity}`);
|
|
1001
|
-
case 2: return function(a, b) {
|
|
1002
|
-
if (arguments.length >= 2) return body(a, b);
|
|
1003
|
-
return function(self) {
|
|
1004
|
-
return body(self, a);
|
|
1005
|
-
};
|
|
1006
|
-
};
|
|
1007
|
-
case 3: return function(a, b, c) {
|
|
1008
|
-
if (arguments.length >= 3) return body(a, b, c);
|
|
1009
|
-
return function(self) {
|
|
1010
|
-
return body(self, a, b);
|
|
1011
|
-
};
|
|
1012
|
-
};
|
|
1013
|
-
default: return function() {
|
|
1014
|
-
if (arguments.length >= arity) return body.apply(this, arguments);
|
|
1015
|
-
const args = arguments;
|
|
1016
|
-
return function(self) {
|
|
1017
|
-
return body(self, ...args);
|
|
1018
|
-
};
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
};
|
|
1022
|
-
/**
|
|
1023
|
-
* Returns its input argument unchanged.
|
|
1024
|
-
*
|
|
1025
|
-
* **When to use**
|
|
1026
|
-
*
|
|
1027
|
-
* Use to return a value unchanged where a function is required.
|
|
1028
|
-
*
|
|
1029
|
-
* **Example** (Returning the same value)
|
|
1030
|
-
*
|
|
1031
|
-
* ```ts
|
|
1032
|
-
* import { identity } from "effect"
|
|
1033
|
-
* import * as assert from "node:assert"
|
|
1034
|
-
*
|
|
1035
|
-
* assert.deepStrictEqual(identity(5), 5)
|
|
1036
|
-
* ```
|
|
1037
|
-
*
|
|
1038
|
-
* @category combinators
|
|
1039
|
-
* @since 2.0.0
|
|
1040
|
-
*/
|
|
1041
|
-
const identity = (a) => a;
|
|
1042
|
-
/**
|
|
1043
|
-
* Returns the input value with a different static type.
|
|
1044
|
-
*
|
|
1045
|
-
* **When to use**
|
|
1046
|
-
*
|
|
1047
|
-
* Use when you need an explicit type-level cast and accept that the value is
|
|
1048
|
-
* returned unchanged at runtime.
|
|
1049
|
-
*
|
|
1050
|
-
* **Gotchas**
|
|
1051
|
-
*
|
|
1052
|
-
* This is a type-level cast only; it performs no runtime validation or
|
|
1053
|
-
* conversion.
|
|
1054
|
-
*
|
|
1055
|
-
* @see {@link satisfies} for checking assignability without changing the resulting type
|
|
1056
|
-
*
|
|
1057
|
-
* @category utility types
|
|
1058
|
-
* @since 4.0.0
|
|
1059
|
-
*/
|
|
1060
|
-
const cast = identity;
|
|
1061
|
-
/**
|
|
1062
|
-
* Creates a zero-argument function that always returns the provided value.
|
|
1063
|
-
*
|
|
1064
|
-
* **When to use**
|
|
1065
|
-
*
|
|
1066
|
-
* Use when you need a thunk or callback that returns the same value on every
|
|
1067
|
-
* invocation.
|
|
1068
|
-
*
|
|
1069
|
-
* **Example** (Creating a constant thunk)
|
|
1070
|
-
*
|
|
1071
|
-
* ```ts
|
|
1072
|
-
* import { Function } from "effect"
|
|
1073
|
-
* import * as assert from "node:assert"
|
|
1074
|
-
*
|
|
1075
|
-
* const constNull = Function.constant(null)
|
|
1076
|
-
*
|
|
1077
|
-
* assert.deepStrictEqual(constNull(), null)
|
|
1078
|
-
* assert.deepStrictEqual(constNull(), null)
|
|
1079
|
-
* ```
|
|
1080
|
-
*
|
|
1081
|
-
* @category constructors
|
|
1082
|
-
* @since 2.0.0
|
|
1083
|
-
*/
|
|
1084
|
-
const constant = (value) => () => value;
|
|
1085
|
-
/**
|
|
1086
|
-
* Returns `true` when called.
|
|
1087
|
-
*
|
|
1088
|
-
* **When to use**
|
|
1089
|
-
*
|
|
1090
|
-
* Use when you need a thunk that returns `true` on every invocation.
|
|
1091
|
-
*
|
|
1092
|
-
* **Example** (Returning true from a thunk)
|
|
1093
|
-
*
|
|
1094
|
-
* ```ts
|
|
1095
|
-
* import { Function } from "effect"
|
|
1096
|
-
* import * as assert from "node:assert"
|
|
1097
|
-
*
|
|
1098
|
-
* assert.deepStrictEqual(Function.constTrue(), true)
|
|
1099
|
-
* ```
|
|
1100
|
-
*
|
|
1101
|
-
* @category constants
|
|
1102
|
-
* @since 2.0.0
|
|
1103
|
-
*/
|
|
1104
|
-
const constTrue = constant(true);
|
|
1105
|
-
/**
|
|
1106
|
-
* Returns `false` when called.
|
|
1107
|
-
*
|
|
1108
|
-
* **When to use**
|
|
1109
|
-
*
|
|
1110
|
-
* Use when you need a thunk that returns `false` on every invocation.
|
|
1111
|
-
*
|
|
1112
|
-
* **Example** (Returning false from a thunk)
|
|
1113
|
-
*
|
|
1114
|
-
* ```ts
|
|
1115
|
-
* import { Function } from "effect"
|
|
1116
|
-
* import * as assert from "node:assert"
|
|
1117
|
-
*
|
|
1118
|
-
* assert.deepStrictEqual(Function.constFalse(), false)
|
|
1119
|
-
* ```
|
|
1120
|
-
*
|
|
1121
|
-
* @category constants
|
|
1122
|
-
* @since 2.0.0
|
|
1123
|
-
*/
|
|
1124
|
-
const constFalse = constant(false);
|
|
1125
|
-
/**
|
|
1126
|
-
* Returns `null` when called.
|
|
1127
|
-
*
|
|
1128
|
-
* **When to use**
|
|
1129
|
-
*
|
|
1130
|
-
* Use when you need a thunk that returns `null` on every invocation.
|
|
1131
|
-
*
|
|
1132
|
-
* **Example** (Returning null from a thunk)
|
|
1133
|
-
*
|
|
1134
|
-
* ```ts
|
|
1135
|
-
* import { Function } from "effect"
|
|
1136
|
-
* import * as assert from "node:assert"
|
|
1137
|
-
*
|
|
1138
|
-
* assert.deepStrictEqual(Function.constNull(), null)
|
|
1139
|
-
* ```
|
|
1140
|
-
*
|
|
1141
|
-
* @category constants
|
|
1142
|
-
* @since 2.0.0
|
|
1143
|
-
*/
|
|
1144
|
-
const constNull = constant(null);
|
|
1145
|
-
/**
|
|
1146
|
-
* Returns `undefined` when called.
|
|
1147
|
-
*
|
|
1148
|
-
* **When to use**
|
|
1149
|
-
*
|
|
1150
|
-
* Use when you need a thunk that returns `undefined` on every invocation.
|
|
1151
|
-
*
|
|
1152
|
-
* **Example** (Returning undefined from a thunk)
|
|
1153
|
-
*
|
|
1154
|
-
* ```ts
|
|
1155
|
-
* import { Function } from "effect"
|
|
1156
|
-
* import * as assert from "node:assert"
|
|
1157
|
-
*
|
|
1158
|
-
* assert.deepStrictEqual(Function.constUndefined(), undefined)
|
|
1159
|
-
* ```
|
|
1160
|
-
*
|
|
1161
|
-
* @category constants
|
|
1162
|
-
* @since 2.0.0
|
|
1163
|
-
*/
|
|
1164
|
-
const constUndefined = constant(void 0);
|
|
1165
|
-
/**
|
|
1166
|
-
* 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`.
|
|
1167
|
-
* The result is obtained by first applying the `ab` function to `a` and then applying the `bc` function to the result of `ab`.
|
|
1168
|
-
*
|
|
1169
|
-
* **When to use**
|
|
1170
|
-
*
|
|
1171
|
-
* Use to compose exactly two unary functions into a reusable unary function.
|
|
1172
|
-
*
|
|
1173
|
-
* **Example** (Composing two functions)
|
|
1174
|
-
*
|
|
1175
|
-
* ```ts
|
|
1176
|
-
* import { Function } from "effect"
|
|
1177
|
-
* import * as assert from "node:assert"
|
|
1178
|
-
*
|
|
1179
|
-
* const increment = (n: number) => n + 1
|
|
1180
|
-
* const square = (n: number) => n * n
|
|
1181
|
-
*
|
|
1182
|
-
* assert.strictEqual(Function.compose(increment, square)(2), 9)
|
|
1183
|
-
* ```
|
|
1184
|
-
*
|
|
1185
|
-
* @see {@link flow} for composing a left-to-right sequence of functions
|
|
1186
|
-
* @see {@link pipe} for applying a value through a left-to-right sequence immediately
|
|
1187
|
-
*
|
|
1188
|
-
* @category combinators
|
|
1189
|
-
* @since 2.0.0
|
|
1190
|
-
*/
|
|
1191
|
-
const compose = dual(2, (ab, bc) => (a) => bc(ab(a)));
|
|
1192
|
-
/**
|
|
1193
|
-
* Marks an impossible branch by accepting a `never` value and returning any
|
|
1194
|
-
* type.
|
|
1195
|
-
*
|
|
1196
|
-
* **When to use**
|
|
1197
|
-
*
|
|
1198
|
-
* Use when you need a return value in a branch that exhaustive checks prove
|
|
1199
|
-
* cannot be reached.
|
|
1200
|
-
*
|
|
1201
|
-
* **Gotchas**
|
|
1202
|
-
*
|
|
1203
|
-
* Calling `absurd` throws, because a value of type `never` should be
|
|
1204
|
-
* impossible at runtime.
|
|
1205
|
-
*
|
|
1206
|
-
* **Example** (Handling impossible values)
|
|
1207
|
-
*
|
|
1208
|
-
* ```ts
|
|
1209
|
-
* import { absurd } from "effect"
|
|
1210
|
-
*
|
|
1211
|
-
* const handleNever = (value: never) => {
|
|
1212
|
-
* return absurd(value) // This will throw an error if called
|
|
1213
|
-
* }
|
|
1214
|
-
* ```
|
|
1215
|
-
*
|
|
1216
|
-
* @category utility types
|
|
1217
|
-
* @since 2.0.0
|
|
1218
|
-
*/
|
|
1219
|
-
const absurd = (_) => {
|
|
1220
|
-
throw new Error("Called `absurd` function which should be uncallable");
|
|
1221
|
-
};
|
|
1222
|
-
/**
|
|
1223
|
-
* Creates a compile-time placeholder for a value of any type.
|
|
1224
|
-
*
|
|
1225
|
-
* **When to use**
|
|
1226
|
-
*
|
|
1227
|
-
* Use as a temporary typed placeholder while developing incomplete code.
|
|
1228
|
-
*
|
|
1229
|
-
* **Gotchas**
|
|
1230
|
-
*
|
|
1231
|
-
* `hole` is intended for temporary development use. If the placeholder is
|
|
1232
|
-
* evaluated at runtime, it throws.
|
|
1233
|
-
*
|
|
1234
|
-
* **Example** (Creating a development placeholder)
|
|
1235
|
-
*
|
|
1236
|
-
* ```ts
|
|
1237
|
-
* import { hole } from "effect"
|
|
1238
|
-
*
|
|
1239
|
-
* // Intentionally not called: `hole` throws if the placeholder is evaluated.
|
|
1240
|
-
* const buildUser = (id: number): { readonly id: number; readonly name: string } => ({
|
|
1241
|
-
* id,
|
|
1242
|
-
* name: hole<string>()
|
|
1243
|
-
* })
|
|
1244
|
-
*
|
|
1245
|
-
* console.log(typeof buildUser) // "function"
|
|
1246
|
-
* ```
|
|
1247
|
-
*
|
|
1248
|
-
* @category utility types
|
|
1249
|
-
* @since 2.0.0
|
|
1250
|
-
*/
|
|
1251
|
-
const hole = cast(absurd);
|
|
1252
|
-
/**
|
|
1253
|
-
* Drops the longest prefix of elements from an array that satisfy the given predicate.
|
|
1254
|
-
*
|
|
1255
|
-
* Supports both data-first and data-last (`pipe`-friendly) call styles.
|
|
1256
|
-
*
|
|
1257
|
-
* @param pred - The predicate to test each element with.
|
|
1258
|
-
* @returns A new array without the matching prefix.
|
|
1259
|
-
* @example
|
|
1260
|
-
* ```ts
|
|
1261
|
-
* import * as assert from "node:assert"
|
|
1262
|
-
* import { dropWhile, pipe } from "@local/eff"
|
|
1263
|
-
*
|
|
1264
|
-
* // data-first
|
|
1265
|
-
* assert.deepStrictEqual(dropWhile([1, 2, 3, 2, 1], (n: number) => n < 3), [3, 2, 1])
|
|
1266
|
-
*
|
|
1267
|
-
* // data-last
|
|
1268
|
-
* assert.deepStrictEqual(pipe([1, 2, 3, 2, 1], dropWhile((n: number) => n < 3)), [3, 2, 1])
|
|
1269
|
-
* ```
|
|
1270
|
-
* @category array
|
|
1271
|
-
*/
|
|
1272
|
-
const dropWhile = dual(2, (xs, pred) => {
|
|
1273
|
-
const len = xs.length;
|
|
1274
|
-
let idx = 0;
|
|
1275
|
-
while (idx < len && pred(xs[idx])) idx++;
|
|
1276
|
-
return xs.slice(idx);
|
|
1277
|
-
});
|
|
1278
|
-
/**
|
|
1279
|
-
* Takes the longest prefix of elements from an array that satisfy the given predicate.
|
|
1280
|
-
*
|
|
1281
|
-
* Supports both data-first and data-last (`pipe`-friendly) call styles.
|
|
1282
|
-
*
|
|
1283
|
-
* @param pred - The predicate to test each element with.
|
|
1284
|
-
* @returns A new array containing only the matching prefix.
|
|
1285
|
-
* @example
|
|
1286
|
-
* ```ts
|
|
1287
|
-
* import * as assert from "node:assert"
|
|
1288
|
-
* import { pipe, takeWhile } from "@local/eff"
|
|
1289
|
-
*
|
|
1290
|
-
* // data-first
|
|
1291
|
-
* assert.deepStrictEqual(takeWhile([1, 2, 3, 2, 1], (n: number) => n < 3), [1, 2])
|
|
1292
|
-
*
|
|
1293
|
-
* // data-last
|
|
1294
|
-
* assert.deepStrictEqual(pipe([1, 2, 3, 2, 1], takeWhile((n: number) => n < 3)), [1, 2])
|
|
1295
|
-
* ```
|
|
1296
|
-
* @category array
|
|
1297
|
-
*/
|
|
1298
|
-
const takeWhile = dual(2, (xs, pred) => {
|
|
1299
|
-
const len = xs.length;
|
|
1300
|
-
let idx = 0;
|
|
1301
|
-
while (idx < len && pred(xs[idx])) idx++;
|
|
1302
|
-
return xs.slice(0, idx);
|
|
1303
|
-
});
|
|
1304
|
-
|
|
1305
583
|
//#endregion
|
|
1306
584
|
//#region src/hook.ts
|
|
1307
|
-
/** The names of React's built-in hooks. */
|
|
1308
585
|
const REACT_BUILTIN_HOOK_NAMES = [
|
|
1309
586
|
"use",
|
|
1310
587
|
"useActionState",
|
|
@@ -1326,55 +603,29 @@ const REACT_BUILTIN_HOOK_NAMES = [
|
|
|
1326
603
|
"useSyncExternalStore",
|
|
1327
604
|
"useTransition"
|
|
1328
605
|
];
|
|
1329
|
-
/**
|
|
1330
|
-
* Check if the name is a hook name (starts with `use` followed by an uppercase letter or digit).
|
|
1331
|
-
* @param name The name of the identifier to check.
|
|
1332
|
-
* @returns `true` if the name is a hook name.
|
|
1333
|
-
* @see https://github.com/facebook/react/blob/1d6c8168db1d82713202e842df3167787ffa00ed/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts#L16
|
|
1334
|
-
*/
|
|
1335
606
|
function isHookName(name) {
|
|
1336
607
|
return name === "use" || /^use[A-Z0-9]/.test(name);
|
|
1337
608
|
}
|
|
1338
|
-
/**
|
|
1339
|
-
* Checks if the given node is a hook identifier.
|
|
1340
|
-
* @param id The AST node to check.
|
|
1341
|
-
* @returns `true` if the node is a hook identifier or member expression with hook name, `false` otherwise.
|
|
1342
|
-
*/
|
|
1343
609
|
function isHookId(id) {
|
|
1344
610
|
switch (id.type) {
|
|
1345
611
|
case AST_NODE_TYPES.Identifier: return isHookName(id.name);
|
|
1346
|
-
case AST_NODE_TYPES.MemberExpression: return "name"
|
|
612
|
+
case AST_NODE_TYPES.MemberExpression: return hasProperty(id.property, "name") && isHookName(id.property.name);
|
|
1347
613
|
default: return false;
|
|
1348
614
|
}
|
|
1349
615
|
}
|
|
1350
|
-
/**
|
|
1351
|
-
* Checks if the given expression is a hook tag (callee / tagged template tag).
|
|
1352
|
-
* @param tag The expression node to check.
|
|
1353
|
-
* @returns `true` if the expression is a hook identifier or member expression with hook name, `false` otherwise.
|
|
1354
|
-
*/
|
|
1355
616
|
function isHookTag(tag) {
|
|
1356
617
|
if (tag == null) return false;
|
|
1357
618
|
return isHookId(Extract.unwrap(tag));
|
|
1358
619
|
}
|
|
1359
|
-
/**
|
|
1360
|
-
* Check if the function node is a hook definition based on its name.
|
|
1361
|
-
* @param node The function node to check.
|
|
1362
|
-
* @returns `true` if the function is a hook definition.
|
|
1363
|
-
*/
|
|
1364
620
|
function isHookDefinition(node) {
|
|
1365
621
|
if (node == null) return false;
|
|
1366
622
|
const id = getFunctionId(node);
|
|
1367
623
|
switch (id?.type) {
|
|
1368
624
|
case AST_NODE_TYPES.Identifier: return isHookName(id.name);
|
|
1369
|
-
case AST_NODE_TYPES.MemberExpression: return "name"
|
|
625
|
+
case AST_NODE_TYPES.MemberExpression: return hasProperty(id.property, "name") && isHookName(id.property.name);
|
|
1370
626
|
default: return false;
|
|
1371
627
|
}
|
|
1372
628
|
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Check if the node is a React Hook call by its name.
|
|
1375
|
-
* @param node The node to check.
|
|
1376
|
-
* @returns `true` if the node is a React Hook call, `false` otherwise.
|
|
1377
|
-
*/
|
|
1378
629
|
function isHookCall(node) {
|
|
1379
630
|
if (node == null) return false;
|
|
1380
631
|
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
@@ -1382,12 +633,6 @@ function isHookCall(node) {
|
|
|
1382
633
|
if (name == null) return false;
|
|
1383
634
|
return isHookName(name);
|
|
1384
635
|
}
|
|
1385
|
-
/**
|
|
1386
|
-
* Check if the node is a useRef-like call (ex: `useRef` or a custom ref hook).
|
|
1387
|
-
* @param node The AST node to check.
|
|
1388
|
-
* @param additionalRefHooks Regex pattern matching custom hooks that should be treated as ref hooks.
|
|
1389
|
-
* @returns `true` if the node is a useRef-like call.
|
|
1390
|
-
*/
|
|
1391
636
|
function isUseRefLikeCall(node, additionalRefHooks = { test: constFalse }) {
|
|
1392
637
|
if (node == null) return false;
|
|
1393
638
|
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
@@ -1395,12 +640,6 @@ function isUseRefLikeCall(node, additionalRefHooks = { test: constFalse }) {
|
|
|
1395
640
|
if (name == null) return false;
|
|
1396
641
|
return name === "useRef" || additionalRefHooks.test(name);
|
|
1397
642
|
}
|
|
1398
|
-
/**
|
|
1399
|
-
* Check if the node is a useState-like call (ex: `useState` or a custom state hook).
|
|
1400
|
-
* @param node The AST node to check.
|
|
1401
|
-
* @param additionalStateHooks Regex pattern matching custom hooks that should be treated as state hooks.
|
|
1402
|
-
* @returns `true` if the node is a useState-like call.
|
|
1403
|
-
*/
|
|
1404
643
|
function isUseStateLikeCall(node, additionalStateHooks = { test: constFalse }) {
|
|
1405
644
|
if (node == null) return false;
|
|
1406
645
|
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
@@ -1408,12 +647,6 @@ function isUseStateLikeCall(node, additionalStateHooks = { test: constFalse }) {
|
|
|
1408
647
|
if (name == null) return false;
|
|
1409
648
|
return name === "useState" || additionalStateHooks.test(name);
|
|
1410
649
|
}
|
|
1411
|
-
/**
|
|
1412
|
-
* Check if the node is a useEffect-like call (ex: `useEffect`, `useLayoutEffect`, or a custom effect hook).
|
|
1413
|
-
* @param node The AST node to check.
|
|
1414
|
-
* @param additionalEffectHooks Regex pattern matching custom hooks that should be treated as effect hooks.
|
|
1415
|
-
* @returns `true` if the node is a useEffect-like call.
|
|
1416
|
-
*/
|
|
1417
650
|
function isUseEffectLikeCall(node, additionalEffectHooks = { test: constFalse }) {
|
|
1418
651
|
if (node == null) return false;
|
|
1419
652
|
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
@@ -1421,21 +654,11 @@ function isUseEffectLikeCall(node, additionalEffectHooks = { test: constFalse })
|
|
|
1421
654
|
if (name == null) return false;
|
|
1422
655
|
return /^use\w*Effect$/u.test(name) || additionalEffectHooks.test(name);
|
|
1423
656
|
}
|
|
1424
|
-
/**
|
|
1425
|
-
* Check if the node is the setup callback passed to a useEffect-like call.
|
|
1426
|
-
* @param node The AST node to check.
|
|
1427
|
-
* @returns `true` if the node is a useEffect setup callback.
|
|
1428
|
-
*/
|
|
1429
657
|
function isUseEffectSetupCallback(node) {
|
|
1430
658
|
if (node == null) return false;
|
|
1431
659
|
const expr = Extract.unwrap(node);
|
|
1432
660
|
return expr.parent?.type === AST_NODE_TYPES.CallExpression && expr.parent.arguments.at(0) === expr && isUseEffectLikeCall(expr.parent);
|
|
1433
661
|
}
|
|
1434
|
-
/**
|
|
1435
|
-
* Check if the node is the cleanup callback returned by a useEffect-like setup callback.
|
|
1436
|
-
* @param node The AST node to check.
|
|
1437
|
-
* @returns `true` if the node is a useEffect cleanup callback.
|
|
1438
|
-
*/
|
|
1439
662
|
function isUseEffectCleanupCallback(node) {
|
|
1440
663
|
if (node == null) return false;
|
|
1441
664
|
const expr = Extract.unwrap(node);
|
|
@@ -1447,12 +670,6 @@ function isUseEffectCleanupCallback(node) {
|
|
|
1447
670
|
|
|
1448
671
|
//#endregion
|
|
1449
672
|
//#region src/function-component-collector.ts
|
|
1450
|
-
/**
|
|
1451
|
-
* Get an api and visitor object for the rule to collect function components.
|
|
1452
|
-
* @param context The ESLint rule context.
|
|
1453
|
-
* @param options The options to use.
|
|
1454
|
-
* @returns The api and visitor of the collector.
|
|
1455
|
-
*/
|
|
1456
673
|
function getFunctionComponentCollector(context, options = {}) {
|
|
1457
674
|
const { collectDisplayName = false, hint = DEFAULT_COMPONENT_DETECTION_HINT } = options;
|
|
1458
675
|
const functionEntries = [];
|
|
@@ -1548,11 +765,6 @@ function getFunctionComponentCollector(context, options = {}) {
|
|
|
1548
765
|
|
|
1549
766
|
//#endregion
|
|
1550
767
|
//#region src/hook-collector.ts
|
|
1551
|
-
/**
|
|
1552
|
-
* Get an api and visitor object for the rule to collect hooks.
|
|
1553
|
-
* @param context The ESLint rule context.
|
|
1554
|
-
* @returns The api and visitor of the collector.
|
|
1555
|
-
*/
|
|
1556
768
|
function getHookCollector(context) {
|
|
1557
769
|
const functionEntries = [];
|
|
1558
770
|
const hooks = /* @__PURE__ */ new Map();
|
|
@@ -1618,25 +830,8 @@ function getHookCollector(context) {
|
|
|
1618
830
|
|
|
1619
831
|
//#endregion
|
|
1620
832
|
//#region src/jsx-config.ts
|
|
1621
|
-
/**
|
|
1622
|
-
* Weak‑map cache keyed by `sourceCode` so that the (potentially expensive)
|
|
1623
|
-
* pragma‑scanning pass runs at most once per file.
|
|
1624
|
-
*/
|
|
1625
833
|
const annotationCache = /* @__PURE__ */ new WeakMap();
|
|
1626
|
-
/**
|
|
1627
|
-
* Weak‑map cache for the fully‑merged config (compiler options + annotation).
|
|
1628
|
-
*/
|
|
1629
834
|
const mergedCache = /* @__PURE__ */ new WeakMap();
|
|
1630
|
-
/**
|
|
1631
|
-
* Read JSX configuration from the TypeScript compiler options exposed by the
|
|
1632
|
-
* parser services.
|
|
1633
|
-
*
|
|
1634
|
-
* Falls back to sensible React defaults when no compiler options are
|
|
1635
|
-
* available (e.g. when the file is parsed without type information).
|
|
1636
|
-
*
|
|
1637
|
-
* @param context The ESLint rule context.
|
|
1638
|
-
* @returns Fully‑populated `JsxConfig` derived from compiler options.
|
|
1639
|
-
*/
|
|
1640
835
|
function getJsxConfigFromCompilerOptions(context) {
|
|
1641
836
|
const options = context.sourceCode.parserServices?.program?.getCompilerOptions() ?? {};
|
|
1642
837
|
return {
|
|
@@ -1646,16 +841,6 @@ function getJsxConfigFromCompilerOptions(context) {
|
|
|
1646
841
|
jsxImportSource: options.jsxImportSource ?? "react"
|
|
1647
842
|
};
|
|
1648
843
|
}
|
|
1649
|
-
/**
|
|
1650
|
-
* Extract JSX configuration from `@jsx`, `@jsxFrag`, `@jsxRuntime` and
|
|
1651
|
-
* `@jsxImportSource` pragma comments in the source file.
|
|
1652
|
-
*
|
|
1653
|
-
* The result is cached per `sourceCode` instance via a `WeakMap` so that
|
|
1654
|
-
* repeated calls from different rules analysing the same file are free.
|
|
1655
|
-
*
|
|
1656
|
-
* @param context The ESLint rule context.
|
|
1657
|
-
* @returns Partial `JsxConfig` containing only the values found in pragmas.
|
|
1658
|
-
*/
|
|
1659
844
|
function getJsxConfigFromAnnotation(context) {
|
|
1660
845
|
const cached = annotationCache.get(context.sourceCode);
|
|
1661
846
|
if (cached != null) return cached;
|
|
@@ -1679,17 +864,6 @@ function getJsxConfigFromAnnotation(context) {
|
|
|
1679
864
|
annotationCache.set(context.sourceCode, options);
|
|
1680
865
|
return options;
|
|
1681
866
|
}
|
|
1682
|
-
/**
|
|
1683
|
-
* Get the fully‑merged JSX configuration for the current file.
|
|
1684
|
-
*
|
|
1685
|
-
* Compiler options provide the base values; pragma annotations found in the
|
|
1686
|
-
* source override them where present. The result is cached per `sourceCode`.
|
|
1687
|
-
*
|
|
1688
|
-
* This is the main entry‑point most consumers should use.
|
|
1689
|
-
*
|
|
1690
|
-
* @param context The ESLint rule context.
|
|
1691
|
-
* @returns Fully‑populated, merged `JsxConfig`.
|
|
1692
|
-
*/
|
|
1693
867
|
function getJsxConfig(context) {
|
|
1694
868
|
const cached = mergedCache.get(context.sourceCode);
|
|
1695
869
|
if (cached != null) return cached;
|
|
@@ -1710,113 +884,30 @@ function isFlagSetOnObject(obj, flag) {
|
|
|
1710
884
|
return isFlagSet(obj.flags, flag);
|
|
1711
885
|
}
|
|
1712
886
|
const isTypeFlagSet = isFlagSetOnObject;
|
|
1713
|
-
/**
|
|
1714
|
-
* Check if the type is a boolean literal type.
|
|
1715
|
-
* @param type The type to check.
|
|
1716
|
-
* @returns `true` if the type is a boolean literal type.
|
|
1717
|
-
*/
|
|
1718
887
|
function isBooleanLiteralType(type) {
|
|
1719
888
|
return isTypeFlagSet(type, ts.TypeFlags.BooleanLiteral);
|
|
1720
889
|
}
|
|
1721
|
-
/**
|
|
1722
|
-
* Check if the type is the `false` literal type.
|
|
1723
|
-
* @internal
|
|
1724
|
-
*/
|
|
1725
890
|
const isFalseLiteralType = (type) => isBooleanLiteralType(type) && type.intrinsicName === "false";
|
|
1726
|
-
/**
|
|
1727
|
-
* Check if the type is the `true` literal type.
|
|
1728
|
-
* @internal
|
|
1729
|
-
*/
|
|
1730
891
|
const isTrueLiteralType = (type) => isBooleanLiteralType(type) && type.intrinsicName === "true";
|
|
1731
|
-
/**
|
|
1732
|
-
* Check if the type is an any-like type.
|
|
1733
|
-
* @internal
|
|
1734
|
-
*/
|
|
1735
892
|
const isAnyType = (type) => isTypeFlagSet(type, ts.TypeFlags.TypeParameter | ts.TypeFlags.Any);
|
|
1736
|
-
/**
|
|
1737
|
-
* Check if the type is a bigint-like type.
|
|
1738
|
-
* @internal
|
|
1739
|
-
*/
|
|
1740
893
|
const isBigIntType = (type) => isTypeFlagSet(type, ts.TypeFlags.BigIntLike);
|
|
1741
|
-
/**
|
|
1742
|
-
* Check if the type is a boolean-like type.
|
|
1743
|
-
* @internal
|
|
1744
|
-
*/
|
|
1745
894
|
const isBooleanType = (type) => isTypeFlagSet(type, ts.TypeFlags.BooleanLike);
|
|
1746
|
-
/**
|
|
1747
|
-
* Check if the type is an enum-like type.
|
|
1748
|
-
* @internal
|
|
1749
|
-
*/
|
|
1750
895
|
const isEnumType = (type) => isTypeFlagSet(type, ts.TypeFlags.EnumLike);
|
|
1751
|
-
/**
|
|
1752
|
-
* Check if the type is a falsy bigint literal type.
|
|
1753
|
-
* @internal
|
|
1754
|
-
*/
|
|
1755
896
|
const isFalsyBigIntType = (type) => type.isLiteral() && isMatching({ value: { base10Value: "0" } }, type);
|
|
1756
|
-
/**
|
|
1757
|
-
* Check if the type is a falsy number literal type.
|
|
1758
|
-
* @internal
|
|
1759
|
-
*/
|
|
1760
897
|
const isFalsyNumberType = (type) => type.isNumberLiteral() && type.value === 0;
|
|
1761
|
-
/**
|
|
1762
|
-
* Check if the type is a falsy string literal type.
|
|
1763
|
-
* @internal
|
|
1764
|
-
*/
|
|
1765
898
|
const isFalsyStringType = (type) => type.isStringLiteral() && type.value === "";
|
|
1766
|
-
/**
|
|
1767
|
-
* Check if the type is the never type.
|
|
1768
|
-
* @internal
|
|
1769
|
-
*/
|
|
1770
899
|
const isNeverType = (type) => isTypeFlagSet(type, ts.TypeFlags.Never);
|
|
1771
|
-
/**
|
|
1772
|
-
* Check if the type is a nullish type (null, undefined, or void).
|
|
1773
|
-
* @internal
|
|
1774
|
-
*/
|
|
1775
900
|
const isNullishType = (type) => isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.VoidLike);
|
|
1776
|
-
/**
|
|
1777
|
-
* Check if the type is a number-like type.
|
|
1778
|
-
* @internal
|
|
1779
|
-
*/
|
|
1780
901
|
const isNumberType = (type) => isTypeFlagSet(type, ts.TypeFlags.NumberLike);
|
|
1781
|
-
/**
|
|
1782
|
-
* Check if the type is an object type.
|
|
1783
|
-
* @internal
|
|
1784
|
-
*/
|
|
1785
902
|
const isObjectType = (type) => !isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.VoidLike | ts.TypeFlags.BooleanLike | ts.TypeFlags.StringLike | ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike | ts.TypeFlags.TypeParameter | ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never);
|
|
1786
|
-
/**
|
|
1787
|
-
* Check if the type is a string-like type.
|
|
1788
|
-
* @internal
|
|
1789
|
-
*/
|
|
1790
903
|
const isStringType = (type) => isTypeFlagSet(type, ts.TypeFlags.StringLike);
|
|
1791
|
-
/**
|
|
1792
|
-
* Check if the type is a truthy bigint literal type.
|
|
1793
|
-
* @internal
|
|
1794
|
-
*/
|
|
1795
904
|
const isTruthyBigIntType = (type) => type.isLiteral() && isMatching({ value: { base10Value: P.not("0") } }, type);
|
|
1796
|
-
/**
|
|
1797
|
-
* Check if the type is a truthy number literal type.
|
|
1798
|
-
* @internal
|
|
1799
|
-
*/
|
|
1800
905
|
const isTruthyNumberType = (type) => type.isNumberLiteral() && type.value !== 0;
|
|
1801
|
-
/**
|
|
1802
|
-
* Check if the type is a truthy string literal type.
|
|
1803
|
-
* @internal
|
|
1804
|
-
*/
|
|
1805
906
|
const isTruthyStringType = (type) => type.isStringLiteral() && type.value !== "";
|
|
1806
|
-
/**
|
|
1807
|
-
* Check if the type is the unknown type.
|
|
1808
|
-
* @internal
|
|
1809
|
-
*/
|
|
1810
907
|
const isUnknownType = (type) => isTypeFlagSet(type, ts.TypeFlags.Unknown);
|
|
1811
908
|
|
|
1812
909
|
//#endregion
|
|
1813
910
|
//#region src/type-name.ts
|
|
1814
|
-
/**
|
|
1815
|
-
* Get the fully qualified name of a symbol, handling cases that `ts.TypeChecker.getFullyQualifiedName` does not handle (ex: `export as namespace preact`).
|
|
1816
|
-
* @param checker The TypeScript type checker.
|
|
1817
|
-
* @param symbol The symbol to get fully qualified name for.
|
|
1818
|
-
* @returns The fully qualified name of the symbol.
|
|
1819
|
-
*/
|
|
1820
911
|
function getFullyQualifiedNameEx(checker, symbol) {
|
|
1821
912
|
let name = symbol.name;
|
|
1822
913
|
let parent = symbol.declarations?.at(0)?.parent;
|
|
@@ -1863,13 +954,6 @@ function getFullyQualifiedNameEx(checker, symbol) {
|
|
|
1863
954
|
|
|
1864
955
|
//#endregion
|
|
1865
956
|
//#region src/type-variant.ts
|
|
1866
|
-
/**
|
|
1867
|
-
* Get the variants of an array of types.
|
|
1868
|
-
* @param types The types to get the variants of.
|
|
1869
|
-
* @returns The variants of the types.
|
|
1870
|
-
* @remarks Ported from https://github.com/typescript-eslint/typescript-eslint/blob/eb736bbfc22554694400e6a4f97051d845d32e0b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts#L826 with some enhancements.
|
|
1871
|
-
* @internal
|
|
1872
|
-
*/
|
|
1873
957
|
function getTypeVariants(types) {
|
|
1874
958
|
const variants = /* @__PURE__ */ new Set();
|
|
1875
959
|
if (types.some(isUnknownType)) {
|