Skip to content

Commit 12ba212

Browse files
committed
Add support for function
1 parent 305f2bc commit 12ba212

File tree

3 files changed

+219
-38
lines changed

3 files changed

+219
-38
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: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ const propName = require('jsx-ast-utils/propName');
1313
// Rule Definition
1414
// -----------------------------------------------------------------------------
1515

16-
const bindViolationMessage = 'JSX props should not use .bind()';
17-
const arrowViolationMessage = 'JSX props should not use arrow functions';
18-
const bindExpressionViolationMessage = 'JSX props should not use ::';
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+
};
1922

2023
module.exports = {
2124
meta: {
@@ -36,6 +39,10 @@ module.exports = {
3639
default: false,
3740
type: 'boolean'
3841
},
42+
allowFunctions: {
43+
default: false,
44+
type: 'boolean'
45+
},
3946
ignoreRefs: {
4047
default: false,
4148
type: 'boolean'
@@ -56,26 +63,32 @@ module.exports = {
5663
blockVariableNameSets[blockStart] = {
5764
arrowFunc: new Set(),
5865
bindCall: new Set(),
59-
bindExpression: new Set()
66+
bindExpression: new Set(),
67+
func: new Set()
6068
};
6169
}
6270

63-
function getVariableViolationType(rightNode) {
64-
const nodeType = rightNode.type;
71+
function getNodeViolationType(node) {
72+
const nodeType = node.type;
6573

6674
if (
6775
!configuration.allowBind &&
6876
nodeType === 'CallExpression' &&
69-
rightNode.callee.type === 'MemberExpression' &&
70-
rightNode.callee.property.type === 'Identifier' &&
71-
rightNode.callee.property.name === 'bind'
77+
node.callee.type === 'MemberExpression' &&
78+
node.callee.property.type === 'Identifier' &&
79+
node.callee.property.name === 'bind'
7280
) {
7381
return 'bindCall';
7482
} else if (
7583
!configuration.allowArrowFunctions &&
7684
nodeType === 'ArrowFunctionExpression'
7785
) {
7886
return 'arrowFunc';
87+
} else if (
88+
!configuration.allowFunctions &&
89+
nodeType === 'FunctionExpression'
90+
) {
91+
return 'func';
7992
} else if (
8093
!configuration.allowBind &&
8194
nodeType === 'BindExpression'
@@ -98,19 +111,16 @@ module.exports = {
98111

99112
function reportVariableViolation(node, name, blockStart) {
100113
const blockSets = blockVariableNameSets[blockStart];
114+
const violationTypes = Object.keys(blockSets);
101115

102-
if (blockSets.bindCall.has(name)) {
103-
context.report({node: node, message: bindViolationMessage});
104-
return true;
105-
} else if (blockSets.arrowFunc.has(name)) {
106-
context.report({node: node, message: arrowViolationMessage});
107-
return true;
108-
} else if (blockSets.bindExpression.has(name)) {
109-
context.report({node: node, message: bindExpressionViolationMessage});
110-
return true;
111-
}
116+
return violationTypes.find(type => {
117+
if (blockSets[type].has(name)) {
118+
context.report({node: node, message: violationMessageStore[type]});
119+
return true;
120+
}
112121

113-
return false;
122+
return false;
123+
});
114124
}
115125

116126
function findVariableViolation(node, name) {
@@ -126,7 +136,7 @@ module.exports = {
126136

127137
VariableDeclarator(node) {
128138
const blockAncestors = getBlockStatementAncestors(node);
129-
const variableViolationType = getVariableViolationType(node.init);
139+
const variableViolationType = getNodeViolationType(node.init);
130140

131141
if (
132142
blockAncestors.length > 0 &&
@@ -146,26 +156,14 @@ module.exports = {
146156
}
147157
const valueNode = node.value.expression;
148158
const valueNodeType = valueNode.type;
159+
const nodeViolationType = getNodeViolationType(valueNode);
149160

150161
if (valueNodeType === 'Identifier') {
151162
findVariableViolation(node, valueNode.name);
152-
} else if (
153-
!configuration.allowBind &&
154-
valueNodeType === 'CallExpression' &&
155-
valueNode.callee.type === 'MemberExpression' &&
156-
valueNode.callee.property.name === 'bind'
157-
) {
158-
context.report({node: node, message: bindViolationMessage});
159-
} else if (
160-
!configuration.allowArrowFunctions &&
161-
valueNodeType === 'ArrowFunctionExpression'
162-
) {
163-
context.report({node: node, message: arrowViolationMessage});
164-
} else if (
165-
!configuration.allowBind &&
166-
valueNodeType === 'BindExpression'
167-
) {
168-
context.report({node: node, message: bindExpressionViolationMessage});
163+
} else if (nodeViolationType) {
164+
context.report({
165+
node: node, message: violationMessageStore[nodeViolationType]
166+
});
169167
}
170168
}
171169
};

tests/lib/rules/jsx-no-bind.js

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ ruleTester.run('jsx-no-bind', rule, {
4949
code: '<div ref={this._refCallback.bind(this)}></div>',
5050
options: [{ignoreRefs: true}]
5151
},
52+
{
53+
code: '<div ref={function (c) {this._input = c}}></div>',
54+
options: [{ignoreRefs: true}]
55+
},
5256

5357
// bind() explicitly allowed
5458
{
@@ -61,6 +65,24 @@ ruleTester.run('jsx-no-bind', rule, {
6165
code: '<div onClick={() => alert("1337")}></div>',
6266
options: [{allowArrowFunctions: true}]
6367
},
68+
{
69+
code: '<div onClick={async () => alert("1337")}></div>',
70+
options: [{allowArrowFunctions: true}]
71+
},
72+
73+
// Functions explicitly allowed
74+
{
75+
code: '<div onClick={function () { alert("1337") }}></div>',
76+
options: [{allowFunctions: true}]
77+
},
78+
{
79+
code: '<div onClick={function * () { alert("1337") }}></div>',
80+
options: [{allowFunctions: true}]
81+
},
82+
{
83+
code: '<div onClick={async function () { alert("1337") }}></div>',
84+
options: [{allowFunctions: true}]
85+
},
6486

6587
// Redux connect
6688
{
@@ -370,6 +392,10 @@ ruleTester.run('jsx-no-bind', rule, {
370392
code: '<div onClick={() => alert("1337")}></div>',
371393
errors: [{message: 'JSX props should not use arrow functions'}]
372394
},
395+
{
396+
code: '<div onClick={async () => alert("1337")}></div>',
397+
errors: [{message: 'JSX props should not use arrow functions'}]
398+
},
373399
{
374400
code: '<div onClick={() => 42}></div>',
375401
errors: [{message: 'JSX props should not use arrow functions'}]
@@ -480,6 +506,154 @@ ruleTester.run('jsx-no-bind', rule, {
480506
parser: 'babel-eslint'
481507
},
482508

509+
// Functions
510+
{
511+
code: '<div onClick={function () { alert("1337") }}></div>',
512+
errors: [{message: 'JSX props should not use functions'}]
513+
},
514+
{
515+
code: '<div onClick={function * () { alert("1337") }}></div>',
516+
errors: [{message: 'JSX props should not use functions'}]
517+
},
518+
{
519+
code: '<div onClick={async function () { alert("1337") }}></div>',
520+
errors: [{message: 'JSX props should not use functions'}]
521+
},
522+
{
523+
code: '<div ref={function (c) { this._input = c }}></div>',
524+
errors: [{message: 'JSX props should not use functions'}]
525+
},
526+
{
527+
code: [
528+
'class Hello23 extends React.Component {',
529+
' renderDiv = () => {',
530+
' const click = function () { return true }',
531+
' return <div onClick={click}>Hello</div>;',
532+
' }',
533+
'};'
534+
].join('\n'),
535+
errors: [{message: 'JSX props should not use functions'}],
536+
parser: 'babel-eslint'
537+
},
538+
{
539+
code: [
540+
'class Hello23 extends React.Component {',
541+
' renderDiv = () => {',
542+
' const click = function * () { return true }',
543+
' return <div onClick={click}>Hello</div>;',
544+
' }',
545+
'};'
546+
].join('\n'),
547+
errors: [{message: 'JSX props should not use functions'}],
548+
parser: 'babel-eslint'
549+
},
550+
{
551+
code: [
552+
'class Hello23 extends React.Component {',
553+
' renderDiv = async () => {',
554+
' const click = function () { return true }',
555+
' return <div onClick={click}>Hello</div>;',
556+
' }',
557+
'};'
558+
].join('\n'),
559+
errors: [{message: 'JSX props should not use functions'}],
560+
parser: 'babel-eslint'
561+
},
562+
{
563+
code: [
564+
'class Hello23 extends React.Component {',
565+
' renderDiv = async () => {',
566+
' const click = async function () { return true }',
567+
' return <div onClick={click}>Hello</div>;',
568+
' }',
569+
'};'
570+
].join('\n'),
571+
errors: [{message: 'JSX props should not use functions'}],
572+
parser: 'babel-eslint'
573+
},
574+
{
575+
code: [
576+
'var Hello = React.createClass({',
577+
' render: function() { ',
578+
' return <div onClick={function () { return true }} />',
579+
' }',
580+
'});'
581+
].join('\n'),
582+
errors: [{message: 'JSX props should not use functions'}]
583+
},
584+
{
585+
code: [
586+
'var Hello = React.createClass({',
587+
' render: function() { ',
588+
' return <div onClick={function * () { return true }} />',
589+
' }',
590+
'});'
591+
].join('\n'),
592+
errors: [{message: 'JSX props should not use functions'}]
593+
},
594+
{
595+
code: [
596+
'var Hello = React.createClass({',
597+
' render: function() { ',
598+
' return <div onClick={async function () { return true }} />',
599+
' }',
600+
'});'
601+
].join('\n'),
602+
errors: [{message: 'JSX props should not use functions'}]
603+
},
604+
{
605+
code: [
606+
'var Hello = React.createClass({',
607+
' render: function() { ',
608+
' const doThing = function () { return true }',
609+
' return <div onClick={doThing} />',
610+
' }',
611+
'});'
612+
].join('\n'),
613+
errors: [{message: 'JSX props should not use functions'}]
614+
},
615+
{
616+
code: [
617+
'var Hello = React.createClass({',
618+
' render: function() { ',
619+
' const doThing = async function () { return true }',
620+
' return <div onClick={doThing} />',
621+
' }',
622+
'});'
623+
].join('\n'),
624+
errors: [{message: 'JSX props should not use functions'}]
625+
},
626+
{
627+
code: [
628+
'var Hello = React.createClass({',
629+
' render: function() { ',
630+
' const doThing = function * () { return true }',
631+
' return <div onClick={doThing} />',
632+
' }',
633+
'});'
634+
].join('\n'),
635+
errors: [{message: 'JSX props should not use functions'}]
636+
},
637+
{
638+
code: [
639+
'class Hello23 extends React.Component {',
640+
' renderDiv = () => {',
641+
' const click = ::this.onChange',
642+
' const renderStuff = () => {',
643+
' const click = function () { return true }',
644+
' return <div onClick={click} />',
645+
' }',
646+
' return <div onClick={click}>Hello</div>;',
647+
' }',
648+
'};'
649+
].join('\n'),
650+
errors: [
651+
{message: 'JSX props should not use functions'},
652+
{message: 'JSX props should not use ::'}
653+
],
654+
parser: 'babel-eslint'
655+
},
656+
483657
// Bind expression
484658
{
485659
code: '<div foo={::this.onChange} />',

0 commit comments

Comments
 (0)