diff --git a/packages/core/docs/README.md b/packages/core/docs/README.md index f24035db66..0bea013ee1 100644 --- a/packages/core/docs/README.md +++ b/packages/core/docs/README.md @@ -55,6 +55,13 @@ - [isChildrenToArrayCall](variables/isChildrenToArrayCall.md) - [isCloneElement](variables/isCloneElement.md) - [isCloneElementCall](variables/isCloneElementCall.md) +- [isComponentDidCatch](variables/isComponentDidCatch.md) +- [isComponentDidMount](variables/isComponentDidMount.md) +- [isComponentDidUpdate](variables/isComponentDidUpdate.md) +- [isComponentWillMount](variables/isComponentWillMount.md) +- [isComponentWillReceiveProps](variables/isComponentWillReceiveProps.md) +- [isComponentWillUnmount](variables/isComponentWillUnmount.md) +- [isComponentWillUpdate](variables/isComponentWillUpdate.md) - [isCreateContext](variables/isCreateContext.md) - [isCreateContextCall](variables/isCreateContextCall.md) - [isCreateElement](variables/isCreateElement.md) @@ -63,11 +70,22 @@ - [isCreateRefCall](variables/isCreateRefCall.md) - [isForwardRef](variables/isForwardRef.md) - [isForwardRefCall](variables/isForwardRefCall.md) +- [isGetChildContext](variables/isGetChildContext.md) +- [isGetDefaultProps](variables/isGetDefaultProps.md) +- [isGetDerivedStateFromError](variables/isGetDerivedStateFromError.md) +- [isGetDerivedStateFromProps](variables/isGetDerivedStateFromProps.md) +- [isGetInitialState](variables/isGetInitialState.md) +- [isGetSnapshotBeforeUpdate](variables/isGetSnapshotBeforeUpdate.md) - [isInversePhase](variables/isInversePhase.md) - [isLazy](variables/isLazy.md) - [isLazyCall](variables/isLazyCall.md) - [isMemo](variables/isMemo.md) - [isMemoCall](variables/isMemoCall.md) +- [isRender](variables/isRender.md) +- [isShouldComponentUpdate](variables/isShouldComponentUpdate.md) +- [isUnsafeComponentWillMount](variables/isUnsafeComponentWillMount.md) +- [isUnsafeComponentWillReceiveProps](variables/isUnsafeComponentWillReceiveProps.md) +- [isUnsafeComponentWillUpdate](variables/isUnsafeComponentWillUpdate.md) - [isUseActionStateCall](variables/isUseActionStateCall.md) - [isUseCall](variables/isUseCall.md) - [isUseCallbackCall](variables/isUseCallbackCall.md) @@ -107,27 +125,14 @@ - [isAssignmentToThisState](functions/isAssignmentToThisState.md) - [isChildrenOfCreateElement](functions/isChildrenOfCreateElement.md) - [isClassComponent](functions/isClassComponent.md) -- [isComponentDidCatch](functions/isComponentDidCatch.md) -- [isComponentDidMount](functions/isComponentDidMount.md) -- [isComponentDidUpdate](functions/isComponentDidUpdate.md) +- [isComponentDefinition](functions/isComponentDefinition.md) - [isComponentName](functions/isComponentName.md) - [isComponentNameLoose](functions/isComponentNameLoose.md) -- [isComponentWillMount](functions/isComponentWillMount.md) -- [isComponentWillReceiveProps](functions/isComponentWillReceiveProps.md) -- [isComponentWillUnmount](functions/isComponentWillUnmount.md) -- [isComponentWillUpdate](functions/isComponentWillUpdate.md) - [isComponentWrapperCall](functions/isComponentWrapperCall.md) - [isComponentWrapperCallLoose](functions/isComponentWrapperCallLoose.md) - [isDeclaredInRenderPropLoose](functions/isDeclaredInRenderPropLoose.md) - [isFragmentElement](functions/isFragmentElement.md) -- [isFunctionOfRender](functions/isFunctionOfRender.md) - [isFunctionOfRenderMethod](functions/isFunctionOfRenderMethod.md) -- [isGetChildContext](functions/isGetChildContext.md) -- [isGetDefaultProps](functions/isGetDefaultProps.md) -- [isGetDerivedStateFromError](functions/isGetDerivedStateFromError.md) -- [isGetDerivedStateFromProps](functions/isGetDerivedStateFromProps.md) -- [isGetInitialState](functions/isGetInitialState.md) -- [isGetSnapshotBeforeUpdate](functions/isGetSnapshotBeforeUpdate.md) - [isHostElement](functions/isHostElement.md) - [isJsxLike](functions/isJsxLike.md) - [isJsxText](functions/isJsxText.md) @@ -142,16 +147,10 @@ - [isReactHookId](functions/isReactHookId.md) - [isReactHookName](functions/isReactHookName.md) - [isRenderFunctionLoose](functions/isRenderFunctionLoose.md) -- [isRenderLike](functions/isRenderLike.md) - [isRenderMethodLike](functions/isRenderMethodLike.md) - [isRenderPropLoose](functions/isRenderPropLoose.md) -- [isShouldComponentUpdate](functions/isShouldComponentUpdate.md) - [isThisSetState](functions/isThisSetState.md) -- [isUnsafeComponentWillMount](functions/isUnsafeComponentWillMount.md) -- [isUnsafeComponentWillReceiveProps](functions/isUnsafeComponentWillReceiveProps.md) -- [isUnsafeComponentWillUpdate](functions/isUnsafeComponentWillUpdate.md) - [isUseEffectCallLoose](functions/isUseEffectCallLoose.md) -- [isValidComponentDefinition](functions/isValidComponentDefinition.md) - [stringifyJsx](functions/stringifyJsx.md) - [useComponentCollector](functions/useComponentCollector.md) - [useComponentCollectorLegacy](functions/useComponentCollectorLegacy.md) diff --git a/packages/core/docs/functions/findParentAttribute.md b/packages/core/docs/functions/findParentAttribute.md index 9d5f4dd5e5..6d1d5b1092 100644 --- a/packages/core/docs/functions/findParentAttribute.md +++ b/packages/core/docs/functions/findParentAttribute.md @@ -8,7 +8,7 @@ > **findParentAttribute**(`node`, `test`): `undefined` \| `JSXAttribute` -Find the parent JSX attribute node of a node +Traverses up the AST to find a parent JSX attribute node that matches a given test ## Parameters @@ -16,16 +16,17 @@ Find the parent JSX attribute node of a node `Node` -The node to find the parent attribute of +The starting AST node ### test (`node`) => `boolean` -The test to apply to the parent attribute +Optional predicate function to test if the attribute meets criteria + Defaults to always returning true (matches any attribute) ## Returns `undefined` \| `JSXAttribute` -The parent attribute node or undefined +The first matching JSX attribute node found when traversing upwards, or undefined diff --git a/packages/core/docs/functions/getAttribute.md b/packages/core/docs/functions/getAttribute.md index 0eeaad383a..b7dd80077c 100644 --- a/packages/core/docs/functions/getAttribute.md +++ b/packages/core/docs/functions/getAttribute.md @@ -8,7 +8,8 @@ > **getAttribute**(`context`, `name`, `attributes`, `initialScope?`): `undefined` \| `JSXAttribute` \| `JSXSpreadAttribute` -Get the JSX attribute node with the given name +Searches for a specific JSX attribute by name in a list of attributes +Returns the last matching attribute (rightmost in JSX) ## Parameters @@ -16,28 +17,28 @@ Get the JSX attribute node with the given name `RuleContext` -The ESLint rule context +ESLint rule context ### name `string` -The name of the attribute +The name of the attribute to find ### attributes (`JSXAttribute` \| `JSXSpreadAttribute`)[] -The attributes to search +Array of JSX attributes to search through ### initialScope? `Scope` -The initial scope to use for variable resolution +Optional scope for resolving variables ## Returns `undefined` \| `JSXAttribute` \| `JSXSpreadAttribute` -The JSX attribute node or undefined +The found attribute or undefined diff --git a/packages/core/docs/functions/getAttributeValue.md b/packages/core/docs/functions/getAttributeValue.md index 6d055b8241..ba208e4f3c 100644 --- a/packages/core/docs/functions/getAttributeValue.md +++ b/packages/core/docs/functions/getAttributeValue.md @@ -8,7 +8,7 @@ > **getAttributeValue**(`context`, `node`, `name`): \{ `initialScope`: `undefined` \| `Scope`; `kind`: `"none"`; `node`: `Node`; \} \| \{ `initialScope`: `undefined` \| `Scope`; `kind`: `"some"`; `node`: `Node`; `value`: `unknown`; \} -Get a StaticValue of the attribute value +Extracts the value of a JSX attribute by name ## Parameters @@ -16,11 +16,11 @@ Get a StaticValue of the attribute value `RuleContext` -The rule context +ESLint rule context ### node -The JSX attribute node +JSX attribute or spread attribute node `JSXAttribute` | `JSXSpreadAttribute` @@ -28,10 +28,10 @@ The JSX attribute node `string` -The name of the attribute +Name of the attribute to extract ## Returns \{ `initialScope`: `undefined` \| `Scope`; `kind`: `"none"`; `node`: `Node`; \} \| \{ `initialScope`: `undefined` \| `Scope`; `kind`: `"some"`; `node`: `Node`; `value`: `unknown`; \} -The StaticValue of the attribute value +The extracted attribute value in a structured format diff --git a/packages/core/docs/functions/getElementType.md b/packages/core/docs/functions/getElementType.md index 1156a60f22..1d0076eaeb 100644 --- a/packages/core/docs/functions/getElementType.md +++ b/packages/core/docs/functions/getElementType.md @@ -8,7 +8,9 @@ > **getElementType**(`context`, `node`): `string` -Get the stringified type of a JSX element +Extracts the element type name from a JSX element or fragment +For JSX elements, returns the stringified name (e.g., "div", "Button", "React.Fragment") +For JSX fragments, returns an empty string ## Parameters @@ -16,11 +18,11 @@ Get the stringified type of a JSX element `RuleContext` -The ESLint rule context +ESLint rule context ### node -The JSX element node +JSX element or fragment node `JSXElement` | `JSXFragment` @@ -28,4 +30,4 @@ The JSX element node `string` -The type of the element +String representation of the element type diff --git a/packages/core/docs/functions/hasAnyAttribute.md b/packages/core/docs/functions/hasAnyAttribute.md index 1427856ec3..33abe8b33e 100644 --- a/packages/core/docs/functions/hasAnyAttribute.md +++ b/packages/core/docs/functions/hasAnyAttribute.md @@ -8,24 +8,36 @@ > **hasAnyAttribute**(`context`, `names`, `attributes`, `initialScope?`): `boolean` +Checks if a JSX element has at least one of the specified attributes + ## Parameters ### context `RuleContext` +ESLint rule context + ### names `string`[] +Array of attribute names to check for + ### attributes (`JSXAttribute` \| `JSXSpreadAttribute`)[] +List of JSX attributes from opening element + ### initialScope? `Scope` +Optional scope for resolving variables in spread attributes + ## Returns `boolean` + +boolean indicating whether any of the attributes exist diff --git a/packages/core/docs/functions/hasAttribute.md b/packages/core/docs/functions/hasAttribute.md index 8d2269f1fc..799d265d62 100644 --- a/packages/core/docs/functions/hasAttribute.md +++ b/packages/core/docs/functions/hasAttribute.md @@ -8,24 +8,36 @@ > **hasAttribute**(`context`, `name`, `attributes`, `initialScope?`): `boolean` +Checks if a JSX element has a specific attribute + ## Parameters ### context `RuleContext` +ESLint rule context + ### name `string` +Name of the attribute to check for + ### attributes (`JSXAttribute` \| `JSXSpreadAttribute`)[] +List of JSX attributes from opening element + ### initialScope? `Scope` +Optional scope for resolving variables in spread attributes + ## Returns `boolean` + +boolean indicating whether the attribute exists diff --git a/packages/core/docs/functions/hasEveryAttribute.md b/packages/core/docs/functions/hasEveryAttribute.md index 9fb6b3dac3..aee0af6e86 100644 --- a/packages/core/docs/functions/hasEveryAttribute.md +++ b/packages/core/docs/functions/hasEveryAttribute.md @@ -8,24 +8,36 @@ > **hasEveryAttribute**(`context`, `names`, `attributes`, `initialScope?`): `boolean` +Checks if a JSX element has all of the specified attributes + ## Parameters ### context `RuleContext` +ESLint rule context + ### names `string`[] +Array of attribute names to check for + ### attributes (`JSXAttribute` \| `JSXSpreadAttribute`)[] +List of JSX attributes from opening element + ### initialScope? `Scope` +Optional scope for resolving variables in spread attributes + ## Returns `boolean` + +boolean indicating whether all of the attributes exist diff --git a/packages/core/docs/functions/isComponentDefinition.md b/packages/core/docs/functions/isComponentDefinition.md new file mode 100644 index 0000000000..304e205ee1 --- /dev/null +++ b/packages/core/docs/functions/isComponentDefinition.md @@ -0,0 +1,37 @@ +[**@eslint-react/core**](../README.md) + +*** + +[@eslint-react/core](../README.md) / isComponentDefinition + +# Function: isComponentDefinition() + +> **isComponentDefinition**(`context`, `node`, `hint`): `boolean` + +Determines if a function node represents a valid React component definition + +## Parameters + +### context + +`RuleContext` + +The rule context + +### node + +`TSESTreeFunction` + +The function node to check + +### hint + +`bigint` + +Component detection hints as bit flags + +## Returns + +`boolean` + +`true` if the node is a valid component definition, `false` otherwise diff --git a/packages/core/docs/functions/isFragmentElement.md b/packages/core/docs/functions/isFragmentElement.md index fa1eb24b38..c8d38e1347 100644 --- a/packages/core/docs/functions/isFragmentElement.md +++ b/packages/core/docs/functions/isFragmentElement.md @@ -8,16 +8,25 @@ > **isFragmentElement**(`context`, `node`): `node is JSXElement` +Determines if a JSX element is a React Fragment +Fragments can be imported from React and used like or + ## Parameters ### context `RuleContext` +ESLint rule context + ### node `Node` +AST node to check + ## Returns `node is JSXElement` + +boolean indicating if the element is a Fragment with type narrowing diff --git a/packages/core/docs/functions/isFunctionOfRender.md b/packages/core/docs/functions/isFunctionOfRender.md deleted file mode 100644 index 101137ec8e..0000000000 --- a/packages/core/docs/functions/isFunctionOfRender.md +++ /dev/null @@ -1,32 +0,0 @@ -[**@eslint-react/core**](../README.md) - -*** - -[@eslint-react/core](../README.md) / isFunctionOfRender - -# Function: isFunctionOfRender() - -> **isFunctionOfRender**(`node`): `boolean` - -Check whether given node is a function of a render function of a class component - -## Parameters - -### node - -`TSESTreeFunction` - -The AST node to check - -## Returns - -`boolean` - -`true` if node is a render function, `false` if not - -## Example - -```tsx -class Component extends React.Component { - render = () =>
; -``` diff --git a/packages/core/docs/functions/isHostElement.md b/packages/core/docs/functions/isHostElement.md index acfe86ead5..b1828bd26d 100644 --- a/packages/core/docs/functions/isHostElement.md +++ b/packages/core/docs/functions/isHostElement.md @@ -8,16 +8,25 @@ > **isHostElement**(`context`, `node`): `boolean` +Determines if a JSX element is a host element +Host elements in React start with lowercase letters (e.g., div, span) + ## Parameters ### context `RuleContext` +ESLint rule context + ### node `Node` +AST node to check + ## Returns `boolean` + +boolean indicating if the element is a host element diff --git a/packages/core/docs/functions/isJsxLike.md b/packages/core/docs/functions/isJsxLike.md index b791b9c4ee..61619bf635 100644 --- a/packages/core/docs/functions/isJsxLike.md +++ b/packages/core/docs/functions/isJsxLike.md @@ -8,13 +8,14 @@ > **isJsxLike**(`code`, `node`, `hint`): `boolean` -Heuristic decision to determine if a node is a JSX-like node. +Determines if a node represents JSX-like content based on heuristics +Supports configuration through hint flags to customize detection behavior ## Parameters ### code -The sourceCode object +The source code with scope lookup capability #### getScope @@ -24,7 +25,7 @@ The function to get the scope of a node ### node -The AST node to check +The AST node to analyze `undefined` | `null` | `Node` @@ -32,10 +33,10 @@ The AST node to check `bigint` = `DEFAULT_JSX_DETECTION_HINT` -The `JSXDetectionHint` to use +The configuration flags to adjust detection behavior ## Returns `boolean` -boolean +boolean Whether the node is considered JSX-like diff --git a/packages/core/docs/functions/isJsxText.md b/packages/core/docs/functions/isJsxText.md index 3d2c2e4952..6e8ccb8e82 100644 --- a/packages/core/docs/functions/isJsxText.md +++ b/packages/core/docs/functions/isJsxText.md @@ -8,7 +8,7 @@ > **isJsxText**(`node`): node is JSXText \| Literal -Check if a node is a `JSXText` or a `Literal` node +Checks if a node is a `JSXText` or a `Literal` node ## Parameters diff --git a/packages/core/docs/functions/isReactHook.md b/packages/core/docs/functions/isReactHook.md index 0fa31a3f1e..8c8d576f38 100644 --- a/packages/core/docs/functions/isReactHook.md +++ b/packages/core/docs/functions/isReactHook.md @@ -8,12 +8,18 @@ > **isReactHook**(`node`): `boolean` +Determines if a function node is a React Hook based on its name. + ## Parameters ### node +The function node to check + `undefined` | `TSESTreeFunction` ## Returns `boolean` + +True if the function is a React Hook, false otherwise diff --git a/packages/core/docs/functions/isReactHookCallWithName.md b/packages/core/docs/functions/isReactHookCallWithName.md index 6844b8eb4a..1898ce48f5 100644 --- a/packages/core/docs/functions/isReactHookCallWithName.md +++ b/packages/core/docs/functions/isReactHookCallWithName.md @@ -8,18 +8,27 @@ > **isReactHookCallWithName**(`context`, `node`): (`name`) => `boolean` +Checks if a node is a call to a specific React hook, with React import validation. +Returns a function that accepts a hook name to check against. + ## Parameters ### context `RuleContext` +The rule context + ### node +The AST node to check + `undefined` | `Node` ## Returns +A function that takes a hook name and returns boolean + > (`name`): `boolean` ### Parameters diff --git a/packages/core/docs/functions/isReactHookCallWithNameAlias.md b/packages/core/docs/functions/isReactHookCallWithNameAlias.md index 0c4505e287..c6906d4763 100644 --- a/packages/core/docs/functions/isReactHookCallWithNameAlias.md +++ b/packages/core/docs/functions/isReactHookCallWithNameAlias.md @@ -8,22 +8,32 @@ > **isReactHookCallWithNameAlias**(`context`, `name`, `alias`): (`node`) => `boolean` +Checks if a node is a call to a specific React hook or one of its aliases. + ## Parameters ### context `RuleContext` +The rule context + ### name `string` +The primary hook name to check + ### alias +Optional array of alias names to also accept + `undefined` | `string`[] ## Returns +Function that checks if a node matches the hook name or aliases + > (`node`): `boolean` ### Parameters diff --git a/packages/core/docs/functions/isReactHookCallWithNameLoose.md b/packages/core/docs/functions/isReactHookCallWithNameLoose.md index 43dc6a854b..7b34a16078 100644 --- a/packages/core/docs/functions/isReactHookCallWithNameLoose.md +++ b/packages/core/docs/functions/isReactHookCallWithNameLoose.md @@ -8,14 +8,20 @@ > **isReactHookCallWithNameLoose**(`node`): (`name`) => `boolean` +Lightweight version of isReactHookCallWithName that doesn't check imports. + ## Parameters ### node +The AST node to check + `undefined` | `Node` ## Returns +A function that takes a hook name and returns boolean + > (`name`): `boolean` ### Parameters diff --git a/packages/core/docs/functions/isRenderLike.md b/packages/core/docs/functions/isRenderLike.md deleted file mode 100644 index e70b29348f..0000000000 --- a/packages/core/docs/functions/isRenderLike.md +++ /dev/null @@ -1,35 +0,0 @@ -[**@eslint-react/core**](../README.md) - -*** - -[@eslint-react/core](../README.md) / isRenderLike - -# Function: isRenderLike() - -> **isRenderLike**(`node`): `node is TSESTreeMethodOrProperty` - -Check whether given node is a render function of a class component - -## Parameters - -### node - -`Node` - -The AST node to check - -## Returns - -`node is TSESTreeMethodOrProperty` - -`true` if node is a render function, `false` if not - -## Example - -```tsx -class Component extends React.Component { - render() { - return
; - } -} -``` diff --git a/packages/core/docs/functions/isUseEffectCallLoose.md b/packages/core/docs/functions/isUseEffectCallLoose.md index c7c7bbd769..819edbf111 100644 --- a/packages/core/docs/functions/isUseEffectCallLoose.md +++ b/packages/core/docs/functions/isUseEffectCallLoose.md @@ -8,12 +8,18 @@ > **isUseEffectCallLoose**(`node`): `boolean` +Detects useEffect calls and variations (useLayoutEffect, etc.) using regex pattern. + ## Parameters ### node +The AST node to check + `undefined` | `Node` ## Returns `boolean` + +True if the node is a useEffect-like call diff --git a/packages/core/docs/functions/isValidComponentDefinition.md b/packages/core/docs/functions/isValidComponentDefinition.md deleted file mode 100644 index 823d163a37..0000000000 --- a/packages/core/docs/functions/isValidComponentDefinition.md +++ /dev/null @@ -1,27 +0,0 @@ -[**@eslint-react/core**](../README.md) - -*** - -[@eslint-react/core](../README.md) / isValidComponentDefinition - -# Function: isValidComponentDefinition() - -> **isValidComponentDefinition**(`context`, `node`, `hint`): `boolean` - -## Parameters - -### context - -`RuleContext` - -### node - -`TSESTreeFunction` - -### hint - -`bigint` - -## Returns - -`boolean` diff --git a/packages/core/docs/functions/stringifyJsx.md b/packages/core/docs/functions/stringifyJsx.md index 6c88de9e50..1d9bb643eb 100644 --- a/packages/core/docs/functions/stringifyJsx.md +++ b/packages/core/docs/functions/stringifyJsx.md @@ -8,13 +8,14 @@ > **stringifyJsx**(`node`): `string` -Get the stringified representation of a JSX node +Converts a JSX AST node to its string representation +Handles different JSX node types and returns their textual form ## Parameters ### node -The JSX node +JSX node from TypeScript ESTree `JSXClosingElement` | `JSXClosingFragment` | `JSXIdentifier` | `JSXMemberExpression` | `JSXNamespacedName` | `JSXOpeningElement` | `JSXOpeningFragment` | `JSXText` @@ -22,4 +23,4 @@ The JSX node `string` -The stringified representation +String representation of the JSX node diff --git a/packages/core/docs/type-aliases/JSXDetectionHint.md b/packages/core/docs/type-aliases/JSXDetectionHint.md index a731ff2769..400be121fc 100644 --- a/packages/core/docs/type-aliases/JSXDetectionHint.md +++ b/packages/core/docs/type-aliases/JSXDetectionHint.md @@ -7,3 +7,6 @@ # Type Alias: JSXDetectionHint > **JSXDetectionHint** = `bigint` + +BitFlags for configuring JSX detection behavior +Uses BigInt for bit operations to support many flags diff --git a/packages/core/docs/variables/DEFAULT_JSX_DETECTION_HINT.md b/packages/core/docs/variables/DEFAULT_JSX_DETECTION_HINT.md index 6575ef7645..f556f7298a 100644 --- a/packages/core/docs/variables/DEFAULT_JSX_DETECTION_HINT.md +++ b/packages/core/docs/variables/DEFAULT_JSX_DETECTION_HINT.md @@ -7,3 +7,6 @@ # Variable: DEFAULT\_JSX\_DETECTION\_HINT > `const` **DEFAULT\_JSX\_DETECTION\_HINT**: `bigint` + +Default JSX detection configuration +Skips undefined and boolean literals (common in React) diff --git a/packages/core/docs/variables/JSXDetectionHint.md b/packages/core/docs/variables/JSXDetectionHint.md index fdd507e1e0..2c6d7cb59b 100644 --- a/packages/core/docs/variables/JSXDetectionHint.md +++ b/packages/core/docs/variables/JSXDetectionHint.md @@ -8,6 +8,10 @@ > **JSXDetectionHint**: `object` +Flags to control JSX detection behavior: +- Skip* flags: Ignore specific node types when detecting JSX +- Strict* flags: Enforce stricter rules for container types + ## Type Declaration ### None diff --git a/packages/core/docs/functions/isComponentDidCatch.md b/packages/core/docs/variables/isComponentDidCatch.md similarity index 61% rename from packages/core/docs/functions/isComponentDidCatch.md rename to packages/core/docs/variables/isComponentDidCatch.md index c172b47a03..ae5d5aa6f0 100644 --- a/packages/core/docs/functions/isComponentDidCatch.md +++ b/packages/core/docs/variables/isComponentDidCatch.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isComponentDidCatch -# Function: isComponentDidCatch() +# Variable: isComponentDidCatch() -> **isComponentDidCatch**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isComponentDidCatch**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isComponentDidMount.md b/packages/core/docs/variables/isComponentDidMount.md similarity index 61% rename from packages/core/docs/functions/isComponentDidMount.md rename to packages/core/docs/variables/isComponentDidMount.md index 305412796e..385425b487 100644 --- a/packages/core/docs/functions/isComponentDidMount.md +++ b/packages/core/docs/variables/isComponentDidMount.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isComponentDidMount -# Function: isComponentDidMount() +# Variable: isComponentDidMount() -> **isComponentDidMount**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isComponentDidMount**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isComponentDidUpdate.md b/packages/core/docs/variables/isComponentDidUpdate.md similarity index 61% rename from packages/core/docs/functions/isComponentDidUpdate.md rename to packages/core/docs/variables/isComponentDidUpdate.md index 50e89919b2..0cfe5cfe0c 100644 --- a/packages/core/docs/functions/isComponentDidUpdate.md +++ b/packages/core/docs/variables/isComponentDidUpdate.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isComponentDidUpdate -# Function: isComponentDidUpdate() +# Variable: isComponentDidUpdate() -> **isComponentDidUpdate**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isComponentDidUpdate**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isComponentWillMount.md b/packages/core/docs/variables/isComponentWillMount.md similarity index 61% rename from packages/core/docs/functions/isComponentWillMount.md rename to packages/core/docs/variables/isComponentWillMount.md index a99ad58fec..47cb462a5f 100644 --- a/packages/core/docs/functions/isComponentWillMount.md +++ b/packages/core/docs/variables/isComponentWillMount.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isComponentWillMount -# Function: isComponentWillMount() +# Variable: isComponentWillMount() -> **isComponentWillMount**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isComponentWillMount**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isComponentWillReceiveProps.md b/packages/core/docs/variables/isComponentWillReceiveProps.md similarity index 59% rename from packages/core/docs/functions/isComponentWillReceiveProps.md rename to packages/core/docs/variables/isComponentWillReceiveProps.md index 958ebc220f..6d1fe47d9c 100644 --- a/packages/core/docs/functions/isComponentWillReceiveProps.md +++ b/packages/core/docs/variables/isComponentWillReceiveProps.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isComponentWillReceiveProps -# Function: isComponentWillReceiveProps() +# Variable: isComponentWillReceiveProps() -> **isComponentWillReceiveProps**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isComponentWillReceiveProps**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isComponentWillUnmount.md b/packages/core/docs/variables/isComponentWillUnmount.md similarity index 60% rename from packages/core/docs/functions/isComponentWillUnmount.md rename to packages/core/docs/variables/isComponentWillUnmount.md index 3fe19cc987..a100e7be20 100644 --- a/packages/core/docs/functions/isComponentWillUnmount.md +++ b/packages/core/docs/variables/isComponentWillUnmount.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isComponentWillUnmount -# Function: isComponentWillUnmount() +# Variable: isComponentWillUnmount() -> **isComponentWillUnmount**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isComponentWillUnmount**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isComponentWillUpdate.md b/packages/core/docs/variables/isComponentWillUpdate.md similarity index 60% rename from packages/core/docs/functions/isComponentWillUpdate.md rename to packages/core/docs/variables/isComponentWillUpdate.md index cf85eb66b0..65923432eb 100644 --- a/packages/core/docs/functions/isComponentWillUpdate.md +++ b/packages/core/docs/variables/isComponentWillUpdate.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isComponentWillUpdate -# Function: isComponentWillUpdate() +# Variable: isComponentWillUpdate() -> **isComponentWillUpdate**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isComponentWillUpdate**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isGetChildContext.md b/packages/core/docs/variables/isGetChildContext.md similarity index 62% rename from packages/core/docs/functions/isGetChildContext.md rename to packages/core/docs/variables/isGetChildContext.md index bbd8d27eed..e29e6ab28c 100644 --- a/packages/core/docs/functions/isGetChildContext.md +++ b/packages/core/docs/variables/isGetChildContext.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isGetChildContext -# Function: isGetChildContext() +# Variable: isGetChildContext() -> **isGetChildContext**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isGetChildContext**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isGetDefaultProps.md b/packages/core/docs/variables/isGetDefaultProps.md similarity index 62% rename from packages/core/docs/functions/isGetDefaultProps.md rename to packages/core/docs/variables/isGetDefaultProps.md index f6e18a39b2..c466595b75 100644 --- a/packages/core/docs/functions/isGetDefaultProps.md +++ b/packages/core/docs/variables/isGetDefaultProps.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isGetDefaultProps -# Function: isGetDefaultProps() +# Variable: isGetDefaultProps() -> **isGetDefaultProps**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isGetDefaultProps**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isGetDerivedStateFromError.md b/packages/core/docs/variables/isGetDerivedStateFromError.md similarity index 59% rename from packages/core/docs/functions/isGetDerivedStateFromError.md rename to packages/core/docs/variables/isGetDerivedStateFromError.md index 714df9e426..a8914e654e 100644 --- a/packages/core/docs/functions/isGetDerivedStateFromError.md +++ b/packages/core/docs/variables/isGetDerivedStateFromError.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isGetDerivedStateFromError -# Function: isGetDerivedStateFromError() +# Variable: isGetDerivedStateFromError() -> **isGetDerivedStateFromError**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isGetDerivedStateFromError**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isGetDerivedStateFromProps.md b/packages/core/docs/variables/isGetDerivedStateFromProps.md similarity index 59% rename from packages/core/docs/functions/isGetDerivedStateFromProps.md rename to packages/core/docs/variables/isGetDerivedStateFromProps.md index a1e42b4c8c..4805f50d79 100644 --- a/packages/core/docs/functions/isGetDerivedStateFromProps.md +++ b/packages/core/docs/variables/isGetDerivedStateFromProps.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isGetDerivedStateFromProps -# Function: isGetDerivedStateFromProps() +# Variable: isGetDerivedStateFromProps() -> **isGetDerivedStateFromProps**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isGetDerivedStateFromProps**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isGetInitialState.md b/packages/core/docs/variables/isGetInitialState.md similarity index 62% rename from packages/core/docs/functions/isGetInitialState.md rename to packages/core/docs/variables/isGetInitialState.md index 47dbcf4cde..3eaa3be850 100644 --- a/packages/core/docs/functions/isGetInitialState.md +++ b/packages/core/docs/variables/isGetInitialState.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isGetInitialState -# Function: isGetInitialState() +# Variable: isGetInitialState() -> **isGetInitialState**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isGetInitialState**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isGetSnapshotBeforeUpdate.md b/packages/core/docs/variables/isGetSnapshotBeforeUpdate.md similarity index 59% rename from packages/core/docs/functions/isGetSnapshotBeforeUpdate.md rename to packages/core/docs/variables/isGetSnapshotBeforeUpdate.md index c631b21d36..cfb12f95e9 100644 --- a/packages/core/docs/functions/isGetSnapshotBeforeUpdate.md +++ b/packages/core/docs/variables/isGetSnapshotBeforeUpdate.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isGetSnapshotBeforeUpdate -# Function: isGetSnapshotBeforeUpdate() +# Variable: isGetSnapshotBeforeUpdate() -> **isGetSnapshotBeforeUpdate**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isGetSnapshotBeforeUpdate**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/variables/isRender.md b/packages/core/docs/variables/isRender.md new file mode 100644 index 0000000000..9954081fc9 --- /dev/null +++ b/packages/core/docs/variables/isRender.md @@ -0,0 +1,19 @@ +[**@eslint-react/core**](../README.md) + +*** + +[@eslint-react/core](../README.md) / isRender + +# Variable: isRender() + +> `const` **isRender**: (`node`) => `node is TSESTreeMethodOrProperty` + +## Parameters + +### node + +`Node` + +## Returns + +`node is TSESTreeMethodOrProperty` diff --git a/packages/core/docs/functions/isShouldComponentUpdate.md b/packages/core/docs/variables/isShouldComponentUpdate.md similarity index 60% rename from packages/core/docs/functions/isShouldComponentUpdate.md rename to packages/core/docs/variables/isShouldComponentUpdate.md index c7889a76d9..d6afce5a8c 100644 --- a/packages/core/docs/functions/isShouldComponentUpdate.md +++ b/packages/core/docs/variables/isShouldComponentUpdate.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isShouldComponentUpdate -# Function: isShouldComponentUpdate() +# Variable: isShouldComponentUpdate() -> **isShouldComponentUpdate**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isShouldComponentUpdate**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isUnsafeComponentWillMount.md b/packages/core/docs/variables/isUnsafeComponentWillMount.md similarity index 59% rename from packages/core/docs/functions/isUnsafeComponentWillMount.md rename to packages/core/docs/variables/isUnsafeComponentWillMount.md index 4c016983ab..4e906b24e4 100644 --- a/packages/core/docs/functions/isUnsafeComponentWillMount.md +++ b/packages/core/docs/variables/isUnsafeComponentWillMount.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isUnsafeComponentWillMount -# Function: isUnsafeComponentWillMount() +# Variable: isUnsafeComponentWillMount() -> **isUnsafeComponentWillMount**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isUnsafeComponentWillMount**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isUnsafeComponentWillReceiveProps.md b/packages/core/docs/variables/isUnsafeComponentWillReceiveProps.md similarity index 58% rename from packages/core/docs/functions/isUnsafeComponentWillReceiveProps.md rename to packages/core/docs/variables/isUnsafeComponentWillReceiveProps.md index 19dc7162b7..f54110f644 100644 --- a/packages/core/docs/functions/isUnsafeComponentWillReceiveProps.md +++ b/packages/core/docs/variables/isUnsafeComponentWillReceiveProps.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isUnsafeComponentWillReceiveProps -# Function: isUnsafeComponentWillReceiveProps() +# Variable: isUnsafeComponentWillReceiveProps() -> **isUnsafeComponentWillReceiveProps**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isUnsafeComponentWillReceiveProps**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/docs/functions/isUnsafeComponentWillUpdate.md b/packages/core/docs/variables/isUnsafeComponentWillUpdate.md similarity index 59% rename from packages/core/docs/functions/isUnsafeComponentWillUpdate.md rename to packages/core/docs/variables/isUnsafeComponentWillUpdate.md index 2aade9ddcc..ac7922e2a0 100644 --- a/packages/core/docs/functions/isUnsafeComponentWillUpdate.md +++ b/packages/core/docs/variables/isUnsafeComponentWillUpdate.md @@ -4,9 +4,9 @@ [@eslint-react/core](../README.md) / isUnsafeComponentWillUpdate -# Function: isUnsafeComponentWillUpdate() +# Variable: isUnsafeComponentWillUpdate() -> **isUnsafeComponentWillUpdate**(`node`): `node is TSESTreeMethodOrProperty` +> `const` **isUnsafeComponentWillUpdate**: (`node`) => `node is TSESTreeMethodOrProperty` ## Parameters diff --git a/packages/core/src/component/component-collector.ts b/packages/core/src/component/component-collector.ts index f4c1de8d2c..199aa042d6 100644 --- a/packages/core/src/component/component-collector.ts +++ b/packages/core/src/component/component-collector.ts @@ -10,7 +10,7 @@ import type { FunctionComponent } from "./component-semantic-node"; import { isReactHookCall } from "../hook"; import { isJsxLike } from "../jsx"; -import { isValidComponentDefinition } from "./component-definition"; +import { isComponentDefinition } from "./component-definition"; import { DEFAULT_COMPONENT_DETECTION_HINT } from "./component-detection-hint"; import { getFunctionComponentId } from "./component-id"; import { getComponentFlagFromInitPath } from "./component-init-path"; @@ -102,7 +102,7 @@ export function useComponentCollector( const { body } = entry.node; const isComponent = hasNoneOrLooseComponentName(context, entry.node) && isJsxLike(context.sourceCode, body, hint) - && isValidComponentDefinition(context, entry.node, hint); + && isComponentDefinition(context, entry.node, hint); if (!isComponent) return; const initPath = AST.getFunctionInitPath(entry.node); const id = getFunctionComponentId(context, entry.node); @@ -151,7 +151,7 @@ export function useComponentCollector( if (entry == null) return; const isComponent = hasNoneOrLooseComponentName(context, entry.node) && isJsxLike(context.sourceCode, node.argument, hint) - && isValidComponentDefinition(context, entry.node, hint); + && isComponentDefinition(context, entry.node, hint); if (!isComponent) return; entry.isComponent = true; const initPath = AST.getFunctionInitPath(entry.node); diff --git a/packages/core/src/component/component-definition.ts b/packages/core/src/component/component-definition.ts index 7b52551ca1..4a9af2daae 100644 --- a/packages/core/src/component/component-definition.ts +++ b/packages/core/src/component/component-definition.ts @@ -1,5 +1,4 @@ import * as AST from "@eslint-react/ast"; - import { type RuleContext } from "@eslint-react/kit"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; import { isMatching, P } from "ts-pattern"; @@ -8,25 +7,30 @@ import { ComponentDetectionHint } from "./component-detection-hint"; import { isClassComponent } from "./component-is"; import { isRenderMethodLike } from "./component-render-method"; -const isFunctionOfClassMethod = isMatching({ - type: P.union(T.ArrowFunctionExpression, T.FunctionExpression), - parent: T.MethodDefinition, -}); +/** + * Function pattern matchers for different contexts + */ +const functionPatterns = { + classMethod: { + type: P.union(T.ArrowFunctionExpression, T.FunctionExpression), + parent: T.MethodDefinition, + }, -const isFunctionOfClassProperty = isMatching({ - type: P.union(T.ArrowFunctionExpression, T.FunctionExpression), - parent: T.Property, -}); + classProperty: { + type: P.union(T.ArrowFunctionExpression, T.FunctionExpression), + parent: T.Property, + }, -const isFunctionOfObjectMethod = isMatching({ - type: P.union(T.ArrowFunctionExpression, T.FunctionExpression), - parent: { - type: T.Property, + objectMethod: { + type: P.union(T.ArrowFunctionExpression, T.FunctionExpression), parent: { - type: T.ObjectExpression, + type: T.Property, + parent: { + type: T.ObjectExpression, + }, }, }, -}); +}; /** * Check whether given node is a function of a render method of a class component @@ -41,29 +45,53 @@ const isFunctionOfObjectMethod = isMatching({ * @returns `true` if node is a render function, `false` if not */ export function isFunctionOfRenderMethod(node: AST.TSESTreeFunction) { - if (!isRenderMethodLike(node.parent)) { - return false; - } - - return isClassComponent(node.parent.parent.parent); + return isRenderMethodLike(node.parent) && isClassComponent(node.parent.parent.parent); } -export function isValidComponentDefinition(context: RuleContext, node: AST.TSESTreeFunction, hint: bigint) { - if (isChildrenOfCreateElement(context, node) || isFunctionOfRenderMethod(node)) { - return false; +/** + * Checks if a function node should be excluded based on detection hints + * @param node The function node to check + * @param hint Component detection hints as bit flags + * @returns `true` if the function should be excluded, `false` otherwise + */ +function shouldExcludeBasedOnHint(node: AST.TSESTreeFunction, hint: bigint) { + if ((hint & ComponentDetectionHint.SkipObjectMethod) && isMatching(functionPatterns.objectMethod)(node)) { + return true; } - if (hint & ComponentDetectionHint.SkipObjectMethod && isFunctionOfObjectMethod(node.parent)) { - return false; + + if ((hint & ComponentDetectionHint.SkipClassMethod) && isMatching(functionPatterns.classMethod)(node)) { + return true; } - if (hint & ComponentDetectionHint.SkipClassMethod && isFunctionOfClassMethod(node.parent)) { - return false; + + if ((hint & ComponentDetectionHint.SkipClassProperty) && isMatching(functionPatterns.classProperty)(node)) { + return true; + } + + if ((hint & ComponentDetectionHint.SkipArrayMapArgument) && AST.isArrayMapCall(node.parent)) { + return true; } - if (hint & ComponentDetectionHint.SkipClassProperty && isFunctionOfClassProperty(node.parent)) { + + return false; +} + +/** + * Determines if a function node represents a valid React component definition + * @param context The rule context + * @param node The function node to check + * @param hint Component detection hints as bit flags + * @returns `true` if the node is a valid component definition, `false` otherwise + */ +export function isComponentDefinition(context: RuleContext, node: AST.TSESTreeFunction, hint: bigint) { + // Check for immediate exclusion cases + if (isChildrenOfCreateElement(context, node) || isFunctionOfRenderMethod(node)) { return false; } - if (hint & ComponentDetectionHint.SkipArrayMapArgument && AST.isArrayMapCall(node.parent)) { + + // Check for hint-based exclusions + if (shouldExcludeBasedOnHint(node, hint)) { return false; } + const significantParent = AST.findParentNode( node, AST.isOneOf([ @@ -74,5 +102,6 @@ export function isValidComponentDefinition(context: RuleContext, node: AST.TSEST T.ClassBody, ]), ); + return significantParent == null || significantParent.type !== T.JSXExpressionContainer; } diff --git a/packages/core/src/component/component-method.ts b/packages/core/src/component/component-method.ts index 2d3acd7d5e..48e311805a 100644 --- a/packages/core/src/component/component-method.ts +++ b/packages/core/src/component/component-method.ts @@ -2,121 +2,40 @@ import * as AST from "@eslint-react/ast"; import type { TSESTree } from "@typescript-eslint/types"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; -export function isComponentDidCatch(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "componentDidCatch"; -} - -export function isComponentDidMount(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "componentDidMount"; -} - -export function isComponentDidUpdate(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "componentDidUpdate"; -} - -export function isComponentWillMount(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "componentWillMount"; -} - -export function isComponentWillReceiveProps(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "componentWillReceiveProps"; -} - -export function isComponentWillUnmount(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "componentWillUnmount"; -} - -export function isComponentWillUpdate(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "componentWillUpdate"; -} - -export function isGetChildContext(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "getChildContext"; -} - -export function isGetDefaultProps(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && node.static - && node.key.type === T.Identifier - && node.key.name === "getDefaultProps"; -} - -export function isGetInitialState(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "getInitialState"; -} - -export function isGetSnapshotBeforeUpdate(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "getSnapshotBeforeUpdate"; -} - -export function isShouldComponentUpdate(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "shouldComponentUpdate"; -} - -export function isUnsafeComponentWillMount(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "UNSAFE_componentWillMount"; -} - -export function isUnsafeComponentWillReceiveProps(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "UNSAFE_componentWillReceiveProps"; -} - -export function isUnsafeComponentWillUpdate(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && !node.static - && node.key.type === T.Identifier - && node.key.name === "UNSAFE_componentWillUpdate"; -} - -export function isGetDerivedStateFromProps(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && node.static - && node.key.type === T.Identifier - && node.key.name === "getDerivedStateFromProps"; -} - -export function isGetDerivedStateFromError(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && node.static - && node.key.type === T.Identifier - && node.key.name === "getDerivedStateFromError"; -} +/** + * Create a lifecycle method checker function + * @param methodName The lifecycle method name + * @param isStatic Whether the method is static + */ +function createLifecycleChecker(methodName: string, isStatic: boolean) { + return function(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { + return ( + AST.isMethodOrProperty(node) + && node.static === isStatic + && node.key.type === T.Identifier + && node.key.name === methodName + ); + }; +} + +// Non-static lifecycle method checkers +export const isRender = createLifecycleChecker("render", false); +export const isComponentDidCatch = createLifecycleChecker("componentDidCatch", false); +export const isComponentDidMount = createLifecycleChecker("componentDidMount", false); +export const isComponentDidUpdate = createLifecycleChecker("componentDidUpdate", false); +export const isComponentWillMount = createLifecycleChecker("componentWillMount", false); +export const isComponentWillReceiveProps = createLifecycleChecker("componentWillReceiveProps", false); +export const isComponentWillUnmount = createLifecycleChecker("componentWillUnmount", false); +export const isComponentWillUpdate = createLifecycleChecker("componentWillUpdate", false); +export const isGetChildContext = createLifecycleChecker("getChildContext", false); +export const isGetInitialState = createLifecycleChecker("getInitialState", false); +export const isGetSnapshotBeforeUpdate = createLifecycleChecker("getSnapshotBeforeUpdate", false); +export const isShouldComponentUpdate = createLifecycleChecker("shouldComponentUpdate", false); +export const isUnsafeComponentWillMount = createLifecycleChecker("UNSAFE_componentWillMount", false); +export const isUnsafeComponentWillReceiveProps = createLifecycleChecker("UNSAFE_componentWillReceiveProps", false); +export const isUnsafeComponentWillUpdate = createLifecycleChecker("UNSAFE_componentWillUpdate", false); + +// Static lifecycle method checkers +export const isGetDefaultProps = createLifecycleChecker("getDefaultProps", true); +export const isGetDerivedStateFromProps = createLifecycleChecker("getDerivedStateFromProps", true); +export const isGetDerivedStateFromError = createLifecycleChecker("getDerivedStateFromError", true); diff --git a/packages/core/src/component/component-render.ts b/packages/core/src/component/component-render.ts deleted file mode 100644 index 531079594f..0000000000 --- a/packages/core/src/component/component-render.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as AST from "@eslint-react/ast"; -import type { TSESTree } from "@typescript-eslint/types"; -import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; - -import { isClassComponent } from "./component-is"; - -/** - * Check whether given node is a render function of a class component - * @example - * ```tsx - * class Component extends React.Component { - * render() { - * return
; - * } - * } - * ``` - * @param node The AST node to check - * @returns `true` if node is a render function, `false` if not - */ -export function isRenderLike(node: TSESTree.Node): node is AST.TSESTreeMethodOrProperty { - return AST.isMethodOrProperty(node) - && node.key.type === T.Identifier - && node.key.name === "render" - && node.parent.parent.type === T.ClassDeclaration; -} - -/** - * Check whether given node is a function of a render function of a class component - * @example - * ```tsx - * class Component extends React.Component { - * render = () =>
; - * ``` - * @param node The AST node to check - * @returns `true` if node is a render function, `false` if not - */ -export function isFunctionOfRender(node: AST.TSESTreeFunction) { - if (!isRenderLike(node.parent)) { - return false; - } - - return isClassComponent(node.parent.parent.parent); -} diff --git a/packages/core/src/component/index.ts b/packages/core/src/component/index.ts index 096e2a4b88..44e278131a 100644 --- a/packages/core/src/component/index.ts +++ b/packages/core/src/component/index.ts @@ -11,7 +11,6 @@ export type * from "./component-kind"; export * from "./component-method"; export * from "./component-name"; export * from "./component-phase"; -export * from "./component-render"; export * from "./component-render-method"; export * from "./component-render-prop"; export type * from "./component-semantic-node"; diff --git a/packages/core/src/hook/hook-hierarchy.ts b/packages/core/src/hook/hook-hierarchy.ts index d1786f1d18..ad06127a5e 100644 --- a/packages/core/src/hook/hook-hierarchy.ts +++ b/packages/core/src/hook/hook-hierarchy.ts @@ -4,6 +4,11 @@ import type { TSESTree } from "@typescript-eslint/types"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; import { isUseEffectCallLoose } from "./hook-is"; +/** + * Determines if the node is the setup function of a useEffect hook + * (the first argument passed to useEffect) + * @param node The node to check + */ export function isFunctionOfUseEffectSetup(node: TSESTree.Node | unit) { if (node == null) return false; return node.parent?.type === T.CallExpression @@ -13,11 +18,21 @@ export function isFunctionOfUseEffectSetup(node: TSESTree.Node | unit) { && isUseEffectCallLoose(node.parent); } +/** + * Determines if the node is part of a useEffect cleanup function + * (the function returned by the setup function of useEffect) + * @param node The node to check + */ export function isFunctionOfUseEffectCleanup(node: TSESTree.Node | unit) { if (node == null) return false; + // Find the parent return statement const pReturn = AST.findParentNode(node, AST.is(T.ReturnStatement)); - const pFunction = AST.findParentNode(node, AST.isFunction); // Correctly named variable + // Find the nearest parent function + const pFunction = AST.findParentNode(node, AST.isFunction); + // Find the function containing the return statement const pFunctionOfReturn = AST.findParentNode(pReturn, AST.isFunction); - if (pFunction !== pFunctionOfReturn) return false; // Ensure consistent variable naming + // Ensure the node is in the same function as the return statement + if (pFunction !== pFunctionOfReturn) return false; + // Check if this function is a useEffect setup function return isFunctionOfUseEffectSetup(pFunction); } diff --git a/packages/core/src/hook/hook-is.ts b/packages/core/src/hook/hook-is.ts index 98a29338fb..b973903f3f 100644 --- a/packages/core/src/hook/hook-is.ts +++ b/packages/core/src/hook/hook-is.ts @@ -9,6 +9,11 @@ import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; import { isInitializedFromReact } from "../utils"; import { isReactHookName } from "./hook-name"; +/** + * Determines if a function node is a React Hook based on its name. + * @param node The function node to check + * @returns True if the function is a React Hook, false otherwise + */ export function isReactHook(node: AST.TSESTreeFunction | unit) { if (node == null) return false; const id = AST.getFunctionId(node); @@ -34,6 +39,13 @@ export function isReactHookCall(node: TSESTree.Node | unit) { return false; } +/** + * Checks if a node is a call to a specific React hook, with React import validation. + * Returns a function that accepts a hook name to check against. + * @param context The rule context + * @param node The AST node to check + * @returns A function that takes a hook name and returns boolean + */ /* eslint-disable function/function-return-boolean */ export function isReactHookCallWithName(context: RuleContext, node: TSESTree.Node | unit) { if (node == null || node.type !== T.CallExpression) return constFalse; @@ -58,6 +70,11 @@ export function isReactHookCallWithName(context: RuleContext, node: TSESTree.Nod }; } +/** + * Lightweight version of isReactHookCallWithName that doesn't check imports. + * @param node The AST node to check + * @returns A function that takes a hook name and returns boolean + */ export function isReactHookCallWithNameLoose(node: TSESTree.Node | unit) { if (node == null || node.type !== T.CallExpression) return constFalse; return (name: string) => { @@ -72,6 +89,13 @@ export function isReactHookCallWithNameLoose(node: TSESTree.Node | unit) { }; } +/** + * Checks if a node is a call to a specific React hook or one of its aliases. + * @param context The rule context + * @param name The primary hook name to check + * @param alias Optional array of alias names to also accept + * @returns Function that checks if a node matches the hook name or aliases + */ export function isReactHookCallWithNameAlias(context: RuleContext, name: string, alias: unit | string[] = []) { const { importSource = DEFAULT_ESLINT_REACT_SETTINGS.importSource, @@ -95,6 +119,11 @@ export function isReactHookCallWithNameAlias(context: RuleContext, name: string, } /* eslint-enable function/function-return-boolean */ +/** + * Detects useEffect calls and variations (useLayoutEffect, etc.) using regex pattern. + * @param node The AST node to check + * @returns True if the node is a useEffect-like call + */ export function isUseEffectCallLoose(node: TSESTree.Node | unit) { if (node == null) return false; if (node.type !== T.CallExpression) { @@ -111,6 +140,7 @@ export function isUseEffectCallLoose(node: TSESTree.Node | unit) { } } +// Utility functions for specific React hooks - each returns a function that checks if a node calls that specific hook export const isUseCall = flip(isReactHookCallWithName)("use"); export const isUseActionStateCall = flip(isReactHookCallWithName)("useActionState"); export const isUseCallbackCall = flip(isReactHookCallWithName)("useCallback"); diff --git a/packages/core/src/jsx/jsx-attribute-value.ts b/packages/core/src/jsx/jsx-attribute-value.ts index db1c68975b..37a72e6758 100644 --- a/packages/core/src/jsx/jsx-attribute-value.ts +++ b/packages/core/src/jsx/jsx-attribute-value.ts @@ -5,20 +5,22 @@ import type { TSESTree } from "@typescript-eslint/utils"; import { match, P } from "ts-pattern"; /** - * Get a StaticValue of the attribute value - * @param context The rule context - * @param node The JSX attribute node - * @param name The name of the attribute - * @returns The StaticValue of the attribute value + * Extracts the value of a JSX attribute by name + * @param context - ESLint rule context + * @param node - JSX attribute or spread attribute node + * @param name - Name of the attribute to extract + * @returns The extracted attribute value in a structured format */ export function getAttributeValue( context: RuleContext, node: TSESTree.JSXAttribute | TSESTree.JSXSpreadAttribute, name: string, ): Exclude { + // Get the initial scope from the node's context const initialScope = context.sourceCode.getScope(node); switch (node.type) { case T.JSXAttribute: + // Case 1: Literal value (e.g., className="container") if (node.value?.type === T.Literal) { return { kind: "some", @@ -27,6 +29,7 @@ export function getAttributeValue( value: node.value.value, } as const; } + // Case 2: Expression container (e.g., className={variable}) if (node.value?.type === T.JSXExpressionContainer) { return VAR.toStaticValue({ kind: "lazy", @@ -34,16 +37,20 @@ export function getAttributeValue( initialScope, }); } + // Case 3: Boolean attribute with no value (e.g., disabled) return { kind: "none", node, initialScope } as const; case T.JSXSpreadAttribute: { + // For spread attributes (e.g., {...props}), try to extract static value const staticValue = VAR.toStaticValue({ kind: "lazy", node: node.argument, initialScope, }); + // If can't extract static value, return none if (staticValue.kind === "none") { return staticValue; } + // If spread object contains the named property, extract its value return match(staticValue.value) .with({ [name]: P.select(P.any) }, (value) => ({ kind: "some", @@ -54,6 +61,7 @@ export function getAttributeValue( .otherwise(() => ({ kind: "none", node, initialScope } as const)); } default: + // Fallback case for unknown node types return { kind: "none", node, initialScope } as const; } } diff --git a/packages/core/src/jsx/jsx-attribute.ts b/packages/core/src/jsx/jsx-attribute.ts index 986dd4e215..9e73d7f59a 100644 --- a/packages/core/src/jsx/jsx-attribute.ts +++ b/packages/core/src/jsx/jsx-attribute.ts @@ -8,12 +8,14 @@ import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; import { getAttributeName } from "./jsx-attribute-name"; /** - * Get the JSX attribute node with the given name - * @param context The ESLint rule context - * @param name The name of the attribute - * @param attributes The attributes to search - * @param initialScope The initial scope to use for variable resolution - * @returns The JSX attribute node or undefined + * Searches for a specific JSX attribute by name in a list of attributes + * Returns the last matching attribute (rightmost in JSX) + * + * @param context - ESLint rule context + * @param name - The name of the attribute to find + * @param attributes - Array of JSX attributes to search through + * @param initialScope - Optional scope for resolving variables + * @returns The found attribute or undefined */ export function getAttribute( context: RuleContext, @@ -22,11 +24,16 @@ export function getAttribute( initialScope?: Scope, ): TSESTree.JSXAttribute | TSESTree.JSXSpreadAttribute | unit { return attributes.findLast((attr) => { + // Case 1: Direct JSX attribute (e.g., className="value") if (attr.type === T.JSXAttribute) { return getAttributeName(context, attr) === name; } + + // For spread attributes, we need a scope to resolve variables if (initialScope == null) return false; + switch (attr.argument.type) { + // Case 2: Spread from variable (e.g., {...props}) case T.Identifier: { const variable = VAR.findVariable(attr.argument.name, initialScope); const variableNode = VAR.getVariableInitNode(variable, 0); @@ -35,6 +42,7 @@ export function getAttribute( } return false; } + // Case 3: Spread from object literal (e.g., {{...{prop: value}}}) case T.ObjectExpression: return VAR.findPropertyInProperties(name, attr.argument.properties, initialScope) != null; } diff --git a/packages/core/src/jsx/jsx-detection-hint.ts b/packages/core/src/jsx/jsx-detection-hint.ts index edcc655675..a3ecba7063 100644 --- a/packages/core/src/jsx/jsx-detection-hint.ts +++ b/packages/core/src/jsx/jsx-detection-hint.ts @@ -1,3 +1,4 @@ +// This is a representation of React Node types for reference // type ReactNode = // | ReactElement // | string @@ -11,25 +12,38 @@ // keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_REACT_NODES // ]; +/** + * BitFlags for configuring JSX detection behavior + * Uses BigInt for bit operations to support many flags + */ export type JSXDetectionHint = bigint; /* eslint-disable perfectionist/sort-objects */ +/** + * Flags to control JSX detection behavior: + * - Skip* flags: Ignore specific node types when detecting JSX + * - Strict* flags: Enforce stricter rules for container types + */ export const JSXDetectionHint = { None: 0n, - SkipUndefined: 1n << 0n, - SkipNullLiteral: 1n << 1n, - SkipBooleanLiteral: 1n << 2n, - SkipStringLiteral: 1n << 3n, - SkipNumberLiteral: 1n << 4n, - SkipBigIntLiteral: 1n << 5n, - SkipEmptyArray: 1n << 6n, - SkipCreateElement: 1n << 7n, - StrictArray: 1n << 8n, - StrictLogical: 1n << 9n, - StrictConditional: 1n << 10n, + SkipUndefined: 1n << 0n, // Ignore undefined values + SkipNullLiteral: 1n << 1n, // Ignore null literals + SkipBooleanLiteral: 1n << 2n, // Ignore boolean literals + SkipStringLiteral: 1n << 3n, // Ignore string literals + SkipNumberLiteral: 1n << 4n, // Ignore number literals + SkipBigIntLiteral: 1n << 5n, // Ignore bigint literals + SkipEmptyArray: 1n << 6n, // Ignore empty arrays + SkipCreateElement: 1n << 7n, // Ignore React.createElement calls + StrictArray: 1n << 8n, // Require all array elements to be JSX + StrictLogical: 1n << 9n, // Require both sides of logical expr to be JSX + StrictConditional: 1n << 10n, // Require both branches of conditional to be JSX } as const; /* eslint-enable perfectionist/sort-objects */ +/** + * Default JSX detection configuration + * Skips undefined and boolean literals (common in React) + */ export const DEFAULT_JSX_DETECTION_HINT = 0n | JSXDetectionHint.SkipUndefined | JSXDetectionHint.SkipBooleanLiteral; diff --git a/packages/core/src/jsx/jsx-detection.ts b/packages/core/src/jsx/jsx-detection.ts index 13a7127211..3c355b4f0c 100644 --- a/packages/core/src/jsx/jsx-detection.ts +++ b/packages/core/src/jsx/jsx-detection.ts @@ -8,7 +8,7 @@ import type { TSESTree } from "@typescript-eslint/utils"; import { DEFAULT_JSX_DETECTION_HINT, JSXDetectionHint } from "./jsx-detection-hint"; /** - * Check if a node is a `JSXText` or a `Literal` node + * Checks if a node is a `JSXText` or a `Literal` node * @param node The AST node to check * @returns `true` if the node is a `JSXText` or a `Literal` node */ @@ -18,12 +18,14 @@ export function isJsxText(node: TSESTree.Node | null | unit): node is TSESTree.J } /** - * Heuristic decision to determine if a node is a JSX-like node. - * @param code The sourceCode object + * Determines if a node represents JSX-like content based on heuristics + * Supports configuration through hint flags to customize detection behavior + * + * @param code The source code with scope lookup capability * @param code.getScope The function to get the scope of a node - * @param node The AST node to check - * @param hint The `JSXDetectionHint` to use - * @returns boolean + * @param node The AST node to analyze + * @param hint The configuration flags to adjust detection behavior + * @returns boolean Whether the node is considered JSX-like */ export function isJsxLike( code: { getScope: (node: TSESTree.Node) => Scope }, @@ -31,9 +33,13 @@ export function isJsxLike( hint: JSXDetectionHint = DEFAULT_JSX_DETECTION_HINT, ): boolean { if (node == null) return false; + + // Actual JSX nodes are always considered JSX-like if (AST.isJSX(node)) return true; + switch (node.type) { case T.Literal: { + // Handle different literal types according to hint flags switch (typeof node.value) { case "boolean": return !(hint & JSXDetectionHint.SkipBooleanLiteral); @@ -50,24 +56,31 @@ export function isJsxLike( return false; } case T.TemplateLiteral: { + // Template literals are treated like string literals return !(hint & JSXDetectionHint.SkipStringLiteral); } case T.ArrayExpression: { + // Empty arrays can be filtered with SkipEmptyArray if (node.elements.length === 0) { return !(hint & JSXDetectionHint.SkipEmptyArray); } + // StrictArray requires all elements to be JSX if (hint & JSXDetectionHint.StrictArray) { return node.elements.every((n) => isJsxLike(code, n, hint)); } + // Default: array is JSX-like if any element is JSX-like return node.elements.some((n) => isJsxLike(code, n, hint)); } case T.LogicalExpression: { + // StrictLogical requires both sides to be JSX if (hint & JSXDetectionHint.StrictLogical) { return isJsxLike(code, node.left, hint) && isJsxLike(code, node.right, hint); } + // Default: logical expression is JSX-like if either side is JSX-like return isJsxLike(code, node.left, hint) || isJsxLike(code, node.right, hint); } case T.ConditionalExpression: { + // Helper function to check if the consequent (then) branch has JSX function leftHasJSX(node: TSESTree.ConditionalExpression) { if (Array.isArray(node.consequent)) { if (node.consequent.length === 0) { @@ -80,22 +93,30 @@ export function isJsxLike( } return isJsxLike(code, node.consequent, hint); } + + // Helper function to check if the alternate (else) branch has JSX function rightHasJSX(node: TSESTree.ConditionalExpression) { return isJsxLike(code, node.alternate, hint); } + + // StrictConditional requires both branches to contain JSX if (hint & JSXDetectionHint.StrictConditional) { return leftHasJSX(node) && rightHasJSX(node); } + // Default: conditional is JSX-like if either branch has JSX return leftHasJSX(node) || rightHasJSX(node); } case T.SequenceExpression: { + // For sequence expressions, only check the last expression const exp = node.expressions.at(-1); return isJsxLike(code, exp, hint); } case T.CallExpression: { + // Skip createElement calls if configured to do so if (hint & JSXDetectionHint.SkipCreateElement) { return false; } + // Check for React.createElement or createElement calls switch (node.callee.type) { case T.Identifier: return node.callee.name === "createElement"; @@ -106,12 +127,15 @@ export function isJsxLike( } case T.Identifier: { const { name } = node; + // Handle 'undefined' identifier according to hint if (name === "undefined") { return !(hint & JSXDetectionHint.SkipUndefined); } + // Check if this is a JSX tag name if (AST.isJSXTagNameExpression(node)) { return true; } + // Resolve variables to their values and check if they're JSX-like const variable = VAR.findVariable(name, code.getScope(node)); const variableNode = variable && VAR.getVariableInitNode(variable, 0); diff --git a/packages/core/src/jsx/jsx-element-is.ts b/packages/core/src/jsx/jsx-element-is.ts index b73bda9b13..69c1e0e3dc 100644 --- a/packages/core/src/jsx/jsx-element-is.ts +++ b/packages/core/src/jsx/jsx-element-is.ts @@ -3,12 +3,28 @@ import type { TSESTree } from "@typescript-eslint/types"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; import { getElementType } from "./jsx-element-type"; +/** + * Determines if a JSX element is a host element + * Host elements in React start with lowercase letters (e.g., div, span) + * + * @param context - ESLint rule context + * @param node - AST node to check + * @returns boolean indicating if the element is a host element + */ export function isHostElement(context: RuleContext, node: TSESTree.Node) { return node.type === T.JSXElement && node.openingElement.name.type === T.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name); } +/** + * Determines if a JSX element is a React Fragment + * Fragments can be imported from React and used like or + * + * @param context - ESLint rule context + * @param node - AST node to check + * @returns boolean indicating if the element is a Fragment with type narrowing + */ export function isFragmentElement(context: RuleContext, node: TSESTree.Node): node is TSESTree.JSXElement { if (node.type !== T.JSXElement) return false; return getElementType(context, node) diff --git a/packages/core/src/jsx/jsx-element-type.ts b/packages/core/src/jsx/jsx-element-type.ts index 379425a184..5909c9a0fa 100644 --- a/packages/core/src/jsx/jsx-element-type.ts +++ b/packages/core/src/jsx/jsx-element-type.ts @@ -4,10 +4,13 @@ import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; import { stringifyJsx } from "./jsx-stringify"; /** - * Get the stringified type of a JSX element - * @param context The ESLint rule context - * @param node The JSX element node - * @returns The type of the element + * Extracts the element type name from a JSX element or fragment + * For JSX elements, returns the stringified name (e.g., "div", "Button", "React.Fragment") + * For JSX fragments, returns an empty string + * + * @param context - ESLint rule context + * @param node - JSX element or fragment node + * @returns String representation of the element type */ export function getElementType(context: RuleContext, node: TSESTree.JSXElement | TSESTree.JSXFragment) { if (node.type === T.JSXFragment) { diff --git a/packages/core/src/jsx/jsx-has.ts b/packages/core/src/jsx/jsx-has.ts index 7c38f769e0..087572c06a 100644 --- a/packages/core/src/jsx/jsx-has.ts +++ b/packages/core/src/jsx/jsx-has.ts @@ -3,6 +3,15 @@ import type { Scope } from "@typescript-eslint/scope-manager"; import type { TSESTree } from "@typescript-eslint/types"; import { getAttribute } from "./jsx-attribute"; +/** + * Checks if a JSX element has a specific attribute + * + * @param context - ESLint rule context + * @param name - Name of the attribute to check for + * @param attributes - List of JSX attributes from opening element + * @param initialScope - Optional scope for resolving variables in spread attributes + * @returns boolean indicating whether the attribute exists + */ export function hasAttribute( context: RuleContext, name: string, @@ -12,6 +21,15 @@ export function hasAttribute( return getAttribute(context, name, attributes, initialScope) != null; } +/** + * Checks if a JSX element has at least one of the specified attributes + * + * @param context - ESLint rule context + * @param names - Array of attribute names to check for + * @param attributes - List of JSX attributes from opening element + * @param initialScope - Optional scope for resolving variables in spread attributes + * @returns boolean indicating whether any of the attributes exist + */ export function hasAnyAttribute( context: RuleContext, names: string[], @@ -21,6 +39,15 @@ export function hasAnyAttribute( return names.some((n) => hasAttribute(context, n, attributes, initialScope)); } +/** + * Checks if a JSX element has all of the specified attributes + * + * @param context - ESLint rule context + * @param names - Array of attribute names to check for + * @param attributes - List of JSX attributes from opening element + * @param initialScope - Optional scope for resolving variables in spread attributes + * @returns boolean indicating whether all of the attributes exist + */ export function hasEveryAttribute( context: RuleContext, names: string[], diff --git a/packages/core/src/jsx/jsx-hierarchy.ts b/packages/core/src/jsx/jsx-hierarchy.ts index a03a7a7716..14f06e4719 100644 --- a/packages/core/src/jsx/jsx-hierarchy.ts +++ b/packages/core/src/jsx/jsx-hierarchy.ts @@ -5,18 +5,22 @@ import type { TSESTree } from "@typescript-eslint/types"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; /** - * Find the parent JSX attribute node of a node - * @param node The node to find the parent attribute of - * @param test The test to apply to the parent attribute - * @returns The parent attribute node or undefined + * Traverses up the AST to find a parent JSX attribute node that matches a given test + * + * @param node - The starting AST node + * @param test - Optional predicate function to test if the attribute meets criteria + * Defaults to always returning true (matches any attribute) + * @returns The first matching JSX attribute node found when traversing upwards, or undefined */ export function findParentAttribute( node: TSESTree.Node, test: (node: TSESTree.JSXAttribute) => boolean = constTrue, ): TSESTree.JSXAttribute | unit { + // Type guard function to verify if a node is a JSXAttribute and passes the test const guard = (node: TSESTree.Node): node is TSESTree.JSXAttribute => { return node.type === T.JSXAttribute && test(node); }; + // Use AST utility to walk up the tree and find the first matching node return AST.findParentNode(node, guard); } diff --git a/packages/core/src/jsx/jsx-stringify.ts b/packages/core/src/jsx/jsx-stringify.ts index fbffd499d9..eb900ea33d 100644 --- a/packages/core/src/jsx/jsx-stringify.ts +++ b/packages/core/src/jsx/jsx-stringify.ts @@ -2,9 +2,11 @@ import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; import type { TSESTree } from "@typescript-eslint/utils"; /** - * Get the stringified representation of a JSX node - * @param node The JSX node - * @returns The stringified representation + * Converts a JSX AST node to its string representation + * Handles different JSX node types and returns their textual form + * + * @param node - JSX node from TypeScript ESTree + * @returns String representation of the JSX node */ export function stringifyJsx( node: @@ -19,20 +21,28 @@ export function stringifyJsx( ): string { switch (node.type) { case T.JSXIdentifier: + // Simple element names like "div" or component names like "Button" return node.name; case T.JSXNamespacedName: + // XML-style namespaced elements like "svg:path" return `${node.namespace.name}:${node.name.name}`; case T.JSXMemberExpression: + // Dot-notation components like "React.Fragment" or "Namespace.Component" return `${stringifyJsx(node.object)}.${stringifyJsx(node.property)}`; case T.JSXText: + // Text content inside JSX return node.value; case T.JSXOpeningElement: + // Opening tags like "
" return `<${stringifyJsx(node.name)}>`; case T.JSXClosingElement: + // Closing tags like "
" return ``; case T.JSXOpeningFragment: + // Fragment opening syntax "<>" return "<>"; case T.JSXClosingFragment: + // Fragment closing syntax "" return ""; } } diff --git a/packages/core/src/utils/get-instance-id.ts b/packages/core/src/utils/get-instance-id.ts index d19c58d9fa..123cc3ac16 100644 --- a/packages/core/src/utils/get-instance-id.ts +++ b/packages/core/src/utils/get-instance-id.ts @@ -1,24 +1,39 @@ -/* eslint-disable jsdoc/require-param */ +/** eslint-disable jsdoc/require-param */ import { unit } from "@eslint-react/eff"; import type { TSESTree } from "@typescript-eslint/types"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; -/** @internal */ +/** + * Gets the identifier node of an instance based on AST node relationships. + * Used for tracking where hooks or components are being assigned in the code. + * @param node The current AST node to evaluate + * @param prev The previous AST node in the traversal (used for context) + * @internal + */ export function getInstanceId(node: TSESTree.Node, prev?: TSESTree.Node) { switch (true) { + // Case: variable declaration (const x = new ResizeObserver()) case node.type === T.VariableDeclarator && node.init === prev: return node.id; + + // Case: assignment expression (x = new ResizeObserver()) case node.type === T.AssignmentExpression && node.right === prev: return node.left; + + // Case: class property definition (class X { y = new ResizeObserver() }) case node.type === T.PropertyDefinition && node.value === prev: return node.key; + + // Case: reached block scope boundary or program root case node.type === T.BlockStatement || node.type === T.Program || node.parent === node: return unit; + + // Continue traversing up the AST until we find an identifier default: return getInstanceId(node.parent, node); } diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.ts index bbf7cf9e3b..d1363986c5 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.ts @@ -171,7 +171,7 @@ function isInsideJSXAttributeValue(node: AST.TSESTreeFunction) { * @returns `true` if node is inside class component's render block, `false` if not */ function isInsideRenderMethod(node: TSESTree.Node) { - return AST.findParentNode(node, (n) => ER.isRenderLike(n) && ER.isClassComponent(n.parent.parent)) != null; + return AST.findParentNode(node, (n) => ER.isRenderMethodLike(n) && ER.isClassComponent(n.parent.parent)) != null; } /**