Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions apps/website/content/docs/rules/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -262,26 +262,26 @@ full: true
DOM rules target `React DOM`-specific concerns including security vulnerabilities, deprecated APIs, and DOM property usage.
</Callout>

| Rule | ✅ | 🌟 | Description | `react-dom` |
| :--------------------------------------------------------------------------------------------- | :-----: | :-------: | :------------------------------------------------------------------------------------------ | :---------: |
| [`no-dangerously-set-innerhtml`](dom-no-dangerously-set-innerhtml) | 1️⃣ 1️⃣ | | Disallows DOM elements from using `dangerouslySetInnerHTML` | |
| [`no-dangerously-set-innerhtml-with-children`](dom-no-dangerously-set-innerhtml-with-children) | 2️⃣ 2️⃣ | | Disallows DOM elements from using `dangerouslySetInnerHTML` and `children` at the same time | |
| [`no-find-dom-node`](dom-no-find-dom-node) | 2️⃣ 2️⃣ | | Disallows `findDOMNode` | |
| [`no-flush-sync`](dom-no-flush-sync) | 2️⃣ 2️⃣ | | Disallows `flushSync` | |
| [`no-hydrate`](dom-no-hydrate) | 2️⃣ 2️⃣ | `🔄` | Replaces usage of `ReactDOM.hydrate()` with `hydrateRoot()` | >=18.0.0 |
| [`no-missing-button-type`](dom-no-missing-button-type) | 0️⃣ 1️⃣ | `🔧` | Enforces an explicit `type` attribute for `button` elements | |
| [`no-missing-iframe-sandbox`](dom-no-missing-iframe-sandbox) | 0️⃣ 1️⃣ | `🔧` | Enforces an explicit `sandbox` attribute for `iframe` elements | |
| [`no-namespace`](dom-no-namespace) | 2️⃣ 2️⃣ | | Enforces the absence of a `namespace` in React elements | |
| [`no-render`](dom-no-render) | 2️⃣ 2️⃣ | `🔄` | Replaces usage of `ReactDOM.render()` with `createRoot(node).render()` | >=18.0.0 |
| [`no-render-return-value`](dom-no-render-return-value) | 2️⃣ 2️⃣ | | Disallows the return value of `ReactDOM.render` | |
| [`no-script-url`](dom-no-script-url) | 1️⃣ 1️⃣ | | Disallows `javascript:` URLs as attribute values | |
| [`no-string-style-prop`](dom-no-string-style-prop) | 0️⃣ 0️⃣ | | Disallows the use of string style prop in JSX. Use an object instead | |
| [`no-unknown-property`](dom-no-unknown-property) | 0️⃣ 0️⃣ | `🔧` `⚙️` | Disallows unknown `DOM` properties | |
| [`no-unsafe-iframe-sandbox`](dom-no-unsafe-iframe-sandbox) | 1️⃣ 1️⃣ | | Enforces `sandbox` attribute for `iframe` elements is not set to unsafe combinations | |
| [`no-unsafe-target-blank`](dom-no-unsafe-target-blank) | 0️⃣ 1️⃣ | `🔧` | Disallows `target="_blank"` without `rel="noreferrer noopener"` | |
| [`no-use-form-state`](dom-no-use-form-state) | 2️⃣ 2️⃣ | `🔄` | Replaces usage of `useFormState` with `useActionState` | >=19.0.0 |
| [`no-void-elements-with-children`](dom-no-void-elements-with-children) | 2️⃣ 2️⃣ | | Disallows `children` in void DOM elements | |
| [`prefer-namespace-import`](dom-prefer-namespace-import) | 0️⃣ 0️⃣ | `🔧` | Enforces importing React DOM via a namespace import | |
| Rule | ✅ | 🌟 | Description | `react-dom` |
| :--------------------------------------------------------------------------------------------- | :-----: | :-------: | :-------------------------------------------------------------------------------------------- | :---------: |
| [`no-dangerously-set-innerhtml`](dom-no-dangerously-set-innerhtml) | 1️⃣ 1️⃣ | | Disallows DOM elements from using `dangerouslySetInnerHTML` | |
| [`no-dangerously-set-innerhtml-with-children`](dom-no-dangerously-set-innerhtml-with-children) | 2️⃣ 2️⃣ | | Disallows DOM elements from using `dangerouslySetInnerHTML` and `children` at the same time | |
| [`no-find-dom-node`](dom-no-find-dom-node) | 2️⃣ 2️⃣ | | Disallows `findDOMNode` | |
| [`no-flush-sync`](dom-no-flush-sync) | 2️⃣ 2️⃣ | | Disallows `flushSync` | |
| [`no-hydrate`](dom-no-hydrate) | 2️⃣ 2️⃣ | `🔄` | Replaces usage of `ReactDOM.hydrate()` with `hydrateRoot()` | >=18.0.0 |
| [`no-missing-button-type`](dom-no-missing-button-type) | 0️⃣ 1️⃣ | `🔧` | Enforces an explicit `type` attribute for `button` elements | |
| [`no-missing-iframe-sandbox`](dom-no-missing-iframe-sandbox) | 0️⃣ 1️⃣ | `🔧` | Enforces an explicit `sandbox` attribute for `iframe` elements | |
| [`no-namespace`](dom-no-namespace) | 2️⃣ 2️⃣ | | Enforces the absence of a `namespace` in React elements | |
| [`no-render`](dom-no-render) | 2️⃣ 2️⃣ | `🔄` | Replaces usage of `ReactDOM.render()` with `createRoot(node).render()` | >=18.0.0 |
| [`no-render-return-value`](dom-no-render-return-value) | 2️⃣ 2️⃣ | | Disallows the return value of `ReactDOM.render` | |
| [`no-script-url`](dom-no-script-url) | 1️⃣ 1️⃣ | | Disallows `javascript:` URLs as attribute values | |
| [`no-string-style-prop`](dom-no-string-style-prop) | 0️⃣ 0️⃣ | | Disallows the use of string style prop in JSX. Use an object instead | |
| [`no-unknown-property`](dom-no-unknown-property) | 0️⃣ 0️⃣ | `🔧` `⚙️` | Disallows unknown `DOM` properties | |
| [`no-unsafe-iframe-sandbox`](dom-no-unsafe-iframe-sandbox) | 1️⃣ 1️⃣ | | Enforces that the `sandbox` attribute for `iframe` elements is not set to unsafe combinations | |
| [`no-unsafe-target-blank`](dom-no-unsafe-target-blank) | 0️⃣ 1️⃣ | `🔧` | Disallows `target="_blank"` without `rel="noreferrer noopener"` | |
| [`no-use-form-state`](dom-no-use-form-state) | 2️⃣ 2️⃣ | `🔄` | Replaces usage of `useFormState` with `useActionState` | >=19.0.0 |
| [`no-void-elements-with-children`](dom-no-void-elements-with-children) | 2️⃣ 2️⃣ | | Disallows `children` in void DOM elements | |
| [`prefer-namespace-import`](dom-prefer-namespace-import) | 0️⃣ 0️⃣ | `🔧` | Enforces importing React DOM via a namespace import | |

## Web API Rules

Expand Down
2 changes: 1 addition & 1 deletion packages/core/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@
| [getJsxConfigFromAnnotation](functions/getJsxConfigFromAnnotation.md) | Get JsxConfig from pragma comments (annotations) in the source code |
| [getJsxConfigFromContext](functions/getJsxConfigFromContext.md) | Get JsxConfig from the rule context by reading compiler options |
| [getJsxElementType](functions/getJsxElementType.md) | 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 |
| [hasNoneOrLooseComponentName](functions/hasNoneOrLooseComponentName.md) | Check if the function has no name or a loose component name |
| [isClassComponent](functions/isClassComponent.md) | Check if a node is a React class component |
| [isComponentDefinition](functions/isComponentDefinition.md) | Determine if a function node represents a valid React component definition |
| [isComponentDidMountCallback](functions/isComponentDidMountCallback.md) | Check if the given node is a componentDidMount callback |
Expand All @@ -136,6 +135,7 @@
| [isComponentWrapperCallbackLoose](functions/isComponentWrapperCallbackLoose.md) | Check if the node is a callback function passed to a component wrapper loosely |
| [isComponentWrapperCallLoose](functions/isComponentWrapperCallLoose.md) | Check if the node is a call expression for a component wrapper loosely |
| [isDeclaredInRenderPropLoose](functions/isDeclaredInRenderPropLoose.md) | Unsafe check whether given node is declared inside a render prop `_ = <Component renderRow={"node"} /> ` ^^^^^^ ` _ = <Component rows={ [{ render: "node" }] } /> ` ^^^^^^ `` |
| [isFunctionWithLooseComponentName](functions/isFunctionWithLooseComponentName.md) | Check if a function has a loose component name |
| [isHook](functions/isHook.md) | Determine if a function node is a React Hook based on its name. |
| [isHookCall](functions/isHookCall.md) | Check if the given node is a React Hook call by its name. |
| [isHookCallWithName](functions/isHookCallWithName.md) | Check if a node is a call to a specific React hook. Returns a function that accepts a hook name to check against. |
Expand Down
20 changes: 0 additions & 20 deletions packages/core/docs/functions/hasNoneOrLooseComponentName.md

This file was deleted.

26 changes: 26 additions & 0 deletions packages/core/docs/functions/isFunctionWithLooseComponentName.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[@eslint-react/core](../README.md) / isFunctionWithLooseComponentName

# Function: isFunctionWithLooseComponentName()

```ts
function isFunctionWithLooseComponentName(
context: RuleContext,
fn: TSESTreeFunction,
allowNone: boolean): boolean;
```

Check if a function has a loose component name

## Parameters

| Parameter | Type | Default value | Description |
| ------ | ------ | ------ | ------ |
| `context` | `RuleContext` | `undefined` | The rule context |
| `fn` | `TSESTreeFunction` | `undefined` | The function to check |
| `allowNone` | `boolean` | `false` | Whether to allow no name |

## Returns

`boolean`

Whether the function has a loose component name
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Represents a React Function Component

| Property | Type | Description | Overrides | Inherited from |
| ------ | ------ | ------ | ------ | ------ |
| <a id="directives"></a> `directives` | `StringLiteral`[] | The directives used in the function (e.g., "use strict", "use client", etc.) | - | - |
| <a id="displayname"></a> `displayName` | `Expression` \| `undefined` | The display name of the component | - | - |
| <a id="flag"></a> `flag` | `bigint` | Flags describing the component's characteristics | [`SemanticNode`](SemanticNode.md).[`flag`](SemanticNode.md#flag) | - |
| <a id="hint"></a> `hint` | `bigint` | Hint for how the component was detected | [`SemanticNode`](SemanticNode.md).[`hint`](SemanticNode.md#hint) | - |
Expand Down
21 changes: 11 additions & 10 deletions packages/core/docs/interfaces/HookSemanticNode.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@

## Properties

| Property | Type | Overrides | Inherited from |
| ------ | ------ | ------ | ------ |
| <a id="flag"></a> `flag` | `bigint` | - | [`SemanticNode`](SemanticNode.md).[`flag`](SemanticNode.md#flag) |
| <a id="hint"></a> `hint` | `bigint` | - | [`SemanticNode`](SemanticNode.md).[`hint`](SemanticNode.md#hint) |
| <a id="hookcalls"></a> `hookCalls` | `CallExpression`[] | - | - |
| <a id="id"></a> `id` | \| `ArrayExpression` \| `ArrayPattern` \| `ArrowFunctionExpression` \| `AssignmentExpression` \| `AwaitExpression` \| `PrivateInExpression` \| `SymmetricBinaryExpression` \| `CallExpression` \| `ChainExpression` \| `ClassExpression` \| `ConditionalExpression` \| `FunctionExpression` \| `Identifier` \| `ImportExpression` \| `JSXElement` \| `JSXFragment` \| `BigIntLiteral` \| `BooleanLiteral` \| `NullLiteral` \| `NumberLiteral` \| `RegExpLiteral` \| `StringLiteral` \| `LogicalExpression` \| `MemberExpressionComputedName` \| `MemberExpressionNonComputedName` \| `MetaProperty` \| `NewExpression` \| `ObjectExpression` \| `ObjectPattern` \| `PrivateIdentifier` \| `SequenceExpression` \| `Super` \| `TaggedTemplateExpression` \| `TemplateLiteral` \| `ThisExpression` \| `TSAsExpression` \| `TSInstantiationExpression` \| `TSNonNullExpression` \| `TSSatisfiesExpression` \| `TSTypeAssertion` \| `UnaryExpressionBitwiseNot` \| `UnaryExpressionDelete` \| `UnaryExpressionMinus` \| `UnaryExpressionNot` \| `UnaryExpressionPlus` \| `UnaryExpressionTypeof` \| `UnaryExpressionVoid` \| `UpdateExpression` \| `YieldExpression` \| `undefined` | [`SemanticNode`](SemanticNode.md).[`id`](SemanticNode.md#id) | - |
| <a id="key"></a> `key` | `string` | - | [`SemanticNode`](SemanticNode.md).[`key`](SemanticNode.md#key) |
| <a id="kind"></a> `kind` | `string` | - | [`SemanticNode`](SemanticNode.md).[`kind`](SemanticNode.md#kind) |
| <a id="name"></a> `name` | `string` | [`SemanticNode`](SemanticNode.md).[`name`](SemanticNode.md#name) | - |
| <a id="node"></a> `node` | `TSESTreeFunction` | [`SemanticNode`](SemanticNode.md).[`node`](SemanticNode.md#node) | - |
| Property | Type | Description | Overrides | Inherited from |
| ------ | ------ | ------ | ------ | ------ |
| <a id="directives"></a> `directives` | `StringLiteral`[] | The directives used in the function (e.g., "use strict", "use client", etc.) | - | - |
| <a id="flag"></a> `flag` | `bigint` | - | - | [`SemanticNode`](SemanticNode.md).[`flag`](SemanticNode.md#flag) |
| <a id="hint"></a> `hint` | `bigint` | - | - | [`SemanticNode`](SemanticNode.md).[`hint`](SemanticNode.md#hint) |
| <a id="hookcalls"></a> `hookCalls` | `CallExpression`[] | - | - | - |
| <a id="id"></a> `id` | \| `ArrayExpression` \| `ArrayPattern` \| `ArrowFunctionExpression` \| `AssignmentExpression` \| `AwaitExpression` \| `PrivateInExpression` \| `SymmetricBinaryExpression` \| `CallExpression` \| `ChainExpression` \| `ClassExpression` \| `ConditionalExpression` \| `FunctionExpression` \| `Identifier` \| `ImportExpression` \| `JSXElement` \| `JSXFragment` \| `BigIntLiteral` \| `BooleanLiteral` \| `NullLiteral` \| `NumberLiteral` \| `RegExpLiteral` \| `StringLiteral` \| `LogicalExpression` \| `MemberExpressionComputedName` \| `MemberExpressionNonComputedName` \| `MetaProperty` \| `NewExpression` \| `ObjectExpression` \| `ObjectPattern` \| `PrivateIdentifier` \| `SequenceExpression` \| `Super` \| `TaggedTemplateExpression` \| `TemplateLiteral` \| `ThisExpression` \| `TSAsExpression` \| `TSInstantiationExpression` \| `TSNonNullExpression` \| `TSSatisfiesExpression` \| `TSTypeAssertion` \| `UnaryExpressionBitwiseNot` \| `UnaryExpressionDelete` \| `UnaryExpressionMinus` \| `UnaryExpressionNot` \| `UnaryExpressionPlus` \| `UnaryExpressionTypeof` \| `UnaryExpressionVoid` \| `UpdateExpression` \| `YieldExpression` \| `undefined` | - | [`SemanticNode`](SemanticNode.md).[`id`](SemanticNode.md#id) | - |
| <a id="key"></a> `key` | `string` | - | - | [`SemanticNode`](SemanticNode.md).[`key`](SemanticNode.md#key) |
| <a id="kind"></a> `kind` | `string` | - | - | [`SemanticNode`](SemanticNode.md).[`kind`](SemanticNode.md#kind) |
| <a id="name"></a> `name` | `string` | - | [`SemanticNode`](SemanticNode.md).[`name`](SemanticNode.md#name) | - |
| <a id="node"></a> `node` | `TSESTreeFunction` | - | [`SemanticNode`](SemanticNode.md).[`node`](SemanticNode.md#node) | - |
12 changes: 10 additions & 2 deletions packages/core/src/component/component-collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ 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";
import { isFunctionWithLooseComponentName } from "./component-name";

const idGen = new IdGenerator("function_component_");

Expand Down Expand Up @@ -64,12 +65,14 @@ export function useComponentCollector(
const id = getFunctionComponentId(context, node);
const name = id == null ? unit : AST.toStringFormat(id, getText);
const initPath = AST.getFunctionInitPath(node);
functionEntries.push({
const directives = AST.getFunctionDirectives(node);
const entry = {
id: getFunctionComponentId(context, node),
key,
kind: "function",
name,
node,
directives,
displayName: unit,
flag: getComponentFlagFromInitPath(initPath),
hint,
Expand All @@ -79,7 +82,12 @@ export function useComponentCollector(
isExportDefault,
isExportDefaultDeclaration,
rets: [],
});
} as const satisfies FunctionEntry;
functionEntries.push(entry);
if (!entry.isComponentDefinition || !isFunctionWithLooseComponentName(context, node, false)) return;
if (directives.some((d) => d.value === "use memo" || d.value === "use no memo")) {
components.set(entry.key, entry);
}
};
const onFunctionExit = () => {
return functionEntries.pop();
Expand Down
Loading
Loading