From b3a3937c9712d3b9ee1e5512f06f076d6fea5dc8 Mon Sep 17 00:00:00 2001 From: TildaDares Date: Fri, 15 Jul 2022 20:33:31 +0100 Subject: [PATCH] [New] `forbid-dom-props`: add `disallowedFor` option --- CHANGELOG.md | 2 + docs/rules/forbid-dom-props.md | 5 +- lib/rules/forbid-dom-props.js | 34 ++++++++--- tests/lib/rules/forbid-dom-props.js | 87 +++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e398952980..01df9f267e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Added * [`jsx-newline`]: add `allowMultiline` option when prevent option is true ([#3311][] @TildaDares) +* [`forbid-dom-props`]: add `disallowedFor` option ([#3338][] @TildaDares) ### Fixed * [`jsx-no-literals`]: properly error on children with noAttributeStrings: true ([#3317][] @TildaDares) @@ -32,6 +33,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange [#3347]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3347 [#3344]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3344 [#3339]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3339 +[#3338]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3338 [#3335]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3335 [#3331]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3331 [#3328]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3328 diff --git a/docs/rules/forbid-dom-props.md b/docs/rules/forbid-dom-props.md index 5e89b84ae7..15a1229324 100644 --- a/docs/rules/forbid-dom-props.md +++ b/docs/rules/forbid-dom-props.md @@ -43,12 +43,13 @@ Examples of **correct** code for this rule: ### `forbid` An array of strings, with the names of props that are forbidden. The default value of this option `[]`. -Each array element can either be a string with the property name or object specifying the property name and an optional -custom message: +Each array element can either be a string with the property name or object specifying the property name, an optional +custom message, and a DOM nodes disallowed list (e.g. `
`): ```js { "propName": "someProp", + "disallowedFor": ["DOMNode", "AnotherDOMNode"], "message": "Avoid using someProp" } ``` diff --git a/lib/rules/forbid-dom-props.js b/lib/rules/forbid-dom-props.js index 45beef4083..3b48098e3f 100644 --- a/lib/rules/forbid-dom-props.js +++ b/lib/rules/forbid-dom-props.js @@ -18,6 +18,21 @@ const DEFAULTS = []; // Rule Definition // ------------------------------------------------------------------------------ +/** + * @param {Map} forbidMap // { disallowList: null | string[], message: null | string } + * @param {string} prop + * @param {string} tagName + * @returns {boolean} + */ +function isForbidden(forbidMap, prop, tagName) { + const options = forbidMap.get(prop); + return options && ( + typeof tagName === 'undefined' + || !options.disallowList + || options.disallowList.indexOf(tagName) !== -1 + ); +} + const messages = { propIsForbidden: 'Prop "{{prop}}" is forbidden on DOM Nodes', }; @@ -47,6 +62,13 @@ module.exports = { propName: { type: 'string', }, + disallowedFor: { + type: 'array', + uniqueItems: true, + items: { + type: 'string', + }, + }, message: { type: 'string', }, @@ -65,16 +87,12 @@ 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 options = { + return [propName, { + disallowList: typeof value === 'string' ? null : (value.disallowedFor || null), message: typeof value === 'string' ? null : value.message, - }; - return [propName, options]; + }]; })); - function isForbidden(prop) { - return forbid.has(prop); - } - return { JSXAttribute(node) { const tag = node.parent.name.name; @@ -85,7 +103,7 @@ module.exports = { const prop = node.name.name; - if (!isForbidden(prop)) { + if (!isForbidden(forbid, prop, tag)) { return; } diff --git a/tests/lib/rules/forbid-dom-props.js b/tests/lib/rules/forbid-dom-props.js index f95a0fe20a..157e4950be 100644 --- a/tests/lib/rules/forbid-dom-props.js +++ b/tests/lib/rules/forbid-dom-props.js @@ -95,6 +95,23 @@ ruleTester.run('forbid-dom-props', rule, { `, options: [{ forbid: ['id'] }], }, + { + code: ` + const First = (props) => ( +
+ ); + `, + options: [ + { + forbid: [ + { + propName: 'otherProp', + disallowedFor: ['span'], + }, + ], + }, + ], + }, ]), invalid: parsers.all([ @@ -237,5 +254,75 @@ ruleTester.run('forbid-dom-props', rule, { }, ], }, + { + code: ` + const First = (props) => ( +
+ + +
+ ); + `, + options: [ + { + forbid: [{ + propName: 'accept', + disallowedFor: ['form'], + message: 'Avoid using the accept attribute on
', + }], + }, + ], + errors: [ + { + message: 'Avoid using the accept attribute on ', + line: 3, + column: 17, + type: 'JSXAttribute', + }, + ], + }, + { + code: ` + const First = (props) => ( +
+ + Foobar +
+
+ ); + `, + options: [ + { + forbid: [ + { + propName: 'className', + disallowedFor: ['div', 'span'], + message: 'Please use class instead of ClassName', + }, + { propName: 'otherProp', message: 'Avoid using otherProp' }, + ], + }, + ], + errors: [ + { + message: 'Please use class instead of ClassName', + line: 3, + column: 16, + type: 'JSXAttribute', + }, + { + message: 'Please use class instead of ClassName', + line: 5, + column: 19, + type: 'JSXAttribute', + }, + { + message: 'Avoid using otherProp', + line: 6, + column: 18, + type: 'JSXAttribute', + }, + ], + }, ]), });