Skip to content

Commit ee144d6

Browse files
authored
Merge pull request jsx-eslint#890 from kentor/forbid-elements
Add forbid-elements rule
2 parents 3e2421e + d884a98 commit ee144d6

File tree

5 files changed

+402
-0
lines changed

5 files changed

+402
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
8282

8383
* [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
8484
* [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components
85+
* [react/forbid-elements](docs/rules/forbid-elements.md): Forbid certain elements
8586
* [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
8687
* [react/forbid-foreign-prop-types](docs/rules/forbid-foreign-prop-types.md): Forbid foreign propTypes
8788
* [react/no-array-index-key](docs/rules/no-array-index-key.md): Prevent using Array index in `key` props

docs/rules/forbid-elements.md

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Forbid certain elements (forbid-elements)
2+
3+
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.
4+
5+
## Rule Details
6+
7+
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.
8+
9+
## Rule Options
10+
11+
```js
12+
...
13+
"forbid-elements": [<enabled>, { "forbid": [<string|object>] }]
14+
...
15+
```
16+
17+
### `forbid`
18+
19+
An array of strings and/or objects. An object in this array may have the following properties:
20+
21+
* `element` (required): the name of the forbidden element (e.g. `'button'`, `'Modal'`)
22+
* `message`: additional message that gets reported
23+
24+
A string item in the array is a shorthand for `{ element: string }`.
25+
26+
The following patterns are not considered warnings:
27+
28+
```jsx
29+
// [1, { "forbid": ["button"] }]
30+
<Button />
31+
32+
// [1, { "forbid": [{ "element": "button" }] }]
33+
<Button />
34+
```
35+
36+
The following patterns are considered warnings:
37+
38+
```jsx
39+
// [1, { "forbid": ["button"] }]
40+
<button />
41+
React.createElement('button');
42+
43+
// [1, { "forbid": ["Modal"] }]
44+
<Modal />
45+
React.createElement(Modal);
46+
47+
// [1, { "forbid": ["Namespaced.Element"] }]
48+
<Namespaced.Element />
49+
React.createElement(Namespaced.Element);
50+
51+
// [1, { "forbid": [{ "element": "button", "message": "use <Button> instead" }, "input"] }]
52+
<div><button /><input /></div>
53+
React.createElement('div', {}, React.createElemet('button', {}, React.createElement('input')));
54+
```
55+
56+
## When not to use
57+
58+
If you don't want to forbid any elements.

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ var allRules = {
4242
'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'),
4343
'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'),
4444
'forbid-component-props': require('./lib/rules/forbid-component-props'),
45+
'forbid-elements': require('./lib/rules/forbid-elements'),
4546
'forbid-prop-types': require('./lib/rules/forbid-prop-types'),
4647
'forbid-foreign-prop-types': require('./lib/rules/forbid-foreign-prop-types'),
4748
'prefer-es6-class': require('./lib/rules/prefer-es6-class'),

lib/rules/forbid-elements.js

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* @fileoverview Forbid certain elements
3+
* @author Kenneth Chung
4+
*/
5+
'use strict';
6+
7+
var has = require('has');
8+
9+
// ------------------------------------------------------------------------------
10+
// Rule Definition
11+
// ------------------------------------------------------------------------------
12+
13+
module.exports = {
14+
meta: {
15+
docs: {
16+
description: 'Forbid certain elements',
17+
category: 'Best Practices',
18+
recommended: false
19+
},
20+
21+
schema: [{
22+
type: 'object',
23+
properties: {
24+
forbid: {
25+
type: 'array',
26+
items: {
27+
anyOf: [
28+
{type: 'string'},
29+
{
30+
type: 'object',
31+
properties: {
32+
element: {type: 'string'},
33+
message: {type: 'string'}
34+
},
35+
required: ['element'],
36+
additionalProperties: false
37+
}
38+
]
39+
}
40+
}
41+
},
42+
additionalProperties: false
43+
}]
44+
},
45+
46+
create: function(context) {
47+
var sourceCode = context.getSourceCode();
48+
var configuration = context.options[0] || {};
49+
var forbidConfiguration = configuration.forbid || [];
50+
51+
var indexedForbidConfigs = {};
52+
53+
forbidConfiguration.forEach(function(item) {
54+
if (typeof item === 'string') {
55+
indexedForbidConfigs[item] = {element: item};
56+
} else {
57+
indexedForbidConfigs[item.element] = item;
58+
}
59+
});
60+
61+
function errorMessageForElement(name) {
62+
var message = '<' + name + '> is forbidden';
63+
var additionalMessage = indexedForbidConfigs[name].message;
64+
65+
if (additionalMessage) {
66+
message = message + ', ' + additionalMessage;
67+
}
68+
69+
return message;
70+
}
71+
72+
function isValidCreateElement(node) {
73+
return node.callee
74+
&& node.callee.type === 'MemberExpression'
75+
&& node.callee.object.name === 'React'
76+
&& node.callee.property.name === 'createElement'
77+
&& node.arguments.length > 0;
78+
}
79+
80+
function reportIfForbidden(element, node) {
81+
if (has(indexedForbidConfigs, element)) {
82+
context.report({
83+
node: node,
84+
message: errorMessageForElement(element)
85+
});
86+
}
87+
}
88+
89+
return {
90+
JSXOpeningElement: function(node) {
91+
reportIfForbidden(sourceCode.getText(node.name), node.name);
92+
},
93+
94+
CallExpression: function(node) {
95+
if (!isValidCreateElement(node)) {
96+
return;
97+
}
98+
99+
var argument = node.arguments[0];
100+
var argType = argument.type;
101+
102+
if (argType === 'Identifier' && /^[A-Z_]/.test(argument.name)) {
103+
reportIfForbidden(argument.name, argument);
104+
} else if (argType === 'Literal' && /^[a-z][^\.]*$/.test(argument.value)) {
105+
reportIfForbidden(argument.value, argument);
106+
} else if (argType === 'MemberExpression') {
107+
reportIfForbidden(sourceCode.getText(argument), argument);
108+
}
109+
}
110+
};
111+
}
112+
};

0 commit comments

Comments
 (0)