From a1448aae99c5906c4e32efb295ac8ad16c4c0327 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Sun, 1 Oct 2017 20:57:36 -0700 Subject: [PATCH 01/11] Make jsx-no-bind only warn for props --- lib/rules/jsx-no-bind.js | 118 +++++++++++++------- tests/lib/rules/jsx-no-bind.js | 193 +++++++++++++++++++++++++++++++-- 2 files changed, 262 insertions(+), 49 deletions(-) diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js index e413e92e48..a4c3c01f50 100644 --- a/lib/rules/jsx-no-bind.js +++ b/lib/rules/jsx-no-bind.js @@ -1,7 +1,7 @@ /** * @fileoverview Prevents usage of Function.prototype.bind and arrow functions - * in React component definition. - * @author Daniel Lo Nigro + * in React component props. + * @author Daniel Lo Nigro , Jacky Ho */ 'use strict'; @@ -12,10 +12,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 }, @@ -40,33 +44,72 @@ module.exports = { }] }, - create: Components.detect((context, components, utils) => { + create: Components.detect(context => { const configuration = context.options[0] || {}; + // This contains the variable names that are defined in the current class + // method and reference a `bind` call expression or an arrow function. This + // needs to be set to an empty Set after each class method defintion. + let currentArrowViolationVariableNames = new Set(); + let currentBindViolationVariableNames = new Set(); + let currentBindExpressionViolationVariableNames = new Set(); + + function resetVariableNameSets() { + currentArrowViolationVariableNames = new Set(); + currentBindViolationVariableNames = new Set(); + currentBindExpressionViolationVariableNames = new Set(); + } + + function reportVariableViolation(node, name) { + if (currentBindViolationVariableNames.has(name)) { + context.report({node: node, message: bindViolationMessage}); + } else if (currentArrowViolationVariableNames.has(name)) { + context.report({node: node, message: arrowViolationMessage}); + } else if ( + currentBindExpressionViolationVariableNames.has(name) + ) { + context.report({node: node, message: BindExpressionViolationMessage}); + } + } + return { - CallExpression: function(node) { - const callee = node.callee; + 'MethodDefinition:exit'() { + resetVariableNameSets(); + }, + + 'ClassProperty:exit'(node) { if ( - !configuration.allowBind && - (callee.type !== 'MemberExpression' || callee.property.name !== 'bind') + !node.static && + node.value && + node.value.type === 'ArrowFunctionExpression' ) { - return; + resetVariableNameSets(); } - const ancestors = context.getAncestors(callee).reverse(); - 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; - } + }, + + VariableDeclarator(node) { + const name = node.id.name; + const init = node.init; + const initType = init.type; + + if ( + !configuration.allowBind && + initType === 'CallExpression' && + init.callee.type === 'MemberExpression' && + init.callee.property.type === 'Identifier' && + init.callee.property.name === 'bind' + ) { + currentBindViolationVariableNames.add(name); + } else if ( + !configuration.allowArrowFunctions && + initType === 'ArrowFunctionExpression' + ) { + currentArrowViolationVariableNames.add(name); + } else if ( + !configuration.allowBind && + initType === 'BindExpression' + ) { + currentBindExpressionViolationVariableNames.add(name); } }, @@ -76,32 +119,27 @@ module.exports = { return; } const valueNode = node.value.expression; - if ( + const valueNodeType = valueNode.type; + + if (valueNodeType === 'Identifier') { + reportVariableViolation(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}); } } }; diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js index 8296f5e45d..21e0fb81b7 100644 --- a/tests/lib/rules/jsx-no-bind.js +++ b/tests/lib/rules/jsx-no-bind.js @@ -82,6 +82,7 @@ ruleTester.run('jsx-no-bind', rule, { options: [{allowBind: true}], parser: 'babel-eslint' }, + // Backbone view with a bind { code: [ @@ -115,6 +116,110 @@ ruleTester.run('jsx-no-bind', rule, { '};' ].join('\n'), parser: 'babel-eslint' + }, + + { + code: [ + 'class Hello extends Component {', + ' render() {', + ' const click = this.onTap.bind(this);', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'class Hello extends Component {', + ' render() {', + ' return (
{', + ' this.props.list.map(this.wrap.bind(this, "span"))', + ' }
);', + ' }', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'class Hello extends Component {', + ' render() {', + ' const click = () => true;', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'class Hello extends Component {', + ' render() {', + ' return (
{', + ' this.props.list.map(item => )', + ' }
);', + ' }', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'class Hello extends Component {', + ' render() {', + ' const click = this.bar::baz', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'class Hello extends Component {', + ' render() {', + ' return (
{', + ' this.props.list.map(this.bar::baz)', + ' }
);', + ' }', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' return (
{', + ' this.props.list.map(this.wrap.bind(this, "span"))', + ' }
);', + ' }', + '});' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' const click = this.bar::baz', + ' return
Hello
;', + ' }', + '});' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' const click = () => true', + ' return
Hello
;', + ' }', + '});' + ].join('\n'), + parser: 'babel-eslint' } ], @@ -166,10 +271,10 @@ ruleTester.run('jsx-no-bind', rule, { }, { code: [ - 'const foo = {', - ' render: function() {', - ' const click = this.onTap.bind(this);', - ' return
Hello
;', + 'class Hello23 extends React.Component {', + ' renderDiv() {', + ' const click = this.doSomething.bind(this, "no")', + ' return
Hello
;', ' }', '};' ].join('\n'), @@ -189,12 +294,23 @@ ruleTester.run('jsx-no-bind', rule, { }, { code: [ - 'const foo = {', - ' render() {', - ' const click = this.onTap.bind(this);', - ' return
Hello
;', + 'var Hello = React.createClass({', + ' render: function() { ', + ' return
', ' }', - '};' + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use .bind()'}], + parser: 'babel-eslint' + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' const doThing = this.doSomething.bind(this, "hey")', + ' return
', + ' }', + '});' ].join('\n'), errors: [{message: 'JSX props should not use .bind()'}], parser: 'babel-eslint' @@ -221,6 +337,41 @@ ruleTester.run('jsx-no-bind', rule, { errors: [{message: 'JSX props should not use arrow functions'}], parser: 'babel-eslint' }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv = () => {', + ' const click = () => true', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [{message: 'JSX props should not use arrow functions'}], + parser: 'babel-eslint' + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' return
true} />', + ' }', + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use arrow functions'}], + parser: 'babel-eslint' + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' const doThing = () => true', + ' return
', + ' }', + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use arrow functions'}], + parser: 'babel-eslint' + }, // Bind expression { @@ -237,6 +388,30 @@ ruleTester.run('jsx-no-bind', rule, { code: '
', errors: [{message: 'JSX props should not use ::'}], parser: 'babel-eslint' + }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv() {', + ' const click = ::this.onChange', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [{message: 'JSX props should not use ::'}], + parser: 'babel-eslint' + }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv() {', + ' const click = this.bar::baz', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [{message: 'JSX props should not use ::'}], + parser: 'babel-eslint' } ] }); From 129222ef95665d285a85ea353eb0b642966af6d8 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 2 Oct 2017 22:12:56 -0700 Subject: [PATCH 02/11] Add some minor changes --- lib/rules/jsx-no-bind.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js index a4c3c01f50..ab6dd7a196 100644 --- a/lib/rules/jsx-no-bind.js +++ b/lib/rules/jsx-no-bind.js @@ -1,7 +1,8 @@ /** * @fileoverview Prevents usage of Function.prototype.bind and arrow functions * in React component props. - * @author Daniel Lo Nigro , Jacky Ho + * @author Daniel Lo Nigro + * @author Jacky Ho */ 'use strict'; @@ -14,7 +15,7 @@ const propName = require('jsx-ast-utils/propName'); 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 bindExpressionViolationMessage = 'JSX props should not use ::'; module.exports = { meta: { @@ -54,7 +55,7 @@ module.exports = { let currentBindViolationVariableNames = new Set(); let currentBindExpressionViolationVariableNames = new Set(); - function resetVariableNameSets() { + function resetViolationVariableNameSets() { currentArrowViolationVariableNames = new Set(); currentBindViolationVariableNames = new Set(); currentBindExpressionViolationVariableNames = new Set(); @@ -68,13 +69,13 @@ module.exports = { } else if ( currentBindExpressionViolationVariableNames.has(name) ) { - context.report({node: node, message: BindExpressionViolationMessage}); + context.report({node: node, message: bindExpressionViolationMessage}); } } return { 'MethodDefinition:exit'() { - resetVariableNameSets(); + resetViolationVariableNameSets(); }, 'ClassProperty:exit'(node) { @@ -83,7 +84,7 @@ module.exports = { node.value && node.value.type === 'ArrowFunctionExpression' ) { - resetVariableNameSets(); + resetViolationVariableNameSets(); } }, @@ -139,7 +140,7 @@ module.exports = { !configuration.allowBind && valueNodeType === 'BindExpression' ) { - context.report({node: node, message: BindExpressionViolationMessage}); + context.report({node: node, message: bindExpressionViolationMessage}); } } }; From 2f4a20ba1b7dfefa4f16fdc295d01014c5e838e5 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Wed, 4 Oct 2017 11:36:05 -0700 Subject: [PATCH 03/11] Add back a test --- tests/lib/rules/jsx-no-bind.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js index 7b6a43176a..b4da4ebd48 100644 --- a/tests/lib/rules/jsx-no-bind.js +++ b/tests/lib/rules/jsx-no-bind.js @@ -269,6 +269,18 @@ ruleTester.run('jsx-no-bind', rule, { errors: [{message: 'JSX props should not use .bind()'}], parser: 'babel-eslint' }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv() {', + ' const click = this.doSomething.bind(this, "no")', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [{message: 'JSX props should not use .bind()'}], + parser: 'babel-eslint' + }, { code: ` const foo = { From 6db7b37fa63f39ab31233434badfe10b7716f627 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Thu, 5 Oct 2017 10:52:36 -0700 Subject: [PATCH 04/11] Better variable names --- lib/rules/jsx-no-bind.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js index ab6dd7a196..417fd17b4a 100644 --- a/lib/rules/jsx-no-bind.js +++ b/lib/rules/jsx-no-bind.js @@ -48,26 +48,27 @@ module.exports = { create: Components.detect(context => { const configuration = context.options[0] || {}; - // This contains the variable names that are defined in the current class - // method and reference a `bind` call expression or an arrow function. This - // needs to be set to an empty Set after each class method defintion. - let currentArrowViolationVariableNames = new Set(); - let currentBindViolationVariableNames = new Set(); - let currentBindExpressionViolationVariableNames = new Set(); + // These contain the variable names that are defined in the current class + // method and reference a `bind` call expression, a 'bind' expression or + // an arrow function. This needs to be set to an empty Set after each + // class method defintion. + let currentArrowFuncVariableNames = new Set(); + let currentBindCallVariableNames = new Set(); + let currentBindExpressionVariableNames = new Set(); function resetViolationVariableNameSets() { - currentArrowViolationVariableNames = new Set(); - currentBindViolationVariableNames = new Set(); - currentBindExpressionViolationVariableNames = new Set(); + currentArrowFuncVariableNames = new Set(); + currentBindCallVariableNames = new Set(); + currentBindExpressionVariableNames = new Set(); } function reportVariableViolation(node, name) { - if (currentBindViolationVariableNames.has(name)) { + if (currentBindCallVariableNames.has(name)) { context.report({node: node, message: bindViolationMessage}); - } else if (currentArrowViolationVariableNames.has(name)) { + } else if (currentArrowFuncVariableNames.has(name)) { context.report({node: node, message: arrowViolationMessage}); } else if ( - currentBindExpressionViolationVariableNames.has(name) + currentBindExpressionVariableNames.has(name) ) { context.report({node: node, message: bindExpressionViolationMessage}); } @@ -100,17 +101,17 @@ module.exports = { init.callee.property.type === 'Identifier' && init.callee.property.name === 'bind' ) { - currentBindViolationVariableNames.add(name); + currentBindCallVariableNames.add(name); } else if ( !configuration.allowArrowFunctions && initType === 'ArrowFunctionExpression' ) { - currentArrowViolationVariableNames.add(name); + currentArrowFuncVariableNames.add(name); } else if ( !configuration.allowBind && initType === 'BindExpression' ) { - currentBindExpressionViolationVariableNames.add(name); + currentBindExpressionVariableNames.add(name); } }, From 3b094efbd53f86403c289eff2276e3015aca5541 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Sat, 14 Oct 2017 13:44:14 -0700 Subject: [PATCH 05/11] Account for variable assignments --- lib/rules/jsx-no-bind.js | 69 ++++++++++++++++++++++------------ tests/lib/rules/jsx-no-bind.js | 47 +++++++++++++++++++++++ 2 files changed, 93 insertions(+), 23 deletions(-) diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js index 417fd17b4a..5e3bf9491e 100644 --- a/lib/rules/jsx-no-bind.js +++ b/lib/rules/jsx-no-bind.js @@ -50,8 +50,9 @@ module.exports = { // These contain the variable names that are defined in the current class // method and reference a `bind` call expression, a 'bind' expression or - // an arrow function. This needs to be set to an empty Set after each - // class method defintion. + // an arrow function. This needs to be set to an empty Set before and + // after each class method defintion. This implementation will produce + // some false negatives related to scope in some rare use cases. let currentArrowFuncVariableNames = new Set(); let currentBindCallVariableNames = new Set(); let currentBindExpressionVariableNames = new Set(); @@ -62,6 +63,30 @@ module.exports = { currentBindExpressionVariableNames = new Set(); } + function findVariableViolation(rightNode, variableName) { + const nodeType = rightNode.type; + + if ( + !configuration.allowBind && + nodeType === 'CallExpression' && + rightNode.callee.type === 'MemberExpression' && + rightNode.callee.property.type === 'Identifier' && + rightNode.callee.property.name === 'bind' + ) { + currentBindCallVariableNames.add(variableName); + } else if ( + !configuration.allowArrowFunctions && + nodeType === 'ArrowFunctionExpression' + ) { + currentArrowFuncVariableNames.add(variableName); + } else if ( + !configuration.allowBind && + nodeType === 'BindExpression' + ) { + currentBindExpressionVariableNames.add(variableName); + } + } + function reportVariableViolation(node, name) { if (currentBindCallVariableNames.has(name)) { context.report({node: node, message: bindViolationMessage}); @@ -75,6 +100,20 @@ module.exports = { } return { + MethodDefinition() { + resetViolationVariableNameSets(); + }, + + ClassProperty(node) { + if ( + !node.static && + node.value && + node.value.type === 'ArrowFunctionExpression' + ) { + resetViolationVariableNameSets(); + } + }, + 'MethodDefinition:exit'() { resetViolationVariableNameSets(); }, @@ -90,28 +129,12 @@ module.exports = { }, VariableDeclarator(node) { - const name = node.id.name; - const init = node.init; - const initType = init.type; + findVariableViolation(node.init, node.id.name); + }, - if ( - !configuration.allowBind && - initType === 'CallExpression' && - init.callee.type === 'MemberExpression' && - init.callee.property.type === 'Identifier' && - init.callee.property.name === 'bind' - ) { - currentBindCallVariableNames.add(name); - } else if ( - !configuration.allowArrowFunctions && - initType === 'ArrowFunctionExpression' - ) { - currentArrowFuncVariableNames.add(name); - } else if ( - !configuration.allowBind && - initType === 'BindExpression' - ) { - currentBindExpressionVariableNames.add(name); + AssignmentExpression(node) { + if (node.left.type === 'Identifier') { + findVariableViolation(node.right, node.left.name); } }, diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js index b4da4ebd48..9019b463de 100644 --- a/tests/lib/rules/jsx-no-bind.js +++ b/tests/lib/rules/jsx-no-bind.js @@ -129,6 +129,17 @@ ruleTester.run('jsx-no-bind', rule, { ].join('\n'), parser: 'babel-eslint' }, + { + code: [ + 'class Hello extends Component {', + ' render() {', + ' foo.onClick = this.onTap.bind(this);', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, { code: [ 'class Hello extends Component {', @@ -315,6 +326,18 @@ ruleTester.run('jsx-no-bind', rule, { errors: [{message: 'JSX props should not use .bind()'}], parser: 'babel-eslint' }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' doThing = this.doSomething.bind(this, "hey")', + ' return
', + ' }', + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use .bind()'}], + parser: 'babel-eslint' + }, // Arrow functions { @@ -372,6 +395,18 @@ ruleTester.run('jsx-no-bind', rule, { errors: [{message: 'JSX props should not use arrow functions'}], parser: 'babel-eslint' }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' doThing = () => true', + ' return
', + ' }', + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use arrow functions'}], + parser: 'babel-eslint' + }, // Bind expression { @@ -412,6 +447,18 @@ ruleTester.run('jsx-no-bind', rule, { ].join('\n'), errors: [{message: 'JSX props should not use ::'}], parser: 'babel-eslint' + }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv() {', + ' click = this.bar::baz', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [{message: 'JSX props should not use ::'}], + parser: 'babel-eslint' } ] }); From ffd1c718c0bbc151a9473b2c9e5e02eceba00209 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Sun, 15 Oct 2017 14:48:09 -0700 Subject: [PATCH 06/11] Change pattern --- lib/rules/jsx-no-bind.js | 107 ++++++++++++++++----------------- tests/lib/rules/jsx-no-bind.js | 46 +++++++++----- 2 files changed, 84 insertions(+), 69 deletions(-) diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js index 5e3bf9491e..935449b10b 100644 --- a/lib/rules/jsx-no-bind.js +++ b/lib/rules/jsx-no-bind.js @@ -48,22 +48,19 @@ module.exports = { create: Components.detect(context => { const configuration = context.options[0] || {}; - // These contain the variable names that are defined in the current class - // method and reference a `bind` call expression, a 'bind' expression or - // an arrow function. This needs to be set to an empty Set before and - // after each class method defintion. This implementation will produce - // some false negatives related to scope in some rare use cases. - let currentArrowFuncVariableNames = new Set(); - let currentBindCallVariableNames = new Set(); - let currentBindExpressionVariableNames = new Set(); - - function resetViolationVariableNameSets() { - currentArrowFuncVariableNames = new Set(); - currentBindCallVariableNames = new Set(); - currentBindExpressionVariableNames = new Set(); + // 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 findVariableViolation(rightNode, variableName) { + function getVariableViolationType(rightNode) { const nodeType = rightNode.type; if ( @@ -73,68 +70,68 @@ module.exports = { rightNode.callee.property.type === 'Identifier' && rightNode.callee.property.name === 'bind' ) { - currentBindCallVariableNames.add(variableName); + return 'bindCall'; } else if ( !configuration.allowArrowFunctions && nodeType === 'ArrowFunctionExpression' ) { - currentArrowFuncVariableNames.add(variableName); + return 'arrowFunc'; } else if ( !configuration.allowBind && nodeType === 'BindExpression' ) { - currentBindExpressionVariableNames.add(variableName); + return 'bindExpression'; } + + return null; + } + + function addVariableNameToSet(violationType, variableName, blockStart) { + blockVariableNameSets[blockStart][violationType].add(variableName); } - function reportVariableViolation(node, name) { - if (currentBindCallVariableNames.has(name)) { + 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}); - } else if (currentArrowFuncVariableNames.has(name)) { + return true; + } else if (blockSets.arrowFunc.has(name)) { context.report({node: node, message: arrowViolationMessage}); - } else if ( - currentBindExpressionVariableNames.has(name) - ) { + return true; + } else if (blockSets.bindExpression.has(name)) { context.report({node: node, message: bindExpressionViolationMessage}); + return true; } - } - - return { - MethodDefinition() { - resetViolationVariableNameSets(); - }, - ClassProperty(node) { - if ( - !node.static && - node.value && - node.value.type === 'ArrowFunctionExpression' - ) { - resetViolationVariableNameSets(); - } - }, + return false; + } - 'MethodDefinition:exit'() { - resetViolationVariableNameSets(); - }, + function findVariableViolation(node, name) { + getBlockStatementAncestors(node).find( + block => reportVariableViolation(node, name, block.start) + ); + } - 'ClassProperty:exit'(node) { - if ( - !node.static && - node.value && - node.value.type === 'ArrowFunctionExpression' - ) { - resetViolationVariableNameSets(); - } + return { + BlockStatement(node) { + setBlockVariableNameSet(node.start); }, VariableDeclarator(node) { - findVariableViolation(node.init, node.id.name); - }, + const blockAncestors = getBlockStatementAncestors(node); + const variableViolationType = getVariableViolationType(node.init); - AssignmentExpression(node) { - if (node.left.type === 'Identifier') { - findVariableViolation(node.right, node.left.name); + if (blockAncestors.length > 0 && variableViolationType) { + addVariableNameToSet( + variableViolationType, node.id.name, blockAncestors[0].start + ); } }, @@ -147,7 +144,7 @@ module.exports = { const valueNodeType = valueNode.type; if (valueNodeType === 'Identifier') { - reportVariableViolation(node, valueNode.name); + findVariableViolation(node, valueNode.name); } else if ( !configuration.allowBind && valueNodeType === 'CallExpression' && diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js index 9019b463de..9622461c55 100644 --- a/tests/lib/rules/jsx-no-bind.js +++ b/tests/lib/rules/jsx-no-bind.js @@ -328,14 +328,21 @@ ruleTester.run('jsx-no-bind', rule, { }, { code: [ - 'var Hello = React.createClass({', - ' render: function() { ', - ' doThing = this.doSomething.bind(this, "hey")', - ' return
', + 'class Hello23 extends React.Component {', + ' renderDiv = () => {', + ' const click = () => true', + ' const renderStuff = () => {', + ' const click = this.doSomething.bind(this, "hey")', + ' return
', + ' }', + ' return
Hello
;', ' }', - '});' + '};' ].join('\n'), - errors: [{message: 'JSX props should not use .bind()'}], + errors: [ + {message: 'JSX props should not use .bind()'}, + {message: 'JSX props should not use arrow functions'} + ], parser: 'babel-eslint' }, @@ -397,14 +404,21 @@ ruleTester.run('jsx-no-bind', rule, { }, { code: [ - 'var Hello = React.createClass({', - ' render: function() { ', - ' doThing = () => true', - ' return
', + 'class Hello23 extends React.Component {', + ' renderDiv = () => {', + ' const click = ::this.onChange', + ' const renderStuff = () => {', + ' const click = () => true', + ' return
', + ' }', + ' return
Hello
;', ' }', - '});' + '};' ].join('\n'), - errors: [{message: 'JSX props should not use arrow functions'}], + errors: [ + {message: 'JSX props should not use arrow functions'}, + {message: 'JSX props should not use ::'} + ], parser: 'babel-eslint' }, @@ -451,8 +465,12 @@ ruleTester.run('jsx-no-bind', rule, { { code: [ 'class Hello23 extends React.Component {', - ' renderDiv() {', - ' click = this.bar::baz', + ' renderDiv = () => {', + ' const click = true', + ' const renderStuff = () => {', + ' const click = this.bar::baz', + ' return
', + ' }', ' return
Hello
;', ' }', '};' From 28073ae0bbd633ba73cde0f720369fb7e680cb77 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Sun, 15 Oct 2017 15:36:34 -0700 Subject: [PATCH 07/11] Add an additional check --- lib/rules/jsx-no-bind.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js index 935449b10b..96897dc677 100644 --- a/lib/rules/jsx-no-bind.js +++ b/lib/rules/jsx-no-bind.js @@ -128,7 +128,11 @@ module.exports = { const blockAncestors = getBlockStatementAncestors(node); const variableViolationType = getVariableViolationType(node.init); - if (blockAncestors.length > 0 && variableViolationType) { + if ( + blockAncestors.length > 0 && + variableViolationType && + node.parent.kind === 'const' // only support const right now + ) { addVariableNameToSet( variableViolationType, node.id.name, blockAncestors[0].start ); From 7326a5edd524e01e0a8d27965deeff03500046b5 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Tue, 24 Oct 2017 21:50:37 -0700 Subject: [PATCH 08/11] Only use babel parser when necessary on tests --- tests/lib/rules/jsx-no-bind.js | 102 +++++++++++---------------------- 1 file changed, 34 insertions(+), 68 deletions(-) diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js index 9622461c55..001b004dd2 100644 --- a/tests/lib/rules/jsx-no-bind.js +++ b/tests/lib/rules/jsx-no-bind.js @@ -31,42 +31,35 @@ ruleTester.run('jsx-no-bind', rule, { valid: [ // Not covered by the rule { - code: '
', - parser: 'babel-eslint' + code: '
' }, { - code: '
', - parser: 'babel-eslint' + code: '
' }, { - code: '
', - parser: 'babel-eslint' + code: '
' }, // bind() and arrow functions in refs explicitly ignored { code: '
this._input = c}>
', - options: [{ignoreRefs: true}], - parser: 'babel-eslint' + options: [{ignoreRefs: true}] }, { code: '
', - options: [{ignoreRefs: true}], - parser: 'babel-eslint' + options: [{ignoreRefs: true}] }, // bind() explicitly allowed { code: '
', - options: [{allowBind: true}], - parser: 'babel-eslint' + options: [{allowBind: true}] }, // Arrow functions explicitly allowed { code: '
alert("1337")}>
', - options: [{allowArrowFunctions: true}], - parser: 'babel-eslint' + options: [{allowArrowFunctions: true}] }, // Redux connect @@ -79,8 +72,7 @@ ruleTester.run('jsx-no-bind', rule, { } export default connect()(Hello); `, - options: [{allowBind: true}], - parser: 'babel-eslint' + options: [{allowBind: true}] }, // Backbone view with a bind @@ -92,8 +84,7 @@ ruleTester.run('jsx-no-bind', rule, { this.onTap.bind(this); } }); - `, - parser: 'babel-eslint' + ` }, { code: ` @@ -103,8 +94,7 @@ ruleTester.run('jsx-no-bind', rule, { return true; } }; - `, - parser: 'babel-eslint' + ` }, { code: ` @@ -114,8 +104,7 @@ ruleTester.run('jsx-no-bind', rule, { return true; } }; - `, - parser: 'babel-eslint' + ` }, { @@ -126,8 +115,7 @@ ruleTester.run('jsx-no-bind', rule, { ' return
Hello
;', ' }', '};' - ].join('\n'), - parser: 'babel-eslint' + ].join('\n') }, { code: [ @@ -137,8 +125,7 @@ ruleTester.run('jsx-no-bind', rule, { ' return
Hello
;', ' }', '};' - ].join('\n'), - parser: 'babel-eslint' + ].join('\n') }, { code: [ @@ -149,8 +136,7 @@ ruleTester.run('jsx-no-bind', rule, { ' }
);', ' }', '};' - ].join('\n'), - parser: 'babel-eslint' + ].join('\n') }, { code: [ @@ -160,8 +146,7 @@ ruleTester.run('jsx-no-bind', rule, { ' return
Hello
;', ' }', '};' - ].join('\n'), - parser: 'babel-eslint' + ].join('\n') }, { code: [ @@ -172,8 +157,7 @@ ruleTester.run('jsx-no-bind', rule, { ' }
);', ' }', '};' - ].join('\n'), - parser: 'babel-eslint' + ].join('\n') }, { code: [ @@ -207,8 +191,7 @@ ruleTester.run('jsx-no-bind', rule, { ' }
);', ' }', '});' - ].join('\n'), - parser: 'babel-eslint' + ].join('\n') }, { code: [ @@ -229,8 +212,7 @@ ruleTester.run('jsx-no-bind', rule, { ' return
Hello
;', ' }', '});' - ].join('\n'), - parser: 'babel-eslint' + ].join('\n') } ], @@ -238,23 +220,19 @@ ruleTester.run('jsx-no-bind', rule, { // .bind() { code: '
', - errors: [{message: 'JSX props should not use .bind()'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use .bind()'}] }, { code: '
', - errors: [{message: 'JSX props should not use .bind()'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use .bind()'}] }, { code: '
', - errors: [{message: 'JSX props should not use .bind()'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use .bind()'}] }, { code: '
', - errors: [{message: 'JSX props should not use .bind()'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use .bind()'}] }, { code: ` @@ -265,8 +243,7 @@ ruleTester.run('jsx-no-bind', rule, { } }); `, - errors: [{message: 'JSX props should not use .bind()'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use .bind()'}] }, { code: ` @@ -277,8 +254,7 @@ ruleTester.run('jsx-no-bind', rule, { } }; `, - errors: [{message: 'JSX props should not use .bind()'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use .bind()'}] }, { code: [ @@ -289,8 +265,7 @@ ruleTester.run('jsx-no-bind', rule, { ' }', '};' ].join('\n'), - errors: [{message: 'JSX props should not use .bind()'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use .bind()'}] }, { code: ` @@ -300,8 +275,7 @@ ruleTester.run('jsx-no-bind', rule, { ) }; `, - errors: [{message: 'JSX props should not use .bind()'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use .bind()'}] }, { code: [ @@ -311,8 +285,7 @@ ruleTester.run('jsx-no-bind', rule, { ' }', '});' ].join('\n'), - errors: [{message: 'JSX props should not use .bind()'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use .bind()'}] }, { code: [ @@ -323,8 +296,7 @@ ruleTester.run('jsx-no-bind', rule, { ' }', '});' ].join('\n'), - errors: [{message: 'JSX props should not use .bind()'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use .bind()'}] }, { code: [ @@ -349,23 +321,19 @@ ruleTester.run('jsx-no-bind', rule, { // Arrow functions { code: '
alert("1337")}>
', - errors: [{message: 'JSX props should not use arrow functions'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use arrow functions'}] }, { code: '
42}>
', - errors: [{message: 'JSX props should not use arrow functions'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use arrow functions'}] }, { code: '
{ first(); second(); }}>
', - errors: [{message: 'JSX props should not use arrow functions'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use arrow functions'}] }, { code: '
this._input = c}>
', - errors: [{message: 'JSX props should not use arrow functions'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use arrow functions'}] }, { code: [ @@ -387,8 +355,7 @@ ruleTester.run('jsx-no-bind', rule, { ' }', '});' ].join('\n'), - errors: [{message: 'JSX props should not use arrow functions'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use arrow functions'}] }, { code: [ @@ -399,8 +366,7 @@ ruleTester.run('jsx-no-bind', rule, { ' }', '});' ].join('\n'), - errors: [{message: 'JSX props should not use arrow functions'}], - parser: 'babel-eslint' + errors: [{message: 'JSX props should not use arrow functions'}] }, { code: [ From cb1d7c5cc30e8b5181f762f591be254f8de484e5 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Tue, 24 Oct 2017 22:06:41 -0700 Subject: [PATCH 09/11] Add more tests regarding async arrow functions --- tests/lib/rules/jsx-no-bind.js | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js index 001b004dd2..74aab83e4e 100644 --- a/tests/lib/rules/jsx-no-bind.js +++ b/tests/lib/rules/jsx-no-bind.js @@ -213,6 +213,29 @@ ruleTester.run('jsx-no-bind', rule, { ' }', '});' ].join('\n') + }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv = () => {', + ' const onClick = this.doSomething.bind(this, "no")', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + parser: 'babel-eslint' + }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv = async () => {', + ' return (
{', + ' this.props.list.map(this.wrap.bind(this, "span"))', + ' }
);', + ' }', + '};' + ].join('\n'), + parser: 'babel-eslint' } ], @@ -267,6 +290,30 @@ ruleTester.run('jsx-no-bind', rule, { ].join('\n'), errors: [{message: 'JSX props should not use .bind()'}] }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv = () => {', + ' const click = this.doSomething.bind(this, "no")', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [{message: 'JSX props should not use .bind()'}], + parser: 'babel-eslint' + }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv = async () => {', + ' const click = this.doSomething.bind(this, "no")', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [{message: 'JSX props should not use .bind()'}], + parser: 'babel-eslint' + }, { code: ` const foo = { @@ -347,6 +394,18 @@ ruleTester.run('jsx-no-bind', rule, { errors: [{message: 'JSX props should not use arrow functions'}], parser: 'babel-eslint' }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv = async () => {', + ' const click = () => true', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [{message: 'JSX props should not use arrow functions'}], + parser: 'babel-eslint' + }, { code: [ 'var Hello = React.createClass({', @@ -428,6 +487,18 @@ ruleTester.run('jsx-no-bind', rule, { errors: [{message: 'JSX props should not use ::'}], parser: 'babel-eslint' }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv = async () => {', + ' const click = this.bar::baz', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [{message: 'JSX props should not use ::'}], + parser: 'babel-eslint' + }, { code: [ 'class Hello23 extends React.Component {', From 305f2bc2d619309b47832190a9c3b4e610168596 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Tue, 24 Oct 2017 22:42:14 -0700 Subject: [PATCH 10/11] Add tests regarding passing async arrow functions --- tests/lib/rules/jsx-no-bind.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js index 74aab83e4e..28e1f15454 100644 --- a/tests/lib/rules/jsx-no-bind.js +++ b/tests/lib/rules/jsx-no-bind.js @@ -406,6 +406,18 @@ ruleTester.run('jsx-no-bind', rule, { errors: [{message: 'JSX props should not use arrow functions'}], parser: 'babel-eslint' }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv = async () => {', + ' const click = async () => true', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [{message: 'JSX props should not use arrow functions'}], + parser: 'babel-eslint' + }, { code: [ 'var Hello = React.createClass({', @@ -416,6 +428,16 @@ ruleTester.run('jsx-no-bind', rule, { ].join('\n'), errors: [{message: 'JSX props should not use arrow functions'}] }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' return
true} />', + ' }', + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use arrow functions'}] + }, { code: [ 'var Hello = React.createClass({', @@ -427,6 +449,17 @@ ruleTester.run('jsx-no-bind', rule, { ].join('\n'), errors: [{message: 'JSX props should not use arrow functions'}] }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' const doThing = async () => true', + ' return
', + ' }', + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use arrow functions'}] + }, { code: [ 'class Hello23 extends React.Component {', From 12ba212714e51c3a3382fb1c4538a09eec785517 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Wed, 25 Oct 2017 22:03:53 -0700 Subject: [PATCH 11/11] Add support for function --- docs/rules/jsx-no-bind.md | 9 ++ lib/rules/jsx-no-bind.js | 74 +++++++------- tests/lib/rules/jsx-no-bind.js | 174 +++++++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+), 38 deletions(-) diff --git a/docs/rules/jsx-no-bind.md b/docs/rules/jsx-no-bind.md index fb3adeb9e5..a4410f3cb3 100644 --- a/docs/rules/jsx-no-bind.md +++ b/docs/rules/jsx-no-bind.md @@ -24,6 +24,7 @@ The following patterns are not considered warnings: "react/jsx-no-bind": [, { "ignoreRefs": || false, "allowArrowFunctions": || false, + "allowFunctions": || false, "allowBind": || false }] ``` @@ -45,6 +46,14 @@ When `true` the following is not considered a warning:
alert("1337")} /> ``` +### `allowFunctions` + +When `true` the following is not considered a warning: + +```jsx +
+``` + ### `allowBind` When `true` the following is not considered a warning: diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js index 96897dc677..aef6a6aab8 100644 --- a/lib/rules/jsx-no-bind.js +++ b/lib/rules/jsx-no-bind.js @@ -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: { @@ -36,6 +39,10 @@ module.exports = { default: false, type: 'boolean' }, + allowFunctions: { + default: false, + type: 'boolean' + }, ignoreRefs: { default: false, type: 'boolean' @@ -56,19 +63,20 @@ 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 ( @@ -76,6 +84,11 @@ module.exports = { nodeType === 'ArrowFunctionExpression' ) { return 'arrowFunc'; + } else if ( + !configuration.allowFunctions && + nodeType === 'FunctionExpression' + ) { + return 'func'; } else if ( !configuration.allowBind && nodeType === 'BindExpression' @@ -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) { @@ -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 && @@ -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] + }); } } }; diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js index 28e1f15454..ec467f98f7 100644 --- a/tests/lib/rules/jsx-no-bind.js +++ b/tests/lib/rules/jsx-no-bind.js @@ -49,6 +49,10 @@ ruleTester.run('jsx-no-bind', rule, { code: '
', options: [{ignoreRefs: true}] }, + { + code: '
', + options: [{ignoreRefs: true}] + }, // bind() explicitly allowed { @@ -61,6 +65,24 @@ ruleTester.run('jsx-no-bind', rule, { code: '
alert("1337")}>
', options: [{allowArrowFunctions: true}] }, + { + code: '
alert("1337")}>
', + options: [{allowArrowFunctions: true}] + }, + + // Functions explicitly allowed + { + code: '
', + options: [{allowFunctions: true}] + }, + { + code: '
', + options: [{allowFunctions: true}] + }, + { + code: '
', + options: [{allowFunctions: true}] + }, // Redux connect { @@ -370,6 +392,10 @@ ruleTester.run('jsx-no-bind', rule, { code: '
alert("1337")}>
', errors: [{message: 'JSX props should not use arrow functions'}] }, + { + code: '
alert("1337")}>
', + errors: [{message: 'JSX props should not use arrow functions'}] + }, { code: '
42}>
', errors: [{message: 'JSX props should not use arrow functions'}] @@ -480,6 +506,154 @@ ruleTester.run('jsx-no-bind', rule, { parser: 'babel-eslint' }, + // Functions + { + code: '
', + errors: [{message: 'JSX props should not use functions'}] + }, + { + code: '
', + errors: [{message: 'JSX props should not use functions'}] + }, + { + code: '
', + errors: [{message: 'JSX props should not use functions'}] + }, + { + code: '
', + errors: [{message: 'JSX props should not use functions'}] + }, + { + code: [ + 'class Hello23 extends React.Component {', + ' renderDiv = () => {', + ' const click = function () { return true }', + ' return
Hello
;', + ' }', + '};' + ].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
Hello
;', + ' }', + '};' + ].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
Hello
;', + ' }', + '};' + ].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
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [{message: 'JSX props should not use functions'}], + parser: 'babel-eslint' + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' return
', + ' }', + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use functions'}] + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' return
', + ' }', + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use functions'}] + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' return
', + ' }', + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use functions'}] + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' const doThing = function () { return true }', + ' return
', + ' }', + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use functions'}] + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' const doThing = async function () { return true }', + ' return
', + ' }', + '});' + ].join('\n'), + errors: [{message: 'JSX props should not use functions'}] + }, + { + code: [ + 'var Hello = React.createClass({', + ' render: function() { ', + ' const doThing = function * () { return true }', + ' return
', + ' }', + '});' + ].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
', + ' }', + ' return
Hello
;', + ' }', + '};' + ].join('\n'), + errors: [ + {message: 'JSX props should not use functions'}, + {message: 'JSX props should not use ::'} + ], + parser: 'babel-eslint' + }, + // Bind expression { code: '
',