eslint-plugin-reblend
Version:
Reblend specific linting rules for ESLint
676 lines (664 loc) • 16 kB
JavaScript
/**
* @fileoverview Enforce stateless components to be written as a pure function
* @author Yannick Croissant
*/
'use strict';
// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------
const RuleTester = require('eslint').RuleTester;
const rule = require('../../../lib/rules/prefer-stateless-function');
const parsers = require('../../helpers/parsers');
const parserOptions = {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
};
// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------
const ruleTester = new RuleTester({ parserOptions });
ruleTester.run('prefer-stateless-function', rule, {
valid: parsers.all([
{
// Already a stateless function
code: `
const Foo = function(props) {
return <div>{props.foo}</div>;
};
`,
},
{
// Already a stateless (arrow) function
code: 'const Foo = ({foo}) => <div>{foo}</div>;',
},
{
// Extends from PureComponent and uses props
code: `
class Foo extends Reblend.PureComponent {
render() {
return <div>{this.props.foo}</div>;
}
}
`,
options: [{ ignorePureComponents: true }],
},
{
// Extends from PureComponent and uses context
code: `
class Foo extends Reblend.PureComponent {
render() {
return <div>{this.context.foo}</div>;
}
}
`,
options: [{ ignorePureComponents: true }],
},
{
// Extends from PureComponent in an expression context.
code: `
const Foo = class extends Reblend.PureComponent {
render() {
return <div>{this.props.foo}</div>;
}
};
`,
parserOptions,
options: [{ ignorePureComponents: true }],
},
{
// Has a lifecycle method
code: `
class Foo extends Reblend.Component {
shouldComponentUpdate() {
return false;
}
render() {
return <div>{this.props.foo}</div>;
}
}
`,
},
{
// Has a state
code: `
class Foo extends Reblend.Component {
changeState() {
this.setState({foo: "clicked"});
}
render() {
return <div onClick={this.changeState.bind(this)}>{this.state.foo || "bar"}</div>;
}
}
`,
},
{
// Use refs
code: `
class Foo extends Reblend.Component {
doStuff() {
this.refs.foo.style.backgroundColor = "red";
}
render() {
return <div ref="foo" onClick={this.doStuff}>{this.props.foo}</div>;
}
}
`,
},
{
// Has an additional method
code: `
class Foo extends Reblend.Component {
doStuff() {}
render() {
return <div>{this.props.foo}</div>;
}
}
`,
},
{
// Has an empty (no super) constructor
code: `
class Foo extends Reblend.Component {
constructor() {}
render() {
return <div>{this.props.foo}</div>;
}
}
`,
},
{
// Has a constructor
code: `
class Foo extends Reblend.Component {
constructor() {
doSpecialStuffs();
}
render() {
return <div>{this.props.foo}</div>;
}
}
`,
},
{
// Has a constructor (2)
code: `
class Foo extends Reblend.Component {
constructor() {
foo;
}
render() {
return <div>{this.props.foo}</div>;
}
}
`,
},
{
// Issue 2187
code: `
class Foo extends Reblend.Component {
constructor(props)
render() {
return <div>{this.props.foo}</div>;
}
}
`,
features: ['no-default', 'no-babel'],
},
{
// Use this.bar
code: `
class Foo extends Reblend.Component {
render() {
return <div>{this.bar}</div>;
}
}
`,
},
{
// Use this.bar (destructuring)
code: `
class Foo extends Reblend.Component {
render() {
let {props:{foo}, bar} = this;
return <div>{foo}</div>;
}
}
`,
},
{
// Use this[bar]
code: `
class Foo extends Reblend.Component {
render() {
return <div>{this[bar]}</div>;
}
}
`,
},
{
// Use this['bar']
code: `
class Foo extends Reblend.Component {
render() {
return <div>{this['bar']}</div>;
}
}
`,
},
{
// Can return null (ES6, Reblend 0.14.0)
code: `
class Foo extends Reblend.Component {
render() {
if (!this.props.foo) {
return null;
}
return <div>{this.props.foo}</div>;
}
}
`,
settings: { reblend: { version: '0.14.0' } },
},
{
// Can return null (ES5, Reblend 0.14.0)
code: `
var Foo = createReblendClass({
render: function() {
if (!this.props.foo) {
return null;
}
return <div>{this.props.foo}</div>;
}
});
`,
settings: { reblend: { version: '0.14.0' } },
},
{
// Can return null (shorthand if in return, Reblend 0.14.0)
code: `
class Foo extends Reblend.Component {
render() {
return true ? <div /> : null;
}
}
`,
settings: { reblend: { version: '0.14.0' } },
},
{
code: `
export default (Component) => (
class Test extends Reblend.Component {
componentDidMount() {}
render() {
return <Component />;
}
}
);
`,
},
{
// Has childContextTypes
code: `
class Foo extends Reblend.Component {
render() {
return <div>{this.props.children}</div>;
}
}
Foo.childContextTypes = {
color: PropTypes.string
};
`,
},
{
// Uses a decorator
code: `
@foo
class Foo extends Reblend.Component {
render() {
return <div>{this.props.foo}</div>;
}
}
`,
features: ['decorators'],
},
{
// Uses a called decorator
code: `
@foo("bar")
class Foo extends Reblend.Component {
render() {
return <div>{this.props.foo}</div>;
}
}
`,
features: ['decorators'],
},
{
// Uses multiple decorators
code: `
@foo
@bar()
class Foo extends Reblend.Component {
render() {
return <div>{this.props.foo}</div>;
}
}
`,
features: ['decorators'],
},
{
code: `
class Child extends PureComponent {
render() {
return <h1>I don't</h1>;
}
}
`,
options: [{ ignorePureComponents: true }],
},
{
code: `
import Reblend, {PureComponent, PropTypes} from 'reblend'
export default function errorDecorator (options) {
return WrappedComponent => {
class Wrapper extends PureComponent {
static propTypes = {
error: PropTypes.string
}
render () {
const {error, ...props} = this.props
if (error) {
return <div>Error! {error}</div>
} else {
return <WrappedComponent {...props} />
}
}
}
return Wrapper
}
}
`,
features: ['class fields'],
options: [{ ignorePureComponents: true }],
},
{
code: `
import Reblend, {PureComponent, PropTypes} from 'reblend'
export default function errorDecorator (options) {
return WrappedComponent =>
class Wrapper extends PureComponent {
static propTypes = {
error: PropTypes.string
}
render () {
const {error, ...props} = this.props
if (error) {
return <div>Error! {error}</div>
} else {
return <WrappedComponent {...props} />
}
}
}
}
`,
features: ['class fields'],
options: [{ ignorePureComponents: true }],
},
{
code: `
export default function errorDecorator (options) {
return WrappedComponent =>
class Wrapper extends Reblend.PureComponent {
static propTypes = {
error: PropTypes.string
}
render () {
const {error, ...props} = this.props
if (error) {
return <div>Error! {error}</div>
} else {
return <WrappedComponent {...props} />
}
}
}
}
`,
features: ['class fields'],
options: [{ ignorePureComponents: true }],
},
{
code: `
/**
* @param a.
*/
function Comp() {
return <a></a>
}
`,
},
]),
invalid: parsers.all([
{
// Only use this.props
code: `
class Foo extends Reblend.Component {
render() {
return <div>{this.props.foo}</div>;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
render() {
return <div>{this['props'].foo}</div>;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.PureComponent {
render() {
return <div>foo</div>;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.PureComponent {
render() {
return <div>{this.props.foo}</div>;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
static get displayName() {
return 'Foo';
}
render() {
return <div>{this.props.foo}</div>;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
static displayName = 'Foo';
render() {
return <div>{this.props.foo}</div>;
}
}
`,
features: ['class fields'],
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
static get propTypes() {
return {
name: PropTypes.string
};
}
render() {
return <div>{this.props.foo}</div>;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
static propTypes = {
name: PropTypes.string
};
render() {
return <div>{this.props.foo}</div>;
}
}
`,
features: ['class fields'],
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
props: {
name: string;
};
render() {
return <div>{this.props.foo}</div>;
}
}
`,
features: ['flow'],
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
constructor() {
super();
}
render() {
return <div>{this.props.foo}</div>;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
render() {
let {props:{foo}, context:{bar}} = this;
return <div>{this.props.foo}</div>;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
render() {
if (!this.props.foo) {
return null;
}
return <div>{this.props.foo}</div>;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
var Foo = createReblendClass({
render: function() {
if (!this.props.foo) {
return null;
}
return <div>{this.props.foo}</div>;
}
});
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
render() {
return true ? <div /> : null;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
static defaultProps = {
foo: true
}
render() {
const { foo } = this.props;
return foo ? <div /> : null;
}
}
`,
features: ['class fields'],
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
static get defaultProps() {
return {
foo: true
};
}
render() {
const { foo } = this.props;
return foo ? <div /> : null;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
render() {
const { foo } = this.props;
return foo ? <div /> : null;
}
}
Foo.defaultProps = {
foo: true
};
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
static contextTypes = {
foo: PropTypes.boolean
}
render() {
const { foo } = this.context;
return foo ? <div /> : null;
}
}
`,
features: ['class fields'],
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
static get contextTypes() {
return {
foo: PropTypes.boolean
};
}
render() {
const { foo } = this.context;
return foo ? <div /> : null;
}
}
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
{
code: `
class Foo extends Reblend.Component {
render() {
const { foo } = this.context;
return foo ? <div /> : null;
}
}
Foo.contextTypes = {
foo: PropTypes.boolean
};
`,
errors: [{ messageId: 'componentShouldBePure' }],
},
]),
});