Skip to content

Commit fcf580b

Browse files
authored
Merge pull request #1459 from jackyho112/make-jsx-no-bind-only-warn-for-props
[jsx-no-bind] Only warn for props and account for variable declaration
2 parents da9cbe9 + 12ba212 commit fcf580b

File tree

3 files changed

+657
-102
lines changed

3 files changed

+657
-102
lines changed

docs/rules/jsx-no-bind.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ The following patterns are **not** considered warnings:
2424
"react/jsx-no-bind": [<enabled>, {
2525
"ignoreRefs": <boolean> || false,
2626
"allowArrowFunctions": <boolean> || false,
27+
"allowFunctions": <boolean> || false,
2728
"allowBind": <boolean> || false
2829
}]
2930
```
@@ -45,6 +46,14 @@ When `true` the following is **not** considered a warning:
4546
<div onClick={() => alert("1337")} />
4647
```
4748

49+
### `allowFunctions`
50+
51+
When `true` the following is not considered a warning:
52+
53+
```jsx
54+
<div onClick={function () { alert("1337") }} />
55+
```
56+
4857
### `allowBind`
4958

5059
When `true` the following is **not** considered a warning:

lib/rules/jsx-no-bind.js

Lines changed: 110 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/**
22
* @fileoverview Prevents usage of Function.prototype.bind and arrow functions
3-
* in React component definition.
3+
* in React component props.
44
* @author Daniel Lo Nigro <dan.cx>
5+
* @author Jacky Ho
56
*/
67
'use strict';
78

@@ -12,10 +13,17 @@ const propName = require('jsx-ast-utils/propName');
1213
// Rule Definition
1314
// -----------------------------------------------------------------------------
1415

16+
const violationMessageStore = {
17+
bindCall: 'JSX props should not use .bind()',
18+
arrowFunc: 'JSX props should not use arrow functions',
19+
bindExpression: 'JSX props should not use ::',
20+
func: 'JSX props should not use functions'
21+
};
22+
1523
module.exports = {
1624
meta: {
1725
docs: {
18-
description: 'Prevents usage of Function.prototype.bind and arrow functions in React component definition',
26+
description: 'Prevents usage of Function.prototype.bind and arrow functions in React component props',
1927
category: 'Best Practices',
2028
recommended: false
2129
},
@@ -31,6 +39,10 @@ module.exports = {
3139
default: false,
3240
type: 'boolean'
3341
},
42+
allowFunctions: {
43+
default: false,
44+
type: 'boolean'
45+
},
3446
ignoreRefs: {
3547
default: false,
3648
type: 'boolean'
@@ -40,33 +52,100 @@ module.exports = {
4052
}]
4153
},
4254

43-
create: Components.detect((context, components, utils) => {
55+
create: Components.detect(context => {
4456
const configuration = context.options[0] || {};
4557

58+
// Keep track of all the variable names pointing to a bind call,
59+
// bind expression or an arrow function in different block statements
60+
const blockVariableNameSets = {};
61+
62+
function setBlockVariableNameSet(blockStart) {
63+
blockVariableNameSets[blockStart] = {
64+
arrowFunc: new Set(),
65+
bindCall: new Set(),
66+
bindExpression: new Set(),
67+
func: new Set()
68+
};
69+
}
70+
71+
function getNodeViolationType(node) {
72+
const nodeType = node.type;
73+
74+
if (
75+
!configuration.allowBind &&
76+
nodeType === 'CallExpression' &&
77+
node.callee.type === 'MemberExpression' &&
78+
node.callee.property.type === 'Identifier' &&
79+
node.callee.property.name === 'bind'
80+
) {
81+
return 'bindCall';
82+
} else if (
83+
!configuration.allowArrowFunctions &&
84+
nodeType === 'ArrowFunctionExpression'
85+
) {
86+
return 'arrowFunc';
87+
} else if (
88+
!configuration.allowFunctions &&
89+
nodeType === 'FunctionExpression'
90+
) {
91+
return 'func';
92+
} else if (
93+
!configuration.allowBind &&
94+
nodeType === 'BindExpression'
95+
) {
96+
return 'bindExpression';
97+
}
98+
99+
return null;
100+
}
101+
102+
function addVariableNameToSet(violationType, variableName, blockStart) {
103+
blockVariableNameSets[blockStart][violationType].add(variableName);
104+
}
105+
106+
function getBlockStatementAncestors(node) {
107+
return context.getAncestors(node).reverse().filter(
108+
ancestor => ancestor.type === 'BlockStatement'
109+
);
110+
}
111+
112+
function reportVariableViolation(node, name, blockStart) {
113+
const blockSets = blockVariableNameSets[blockStart];
114+
const violationTypes = Object.keys(blockSets);
115+
116+
return violationTypes.find(type => {
117+
if (blockSets[type].has(name)) {
118+
context.report({node: node, message: violationMessageStore[type]});
119+
return true;
120+
}
121+
122+
return false;
123+
});
124+
}
125+
126+
function findVariableViolation(node, name) {
127+
getBlockStatementAncestors(node).find(
128+
block => reportVariableViolation(node, name, block.start)
129+
);
130+
}
131+
46132
return {
47-
CallExpression: function(node) {
48-
const callee = node.callee;
133+
BlockStatement(node) {
134+
setBlockVariableNameSet(node.start);
135+
},
136+
137+
VariableDeclarator(node) {
138+
const blockAncestors = getBlockStatementAncestors(node);
139+
const variableViolationType = getNodeViolationType(node.init);
140+
49141
if (
50-
!configuration.allowBind &&
51-
(callee.type !== 'MemberExpression' || callee.property.name !== 'bind')
142+
blockAncestors.length > 0 &&
143+
variableViolationType &&
144+
node.parent.kind === 'const' // only support const right now
52145
) {
53-
return;
54-
}
55-
const ancestors = context.getAncestors(callee).reverse();
56-
for (let i = 0, j = ancestors.length; i < j; i++) {
57-
if (
58-
!configuration.allowBind &&
59-
(ancestors[i].type === 'MethodDefinition' && ancestors[i].key.name === 'render') ||
60-
(ancestors[i].type === 'Property' && ancestors[i].key.name === 'render')
61-
) {
62-
if (utils.isReturningJSX(ancestors[i])) {
63-
context.report({
64-
node: callee,
65-
message: 'JSX props should not use .bind()'
66-
});
67-
}
68-
break;
69-
}
146+
addVariableNameToSet(
147+
variableViolationType, node.id.name, blockAncestors[0].start
148+
);
70149
}
71150
},
72151

@@ -76,31 +155,14 @@ module.exports = {
76155
return;
77156
}
78157
const valueNode = node.value.expression;
79-
if (
80-
!configuration.allowBind &&
81-
valueNode.type === 'CallExpression' &&
82-
valueNode.callee.type === 'MemberExpression' &&
83-
valueNode.callee.property.name === 'bind'
84-
) {
85-
context.report({
86-
node: node,
87-
message: 'JSX props should not use .bind()'
88-
});
89-
} else if (
90-
!configuration.allowArrowFunctions &&
91-
valueNode.type === 'ArrowFunctionExpression'
92-
) {
93-
context.report({
94-
node: node,
95-
message: 'JSX props should not use arrow functions'
96-
});
97-
} else if (
98-
!configuration.allowBind &&
99-
valueNode.type === 'BindExpression'
100-
) {
158+
const valueNodeType = valueNode.type;
159+
const nodeViolationType = getNodeViolationType(valueNode);
160+
161+
if (valueNodeType === 'Identifier') {
162+
findVariableViolation(node, valueNode.name);
163+
} else if (nodeViolationType) {
101164
context.report({
102-
node: node,
103-
message: 'JSX props should not use ::'
165+
node: node, message: violationMessageStore[nodeViolationType]
104166
});
105167
}
106168
}

0 commit comments

Comments
 (0)