Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New]forbid-component-props: add propNamePattern option #3774

Merged
merged 1 commit into from
Jul 15, 2024
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* support eslint v9 ([#3759][] @mdjermanovic)
* export flat configs from plugin root and fix flat config crash ([#3694][] @bradzacher @mdjermanovic)
* add [`jsx-props-no-spread-multi`] ([#3724][] @SimonSchick)
* [`forbid-component-props`]: add `propNamePattern` to allow / disallow prop name patterns ([#3774][] @akulsr0)

[#3774]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3774
[#3759]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3759
[#3724]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3724
[#3694]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3694
Expand Down
22 changes: 21 additions & 1 deletion docs/rules/forbid-component-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Examples of **correct** code for this rule:
### `forbid`

An array specifying the names of props that are forbidden. The default value of this option is `['className', 'style']`.
Each array element can either be a string with the property name or object specifying the property name, an optional
Each array element can either be a string with the property name or object specifying the property name or glob string, an optional
custom message, and a component allowlist:

```js
Expand All @@ -55,6 +55,16 @@ custom message, and a component allowlist:
}
```

For glob string patterns:

```js
{
"propNamePattern": '**-**',
"allowedFor": ['div'],
"message": "Avoid using kebab-case except div"
}
```

Use `disallowedFor` as an exclusion list to warn on props for specific components. `disallowedFor` must have at least one item.

```js
Expand All @@ -65,6 +75,16 @@ Use `disallowedFor` as an exclusion list to warn on props for specific component
}
```

For glob string patterns:

```js
{
"propNamePattern": "**-**",
"disallowedFor": ["MyComponent"],
"message": "Avoid using kebab-case for MyComponent"
}
```

### Related rules

- [forbid-dom-props](./forbid-dom-props.md)
51 changes: 48 additions & 3 deletions lib/rules/forbid-component-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

'use strict';

const minimatch = require('minimatch');
const docsUrl = require('../util/docsUrl');
const report = require('../util/report');

Expand Down Expand Up @@ -70,6 +71,35 @@ module.exports = {
required: ['disallowedFor'],
additionalProperties: false,
},

{
type: 'object',
properties: {
propNamePattern: { type: 'string' },
allowedFor: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
message: { type: 'string' },
},
additionalProperties: false,
},
{
type: 'object',
properties: {
propNamePattern: { type: 'string' },
disallowedFor: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: { type: 'string' },
},
message: { type: 'string' },
},
required: ['disallowedFor'],
additionalProperties: false,
},
],
},
},
Expand All @@ -81,16 +111,31 @@ module.exports = {
const configuration = context.options[0] || {};
const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => {
const propName = typeof value === 'string' ? value : value.propName;
const propPattern = value.propNamePattern;
const prop = propName || propPattern;
const options = {
allowList: typeof value === 'string' ? [] : (value.allowedFor || []),
disallowList: typeof value === 'string' ? [] : (value.disallowedFor || []),
message: typeof value === 'string' ? null : value.message,
isPattern: !!value.propNamePattern,
};
return [propName, options];
return [prop, options];
}));

function getPropOptions(prop) {
// Get config options having pattern
const propNamePatternArray = Array.from(forbid.entries()).filter((propEntry) => propEntry[1].isPattern);
// Match current prop with pattern options, return if matched
const propNamePattern = propNamePatternArray.find((propPatternVal) => minimatch(prop, propPatternVal[0]));
// Get options for matched propNamePattern
const propNamePatternOptions = propNamePattern && propNamePattern[1];

const options = forbid.get(prop) || propNamePatternOptions;
return options;
}

function isForbidden(prop, tagName) {
const options = forbid.get(prop);
const options = getPropOptions(prop);
if (!options) {
return false;
}
Expand Down Expand Up @@ -121,7 +166,7 @@ module.exports = {
return;
}

const customMessage = forbid.get(prop).message;
const customMessage = getPropOptions(prop).message;

report(context, customMessage || messages.propIsForbidden, !customMessage && 'propIsForbidden', {
node,
Expand Down
126 changes: 126 additions & 0 deletions tests/lib/rules/forbid-component-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,23 @@ ruleTester.run('forbid-component-props', rule, {
},
],
},
{
code: `
const MyComponent = () => (
<div aria-label="welcome" />
);
`,
options: [
{
forbid: [
{
propNamePattern: '**-**',
allowedFor: ['div'],
},
],
},
],
},
]),

invalid: parsers.all([
Expand Down Expand Up @@ -553,5 +570,114 @@ ruleTester.run('forbid-component-props', rule, {
},
],
},
{
code: `
const MyComponent = () => (
<Foo kebab-case-prop={123} />
);
`,
options: [
{
forbid: [
{
propNamePattern: '**-**',
},
],
},
],
errors: [
{
messageId: 'propIsForbidden',
data: { prop: 'kebab-case-prop' },
line: 3,
column: 16,
type: 'JSXAttribute',
},
],
},
{
code: `
const MyComponent = () => (
<Foo kebab-case-prop={123} />
);
`,
options: [
{
forbid: [
{
propNamePattern: '**-**',
message: 'Avoid using kebab-case',
},
],
},
],
errors: [
{
message: 'Avoid using kebab-case',
line: 3,
column: 16,
type: 'JSXAttribute',
},
],
},
{
code: `
const MyComponent = () => (
<div>
<div aria-label="Hello Akul" />
<Foo kebab-case-prop={123} />
</div>
);
`,
options: [
{
forbid: [
{
propNamePattern: '**-**',
allowedFor: ['div'],
},
],
},
],
errors: [
{
messageId: 'propIsForbidden',
data: { prop: 'kebab-case-prop' },
line: 5,
column: 18,
type: 'JSXAttribute',
},
],
},
{
code: `
const MyComponent = () => (
<div>
<div aria-label="Hello Akul" />
<h1 data-id="my-heading" />
<Foo kebab-case-prop={123} />
</div>
);
`,
options: [
{
forbid: [
{
propNamePattern: '**-**',
disallowedFor: ['Foo'],
},
],
},
],
errors: [
{
messageId: 'propIsForbidden',
data: { prop: 'kebab-case-prop' },
line: 6,
column: 18,
type: 'JSXAttribute',
},
],
},
]),
});
Loading