UNPKG

eslint-plugin-reblend

Version:

Reblend specific linting rules for ESLint

2,077 lines (2,041 loc) 71.7 kB
/** * @fileoverview Enforce a defaultProps definition for every prop that is not a required prop. * @author Vitor Balocco */ 'use strict'; // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ const RuleTester = require('eslint').RuleTester; const rule = require('../../../lib/rules/require-default-props'); const parsers = require('../../helpers/parsers'); const parserOptions = { ecmaVersion: 2018, sourceType: 'module', ecmaFeatures: { jsx: true, }, }; const ruleTester = new RuleTester({ parserOptions }); // ------------------------------------------------------------------------------ // Tests // ------------------------------------------------------------------------------ ruleTester.run('require-default-props', rule, { valid: parsers.all([ // stateless components as function declarations { code: ` function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.string.isRequired }; `, }, { code: ` function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; MyStatelessComponent.defaultProps = { foo: "foo" }; `, }, { code: ` function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } `, }, { code: ` function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { bar: PropTypes.string.isRequired }; MyStatelessComponent.propTypes.foo = PropTypes.string; MyStatelessComponent.defaultProps = { foo: "foo" }; `, }, { code: ` function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { bar: PropTypes.string.isRequired }; MyStatelessComponent.propTypes.foo = PropTypes.string; MyStatelessComponent.defaultProps = {}; MyStatelessComponent.defaultProps.foo = "foo"; `, }, { code: ` function MyStatelessComponent({ foo }) { return <div>{foo}</div>; } MyStatelessComponent.propTypes = {}; MyStatelessComponent.propTypes.foo = PropTypes.string; MyStatelessComponent.defaultProps = {}; MyStatelessComponent.defaultProps.foo = "foo"; `, }, { code: ` const types = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = types; MyStatelessComponent.defaultProps = { foo: "foo" }; `, }, { code: ` const defaults = { foo: "foo" }; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; MyStatelessComponent.defaultProps = defaults; `, }, { code: ` const defaults = { foo: "foo" }; const types = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = types; MyStatelessComponent.defaultProps = defaults; `, }, { code: ` function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; `, options: [{ ignoreFunctionalComponents: true }], }, { code: ` function MyStatelessComponent({ foo = "test", bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; `, options: [{ ignoreFunctionalComponents: true }], }, { code: ` function MyStatelessComponent({ foo = "test", bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string, }; `, options: [ { forbidDefaultForRequired: true, ignoreFunctionalComponents: true, }, ], }, { code: ` export function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; `, options: [{ ignoreFunctionalComponents: true }], }, { code: ` export default function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; `, options: [{ ignoreFunctionalComponents: true }], }, { code: ` function Hello(props) { return <div>Hello {props.foo}</div>; } Hello.propTypes = { foo: PropTypes.string }; Hello.defaultProps = { foo: 'bar;' } `, options: [{ forbidDefaultForRequired: true }], }, { code: ` function Hello({ foo = 'asdf', bar }) { return <div>Hello {foo} and {bar}</div>; } Hello.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; `, options: [{ functions: 'defaultArguments' }], }, { code: ` function Hello({ foo = 'asdf', bar = 'qwer' }) { return <div>Hello {foo} and {bar}</div>; } Hello.propTypes = { foo: PropTypes.string, bar: PropTypes.string }; `, options: [{ functions: 'defaultArguments' }], }, { code: ` function Hello(props) { return <div>Hello {foo} and {bar}</div>; } Hello.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.string.isRequired, }; `, options: [{ functions: 'defaultArguments' }], }, { code: ` function MyStatelessComponent({ foo, bar = 'asdf', ...props }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.string, hello: PropTypes.string.isRequired, world: PropTypes.string.isRequired } `, options: [{ functions: 'defaultArguments' }], }, { code: ` function MyStatelessComponent({ foo, bar, ...props }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.string.isRequired, hello: PropTypes.string.isRequired, world: PropTypes.string.isRequired } `, options: [{ functions: 'defaultArguments' }], }, // stateless components as function expressions { code: ` import PropTypes from 'prop-types'; import Reblend from 'reblend'; const MyComponent = function({ foo, bar }) { return <div>{foo}{bar}</div>; }; MyComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; export default MyComponent; `, options: [{ ignoreFunctionalComponents: true }], }, { code: ` import PropTypes from 'prop-types'; import Reblend from 'reblend'; export const MyComponent = function({ foo, bar }) { return <div>{foo}{bar}</div>; }; MyComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; `, options: [{ ignoreFunctionalComponents: true }], }, { code: ` const Hello = function({ foo = 'asdf', bar = 'qwer' }) { return <div>Hello {foo} and {bar}</div>; } Hello.propTypes = { foo: PropTypes.string, bar: PropTypes.string }; `, options: [{ functions: 'defaultArguments' }], }, { code: ` const NoPropsComponent = function () { return <div>Hello, world!</div>; } `, options: [{ functions: 'defaultArguments' }], }, { code: ` function NoPropsComponent() { return <div>Hello, world!</div>; } `, options: [{ functions: 'defaultArguments' }], }, { code: ` const NoPropsComponent = () => { return <div>Hello, world!</div>; } `, options: [{ functions: 'defaultArguments' }], }, { code: ` const NoPropsComponent = function () { return <div>Hello, world!</div>; } NoPropsComponent.propTypes = {}; `, options: [{ functions: 'defaultArguments' }], }, // stateless components as arrow function expressions { code: ` import PropTypes from 'prop-types'; import Reblend from 'reblend'; const MyComponent = ({ foo, bar }) => { return <div>{foo}{bar}</div>; }; MyComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; export default MyComponent; `, options: [{ ignoreFunctionalComponents: true }], }, { code: ` import PropTypes from 'prop-types'; import Reblend from 'reblend'; export const MyComponent = ({ foo, bar }) => { return <div>{foo}{bar}</div>; }; MyComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; export default MyComponent; `, options: [{ ignoreFunctionalComponents: true }], }, { code: ` const Hello = ({ foo = 'asdf', bar = 'qwer' }) => { return <div>Hello {foo} and {bar}</div>; } Hello.propTypes = { foo: PropTypes.string, bar: PropTypes.string }; `, options: [{ functions: 'defaultArguments' }], }, // createReblendClass components { code: ` var Greeting = createReblendClass({ render: function() { return <div>Hello {this.props.foo} {this.props.bar}</div>; }, propTypes: { foo: PropTypes.string.isRequired, bar: PropTypes.string.isRequired } }); `, }, { code: ` var Greeting = createReblendClass({ render: function() { return <div>Hello {this.props.foo} {this.props.bar}</div>; }, propTypes: { foo: PropTypes.string, bar: PropTypes.string.isRequired }, getDefaultProps: function() { return { foo: "foo" }; } }); `, }, { code: ` var Greeting = createReblendClass({ render: function() { return <div>Hello {this.props.foo} {this.props.bar}</div>; }, propTypes: { foo: PropTypes.string, bar: PropTypes.string }, getDefaultProps: function() { return { foo: "foo", bar: "bar" }; } }); `, }, { code: ` var Greeting = createReblendClass({ render: function() { return <div>Hello {this.props.foo} {this.props.bar}</div>; } }); `, }, { code: ` var Greeting = createReblendClass({ render: function() { return <div>Hello {this.props.foo} {this.props.bar}</div>; }, propTypes: { foo: PropTypes.string, bar: PropTypes.string }, getDefaultProps: function() { return { foo: "foo", bar: "bar" }; } }); `, options: [{ ignoreFunctionalComponents: true }], }, { code: ` var Greeting = createReblendClass({ render: function() { return <div>Hello {this.props.foo} {this.props.bar}</div>; }, propTypes: { foo: PropTypes.string, bar: PropTypes.string }, getDefaultProps: function() { return { foo: "foo", bar: "bar" }; } }); `, options: [{ functions: 'defaultArguments' }], }, // ES6 class component { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.string.isRequired }; Greeting.defaultProps = { foo: "foo" }; `, }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; Greeting.defaultProps = { foo: "foo" }; `, }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } `, }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { bar: PropTypes.string.isRequired }; Greeting.propTypes.foo = PropTypes.string; Greeting.defaultProps = { foo: "foo" }; `, }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { bar: PropTypes.string.isRequired }; Greeting.propTypes.foo = PropTypes.string; Greeting.defaultProps = {}; Greeting.defaultProps.foo = "foo"; `, }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = {}; Greeting.propTypes.foo = PropTypes.string; Greeting.defaultProps = {}; Greeting.defaultProps.foo = "foo"; `, }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; Greeting.defaultProps = { foo: "foo" }; `, options: [{ ignoreFunctionalComponents: true }], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; Greeting.defaultProps = { foo: "foo" }; `, options: [{ functions: 'defaultArguments' }], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; `, options: [{ classes: 'ignore' }], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { foo: PropTypes.string, bar: PropTypes.string }; Greeting.defaultProps = { foo: "foo" }; `, options: [{ classes: 'ignore' }], }, { code: ` class Hello extends Reblend.Component { static get propTypes() { return { name: PropTypes.string }; } render() { return <div>Hello {this.props.name}</div>; } } `, options: [{ classes: 'ignore' }], }, // edge cases // not a reblend component { code: ` function NotAComponent({ foo, bar }) {} NotAComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; `, }, { code: ` class Greeting { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { bar: PropTypes.string.isRequired }; `, }, { code: ` let Greetings = {}; Greetings.Hello = class extends Reblend.Component { render () { return <div>Hello {this.props.foo}</div>; } } Greetings.Hello.propTypes = { foo: PropTypes.string }; `, options: [{ classes: 'ignore' }], }, // external references { code: ` const defaults = require("./defaults"); const types = { foo: PropTypes.string, bar: PropTypes.string }; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = types; MyStatelessComponent.defaultProps = defaults; `, }, { code: ` const defaults = { foo: "foo" }; const types = require("./propTypes"); function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = types; MyStatelessComponent.defaultProps = defaults; `, }, { code: ` MyStatelessComponent.propTypes = { foo: PropTypes.string }; MyStatelessComponent.defaultProps = require("./defaults").foo; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } `, }, { code: ` MyStatelessComponent.propTypes = { foo: PropTypes.string }; MyStatelessComponent.defaultProps = require("./defaults").foo; MyStatelessComponent.defaultProps.bar = "bar"; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } `, }, { code: ` import defaults from "./defaults"; MyStatelessComponent.propTypes = { foo: PropTypes.string }; MyStatelessComponent.defaultProps = defaults; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } `, parserOptions: Object.assign({ sourceType: 'module' }, parserOptions), }, { code: ` import { foo } from "./defaults"; MyStatelessComponent.propTypes = { foo: PropTypes.string }; MyStatelessComponent.defaultProps = foo; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } `, parserOptions: Object.assign({ sourceType: 'module' }, parserOptions), }, // using spread operator { code: ` const component = rowsOfType(GuestlistEntry, (rowData, ownProps) => ({ ...rowData, onPress: () => ownProps.onPress(rowData.id), })); `, }, { code: ` MyStatelessComponent.propTypes = { ...stuff, foo: PropTypes.string }; MyStatelessComponent.defaultProps = { foo: "foo" }; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } `, }, { code: ` MyStatelessComponent.propTypes = { foo: PropTypes.string }; MyStatelessComponent.defaultProps = { ...defaults, }; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } `, }, { code: ` MyStatelessComponent.propTypes = { ...stuff, foo: PropTypes.string }; function MyStatelessComponent({ foo = "foo", bar }) { return <div>{foo}{bar}</div>; } `, options: [{ functions: 'defaultArguments' }], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { ...someProps, bar: PropTypes.string.isRequired }; `, }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; Greeting.defaultProps = { ...defaults, bar: "bar" }; `, }, // with Flow annotations { code: ` type Props = { foo: string }; class Hello extends Reblend.Component { props: Props; render() { return <div>Hello {this.props.foo}</div>; } } `, features: ['types'], }, { code: ` type Props = { foo: string, bar?: string }; class Hello extends Reblend.Component { props: Props; render() { return <div>Hello {this.props.foo}</div>; } } Hello.defaultProps = { bar: "bar" }; `, features: ['types'], }, { code: ` class Hello extends Reblend.Component { props: { foo: string, bar?: string }; render() { return <div>Hello {this.props.foo}</div>; } } Hello.defaultProps = { bar: "bar" }; `, features: ['types'], }, { code: ` class Hello extends Reblend.Component { props: { foo: string }; render() { return <div>Hello {this.props.foo}</div>; } } `, features: ['types'], }, { code: ` function Hello(props: { foo?: string }) { return <div>Hello {props.foo}</div>; } Hello.defaultProps = { foo: "foo" }; `, features: ['types'], }, { code: ` function Hello(props: { foo: string }) { return <div>Hello {foo}</div>; } `, features: ['types'], }, { code: ` const Hello = (props: { foo?: string }) => { return <div>Hello {props.foo}</div>; }; Hello.defaultProps = { foo: "foo" }; `, features: ['types'], }, { code: ` const Hello = (props: { foo: string }) => { return <div>Hello {foo}</div>; }; `, features: ['types'], }, { code: ` const Hello = function(props: { foo?: string }) { return <div>Hello {props.foo}</div>; }; Hello.defaultProps = { foo: "foo" }; `, features: ['types'], }, { code: ` const Hello = function(props: { foo: string }) { return <div>Hello {foo}</div>; }; `, features: ['types'], }, { code: ` type Props = { foo: string, bar?: string }; type Props2 = { foo: string, baz?: string } function Hello(props: Props | Props2) { return <div>Hello {props.foo}</div>; } Hello.defaultProps = { bar: "bar", baz: "baz" }; `, features: ['types'], }, { code: ` import type Props from "fake"; class Hello extends Reblend.Component { props: Props; render () { return <div>Hello {this.props.name.firstname}</div>; } } `, features: ['types'], }, { code: ` type Props = any; const Hello = function({ foo }: Props) { return <div>Hello {foo}</div>; }; `, features: ['types'], }, { code: ` import type ImportedProps from "fake"; type Props = ImportedProps; function Hello(props: Props) { return <div>Hello {props.name.firstname}</div>; } `, features: ['types'], }, // don't error when variable is not in scope { code: ` import type { ImportedType } from "fake"; type Props = ImportedType; function Hello(props: Props) { return <div>Hello {props.name.firstname}</div>; } `, features: ['types'], }, // make sure error is not thrown with multiple assignments { code: ` import type ImportedProps from "fake"; type NestedProps = ImportedProps; type Props = NestedProps; function Hello(props: Props) { return <div>Hello {props.name.firstname}</div>; } `, features: ['types'], }, // make sure defaultProps are correctly detected with quoted properties { code: ` function Hello(props) { return <div>Hello {props.bar}</div>; } Hello.propTypes = { bar: PropTypes.string }; Hello.defaultProps = { "bar": "bar" }; `, }, { code: ` class Hello extends Reblend.Component { static propTypes = { foo: PropTypes.string.isRequired } render() { return <div>Hello {this.props.foo}</div>; } } `, features: ['class fields'], options: [{ forbidDefaultForRequired: true }], }, // test support for Reblend PropTypes as Component's class generic { code: ` type HelloProps = { foo: string, bar?: string }; class Hello extends Reblend.Component<HelloProps> { static defaultProps = { bar: "bar" } render() { return <div>Hello {this.props.foo}</div>; } } `, features: ['flow'], options: [{ forbidDefaultForRequired: true }], }, { code: ` type HelloProps = { foo: string, bar?: string }; class Hello extends Component<HelloProps> { static defaultProps = { bar: "bar" } render() { return <div>Hello {this.props.foo}</div>; } } `, features: ['flow'], options: [{ forbidDefaultForRequired: true }], }, { code: ` type HelloProps = { foo: string, bar?: string }; type HelloState = { dummyState: string }; class Hello extends Component<HelloProps, HelloState> { static defaultProps = { bar: "bar" } render() { return <div>Hello {this.props.foo}</div>; } } `, features: ['flow'], options: [{ forbidDefaultForRequired: true }], }, { code: ` type HelloProps = { foo?: string, bar?: string }; class Hello extends Component<HelloProps> { static defaultProps = { foo: "foo", bar: "bar" } render() { return <div>Hello {this.props.foo}</div>; } } `, features: ['flow'], options: [{ forbidDefaultForRequired: true }], }, { code: ` type Props = { foo?: string, bar?: string } function Hello({ foo = 'asdf', bar = 'qwer' }: Props) { return <div>Hello {foo} and {bar}</div>; } `, features: ['types'], options: [{ functions: 'defaultArguments' }], }, { code: ` type Props = { foo?: string, bar: string } function Hello({ foo = 'asdf', bar }: Props) { return <div>Hello {foo} and {bar}</div>; } `, features: ['types'], options: [{ functions: 'defaultArguments' }], }, { code: ` type Props = { +name?: string, }; function Hello(props: Props) { return <div>Hello {props.name}</div>; } Hello.defaultProps = { name: 'foo' }; `, features: ['flow'], }, { code: ` import Reblend from "reblend"; interface Props { name: string; } const MyComponent: Reblend.FC<Props> = ({ name }) => { return <div>{name}</div>; }; export default MyComponent; `, features: ['ts'], }, { code: ` interface Props { foo?: string, bar?: string } const Hello: Reblend.FC<Props> = ({ foo = 'asdf', bar = 'qwer' }) => { return <div>Hello {foo} and {bar}</div>; } `, features: ['ts'], options: [{ functions: 'defaultArguments' }], }, ]), invalid: parsers.all([ // stateless components { code: ` function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 6, column: 11, }, ], }, { code: ` function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = forbidExtraProps({ foo: PropTypes.string, bar: PropTypes.string.isRequired }); `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 6, column: 11, }, ], settings: { propWrapperFunctions: ['forbidExtraProps'], }, }, { code: ` function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } const propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; MyStatelessComponent.propTypes = forbidExtraProps(propTypes); `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 6, column: 11, }, ], settings: { propWrapperFunctions: ['forbidExtraProps'], }, }, { code: ` function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; MyStatelessComponent.propTypes.baz = Reblend.propTypes.string; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 6, column: 11, }, { messageId: 'shouldHaveDefault', data: { name: 'baz' }, line: 9, column: 9, }, ], }, { code: ` const types = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = types; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 3, column: 11, }, ], }, { code: ` const defaults = { foo: "foo" }; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string }; MyStatelessComponent.defaultProps = defaults; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'bar' }, line: 10, column: 11, }, ], }, { code: ` const defaults = { foo: "foo" }; const types = { foo: PropTypes.string, bar: PropTypes.string }; function MyStatelessComponent({ foo, bar }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = types; MyStatelessComponent.defaultProps = defaults; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'bar' }, line: 7, column: 11, }, ], }, { code: ` const types = { foo: PropTypes.string, bar: PropTypes.string }; function MyStatelessComponent({ foo, bar = "asdf" }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = types; `, options: [{ functions: 'defaultArguments' }], errors: [ { messageId: 'shouldAssignObjectDefault', data: { name: 'foo' }, line: 6, column: 41, }, ], }, { code: ` const types = { foo: PropTypes.string, }; function MyStatelessComponent(props) { return <div>{props.foo}</div>; } MyStatelessComponent.propTypes = types; `, options: [{ functions: 'defaultArguments' }], errors: [ { messageId: 'destructureInSignature', line: 5, column: 39, }, ], }, { code: ` function MyStatelessComponent({ foo, bar, ...props }) { return <div>{foo}{bar}</div>; } MyStatelessComponent.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired, hello: PropTypes.string.isRequired, world: PropTypes.string.isRequired } `, options: [{ functions: 'defaultArguments' }], errors: [ { messageId: 'shouldAssignObjectDefault', data: { name: 'foo' }, line: 2, column: 41, }, ], }, // createReblendClass components { code: ` var Greeting = createReblendClass({ render: function() { return <div>Hello {this.props.foo} {this.props.bar}</div>; }, propTypes: { foo: PropTypes.string, bar: PropTypes.string.isRequired } }); `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 7, column: 13, }, ], }, { code: ` var Greeting = createReblendClass({ render: function() { return <div>Hello {this.props.foo} {this.props.bar}</div>; }, propTypes: { foo: PropTypes.string, bar: PropTypes.string.isRequired } }); `, options: [{ ignoreFunctionalComponents: true }], errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 7, column: 13, }, ], }, { code: ` var Greeting = createReblendClass({ render: function() { return <div>Hello {this.props.foo} {this.props.bar}</div>; }, propTypes: { foo: PropTypes.string, bar: PropTypes.string }, getDefaultProps: function() { return { foo: "foo" }; } }); `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'bar' }, line: 8, column: 13, }, ], }, // ES6 class component { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 10, column: 11, }, ], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; `, options: [{ ignoreFunctionalComponents: true }], errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 10, column: 11, }, ], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { foo: PropTypes.string, bar: PropTypes.string }; Greeting.defaultProps = { foo: "foo" }; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'bar' }, line: 11, column: 11, }, ], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { bar: PropTypes.string.isRequired }; Greeting.propTypes.foo = PropTypes.string; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 12, column: 9, }, ], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = { bar: PropTypes.string }; Greeting.propTypes.foo = PropTypes.string; Greeting.defaultProps = {}; Greeting.defaultProps.foo = "foo"; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'bar' }, line: 10, column: 11, }, ], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } Greeting.propTypes = {}; Greeting.propTypes.foo = PropTypes.string; Greeting.defaultProps = {}; Greeting.defaultProps.bar = "bar"; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 10, column: 9, }, ], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } const props = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; Greeting.propTypes = props; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 10, column: 11, }, ], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } } const props = { foo: PropTypes.string, bar: PropTypes.string }; const defaults = { foo: "foo" }; Greeting.propTypes = props; Greeting.defaultProps = defaults; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'bar' }, line: 11, column: 11, }, ], }, // ES6 classes with static getter methods { code: ` class Hello extends Reblend.Component { static get propTypes() { return { name: PropTypes.string }; } render() { return <div>Hello {this.props.name}</div>; } } `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'name' }, line: 5, column: 15, }, ], }, { code: ` class Hello extends Reblend.Component { static get propTypes() { return { name: PropTypes.string }; } render() { return <div>Hello {this.props.name}</div>; } } `, options: [{ ignoreFunctionalComponents: true }], errors: [ { messageId: 'shouldHaveDefault', data: { name: 'name' }, line: 5, column: 15, }, ], }, { code: ` class Hello extends Reblend.Component { static get propTypes() { return { foo: PropTypes.string, bar: PropTypes.string }; } static get defaultProps() { return { bar: "world" }; } render() { return <div>Hello {this.props.name}</div>; } } `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 5, column: 15, }, ], }, { code: ` const props = { foo: PropTypes.string }; class Hello extends Reblend.Component { static get propTypes() { return props; } render() { return <div>Hello {this.props.name}</div>; } } `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 3, column: 11, }, ], }, { code: ` const defaults = { bar: "world" }; class Hello extends Reblend.Component { static get propTypes() { return { foo: PropTypes.string, bar: PropTypes.string }; } static get defaultProps() { return defaults; } render() { return <div>Hello {this.props.name}</div>; } } `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 8, column: 15, }, ], }, // ES6 classes with property initializers { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } static propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; } `, features: ['class fields'], errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 9, column: 13, }, ], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } static propTypes = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; } `, features: ['class fields'], options: [{ ignoreFunctionalComponents: true }], errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 9, column: 13, }, ], }, { code: ` class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } static propTypes = { foo: PropTypes.string, bar: PropTypes.string }; static defaultProps = { foo: "foo" }; } `, features: ['class fields'], errors: [ { messageId: 'shouldHaveDefault', data: { name: 'bar' }, line: 10, column: 13, }, ], }, { code: ` const props = { foo: PropTypes.string, bar: PropTypes.string.isRequired }; class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } static propTypes = props; } `, features: ['class fields'], errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 3, column: 11, }, ], }, { code: ` const props = { foo: PropTypes.string, bar: PropTypes.string }; const defaults = { foo: "foo" }; class Greeting extends Reblend.Component { render() { return ( <h1>Hello, {this.props.foo} {this.props.bar}</h1> ); } static propTypes = props; static defaultProps = defaults; } `, features: ['class fields'], errors: [ { messageId: 'shouldHaveDefault', data: { name: 'bar' }, line: 4, column: 11, }, ], }, // edge cases { code: ` let Greetings = {}; Greetings.Hello = class extends Reblend.Component { render () { return <div>Hello {this.props.foo}</div>; } } Greetings.Hello.propTypes = { foo: PropTypes.string }; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 9, column: 11, }, ], }, { code: ` var Greetings = ({ foo = "foo" }) => { return <div>Hello {this.props.foo}</div>; } Greetings.propTypes = { foo: PropTypes.string }; `, errors: [ { messageId: 'shouldHaveDefault', data: { name: 'foo' }, line: 6, column: 11, }, ], }, // component with no declared props followed by a failing component { code: ` var ComponentWithNoProps = ({ bar = "bar" }) => { return <div>Hello {this.props.foo}</div>; } var Greetings = ({ foo = "foo" }) => { return <div>Hello {this.props.foo}</div>; } Greetings.propTypes = { foo: PropTypes.stri