diff --git a/docs/rules/no-typos.md b/docs/rules/no-typos.md index 87d0a94fd9..1a1c3d92ce 100644 --- a/docs/rules/no-typos.md +++ b/docs/rules/no-typos.md @@ -86,6 +86,9 @@ class MyComponent extends React.Component { } } +class MyComponent extends React.Component { + getDerivedStateFromProps() {} +} ``` The following patterns are **not** considered warnings: diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js index f8b6d24039..ec1d6a25d6 100644 --- a/lib/rules/no-typos.js +++ b/lib/rules/no-typos.js @@ -13,6 +13,7 @@ const docsUrl = require('../util/docsUrl'); // ------------------------------------------------------------------------------ const STATIC_CLASS_PROPERTIES = ['propTypes', 'contextTypes', 'childContextTypes', 'defaultProps']; +const STATIC_LIFECYCLE_METHODS = ['getDerivedStateFromProps']; const LIFECYCLE_METHODS = [ 'getDerivedStateFromProps', 'componentWillMount', @@ -49,7 +50,8 @@ module.exports = { if (node.name !== 'isRequired') { context.report({ node, - message: `Typo in prop type chain qualifier: ${node.name}` + message: 'Typo in prop type chain qualifier: {{name}}', + data: {name: node.name} }); } } @@ -58,7 +60,8 @@ module.exports = { if (node.name && !PROP_TYPES.some(propTypeName => propTypeName === node.name)) { context.report({ node, - message: `Typo in declared prop type: ${node.name}` + message: 'Typo in declared prop type: {{name}}', + data: {name: node.name} }); } } @@ -143,15 +146,26 @@ module.exports = { } function reportErrorIfLifecycleMethodCasingTypo(node) { - LIFECYCLE_METHODS.forEach((method) => { - let nodeKeyName = node.key.name; - if (node.key.type === 'Literal') { - nodeKeyName = node.key.value; + let nodeKeyName = node.key.name; + if (node.key.type === 'Literal') { + nodeKeyName = node.key.value; + } + + STATIC_LIFECYCLE_METHODS.forEach((method) => { + if (!node.static && nodeKeyName.toLowerCase() === method.toLowerCase()) { + context.report({ + node, + message: `Lifecycle method should be static: ${nodeKeyName}` + }); } + }); + + LIFECYCLE_METHODS.forEach((method) => { if (method.toLowerCase() === nodeKeyName.toLowerCase() && method !== nodeKeyName) { context.report({ node, - message: 'Typo in component lifecycle method declaration' + message: 'Typo in component lifecycle method declaration: {{actual}} should be {{expected}}', + data: {actual: nodeKeyName, expected: method} }); } }); diff --git a/tests/lib/rules/no-typos.js b/tests/lib/rules/no-typos.js index 8e6b79f91b..9a7b6f751c 100644 --- a/tests/lib/rules/no-typos.js +++ b/tests/lib/rules/no-typos.js @@ -27,7 +27,8 @@ const parserOptions = { const ERROR_MESSAGE = 'Typo in static class property declaration'; const ERROR_MESSAGE_ES5 = 'Typo in property declaration'; -const ERROR_MESSAGE_LIFECYCLE_METHOD = 'Typo in component lifecycle method declaration'; +const ERROR_MESSAGE_LIFECYCLE_METHOD = (actual, expected) => `Typo in component lifecycle method declaration: ${actual} should be ${expected}`; +const ERROR_MESSAGE_STATIC = method => `Lifecycle method should be static: ${method}`; const ruleTester = new RuleTester(); ruleTester.run('no-typos', rule, { @@ -190,6 +191,7 @@ ruleTester.run('no-typos', rule, { }, { code: ` class Hello extends React.Component { + static getDerivedStateFromProps() { } componentWillMount() { } componentDidMount() { } componentWillReceiveProps() { } @@ -840,43 +842,43 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('GetDerivedStateFromProps', 'getDerivedStateFromProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillMount', 'UNSAFE_componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillReceiveProps', 'UNSAFE_componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillUpdate', 'UNSAFE_componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('GetSnapshotBeforeUpdate', 'getSnapshotBeforeUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidCatch', 'componentDidCatch'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUnmount', 'componentWillUnmount'), type: 'MethodDefinition' }] }, { @@ -902,47 +904,47 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Getderivedstatefromprops', 'getDerivedStateFromProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillmount', 'componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillmount', 'UNSAFE_componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidmount', 'componentDidMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillreceiveprops', 'componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillreceiveprops', 'UNSAFE_componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Shouldcomponentupdate', 'shouldComponentUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillupdate', 'componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillupdate', 'UNSAFE_componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Getsnapshotbeforeupdate', 'getSnapshotBeforeUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidupdate', 'componentDidUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidcatch', 'componentDidCatch'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillunmount', 'componentWillUnmount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, - type: 'MethodDefinition' + message: ERROR_MESSAGE_LIFECYCLE_METHOD('Render', 'render'), + nodeType: 'MethodDefinition' }] }, { code: ` @@ -967,43 +969,43 @@ ruleTester.run('no-typos', rule, { `, parserOptions, errors: [{ - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('getderivedstatefromprops', 'getDerivedStateFromProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillmount', 'componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillmount', 'UNSAFE_componentWillMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidmount', 'componentDidMount'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillreceiveprops', 'componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillreceiveprops', 'UNSAFE_componentWillReceiveProps'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('shouldcomponentupdate', 'shouldComponentUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillupdate', 'componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillupdate', 'UNSAFE_componentWillUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('getsnapshotbeforeupdate', 'getSnapshotBeforeUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidupdate', 'componentDidUpdate'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidcatch', 'componentDidCatch'), type: 'MethodDefinition' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillunmount', 'componentWillUnmount'), type: 'MethodDefinition' }] }, { @@ -1583,27 +1585,54 @@ ruleTester.run('no-typos', rule, { message: ERROR_MESSAGE_ES5, type: 'Identifier' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUnmount', 'componentWillUnmount'), type: 'Property' }] + }, { + code: ` + class Hello extends React.Component { + getDerivedStateFromProps() { } + } + `, + parser: parsers.BABEL_ESLINT, + parserOptions, + errors: [{ + message: ERROR_MESSAGE_STATIC('getDerivedStateFromProps'), + nodeType: 'MethodDefinition' + }] + }, { + code: ` + class Hello extends React.Component { + GetDerivedStateFromProps() { } + } + `, + parser: parsers.BABEL_ESLINT, + parserOptions, + errors: [{ + message: ERROR_MESSAGE_STATIC('GetDerivedStateFromProps'), + nodeType: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD('GetDerivedStateFromProps', 'getDerivedStateFromProps'), + nodeType: 'MethodDefinition' + }] }, { code: ` import React from 'react'; @@ -1635,25 +1664,25 @@ ruleTester.run('no-typos', rule, { message: ERROR_MESSAGE_ES5, type: 'Identifier' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'), type: 'Property' }, { - message: ERROR_MESSAGE_LIFECYCLE_METHOD, + message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUnmount', 'componentWillUnmount'), type: 'Property' }] /*