Skip to content

Commit

Permalink
[New] no-children-prop: Add allowFunctions option
Browse files Browse the repository at this point in the history
Resolves #1803
  • Loading branch information
alexzherdev authored and ljharb committed Jul 26, 2018
1 parent e0c0812 commit 241c38b
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -10,6 +10,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
* add [`prefer-exact-props`] rule ([#1547][] @jomasti)
* [`jsx-no-target-blank`]: add `forms` option ([#1617][] @jaaberg)
* [`jsx-pascal-case`]: add `allowLeadingUnderscore` option ([#3039][] @pangaeatech)
* [`no-children-prop`]: Add `allowFunctions` option ([#1903][] @alexzherdev)

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

Expand Down
26 changes: 26 additions & 0 deletions docs/rules/no-children-prop.md
Expand Up @@ -34,3 +34,29 @@ Examples of **correct** code for this rule:
React.createElement("div", {}, 'Children')
React.createElement("div", 'Child 1', 'Child 2')
```

## Rule Options

```js
"react/no-children-prop": [<enabled>, {
"allowFunctions": <boolean> || false
}]
```

### `allowFunctions`

When `true`, and passing a function as `children`, it must be in prop position and not child position.

The following patterns are considered warnings:

```jsx
<MyComponent>{data => data.value}</MyComponent>
React.createElement(MyComponent, {}, data => data.value)
```

The following are **not** considered warnings:

```jsx
<MyComponent children={data => data.value} />
React.createElement(MyComponent, { children: data => data.value })
```
55 changes: 49 additions & 6 deletions lib/rules/no-children-prop.js
Expand Up @@ -40,18 +40,40 @@ module.exports = {

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

schema: []
schema: [{
type: 'object',
properties: {
allowFunctions: {
type: 'boolean',
default: false
}
},
additionalProperties: false
}]
},
create(context) {
const configuration = context.options[0] || {};

function isFunction(node) {
return configuration.allowFunctions && (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression');
}

return {
JSXAttribute(node) {
if (node.name.name !== 'children') {
return;
}

const value = node.value;
if (value && value.type === 'JSXExpressionContainer' && isFunction(value.expression)) {
return;
}

context.report({
node,
messageId: 'nestChildren'
Expand All @@ -66,10 +88,31 @@ module.exports = {
const childrenProp = props.find((prop) => prop.key && prop.key.name === 'children');

if (childrenProp) {
context.report({
node,
messageId: 'passChildrenAsArgs'
});
if (childrenProp.value && !isFunction(childrenProp.value)) {
context.report({
node,
messageId: 'passChildrenAsArgs'
});
}
} else if (node.arguments.length === 3) {
const children = node.arguments[2];
if (isFunction(children)) {
context.report({
node,
messageId: 'passFunctionAsArgs'
});
}
}
},
JSXElement(node) {
const children = node.children;
if (children && children.length === 1 && children[0].type === 'JSXExpressionContainer') {
if (isFunction(children[0].expression)) {
context.report({
node,
messageId: 'nestFunction'
});
}
}
}
};
Expand Down
108 changes: 108 additions & 0 deletions tests/lib/rules/no-children-prop.js
Expand Up @@ -137,9 +137,61 @@ ruleTester.run('no-children-prop', rule, {
},
{
code: 'React.createElement(MyComponent, {className: "class-name", ...props});'
},
{
code: '<MyComponent children={() => {}} />;',
options: [{
allowFunctions: true
}]
},
{
code: '<MyComponent children={function() {}} />;',
options: [{
allowFunctions: true
}]
},
{
code: '<MyComponent children={async function() {}} />;',
options: [{
allowFunctions: true
}]
},
{
code: '<MyComponent children={function* () {}} />;',
options: [{
allowFunctions: true
}]
},
{
code: 'React.createElement(MyComponent, {children: () => {}});',
options: [{
allowFunctions: true
}]
},
{
code: 'React.createElement(MyComponent, {children: function() {}});',
options: [{
allowFunctions: true
}]
},
{
code: 'React.createElement(MyComponent, {children: async function() {}});',
options: [{
allowFunctions: true
}]
},
{
code: 'React.createElement(MyComponent, {children: function* () {}});',
options: [{
allowFunctions: true
}]
}
],
invalid: [
{
code: '<div children />;', // not a valid use case but make sure we don't crash
errors: [{messageId: 'nestChildren'}]
},
{
code: '<div children="Children" />;',
errors: [{messageId: 'nestChildren'}]
Expand Down Expand Up @@ -195,6 +247,62 @@ ruleTester.run('no-children-prop', rule, {
{
code: 'React.createElement(MyComponent, {...props, children: "Children"})',
errors: [{messageId: 'passChildrenAsArgs'}]
},
{
code: '<MyComponent>{() => {}}</MyComponent>;',
options: [{
allowFunctions: true
}],
errors: [{messageId: 'nestFunction'}]
},
{
code: '<MyComponent>{function() {}}</MyComponent>;',
options: [{
allowFunctions: true
}],
errors: [{messageId: 'nestFunction'}]
},
{
code: '<MyComponent>{async function() {}}</MyComponent>;',
options: [{
allowFunctions: true
}],
errors: [{messageId: 'nestFunction'}]
},
{
code: '<MyComponent>{function* () {}}</MyComponent>;',
options: [{
allowFunctions: true
}],
errors: [{messageId: 'nestFunction'}]
},
{
code: 'React.createElement(MyComponent, {}, () => {});',
options: [{
allowFunctions: true
}],
errors: [{messageId: 'passFunctionAsArgs'}]
},
{
code: 'React.createElement(MyComponent, {}, function() {});',
options: [{
allowFunctions: true
}],
errors: [{messageId: 'passFunctionAsArgs'}]
},
{
code: 'React.createElement(MyComponent, {}, async function() {});',
options: [{
allowFunctions: true
}],
errors: [{messageId: 'passFunctionAsArgs'}]
},
{
code: 'React.createElement(MyComponent, {}, function* () {});',
options: [{
allowFunctions: true
}],
errors: [{messageId: 'passFunctionAsArgs'}]
}
]
});

0 comments on commit 241c38b

Please sign in to comment.