Skip to content

Commit

Permalink
[New] forbid-component-props: add propNamePattern to allow / disa…
Browse files Browse the repository at this point in the history
…llow prop name patterns
  • Loading branch information
akulsr0 authored and ljharb committed Jun 27, 2024
1 parent 9bf81af commit 6ce58e5
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 4 deletions.
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',
},
],
},
]),
});

0 comments on commit 6ce58e5

Please sign in to comment.