diff --git a/docs/rules/forbid-component-props.md b/docs/rules/forbid-component-props.md index 2d624be31c..4f828fd3b8 100644 --- a/docs/rules/forbid-component-props.md +++ b/docs/rules/forbid-component-props.md @@ -55,7 +55,16 @@ custom message, and a component allowlist: } ``` -Use `disallowedFor` as an exclusion list to warn on props for specific components. `disallowedFor` must have at least one item. +```js +{ + "propName": "someProp", + "allowedFor": ["SomeComponent", "AnotherComponent"], + "allowedForRegex": "^Icon\d+[A-Z]", + "message": "Avoid using someProp except SomeComponent, AnotherComponent, and Icons" +} +``` + +Use `disallowedFor` as an exclusion list to warn on props for specific components. `disallowedFor` must have at least one item, or use `disallowedForRegex`. The two may also be combined. ```js { @@ -65,6 +74,23 @@ Use `disallowedFor` as an exclusion list to warn on props for specific component } ``` +```js +{ + "propName": "someProp", + "disallowedForRegex": "^Icon\d+[A-Z]", + "message": "Avoid using someProp for Icons" +} +``` + +```js +{ + "propName": "someProp", + "disallowedFor": ["SomeComponent"], + "disallowedForRegex": "^Icon\d+[A-Z]", + "message": "Avoid using someProp for SomeComponent and Icons" +} +``` + ### Related rules - [forbid-dom-props](./forbid-dom-props.md) diff --git a/lib/rules/forbid-component-props.js b/lib/rules/forbid-component-props.js index cca553e123..cbca1b5afe 100644 --- a/lib/rules/forbid-component-props.js +++ b/lib/rules/forbid-component-props.js @@ -50,6 +50,7 @@ module.exports = { uniqueItems: true, items: { type: 'string' }, }, + allowedForRegex: { type: 'string' }, message: { type: 'string' }, }, additionalProperties: false, @@ -64,11 +65,27 @@ module.exports = { minItems: 1, items: { type: 'string' }, }, + disallowedForRegex: { type: 'string' }, message: { type: 'string' }, }, required: ['disallowedFor'], additionalProperties: false, }, + { + type: 'object', + properties: { + propName: { type: 'string' }, + disallowedFor: { + type: 'array', + uniqueItems: true, + items: { type: 'string' }, + }, + disallowedForRegex: { type: 'string' }, + message: { type: 'string' }, + }, + required: ['disallowedForRegex'], + additionalProperties: false, + }, ], }, }, @@ -80,9 +97,22 @@ module.exports = { const configuration = context.options[0] || {}; const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => { const propName = typeof value === 'string' ? value : value.propName; + + let allowRegex = null; + if (typeof value !== 'string' && value.allowedForRegex) { + allowRegex = new RegExp(value.allowedForRegex); + } + + let disallowRegex = null; + if (typeof value !== 'string' && value.disallowedForRegex) { + disallowRegex = new RegExp(value.disallowedForRegex); + } + const options = { allowList: typeof value === 'string' ? [] : (value.allowedFor || []), + allowRegex, disallowList: typeof value === 'string' ? [] : (value.disallowedFor || []), + disallowRegex, message: typeof value === 'string' ? null : value.message, }; return [propName, options]; @@ -94,13 +124,23 @@ module.exports = { return false; } - // 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; - // if the tagName is undefined (``), we assume it's a forbidden element - return typeof tagName === 'undefined' || isTagForbidden; + if (typeof tagName === 'undefined') { + return true; + } + + // either disallowList should have a least one item or disallowListRegex is given (schema configuration) + if (options.disallowList.length > 0 || options.disallowRegex) { + return ( + options.disallowList.indexOf(tagName) !== -1 + || (options.disallowRegex && options.disallowRegex.test(tagName)) + ); + } + + return ( + options.allowList.indexOf(tagName) === -1 + && (!options.allowRegex || !options.allowRegex.test(tagName)) + ); } return { diff --git a/tests/lib/rules/forbid-component-props.js b/tests/lib/rules/forbid-component-props.js index 5f1aa7e68d..d8ae502d6a 100644 --- a/tests/lib/rules/forbid-component-props.js +++ b/tests/lib/rules/forbid-component-props.js @@ -248,6 +248,43 @@ ruleTester.run('forbid-component-props', rule, { }, ], }, + { + code: ` + const item = (); + `, + options: [ + { + forbid: [ + { + propName: 'className', + allowedForRegex: '^Fo{2}$', + }, + ], + }, + ], + }, + { + code: ` + const item = ( + + + + + + ); + `, + options: [ + { + forbid: [ + { + propName: 'className', + allowedFor: ['Parent'], + allowedForRegex: '^Thing', + }, + ], + }, + ], + }, ]), invalid: parsers.all([ @@ -568,5 +605,69 @@ ruleTester.run('forbid-component-props', rule, { }, ], }, + { + code: ` + const item = () => ( + + + + ); + `, + options: [ + { + forbid: [ + { + propName: 'className', + disallowedFor: ['Foo'], + disallowedForRegex: '^B', + }, + ], + }, + ], + errors: [ + { + messageId: 'propIsForbidden', + data: { prop: 'className' }, + line: 3, + column: 16, + type: 'JSXAttribute', + }, + { + messageId: 'propIsForbidden', + data: { prop: 'className' }, + line: 4, + column: 18, + type: 'JSXAttribute', + }, + ], + }, + { + code: ` + const item = () => ( + + + + ); + `, + options: [ + { + forbid: [ + { + propName: 'className', + disallowedForRegex: '^B', + }, + ], + }, + ], + errors: [ + { + messageId: 'propIsForbidden', + data: { prop: 'className' }, + line: 4, + column: 18, + type: 'JSXAttribute', + }, + ], + }, ]), });