Skip to content

Commit 3f1d0b2

Browse files
committed
Add new no-children-prop rule (Fixes jsx-eslint#720)
Prevents children being passed as props. Children should be actual children.
1 parent 8b8eba7 commit 3f1d0b2

File tree

5 files changed

+219
-1
lines changed

5 files changed

+219
-1
lines changed

README.md

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

8282
* [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
8383
* [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
84+
* [react/no-children-prop](docs/rules/no-children-prop.md): Prevent passing children as props
8485
* [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
8586
* [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods
8687
* [react/no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of `setState` in `componentDidMount`

docs/rules/no-children-prop.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Prevent passing of children as props (no-children-prop)
2+
3+
Children should always be actual children, not passed in as a prop.
4+
5+
## Rule Details
6+
7+
The following patterns are considered warnings:
8+
9+
```js
10+
<div children='Children' />
11+
12+
React.createElement("div", { children: 'Children' })
13+
```
14+
15+
The following patterns are not considered warnings:
16+
17+
```js
18+
<div>Children</div>
19+
20+
React.createElement("div", {}, 'Children')
21+
```

index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ var rules = {
5050
'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'),
5151
'jsx-filename-extension': require('./lib/rules/jsx-filename-extension'),
5252
'require-optimization': require('./lib/rules/require-optimization'),
53-
'no-find-dom-node': require('./lib/rules/no-find-dom-node')
53+
'no-find-dom-node': require('./lib/rules/no-find-dom-node'),
54+
'no-children-prop': require('./lib/rules/no-children-prop')
5455
};
5556

5657
var ruleNames = Object.keys(rules);

lib/rules/no-children-prop.js

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* @fileoverview Prevent passing of children as props
3+
* @author Benjamin Stepp
4+
*/
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Helpers
9+
// ------------------------------------------------------------------------------
10+
11+
/**
12+
* Checks if a node name match the JSX tag convention.
13+
* @param {String} name - Name of the node to check.
14+
* @returns {Boolean} Whether or not the node name match the JSX tag convention.
15+
*/
16+
var tagConvention = /^[a-z]|\-/;
17+
function isTagName(name) {
18+
return tagConvention.test(name);
19+
}
20+
21+
/**
22+
* Checks if the node is a createElement call with a props literal.
23+
* @param {ASTNode} node - The AST node being checked.
24+
* @returns {Boolean} - True if node is a createElement call with a props
25+
* object literal, False if not.
26+
*/
27+
function isCreateElementWithProps(node) {
28+
return node.callee
29+
&& node.callee.type === 'MemberExpression'
30+
&& node.callee.property.name === 'createElement'
31+
&& node.arguments.length > 1
32+
&& node.arguments[1].type === 'ObjectExpression';
33+
}
34+
35+
// ------------------------------------------------------------------------------
36+
// Rule Definition
37+
// ------------------------------------------------------------------------------
38+
39+
module.exports = {
40+
meta: {
41+
docs: {},
42+
schema: []
43+
},
44+
create: function(context) {
45+
return {
46+
JSXAttribute: function(node) {
47+
if (isTagName(node.parent.name.name) && node.name.name === 'children') {
48+
context.report({
49+
node: node,
50+
message: 'Do not pass children as props.'
51+
});
52+
}
53+
},
54+
CallExpression: function(node) {
55+
if (isCreateElementWithProps(node)) {
56+
var props = node.arguments[1].properties;
57+
var childrenProp = props.find(function(prop) {
58+
return prop.key.name === 'children';
59+
});
60+
61+
if (childrenProp) {
62+
context.report({
63+
node: node,
64+
message: 'Do not pass children as props.'
65+
});
66+
}
67+
}
68+
}
69+
};
70+
}
71+
};

tests/lib/rules/no-children-prop.js

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* @fileoverview Tests for no-children-prop
3+
* @author Benjamin Stepp
4+
*/
5+
6+
'use strict';
7+
8+
// -----------------------------------------------------------------------------
9+
// Requirements
10+
// -----------------------------------------------------------------------------
11+
12+
var rule = require('../../../lib/rules/no-children-prop');
13+
var RuleTester = require('eslint').RuleTester;
14+
15+
var parserOptions = {
16+
ecmaVersion: 6,
17+
ecmaFeatures: {
18+
jsx: true
19+
}
20+
};
21+
22+
// -----------------------------------------------------------------------------
23+
// Tests
24+
// -----------------------------------------------------------------------------
25+
26+
var ruleTester = new RuleTester();
27+
ruleTester.run('no-children-prop', rule, {
28+
valid: [
29+
{
30+
code: '<div />;',
31+
parserOptions: parserOptions
32+
},
33+
{
34+
code: '<div></div>;',
35+
parserOptions: parserOptions
36+
},
37+
{
38+
code: 'React.createElement("div", {});',
39+
parserOptions: parserOptions
40+
},
41+
{
42+
code: 'React.createElement("div", undefined);',
43+
parserOptions: parserOptions
44+
},
45+
{
46+
code: '<div>Children</div>;',
47+
parserOptions: parserOptions
48+
},
49+
{
50+
code: 'React.createElement("div", {}, "Children");',
51+
parserOptions: parserOptions
52+
},
53+
{
54+
code: 'React.createElement("div", undefined, "Children");',
55+
parserOptions: parserOptions
56+
},
57+
{
58+
code: '<div><div /></div>;',
59+
parserOptions: parserOptions
60+
},
61+
{
62+
code: 'React.createElement("div", {}, React.createElement("div"));',
63+
parserOptions: parserOptions
64+
},
65+
{
66+
code: 'React.createElement("div", undefined, React.createElement("div"));',
67+
parserOptions: parserOptions
68+
},
69+
{
70+
code: '<div><div /><div /></div>;',
71+
parserOptions: parserOptions
72+
},
73+
{
74+
code: 'React.createElement("div", {}, [React.createElement("div"), React.createElement("div")]);',
75+
parserOptions: parserOptions
76+
},
77+
{
78+
code: 'React.createElement("div", undefined, [React.createElement("div"), React.createElement("div")]);',
79+
parserOptions: parserOptions
80+
}
81+
],
82+
invalid: [
83+
{
84+
code: '<div children="Children" />;',
85+
errors: [{message: 'Do not pass children as props.'}],
86+
parserOptions: parserOptions
87+
},
88+
{
89+
code: '<div children={<div />} />;',
90+
errors: [{message: 'Do not pass children as props.'}],
91+
parserOptions: parserOptions
92+
},
93+
{
94+
code: '<div children={[<div />, <div />]} />;',
95+
errors: [{message: 'Do not pass children as props.'}],
96+
parserOptions: parserOptions
97+
},
98+
{
99+
code: '<div children="Children">Children</div>;',
100+
errors: [{message: 'Do not pass children as props.'}],
101+
parserOptions: parserOptions
102+
},
103+
{
104+
code: 'React.createElement("div", {children: "Children"});',
105+
errors: [{message: 'Do not pass children as props.'}],
106+
parserOptions: parserOptions
107+
},
108+
{
109+
code: 'React.createElement("div", {children: "Children"}, "Children");',
110+
errors: [{message: 'Do not pass children as props.'}],
111+
parserOptions: parserOptions
112+
},
113+
{
114+
code: 'React.createElement("div", {children: React.createElement("div")});',
115+
errors: [{message: 'Do not pass children as props.'}],
116+
parserOptions: parserOptions
117+
},
118+
{
119+
code: 'React.createElement("div", {children: [React.createElement("div"), React.createElement("div")]});',
120+
errors: [{message: 'Do not pass children as props.'}],
121+
parserOptions: parserOptions
122+
}
123+
]
124+
});

0 commit comments

Comments
 (0)