diff --git a/docs/rules/no-children-prop.md b/docs/rules/no-children-prop.md index b3a5b4b705..c4607ae33b 100644 --- a/docs/rules/no-children-prop.md +++ b/docs/rules/no-children-prop.md @@ -34,3 +34,29 @@ The following patterns are **not** considered warnings: React.createElement("div", {}, 'Children') React.createElement("div", 'Child 1', 'Child 2') ``` + +## Rule Options + +```js +"react/no-children-prop": [, { + "allowFunctions": || false +}] +``` + +### `allowFunctions` + +When `true`, passing a function in the children prop is preferred. + +The following patterns are considered warnings: + +```jsx +{data => data.value} +React.createElement(MyComponent, {}, data => data.value) +``` + +The following are **not** considered warnings: + +```jsx + data.value} /> +React.createElement(MyComponent, { children: data => data.value }) +``` diff --git a/lib/rules/no-children-prop.js b/lib/rules/no-children-prop.js index f8e341dab7..35942b687e 100644 --- a/lib/rules/no-children-prop.js +++ b/lib/rules/no-children-prop.js @@ -36,15 +36,38 @@ module.exports = { recommended: true, url: docsUrl('no-children-prop') }, - schema: [] + schema: [{ + type: 'object', + properties: { + allowFunctions: { + type: 'boolean', + default: false + } + }, + additionalProperties: false + }] }, create: function(context) { + const configuration = context.options[0] || {}; + + function isFunction(node) { + return (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') + && configuration.allowFunctions; + } + return { JSXAttribute: function(node) { if (node.name.name !== 'children') { return; } + const value = node.value; + if (value && value.type === 'JSXExpressionContainer') { + if (isFunction(value.expression)) { + return; + } + } + context.report({ node: node, message: 'Do not pass children as props. Instead, nest children between the opening and closing tags.' @@ -59,10 +82,31 @@ module.exports = { const childrenProp = props.find(prop => prop.key && prop.key.name === 'children'); if (childrenProp) { - context.report({ - node: node, - message: 'Do not pass children as props. Instead, pass them as additional arguments to React.createElement.' - }); + if (childrenProp.value && !isFunction(childrenProp.value)) { + context.report({ + node: node, + message: 'Do not pass children as props. Instead, pass them as additional arguments to React.createElement.' + }); + } + } else if (node.arguments.length === 3) { + const children = node.arguments[2]; + if (isFunction(children)) { + context.report({ + node: node, + message: 'Do not pass a function as an additional argument to React.createElement. Instead, pass it as a prop.' + }); + } + } + }, + JSXElement: function(node) { + const children = node.children; + if (children && children.length === 1 && children[0].type === 'JSXExpressionContainer') { + if (isFunction(children[0].expression)) { + context.report({ + node: node, + message: 'Do not pass nest a function between the opening and closing tags. Instead, pass it as a prop.' + }); + } } } }; diff --git a/tests/lib/rules/no-children-prop.js b/tests/lib/rules/no-children-prop.js index 33de83bfa6..fcda031f70 100644 --- a/tests/lib/rules/no-children-prop.js +++ b/tests/lib/rules/no-children-prop.js @@ -22,6 +22,8 @@ const parserOptions = { const JSX_ERROR = 'Do not pass children as props. Instead, nest children between the opening and closing tags.'; const CREATE_ELEMENT_ERROR = 'Do not pass children as props. Instead, pass them as additional arguments to React.createElement.'; +const JSX_FUNCTION_ERROR = 'Do not pass nest a function between the opening and closing tags. Instead, pass it as a prop.'; +const CREATE_ELEMENT_FUNCTION_ERROR = 'Do not pass a function as an additional argument to React.createElement. Instead, pass it as a prop.'; // ----------------------------------------------------------------------------- // Tests @@ -140,9 +142,37 @@ ruleTester.run('no-children-prop', rule, { }, { code: 'React.createElement(MyComponent, {className: "class-name", ...props});' + }, + { + code: ' {}} />;', + options: [{ + allowFunctions: true + }] + }, + { + code: ';', + options: [{ + allowFunctions: true + }] + }, + { + code: 'React.createElement(MyComponent, {children: () => {}});', + options: [{ + allowFunctions: true + }] + }, + { + code: 'React.createElement(MyComponent, {children: function() {}});', + options: [{ + allowFunctions: true + }] } ], invalid: [ + { + code: '
;', // not a valid use case but make sure we don't crash + errors: [{message: JSX_ERROR}] + }, { code: '
;', errors: [{message: JSX_ERROR}] @@ -198,6 +228,34 @@ ruleTester.run('no-children-prop', rule, { { code: 'React.createElement(MyComponent, {...props, children: "Children"})', errors: [{message: CREATE_ELEMENT_ERROR}] + }, + { + code: '{() => {}};', + options: [{ + allowFunctions: true + }], + errors: [{message: JSX_FUNCTION_ERROR}] + }, + { + code: '{function() {}};', + options: [{ + allowFunctions: true + }], + errors: [{message: JSX_FUNCTION_ERROR}] + }, + { + code: 'React.createElement(MyComponent, {}, () => {});', + options: [{ + allowFunctions: true + }], + errors: [{message: CREATE_ELEMENT_FUNCTION_ERROR}] + }, + { + code: 'React.createElement(MyComponent, {}, function() {});', + options: [{ + allowFunctions: true + }], + errors: [{message: CREATE_ELEMENT_FUNCTION_ERROR}] } ] });