@eslint-react/core 5.26.6 → 5.26.8
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.d.ts +56 -25
- package/dist/index.js +73 -40
- package/package.json +6 -5
package/dist/index.d.ts
CHANGED
|
@@ -14,8 +14,13 @@ export declare namespace isAPI {
|
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Check if the node is a React API identifier or member expression.
|
|
17
|
+
* @remarks
|
|
18
|
+
* Matching is namespace-agnostic by design: the fully qualified name of the node is compared
|
|
19
|
+
* against the API name by exact match or by suffix (`.${api}`), so `foo.bar.useState` matches
|
|
20
|
+
* `"useState"` just as `React.useState` does.
|
|
17
21
|
* @param api The React API name to check against (ex: "useState", "React.memo").
|
|
18
|
-
* @returns A predicate
|
|
22
|
+
* @returns A dual-signature predicate: call it as `(context, node)` to check a node directly,
|
|
23
|
+
* or as `(context)` to get a `(node) => boolean` predicate (ex: for use in array filters).
|
|
19
24
|
*/
|
|
20
25
|
export declare function isAPI(api: string): isAPI.ReturnType;
|
|
21
26
|
export declare namespace isAPICall {
|
|
@@ -28,7 +33,8 @@ export declare namespace isAPICall {
|
|
|
28
33
|
/**
|
|
29
34
|
* Check if the node is a call expression to a specific React API.
|
|
30
35
|
* @param api The React API name to check against.
|
|
31
|
-
* @returns A predicate
|
|
36
|
+
* @returns A dual-signature predicate: call it as `(context, node)` to check a node directly,
|
|
37
|
+
* or as `(context)` to get a `(node) => node is TSESTree.CallExpression` predicate (ex: for use in array filters).
|
|
32
38
|
*/
|
|
33
39
|
export declare function isAPICall(api: string): isAPICall.ReturnType;
|
|
34
40
|
/** Check if the node is a React `captureOwnerStack` API identifier or member expression. */
|
|
@@ -53,10 +59,10 @@ export declare const isCreateElement: isAPI.ReturnType;
|
|
|
53
59
|
export declare const isCreateRef: isAPI.ReturnType;
|
|
54
60
|
/** Check if the node is a React `forwardRef` API identifier or member expression. */
|
|
55
61
|
export declare const isForwardRef: isAPI.ReturnType;
|
|
56
|
-
/** Check if the node is a React `memo` API identifier or member expression. */
|
|
57
|
-
export declare const isMemo: isAPI.ReturnType;
|
|
58
62
|
/** Check if the node is a React `lazy` API identifier or member expression. */
|
|
59
63
|
export declare const isLazy: isAPI.ReturnType;
|
|
64
|
+
/** Check if the node is a React `memo` API identifier or member expression. */
|
|
65
|
+
export declare const isMemo: isAPI.ReturnType;
|
|
60
66
|
/** Check if the node is a call expression to the React `captureOwnerStack` API. */
|
|
61
67
|
export declare const isCaptureOwnerStackCall: isAPICall.ReturnType;
|
|
62
68
|
/** Check if the node is a call expression to the React `Children.count` API. */
|
|
@@ -79,10 +85,10 @@ export declare const isCreateElementCall: isAPICall.ReturnType;
|
|
|
79
85
|
export declare const isCreateRefCall: isAPICall.ReturnType;
|
|
80
86
|
/** Check if the node is a call expression to the React `forwardRef` API. */
|
|
81
87
|
export declare const isForwardRefCall: isAPICall.ReturnType;
|
|
82
|
-
/** Check if the node is a call expression to the React `memo` API. */
|
|
83
|
-
export declare const isMemoCall: isAPICall.ReturnType;
|
|
84
88
|
/** Check if the node is a call expression to the React `lazy` API. */
|
|
85
89
|
export declare const isLazyCall: isAPICall.ReturnType;
|
|
90
|
+
/** Check if the node is a call expression to the React `memo` API. */
|
|
91
|
+
export declare const isMemoCall: isAPICall.ReturnType;
|
|
86
92
|
/** Check if the node is a React `use` API identifier or member expression. */
|
|
87
93
|
export declare const isUse: isAPI.ReturnType;
|
|
88
94
|
/** Check if the node is a React `useActionState` API identifier or member expression. */
|
|
@@ -177,7 +183,7 @@ export interface SemanticNode {
|
|
|
177
183
|
/** The identifier of the node. */
|
|
178
184
|
id: null | TSESTree.Node;
|
|
179
185
|
/** The unique key of the node. */
|
|
180
|
-
key:
|
|
186
|
+
key: number;
|
|
181
187
|
/** The kind of the node. */
|
|
182
188
|
kind: string;
|
|
183
189
|
/** The name of the node. */
|
|
@@ -199,7 +205,7 @@ export interface SemanticFunc extends SemanticNode {
|
|
|
199
205
|
/** The AST node of the function. */
|
|
200
206
|
node: TSESTreeFunction;
|
|
201
207
|
/** The name of the function. */
|
|
202
|
-
name:
|
|
208
|
+
name: null | string;
|
|
203
209
|
/** The return type annotation of the function. */
|
|
204
210
|
type: TSESTree.TSTypeAnnotation | null;
|
|
205
211
|
/** The body of the function. */
|
|
@@ -275,23 +281,31 @@ export declare const isUnsafeComponentWillMount: (node: TSESTree.Node) => node i
|
|
|
275
281
|
export declare const isUnsafeComponentWillReceiveProps: (node: TSESTree.Node) => node is TSESTreeMethodOrPropertyDefinition;
|
|
276
282
|
/** @deprecated Class components are legacy. */
|
|
277
283
|
export declare const isUnsafeComponentWillUpdate: (node: TSESTree.Node) => node is TSESTreeMethodOrPropertyDefinition;
|
|
278
|
-
/**
|
|
284
|
+
/**
|
|
285
|
+
* @deprecated Class components are legacy. Additionally, this predicate does not correspond to any
|
|
286
|
+
* real React semantics: `getDefaultProps` is a `createReactClass` spec method (an instance-side
|
|
287
|
+
* spec object member, not a static class member), while React class components use the static
|
|
288
|
+
* `defaultProps` property — a `static getDefaultProps` class method is meaningless to React.
|
|
289
|
+
* Retained only to preserve the published API; do not use.
|
|
290
|
+
*/
|
|
279
291
|
export declare const isGetDefaultProps: (node: TSESTree.Node) => node is TSESTreeMethodOrPropertyDefinition;
|
|
280
292
|
/** @deprecated Class components are legacy. */
|
|
281
293
|
export declare const isGetDerivedStateFromProps: (node: TSESTree.Node) => node is TSESTreeMethodOrPropertyDefinition;
|
|
282
294
|
/** @deprecated Class components are legacy. */
|
|
283
295
|
export declare const isGetDerivedStateFromError: (node: TSESTree.Node) => node is TSESTreeMethodOrPropertyDefinition;
|
|
284
296
|
/**
|
|
285
|
-
* Check if the node is a render-like method of a class
|
|
297
|
+
* Check if the node is a render-like method or property of a class.
|
|
298
|
+
* Only requires the grandparent node to be a class; the class does not need to be a class component.
|
|
286
299
|
* @param node The AST node to check.
|
|
287
300
|
* @returns `true` if the node is a render-like method.
|
|
288
301
|
* @deprecated Class components are legacy. This function exists only to support legacy rules.
|
|
289
302
|
*/
|
|
290
303
|
export declare function isRenderMethodLike(node: TSESTree.Node): node is TSESTreeMethodOrPropertyDefinition;
|
|
291
304
|
/**
|
|
292
|
-
* Check if the function is
|
|
305
|
+
* Check if the function is the implementation value of a class component's render method or property
|
|
306
|
+
* (e.g. the function of `render() {}` or the arrow function of `render = () => {}` in a class component).
|
|
293
307
|
* @param node The function node to check.
|
|
294
|
-
* @returns `true` if the function
|
|
308
|
+
* @returns `true` if the function implements a class component's render method.
|
|
295
309
|
*/
|
|
296
310
|
export declare function isRenderMethodCallback(node: TSESTreeFunction): boolean;
|
|
297
311
|
/**
|
|
@@ -390,7 +404,7 @@ export type FunctionID = ReturnType<typeof getFunctionId>;
|
|
|
390
404
|
* Represents various AST paths for function declarations.
|
|
391
405
|
* Each tuple type represents a specific function definition pattern.
|
|
392
406
|
*/
|
|
393
|
-
export type FunctionInitPath = readonly [TSESTree.FunctionDeclaration] | readonly [TSESTree.VariableDeclaration, TSESTree.VariableDeclarator, TSESTreeFunction] | readonly [TSESTree.VariableDeclaration, TSESTree.VariableDeclarator, TSESTree.CallExpression, TSESTreeFunction] | readonly [TSESTree.VariableDeclaration, TSESTree.VariableDeclarator, TSESTree.CallExpression, TSESTree.CallExpression, TSESTreeFunction] | readonly [TSESTree.VariableDeclaration, TSESTree.VariableDeclarator, TSESTree.ObjectExpression, TSESTree.Property, TSESTreeFunction] | readonly [TSESTree.
|
|
407
|
+
export type FunctionInitPath = readonly [TSESTree.FunctionDeclaration] | readonly [TSESTree.VariableDeclaration, TSESTree.VariableDeclarator, TSESTreeFunction] | readonly [TSESTree.VariableDeclaration, TSESTree.VariableDeclarator, TSESTree.CallExpression, TSESTreeFunction] | readonly [TSESTree.VariableDeclaration, TSESTree.VariableDeclarator, TSESTree.CallExpression, TSESTree.CallExpression, TSESTreeFunction] | readonly [TSESTree.VariableDeclaration, TSESTree.VariableDeclarator, TSESTree.ObjectExpression, TSESTree.Property, TSESTreeFunction] | readonly [TSESTree.ClassDeclaration | TSESTree.ClassExpression, TSESTree.ClassBody, TSESTree.MethodDefinition, TSESTreeFunction] | readonly [TSESTree.ClassDeclaration | TSESTree.ClassExpression, TSESTree.ClassBody, TSESTree.PropertyDefinition, TSESTreeFunction];
|
|
394
408
|
/**
|
|
395
409
|
* Represents the kind of a function.
|
|
396
410
|
*/
|
|
@@ -431,6 +445,12 @@ export declare function getFunctionId(node: TSESTree.Expression | TSESTreeFuncti
|
|
|
431
445
|
/**
|
|
432
446
|
* Get the initialization path of a function node in the AST.
|
|
433
447
|
*
|
|
448
|
+
* @remarks
|
|
449
|
+
* Higher-order component wrappers (ex: `memo`, `forwardRef`) are only
|
|
450
|
+
* recognized when the call expression initializes a variable declarator
|
|
451
|
+
* directly. HOCs inside object properties (ex: `{ Nav: memo(() => {}) }`)
|
|
452
|
+
* are not supported and yield `null`.
|
|
453
|
+
*
|
|
434
454
|
* @param node The function node to analyze.
|
|
435
455
|
* @returns The function initialization path or `null` if not identifiable.
|
|
436
456
|
*/
|
|
@@ -469,12 +489,14 @@ export type FunctionDisplayNameAssignment = TSESTree.AssignmentExpression & {
|
|
|
469
489
|
left: TSESTree.MemberExpression & {
|
|
470
490
|
property: TSESTree.Identifier & {
|
|
471
491
|
name: "displayName";
|
|
492
|
+
} | TSESTree.StringLiteral & {
|
|
493
|
+
value: "displayName";
|
|
472
494
|
};
|
|
473
495
|
};
|
|
474
496
|
operator: "=";
|
|
475
497
|
right: TSESTree.Literal;
|
|
476
498
|
};
|
|
477
|
-
/** The esquery selector matching `displayName` assignment expressions. */
|
|
499
|
+
/** The esquery selector matching `displayName` assignment expressions with a statically determinable property name and a literal right side. */
|
|
478
500
|
export declare const SEL_FUNCTION_DISPLAY_NAME_ASSIGNMENT: string;
|
|
479
501
|
//#endregion
|
|
480
502
|
//#region src/hook.d.ts
|
|
@@ -498,7 +520,7 @@ export interface HookSemanticNode extends SemanticNode {
|
|
|
498
520
|
/** Represents a hook call, which can be a call expression or a tagged template expression. */
|
|
499
521
|
export type HookCall = TSESTree.CallExpression | TSESTree.TaggedTemplateExpression;
|
|
500
522
|
/**
|
|
501
|
-
* Check if the name is a hook name (starts with `use` followed by an uppercase letter or digit).
|
|
523
|
+
* Check if the name is a hook name (`use` itself, or starts with `use` followed by an uppercase letter or digit).
|
|
502
524
|
* @param name The name of the identifier to check.
|
|
503
525
|
* @returns `true` if the name is a hook name.
|
|
504
526
|
* @see https://github.com/facebook/react/blob/1d6c8168db1d82713202e842df3167787ffa00ed/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts#L16
|
|
@@ -575,7 +597,7 @@ export interface FunctionComponentSemanticNode extends SemanticNode {
|
|
|
575
597
|
node: TSESTreeFunction;
|
|
576
598
|
/** Flags describing the component's characteristics. */
|
|
577
599
|
flag: bigint;
|
|
578
|
-
/**
|
|
600
|
+
/** Detection hints configured by the caller when collecting this component; identical for every component collected under the same settings. */
|
|
579
601
|
hint: bigint;
|
|
580
602
|
/** List of expressions returned by the component. */
|
|
581
603
|
rets: TSESTree.ReturnStatement["argument"][];
|
|
@@ -598,9 +620,15 @@ export interface FunctionComponentSemanticNode extends SemanticNode {
|
|
|
598
620
|
export declare const FunctionComponentFlag: {
|
|
599
621
|
/** No flags set. */
|
|
600
622
|
None: bigint;
|
|
601
|
-
/**
|
|
623
|
+
/**
|
|
624
|
+
* Indicates the component is a pure component.
|
|
625
|
+
* @deprecated This flag is never set; purity of class components (`extends PureComponent`) is not tracked here.
|
|
626
|
+
*/
|
|
602
627
|
PureComponent: bigint;
|
|
603
|
-
/**
|
|
628
|
+
/**
|
|
629
|
+
* Indicates the component creates elements using `createElement` instead of JSX.
|
|
630
|
+
* @deprecated This flag is never set; createElement usage is not tracked here.
|
|
631
|
+
*/
|
|
604
632
|
CreateElement: bigint;
|
|
605
633
|
/** Indicates the component is memoized (ex: React.memo). */
|
|
606
634
|
Memo: bigint;
|
|
@@ -671,11 +699,11 @@ export declare const FunctionComponentDetectionHint: {
|
|
|
671
699
|
readonly DoNotIncludeFunctionDefinedAsArrayExpressionElement: bigint;
|
|
672
700
|
/** Exclude functions defined as array pattern elements from component detection. */
|
|
673
701
|
readonly DoNotIncludeFunctionDefinedAsArrayPatternElement: bigint;
|
|
674
|
-
/** Exclude functions defined as array flatMap callbacks from component detection. */
|
|
702
|
+
/** Exclude functions defined as array flatMap callbacks (named or anonymous) from component detection. */
|
|
675
703
|
readonly DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: bigint;
|
|
676
|
-
/** Exclude functions defined as array map callbacks from component detection. */
|
|
704
|
+
/** Exclude functions defined as array map callbacks (named or anonymous) from component detection. */
|
|
677
705
|
readonly DoNotIncludeFunctionDefinedAsArrayMapCallback: bigint;
|
|
678
|
-
/** Exclude functions defined as arbitrary call expression callbacks from component detection. */
|
|
706
|
+
/** Exclude anonymous functions defined as arbitrary call expression callbacks from component detection. */
|
|
679
707
|
readonly DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback: bigint;
|
|
680
708
|
readonly None: 0n;
|
|
681
709
|
readonly DoNotIncludeJsxWithNullValue: bigint;
|
|
@@ -784,9 +812,12 @@ export declare const JsxDetectionHint: {
|
|
|
784
812
|
/**
|
|
785
813
|
* Default JSX detection hint.
|
|
786
814
|
*
|
|
787
|
-
* Skips number, bigint, boolean, string, and undefined literals
|
|
788
|
-
* the value types
|
|
789
|
-
*
|
|
815
|
+
* Skips number, bigint, boolean, string, and undefined literals —
|
|
816
|
+
* the value types commonly returned alongside JSX in React components.
|
|
817
|
+
* They are excluded because callers are typically interested in
|
|
818
|
+
* element-shaped values rather than text or flag content, not because
|
|
819
|
+
* they are unrenderable (strings and numbers are renderable since
|
|
820
|
+
* React 16).
|
|
790
821
|
*/
|
|
791
822
|
export declare const DEFAULT_JSX_DETECTION_HINT: JsxDetectionHint;
|
|
792
823
|
/**
|
|
@@ -871,7 +902,7 @@ export declare function getJsxConfig(context: RuleContext): Required<JsxConfig>;
|
|
|
871
902
|
* @param type The type to check.
|
|
872
903
|
* @returns `true` if the type is a boolean literal type.
|
|
873
904
|
*/
|
|
874
|
-
export declare
|
|
905
|
+
export declare const isBooleanLiteralType: <TType extends ts.Type>(type: TType) => type is TType & {
|
|
875
906
|
intrinsicName: "false" | "true";
|
|
876
907
|
};
|
|
877
908
|
/**
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Check, Extract, Traverse } from "@eslint-react/ast";
|
|
|
2
2
|
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
3
3
|
import { RE_ANNOTATION_JSX, RE_ANNOTATION_JSX_FRAG, RE_ANNOTATION_JSX_IMPORT_SOURCE, RE_ANNOTATION_JSX_RUNTIME, RE_COMPONENT_NAME, RE_COMPONENT_NAME_LOOSE } from "@eslint-react/shared";
|
|
4
4
|
import { resolve } from "@eslint-react/var";
|
|
5
|
+
import { findVariable } from "@typescript-eslint/utils/ast-utils";
|
|
5
6
|
import { P, isMatching, match } from "ts-pattern";
|
|
6
7
|
import ts from "typescript";
|
|
7
8
|
//#region src/api.ts
|
|
@@ -40,8 +41,8 @@ const isCreateContext = isAPI("createContext");
|
|
|
40
41
|
const isCreateElement = isAPI("createElement");
|
|
41
42
|
const isCreateRef = isAPI("createRef");
|
|
42
43
|
const isForwardRef = isAPI("forwardRef");
|
|
43
|
-
const isMemo = isAPI("memo");
|
|
44
44
|
const isLazy = isAPI("lazy");
|
|
45
|
+
const isMemo = isAPI("memo");
|
|
45
46
|
const isCaptureOwnerStackCall = isAPICall("captureOwnerStack");
|
|
46
47
|
const isChildrenCountCall = isAPICall("Children.count");
|
|
47
48
|
const isChildrenForEachCall = isAPICall("Children.forEach");
|
|
@@ -53,8 +54,8 @@ const isCreateContextCall = isAPICall("createContext");
|
|
|
53
54
|
const isCreateElementCall = isAPICall("createElement");
|
|
54
55
|
const isCreateRefCall = isAPICall("createRef");
|
|
55
56
|
const isForwardRefCall = isAPICall("forwardRef");
|
|
56
|
-
const isMemoCall = isAPICall("memo");
|
|
57
57
|
const isLazyCall = isAPICall("lazy");
|
|
58
|
+
const isMemoCall = isAPICall("memo");
|
|
58
59
|
const isUse = isAPI("use");
|
|
59
60
|
const isUseActionState = isAPI("useActionState");
|
|
60
61
|
const isUseCallback = isAPI("useCallback");
|
|
@@ -173,6 +174,9 @@ dual(2, (a, b) => (data) => a(data) === b(data));
|
|
|
173
174
|
dual(2, (antecedent, consequent) => (data) => !antecedent(data) || consequent(data));
|
|
174
175
|
dual(2, (a, b) => (data) => !a(data) && !b(data));
|
|
175
176
|
dual(2, (a, b) => (data) => !a(data) || !b(data));
|
|
177
|
+
function isString(input) {
|
|
178
|
+
return typeof input === "string";
|
|
179
|
+
}
|
|
176
180
|
function isFunction(input) {
|
|
177
181
|
return typeof input === "function";
|
|
178
182
|
}
|
|
@@ -196,12 +200,20 @@ dual(2, (self, predicate) => {
|
|
|
196
200
|
});
|
|
197
201
|
//#endregion
|
|
198
202
|
//#region src/class-component.ts
|
|
203
|
+
function getStaticKeyName(key, computed) {
|
|
204
|
+
if (!computed && Check.isIdentifier(key)) return key.name;
|
|
205
|
+
if (key.type === AST_NODE_TYPES.Literal && isString(key.value)) return key.value;
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
199
208
|
function isClassComponent(node) {
|
|
200
209
|
if (hasProperty(node, "superClass") && node.superClass != null) {
|
|
201
210
|
const re = /^(?:Pure)?Component$/u;
|
|
202
211
|
switch (true) {
|
|
203
212
|
case Check.isIdentifier(node.superClass): return re.test(node.superClass.name);
|
|
204
|
-
case node.superClass.type === AST_NODE_TYPES.MemberExpression
|
|
213
|
+
case node.superClass.type === AST_NODE_TYPES.MemberExpression: {
|
|
214
|
+
const name = getStaticKeyName(node.superClass.property, node.superClass.computed);
|
|
215
|
+
return name != null && re.test(name);
|
|
216
|
+
}
|
|
205
217
|
}
|
|
206
218
|
}
|
|
207
219
|
return false;
|
|
@@ -211,13 +223,16 @@ function isPureComponent(node) {
|
|
|
211
223
|
const re = /^PureComponent$/u;
|
|
212
224
|
switch (true) {
|
|
213
225
|
case Check.isIdentifier(node.superClass): return re.test(node.superClass.name);
|
|
214
|
-
case node.superClass.type === AST_NODE_TYPES.MemberExpression
|
|
226
|
+
case node.superClass.type === AST_NODE_TYPES.MemberExpression: {
|
|
227
|
+
const name = getStaticKeyName(node.superClass.property, node.superClass.computed);
|
|
228
|
+
return name != null && re.test(name);
|
|
229
|
+
}
|
|
215
230
|
}
|
|
216
231
|
}
|
|
217
232
|
return false;
|
|
218
233
|
}
|
|
219
234
|
function createLifecycleChecker(methodName, isStatic = false) {
|
|
220
|
-
return (node) => Check.isPropertyOrMethod(node) && node.static === isStatic &&
|
|
235
|
+
return (node) => Check.isPropertyOrMethod(node) && node.static === isStatic && !node.computed && getStaticKeyName(node.key, node.computed) === methodName;
|
|
221
236
|
}
|
|
222
237
|
const isRender = createLifecycleChecker("render");
|
|
223
238
|
const isComponentDidCatch = createLifecycleChecker("componentDidCatch");
|
|
@@ -241,7 +256,8 @@ function isRenderMethodLike(node) {
|
|
|
241
256
|
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);
|
|
242
257
|
}
|
|
243
258
|
function isRenderMethodCallback(node) {
|
|
244
|
-
|
|
259
|
+
let parent = node.parent;
|
|
260
|
+
while (Check.isTypeExpression(parent)) parent = parent.parent;
|
|
245
261
|
const greatGrandparent = parent.parent?.parent;
|
|
246
262
|
if (greatGrandparent == null) return false;
|
|
247
263
|
return isRenderMethodLike(parent) && isClassComponent(greatGrandparent);
|
|
@@ -251,12 +267,6 @@ function isThisSetStateCall(node) {
|
|
|
251
267
|
return callee.type === AST_NODE_TYPES.MemberExpression && Extract.unwrap(callee.object).type === AST_NODE_TYPES.ThisExpression && Extract.getCalleeName(node) === "setState";
|
|
252
268
|
}
|
|
253
269
|
//#endregion
|
|
254
|
-
//#region src/collector-key.ts
|
|
255
|
-
let key = 0;
|
|
256
|
-
function getNextCollectorKey() {
|
|
257
|
-
return String(key++);
|
|
258
|
-
}
|
|
259
|
-
//#endregion
|
|
260
270
|
//#region src/class-component-collector.ts
|
|
261
271
|
function getClassComponentCollector(context) {
|
|
262
272
|
const components = /* @__PURE__ */ new Map();
|
|
@@ -264,7 +274,7 @@ function getClassComponentCollector(context) {
|
|
|
264
274
|
const collect = (node) => {
|
|
265
275
|
if (!isClassComponent(node)) return;
|
|
266
276
|
const id = getClassId(node);
|
|
267
|
-
const key =
|
|
277
|
+
const key = node.range[0];
|
|
268
278
|
const name = id == null ? null : Extract.getFullyQualifiedName(id, getText);
|
|
269
279
|
components.set(key, {
|
|
270
280
|
id,
|
|
@@ -314,14 +324,16 @@ function getCreateElementProp(context, node, name) {
|
|
|
314
324
|
function isCreateElementChildrenArgument(context, node) {
|
|
315
325
|
let parent = node.parent;
|
|
316
326
|
while (Check.isTypeExpression(parent)) parent = parent.parent;
|
|
317
|
-
return parent?.type === AST_NODE_TYPES.CallExpression && isCreateElementCall(context, parent) && parent.arguments.slice(2).some((arg) => Extract.unwrap(arg) === node);
|
|
327
|
+
return parent?.type === AST_NODE_TYPES.CallExpression && isCreateElementCall(context, parent) && parent.arguments.slice(2).some((arg) => Extract.unwrap(arg) === Extract.unwrap(node));
|
|
318
328
|
}
|
|
319
329
|
function isInsideCreateElementProps(context, node) {
|
|
320
330
|
const call = Traverse.findParent(node, isCreateElementCall(context));
|
|
321
331
|
if (call == null) return false;
|
|
322
332
|
const prop = Traverse.findParent(node, Check.isObjectExpression);
|
|
323
333
|
if (prop == null) return false;
|
|
324
|
-
|
|
334
|
+
const propsArg = call.arguments[1];
|
|
335
|
+
if (propsArg == null) return false;
|
|
336
|
+
return prop === Extract.unwrap(propsArg);
|
|
325
337
|
}
|
|
326
338
|
//#endregion
|
|
327
339
|
//#region src/function.ts
|
|
@@ -331,9 +343,9 @@ function getFunctionId(node) {
|
|
|
331
343
|
case node.parent.type === AST_NODE_TYPES.VariableDeclarator && node.parent.init === node: return node.parent.id;
|
|
332
344
|
case node.parent.type === AST_NODE_TYPES.AssignmentExpression && node.parent.right === node && node.parent.operator === "=": return node.parent.left;
|
|
333
345
|
case node.parent.type === AST_NODE_TYPES.Property && node.parent.value === node && !node.parent.computed: return node.parent.key;
|
|
334
|
-
case Check.isPropertyOrMethod(node.parent) && node.parent.value === node: return node.parent.key;
|
|
346
|
+
case Check.isPropertyOrMethod(node.parent) && node.parent.value === node && !node.parent.computed: return node.parent.key;
|
|
335
347
|
case node.parent.type === AST_NODE_TYPES.AssignmentPattern && node.parent.right === node: return node.parent.left;
|
|
336
|
-
case node.parent.type === AST_NODE_TYPES.ConditionalExpression: return getFunctionId(node.parent);
|
|
348
|
+
case node.parent.type === AST_NODE_TYPES.ConditionalExpression && (node.parent.consequent === node || node.parent.alternate === node): return getFunctionId(node.parent);
|
|
337
349
|
case Check.isTypeAssertionExpression(node.parent): return getFunctionId(node.parent);
|
|
338
350
|
}
|
|
339
351
|
return null;
|
|
@@ -341,20 +353,24 @@ function getFunctionId(node) {
|
|
|
341
353
|
function getFunctionInitPath(node) {
|
|
342
354
|
if (node.type === AST_NODE_TYPES.FunctionDeclaration) return [node];
|
|
343
355
|
let parent = node.parent;
|
|
344
|
-
|
|
356
|
+
let child = node;
|
|
357
|
+
while (Check.isTypeExpression(parent)) {
|
|
358
|
+
child = parent;
|
|
359
|
+
parent = parent.parent;
|
|
360
|
+
}
|
|
345
361
|
switch (true) {
|
|
346
362
|
case parent.type === AST_NODE_TYPES.VariableDeclarator: return [
|
|
347
363
|
parent.parent,
|
|
348
364
|
parent,
|
|
349
365
|
node
|
|
350
366
|
];
|
|
351
|
-
case parent.type === AST_NODE_TYPES.CallExpression && parent.parent.type === AST_NODE_TYPES.VariableDeclarator: return [
|
|
367
|
+
case parent.type === AST_NODE_TYPES.CallExpression && parent.arguments.includes(child) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator: return [
|
|
352
368
|
parent.parent.parent,
|
|
353
369
|
parent.parent,
|
|
354
370
|
parent,
|
|
355
371
|
node
|
|
356
372
|
];
|
|
357
|
-
case parent.type === AST_NODE_TYPES.CallExpression && parent.parent.type === AST_NODE_TYPES.CallExpression && parent.parent.parent.type === AST_NODE_TYPES.VariableDeclarator: return [
|
|
373
|
+
case parent.type === AST_NODE_TYPES.CallExpression && parent.arguments.includes(child) && parent.parent.type === AST_NODE_TYPES.CallExpression && parent.parent.arguments.includes(parent) && parent.parent.parent.type === AST_NODE_TYPES.VariableDeclarator: return [
|
|
358
374
|
parent.parent.parent.parent,
|
|
359
375
|
parent.parent.parent,
|
|
360
376
|
parent.parent,
|
|
@@ -414,7 +430,12 @@ const SEL_FUNCTION_DISPLAY_NAME_ASSIGNMENT = [
|
|
|
414
430
|
"AssignmentExpression",
|
|
415
431
|
"[operator='=']",
|
|
416
432
|
"[left.type='MemberExpression']",
|
|
417
|
-
"[
|
|
433
|
+
"[right.type='Literal']",
|
|
434
|
+
":matches(",
|
|
435
|
+
"[left.computed=false][left.property.name='displayName']",
|
|
436
|
+
", ",
|
|
437
|
+
"[left.computed=true][left.property.type='Literal'][left.property.value='displayName']",
|
|
438
|
+
")"
|
|
418
439
|
].join("");
|
|
419
440
|
//#endregion
|
|
420
441
|
//#region src/jsx.ts
|
|
@@ -459,11 +480,14 @@ function isJsxLike(context, node, hint = DEFAULT_JSX_DETECTION_HINT) {
|
|
|
459
480
|
case AST_NODE_TYPES.CallExpression:
|
|
460
481
|
if (hint & JsxDetectionHint.DoNotIncludeJsxWithCreateElementValue) return false;
|
|
461
482
|
return isCreateElementCall(context, node);
|
|
462
|
-
case AST_NODE_TYPES.Identifier:
|
|
463
|
-
if (node.name === "undefined") return !(hint & JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue);
|
|
483
|
+
case AST_NODE_TYPES.Identifier: {
|
|
484
|
+
if (node.name === "undefined" && findVariable(context.sourceCode.getScope(node), node) == null) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue);
|
|
464
485
|
if (seen.has(node)) return false;
|
|
465
486
|
seen.add(node);
|
|
466
|
-
|
|
487
|
+
const result = visit(resolve(context, node));
|
|
488
|
+
seen.delete(node);
|
|
489
|
+
return result;
|
|
490
|
+
}
|
|
467
491
|
}
|
|
468
492
|
return false;
|
|
469
493
|
}
|
|
@@ -490,9 +514,14 @@ function isFunctionComponentWrapperCall(context, node) {
|
|
|
490
514
|
}
|
|
491
515
|
function isFunctionComponentWrapperCallback(context, node) {
|
|
492
516
|
if (!Check.isFunction(node)) return false;
|
|
517
|
+
let argument = node;
|
|
493
518
|
let parent = node.parent;
|
|
494
|
-
while (Check.isTypeExpression(parent))
|
|
519
|
+
while (Check.isTypeExpression(parent)) {
|
|
520
|
+
argument = parent;
|
|
521
|
+
parent = parent.parent;
|
|
522
|
+
}
|
|
495
523
|
if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
524
|
+
if (parent.arguments[0] !== argument) return false;
|
|
496
525
|
return isFunctionComponentWrapperCall(context, parent);
|
|
497
526
|
}
|
|
498
527
|
function getFunctionComponentId(context, node) {
|
|
@@ -580,7 +609,7 @@ function isHookName(name) {
|
|
|
580
609
|
function isHookId(id) {
|
|
581
610
|
switch (id.type) {
|
|
582
611
|
case AST_NODE_TYPES.Identifier: return isHookName(id.name);
|
|
583
|
-
case AST_NODE_TYPES.MemberExpression: return hasProperty(id.property, "name") && isHookName(id.property.name);
|
|
612
|
+
case AST_NODE_TYPES.MemberExpression: return !id.computed && hasProperty(id.property, "name") && isHookName(id.property.name);
|
|
584
613
|
default: return false;
|
|
585
614
|
}
|
|
586
615
|
}
|
|
@@ -593,7 +622,7 @@ function isHookDefinition(node) {
|
|
|
593
622
|
const id = getFunctionId(node);
|
|
594
623
|
switch (id?.type) {
|
|
595
624
|
case AST_NODE_TYPES.Identifier: return isHookName(id.name);
|
|
596
|
-
case AST_NODE_TYPES.MemberExpression: return hasProperty(id.property, "name") && isHookName(id.property.name);
|
|
625
|
+
case AST_NODE_TYPES.MemberExpression: return !id.computed && hasProperty(id.property, "name") && isHookName(id.property.name);
|
|
597
626
|
default: return false;
|
|
598
627
|
}
|
|
599
628
|
}
|
|
@@ -628,15 +657,20 @@ function isUseEffectLikeCall(node, additionalEffectHooks = { test: constFalse })
|
|
|
628
657
|
function isUseEffectSetupCallback(node) {
|
|
629
658
|
if (node == null) return false;
|
|
630
659
|
const expr = Extract.unwrap(node);
|
|
631
|
-
|
|
660
|
+
let parent = expr.parent;
|
|
661
|
+
while (parent != null && (Check.isTypeExpression(parent) || parent.type === AST_NODE_TYPES.ChainExpression)) parent = parent.parent;
|
|
662
|
+
if (parent?.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
663
|
+
const firstArgument = parent.arguments.at(0);
|
|
664
|
+
return firstArgument != null && Extract.unwrap(firstArgument) === expr && isUseEffectLikeCall(parent);
|
|
632
665
|
}
|
|
633
666
|
function isUseEffectCleanupCallback(node) {
|
|
634
667
|
if (node == null) return false;
|
|
635
668
|
const expr = Extract.unwrap(node);
|
|
636
669
|
const returnStatement = Traverse.findParent(expr, Check.isReturnStatement);
|
|
637
670
|
const enclosingFunction = Traverse.findParent(expr, Check.isFunction);
|
|
671
|
+
if (returnStatement == null) return Check.isFunction(expr) && enclosingFunction?.type === AST_NODE_TYPES.ArrowFunctionExpression && Extract.unwrap(enclosingFunction.body) === expr && isUseEffectSetupCallback(enclosingFunction);
|
|
638
672
|
if (enclosingFunction !== Traverse.findParent(returnStatement, Check.isFunction)) return false;
|
|
639
|
-
return isUseEffectSetupCallback(enclosingFunction);
|
|
673
|
+
return returnStatement.argument != null && Extract.unwrap(returnStatement.argument) === expr && isUseEffectSetupCallback(enclosingFunction);
|
|
640
674
|
}
|
|
641
675
|
//#endregion
|
|
642
676
|
//#region src/function-component-collector.ts
|
|
@@ -647,7 +681,7 @@ function getFunctionComponentCollector(context, options = {}) {
|
|
|
647
681
|
const getText = (n) => context.sourceCode.getText(n);
|
|
648
682
|
const getCurrentEntry = () => functionEntries.at(-1) ?? null;
|
|
649
683
|
const onFunctionEnter = (node) => {
|
|
650
|
-
const key =
|
|
684
|
+
const key = node.range[0];
|
|
651
685
|
const exp = Traverse.findParent(node, (n) => n.type === AST_NODE_TYPES.ExportDefaultDeclaration);
|
|
652
686
|
const isExportDefault = exp != null;
|
|
653
687
|
const isExportDefaultDeclaration = exp != null && Extract.unwrap(exp.declaration) === node;
|
|
@@ -698,7 +732,6 @@ function getFunctionComponentCollector(context, options = {}) {
|
|
|
698
732
|
},
|
|
699
733
|
...collectDisplayName ? { [SEL_FUNCTION_DISPLAY_NAME_ASSIGNMENT](node) {
|
|
700
734
|
const { left, right } = node;
|
|
701
|
-
if (left.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
702
735
|
const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name : null;
|
|
703
736
|
const component = [...components.values()].findLast(({ name }) => name != null && name === componentName);
|
|
704
737
|
if (component == null) return;
|
|
@@ -744,10 +777,10 @@ function getHookCollector(context) {
|
|
|
744
777
|
const id = getFunctionId(node);
|
|
745
778
|
const entry = {
|
|
746
779
|
id,
|
|
747
|
-
key:
|
|
780
|
+
key: node.range[0],
|
|
748
781
|
kind: "hook",
|
|
749
782
|
name: id == null ? null : Extract.getFullyQualifiedName(id, getText),
|
|
750
|
-
directives:
|
|
783
|
+
directives: getFunctionDirectives(node),
|
|
751
784
|
flag: 0n,
|
|
752
785
|
hint: 0n,
|
|
753
786
|
hookCalls: [],
|
|
@@ -818,16 +851,17 @@ function getJsxConfigFromAnnotation(context) {
|
|
|
818
851
|
return options;
|
|
819
852
|
}
|
|
820
853
|
let jsx, jsxFrag, jsxRuntime, jsxImportSource;
|
|
821
|
-
for (const comment of context.sourceCode.getAllComments()
|
|
854
|
+
for (const comment of context.sourceCode.getAllComments()) {
|
|
822
855
|
const value = comment.value;
|
|
823
856
|
jsx ??= value.match(RE_ANNOTATION_JSX)?.[1];
|
|
824
857
|
jsxFrag ??= value.match(RE_ANNOTATION_JSX_FRAG)?.[1];
|
|
825
|
-
jsxRuntime
|
|
826
|
-
jsxImportSource
|
|
858
|
+
jsxRuntime = value.match(RE_ANNOTATION_JSX_RUNTIME)?.[1] ?? jsxRuntime;
|
|
859
|
+
jsxImportSource = value.match(RE_ANNOTATION_JSX_IMPORT_SOURCE)?.[1] ?? jsxImportSource;
|
|
827
860
|
}
|
|
828
861
|
if (jsx != null) options.jsxFactory = jsx;
|
|
829
862
|
if (jsxFrag != null) options.jsxFragmentFactory = jsxFrag;
|
|
830
|
-
if (jsxRuntime
|
|
863
|
+
if (jsxRuntime === "classic") options.jsx = ts.JsxEmit.React;
|
|
864
|
+
else if (jsxRuntime === "automatic") options.jsx = ts.JsxEmit.ReactJSX;
|
|
831
865
|
if (jsxImportSource != null) options.jsxImportSource = jsxImportSource;
|
|
832
866
|
annotationCache.set(context.sourceCode, options);
|
|
833
867
|
return options;
|
|
@@ -851,9 +885,7 @@ function isFlagSetOnObject(obj, flag) {
|
|
|
851
885
|
return isFlagSet(obj.flags, flag);
|
|
852
886
|
}
|
|
853
887
|
const isTypeFlagSet = isFlagSetOnObject;
|
|
854
|
-
|
|
855
|
-
return isTypeFlagSet(type, ts.TypeFlags.BooleanLiteral);
|
|
856
|
-
}
|
|
888
|
+
const isBooleanLiteralType = (type) => isTypeFlagSet(type, ts.TypeFlags.BooleanLiteral);
|
|
857
889
|
const isFalseLiteralType = (type) => isBooleanLiteralType(type) && type.intrinsicName === "false";
|
|
858
890
|
const isTrueLiteralType = (type) => isBooleanLiteralType(type) && type.intrinsicName === "true";
|
|
859
891
|
const isAnyType = (type) => isTypeFlagSet(type, ts.TypeFlags.TypeParameter | ts.TypeFlags.Any);
|
|
@@ -931,6 +963,7 @@ function getTypeVariants(types) {
|
|
|
931
963
|
if (booleans.length === 1 && boolean0 != null) {
|
|
932
964
|
if (isFalseLiteralType(boolean0)) variants.add("falsy boolean");
|
|
933
965
|
else if (isTrueLiteralType(boolean0)) variants.add("truthy boolean");
|
|
966
|
+
else variants.add("boolean");
|
|
934
967
|
} else if (booleans.length === 2) variants.add("boolean");
|
|
935
968
|
const strings = types.filter(isStringType);
|
|
936
969
|
if (strings.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-react/core",
|
|
3
|
-
"version": "5.26.
|
|
3
|
+
"version": "5.26.8",
|
|
4
4
|
"description": "ESLint React's ESLint utility module for static analysis of React core APIs and patterns.",
|
|
5
5
|
"homepage": "https://github.com/Rel1cx/eslint-react",
|
|
6
6
|
"bugs": {
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"dist"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@eslint-react/ast": "5.26.
|
|
33
|
-
"@eslint-react/eslint": "5.26.
|
|
34
|
-
"@eslint-react/shared": "5.26.
|
|
35
|
-
"@eslint-react/var": "5.26.
|
|
32
|
+
"@eslint-react/ast": "5.26.8",
|
|
33
|
+
"@eslint-react/eslint": "5.26.8",
|
|
34
|
+
"@eslint-react/shared": "5.26.8",
|
|
35
|
+
"@eslint-react/var": "5.26.8",
|
|
36
36
|
"@typescript-eslint/types": "^8.71.2",
|
|
37
37
|
"@typescript-eslint/utils": "^8.71.2",
|
|
38
38
|
"ts-pattern": "^5.9.2"
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"@local/testkit": "0.0.0",
|
|
44
44
|
"@types/node": "^20.19.43",
|
|
45
45
|
"@typescript-eslint/typescript-estree": "^8.71.2",
|
|
46
|
+
"dedent": "^1.7.2",
|
|
46
47
|
"eslint": "^10.12.0",
|
|
47
48
|
"tsdown": "^0.23.1",
|
|
48
49
|
"typescript": "6.0.3",
|