| /** |
| * @fileoverview Enforce consistent usage of destructuring assignment of props, state, and context. |
| */ |
| |
| 'use strict'; |
| |
| const Components = require('../util/Components'); |
| const docsUrl = require('../util/docsUrl'); |
| const isAssignmentLHS = require('../util/ast').isAssignmentLHS; |
| |
| const DEFAULT_OPTION = 'always'; |
| |
| module.exports = { |
| meta: { |
| docs: { |
| description: 'Enforce consistent usage of destructuring assignment of props, state, and context', |
| category: 'Stylistic Issues', |
| recommended: false, |
| url: docsUrl('destructuring-assignment') |
| }, |
| schema: [{ |
| type: 'string', |
| enum: [ |
| 'always', |
| 'never' |
| ] |
| }, { |
| type: 'object', |
| properties: { |
| ignoreClassFields: { |
| type: 'boolean' |
| } |
| }, |
| additionalProperties: false |
| }] |
| }, |
| |
| create: Components.detect((context, components, utils) => { |
| const configuration = context.options[0] || DEFAULT_OPTION; |
| const ignoreClassFields = context.options[1] && context.options[1].ignoreClassFields === true || false; |
| |
| /** |
| * @param {ASTNode} node We expect either an ArrowFunctionExpression, |
| * FunctionDeclaration, or FunctionExpression |
| */ |
| function handleStatelessComponent(node) { |
| const destructuringProps = node.params && node.params[0] && node.params[0].type === 'ObjectPattern'; |
| const destructuringContext = node.params && node.params[1] && node.params[1].type === 'ObjectPattern'; |
| |
| if (destructuringProps && components.get(node) && configuration === 'never') { |
| context.report({ |
| node, |
| message: 'Must never use destructuring props assignment in SFC argument' |
| }); |
| } else if (destructuringContext && components.get(node) && configuration === 'never') { |
| context.report({ |
| node, |
| message: 'Must never use destructuring context assignment in SFC argument' |
| }); |
| } |
| } |
| |
| function handleSFCUsage(node) { |
| // props.aProp || context.aProp |
| const isPropUsed = (node.object.name === 'props' || node.object.name === 'context') && !isAssignmentLHS(node); |
| if (isPropUsed && configuration === 'always') { |
| context.report({ |
| node, |
| message: `Must use destructuring ${node.object.name} assignment` |
| }); |
| } |
| } |
| |
| function isInClassProperty(node) { |
| let curNode = node.parent; |
| while (curNode) { |
| if (curNode.type === 'ClassProperty') { |
| return true; |
| } |
| curNode = curNode.parent; |
| } |
| return false; |
| } |
| |
| function handleClassUsage(node) { |
| // this.props.Aprop || this.context.aProp || this.state.aState |
| const isPropUsed = ( |
| node.object.type === 'MemberExpression' && node.object.object.type === 'ThisExpression' && |
| (node.object.property.name === 'props' || node.object.property.name === 'context' || node.object.property.name === 'state') && |
| !isAssignmentLHS(node) |
| ); |
| |
| if ( |
| isPropUsed && configuration === 'always' && |
| !(ignoreClassFields && isInClassProperty(node)) |
| ) { |
| context.report({ |
| node, |
| message: `Must use destructuring ${node.object.property.name} assignment` |
| }); |
| } |
| } |
| |
| return { |
| |
| FunctionDeclaration: handleStatelessComponent, |
| |
| ArrowFunctionExpression: handleStatelessComponent, |
| |
| FunctionExpression: handleStatelessComponent, |
| |
| MemberExpression(node) { |
| const SFCComponent = components.get(context.getScope(node).block); |
| const classComponent = utils.getParentComponent(node); |
| if (SFCComponent) { |
| handleSFCUsage(node); |
| } |
| if (classComponent) { |
| handleClassUsage(node); |
| } |
| }, |
| |
| VariableDeclarator(node) { |
| const classComponent = utils.getParentComponent(node); |
| const SFCComponent = components.get(context.getScope(node).block); |
| |
| const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern'); |
| // let {foo} = props; |
| const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context'); |
| // let {foo} = this.props; |
| const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && ( |
| node.init.property.name === 'props' || node.init.property.name === 'context' || node.init.property.name === 'state' |
| ); |
| |
| if (SFCComponent && destructuringSFC && configuration === 'never') { |
| context.report({ |
| node, |
| message: `Must never use destructuring ${node.init.name} assignment` |
| }); |
| } |
| |
| if ( |
| classComponent && destructuringClass && configuration === 'never' && |
| !(ignoreClassFields && node.parent.type === 'ClassProperty') |
| ) { |
| context.report({ |
| node, |
| message: `Must never use destructuring ${node.init.property.name} assignment` |
| }); |
| } |
| } |
| }; |
| }) |
| }; |