From a9f0b9569b0d13d8bea4bdfab14a3b1d2904e6f0 Mon Sep 17 00:00:00 2001 From: Tan Nguyen Date: Thu, 13 Sep 2018 13:54:38 +0700 Subject: [PATCH] [New] add `no-arrow-function-lifecycle` --- CHANGELOG.md | 3 + README.md | 1 + docs/rules/no-arrow-function-lifecycle.md | 41 + index.js | 1 + lib/rules/no-arrow-function-lifecycle.js | 80 ++ lib/rules/no-typos.js | 22 +- lib/util/lifecycleMethods.js | 30 + .../lib/rules/no-arrow-function-lifecycle.js | 1011 +++++++++++++++++ tests/lib/rules/no-typos.js | 30 + 9 files changed, 1200 insertions(+), 19 deletions(-) create mode 100644 docs/rules/no-arrow-function-lifecycle.md create mode 100644 lib/rules/no-arrow-function-lifecycle.js create mode 100644 lib/util/lifecycleMethods.js create mode 100644 tests/lib/rules/no-arrow-function-lifecycle.js diff --git a/CHANGELOG.md b/CHANGELOG.md index b9cdd6b7e8..dc66106ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Added * [`no-unused-class-component-methods`]: Handle unused class component methods ([#2166][] @jakeleventhal @pawelnvk) +* add [`no-arrow-function-lifecycle`] ([#1980][] @ngtan) [#2166]: https://github.com/yannickcr/eslint-plugin-react/pull/2166 +[#1980]: https://github.com/yannickcr/eslint-plugin-react/pull/1980 ## [7.26.1] - 2021.09.29 @@ -3465,6 +3467,7 @@ If you're still not using React 15 you can keep the old behavior by setting the [`no-access-state-in-setstate`]: docs/rules/no-access-state-in-setstate.md [`no-adjacent-inline-elements`]: docs/rules/no-adjacent-inline-elements.md [`no-array-index-key`]: docs/rules/no-array-index-key.md +[`no-arrow-function-lifecycle`]: docs/rules/no-arrow-function-lifecycle.md [`no-children-prop`]: docs/rules/no-children-prop.md [`no-comment-textnodes`]: docs/rules/jsx-no-comment-textnodes.md [`no-danger-with-children`]: docs/rules/no-danger-with-children.md diff --git a/README.md b/README.md index 09b4d59131..5fcc54a576 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ Enable the rules that you would like to use. | | | [react/no-access-state-in-setstate](docs/rules/no-access-state-in-setstate.md) | Reports when this.state is accessed within setState | | | | [react/no-adjacent-inline-elements](docs/rules/no-adjacent-inline-elements.md) | Prevent adjacent inline elements not separated by whitespace. | | | | [react/no-array-index-key](docs/rules/no-array-index-key.md) | Prevent usage of Array index in keys | +| | 🔧 | [react/no-arrow-function-lifecycle](docs/rules/no-arrow-function-lifecycle.md) | Lifecycle methods should be methods on the prototype, not class fields | | ✔ | | [react/no-children-prop](docs/rules/no-children-prop.md) | Prevent passing of children as props. | | | | [react/no-danger](docs/rules/no-danger.md) | Prevent usage of dangerous JSX props | | ✔ | | [react/no-danger-with-children](docs/rules/no-danger-with-children.md) | Report when a DOM element is using both children and dangerouslySetInnerHTML | diff --git a/docs/rules/no-arrow-function-lifecycle.md b/docs/rules/no-arrow-function-lifecycle.md new file mode 100644 index 0000000000..96d6b5b891 --- /dev/null +++ b/docs/rules/no-arrow-function-lifecycle.md @@ -0,0 +1,41 @@ +# Lifecycle methods should be methods on the prototype, not class fields (react/no-arrow-function-lifecycle) + +It is not neccessary to use arrow function for lifecycle methods. This makes things harder to test, conceptually less performant (although in practice, performance will not be affected, since most engines will optimize efficiently), and can break hot reloading patterns. + +## Rule Details + +The following patterns are considered warnings: + +```jsx +class Hello extends React.Component { + render = () => { + return
; + } +} + +var AnotherHello = createReactClass({ + render: () => { + return
; + }, +}); +``` + +The following patterns are **not** considered warnings: + +```jsx +class Hello extends React.Component { + render() { + return
; + } +} + +var AnotherHello = createReactClass({ + render() { + return
; + }, +}); + +``` +## When Not To Use It + +If you don't care about performance of your application or conceptual correctness of class property placement, you can disable this rule. diff --git a/index.js b/index.js index 700780b3a9..0072652f8b 100644 --- a/index.js +++ b/index.js @@ -57,6 +57,7 @@ const allRules = { 'no-access-state-in-setstate': require('./lib/rules/no-access-state-in-setstate'), 'no-adjacent-inline-elements': require('./lib/rules/no-adjacent-inline-elements'), 'no-array-index-key': require('./lib/rules/no-array-index-key'), + 'no-arrow-function-lifecycle': require('./lib/rules/no-arrow-function-lifecycle'), 'no-children-prop': require('./lib/rules/no-children-prop'), 'no-danger': require('./lib/rules/no-danger'), 'no-danger-with-children': require('./lib/rules/no-danger-with-children'), diff --git a/lib/rules/no-arrow-function-lifecycle.js b/lib/rules/no-arrow-function-lifecycle.js new file mode 100644 index 0000000000..32a26d2936 --- /dev/null +++ b/lib/rules/no-arrow-function-lifecycle.js @@ -0,0 +1,80 @@ +/** + * @fileoverview Lifecycle methods should be methods on the prototype, not class fields + * @author Tan Nguyen + */ + +'use strict'; + +const values = require('object.values'); + +const Components = require('../util/Components'); +const astUtil = require('../util/ast'); +const docsUrl = require('../util/docsUrl'); +const lifecycleMethods = require('../util/lifecycleMethods'); + +module.exports = { + meta: { + docs: { + description: 'Lifecycle methods should be methods on the prototype, not class fields', + category: 'Best Practices', + recommended: false, + url: docsUrl('no-arrow-function-lifecycle') + }, + schema: [], + fixable: 'code' + }, + + create: Components.detect((context, components, utils) => { + function getText(node) { + const params = node.value.params.map((p) => p.name); + + if (node.type === 'Property') { + return `: function(${params.join(', ')}) `; + } + + if (node.type === 'ClassProperty') { + return `(${params.join(', ')}) `; + } + + return null; + } + + /** + * @param {Array} properties list of component properties + */ + function reportNoArrowFunctionLifecycle(properties) { + properties.forEach((node) => { + const propertyName = astUtil.getPropertyName(node); + const nodeType = node.value.type; + const isLifecycleMethod = ( + node.static && !utils.isES5Component(node) + ? lifecycleMethods.static + : lifecycleMethods.instance + ).indexOf(propertyName) > -1; + + if (nodeType === 'ArrowFunctionExpression' && isLifecycleMethod) { + const range = [node.key.range[1], node.value.body.range[0]]; + const text = getText(node); + + context.report({ + node, + message: '{{propertyName}} is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.', + data: { + propertyName + }, + fix: (fixer) => fixer.replaceTextRange(range, text) + }); + } + }); + } + + return { + 'Program:exit'() { + values(components.list()).forEach((component) => { + const properties = astUtil.getComponentProperties(component.node); + reportNoArrowFunctionLifecycle(properties); + }); + } + }; + }) +}; diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js index 7022f27b88..a44b54b8ed 100644 --- a/lib/rules/no-typos.js +++ b/lib/rules/no-typos.js @@ -8,29 +8,13 @@ const PROP_TYPES = Object.keys(require('prop-types')); const Components = require('../util/Components'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +const lifecycleMethods = require('../util/lifecycleMethods'); // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ const STATIC_CLASS_PROPERTIES = ['propTypes', 'contextTypes', 'childContextTypes', 'defaultProps']; -const STATIC_LIFECYCLE_METHODS = ['getDerivedStateFromProps']; -const LIFECYCLE_METHODS = [ - 'getDerivedStateFromProps', - 'componentWillMount', - 'UNSAFE_componentWillMount', - 'componentDidMount', - 'componentWillReceiveProps', - 'UNSAFE_componentWillReceiveProps', - 'shouldComponentUpdate', - 'componentWillUpdate', - 'UNSAFE_componentWillUpdate', - 'getSnapshotBeforeUpdate', - 'componentDidUpdate', - 'componentDidCatch', - 'componentWillUnmount', - 'render' -]; const messages = { typoPropTypeChain: 'Typo in prop type chain qualifier: {{name}}', @@ -167,7 +151,7 @@ module.exports = { return; } - STATIC_LIFECYCLE_METHODS.forEach((method) => { + lifecycleMethods.static.forEach((method) => { if (!node.static && nodeKeyName.toLowerCase() === method.toLowerCase()) { report(context, messages.staticLifecycleMethod, 'staticLifecycleMethod', { node, @@ -178,7 +162,7 @@ module.exports = { } }); - LIFECYCLE_METHODS.forEach((method) => { + lifecycleMethods.instance.concat(lifecycleMethods.static).forEach((method) => { if (method.toLowerCase() === nodeKeyName.toLowerCase() && method !== nodeKeyName) { report(context, messages.typoLifecycleMethod, 'typoLifecycleMethod', { node, diff --git a/lib/util/lifecycleMethods.js b/lib/util/lifecycleMethods.js new file mode 100644 index 0000000000..7829d6d2b3 --- /dev/null +++ b/lib/util/lifecycleMethods.js @@ -0,0 +1,30 @@ +/** + * @fileoverview lifecycle methods + * @author Tan Nguyen + */ + +'use strict'; + +module.exports = { + instance: [ + 'getDefaultProps', + 'getInitialState', + 'getChildContext', + 'componentWillMount', + 'UNSAFE_componentWillMount', + 'componentDidMount', + 'componentWillReceiveProps', + 'UNSAFE_componentWillReceiveProps', + 'shouldComponentUpdate', + 'componentWillUpdate', + 'UNSAFE_componentWillUpdate', + 'getSnapshotBeforeUpdate', + 'componentDidUpdate', + 'componentDidCatch', + 'componentWillUnmount', + 'render' + ], + static: [ + 'getDerivedStateFromProps' + ] +}; diff --git a/tests/lib/rules/no-arrow-function-lifecycle.js b/tests/lib/rules/no-arrow-function-lifecycle.js new file mode 100644 index 0000000000..600332a7cb --- /dev/null +++ b/tests/lib/rules/no-arrow-function-lifecycle.js @@ -0,0 +1,1011 @@ +/** + * @fileoverview It is not necessary to use arrow function for lifecycle methods + * @author Tan Nguyen + */ + +'use strict'; + +const RuleTester = require('eslint').RuleTester; +const rule = require('../../../lib/rules/no-arrow-function-lifecycle'); + +const parsers = require('../../helpers/parsers'); + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true + } +}; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({parserOptions}); +ruleTester.run('no-arrow-function-lifecycle', rule, { + valid: [ + { + code: ` + var Hello = createReactClass({ + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + getDefaultProps: function() { return {}; }, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + getInitialState: function() { return {}; }, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + getChildContext: function() { return {}; }, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + getDerivedStateFromProps: function() { return {}; }, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentWillMount: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + UNSAFE_componentWillMount: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentDidMount: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentWillReceiveProps: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + UNSAFE_componentWillReceiveProps: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + shouldComponentUpdate: function() { return true; }, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentWillUpdate: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + UNSAFE_componentWillUpdate: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + getSnapshotBeforeUpdate: function() { return {}; }, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentDidUpdate: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentDidCatch: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentWillUnmount: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getDefaultProps() { return {}; } + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getInitialState() { return {}; } + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getChildContext() { return {}; } + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getDerivedStateFromProps() { return {}; } + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillMount() {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + UNSAFE_componentWillMount() {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentDidMount() {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillReceiveProps() {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + UNSAFE_componentWillReceiveProps() {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + shouldComponentUpdate() { return true; } + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillUpdate() {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + UNSAFE_componentWillUpdate() {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getSnapshotBeforeUpdate() { return {}; } + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentDidUpdate() {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentDidCatch() {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillUnmount() {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getDerivedStateFromProps = () => { return {}; } // not a lifecycle method + static getDerivedStateFromProps() {} + render() { return
; } + } + `, + parser: parsers.BABEL_ESLINT + }, + { + code: ` + var Hello = createReactClass({ + getDerivedStateFromProps: () => { return {}; }, + render: function() { return
; } + }); + ` + } + ], + + invalid: [ + { + code: ` + var Hello = createReactClass({ + render: () => { return
; } + }); + `, + errors: [{ + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + getDefaultProps: () => { return {}; }, + render: function() { return
; } + }); + `, + errors: [{ + message: 'getDefaultProps is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + getDefaultProps: function() { return {}; }, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + getInitialState: () => { return {}; }, + render: function() { return
; } + }); + `, + errors: [{ + message: 'getInitialState is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + getInitialState: function() { return {}; }, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + getChildContext: () => { return {}; }, + render: function() { return
; } + }); + `, + errors: [{ + message: 'getChildContext is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + getChildContext: function() { return {}; }, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentWillMount: () => {}, + render: function() { return
; } + }); + `, + errors: [{ + message: 'componentWillMount is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + componentWillMount: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + UNSAFE_componentWillMount: () => {}, + render: function() { return
; } + }); + `, + errors: [{ + message: 'UNSAFE_componentWillMount is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + UNSAFE_componentWillMount: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentDidMount: () => {}, + render: function() { return
; } + }); + `, + errors: [{ + message: 'componentDidMount is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + componentDidMount: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentWillReceiveProps: () => {}, + render: function() { return
; } + }); + `, + errors: [{ + message: 'componentWillReceiveProps is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + componentWillReceiveProps: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + UNSAFE_componentWillReceiveProps: () => {}, + render: function() { return
; } + }); + `, + errors: [{ + message: 'UNSAFE_componentWillReceiveProps is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + UNSAFE_componentWillReceiveProps: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + shouldComponentUpdate: () => { return true; }, + render: function() { return
; } + }); + `, + errors: [{ + message: 'shouldComponentUpdate is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + shouldComponentUpdate: function() { return true; }, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentWillUpdate: () => {}, + render: function() { return
; } + }); + `, + errors: [{ + message: 'componentWillUpdate is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + componentWillUpdate: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + UNSAFE_componentWillUpdate: () => {}, + render: function() { return
; } + }); + `, + errors: [{ + message: 'UNSAFE_componentWillUpdate is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + UNSAFE_componentWillUpdate: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + getSnapshotBeforeUpdate: () => { return {}; }, + render: function() { return
; } + }); + `, + errors: [{ + message: 'getSnapshotBeforeUpdate is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + getSnapshotBeforeUpdate: function() { return {}; }, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentDidUpdate: () => {}, + render: function() { return
; } + }); + `, + errors: [{ + message: 'componentDidUpdate is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + componentDidUpdate: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentDidCatch: () => {}, + render: function() { return
; } + }); + `, + errors: [{ + message: 'componentDidCatch is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + componentDidCatch: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + var Hello = createReactClass({ + componentWillUnmount: () => {}, + render: function() { return
; } + }); + `, + errors: [{ + message: 'componentWillUnmount is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + var Hello = createReactClass({ + componentWillUnmount: function() {}, + render: function() { return
; } + }); + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getDefaultProps = () => { return {}; } + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'getDefaultProps is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getDefaultProps() { return {}; } + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getInitialState = () => { return {}; } + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'getInitialState is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getInitialState() { return {}; } + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getChildContext = () => { return {}; } + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'getChildContext is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getChildContext() { return {}; } + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + static getDerivedStateFromProps = () => { return {}; } + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'getDerivedStateFromProps is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + static getDerivedStateFromProps() { return {}; } + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillMount = () => {} + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'componentWillMount is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillMount() {} + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + UNSAFE_componentWillMount = () => {} + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'UNSAFE_componentWillMount is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + UNSAFE_componentWillMount() {} + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentDidMount = () => {} + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'componentDidMount is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentDidMount() {} + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillReceiveProps = () => {} + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'componentWillReceiveProps is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillReceiveProps() {} + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + UNSAFE_componentWillReceiveProps = () => {} + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'UNSAFE_componentWillReceiveProps is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + UNSAFE_componentWillReceiveProps() {} + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + shouldComponentUpdate = () => { return true; } + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'shouldComponentUpdate is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + shouldComponentUpdate() { return true; } + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillUpdate = () => {} + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'componentWillUpdate is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillUpdate() {} + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + UNSAFE_componentWillUpdate = () => {} + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'UNSAFE_componentWillUpdate is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + UNSAFE_componentWillUpdate() {} + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getSnapshotBeforeUpdate = () => { return {}; } + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'getSnapshotBeforeUpdate is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + getSnapshotBeforeUpdate() { return {}; } + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentDidUpdate = (prevProps) => {} + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'componentDidUpdate is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentDidUpdate(prevProps) {} + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentDidCatch = () => {} + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'componentDidCatch is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentDidCatch() {} + render() { return
; } + } + ` + }, + { + code: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillUnmount = () => {} + render = () => { return
; } + } + `, + parser: parsers.BABEL_ESLINT, + errors: [{ + message: 'componentWillUnmount is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }, + { + message: 'render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.' + }], + output: ` + class Hello extends React.Component { + handleEventMethods = () => {} + componentWillUnmount() {} + render() { return
; } + } + ` + } + ] +}); diff --git a/tests/lib/rules/no-typos.js b/tests/lib/rules/no-typos.js index bf67682454..bf9239beee 100644 --- a/tests/lib/rules/no-typos.js +++ b/tests/lib/rules/no-typos.js @@ -1745,6 +1745,9 @@ ruleTester.run('no-typos', rule, { proptypes: {}, childcontexttypes: {}, contexttypes: {}, + getdefaultProps() { }, + getinitialState() { }, + getChildcontext() { }, ComponentWillMount() { }, ComponentDidMount() { }, ComponentWillReceiveProps() { }, @@ -1767,6 +1770,18 @@ ruleTester.run('no-typos', rule, { }, { messageId: 'typoPropDeclaration', type: 'Identifier' + }, { + messageId: 'typoLifecycleMethod', + data: {actual: 'getdefaultProps', expected: 'getDefaultProps'}, + type: 'Property' + }, { + messageId: 'typoLifecycleMethod', + data: {actual: 'getinitialState', expected: 'getInitialState'}, + type: 'Property' + }, { + messageId: 'typoLifecycleMethod', + data: {actual: 'getChildcontext', expected: 'getChildContext'}, + type: 'Property' }, { messageId: 'typoLifecycleMethod', data: {actual: 'ComponentWillMount', expected: 'componentWillMount'}, @@ -1833,6 +1848,9 @@ ruleTester.run('no-typos', rule, { proptypes: {}, childcontexttypes: {}, contexttypes: {}, + getdefaultProps() { }, + getinitialState() { }, + getChildcontext() { }, ComponentWillMount() { }, ComponentDidMount() { }, ComponentWillReceiveProps() { }, @@ -1856,6 +1874,18 @@ ruleTester.run('no-typos', rule, { }, { messageId: 'typoPropDeclaration', type: 'Identifier' + }, { + messageId: 'typoLifecycleMethod', + data: {actual: 'getdefaultProps', expected: 'getDefaultProps'}, + type: 'Property' + }, { + messageId: 'typoLifecycleMethod', + data: {actual: 'getinitialState', expected: 'getInitialState'}, + type: 'Property' + }, { + messageId: 'typoLifecycleMethod', + data: {actual: 'getChildcontext', expected: 'getChildContext'}, + type: 'Property' }, { messageId: 'typoLifecycleMethod', data: {actual: 'ComponentWillMount', expected: 'componentWillMount'},