Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] no-children-prop: Add allowFunctions option #1903

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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'}]
}
]
});