@eslint-react/core 5.26.7 → 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 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 function to check if a node matches the API.
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 function to check if a node is a call to the API.
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. */
@@ -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: string | null;
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
- /** @deprecated Class components are legacy. */
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 component.
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 a callback passed to a class component's render method.
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 is a render method callback.
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.VariableDeclaration, TSESTree.VariableDeclarator, TSESTree.ObjectExpression, TSESTree.Property, TSESTree.CallExpression, TSESTreeFunction] | readonly [TSESTree.VariableDeclaration, TSESTree.VariableDeclarator, TSESTree.ObjectExpression, TSESTree.Property, TSESTree.CallExpression, TSESTree.CallExpression, TSESTreeFunction] | readonly [TSESTree.ClassDeclaration | TSESTree.ClassExpression, TSESTree.ClassBody, TSESTree.MethodDefinition, TSESTreeFunction] | readonly [TSESTree.ClassDeclaration | TSESTree.ClassExpression, TSESTree.ClassBody, TSESTree.PropertyDefinition, TSESTreeFunction];
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
- /** Hint for how the component was detected. */
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
- /** Indicates the component is a pure component (ex: extends PureComponent). */
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
- /** Indicates the component creates elements using `createElement` instead of JSX. */
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 that are commonly returned alongside JSX in React
789
- * components but are not themselves renderable elements.
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 function isBooleanLiteralType<TType extends ts.Type>(type: TType): type is TType & {
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 && Check.isIdentifier(node.superClass.property): return re.test(node.superClass.property.name);
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 && Check.isIdentifier(node.superClass.property): return re.test(node.superClass.property.name);
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 && Check.isIdentifier(node.key, methodName);
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
- const parent = node.parent;
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);
@@ -308,14 +324,16 @@ function getCreateElementProp(context, node, name) {
308
324
  function isCreateElementChildrenArgument(context, node) {
309
325
  let parent = node.parent;
310
326
  while (Check.isTypeExpression(parent)) parent = parent.parent;
311
- 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));
312
328
  }
313
329
  function isInsideCreateElementProps(context, node) {
314
330
  const call = Traverse.findParent(node, isCreateElementCall(context));
315
331
  if (call == null) return false;
316
332
  const prop = Traverse.findParent(node, Check.isObjectExpression);
317
333
  if (prop == null) return false;
318
- return prop === call.arguments[1];
334
+ const propsArg = call.arguments[1];
335
+ if (propsArg == null) return false;
336
+ return prop === Extract.unwrap(propsArg);
319
337
  }
320
338
  //#endregion
321
339
  //#region src/function.ts
@@ -325,9 +343,9 @@ function getFunctionId(node) {
325
343
  case node.parent.type === AST_NODE_TYPES.VariableDeclarator && node.parent.init === node: return node.parent.id;
326
344
  case node.parent.type === AST_NODE_TYPES.AssignmentExpression && node.parent.right === node && node.parent.operator === "=": return node.parent.left;
327
345
  case node.parent.type === AST_NODE_TYPES.Property && node.parent.value === node && !node.parent.computed: return node.parent.key;
328
- 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;
329
347
  case node.parent.type === AST_NODE_TYPES.AssignmentPattern && node.parent.right === node: return node.parent.left;
330
- 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);
331
349
  case Check.isTypeAssertionExpression(node.parent): return getFunctionId(node.parent);
332
350
  }
333
351
  return null;
@@ -335,20 +353,24 @@ function getFunctionId(node) {
335
353
  function getFunctionInitPath(node) {
336
354
  if (node.type === AST_NODE_TYPES.FunctionDeclaration) return [node];
337
355
  let parent = node.parent;
338
- while (Check.isTypeExpression(parent)) parent = parent.parent;
356
+ let child = node;
357
+ while (Check.isTypeExpression(parent)) {
358
+ child = parent;
359
+ parent = parent.parent;
360
+ }
339
361
  switch (true) {
340
362
  case parent.type === AST_NODE_TYPES.VariableDeclarator: return [
341
363
  parent.parent,
342
364
  parent,
343
365
  node
344
366
  ];
345
- 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 [
346
368
  parent.parent.parent,
347
369
  parent.parent,
348
370
  parent,
349
371
  node
350
372
  ];
351
- 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 [
352
374
  parent.parent.parent.parent,
353
375
  parent.parent.parent,
354
376
  parent.parent,
@@ -408,7 +430,12 @@ const SEL_FUNCTION_DISPLAY_NAME_ASSIGNMENT = [
408
430
  "AssignmentExpression",
409
431
  "[operator='=']",
410
432
  "[left.type='MemberExpression']",
411
- "[left.property.name='displayName']"
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
+ ")"
412
439
  ].join("");
413
440
  //#endregion
414
441
  //#region src/jsx.ts
@@ -453,11 +480,14 @@ function isJsxLike(context, node, hint = DEFAULT_JSX_DETECTION_HINT) {
453
480
  case AST_NODE_TYPES.CallExpression:
454
481
  if (hint & JsxDetectionHint.DoNotIncludeJsxWithCreateElementValue) return false;
455
482
  return isCreateElementCall(context, node);
456
- case AST_NODE_TYPES.Identifier:
457
- 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);
458
485
  if (seen.has(node)) return false;
459
486
  seen.add(node);
460
- return visit(resolve(context, node));
487
+ const result = visit(resolve(context, node));
488
+ seen.delete(node);
489
+ return result;
490
+ }
461
491
  }
462
492
  return false;
463
493
  }
@@ -484,9 +514,14 @@ function isFunctionComponentWrapperCall(context, node) {
484
514
  }
485
515
  function isFunctionComponentWrapperCallback(context, node) {
486
516
  if (!Check.isFunction(node)) return false;
517
+ let argument = node;
487
518
  let parent = node.parent;
488
- while (Check.isTypeExpression(parent)) parent = parent.parent;
519
+ while (Check.isTypeExpression(parent)) {
520
+ argument = parent;
521
+ parent = parent.parent;
522
+ }
489
523
  if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
524
+ if (parent.arguments[0] !== argument) return false;
490
525
  return isFunctionComponentWrapperCall(context, parent);
491
526
  }
492
527
  function getFunctionComponentId(context, node) {
@@ -574,7 +609,7 @@ function isHookName(name) {
574
609
  function isHookId(id) {
575
610
  switch (id.type) {
576
611
  case AST_NODE_TYPES.Identifier: return isHookName(id.name);
577
- 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);
578
613
  default: return false;
579
614
  }
580
615
  }
@@ -587,7 +622,7 @@ function isHookDefinition(node) {
587
622
  const id = getFunctionId(node);
588
623
  switch (id?.type) {
589
624
  case AST_NODE_TYPES.Identifier: return isHookName(id.name);
590
- 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);
591
626
  default: return false;
592
627
  }
593
628
  }
@@ -622,15 +657,20 @@ function isUseEffectLikeCall(node, additionalEffectHooks = { test: constFalse })
622
657
  function isUseEffectSetupCallback(node) {
623
658
  if (node == null) return false;
624
659
  const expr = Extract.unwrap(node);
625
- return expr.parent?.type === AST_NODE_TYPES.CallExpression && expr.parent.arguments.at(0) === expr && isUseEffectLikeCall(expr.parent);
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);
626
665
  }
627
666
  function isUseEffectCleanupCallback(node) {
628
667
  if (node == null) return false;
629
668
  const expr = Extract.unwrap(node);
630
669
  const returnStatement = Traverse.findParent(expr, Check.isReturnStatement);
631
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);
632
672
  if (enclosingFunction !== Traverse.findParent(returnStatement, Check.isFunction)) return false;
633
- return isUseEffectSetupCallback(enclosingFunction);
673
+ return returnStatement.argument != null && Extract.unwrap(returnStatement.argument) === expr && isUseEffectSetupCallback(enclosingFunction);
634
674
  }
635
675
  //#endregion
636
676
  //#region src/function-component-collector.ts
@@ -692,7 +732,6 @@ function getFunctionComponentCollector(context, options = {}) {
692
732
  },
693
733
  ...collectDisplayName ? { [SEL_FUNCTION_DISPLAY_NAME_ASSIGNMENT](node) {
694
734
  const { left, right } = node;
695
- if (left.type !== AST_NODE_TYPES.MemberExpression) return;
696
735
  const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name : null;
697
736
  const component = [...components.values()].findLast(({ name }) => name != null && name === componentName);
698
737
  if (component == null) return;
@@ -741,7 +780,7 @@ function getHookCollector(context) {
741
780
  key: node.range[0],
742
781
  kind: "hook",
743
782
  name: id == null ? null : Extract.getFullyQualifiedName(id, getText),
744
- directives: [],
783
+ directives: getFunctionDirectives(node),
745
784
  flag: 0n,
746
785
  hint: 0n,
747
786
  hookCalls: [],
@@ -812,16 +851,17 @@ function getJsxConfigFromAnnotation(context) {
812
851
  return options;
813
852
  }
814
853
  let jsx, jsxFrag, jsxRuntime, jsxImportSource;
815
- for (const comment of context.sourceCode.getAllComments().reverse()) {
854
+ for (const comment of context.sourceCode.getAllComments()) {
816
855
  const value = comment.value;
817
856
  jsx ??= value.match(RE_ANNOTATION_JSX)?.[1];
818
857
  jsxFrag ??= value.match(RE_ANNOTATION_JSX_FRAG)?.[1];
819
- jsxRuntime ??= value.match(RE_ANNOTATION_JSX_RUNTIME)?.[1];
820
- jsxImportSource ??= value.match(RE_ANNOTATION_JSX_IMPORT_SOURCE)?.[1];
858
+ jsxRuntime = value.match(RE_ANNOTATION_JSX_RUNTIME)?.[1] ?? jsxRuntime;
859
+ jsxImportSource = value.match(RE_ANNOTATION_JSX_IMPORT_SOURCE)?.[1] ?? jsxImportSource;
821
860
  }
822
861
  if (jsx != null) options.jsxFactory = jsx;
823
862
  if (jsxFrag != null) options.jsxFragmentFactory = jsxFrag;
824
- if (jsxRuntime != null) options.jsx = jsxRuntime === "classic" ? ts.JsxEmit.React : ts.JsxEmit.ReactJSX;
863
+ if (jsxRuntime === "classic") options.jsx = ts.JsxEmit.React;
864
+ else if (jsxRuntime === "automatic") options.jsx = ts.JsxEmit.ReactJSX;
825
865
  if (jsxImportSource != null) options.jsxImportSource = jsxImportSource;
826
866
  annotationCache.set(context.sourceCode, options);
827
867
  return options;
@@ -845,9 +885,7 @@ function isFlagSetOnObject(obj, flag) {
845
885
  return isFlagSet(obj.flags, flag);
846
886
  }
847
887
  const isTypeFlagSet = isFlagSetOnObject;
848
- function isBooleanLiteralType(type) {
849
- return isTypeFlagSet(type, ts.TypeFlags.BooleanLiteral);
850
- }
888
+ const isBooleanLiteralType = (type) => isTypeFlagSet(type, ts.TypeFlags.BooleanLiteral);
851
889
  const isFalseLiteralType = (type) => isBooleanLiteralType(type) && type.intrinsicName === "false";
852
890
  const isTrueLiteralType = (type) => isBooleanLiteralType(type) && type.intrinsicName === "true";
853
891
  const isAnyType = (type) => isTypeFlagSet(type, ts.TypeFlags.TypeParameter | ts.TypeFlags.Any);
@@ -925,6 +963,7 @@ function getTypeVariants(types) {
925
963
  if (booleans.length === 1 && boolean0 != null) {
926
964
  if (isFalseLiteralType(boolean0)) variants.add("falsy boolean");
927
965
  else if (isTrueLiteralType(boolean0)) variants.add("truthy boolean");
966
+ else variants.add("boolean");
928
967
  } else if (booleans.length === 2) variants.add("boolean");
929
968
  const strings = types.filter(isStringType);
930
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.7",
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.7",
33
- "@eslint-react/eslint": "5.26.7",
34
- "@eslint-react/shared": "5.26.7",
35
- "@eslint-react/var": "5.26.7",
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",