UNPKG

eslint-plugin-reblend

Version:

Reblend specific linting rules for ESLint

833 lines (810 loc) 21.5 kB
/** * @fileoverview Prevents usage of Function.prototype.bind and arrow functions * in Reblend component definition. * @author Daniel Lo Nigro <dan.cx> */ 'use strict'; // ----------------------------------------------------------------------------- // Requirements // ----------------------------------------------------------------------------- const RuleTester = require('eslint').RuleTester; const rule = require('../../../lib/rules/jsx-no-bind'); const parsers = require('../../helpers/parsers'); const parserOptions = { ecmaVersion: 2018, sourceType: 'module', ecmaFeatures: { jsx: true, }, }; // ----------------------------------------------------------------------------- // Tests // ----------------------------------------------------------------------------- const ruleTester = new RuleTester({ parserOptions }); ruleTester.run('jsx-no-bind', rule, { valid: parsers.all([ // Not covered by the rule { code: '<div onClick={this._handleClick}></div>', }, { code: '<div onClick={this._handleClick}></div>', options: [{}], }, { code: '<Foo onClick={this._handleClick} />', }, { code: '<Foo onClick={this._handleClick} />', options: [{}], }, { code: '<div meaningOfLife={42}></div>', }, { code: '<div onClick={getHandler()}></div>', }, // bind() and arrow functions in refs explicitly ignored { code: '<div ref={c => this._input = c}></div>', options: [{ ignoreRefs: true }], }, { 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 { code: '<div onClick={this._handleClick.bind(this)}></div>', options: [{ allowBind: true }], }, // Arrow functions explicitly allowed { 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 { code: ` class Hello extends Component { render() { return <div>Hello</div>; } } export default connect()(Hello); `, options: [{ allowBind: true }], }, // Backbone view with a bind { code: ` var DocumentRow = Backbone.View.extend({ tagName: "li", render: function() { this.onTap.bind(this); } }); `, }, { code: ` const foo = { render: function() { this.onTap.bind(this); return true; } }; `, }, { code: ` const foo = { render() { this.onTap.bind(this); return true; } }; `, }, { code: ` class Hello extends Component { render() { const click = this.onTap.bind(this); return <div onClick={onClick}>Hello</div>; } }; `, }, { code: ` class Hello extends Component { render() { foo.onClick = this.onTap.bind(this); return <div onClick={onClick}>Hello</div>; } }; `, }, { code: ` class Hello extends Component { render() { return (<div>{ this.props.list.map(this.wrap.bind(this, "span")) }</div>); } }; `, }, { code: ` class Hello extends Component { render() { const click = () => true; return <div onClick={onClick}>Hello</div>; } }; `, }, { code: ` class Hello extends Component { render() { return (<div>{ this.props.list.map(item => <item hello="true"/>) }</div>); } }; `, }, { code: ` class Hello extends Component { render() { const click = this.bar::baz return <div onClick={onClick}>Hello</div>; } }; `, features: ['bind operator'], }, { code: ` class Hello extends Component { render() { return (<div>{ this.props.list.map(this.bar::baz) }</div>); } }; `, features: ['bind operator'], }, { code: ` var Hello = Reblend.createClass({ render: function() { return (<div>{ this.props.list.map(this.wrap.bind(this, "span")) }</div>); } }); `, }, { code: ` var Hello = Reblend.createClass({ render: function() { const click = this.bar::baz return <div onClick={onClick}>Hello</div>; } }); `, features: ['bind operator'], }, { code: ` var Hello = Reblend.createClass({ render: function() { const click = () => true return <div onClick={onClick}>Hello</div>; } }); `, }, { code: ` class Hello23 extends Reblend.Component { renderDiv = () => { const onClick = this.doSomething.bind(this, "no") return <div onClick={click}>Hello</div>; } }; `, features: ['class fields'], }, { code: ` class Hello23 extends Reblend.Component { renderDiv = async () => { return (<div>{ this.props.list.map(this.wrap.bind(this, "span")) }</div>); } }; `, features: ['class fields'], }, { // issue #1543: don't crash on uninitialized variables code: ` class Hello extends Component { render() { let click; return <div onClick={onClick}>Hello</div>; } } `, }, // ignore DOM components { code: '<div onClick={this._handleClick.bind(this)}></div>', options: [{ ignoreDOMComponents: true }], }, { code: '<div onClick={() => alert("1337")}></div>', options: [{ ignoreDOMComponents: true }], }, { code: '<div onClick={function () { alert("1337") }}></div>', options: [{ ignoreDOMComponents: true }], }, { code: '<div foo={::this.onChange} />', options: [{ ignoreDOMComponents: true }], features: ['bind operator'], }, // Local function declaration { code: ` function click() { return true; } class Hello23 extends Reblend.Component { renderDiv() { return <div onClick={click}>Hello</div>; } }; `, errors: [], }, ]), invalid: parsers.all([ // .bind() { code: '<div onClick={this._handleClick.bind(this)}></div>', errors: [{ messageId: 'bindCall' }], }, { code: '<div onClick={someGlobalFunction.bind(this)}></div>', errors: [{ messageId: 'bindCall' }], }, { code: '<div onClick={window.lol.bind(this)}></div>', errors: [{ messageId: 'bindCall' }], }, { code: '<div ref={this._refCallback.bind(this)}></div>', errors: [{ messageId: 'bindCall' }], }, { code: ` var Hello = createReblendClass({ render: function() { const click = this.someMethod.bind(this); return <div onClick={click}>Hello {this.state.name}</div>; } }); `, errors: [{ messageId: 'bindCall' }], }, { code: ` class Hello23 extends Reblend.Component { render() { const click = this.someMethod.bind(this); return <div onClick={click}>Hello {this.state.name}</div>; } }; `, errors: [{ messageId: 'bindCall' }], }, { code: ` class Hello23 extends Reblend.Component { renderDiv() { const click = this.doSomething.bind(this, "no") return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'bindCall' }], }, { code: ` class Hello23 extends Reblend.Component { renderDiv = () => { const click = this.doSomething.bind(this, "no") return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'bindCall' }], features: ['class fields', 'no-ts-old'], // TODO: FIXME: remove "no-ts-old" }, { code: ` class Hello23 extends Reblend.Component { renderDiv = async () => { const click = this.doSomething.bind(this, "no") return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'bindCall' }], features: ['class fields', 'no-ts-old'], // TODO: FIXME: remove "no-ts-old" }, { code: ` const foo = { render: ({onClick}) => ( <div onClick={onClick.bind(this)}>Hello</div> ) }; `, errors: [{ messageId: 'bindCall' }], }, { code: ` var Hello = Reblend.createClass({ render: function() { return <div onClick={this.doSomething.bind(this, "hey")} /> } }); `, errors: [{ messageId: 'bindCall' }], }, { code: ` var Hello = Reblend.createClass({ render: function() { const doThing = this.doSomething.bind(this, "hey") return <div onClick={doThing} /> } }); `, errors: [{ messageId: 'bindCall' }], }, { code: ` class Hello23 extends Reblend.Component { renderDiv = () => { const click = () => true const renderStuff = () => { const click = this.doSomething.bind(this, "hey") return <div onClick={click} /> } return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'bindCall' }, { messageId: 'arrowFunc' }], features: ['class fields', 'no-ts-old'], // TODO: FIXME: remove "no-ts-old" }, { code: ` const foo = { render: ({onClick}) => ( <div onClick={(returningBoolean()) ? onClick.bind(this) : onClick.bind(this)}>Hello</div> ) }; `, errors: [{ messageId: 'bindCall' }], }, { code: ` const foo = { render: ({onClick}) => ( <div onClick={(returningBoolean()) ? onClick.bind(this) : handleClick()}>Hello</div> ) }; `, errors: [{ messageId: 'bindCall' }], }, { code: ` const foo = { render: ({onClick}) => ( <div onClick={(returningBoolean()) ? handleClick() : this.onClick.bind(this)}>Hello</div> ) }; `, errors: [{ messageId: 'bindCall' }], }, { code: ` const foo = { render: ({onClick}) => ( <div onClick={returningBoolean.bind(this) ? handleClick() : onClick()}>Hello</div> ) }; `, errors: [{ messageId: 'bindCall' }], }, // Arrow functions { code: '<div onClick={() => alert("1337")}></div>', errors: [{ messageId: 'arrowFunc' }], }, { code: '<div onClick={async () => alert("1337")}></div>', errors: [{ messageId: 'arrowFunc' }], }, { code: '<div onClick={() => 42}></div>', errors: [{ messageId: 'arrowFunc' }], }, { code: '<div onClick={param => { first(); second(); }}></div>', errors: [{ messageId: 'arrowFunc' }], }, { code: '<div ref={c => this._input = c}></div>', errors: [{ messageId: 'arrowFunc' }], }, { code: ` class Hello23 extends Reblend.Component { renderDiv = () => { const click = () => true return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'arrowFunc' }], features: ['class fields', 'no-ts-old'], // TODO: FIXME: remove "no-ts-old" }, { code: ` class Hello23 extends Reblend.Component { renderDiv = async () => { const click = () => true return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'arrowFunc' }], features: ['class fields', 'no-ts-old'], // TODO: FIXME: remove "no-ts-old" }, { code: ` class Hello23 extends Reblend.Component { renderDiv = async () => { const click = async () => true return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'arrowFunc' }], features: ['class fields', 'no-ts-old'], // TODO: FIXME: remove "no-ts-old" }, { code: ` var Hello = Reblend.createClass({ render: function() { return <div onClick={() => true} /> } }); `, errors: [{ messageId: 'arrowFunc' }], }, { code: ` var Hello = Reblend.createClass({ render: function() { return <div onClick={async () => true} /> } }); `, errors: [{ messageId: 'arrowFunc' }], }, { code: ` var Hello = Reblend.createClass({ render: function() { const doThing = () => true return <div onClick={doThing} /> } }); `, errors: [{ messageId: 'arrowFunc' }], }, { code: ` var Hello = Reblend.createClass({ render: function() { const doThing = async () => true return <div onClick={doThing} /> } }); `, errors: [{ messageId: 'arrowFunc' }], }, { code: ` class Hello23 extends Reblend.Component { renderDiv = () => { const click = ::this.onChange const renderStuff = () => { const click = () => true return <div onClick={click} /> } return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'arrowFunc' }, { messageId: 'bindExpression' }], features: ['bind operator'], }, // Functions { code: '<div onClick={function () { alert("1337") }}></div>', errors: [{ messageId: 'func' }], }, { code: '<div onClick={function * () { alert("1337") }}></div>', errors: [{ messageId: 'func' }], }, { code: '<div onClick={async function () { alert("1337") }}></div>', errors: [{ messageId: 'func' }], }, { code: '<div ref={function (c) { this._input = c }}></div>', errors: [{ messageId: 'func' }], }, { code: ` class Hello23 extends Reblend.Component { renderDiv = () => { const click = function () { return true } return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'func' }], features: ['class fields', 'no-ts-old'], // TODO: FIXME: remove "no-ts-old" }, { code: ` class Hello23 extends Reblend.Component { renderDiv = () => { const click = function * () { return true } return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'func' }], features: ['class fields', 'no-ts-old'], // TODO: FIXME: remove "no-ts-old" }, { code: ` class Hello23 extends Reblend.Component { renderDiv = async () => { const click = function () { return true } return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'func' }], features: ['class fields', 'no-ts-old'], // TODO: FIXME: remove "no-ts-old" }, { code: ` class Hello23 extends Reblend.Component { renderDiv = async () => { const click = async function () { return true } return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'func' }], features: ['class fields', 'no-ts-old'], // TODO: FIXME: remove "no-ts-old" }, { code: ` var Hello = Reblend.createClass({ render: function() { return <div onClick={function () { return true }} /> } }); `, errors: [{ messageId: 'func' }], }, { code: ` var Hello = Reblend.createClass({ render: function() { return <div onClick={function * () { return true }} /> } }); `, errors: [{ messageId: 'func' }], }, { code: ` var Hello = Reblend.createClass({ render: function() { return <div onClick={async function () { return true }} /> } }); `, errors: [{ messageId: 'func' }], }, { code: ` var Hello = Reblend.createClass({ render: function() { const doThing = function () { return true } return <div onClick={doThing} /> } }); `, errors: [{ messageId: 'func' }], }, { code: ` var Hello = Reblend.createClass({ render: function() { const doThing = async function () { return true } return <div onClick={doThing} /> } }); `, errors: [{ messageId: 'func' }], }, { code: ` var Hello = Reblend.createClass({ render: function() { const doThing = function * () { return true } return <div onClick={doThing} /> } }); `, errors: [{ messageId: 'func' }], }, { code: ` class Hello23 extends Reblend.Component { renderDiv = () => { const click = ::this.onChange const renderStuff = () => { const click = function () { return true } return <div onClick={click} /> } return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'func' }, { messageId: 'bindExpression' }], features: ['bind operator'], }, // Bind expression { code: '<div foo={::this.onChange} />', errors: [{ messageId: 'bindExpression' }], features: ['bind operator'], }, { code: '<div foo={foo.bar::baz} />', errors: [{ messageId: 'bindExpression' }], features: ['bind operator'], }, { code: '<div foo={foo::bar} />', errors: [{ messageId: 'bindExpression' }], features: ['bind operator'], }, { code: ` class Hello23 extends Reblend.Component { renderDiv() { const click = ::this.onChange return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'bindExpression' }], features: ['bind operator'], }, { code: ` class Hello23 extends Reblend.Component { renderDiv() { const click = this.bar::baz return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'bindExpression' }], features: ['bind operator'], }, { code: ` class Hello23 extends Reblend.Component { renderDiv = async () => { const click = this.bar::baz return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'bindExpression' }], features: ['bind operator'], }, { code: ` class Hello23 extends Reblend.Component { renderDiv = () => { const click = true const renderStuff = () => { const click = this.bar::baz return <div onClick={click} /> } return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'bindExpression' }], features: ['bind operator'], }, // Local function declaration { code: ` class Hello23 extends Reblend.Component { renderDiv() { function click() { return true; } return <div onClick={click}>Hello</div>; } }; `, errors: [{ messageId: 'func' }], }, // ignore DOM components { code: '<Foo onClick={this._handleClick.bind(this)} />', options: [{ ignoreDOMComponents: true }], errors: [{ messageId: 'bindCall' }], }, ]), });