Skip to content

Commit 52e3303

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 --- * Remove recommended config for no-children-prop
1 parent 21a3339 commit 52e3303

File tree

5 files changed

+346
-1
lines changed

5 files changed

+346
-1
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
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
8585
* [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
86+
* [react/no-children-prop](docs/rules/no-children-prop.md): Prevent passing children as props
8687
* [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
8788
* [react/no-danger-with-children](docs/rules/no-danger-with-children.md): Prevent problem with children and props.dangerouslySetInnerHTML
8889
* [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods
@@ -166,6 +167,7 @@ The rules enabled in this configuration are:
166167
* [react/jsx-no-undef](docs/rules/jsx-no-undef.md)
167168
* [react/jsx-uses-react](docs/rules/jsx-uses-react.md)
168169
* [react/jsx-uses-vars](docs/rules/jsx-uses-vars.md)
170+
* [react/no-danger](docs/rules/no-danger.md)
169171
* [react/no-deprecated](docs/rules/no-deprecated.md)
170172
* [react/no-direct-mutation-state](docs/rules/no-direct-mutation-state.md)
171173
* [react/no-find-dom-node](docs/rules/no-find-dom-node.md)

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ var rules = {
5757
'no-find-dom-node': require('./lib/rules/no-find-dom-node'),
5858
'no-danger-with-children': require('./lib/rules/no-danger-with-children'),
5959
'style-prop-object': require('./lib/rules/style-prop-object'),
60-
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types')
60+
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
61+
'no-children-prop': require('./lib/rules/no-children-prop')
6162
};
6263

6364
var ruleNames = Object.keys(rules);

lib/rules/no-children-prop.js

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
description: 'Prevent passing of children as props.',
33+
category: 'Best Practices',
34+
recommended: false
35+
},
36+
schema: []
37+
},
38+
create: function(context) {
39+
return {
40+
JSXAttribute: function(node) {
41+
if (node.name.name !== 'children') {
42+
return;
43+
}
44+
45+
context.report({
46+
node: node,
47+
message: 'Do not pass children as props. Instead, nest children between the opening and closing tags.'
48+
});
49+
},
50+
CallExpression: function(node) {
51+
if (!isCreateElementWithProps(node)) {
52+
return;
53+
}
54+
55+
var props = node.arguments[1].properties;
56+
var childrenProp = props.find(function(prop) {
57+
return prop.key.name === 'children';
58+
});
59+
60+
if (childrenProp) {
61+
context.report({
62+
node: node,
63+
message: 'Do not pass children as props. Instead, pass them as additional arguments to React.createElement.'
64+
});
65+
}
66+
}
67+
};
68+
}
69+
};

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)