From d884a9878912856fc86788224c77a01ed47f77e2 Mon Sep 17 00:00:00 2001 From: Kenneth Chung Date: Wed, 5 Oct 2016 21:20:02 -0700 Subject: [PATCH] Add new rule: forbid-elements May specify a list of forbidden elements and a custom reporting message --- README.md | 1 + docs/rules/forbid-elements.md | 58 ++++++++ index.js | 1 + lib/rules/forbid-elements.js | 112 ++++++++++++++ tests/lib/rules/forbid-elements.js | 230 +++++++++++++++++++++++++++++ 5 files changed, 402 insertions(+) create mode 100644 docs/rules/forbid-elements.md create mode 100644 lib/rules/forbid-elements.js create mode 100644 tests/lib/rules/forbid-elements.js diff --git a/README.md b/README.md index 5137ca0fe3..21deb17970 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](# * [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition * [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components +* [react/forbid-elements](docs/rules/forbid-elements.md): Forbid certain elements * [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes * [react/no-array-index-key](docs/rules/no-array-index-key.md): Prevent using Array index in `key` props * [react/no-children-prop](docs/rules/no-children-prop.md): Prevent passing children as props diff --git a/docs/rules/forbid-elements.md b/docs/rules/forbid-elements.md new file mode 100644 index 0000000000..16c4c62ddd --- /dev/null +++ b/docs/rules/forbid-elements.md @@ -0,0 +1,58 @@ +# Forbid certain elements (forbid-elements) + +You may want to forbid usage of certain elements in favor of others, (e.g. forbid all `
` and use `` instead). This rule allows you to configure a list of forbidden elements and to specify their desired replacements. + +## Rule Details + +This rule checks all JSX elements and `React.createElement` calls and verifies that no forbidden elements are used. This rule is off by default. If on, no elements are forbidden by default. + +## Rule Options + +```js +... +"forbid-elements": [, { "forbid": [] }] +... +``` + +### `forbid` + +An array of strings and/or objects. An object in this array may have the following properties: + +* `element` (required): the name of the forbidden element (e.g. `'button'`, `'Modal'`) +* `message`: additional message that gets reported + +A string item in the array is a shorthand for `{ element: string }`. + +The following patterns are not considered warnings: + +```jsx +// [1, { "forbid": ["button"] }] +
+React.createElement('div', {}, React.createElemet('button', {}, React.createElement('input'))); +``` + +## When not to use + +If you don't want to forbid any elements. diff --git a/index.js b/index.js index df651dfbd1..eb894a8f9b 100644 --- a/index.js +++ b/index.js @@ -42,6 +42,7 @@ var allRules = { 'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'), 'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'), 'forbid-component-props': require('./lib/rules/forbid-component-props'), + 'forbid-elements': require('./lib/rules/forbid-elements'), 'forbid-prop-types': require('./lib/rules/forbid-prop-types'), 'prefer-es6-class': require('./lib/rules/prefer-es6-class'), 'jsx-key': require('./lib/rules/jsx-key'), diff --git a/lib/rules/forbid-elements.js b/lib/rules/forbid-elements.js new file mode 100644 index 0000000000..8cce882fb4 --- /dev/null +++ b/lib/rules/forbid-elements.js @@ -0,0 +1,112 @@ +/** + * @fileoverview Forbid certain elements + * @author Kenneth Chung + */ +'use strict'; + +var has = require('has'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'Forbid certain elements', + category: 'Best Practices', + recommended: false + }, + + schema: [{ + type: 'object', + properties: { + forbid: { + type: 'array', + items: { + anyOf: [ + {type: 'string'}, + { + type: 'object', + properties: { + element: {type: 'string'}, + message: {type: 'string'} + }, + required: ['element'], + additionalProperties: false + } + ] + } + } + }, + additionalProperties: false + }] + }, + + create: function(context) { + var sourceCode = context.getSourceCode(); + var configuration = context.options[0] || {}; + var forbidConfiguration = configuration.forbid || []; + + var indexedForbidConfigs = {}; + + forbidConfiguration.forEach(function(item) { + if (typeof item === 'string') { + indexedForbidConfigs[item] = {element: item}; + } else { + indexedForbidConfigs[item.element] = item; + } + }); + + function errorMessageForElement(name) { + var message = '<' + name + '> is forbidden'; + var additionalMessage = indexedForbidConfigs[name].message; + + if (additionalMessage) { + message = message + ', ' + additionalMessage; + } + + return message; + } + + function isValidCreateElement(node) { + return node.callee + && node.callee.type === 'MemberExpression' + && node.callee.object.name === 'React' + && node.callee.property.name === 'createElement' + && node.arguments.length > 0; + } + + function reportIfForbidden(element, node) { + if (has(indexedForbidConfigs, element)) { + context.report({ + node: node, + message: errorMessageForElement(element) + }); + } + } + + return { + JSXOpeningElement: function(node) { + reportIfForbidden(sourceCode.getText(node.name), node.name); + }, + + CallExpression: function(node) { + if (!isValidCreateElement(node)) { + return; + } + + var argument = node.arguments[0]; + var argType = argument.type; + + if (argType === 'Identifier' && /^[A-Z_]/.test(argument.name)) { + reportIfForbidden(argument.name, argument); + } else if (argType === 'Literal' && /^[a-z][^\.]*$/.test(argument.value)) { + reportIfForbidden(argument.value, argument); + } else if (argType === 'MemberExpression') { + reportIfForbidden(sourceCode.getText(argument), argument); + } + } + }; + } +}; diff --git a/tests/lib/rules/forbid-elements.js b/tests/lib/rules/forbid-elements.js new file mode 100644 index 0000000000..0ab7fcdc1f --- /dev/null +++ b/tests/lib/rules/forbid-elements.js @@ -0,0 +1,230 @@ +/** + * @fileoverview Tests for forbid-elements + */ +'use strict'; + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +var rule = require('../../../lib/rules/forbid-elements'); +var RuleTester = require('eslint').RuleTester; + +var parserOptions = { + ecmaVersion: 6, + ecmaFeatures: { + experimentalObjectRestSpread: true, + jsx: true + } +}; + +require('babel-eslint'); + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +var ruleTester = new RuleTester(); +ruleTester.run('forbid-elements', rule, { + valid: [ + { + code: '', + options: [{forbid: [{element: 'button'}, {element: 'input'}]}], + parserOptions: parserOptions, + errors: [ + {message: '', + options: [{forbid: [{element: 'button'}, 'input']}], + parserOptions: parserOptions, + errors: [ + {message: '', + options: [{forbid: ['input', {element: 'button'}]}], + parserOptions: parserOptions, + errors: [ + {message: '