@eslint-react/core 2.3.13-next.0 → 2.3.13-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +97 -34
  2. package/dist/index.js +210 -181
  3. package/package.json +5 -5
package/dist/index.d.ts CHANGED
@@ -1,21 +1,12 @@
1
- import { TSESTree } from "@typescript-eslint/types";
2
1
  import * as AST from "@eslint-react/ast";
3
2
  import { unit } from "@eslint-react/eff";
4
3
  import { RuleContext } from "@eslint-react/shared";
5
- import * as birecord0 from "birecord";
4
+ import { TSESTree } from "@typescript-eslint/types";
6
5
  import { ESLintUtils, TSESTree as TSESTree$1 } from "@typescript-eslint/utils";
6
+ import * as birecord0 from "birecord";
7
7
  import { Scope } from "@typescript-eslint/scope-manager";
8
8
  import * as typescript0 from "typescript";
9
9
 
10
- //#region src/component/component-children.d.ts
11
- /**
12
- * Determines whether inside `createElement`'s children.
13
- * @param context The rule context
14
- * @param node The AST node to check
15
- * @returns `true` if the node is inside createElement's children
16
- */
17
- declare function isChildrenOfCreateElement(context: RuleContext, node: TSESTree.Node): boolean;
18
- //#endregion
19
10
  //#region src/component/component-detection-hint.d.ts
20
11
  type ComponentDetectionHint = bigint;
21
12
  /**
@@ -93,34 +84,94 @@ interface SemanticNode {
93
84
  //#region src/component/component-flag.d.ts
94
85
  type ComponentFlag = bigint;
95
86
  declare const ComponentFlag: {
87
+ /** No flags set */
96
88
  None: bigint;
89
+ /** Indicates the component is a pure component (e.g. extends PureComponent) */
97
90
  PureComponent: bigint;
91
+ /** Indicates the component creates elements using `createElement` instead of JSX */
98
92
  CreateElement: bigint;
93
+ /** Indicates the component is memoized (e.g. React.memo) */
99
94
  Memo: bigint;
95
+ /** Indicates the component forwards a ref (e.g. React.forwardRef) */
100
96
  ForwardRef: bigint;
97
+ /** Indicates the component is asynchronous */
101
98
  Async: bigint;
102
99
  };
103
100
  //#endregion
104
101
  //#region src/component/component-semantic-node.d.ts
102
+ /**
103
+ * Represents a React function component
104
+ */
105
105
  interface FunctionComponent extends SemanticNode {
106
+ /**
107
+ * The identifier or identifier sequence of the component
108
+ */
106
109
  id: unit | TSESTree.Identifier | TSESTree.Identifier[];
110
+ /**
111
+ * The kind of component
112
+ */
107
113
  kind: "function";
114
+ /**
115
+ * The AST node of the function
116
+ */
108
117
  node: AST.TSESTreeFunction;
118
+ /**
119
+ * Flags describing the component's characteristics
120
+ */
109
121
  flag: ComponentFlag;
122
+ /**
123
+ * Hint for how the component was detected
124
+ */
110
125
  hint: ComponentDetectionHint;
126
+ /**
127
+ * The initialization path of the function
128
+ */
111
129
  initPath: unit | AST.FunctionInitPath;
130
+ /**
131
+ * List of hook calls within the component
132
+ */
112
133
  hookCalls: TSESTree.CallExpression[];
134
+ /**
135
+ * The display name of the component
136
+ */
113
137
  displayName: unit | TSESTree.Expression;
114
138
  }
139
+ /**
140
+ * Represents a React class component
141
+ */
115
142
  interface ClassComponent extends SemanticNode {
143
+ /**
144
+ * The identifier of the component
145
+ */
116
146
  id: unit | TSESTree.Identifier;
147
+ /**
148
+ * The kind of component
149
+ */
117
150
  kind: "class";
151
+ /**
152
+ * The AST node of the class
153
+ */
118
154
  node: AST.TSESTreeClass;
155
+ /**
156
+ * Flags describing the component's characteristics
157
+ */
119
158
  flag: ComponentFlag;
159
+ /**
160
+ * Hint for how the component was detected
161
+ */
120
162
  hint: ComponentDetectionHint;
163
+ /**
164
+ * List of methods and properties in the class
165
+ */
121
166
  methods: AST.TSESTreeMethodOrProperty[];
167
+ /**
168
+ * The display name of the component
169
+ */
122
170
  displayName: unit | TSESTree.Expression;
123
171
  }
172
+ /**
173
+ * Union type representing either a class or function component
174
+ */
124
175
  type Component = ClassComponent | FunctionComponent;
125
176
  //#endregion
126
177
  //#region src/component/component-collector.d.ts
@@ -167,27 +218,27 @@ declare namespace useComponentCollectorLegacy {
167
218
  * @returns The context and listeners for the rule
168
219
  */
169
220
  declare function useComponentCollectorLegacy(): useComponentCollectorLegacy.ReturnType;
170
- //#endregion
171
- //#region src/component/component-definition.d.ts
172
221
  /**
173
- * Check whether given node is a function of a render method of a class component
174
- * @example
175
- * ```tsx
176
- * class Component extends React.Component {
177
- * renderHeader = () => <div />;
178
- * renderFooter = () => <div />;
179
- * }
180
- * ```
181
- * @param node The AST node to check
182
- * @returns `true` if node is a render function, `false` if not
222
+ * Check whether the given node is a this.setState() call
223
+ * @param node - The node to check
224
+ * @internal
183
225
  */
184
- declare function isFunctionOfRenderMethod(node: AST.TSESTreeFunction): boolean;
226
+ declare function isThisSetState(node: TSESTree$1.CallExpression): boolean;
227
+ /**
228
+ * Check whether the given node is an assignment to this.state
229
+ * @param node - The node to check
230
+ * @internal
231
+ */
232
+ declare function isAssignmentToThisState(node: TSESTree$1.AssignmentExpression): boolean;
233
+ //#endregion
234
+ //#region src/component/component-definition.d.ts
185
235
  /**
186
236
  * Determines if a function node represents a valid React component definition
187
- * @param context The rule context
188
- * @param node The function node to check
189
- * @param hint Component detection hints as bit flags
190
- * @returns `true` if the node is a valid component definition, `false` otherwise
237
+ *
238
+ * @param context - The rule context
239
+ * @param node - The function node to analyze
240
+ * @param hint - Component detection hints (bit flags) to customize detection logic
241
+ * @returns `true` if the node is considered a component definition
191
242
  */
192
243
  declare function isComponentDefinition(context: RuleContext, node: AST.TSESTreeFunction, hint: bigint): boolean;
193
244
  //#endregion
@@ -239,9 +290,26 @@ declare function isFunctionOfComponentDidMount(node: TSESTree.Node): boolean;
239
290
  declare function isFunctionOfComponentWillUnmount(node: TSESTree.Node): boolean;
240
291
  //#endregion
241
292
  //#region src/component/component-name.d.ts
293
+ /**
294
+ * Check if a string matches the strict component name pattern
295
+ * @param name - The name to check
296
+ */
242
297
  declare function isComponentName(name: string): boolean;
298
+ /**
299
+ * Check if a string matches the loose component name pattern
300
+ * @param name - The name to check
301
+ */
243
302
  declare function isComponentNameLoose(name: string): boolean;
303
+ /**
304
+ * Get component name from an identifier or identifier sequence (e.g., MemberExpression)
305
+ * @param id - The identifier or identifier sequence
306
+ */
244
307
  declare function getComponentNameFromId(id: TSESTree.Identifier | TSESTree.Identifier[] | unit): string | undefined;
308
+ /**
309
+ * Check if the function has no name or a loose component name
310
+ * @param context - The rule context
311
+ * @param fn - The function node
312
+ */
245
313
  declare function hasNoneOrLooseComponentName(context: RuleContext, fn: AST.TSESTreeFunction): boolean;
246
314
  //#endregion
247
315
  //#region src/component/component-phase-helpers.d.ts
@@ -317,11 +385,6 @@ declare function isDirectValueOfRenderPropertyLoose(node: TSESTree.Node): boolea
317
385
  */
318
386
  declare function isDeclaredInRenderPropLoose(node: TSESTree.Node): boolean;
319
387
  //#endregion
320
- //#region src/component/component-state.d.ts
321
- type ComponentStateKind = "actionState" | "state";
322
- declare function isThisSetState(node: TSESTree.CallExpression): boolean;
323
- declare function isAssignmentToThisState(node: TSESTree.AssignmentExpression): boolean;
324
- //#endregion
325
388
  //#region src/component/component-wrapper.d.ts
326
389
  /**
327
390
  * Check if the node is a call expression for a component wrapper
@@ -706,4 +769,4 @@ declare const isForwardRefCall: isReactAPICall.ReturnType;
706
769
  declare const isMemoCall: isReactAPICall.ReturnType;
707
770
  declare const isLazyCall: isReactAPICall.ReturnType;
708
771
  //#endregion
709
- export { ClassComponent, Component, ComponentDetectionHint, ComponentEffectPhaseKind, ComponentFlag, ComponentKind, ComponentLifecyclePhaseKind, ComponentPhaseKind, ComponentPhaseRelevance, ComponentStateKind, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, FunctionComponent, Hook, JsxAttributeValue, JsxConfig, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, SemanticEntry, SemanticNode, findParentJsxAttribute, getComponentFlagFromInitPath, getComponentNameFromId, getFunctionComponentId, getInstanceId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, getPhaseKindOfFunction, hasNoneOrLooseComponentName, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOfCreateElement, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isFunctionOfComponentDidMount, isFunctionOfComponentWillUnmount, isFunctionOfRenderMethod, isFunctionOfUseEffectCleanup, isFunctionOfUseEffectSetup, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isInitializedFromReact, isInitializedFromSource, isInstanceIdEqual, isInversePhase, isJsxFragmentElement, isJsxHostElement, isJsxLike, isJsxText, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isReactHook, isReactHookCall, isReactHookCallWithName, isReactHookCallWithNameAlias, isReactHookId, isReactHookName, isRender, isRenderFunctionLoose, isRenderMethodLike, isRenderPropLoose, isShouldComponentUpdate, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectLikeCall, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseSyncExternalStoreCall, isUseTransitionCall, resolveJsxAttributeValue, stringifyJsx, useComponentCollector, useComponentCollectorLegacy, useHookCollector };
772
+ export { ClassComponent, Component, ComponentDetectionHint, ComponentEffectPhaseKind, ComponentFlag, ComponentKind, ComponentLifecyclePhaseKind, ComponentPhaseKind, ComponentPhaseRelevance, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, FunctionComponent, Hook, JsxAttributeValue, JsxConfig, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, SemanticEntry, SemanticNode, findParentJsxAttribute, getComponentFlagFromInitPath, getComponentNameFromId, getFunctionComponentId, getInstanceId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, getPhaseKindOfFunction, hasNoneOrLooseComponentName, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isFunctionOfComponentDidMount, isFunctionOfComponentWillUnmount, isFunctionOfUseEffectCleanup, isFunctionOfUseEffectSetup, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isInitializedFromReact, isInitializedFromSource, isInstanceIdEqual, isInversePhase, isJsxFragmentElement, isJsxHostElement, isJsxLike, isJsxText, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isReactHook, isReactHookCall, isReactHookCallWithName, isReactHookCallWithNameAlias, isReactHookId, isReactHookName, isRender, isRenderFunctionLoose, isRenderMethodLike, isRenderPropLoose, isShouldComponentUpdate, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectLikeCall, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseSyncExternalStoreCall, isUseTransitionCall, resolveJsxAttributeValue, stringifyJsx, useComponentCollector, useComponentCollectorLegacy, useHookCollector };
package/dist/index.js CHANGED
@@ -1,75 +1,13 @@
1
- import { AST_NODE_TYPES } from "@typescript-eslint/types";
2
1
  import * as AST from "@eslint-react/ast";
3
2
  import { constFalse, constTrue, dual, flip, getOrElseUpdate, identity, unit } from "@eslint-react/eff";
4
3
  import { IdGenerator, 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
+ import { AST_NODE_TYPES } from "@typescript-eslint/types";
5
5
  import { findProperty, findVariable, getVariableDefinitionNode, isNodeValueEqual } from "@eslint-react/var";
6
6
  import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
7
7
  import { P, isMatching, match } from "ts-pattern";
8
+ import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from "@typescript-eslint/utils";
8
9
  import birecord from "birecord";
9
10
 
10
- //#region src/utils/is-react-api.ts
11
- function isReactAPI(api) {
12
- const func = (context, node) => {
13
- if (node == null) return false;
14
- const getText = (n) => context.sourceCode.getText(n);
15
- const name = AST.toStringFormat(node, getText);
16
- if (name === api) return true;
17
- if (name.substring(name.indexOf(".") + 1) === api) return true;
18
- return false;
19
- };
20
- return dual(2, func);
21
- }
22
- function isReactAPICall(api) {
23
- const func = (context, node) => {
24
- if (node == null) return false;
25
- if (node.type !== AST_NODE_TYPES.CallExpression) return false;
26
- return isReactAPI(api)(context, node.callee);
27
- };
28
- return dual(2, func);
29
- }
30
- const isCaptureOwnerStack = isReactAPI("captureOwnerStack");
31
- const isChildrenCount = isReactAPI("Children.count");
32
- const isChildrenForEach = isReactAPI("Children.forEach");
33
- const isChildrenMap = isReactAPI("Children.map");
34
- const isChildrenOnly = isReactAPI("Children.only");
35
- const isChildrenToArray = isReactAPI("Children.toArray");
36
- const isCloneElement = isReactAPI("cloneElement");
37
- const isCreateContext = isReactAPI("createContext");
38
- const isCreateElement = isReactAPI("createElement");
39
- const isCreateRef = isReactAPI("createRef");
40
- const isForwardRef = isReactAPI("forwardRef");
41
- const isMemo = isReactAPI("memo");
42
- const isLazy = isReactAPI("lazy");
43
- const isCaptureOwnerStackCall = isReactAPICall("captureOwnerStack");
44
- const isChildrenCountCall = isReactAPICall("Children.count");
45
- const isChildrenForEachCall = isReactAPICall("Children.forEach");
46
- const isChildrenMapCall = isReactAPICall("Children.map");
47
- const isChildrenOnlyCall = isReactAPICall("Children.only");
48
- const isChildrenToArrayCall = isReactAPICall("Children.toArray");
49
- const isCloneElementCall = isReactAPICall("cloneElement");
50
- const isCreateContextCall = isReactAPICall("createContext");
51
- const isCreateElementCall = isReactAPICall("createElement");
52
- const isCreateRefCall = isReactAPICall("createRef");
53
- const isForwardRefCall = isReactAPICall("forwardRef");
54
- const isMemoCall = isReactAPICall("memo");
55
- const isLazyCall = isReactAPICall("lazy");
56
-
57
- //#endregion
58
- //#region src/component/component-children.ts
59
- /**
60
- * Determines whether inside `createElement`'s children.
61
- * @param context The rule context
62
- * @param node The AST node to check
63
- * @returns `true` if the node is inside createElement's children
64
- */
65
- function isChildrenOfCreateElement(context, node) {
66
- const parent = node.parent;
67
- if (parent == null || parent.type !== AST_NODE_TYPES.CallExpression) return false;
68
- if (!isCreateElementCall(context, parent)) return false;
69
- return parent.arguments.slice(2).some((arg) => arg === node);
70
- }
71
-
72
- //#endregion
73
11
  //#region src/hook/hook-name.ts
74
12
  const REACT_BUILTIN_HOOK_NAMES = [
75
13
  "use",
@@ -606,6 +544,137 @@ function findParentJsxAttribute(node, test = constTrue) {
606
544
  return AST.findParentNode(node, guard);
607
545
  }
608
546
 
547
+ //#endregion
548
+ //#region src/utils/get-instance-id.ts
549
+ /** eslint-disable jsdoc/require-param */
550
+ /**
551
+ * Gets the identifier node of an instance based on AST node relationships.
552
+ * Used for tracking where hooks or components are being assigned in the code.
553
+ * @param node The current AST node to evaluate
554
+ * @param prev The previous AST node in the traversal (used for context)
555
+ * @internal
556
+ */
557
+ function getInstanceId(node, prev) {
558
+ switch (true) {
559
+ case node.type === AST_NODE_TYPES.VariableDeclarator && node.init === prev: return node.id;
560
+ case node.type === AST_NODE_TYPES.AssignmentExpression && node.right === prev: return node.left;
561
+ case node.type === AST_NODE_TYPES.PropertyDefinition && node.value === prev: return node.key;
562
+ case node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program || node.parent === node: return unit;
563
+ default: return getInstanceId(node.parent, node);
564
+ }
565
+ }
566
+
567
+ //#endregion
568
+ //#region src/utils/is-from-source.ts
569
+ /**
570
+ * Get the arguments of a require expression
571
+ * @param node The node to match
572
+ * @returns The require expression arguments or undefined if the node is not a require expression
573
+ */
574
+ function getRequireExpressionArguments(node) {
575
+ return match(node).with({
576
+ type: AST_NODE_TYPES.CallExpression,
577
+ arguments: P.select(),
578
+ callee: {
579
+ type: AST_NODE_TYPES.Identifier,
580
+ name: "require"
581
+ }
582
+ }, identity).with({
583
+ type: AST_NODE_TYPES.MemberExpression,
584
+ object: P.select()
585
+ }, getRequireExpressionArguments).otherwise(() => null);
586
+ }
587
+ /**
588
+ * Check if an identifier name is initialized from source
589
+ * @param name The top-level identifier's name
590
+ * @param source The import source to check against
591
+ * @param initialScope Initial scope to search for the identifier
592
+ * @returns Whether the identifier name is initialized from source
593
+ * @internal
594
+ */
595
+ function isInitializedFromSource(name, source, initialScope) {
596
+ const latestDef = findVariable(name, initialScope)?.defs.at(-1);
597
+ if (latestDef == null) return false;
598
+ const { node, parent } = latestDef;
599
+ if (node.type === AST_NODE_TYPES.VariableDeclarator && node.init != null) {
600
+ const { init } = node;
601
+ if (init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier) return isInitializedFromSource(init.object.name, source, initialScope);
602
+ if (init.type === AST_NODE_TYPES.Identifier) return isInitializedFromSource(init.name, source, initialScope);
603
+ const arg0 = getRequireExpressionArguments(init)?.[0];
604
+ if (arg0 == null || !AST.isLiteral(arg0, "string")) return false;
605
+ return arg0.value === source || arg0.value.startsWith(`${source}/`);
606
+ }
607
+ return parent?.type === AST_NODE_TYPES.ImportDeclaration && parent.source.value === source;
608
+ }
609
+
610
+ //#endregion
611
+ //#region src/utils/is-from-react.ts
612
+ /**
613
+ * Check if an identifier name is initialized from react
614
+ * @param name The top-level identifier's name
615
+ * @param importSource The import source to check against
616
+ * @param initialScope Initial scope to search for the identifier
617
+ * @returns Whether the identifier name is initialized from react
618
+ */
619
+ function isInitializedFromReact(name, importSource, initialScope) {
620
+ return name.toLowerCase() === "react" || isInitializedFromSource(name, importSource, initialScope);
621
+ }
622
+
623
+ //#endregion
624
+ //#region src/utils/is-instance-id-equal.ts
625
+ /** @internal */
626
+ function isInstanceIdEqual(context, a, b) {
627
+ return AST.isNodeEqual(a, b) || isNodeValueEqual(a, b, [context.sourceCode.getScope(a), context.sourceCode.getScope(b)]);
628
+ }
629
+
630
+ //#endregion
631
+ //#region src/utils/is-react-api.ts
632
+ function isReactAPI(api) {
633
+ const func = (context, node) => {
634
+ if (node == null) return false;
635
+ const getText = (n) => context.sourceCode.getText(n);
636
+ const name = AST.toStringFormat(node, getText);
637
+ if (name === api) return true;
638
+ if (name.substring(name.indexOf(".") + 1) === api) return true;
639
+ return false;
640
+ };
641
+ return dual(2, func);
642
+ }
643
+ function isReactAPICall(api) {
644
+ const func = (context, node) => {
645
+ if (node == null) return false;
646
+ if (node.type !== AST_NODE_TYPES.CallExpression) return false;
647
+ return isReactAPI(api)(context, node.callee);
648
+ };
649
+ return dual(2, func);
650
+ }
651
+ const isCaptureOwnerStack = isReactAPI("captureOwnerStack");
652
+ const isChildrenCount = isReactAPI("Children.count");
653
+ const isChildrenForEach = isReactAPI("Children.forEach");
654
+ const isChildrenMap = isReactAPI("Children.map");
655
+ const isChildrenOnly = isReactAPI("Children.only");
656
+ const isChildrenToArray = isReactAPI("Children.toArray");
657
+ const isCloneElement = isReactAPI("cloneElement");
658
+ const isCreateContext = isReactAPI("createContext");
659
+ const isCreateElement = isReactAPI("createElement");
660
+ const isCreateRef = isReactAPI("createRef");
661
+ const isForwardRef = isReactAPI("forwardRef");
662
+ const isMemo = isReactAPI("memo");
663
+ const isLazy = isReactAPI("lazy");
664
+ const isCaptureOwnerStackCall = isReactAPICall("captureOwnerStack");
665
+ const isChildrenCountCall = isReactAPICall("Children.count");
666
+ const isChildrenForEachCall = isReactAPICall("Children.forEach");
667
+ const isChildrenMapCall = isReactAPICall("Children.map");
668
+ const isChildrenOnlyCall = isReactAPICall("Children.only");
669
+ const isChildrenToArrayCall = isReactAPICall("Children.toArray");
670
+ const isCloneElementCall = isReactAPICall("cloneElement");
671
+ const isCreateContextCall = isReactAPICall("createContext");
672
+ const isCreateElementCall = isReactAPICall("createElement");
673
+ const isCreateRefCall = isReactAPICall("createRef");
674
+ const isForwardRefCall = isReactAPICall("forwardRef");
675
+ const isMemoCall = isReactAPICall("memo");
676
+ const isLazyCall = isReactAPICall("lazy");
677
+
609
678
  //#endregion
610
679
  //#region src/component/component-detection-hint.ts
611
680
  /**
@@ -679,18 +748,19 @@ function isRenderMethodLike(node) {
679
748
  //#endregion
680
749
  //#region src/component/component-definition.ts
681
750
  /**
682
- * Function pattern matchers for different contexts
751
+ * Function patterns for matching specific AST structures
752
+ * Used to identify where a function is defined (e.g., method, property)
683
753
  */
684
- const functionPatterns = {
685
- classMethod: {
754
+ const FUNCTION_PATTERNS = {
755
+ CLASS_METHOD: {
686
756
  type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
687
757
  parent: AST_NODE_TYPES.MethodDefinition
688
758
  },
689
- classProperty: {
759
+ CLASS_PROPERTY: {
690
760
  type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
691
761
  parent: AST_NODE_TYPES.Property
692
762
  },
693
- objectMethod: {
763
+ OBJECT_METHOD: {
694
764
  type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
695
765
  parent: {
696
766
  type: AST_NODE_TYPES.Property,
@@ -699,39 +769,57 @@ const functionPatterns = {
699
769
  }
700
770
  };
701
771
  /**
702
- * Check whether given node is a function of a render method of a class component
772
+ * Checks if the given node is a function within a render method of a class component.
773
+ *
774
+ * @param node - The AST node to check
775
+ * @returns `true` if the node is a render function inside a class component
776
+ *
703
777
  * @example
704
778
  * ```tsx
705
779
  * class Component extends React.Component {
706
- * renderHeader = () => <div />;
707
- * renderFooter = () => <div />;
780
+ * renderHeader = () => <div />; // Returns true
708
781
  * }
709
782
  * ```
710
- * @param node The AST node to check
711
- * @returns `true` if node is a render function, `false` if not
712
783
  */
713
784
  function isFunctionOfRenderMethod(node) {
714
- return isRenderMethodLike(node.parent) && isClassComponent(node.parent.parent.parent);
785
+ const parent = node.parent;
786
+ const greatGrandparent = parent.parent?.parent;
787
+ return greatGrandparent != null && isRenderMethodLike(parent) && isClassComponent(greatGrandparent);
715
788
  }
716
789
  /**
717
- * Checks if a function node should be excluded based on detection hints
718
- * @param node The function node to check
719
- * @param hint Component detection hints as bit flags
720
- * @returns `true` if the function should be excluded, `false` otherwise
790
+ * Checks if a function node should be excluded based on provided detection hints
791
+ *
792
+ * @param node - The function node to check
793
+ * @param hint - Component detection hints as bit flags
794
+ * @returns `true` if the function matches an exclusion hint
721
795
  */
722
796
  function shouldExcludeBasedOnHint(node, hint) {
723
- if (hint & ComponentDetectionHint.SkipObjectMethod && isMatching(functionPatterns.objectMethod)(node)) return true;
724
- if (hint & ComponentDetectionHint.SkipClassMethod && isMatching(functionPatterns.classMethod)(node)) return true;
725
- if (hint & ComponentDetectionHint.SkipClassProperty && isMatching(functionPatterns.classProperty)(node)) return true;
797
+ if (hint & ComponentDetectionHint.SkipObjectMethod && isMatching(FUNCTION_PATTERNS.OBJECT_METHOD)(node)) return true;
798
+ if (hint & ComponentDetectionHint.SkipClassMethod && isMatching(FUNCTION_PATTERNS.CLASS_METHOD)(node)) return true;
799
+ if (hint & ComponentDetectionHint.SkipClassProperty && isMatching(FUNCTION_PATTERNS.CLASS_PROPERTY)(node)) return true;
726
800
  if (hint & ComponentDetectionHint.SkipArrayMapArgument && AST.isArrayMapCall(node.parent)) return true;
727
801
  return false;
728
802
  }
729
803
  /**
804
+ * Determines if the node is an argument within `createElement`'s children list (3rd argument onwards)
805
+ *
806
+ * @param context - The rule context
807
+ * @param node - The AST node to check
808
+ * @returns `true` if the node is passed as a child to `createElement`
809
+ */
810
+ function isChildrenOfCreateElement(context, node) {
811
+ const parent = node.parent;
812
+ if (parent?.type !== AST_NODE_TYPES.CallExpression) return false;
813
+ if (!isCreateElementCall(context, parent)) return false;
814
+ return parent.arguments.slice(2).some((arg) => arg === node);
815
+ }
816
+ /**
730
817
  * Determines if a function node represents a valid React component definition
731
- * @param context The rule context
732
- * @param node The function node to check
733
- * @param hint Component detection hints as bit flags
734
- * @returns `true` if the node is a valid component definition, `false` otherwise
818
+ *
819
+ * @param context - The rule context
820
+ * @param node - The function node to analyze
821
+ * @param hint - Component detection hints (bit flags) to customize detection logic
822
+ * @returns `true` if the node is considered a component definition
735
823
  */
736
824
  function isComponentDefinition(context, node, hint) {
737
825
  if (isChildrenOfCreateElement(context, node) || isFunctionOfRenderMethod(node)) return false;
@@ -746,89 +834,6 @@ function isComponentDefinition(context, node, hint) {
746
834
  return significantParent == null || significantParent.type !== AST_NODE_TYPES.JSXExpressionContainer;
747
835
  }
748
836
 
749
- //#endregion
750
- //#region src/utils/get-instance-id.ts
751
- /** eslint-disable jsdoc/require-param */
752
- /**
753
- * Gets the identifier node of an instance based on AST node relationships.
754
- * Used for tracking where hooks or components are being assigned in the code.
755
- * @param node The current AST node to evaluate
756
- * @param prev The previous AST node in the traversal (used for context)
757
- * @internal
758
- */
759
- function getInstanceId(node, prev) {
760
- switch (true) {
761
- case node.type === AST_NODE_TYPES.VariableDeclarator && node.init === prev: return node.id;
762
- case node.type === AST_NODE_TYPES.AssignmentExpression && node.right === prev: return node.left;
763
- case node.type === AST_NODE_TYPES.PropertyDefinition && node.value === prev: return node.key;
764
- case node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program || node.parent === node: return unit;
765
- default: return getInstanceId(node.parent, node);
766
- }
767
- }
768
-
769
- //#endregion
770
- //#region src/utils/is-from-source.ts
771
- /**
772
- * Get the arguments of a require expression
773
- * @param node The node to match
774
- * @returns The require expression arguments or undefined if the node is not a require expression
775
- */
776
- function getRequireExpressionArguments(node) {
777
- return match(node).with({
778
- type: AST_NODE_TYPES.CallExpression,
779
- arguments: P.select(),
780
- callee: {
781
- type: AST_NODE_TYPES.Identifier,
782
- name: "require"
783
- }
784
- }, identity).with({
785
- type: AST_NODE_TYPES.MemberExpression,
786
- object: P.select()
787
- }, getRequireExpressionArguments).otherwise(() => null);
788
- }
789
- /**
790
- * Check if an identifier name is initialized from source
791
- * @param name The top-level identifier's name
792
- * @param source The import source to check against
793
- * @param initialScope Initial scope to search for the identifier
794
- * @returns Whether the identifier name is initialized from source
795
- * @internal
796
- */
797
- function isInitializedFromSource(name, source, initialScope) {
798
- const latestDef = findVariable(name, initialScope)?.defs.at(-1);
799
- if (latestDef == null) return false;
800
- const { node, parent } = latestDef;
801
- if (node.type === AST_NODE_TYPES.VariableDeclarator && node.init != null) {
802
- const { init } = node;
803
- if (init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier) return isInitializedFromSource(init.object.name, source, initialScope);
804
- if (init.type === AST_NODE_TYPES.Identifier) return isInitializedFromSource(init.name, source, initialScope);
805
- const arg0 = getRequireExpressionArguments(init)?.[0];
806
- if (arg0 == null || !AST.isLiteral(arg0, "string")) return false;
807
- return arg0.value === source || arg0.value.startsWith(`${source}/`);
808
- }
809
- return parent?.type === AST_NODE_TYPES.ImportDeclaration && parent.source.value === source;
810
- }
811
-
812
- //#endregion
813
- //#region src/utils/is-from-react.ts
814
- /**
815
- * Check if an identifier name is initialized from react
816
- * @param name The top-level identifier's name
817
- * @param importSource The import source to check against
818
- * @param initialScope Initial scope to search for the identifier
819
- * @returns Whether the identifier name is initialized from react
820
- */
821
- function isInitializedFromReact(name, importSource, initialScope) {
822
- return name.toLowerCase() === "react" || isInitializedFromSource(name, importSource, initialScope);
823
- }
824
-
825
- //#endregion
826
- //#region src/utils/is-instance-id-equal.ts
827
- /** @internal */
828
- function isInstanceIdEqual(context, a, b) {
829
- return AST.isNodeEqual(a, b) || isNodeValueEqual(a, b, [context.sourceCode.getScope(a), context.sourceCode.getScope(b)]);
830
- }
831
-
832
837
  //#endregion
833
838
  //#region src/component/component-wrapper.ts
834
839
  /**
@@ -885,16 +890,33 @@ function getComponentFlagFromInitPath(initPath) {
885
890
 
886
891
  //#endregion
887
892
  //#region src/component/component-name.ts
893
+ /**
894
+ * Check if a string matches the strict component name pattern
895
+ * @param name - The name to check
896
+ */
888
897
  function isComponentName(name) {
889
898
  return RE_COMPONENT_NAME.test(name);
890
899
  }
900
+ /**
901
+ * Check if a string matches the loose component name pattern
902
+ * @param name - The name to check
903
+ */
891
904
  function isComponentNameLoose(name) {
892
905
  return RE_COMPONENT_NAME_LOOSE.test(name);
893
906
  }
907
+ /**
908
+ * Get component name from an identifier or identifier sequence (e.g., MemberExpression)
909
+ * @param id - The identifier or identifier sequence
910
+ */
894
911
  function getComponentNameFromId(id) {
895
912
  if (id == null) return unit;
896
913
  return Array.isArray(id) ? id.map((n) => n.name).join(".") : id.name;
897
914
  }
915
+ /**
916
+ * Check if the function has no name or a loose component name
917
+ * @param context - The rule context
918
+ * @param fn - The function node
919
+ */
898
920
  function hasNoneOrLooseComponentName(context, fn) {
899
921
  const id = getFunctionComponentId(context, fn);
900
922
  if (id == null) return true;
@@ -1040,6 +1062,24 @@ function useComponentCollectorLegacy() {
1040
1062
  }
1041
1063
  };
1042
1064
  }
1065
+ /**
1066
+ * Check whether the given node is a this.setState() call
1067
+ * @param node - The node to check
1068
+ * @internal
1069
+ */
1070
+ function isThisSetState(node) {
1071
+ const { callee } = node;
1072
+ return callee.type === AST_NODE_TYPES$1.MemberExpression && AST.isThisExpression(callee.object) && callee.property.type === AST_NODE_TYPES$1.Identifier && callee.property.name === "setState";
1073
+ }
1074
+ /**
1075
+ * Check whether the given node is an assignment to this.state
1076
+ * @param node - The node to check
1077
+ * @internal
1078
+ */
1079
+ function isAssignmentToThisState(node) {
1080
+ const { left } = node;
1081
+ return left.type === AST_NODE_TYPES$1.MemberExpression && AST.isThisExpression(left.object) && AST.getPropertyName(left.property) === "state";
1082
+ }
1043
1083
 
1044
1084
  //#endregion
1045
1085
  //#region src/component/component-method.ts
@@ -1165,15 +1205,4 @@ function isDeclaredInRenderPropLoose(node) {
1165
1205
  }
1166
1206
 
1167
1207
  //#endregion
1168
- //#region src/component/component-state.ts
1169
- function isThisSetState(node) {
1170
- const { callee } = node;
1171
- return callee.type === AST_NODE_TYPES.MemberExpression && AST.isThisExpression(callee.object) && callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "setState";
1172
- }
1173
- function isAssignmentToThisState(node) {
1174
- const { left } = node;
1175
- return left.type === AST_NODE_TYPES.MemberExpression && AST.isThisExpression(left.object) && AST.getPropertyName(left.property) === "state";
1176
- }
1177
-
1178
- //#endregion
1179
- export { ComponentDetectionHint, ComponentFlag, ComponentPhaseRelevance, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, findParentJsxAttribute, getComponentFlagFromInitPath, getComponentNameFromId, getFunctionComponentId, getInstanceId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, getPhaseKindOfFunction, hasNoneOrLooseComponentName, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOfCreateElement, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isFunctionOfComponentDidMount, isFunctionOfComponentWillUnmount, isFunctionOfRenderMethod, isFunctionOfUseEffectCleanup, isFunctionOfUseEffectSetup, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isInitializedFromReact, isInitializedFromSource, isInstanceIdEqual, isInversePhase, isJsxFragmentElement, isJsxHostElement, isJsxLike, isJsxText, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isReactHook, isReactHookCall, isReactHookCallWithName, isReactHookCallWithNameAlias, isReactHookId, isReactHookName, isRender, isRenderFunctionLoose, isRenderMethodLike, isRenderPropLoose, isShouldComponentUpdate, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectLikeCall, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseSyncExternalStoreCall, isUseTransitionCall, resolveJsxAttributeValue, stringifyJsx, useComponentCollector, useComponentCollectorLegacy, useHookCollector };
1208
+ export { ComponentDetectionHint, ComponentFlag, ComponentPhaseRelevance, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, findParentJsxAttribute, getComponentFlagFromInitPath, getComponentNameFromId, getFunctionComponentId, getInstanceId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, getPhaseKindOfFunction, hasNoneOrLooseComponentName, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isFunctionOfComponentDidMount, isFunctionOfComponentWillUnmount, isFunctionOfUseEffectCleanup, isFunctionOfUseEffectSetup, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isInitializedFromReact, isInitializedFromSource, isInstanceIdEqual, isInversePhase, isJsxFragmentElement, isJsxHostElement, isJsxLike, isJsxText, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isReactHook, isReactHookCall, isReactHookCallWithName, isReactHookCallWithNameAlias, isReactHookId, isReactHookName, isRender, isRenderFunctionLoose, isRenderMethodLike, isRenderPropLoose, isShouldComponentUpdate, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectLikeCall, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseSyncExternalStoreCall, isUseTransitionCall, resolveJsxAttributeValue, stringifyJsx, useComponentCollector, useComponentCollectorLegacy, useHookCollector };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-react/core",
3
- "version": "2.3.13-next.0",
3
+ "version": "2.3.13-next.1",
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": {
@@ -35,10 +35,10 @@
35
35
  "@typescript-eslint/utils": "^8.48.1",
36
36
  "birecord": "^0.1.1",
37
37
  "ts-pattern": "^5.9.0",
38
- "@eslint-react/ast": "2.3.13-next.0",
39
- "@eslint-react/shared": "2.3.13-next.0",
40
- "@eslint-react/eff": "2.3.13-next.0",
41
- "@eslint-react/var": "2.3.13-next.0"
38
+ "@eslint-react/ast": "2.3.13-next.1",
39
+ "@eslint-react/eff": "2.3.13-next.1",
40
+ "@eslint-react/var": "2.3.13-next.1",
41
+ "@eslint-react/shared": "2.3.13-next.1"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "eslint": "^8.57.0 || ^9.0.0",