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
205 changes: 205 additions & 0 deletions packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
---
title: no-forbidden-props
---

**Full Name in `eslint-plugin-react-x`**

```sh copy
react-x/no-forbidden-props
```

**Full Name in `@eslint-react/eslint-plugin`**

```sh copy
@eslint-react/no-forbidden-props
```

**Presets**

None

## Description

Disallow certain props on components. This rule helps enforce consistent prop naming conventions and prevents the use of specific props that may be problematic or against your team's coding standards.

By default, this rule forbids snake_case props (props containing underscores) to encourage camelCase naming conventions.

## Options

The rule accepts an object with the following properties:

- `forbid` (array): An array of forbidden prop configurations. Each item can be:
- A string: The exact prop name to forbid
- An object with `prop` and optional `excludedNodes` or `includedNodes`:
- `prop` (string): The prop name or regex pattern to forbid
- `excludedNodes` (array): Component names where this prop is allowed
- `includedNodes` (array): Component names where this prop is forbidden (others are allowed)

### Default Configuration

```json
{
"forbid": [{ "prop": "/_/" }]
}
```

This default configuration forbids any prop containing an underscore (snake_case).

## Examples

### Default Behavior (Forbids snake_case props)

#### Failing

```tsx
<div snake_case="value" />
<Component user_name="test" />
<ns:Element data_value="test" />
```

#### Passing

```tsx
<div camelCase="value" />
<Component userName="test" />
<ns:Element dataValue="test" />
```

### Custom Forbidden Props

Configuration:

```json
{
"forbid": ["className", "style"]
}
```

#### Failing

```tsx
<div className="test" />
<Component style={{}} />
```

#### Passing

```tsx
<div id="test" />
<Component id="test" />
```

### Regex Patterns

Configuration:

```json
{
"forbid": [
{ "prop": "/^data-/" },
{ "prop": "/^aria-/" }
]
}
```

#### Failing

```tsx
<div data-testid="test" />
<div aria-label="test" />
<div data-cy="test" />
```

#### Passing

```tsx
<div id="test" />
<div className="test" />
```

### Node-Specific Exclusions

Configuration:

```json
{
"forbid": [
{
"prop": "/_/",
"excludedNodes": ["Button"]
}
]
}
```

#### Failing

```tsx
<div snake_case="value" />
<Span snake_case="value" />
```

#### Passing

```tsx
<Button snake_case="value" />;
```

### Node-Specific Inclusions

Configuration:

```json
{
"forbid": [
{
"prop": "/_/",
"includedNodes": ["Button", "Input"]
}
]
}
```

#### Failing

```tsx
<Button snake_case="value" />
<Input snake_case="value" />
```

#### Passing

```tsx
<div snake_case="value" />
<span snake_case="value" />
```

### Mixed Configuration

```json
{
"forbid": [
"className",
{ "prop": "/^data-/", "excludedNodes": ["Button"] },
{ "prop": "/^aria-/", "includedNodes": ["Input"] }
]
}
```

This configuration:

- Forbids `className` on all components
- Forbids `data-*` props on all components except `Button`
- Forbids `aria-*` props only on `Input` components

## Implementation

- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.ts)
- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.spec.ts)

## Notes

- Spread attributes (`{...props}`) are ignored by this rule
- JSXMemberExpression components (like `React.Component`) are supported for prop checking, but node-specific filtering may not work as expected
- Regex patterns should be wrapped in forward slashes (e.g., `/pattern/`)
- The rule processes each forbidden prop configuration independently
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import tsx from "dedent";

import { allValid, ruleTester } from "../../../../../test";
import rule, { RULE_NAME } from "./no-forbidden-props";

ruleTester.run(RULE_NAME, rule, {
invalid: [
// Default behavior - snake_case props are forbidden
{
code: tsx`
<div snake_case="value" />
`,
errors: [{ messageId: "noForbiddenProps", data: { name: "snake_case" } }],
},
{
code: tsx`
<Component user_name="test" />
`,
errors: [{ messageId: "noForbiddenProps", data: { name: "user_name" } }],
},
// String-based forbidden props
{
code: tsx`
<div className="test" />
`,
errors: [{ messageId: "noForbiddenProps", data: { name: "className" } }],
options: [{ forbid: ["className"] }],
},
// Regex-based forbidden props
{
code: tsx`
<div data-testid="test" />
`,
errors: [{ messageId: "noForbiddenProps", data: { name: "data-testid" } }],
options: [{ forbid: [{ prop: "/^data-/" }] }],
},
// Node-specific exclusions - should still fail for non-excluded nodes
{
code: tsx`
<div snake_case="value" />
`,
errors: [{ messageId: "noForbiddenProps", data: { name: "snake_case" } }],
options: [{
forbid: [{
excludedNodes: ["Button"],
prop: "/_/",
}],
}],
},
// Node-specific inclusions
{
code: tsx`
<Button snake_case="value" />
`,
errors: [{ messageId: "noForbiddenProps", data: { name: "snake_case" } }],
options: [{
forbid: [{
includedNodes: ["Button"],
prop: "/_/",
}],
}],
},
// Multiple forbidden props
{
code: tsx`
<div className="test" style={{}} />
`,
errors: [
{ messageId: "noForbiddenProps", data: { name: "className" } },
{ messageId: "noForbiddenProps", data: { name: "style" } },
],
options: [{ forbid: ["className", "style"] }],
},
// Mixed string and object configurations
{
code: tsx`
<div className="test" data-testid="test" />
`,
errors: [
{ messageId: "noForbiddenProps", data: { name: "className" } },
{ messageId: "noForbiddenProps", data: { name: "data-testid" } },
],
options: [{
forbid: [
"className",
{ excludedNodes: ["Button"], prop: "/^data-/" },
],
}],
},
// Namespaced JSX elements
{
code: tsx`
<ns:Element snake_case="value" />
`,
errors: [{ messageId: "noForbiddenProps", data: { name: "snake_case" } }],
},
// JSXMemberExpression - should still be checked for forbidden props
{
code: tsx`
<React.Component snake_case="value" />
`,
errors: [{ messageId: "noForbiddenProps", data: { name: "snake_case" } }],
},
],
valid: [
...allValid,
// Default behavior - camelCase props are allowed
tsx`
<div camelCase="value" />
`,
tsx`
<Component userName="test" />
`,
// String-based forbidden props - other props allowed
{
code: tsx`
<div id="test" />
`,
options: [{ forbid: ["className"] }],
},
{
code: tsx`
<Component id="test" />
`,
options: [{ forbid: ["style"] }],
},
// Regex-based forbidden props - non-matching props allowed
{
code: tsx`
<div id="test" />
`,
options: [{ forbid: [{ prop: "/^data-/" }] }],
},
{
code: tsx`
<div className="test" />
`,
options: [{ forbid: [{ prop: "/^aria-/" }] }],
},
// Node-specific exclusions - excluded nodes should be allowed
{
code: tsx`
<Button snake_case="value" />
`,
options: [{
forbid: [{
excludedNodes: ["Button"],
prop: "/_/",
}],
}],
},
// Node-specific inclusions - other nodes allowed
{
code: tsx`
<div snake_case="value" />
`,
options: [{
forbid: [{
includedNodes: ["Button"],
prop: "/_/",
}],
}],
},

// Complex nested structures
{
code: tsx`
<div>
<span className="test" />
</div>
`,
options: [{ forbid: ["style"] }],
},
],
});
Loading
Loading