Skip to content

Commit

Permalink
Add support for function
Browse files Browse the repository at this point in the history
  • Loading branch information
jackyho112 committed Oct 26, 2017
1 parent 305f2bc commit 12ba212
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 38 deletions.
9 changes: 9 additions & 0 deletions docs/rules/jsx-no-bind.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The following patterns are not considered warnings:
"react/jsx-no-bind": [<enabled>, {
"ignoreRefs": <boolean> || false,
"allowArrowFunctions": <boolean> || false,
"allowFunctions": <boolean> || false,
"allowBind": <boolean> || false
}]
```
Expand All @@ -45,6 +46,14 @@ When `true` the following is not considered a warning:
<div onClick={() => alert("1337")} />
```

### `allowFunctions`

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

```jsx
<div onClick={function () { alert("1337") }} />
```

### `allowBind`

When `true` the following is not considered a warning:
Expand Down
74 changes: 36 additions & 38 deletions lib/rules/jsx-no-bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ 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 ::';
const violationMessageStore = {
bindCall: 'JSX props should not use .bind()',
arrowFunc: 'JSX props should not use arrow functions',
bindExpression: 'JSX props should not use ::',
func: 'JSX props should not use functions'
};

module.exports = {
meta: {
Expand All @@ -36,6 +39,10 @@ module.exports = {
default: false,
type: 'boolean'
},
allowFunctions: {
default: false,
type: 'boolean'
},
ignoreRefs: {
default: false,
type: 'boolean'
Expand All @@ -56,26 +63,32 @@ module.exports = {
blockVariableNameSets[blockStart] = {
arrowFunc: new Set(),
bindCall: new Set(),
bindExpression: new Set()
bindExpression: new Set(),
func: new Set()
};
}

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

if (
!configuration.allowBind &&
nodeType === 'CallExpression' &&
rightNode.callee.type === 'MemberExpression' &&
rightNode.callee.property.type === 'Identifier' &&
rightNode.callee.property.name === 'bind'
node.callee.type === 'MemberExpression' &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'bind'
) {
return 'bindCall';
} else if (
!configuration.allowArrowFunctions &&
nodeType === 'ArrowFunctionExpression'
) {
return 'arrowFunc';
} else if (
!configuration.allowFunctions &&
nodeType === 'FunctionExpression'
) {
return 'func';
} else if (
!configuration.allowBind &&
nodeType === 'BindExpression'
Expand All @@ -98,19 +111,16 @@ module.exports = {

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

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 violationTypes.find(type => {
if (blockSets[type].has(name)) {
context.report({node: node, message: violationMessageStore[type]});
return true;
}

return false;
return false;
});
}

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

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

if (
blockAncestors.length > 0 &&
Expand All @@ -146,26 +156,14 @@ module.exports = {
}
const valueNode = node.value.expression;
const valueNodeType = valueNode.type;
const nodeViolationType = getNodeViolationType(valueNode);

if (valueNodeType === 'Identifier') {
findVariableViolation(node, valueNode.name);
} else if (
!configuration.allowBind &&
valueNodeType === 'CallExpression' &&
valueNode.callee.type === 'MemberExpression' &&
valueNode.callee.property.name === 'bind'
) {
context.report({node: node, message: bindViolationMessage});
} else if (
!configuration.allowArrowFunctions &&
valueNodeType === 'ArrowFunctionExpression'
) {
context.report({node: node, message: arrowViolationMessage});
} else if (
!configuration.allowBind &&
valueNodeType === 'BindExpression'
) {
context.report({node: node, message: bindExpressionViolationMessage});
} else if (nodeViolationType) {
context.report({
node: node, message: violationMessageStore[nodeViolationType]
});
}
}
};
Expand Down
174 changes: 174 additions & 0 deletions tests/lib/rules/jsx-no-bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ ruleTester.run('jsx-no-bind', rule, {
code: '<div ref={this._refCallback.bind(this)}></div>',
options: [{ignoreRefs: true}]
},
{
code: '<div ref={function (c) {this._input = c}}></div>',
options: [{ignoreRefs: true}]
},

// bind() explicitly allowed
{
Expand All @@ -61,6 +65,24 @@ ruleTester.run('jsx-no-bind', rule, {
code: '<div onClick={() => alert("1337")}></div>',
options: [{allowArrowFunctions: true}]
},
{
code: '<div onClick={async () => alert("1337")}></div>',
options: [{allowArrowFunctions: true}]
},

// Functions explicitly allowed
{
code: '<div onClick={function () { alert("1337") }}></div>',
options: [{allowFunctions: true}]
},
{
code: '<div onClick={function * () { alert("1337") }}></div>',
options: [{allowFunctions: true}]
},
{
code: '<div onClick={async function () { alert("1337") }}></div>',
options: [{allowFunctions: true}]
},

// Redux connect
{
Expand Down Expand Up @@ -370,6 +392,10 @@ ruleTester.run('jsx-no-bind', rule, {
code: '<div onClick={() => alert("1337")}></div>',
errors: [{message: 'JSX props should not use arrow functions'}]
},
{
code: '<div onClick={async () => alert("1337")}></div>',
errors: [{message: 'JSX props should not use arrow functions'}]
},
{
code: '<div onClick={() => 42}></div>',
errors: [{message: 'JSX props should not use arrow functions'}]
Expand Down Expand Up @@ -480,6 +506,154 @@ ruleTester.run('jsx-no-bind', rule, {
parser: 'babel-eslint'
},

// Functions
{
code: '<div onClick={function () { alert("1337") }}></div>',
errors: [{message: 'JSX props should not use functions'}]
},
{
code: '<div onClick={function * () { alert("1337") }}></div>',
errors: [{message: 'JSX props should not use functions'}]
},
{
code: '<div onClick={async function () { alert("1337") }}></div>',
errors: [{message: 'JSX props should not use functions'}]
},
{
code: '<div ref={function (c) { this._input = c }}></div>',
errors: [{message: 'JSX props should not use functions'}]
},
{
code: [
'class Hello23 extends React.Component {',
' renderDiv = () => {',
' const click = function () { return true }',
' return <div onClick={click}>Hello</div>;',
' }',
'};'
].join('\n'),
errors: [{message: 'JSX props should not use functions'}],
parser: 'babel-eslint'
},
{
code: [
'class Hello23 extends React.Component {',
' renderDiv = () => {',
' const click = function * () { return true }',
' return <div onClick={click}>Hello</div>;',
' }',
'};'
].join('\n'),
errors: [{message: 'JSX props should not use functions'}],
parser: 'babel-eslint'
},
{
code: [
'class Hello23 extends React.Component {',
' renderDiv = async () => {',
' const click = function () { return true }',
' return <div onClick={click}>Hello</div>;',
' }',
'};'
].join('\n'),
errors: [{message: 'JSX props should not use functions'}],
parser: 'babel-eslint'
},
{
code: [
'class Hello23 extends React.Component {',
' renderDiv = async () => {',
' const click = async function () { return true }',
' return <div onClick={click}>Hello</div>;',
' }',
'};'
].join('\n'),
errors: [{message: 'JSX props should not use functions'}],
parser: 'babel-eslint'
},
{
code: [
'var Hello = React.createClass({',
' render: function() { ',
' return <div onClick={function () { return true }} />',
' }',
'});'
].join('\n'),
errors: [{message: 'JSX props should not use functions'}]
},
{
code: [
'var Hello = React.createClass({',
' render: function() { ',
' return <div onClick={function * () { return true }} />',
' }',
'});'
].join('\n'),
errors: [{message: 'JSX props should not use functions'}]
},
{
code: [
'var Hello = React.createClass({',
' render: function() { ',
' return <div onClick={async function () { return true }} />',
' }',
'});'
].join('\n'),
errors: [{message: 'JSX props should not use functions'}]
},
{
code: [
'var Hello = React.createClass({',
' render: function() { ',
' const doThing = function () { return true }',
' return <div onClick={doThing} />',
' }',
'});'
].join('\n'),
errors: [{message: 'JSX props should not use functions'}]
},
{
code: [
'var Hello = React.createClass({',
' render: function() { ',
' const doThing = async function () { return true }',
' return <div onClick={doThing} />',
' }',
'});'
].join('\n'),
errors: [{message: 'JSX props should not use functions'}]
},
{
code: [
'var Hello = React.createClass({',
' render: function() { ',
' const doThing = function * () { return true }',
' return <div onClick={doThing} />',
' }',
'});'
].join('\n'),
errors: [{message: 'JSX props should not use functions'}]
},
{
code: [
'class Hello23 extends React.Component {',
' renderDiv = () => {',
' const click = ::this.onChange',
' const renderStuff = () => {',
' const click = function () { return true }',
' return <div onClick={click} />',
' }',
' return <div onClick={click}>Hello</div>;',
' }',
'};'
].join('\n'),
errors: [
{message: 'JSX props should not use functions'},
{message: 'JSX props should not use ::'}
],
parser: 'babel-eslint'
},

// Bind expression
{
code: '<div foo={::this.onChange} />',
Expand Down

0 comments on commit 12ba212

Please sign in to comment.