Skip to content

Commit

Permalink
[New] forbid-component-props: add allowedForPatterns/`disallowedF…
Browse files Browse the repository at this point in the history
…orPatterns` options
  • Loading branch information
Efimenko authored and ljharb committed Aug 21, 2024
1 parent 95297ed commit 3073214
Show file tree
Hide file tree
Showing 4 changed files with 299 additions and 16 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
### Added
* add type generation ([#3830][] @voxpelli)
* [`no-unescaped-entities`]: add suggestions ([#3831][] @StyleShit)
* [`forbid-component-props`]: add `allowedForPatterns`/`disallowedForPatterns` options ([#3805][] @Efimenko)

[#3831]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3831
[#3830]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3830
[#3805]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3805

## [7.36.1] - 2024.09.12

Expand Down
45 changes: 37 additions & 8 deletions docs/rules/forbid-component-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,17 @@ custom message, and a component allowlist:
}
```

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

```js
{
"propName": "someProp",
"disallowedFor": ["SomeComponent", "AnotherComponent"],
"message": "Avoid using someProp for SomeComponent and AnotherComponent"
}
```

For `propNamePattern` glob string patterns:

```js
{
Expand All @@ -65,23 +75,42 @@ For glob string patterns:
}
```

Use `disallowedFor` as an exclusion list to warn on props for specific components. `disallowedFor` must have at least one item.
```js
{
"propNamePattern": '**-**',
"allowedForPatterns": ["*Component"],
"message": "Avoid using kebab-case except components that match the `*Component` pattern"
}
```

Use `allowedForPatterns` for glob string patterns:

```js
{
"propName": "someProp",
"disallowedFor": ["SomeComponent", "AnotherComponent"],
"message": "Avoid using someProp for SomeComponent and AnotherComponent"
"allowedForPatterns": ["*Component"],
"message": "Avoid using `someProp` except components that match the `*Component` pattern"
}
```

Use `disallowedForPatterns` for glob string patterns:

```js
{
"propName": "someProp",
"disallowedForPatterns": ["*Component"],
"message": "Avoid using `someProp` for components that match the `*Component` pattern"
}
```

For glob string patterns:
Combine several properties to cover more cases:

```js
{
"propNamePattern": "**-**",
"disallowedFor": ["MyComponent"],
"message": "Avoid using kebab-case for MyComponent"
"propName": "someProp",
"allowedFor": ['div'],
"allowedForPatterns": ["*Component"],
"message": "Avoid using `someProp` except `div` and components that match the `*Component` pattern"
}
```

Expand Down
75 changes: 67 additions & 8 deletions lib/rules/forbid-component-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ module.exports = {
uniqueItems: true,
items: { type: 'string' },
},
allowedForPatterns: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
message: { type: 'string' },
},
additionalProperties: false,
Expand All @@ -66,12 +71,20 @@ module.exports = {
minItems: 1,
items: { type: 'string' },
},
disallowedForPatterns: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: { type: 'string' },
},
message: { type: 'string' },
},
required: ['disallowedFor'],
anyOf: [
{ required: ['disallowedFor'] },
{ required: ['disallowedForPatterns'] },
],
additionalProperties: false,
},

{
type: 'object',
properties: {
Expand All @@ -81,6 +94,11 @@ module.exports = {
uniqueItems: true,
items: { type: 'string' },
},
allowedForPatterns: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
message: { type: 'string' },
},
additionalProperties: false,
Expand All @@ -95,9 +113,18 @@ module.exports = {
minItems: 1,
items: { type: 'string' },
},
disallowedForPatterns: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: { type: 'string' },
},
message: { type: 'string' },
},
required: ['disallowedFor'],
anyOf: [
{ required: ['disallowedFor'] },
{ required: ['disallowedForPatterns'] },
],
additionalProperties: false,
},
],
Expand All @@ -114,8 +141,10 @@ module.exports = {
const propPattern = value.propNamePattern;
const prop = propName || propPattern;
const options = {
allowList: typeof value === 'string' ? [] : (value.allowedFor || []),
disallowList: typeof value === 'string' ? [] : (value.disallowedFor || []),
allowList: [].concat(value.allowedFor || []),
allowPatternList: [].concat(value.allowedForPatterns || []),
disallowList: [].concat(value.disallowedFor || []),
disallowPatternList: [].concat(value.disallowedForPatterns || []),
message: typeof value === 'string' ? null : value.message,
isPattern: !!value.propNamePattern,
};
Expand All @@ -140,10 +169,40 @@ module.exports = {
return false;
}

function checkIsTagForbiddenByAllowOptions() {
if (options.allowList.indexOf(tagName) !== -1) {
return false;
}

if (options.allowPatternList.length === 0) {
return true;
}

return options.allowPatternList.every(
(pattern) => !minimatch(tagName, pattern)
);
}

function checkIsTagForbiddenByDisallowOptions() {
if (options.disallowList.indexOf(tagName) !== -1) {
return true;
}

if (options.disallowPatternList.length === 0) {
return false;
}

return options.disallowPatternList.some(
(pattern) => minimatch(tagName, pattern)
);
}

const hasDisallowOptions = options.disallowList.length > 0 || options.disallowPatternList.length > 0;

// disallowList should have a least one item (schema configuration)
const isTagForbidden = options.disallowList.length > 0
? options.disallowList.indexOf(tagName) !== -1
: options.allowList.indexOf(tagName) === -1;
const isTagForbidden = hasDisallowOptions
? checkIsTagForbiddenByDisallowOptions()
: checkIsTagForbiddenByAllowOptions();

// if the tagName is undefined (`<this.something>`), we assume it's a forbidden element
return typeof tagName === 'undefined' || isTagForbidden;
Expand Down
Loading

0 comments on commit 3073214

Please sign in to comment.