Skip to content

Commit b1f9f08

Browse files
committed
Add new no-children-prop rule (Fixes jsx-eslint#720)
Prevents children being passed as props. Children should be actual children. --- * Add Component to tests for no-children-prop * Add note in docs about recommended behavior * Change syntax in docs for no-children-prop to jsx --- * Clarify that multiple arguments may be passed as children to React.createElement * Reorganize tests to show JSX with createElement counterpart immediately following. * Added test cases with a non-children property * Added test cases with multiple children --- * Add no-children-prop to recommended config
1 parent 8b8eba7 commit b1f9f08

File tree

5 files changed

+343
-1
lines changed

5 files changed

+343
-1
lines changed

README.md

+2
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`
@@ -160,6 +161,7 @@ The rules enabled in this configuration are:
160161
* [react/jsx-no-undef](docs/rules/jsx-no-undef.md)
161162
* [react/jsx-uses-react](docs/rules/jsx-uses-react.md)
162163
* [react/jsx-uses-vars](docs/rules/jsx-uses-vars.md)
164+
* [react/no-children-prop](docs/rules/no-children-prop.md)
163165
* [react/no-danger](docs/rules/no-danger.md)
164166
* [react/no-deprecated](docs/rules/no-deprecated.md)
165167
* [react/no-did-mount-set-state](docs/rules/no-did-mount-set-state.md) with `allow-in-func` option

docs/rules/no-children-prop.md

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
When using JSX, the children should be nested between the opening and closing
6+
tags. When not using JSX, the children should be passed as additional
7+
arguments to `React.createElement`.
8+
9+
## Rule Details
10+
11+
The following patterns are considered warnings:
12+
13+
```jsx
14+
<div children='Children' />
15+
16+
<MyComponent children={<AnotherComponent />} />
17+
<MyComponent children={['Child 1', 'Child 2']} />
18+
19+
React.createElement("div", { children: 'Children' })
20+
```
21+
22+
The following patterns are not considered warnings:
23+
24+
```jsx
25+
<div>Children</div>
26+
27+
<MyComponent>Children</MyComponent>
28+
29+
<MyComponent>
30+
<span>Child 1</span>
31+
<span>Child 2</span>
32+
</MyComponent>
33+
34+
React.createElement("div", {}, 'Children')
35+
React.createElement("div", 'Child 1', 'Child 2')
36+
```

index.js

+3-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);
@@ -74,6 +75,7 @@ module.exports = {
7475
'react/jsx-no-undef': 2,
7576
'react/jsx-uses-react': 2,
7677
'react/jsx-uses-vars': 2,
78+
'react/no-children-prop': 2,
7779
'react/no-danger': 2,
7880
'react/no-deprecated': 2,
7981
'react/no-did-mount-set-state': [2, 'allow-in-func'],

lib/rules/no-children-prop.js

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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 the node is a createElement call with a props literal.
13+
* @param {ASTNode} node - The AST node being checked.
14+
* @returns {Boolean} - True if node is a createElement call with a props
15+
* object literal, False if not.
16+
*/
17+
function isCreateElementWithProps(node) {
18+
return node.callee
19+
&& node.callee.type === 'MemberExpression'
20+
&& node.callee.property.name === 'createElement'
21+
&& node.arguments.length > 1
22+
&& node.arguments[1].type === 'ObjectExpression';
23+
}
24+
25+
// ------------------------------------------------------------------------------
26+
// Rule Definition
27+
// ------------------------------------------------------------------------------
28+
29+
module.exports = {
30+
meta: {
31+
docs: {},
32+
schema: []
33+
},
34+
create: function(context) {
35+
return {
36+
JSXAttribute: function(node) {
37+
if (node.name.name !== 'children') {
38+
return;
39+
}
40+
41+
context.report({
42+
node: node,
43+
message: 'Do not pass children as props. Instead, nest children between the opening and closing tags.'
44+
});
45+
},
46+
CallExpression: function(node) {
47+
if (!isCreateElementWithProps(node)) {
48+
return;
49+
}
50+
51+
var props = node.arguments[1].properties;
52+
var childrenProp = props.find(function(prop) {
53+
return prop.key.name === 'children';
54+
});
55+
56+
if (childrenProp) {
57+
context.report({
58+
node: node,
59+
message: 'Do not pass children as props. Instead, pass them as additional arguments to React.createElement.'
60+
});
61+
}
62+
}
63+
};
64+
}
65+
};

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

+237
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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+
var JSX_ERROR = 'Do not pass children as props. Instead, nest children between \
23+
the opening and closing tags.';
24+
var CREATE_ELEMENT_ERROR = 'Do not pass children as props. Instead, pass them \
25+
as additional arguments to React.createElement.';
26+
27+
// -----------------------------------------------------------------------------
28+
// Tests
29+
// -----------------------------------------------------------------------------
30+
31+
var ruleTester = new RuleTester();
32+
ruleTester.run('no-children-prop', rule, {
33+
valid: [
34+
{
35+
code: '<div />;',
36+
parserOptions: parserOptions
37+
},
38+
{
39+
code: '<div></div>;',
40+
parserOptions: parserOptions
41+
},
42+
{
43+
code: 'React.createElement("div", {});',
44+
parserOptions: parserOptions
45+
},
46+
{
47+
code: 'React.createElement("div", undefined);',
48+
parserOptions: parserOptions
49+
},
50+
{
51+
code: '<div className="class-name"></div>;',
52+
parserOptions: parserOptions
53+
},
54+
{
55+
code: 'React.createElement("div", {className: "class-name"});',
56+
parserOptions: parserOptions
57+
},
58+
{
59+
code: '<div>Children</div>;',
60+
parserOptions: parserOptions
61+
},
62+
{
63+
code: 'React.createElement("div", "Children");',
64+
parserOptions: parserOptions
65+
},
66+
{
67+
code: 'React.createElement("div", {}, "Children");',
68+
parserOptions: parserOptions
69+
},
70+
{
71+
code: 'React.createElement("div", undefined, "Children");',
72+
parserOptions: parserOptions
73+
},
74+
{
75+
code: '<div className="class-name">Children</div>;',
76+
parserOptions: parserOptions
77+
},
78+
{
79+
code: 'React.createElement("div", {className: "class-name"}, "Children");',
80+
parserOptions: parserOptions
81+
},
82+
{
83+
code: '<div><div /></div>;',
84+
parserOptions: parserOptions
85+
},
86+
{
87+
code: 'React.createElement("div", React.createElement("div"));',
88+
parserOptions: parserOptions
89+
},
90+
{
91+
code: 'React.createElement("div", {}, React.createElement("div"));',
92+
parserOptions: parserOptions
93+
},
94+
{
95+
code: 'React.createElement("div", undefined, React.createElement("div"));',
96+
parserOptions: parserOptions
97+
},
98+
{
99+
code: '<div><div /><div /></div>;',
100+
parserOptions: parserOptions
101+
},
102+
{
103+
code: 'React.createElement("div", React.createElement("div"), React.createElement("div"));',
104+
parserOptions: parserOptions
105+
},
106+
{
107+
code: 'React.createElement("div", {}, React.createElement("div"), React.createElement("div"));',
108+
parserOptions: parserOptions
109+
},
110+
{
111+
code: 'React.createElement("div", undefined, React.createElement("div"), React.createElement("div"));',
112+
parserOptions: parserOptions
113+
},
114+
{
115+
code: 'React.createElement("div", [React.createElement("div"), React.createElement("div")]);',
116+
parserOptions: parserOptions
117+
},
118+
{
119+
code: 'React.createElement("div", {}, [React.createElement("div"), React.createElement("div")]);',
120+
parserOptions: parserOptions
121+
},
122+
{
123+
code: 'React.createElement("div", undefined, [React.createElement("div"), React.createElement("div")]);',
124+
parserOptions: parserOptions
125+
},
126+
{
127+
code: '<MyComponent />',
128+
parserOptions: parserOptions
129+
},
130+
{
131+
code: 'React.createElement(MyComponent);',
132+
parserOptions: parserOptions
133+
},
134+
{
135+
code: 'React.createElement(MyComponent, {});',
136+
parserOptions: parserOptions
137+
},
138+
{
139+
code: 'React.createElement(MyComponent, undefined);',
140+
parserOptions: parserOptions
141+
},
142+
{
143+
code: '<MyComponent>Children</MyComponent>;',
144+
parserOptions: parserOptions
145+
},
146+
{
147+
code: 'React.createElement(MyComponent, "Children");',
148+
parserOptions: parserOptions
149+
},
150+
{
151+
code: 'React.createElement(MyComponent, {}, "Children");',
152+
parserOptions: parserOptions
153+
},
154+
{
155+
code: 'React.createElement(MyComponent, undefined, "Children");',
156+
parserOptions: parserOptions
157+
},
158+
{
159+
code: '<MyComponent className="class-name"></MyComponent>;',
160+
parserOptions: parserOptions
161+
},
162+
{
163+
code: 'React.createElement(MyComponent, {className: "class-name"});',
164+
parserOptions: parserOptions
165+
},
166+
{
167+
code: '<MyComponent className="class-name">Children</MyComponent>;',
168+
parserOptions: parserOptions
169+
},
170+
{
171+
code: 'React.createElement(MyComponent, {className: "class-name"}, "Children");',
172+
parserOptions: parserOptions
173+
}
174+
],
175+
invalid: [
176+
{
177+
code: '<div children="Children" />;',
178+
errors: [{message: JSX_ERROR}],
179+
parserOptions: parserOptions
180+
},
181+
{
182+
code: '<div children={<div />} />;',
183+
errors: [{message: JSX_ERROR}],
184+
parserOptions: parserOptions
185+
},
186+
{
187+
code: '<div children={[<div />, <div />]} />;',
188+
errors: [{message: JSX_ERROR}],
189+
parserOptions: parserOptions
190+
},
191+
{
192+
code: '<div children="Children">Children</div>;',
193+
errors: [{message: JSX_ERROR}],
194+
parserOptions: parserOptions
195+
},
196+
{
197+
code: 'React.createElement("div", {children: "Children"});',
198+
errors: [{message: CREATE_ELEMENT_ERROR}],
199+
parserOptions: parserOptions
200+
},
201+
{
202+
code: 'React.createElement("div", {children: "Children"}, "Children");',
203+
errors: [{message: CREATE_ELEMENT_ERROR}],
204+
parserOptions: parserOptions
205+
},
206+
{
207+
code: 'React.createElement("div", {children: React.createElement("div")});',
208+
errors: [{message: CREATE_ELEMENT_ERROR}],
209+
parserOptions: parserOptions
210+
},
211+
{
212+
code: 'React.createElement("div", {children: [React.createElement("div"), React.createElement("div")]});',
213+
errors: [{message: CREATE_ELEMENT_ERROR}],
214+
parserOptions: parserOptions
215+
},
216+
{
217+
code: '<MyComponent children="Children" />',
218+
errors: [{message: JSX_ERROR}],
219+
parserOptions: parserOptions
220+
},
221+
{
222+
code: 'React.createElement(MyComponent, {children: "Children"});',
223+
errors: [{message: CREATE_ELEMENT_ERROR}],
224+
parserOptions: parserOptions
225+
},
226+
{
227+
code: '<MyComponent className="class-name" children="Children" />;',
228+
errors: [{message: JSX_ERROR}],
229+
parserOptions: parserOptions
230+
},
231+
{
232+
code: 'React.createElement(MyComponent, {children: "Children", className: "class-name"});',
233+
errors: [{message: CREATE_ELEMENT_ERROR}],
234+
parserOptions: parserOptions
235+
}
236+
]
237+
});

0 commit comments

Comments
 (0)