blob: 02999116a253539d4d13993641cdca0ef3fed8c9 [file] [log] [blame]
/**
* @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`
});
}
}
};
})
};