Skip to content

[jsx-no-bind] Only warn for props and account for variable declaration #1459

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

Merged
Merged
144 changes: 104 additions & 40 deletions lib/rules/jsx-no-bind.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* @fileoverview Prevents usage of Function.prototype.bind and arrow functions
* in React component definition.
* in React component props.
* @author Daniel Lo Nigro <dan.cx>
* @author Jacky Ho
*/
'use strict';

Expand All @@ -12,10 +13,14 @@ const propName = require('jsx-ast-utils/propName');
// Rule Definition
// -----------------------------------------------------------------------------

const bindViolationMessage = 'JSX props should not use .bind()';
const arrowViolationMessage = 'JSX props should not use arrow functions';
const bindExpressionViolationMessage = 'JSX props should not use ::';

module.exports = {
meta: {
docs: {
description: 'Prevents usage of Function.prototype.bind and arrow functions in React component definition',
description: 'Prevents usage of Function.prototype.bind and arrow functions in React component props',
category: 'Best Practices',
recommended: false
},
Expand All @@ -40,33 +45,97 @@ module.exports = {
}]
},

create: Components.detect((context, components, utils) => {
create: Components.detect(context => {
const configuration = context.options[0] || {};

// Keep track of all the variable names pointing to a bind call,
// bind expression or an arrow function in different block statements
const blockVariableNameSets = {};

function setBlockVariableNameSet(blockStart) {
blockVariableNameSets[blockStart] = {
arrowFunc: new Set(),
bindCall: new Set(),
bindExpression: new Set()
};
}

function getVariableViolationType(rightNode) {
const nodeType = rightNode.type;

if (
!configuration.allowBind &&
nodeType === 'CallExpression' &&
rightNode.callee.type === 'MemberExpression' &&
rightNode.callee.property.type === 'Identifier' &&
rightNode.callee.property.name === 'bind'
) {
return 'bindCall';
} else if (
!configuration.allowArrowFunctions &&
nodeType === 'ArrowFunctionExpression'
) {
return 'arrowFunc';
} else if (
!configuration.allowBind &&
nodeType === 'BindExpression'
) {
return 'bindExpression';
}

return null;
}

function addVariableNameToSet(violationType, variableName, blockStart) {
blockVariableNameSets[blockStart][violationType].add(variableName);
}

function getBlockStatementAncestors(node) {
return context.getAncestors(node).reverse().filter(
ancestor => ancestor.type === 'BlockStatement'
);
}

function reportVariableViolation(node, name, blockStart) {
const blockSets = blockVariableNameSets[blockStart];

if (blockSets.bindCall.has(name)) {
context.report({node: node, message: bindViolationMessage});
return true;
} else if (blockSets.arrowFunc.has(name)) {
context.report({node: node, message: arrowViolationMessage});
return true;
} else if (blockSets.bindExpression.has(name)) {
context.report({node: node, message: bindExpressionViolationMessage});
return true;
}

return false;
}

function findVariableViolation(node, name) {
getBlockStatementAncestors(node).find(
block => reportVariableViolation(node, name, block.start)
);
}

return {
CallExpression: function(node) {
const callee = node.callee;
BlockStatement(node) {
setBlockVariableNameSet(node.start);
},

VariableDeclarator(node) {
const blockAncestors = getBlockStatementAncestors(node);
const variableViolationType = getVariableViolationType(node.init);

if (
!configuration.allowBind &&
(callee.type !== 'MemberExpression' || callee.property.name !== 'bind')
blockAncestors.length > 0 &&
variableViolationType &&
node.parent.kind === 'const' // only support const right now
) {
return;
}
const ancestors = context.getAncestors(callee).reverse();
Copy link
Contributor Author

@jackyho112 jackyho112 Oct 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this block of code reports any violation in a component's render method. This rule is supposed to only report violations in JSX props, so this is not needed.

for (let i = 0, j = ancestors.length; i < j; i++) {
if (
!configuration.allowBind &&
(ancestors[i].type === 'MethodDefinition' && ancestors[i].key.name === 'render') ||
(ancestors[i].type === 'Property' && ancestors[i].key.name === 'render')
) {
if (utils.isReturningJSX(ancestors[i])) {
context.report({
node: callee,
message: 'JSX props should not use .bind()'
});
}
break;
}
addVariableNameToSet(
variableViolationType, node.id.name, blockAncestors[0].start
);
}
},

Expand All @@ -76,32 +145,27 @@ module.exports = {
return;
}
const valueNode = node.value.expression;
if (
const valueNodeType = valueNode.type;

if (valueNodeType === 'Identifier') {
findVariableViolation(node, valueNode.name);
} else if (
!configuration.allowBind &&
valueNode.type === 'CallExpression' &&
valueNodeType === 'CallExpression' &&
valueNode.callee.type === 'MemberExpression' &&
valueNode.callee.property.name === 'bind'
) {
context.report({
node: node,
message: 'JSX props should not use .bind()'
});
context.report({node: node, message: bindViolationMessage});
} else if (
!configuration.allowArrowFunctions &&
valueNode.type === 'ArrowFunctionExpression'
valueNodeType === 'ArrowFunctionExpression'
) {
context.report({
node: node,
message: 'JSX props should not use arrow functions'
});
context.report({node: node, message: arrowViolationMessage});
} else if (
!configuration.allowBind &&
valueNode.type === 'BindExpression'
valueNodeType === 'BindExpression'
) {
context.report({
node: node,
message: 'JSX props should not use ::'
});
context.report({node: node, message: bindExpressionViolationMessage});
}
}
};
Expand Down
Loading