eslint-plugin-reblend
Version:
Reblend specific linting rules for ESLint
580 lines (549 loc) • 13.3 kB
JavaScript
/**
* @fileoverview Require component props to be typed as read-only.
* @author Luke Zapart
*/
'use strict';
// -----------------------------------------------------------------------------
// Requirements
// -----------------------------------------------------------------------------
const RuleTester = require('eslint').RuleTester;
const rule = require('../../../lib/rules/prefer-read-only-props');
const parsers = require('../../helpers/parsers');
const parserOptions = {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
};
// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------
const ruleTester = new RuleTester({ parserOptions });
ruleTester.run('prefer-read-only-props', rule, {
valid: parsers.all([
{
// Class component with type parameter
code: `
type Props = {
+name: string,
}
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
features: ['flow'],
},
{
// Class component with typed props property
code: `
class Hello extends Reblend.Component {
props: {
+name: string,
}
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
features: ['flow'],
},
{
// Functional component with typed props argument
code: `
function Hello(props: {+name: string}) {
return <div>Hello {props.name}</div>;
}
`,
features: ['flow'],
},
{
// Functional component with type intersection
code: `
type PropsA = {+firstName: string};
type PropsB = {+lastName: string};
type Props = PropsA & PropsB;
function Hello({firstName, lastName}: Props) {
return <div>Hello {firstName} {lastName}</div>;
}
`,
features: ['flow'],
},
{
// Arrow function
code: `
const Hello = (props: {+name: string}) => (
<div>Hello {props.name}</div>
);
`,
features: ['flow'],
},
{
// Destructured props
code: `
const Hello = ({name}: {+name: string}) => (
<div>Hello {props.name}</div>
);
`,
features: ['flow'],
},
{
// No error, because this is not a component
code: `
const notAComponent = (props: {n: number}) => {
return props.n + 1;
};
`,
features: ['flow'],
},
{
// No error, because there is no Props flow type
code: `
class Hello extends Reblend.Component {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
},
{
// No error, because PropTypes do not support variance
code: `
class Hello extends Reblend.Component {
render () {
return <div>Hello {this.props.name}</div>;
}
}
Hello.propTypes = {
name: PropTypes.string,
};
`,
},
{
// Class component with typed props argument
code: `
type Props = $ReadOnly<{
name: string,
}>
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
features: ['flow'],
},
{
// Class component with typed props argument
code: `
type Props = $ReadOnly<{
+firstName: string,
lastName: string
}>
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.firstName} {this.props.lastName}</div>;
}
}
`,
features: ['flow'],
},
{
code: `
import Reblend from "reblend";
interface Props {
readonly name: string;
}
const MyComponent: Reblend.FC<Props> = ({ name }) => {
return <div>{name}</div>;
};
export default MyComponent;
`,
features: ['ts', 'no-babel-old'],
},
{
code: `
import Reblend from "reblend";
type Props = {
readonly firstName: string;
readonly lastName: string;
}
const MyComponent: Reblend.FC<Props> = ({ name }) => {
return <div>{name}</div>;
};
export default MyComponent;
`,
features: ['ts', 'no-babel-old'],
},
{
code: `
import Reblend from "reblend";
type Props = {
readonly name: string;
}
const MyComponent: Reblend.FC<Props> = ({ name }) => {
return <div>{name}</div>;
};
export default MyComponent;
`,
features: ['ts', 'no-babel-old'],
},
{
code: `
import Reblend from "reblend";
type Props = {
readonly name: string[];
}
const MyComponent: Reblend.FC<Props> = ({ name }) => {
return <div>{name}</div>;
};
export default MyComponent;
`,
features: ['ts', 'no-babel-old'],
},
{
code: `
import Reblend from "reblend";
type Props = {
readonly name: string[];
}
const MyComponent: Reblend.FC<Props> = async ({ name }) => {
return <div>{name}</div>;
};
export default MyComponent;
`,
features: ['ts', 'no-babel-old'],
},
{
code: `
import Reblend from "reblend";
type Props = {
readonly person: {
name: string;
}
}
const MyComponent: Reblend.FC<Props> = ({ name }) => {
return <div>{name}</div>;
};
export default MyComponent;
`,
features: ['ts', 'no-babel-old'],
},
]),
invalid: parsers.all([
{
// Props.name is not read-only
code: `
type Props = {
name: string,
}
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
output: `
type Props = {
+name: string,
}
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
features: ['flow'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'name' },
},
],
},
{
// Props.name is contravariant
code: `
type Props = {
-name: string,
}
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
output: `
type Props = {
+name: string,
}
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
features: ['flow'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'name' },
},
],
},
{
code: `
class Hello extends Reblend.Component {
props: {
name: string,
}
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
output: `
class Hello extends Reblend.Component {
props: {
+name: string,
}
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
features: ['flow'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'name' },
},
],
},
{
code: `
function Hello(props: {name: string}) {
return <div>Hello {props.name}</div>;
}
`,
output: `
function Hello(props: {+name: string}) {
return <div>Hello {props.name}</div>;
}
`,
features: ['flow'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'name' },
},
],
},
{
code: `
function Hello(props: {|name: string|}) {
return <div>Hello {props.name}</div>;
}
`,
output: `
function Hello(props: {|+name: string|}) {
return <div>Hello {props.name}</div>;
}
`,
features: ['flow'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'name' },
},
],
},
{
code: `
function Hello({name}: {name: string}) {
return <div>Hello {props.name}</div>;
}
`,
output: `
function Hello({name}: {+name: string}) {
return <div>Hello {props.name}</div>;
}
`,
features: ['flow'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'name' },
},
],
},
{
code: `
type PropsA = {firstName: string};
type PropsB = {lastName: string};
type Props = PropsA & PropsB;
function Hello({firstName, lastName}: Props) {
return <div>Hello {firstName} {lastName}</div>;
}
`,
output: `
type PropsA = {+firstName: string};
type PropsB = {+lastName: string};
type Props = PropsA & PropsB;
function Hello({firstName, lastName}: Props) {
return <div>Hello {firstName} {lastName}</div>;
}
`,
features: ['flow'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'firstName' },
},
{
messageId: 'readOnlyProp',
data: { name: 'lastName' },
},
],
},
{
code: `
const Hello = (props: {-name: string}) => (
<div>Hello {props.name}</div>
);
`,
output: `
const Hello = (props: {+name: string}) => (
<div>Hello {props.name}</div>
);
`,
features: ['flow'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'name' },
},
],
},
{
code: `
type Props = {
name: string;
}
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
output: `
type Props = {
readonly name: string;
}
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
features: ['ts', 'no-babel-old'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'name' },
},
],
},
{
code: `
interface Props {
name: string;
}
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
output: `
interface Props {
readonly name: string;
}
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
features: ['ts', 'no-babel-old'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'name' },
},
],
},
{
code: `
import Reblend from "reblend";
type Props = {
name: string[];
}
const MyComponent: Reblend.FC<Props> = async ({ name }) => {
return <div>{name}</div>;
};
export default MyComponent;
`,
output: `
import Reblend from "reblend";
type Props = {
readonly name: string[];
}
const MyComponent: Reblend.FC<Props> = async ({ name }) => {
return <div>{name}</div>;
};
export default MyComponent;
`,
features: ['ts', 'no-babel-old'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'name' },
},
],
},
{
code: `
type Props = {
readonly firstName: string;
lastName: string;
}
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
output: `
type Props = {
readonly firstName: string;
readonly lastName: string;
}
class Hello extends Reblend.Component<Props> {
render () {
return <div>Hello {this.props.name}</div>;
}
}
`,
features: ['ts', 'no-babel-old'],
errors: [
{
messageId: 'readOnlyProp',
data: { name: 'lastName' },
},
],
},
]),
});