Skip to content

Commit a20cc8f

Browse files
committed
[New]forbid-component-props: add propNameRegex to allow / disallow prop name patterns
1 parent 3c1d520 commit a20cc8f

File tree

3 files changed

+194
-4
lines changed

3 files changed

+194
-4
lines changed

docs/rules/forbid-component-props.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Examples of **correct** code for this rule:
4444
### `forbid`
4545

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

5050
```js
@@ -55,6 +55,16 @@ custom message, and a component allowlist:
5555
}
5656
```
5757

58+
For regex:
59+
60+
```js
61+
{
62+
"propNameRegex": '.-.',
63+
"allowedFor": ['div'],
64+
"message": "Avoid using kebab-case except div"
65+
}
66+
```
67+
5868
Use `disallowedFor` as an exclusion list to warn on props for specific components. `disallowedFor` must have at least one item.
5969

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

78+
For regex:
79+
80+
```js
81+
{
82+
"propNameRegex": ".-.",
83+
"disallowedFor": ["MyComponent"],
84+
"message": "Avoid using kebab-case for MyComponent"
85+
}
86+
```
87+
6888
### Related rules
6989

7090
- [forbid-dom-props](./forbid-dom-props.md)

lib/rules/forbid-component-props.js

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,35 @@ module.exports = {
7070
required: ['disallowedFor'],
7171
additionalProperties: false,
7272
},
73+
74+
{
75+
type: 'object',
76+
properties: {
77+
propNameRegex: { type: 'string' },
78+
allowedFor: {
79+
type: 'array',
80+
uniqueItems: true,
81+
items: { type: 'string' },
82+
},
83+
message: { type: 'string' },
84+
},
85+
additionalProperties: false,
86+
},
87+
{
88+
type: 'object',
89+
properties: {
90+
propNameRegex: { type: 'string' },
91+
disallowedFor: {
92+
type: 'array',
93+
uniqueItems: true,
94+
minItems: 1,
95+
items: { type: 'string' },
96+
},
97+
message: { type: 'string' },
98+
},
99+
required: ['disallowedFor'],
100+
additionalProperties: false,
101+
},
73102
],
74103
},
75104
},
@@ -81,16 +110,31 @@ module.exports = {
81110
const configuration = context.options[0] || {};
82111
const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => {
83112
const propName = typeof value === 'string' ? value : value.propName;
113+
const propRegex = value.propNameRegex;
114+
const prop = propName || propRegex;
84115
const options = {
85116
allowList: typeof value === 'string' ? [] : (value.allowedFor || []),
86117
disallowList: typeof value === 'string' ? [] : (value.disallowedFor || []),
87118
message: typeof value === 'string' ? null : value.message,
119+
isRegex: !!value.propNameRegex,
88120
};
89-
return [propName, options];
121+
return [prop, options];
90122
}));
91123

124+
function getPropOptions(prop) {
125+
// Get config options having regex
126+
const propNameRegexArray = [...forbid.entries()].filter(([, options]) => options.isRegex);
127+
// Match current prop with regex options, return if matched
128+
const propNameRegex = propNameRegexArray.find(([propRegex]) => new RegExp(propRegex).test(prop));
129+
// Get options for matched propNameRegex
130+
const propNameRegexOptions = propNameRegex && propNameRegex[1];
131+
132+
const options = forbid.get(prop) || propNameRegexOptions;
133+
return options;
134+
}
135+
92136
function isForbidden(prop, tagName) {
93-
const options = forbid.get(prop);
137+
const options = getPropOptions(prop);
94138
if (!options) {
95139
return false;
96140
}
@@ -121,7 +165,7 @@ module.exports = {
121165
return;
122166
}
123167

124-
const customMessage = forbid.get(prop).message;
168+
const customMessage = getPropOptions(prop).message;
125169

126170
report(context, customMessage || messages.propIsForbidden, !customMessage && 'propIsForbidden', {
127171
node,

tests/lib/rules/forbid-component-props.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,23 @@ ruleTester.run('forbid-component-props', rule, {
248248
},
249249
],
250250
},
251+
{
252+
code: `
253+
const MyComponent = () => (
254+
<div aria-label="welcome" />
255+
);
256+
`,
257+
options: [
258+
{
259+
forbid: [
260+
{
261+
propNameRegex: '.-.',
262+
allowedFor: ['div'],
263+
},
264+
],
265+
},
266+
],
267+
},
251268
]),
252269

253270
invalid: parsers.all([
@@ -568,5 +585,114 @@ ruleTester.run('forbid-component-props', rule, {
568585
},
569586
],
570587
},
588+
{
589+
code: `
590+
const MyComponent = () => (
591+
<Foo kebab-case-prop={123} />
592+
);
593+
`,
594+
options: [
595+
{
596+
forbid: [
597+
{
598+
propNameRegex: '.-.',
599+
},
600+
],
601+
},
602+
],
603+
errors: [
604+
{
605+
messageId: 'propIsForbidden',
606+
data: { prop: 'kebab-case-prop' },
607+
line: 3,
608+
column: 16,
609+
type: 'JSXAttribute',
610+
},
611+
],
612+
},
613+
{
614+
code: `
615+
const MyComponent = () => (
616+
<Foo kebab-case-prop={123} />
617+
);
618+
`,
619+
options: [
620+
{
621+
forbid: [
622+
{
623+
propNameRegex: '.-.',
624+
message: 'Avoid using kebab-case',
625+
},
626+
],
627+
},
628+
],
629+
errors: [
630+
{
631+
message: 'Avoid using kebab-case',
632+
line: 3,
633+
column: 16,
634+
type: 'JSXAttribute',
635+
},
636+
],
637+
},
638+
{
639+
code: `
640+
const MyComponent = () => (
641+
<div>
642+
<div aria-label="Hello Akul" />
643+
<Foo kebab-case-prop={123} />
644+
</div>
645+
);
646+
`,
647+
options: [
648+
{
649+
forbid: [
650+
{
651+
propNameRegex: '.-.',
652+
allowedFor: ['div'],
653+
},
654+
],
655+
},
656+
],
657+
errors: [
658+
{
659+
messageId: 'propIsForbidden',
660+
data: { prop: 'kebab-case-prop' },
661+
line: 5,
662+
column: 18,
663+
type: 'JSXAttribute',
664+
},
665+
],
666+
},
667+
{
668+
code: `
669+
const MyComponent = () => (
670+
<div>
671+
<div aria-label="Hello Akul" />
672+
<h1 data-id="my-heading" />
673+
<Foo kebab-case-prop={123} />
674+
</div>
675+
);
676+
`,
677+
options: [
678+
{
679+
forbid: [
680+
{
681+
propNameRegex: '.-.',
682+
disallowedFor: ['Foo'],
683+
},
684+
],
685+
},
686+
],
687+
errors: [
688+
{
689+
messageId: 'propIsForbidden',
690+
data: { prop: 'kebab-case-prop' },
691+
line: 6,
692+
column: 18,
693+
type: 'JSXAttribute',
694+
},
695+
],
696+
},
571697
]),
572698
});

0 commit comments

Comments
 (0)