diff --git a/README.md b/README.md index 35312840e3..38d237781f 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](# * [react/boolean-prop-naming](docs/rules/boolean-prop-naming.md): Enforces consistent naming for boolean props * [react/default-props-match-prop-types](docs/rules/default-props-match-prop-types.md): Prevent extraneous defaultProps on components +* [react/destructuring-props-argument](docs/rules/destructuring-props-argument.md): Rule enforces consistent usage of destructuring assignment in component arguments * [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition * [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components * [react/forbid-elements](docs/rules/forbid-elements.md): Forbid certain elements diff --git a/docs/rules/destructuring-props-argument.md b/docs/rules/destructuring-props-argument.md new file mode 100644 index 0000000000..3e944417ec --- /dev/null +++ b/docs/rules/destructuring-props-argument.md @@ -0,0 +1,45 @@ +# Enforces consistent usage of destructuring props argument assignment(react/destructuring-props-argument) + +Rule enforces consistent usage of destructuring assignment in component arguments. + +Rule can be set to either of `always`, `never`, `ignore` for each type of the component. +Currently only SFC is implemented. + +```js +"react/destructuring-props-argument": [, { "SFC": "always"}] +``` + +## Rule Details + +By default rule is set to `always` enforce destructuring assignment. The following pattern is considered warning: + +```js +const MyComponent = (props) => { + return (
) +}; +``` + +Below pattern is correct: + +```js +const MyComponent = ({id}) => { + return (
) +}; +``` + + +If rule option is set to `never`, the following pattern is considered warning: + +```js +const MyComponent = ({id}) => { + return (
) +}; +``` + +and below pattern is correct: + +```js +const MyComponent = (props) => { + return (
) +}; +``` diff --git a/lib/rules/destructuring-props-argument.js b/lib/rules/destructuring-props-argument.js new file mode 100644 index 0000000000..87ed67e79c --- /dev/null +++ b/lib/rules/destructuring-props-argument.js @@ -0,0 +1,89 @@ +/** + * @fileoverview Rule to forbid or enforce destructuring props argument. + **/ +'use strict'; + +const Components = require('../util/Components'); + +const DEFAULT_OPTIONS = { + SFC: 'always' +}; + +module.exports = { + meta: { + docs: { + description: 'Forbid or enforce destructuring props argument', + category: 'Stylistic Issues', + recommended: false + }, + schema: [{ + definitions: { + value: { + enum: [ + 'always', + 'never', + 'ignore' + ] + } + }, + type: 'object', + properties: { + SFC: {$ref: '#/definitions/value'} + }, + additionalProperties: false + }] + }, + + create: Components.detect((context, components) => { + const configuration = context.options[0] || {}; + const options = {SFC: configuration.SFC || DEFAULT_OPTIONS.SFC}; + + + /** + * Checks if a prop is being assigned a value props.bar = 'bar' + * @param {ASTNode} node The AST node being checked. + * @returns {Boolean} + */ + + function isAssignmentToProp(node) { + return ( + node.parent && + node.parent.type === 'AssignmentExpression' && + node.parent.left === node + ); + } + /** + * @param {ASTNode} node We expect either an ArrowFunctionExpression, + * FunctionDeclaration, or FunctionExpression + */ + function handleStatelessComponent(node) { + const destructuring = node.params && node.params[0] && node.params[0].type === 'ObjectPattern'; + if (destructuring && components.get(node) && options.SFC === 'never') { + context.report({ + node: node, + message: 'Should never use destructuring props assignment in SFC argument' + }); + } + } + + return { + + FunctionDeclaration: handleStatelessComponent, + + ArrowFunctionExpression: handleStatelessComponent, + + FunctionExpression: handleStatelessComponent, + + MemberExpression: function(node) { + const relatedComponent = components.get(context.getScope(node).block); + const isStatelessFunctionUsage = node.object.name === 'props' && !isAssignmentToProp(node); + if (relatedComponent && isStatelessFunctionUsage && options.SFC === 'always') { + context.report({ + node: node, + message: 'Should use destructuring props assignment in SFC argument' + }); + } + } + }; + }) +}; diff --git a/tests/lib/rules/destructuring-props-argument.js b/tests/lib/rules/destructuring-props-argument.js new file mode 100644 index 0000000000..b2a324d4fd --- /dev/null +++ b/tests/lib/rules/destructuring-props-argument.js @@ -0,0 +1,60 @@ + +/** + * @fileoverview Rule to forbid or enforce destructuring props argument. + **/ +'use strict'; + +const rule = require('../../../lib/rules/destructuring-props-argument'); +const RuleTester = require('eslint').RuleTester; + +require('babel-eslint'); + +const parserOptions = { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true + } +}; + +const ruleTester = new RuleTester({parserOptions}); +ruleTester.run('destructuring-props-argument', rule, { + valid: [{ + code: `const MyComponent = ({ id, className }) => ( +
+ );` + }, { + code: `const MyComponent = (props) => ( +
+ );` + }, { + code: `const Foo = class extends React.PureComponent { + render() { + return
{this.props.foo}
; + } + };` + }, { + code: `class Foo extends React.Component { + doStuff() {} + render() { + return
{this.props.foo}
; + } + }` + }], + invalid: [{ + code: `const MyComponent = (props) => { + return (
) + };`, + errors: [ + {message: 'Should use destructuring props assignment in SFC argument'} + ] + }, { + code: `const MyComponent = ({ id, className }) => ( +
+ );`, + options: [{SFC: 'never'}], + errors: [ + {message: 'Should never use destructuring props assignment in SFC argument'} + ] + }] +});