Skip to content

Add forbid-elements rule #890

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 58 additions & 0 deletions docs/rules/forbid-elements.md
Original file line number Diff line number Diff line change
@@ -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 `<div />` and use `<Box />` 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": [<enabled>, { "forbid": [<string|object>] }]
...
```

### `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"] }]
<Button />

// [1, { "forbid": [{ "element": "button" }] }]
<Button />
```

The following patterns are considered warnings:

```jsx
// [1, { "forbid": ["button"] }]
<button />
React.createElement('button');

// [1, { "forbid": ["Modal"] }]
<Modal />
React.createElement(Modal);

// [1, { "forbid": ["Namespaced.Element"] }]
<Namespaced.Element />
React.createElement(Namespaced.Element);

// [1, { "forbid": [{ "element": "button", "message": "use <Button> instead" }, "input"] }]
<div><button /><input /></div>
React.createElement('div', {}, React.createElemet('button', {}, React.createElement('input')));
```

## When not to use

If you don't want to forbid any elements.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
112 changes: 112 additions & 0 deletions lib/rules/forbid-elements.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
};
}
};
Loading