Skip to content

Commit 241c38b

Browse files
alexzherdevljharb
authored andcommitted
[New] no-children-prop: Add allowFunctions option
Resolves #1803
1 parent e0c0812 commit 241c38b

File tree

4 files changed

+185
-6
lines changed

4 files changed

+185
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
1010
* add [`prefer-exact-props`] rule ([#1547][] @jomasti)
1111
* [`jsx-no-target-blank`]: add `forms` option ([#1617][] @jaaberg)
1212
* [`jsx-pascal-case`]: add `allowLeadingUnderscore` option ([#3039][] @pangaeatech)
13+
* [`no-children-prop`]: Add `allowFunctions` option ([#1903][] @alexzherdev)
1314

1415
### Fixed
1516
* component detection: use `estraverse` to improve component detection ([#2992][] @Wesitos)
@@ -39,6 +40,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
3940
[#2994]: https://github.com/yannickcr/eslint-plugin-react/pull/2994
4041
[#2992]: https://github.com/yannickcr/eslint-plugin-react/pull/2992
4142
[#2963]: https://github.com/yannickcr/eslint-plugin-react/pull/2963
43+
[#1903]: https://github.com/yannickcr/eslint-plugin-react/pull/1903
4244
[#1617]: https://github.com/yannickcr/eslint-plugin-react/pull/1617
4345
[#1547]: https://github.com/yannickcr/eslint-plugin-react/pull/1547
4446

docs/rules/no-children-prop.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,29 @@ Examples of **correct** code for this rule:
3434
React.createElement("div", {}, 'Children')
3535
React.createElement("div", 'Child 1', 'Child 2')
3636
```
37+
38+
## Rule Options
39+
40+
```js
41+
"react/no-children-prop": [<enabled>, {
42+
"allowFunctions": <boolean> || false
43+
}]
44+
```
45+
46+
### `allowFunctions`
47+
48+
When `true`, and passing a function as `children`, it must be in prop position and not child position.
49+
50+
The following patterns are considered warnings:
51+
52+
```jsx
53+
<MyComponent>{data => data.value}</MyComponent>
54+
React.createElement(MyComponent, {}, data => data.value)
55+
```
56+
57+
The following are **not** considered warnings:
58+
59+
```jsx
60+
<MyComponent children={data => data.value} />
61+
React.createElement(MyComponent, { children: data => data.value })
62+
```

lib/rules/no-children-prop.js

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,40 @@ module.exports = {
4040

4141
messages: {
4242
nestChildren: 'Do not pass children as props. Instead, nest children between the opening and closing tags.',
43-
passChildrenAsArgs: 'Do not pass children as props. Instead, pass them as additional arguments to React.createElement.'
43+
passChildrenAsArgs: 'Do not pass children as props. Instead, pass them as additional arguments to React.createElement.',
44+
nestFunction: 'Do not nest a function between the opening and closing tags. Instead, pass it as a prop.',
45+
passFunctionAsArgs: 'Do not pass a function as an additional argument to React.createElement. Instead, pass it as a prop.'
4446
},
4547

46-
schema: []
48+
schema: [{
49+
type: 'object',
50+
properties: {
51+
allowFunctions: {
52+
type: 'boolean',
53+
default: false
54+
}
55+
},
56+
additionalProperties: false
57+
}]
4758
},
4859
create(context) {
60+
const configuration = context.options[0] || {};
61+
62+
function isFunction(node) {
63+
return configuration.allowFunctions && (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression');
64+
}
65+
4966
return {
5067
JSXAttribute(node) {
5168
if (node.name.name !== 'children') {
5269
return;
5370
}
5471

72+
const value = node.value;
73+
if (value && value.type === 'JSXExpressionContainer' && isFunction(value.expression)) {
74+
return;
75+
}
76+
5577
context.report({
5678
node,
5779
messageId: 'nestChildren'
@@ -66,10 +88,31 @@ module.exports = {
6688
const childrenProp = props.find((prop) => prop.key && prop.key.name === 'children');
6789

6890
if (childrenProp) {
69-
context.report({
70-
node,
71-
messageId: 'passChildrenAsArgs'
72-
});
91+
if (childrenProp.value && !isFunction(childrenProp.value)) {
92+
context.report({
93+
node,
94+
messageId: 'passChildrenAsArgs'
95+
});
96+
}
97+
} else if (node.arguments.length === 3) {
98+
const children = node.arguments[2];
99+
if (isFunction(children)) {
100+
context.report({
101+
node,
102+
messageId: 'passFunctionAsArgs'
103+
});
104+
}
105+
}
106+
},
107+
JSXElement(node) {
108+
const children = node.children;
109+
if (children && children.length === 1 && children[0].type === 'JSXExpressionContainer') {
110+
if (isFunction(children[0].expression)) {
111+
context.report({
112+
node,
113+
messageId: 'nestFunction'
114+
});
115+
}
73116
}
74117
}
75118
};

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

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,61 @@ ruleTester.run('no-children-prop', rule, {
137137
},
138138
{
139139
code: 'React.createElement(MyComponent, {className: "class-name", ...props});'
140+
},
141+
{
142+
code: '<MyComponent children={() => {}} />;',
143+
options: [{
144+
allowFunctions: true
145+
}]
146+
},
147+
{
148+
code: '<MyComponent children={function() {}} />;',
149+
options: [{
150+
allowFunctions: true
151+
}]
152+
},
153+
{
154+
code: '<MyComponent children={async function() {}} />;',
155+
options: [{
156+
allowFunctions: true
157+
}]
158+
},
159+
{
160+
code: '<MyComponent children={function* () {}} />;',
161+
options: [{
162+
allowFunctions: true
163+
}]
164+
},
165+
{
166+
code: 'React.createElement(MyComponent, {children: () => {}});',
167+
options: [{
168+
allowFunctions: true
169+
}]
170+
},
171+
{
172+
code: 'React.createElement(MyComponent, {children: function() {}});',
173+
options: [{
174+
allowFunctions: true
175+
}]
176+
},
177+
{
178+
code: 'React.createElement(MyComponent, {children: async function() {}});',
179+
options: [{
180+
allowFunctions: true
181+
}]
182+
},
183+
{
184+
code: 'React.createElement(MyComponent, {children: function* () {}});',
185+
options: [{
186+
allowFunctions: true
187+
}]
140188
}
141189
],
142190
invalid: [
191+
{
192+
code: '<div children />;', // not a valid use case but make sure we don't crash
193+
errors: [{messageId: 'nestChildren'}]
194+
},
143195
{
144196
code: '<div children="Children" />;',
145197
errors: [{messageId: 'nestChildren'}]
@@ -195,6 +247,62 @@ ruleTester.run('no-children-prop', rule, {
195247
{
196248
code: 'React.createElement(MyComponent, {...props, children: "Children"})',
197249
errors: [{messageId: 'passChildrenAsArgs'}]
250+
},
251+
{
252+
code: '<MyComponent>{() => {}}</MyComponent>;',
253+
options: [{
254+
allowFunctions: true
255+
}],
256+
errors: [{messageId: 'nestFunction'}]
257+
},
258+
{
259+
code: '<MyComponent>{function() {}}</MyComponent>;',
260+
options: [{
261+
allowFunctions: true
262+
}],
263+
errors: [{messageId: 'nestFunction'}]
264+
},
265+
{
266+
code: '<MyComponent>{async function() {}}</MyComponent>;',
267+
options: [{
268+
allowFunctions: true
269+
}],
270+
errors: [{messageId: 'nestFunction'}]
271+
},
272+
{
273+
code: '<MyComponent>{function* () {}}</MyComponent>;',
274+
options: [{
275+
allowFunctions: true
276+
}],
277+
errors: [{messageId: 'nestFunction'}]
278+
},
279+
{
280+
code: 'React.createElement(MyComponent, {}, () => {});',
281+
options: [{
282+
allowFunctions: true
283+
}],
284+
errors: [{messageId: 'passFunctionAsArgs'}]
285+
},
286+
{
287+
code: 'React.createElement(MyComponent, {}, function() {});',
288+
options: [{
289+
allowFunctions: true
290+
}],
291+
errors: [{messageId: 'passFunctionAsArgs'}]
292+
},
293+
{
294+
code: 'React.createElement(MyComponent, {}, async function() {});',
295+
options: [{
296+
allowFunctions: true
297+
}],
298+
errors: [{messageId: 'passFunctionAsArgs'}]
299+
},
300+
{
301+
code: 'React.createElement(MyComponent, {}, function* () {});',
302+
options: [{
303+
allowFunctions: true
304+
}],
305+
errors: [{messageId: 'passFunctionAsArgs'}]
198306
}
199307
]
200308
});

0 commit comments

Comments
 (0)