From 13fddef1854adb9100d7912e3eda71353855393c Mon Sep 17 00:00:00 2001 From: Haridu Senadeera Date: Tue, 11 Jul 2017 17:24:34 -0500 Subject: [PATCH 1/5] add lifecycle method typos --- docs/rules/no-typos.md | 43 ++++++++++++++++-- lib/rules/no-typos.js | 28 ++++++++++++ tests/lib/rules/no-typos.js | 87 +++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 3 deletions(-) diff --git a/docs/rules/no-typos.md b/docs/rules/no-typos.md index a7dd2b9287..7861797fcc 100644 --- a/docs/rules/no-typos.md +++ b/docs/rules/no-typos.md @@ -1,11 +1,13 @@ # Prevents common casing typos (react/no-typos) -Ensure no casing typos were made declaring static class properties +Ensure no casing typos were made declaring static class properties and lifecycle methods. ## Rule Details -This rule checks whether the declared static class properties related to React components -do not contain any typos. It currently makes sure that the following class properties have +This rule checks whether the declared static class properties and lifecycle methods related to React components +do not contain any typos. + +It currently makes sure that the following class properties have no casing typos: * propTypes @@ -13,6 +15,17 @@ no casing typos: * childContextTypes * defaultProps +and the following react lifecycle methods: + +* componentWillMount +* componentDidMount +* componentWillReceiveProps +* shouldComponentUpdate +* componentWillUpdate +* componentDidUpdate +* componentWillUnmount + + The following patterns are considered warnings: ```js @@ -47,6 +60,18 @@ class MyComponent extends React.Component { class MyComponent extends React.Component { static defaultprops = {} } + +class MyComponent extends React.Component { + componentwillMount() {} +} + +class MyComponent extends React.Component { + ComponentWillReceiveProps() {} +} + +class MyComponent extends React.Component { + componentdidupdate() {} +} ``` The following patterns are not considered warnings: @@ -67,4 +92,16 @@ class MyComponent extends React.Component { class MyComponent extends React.Component { static defaultProps = {} } + +class MyComponent extends React.Component { + componentWillMount() {} +} + +class MyComponent extends React.Component { + componentWillReceiveProps() {} +} + +class MyComponent extends React.Component { + componentDidUpdate() {} +} ``` diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js index b4e8bdf2af..de96241606 100644 --- a/lib/rules/no-typos.js +++ b/lib/rules/no-typos.js @@ -10,6 +10,15 @@ const Components = require('../util/Components'); // ------------------------------------------------------------------------------ const STATIC_CLASS_PROPERTIES = ['propTypes', 'contextTypes', 'childContextTypes', 'defaultProps']; +const LIFECYCLE_METHODS = [ + 'componentWillMount', + 'componentDidMount', + 'componentWillReceiveProps', + 'shouldComponentUpdate', + 'componentWillUpdate', + 'componentDidUpdate', + 'componentWillUnmount' +]; module.exports = { meta: { @@ -33,6 +42,17 @@ module.exports = { }); } + function reportErrorIfLifecycleMethodCasingTypo(node) { + LIFECYCLE_METHODS.forEach(function(method) { + if (method.toLowerCase() === node.key.name.toLowerCase() && method !== node.key.name) { + context.report({ + node: node, + message: 'Typo in component lifecycle method declaration' + }); + } + }); + } + return { ClassProperty: function(node) { if (!node.static || !utils.isES6Component(node.parent.parent)) { @@ -54,6 +74,14 @@ module.exports = { const propertyName = node.property.name; reportErrorIfCasingTypo(node, propertyName); } + }, + + MethodDefinition: function (node) { + if (!utils.isES6Component(node.parent.parent) || utils.isReturningJSX(node.parent.parent)) { + return; + } + + reportErrorIfLifecycleMethodCasingTypo(node); } }; }) diff --git a/tests/lib/rules/no-typos.js b/tests/lib/rules/no-typos.js index 7883352453..422672f41c 100644 --- a/tests/lib/rules/no-typos.js +++ b/tests/lib/rules/no-typos.js @@ -22,6 +22,7 @@ const parserOptions = { // ----------------------------------------------------------------------------- const ERROR_MESSAGE = 'Typo in static class property declaration'; +const ERROR_MESSAGE_LIFECYCLE_METHOD = 'Typo in component lifecycle method declaration'; const ruleTester = new RuleTester(); ruleTester.run('no-typos', rule, { @@ -181,6 +182,49 @@ ruleTester.run('no-typos', rule, { 'First[defautProps] = {};' ].join('\n'), parserOptions: parserOptions + }, { + code: [ + 'class Hello extends React.Component {', + ' componentDidUpdate() { }', + ' render() {', + ' return
Hello {this.props.name}
;', + ' }', + '}' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'class Hello extends React.Component {', + ' componentWillMount() { }', + ' componentDidUpdate() { }', + ' render() {', + ' return
Hello {this.props.name}
;', + ' }', + '}' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'class Hello extends React.Component {', + ' componentWillUnmount() { }', + ' componentWillMount() { }', + ' render() {', + ' return
Hello {this.props.name}
;', + ' }', + '}' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'class Hello extends React.Component {', + ' shouldComponentUpdate() { }', + ' componentWillReceiveProps() { }', + ' render() {', + ' return
Hello {this.props.name}
;', + ' }', + '}' + ].join('\n'), + parserOptions: parserOptions }], invalid: [{ @@ -367,5 +411,48 @@ ruleTester.run('no-typos', rule, { ].join('\n'), parserOptions: parserOptions, errors: [{message: ERROR_MESSAGE}] + }, { + code: [ + 'class Hello extends React.Component {', + ' ComponentDidUpdate() { }', + ' render() {', + ' return
Hello {this.props.name}
;', + ' }', + '}' + ].join('\n'), + parserOptions: parserOptions, + errors: [{ + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }] + }, { + code: [ + 'class Hello extends React.Component {', + ' componentwillreceiveprops() { }', + ' render() {', + ' return
Hello {this.props.name}
;', + ' }', + '}' + ].join('\n'), + parserOptions: parserOptions, + errors: [{ + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }] + }, { + code: [ + 'class Hello extends React.Component {', + ' componentWillReceiveProps() { }', + ' componentWillupdate() { }', + ' render() {', + ' return
Hello {this.props.name}
;', + ' }', + '}' + ].join('\n'), + parserOptions: parserOptions, + errors: [{ + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }] }] }); From 968f7459619d78676d1cc9296a248bc4d44b7954 Mon Sep 17 00:00:00 2001 From: Haridu Senadeera Date: Wed, 12 Jul 2017 11:33:28 -0500 Subject: [PATCH 2/5] renamed function --- lib/rules/no-typos.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js index de96241606..e5bc9807d6 100644 --- a/lib/rules/no-typos.js +++ b/lib/rules/no-typos.js @@ -31,7 +31,7 @@ module.exports = { }, create: Components.detect((context, components, utils) => { - function reportErrorIfCasingTypo(node, propertyName) { + function reportErrorIfClassPropertyCasingTypo(node, propertyName) { STATIC_CLASS_PROPERTIES.forEach(CLASS_PROP => { if (propertyName && CLASS_PROP.toLowerCase() === propertyName.toLowerCase() && CLASS_PROP !== propertyName) { context.report({ @@ -43,7 +43,7 @@ module.exports = { } function reportErrorIfLifecycleMethodCasingTypo(node) { - LIFECYCLE_METHODS.forEach(function(method) { + LIFECYCLE_METHODS.forEach(method => { if (method.toLowerCase() === node.key.name.toLowerCase() && method !== node.key.name) { context.report({ node: node, @@ -61,7 +61,7 @@ module.exports = { const tokens = context.getFirstTokens(node, 2); const propertyName = tokens[1].value; - reportErrorIfCasingTypo(node, propertyName); + reportErrorIfClassPropertyCasingTypo(node, propertyName); }, MemberExpression: function(node) { @@ -72,7 +72,7 @@ module.exports = { (utils.isES6Component(relatedComponent.node) || utils.isReturningJSX(relatedComponent.node)) ) { const propertyName = node.property.name; - reportErrorIfCasingTypo(node, propertyName); + reportErrorIfClassPropertyCasingTypo(node, propertyName); } }, From 2ee095e17a4a5a66a42e2daaad4222274d357dac Mon Sep 17 00:00:00 2001 From: Haridu Senadeera Date: Wed, 12 Jul 2017 13:07:57 -0500 Subject: [PATCH 3/5] added render method --- docs/rules/no-typos.md | 1 + lib/rules/no-typos.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/rules/no-typos.md b/docs/rules/no-typos.md index 7861797fcc..c61cb70496 100644 --- a/docs/rules/no-typos.md +++ b/docs/rules/no-typos.md @@ -24,6 +24,7 @@ and the following react lifecycle methods: * componentWillUpdate * componentDidUpdate * componentWillUnmount +* render The following patterns are considered warnings: diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js index e5bc9807d6..1e732dfed0 100644 --- a/lib/rules/no-typos.js +++ b/lib/rules/no-typos.js @@ -17,7 +17,8 @@ const LIFECYCLE_METHODS = [ 'shouldComponentUpdate', 'componentWillUpdate', 'componentDidUpdate', - 'componentWillUnmount' + 'componentWillUnmount', + 'render' ]; module.exports = { From 75711e5c634a6af90c77d18c75f2a742b1a5cc02 Mon Sep 17 00:00:00 2001 From: Haridu Senadeera Date: Wed, 12 Jul 2017 14:45:24 -0500 Subject: [PATCH 4/5] updated tests --- tests/lib/rules/no-typos.js | 129 ++++++++++++++++++++++++++++++------ 1 file changed, 109 insertions(+), 20 deletions(-) diff --git a/tests/lib/rules/no-typos.js b/tests/lib/rules/no-typos.js index 422672f41c..1e0efa87c5 100644 --- a/tests/lib/rules/no-typos.js +++ b/tests/lib/rules/no-typos.js @@ -185,7 +185,13 @@ ruleTester.run('no-typos', rule, { }, { code: [ 'class Hello extends React.Component {', + ' componentWillMount() { }', + ' componentDidMount() { }', + ' componentWillReceiveProps() { }', + ' shouldComponentUpdate() { }', + ' componentWillUpdate() { }', ' componentDidUpdate() { }', + ' componentWillUnmount() { }', ' render() {', ' return
Hello {this.props.name}
;', ' }', @@ -194,34 +200,43 @@ ruleTester.run('no-typos', rule, { parserOptions: parserOptions }, { code: [ - 'class Hello extends React.Component {', + 'class MyClass {', ' componentWillMount() { }', + ' componentDidMount() { }', + ' componentWillReceiveProps() { }', + ' shouldComponentUpdate() { }', + ' componentWillUpdate() { }', ' componentDidUpdate() { }', - ' render() {', - ' return
Hello {this.props.name}
;', - ' }', + ' componentWillUnmount() { }', + ' render() { }', '}' ].join('\n'), parserOptions: parserOptions }, { code: [ - 'class Hello extends React.Component {', - ' componentWillUnmount() { }', - ' componentWillMount() { }', - ' render() {', - ' return
Hello {this.props.name}
;', - ' }', + 'class MyClass {', + ' componentwillmount() { }', + ' componentdidmount() { }', + ' componentwillreceiveprops() { }', + ' shouldcomponentupdate() { }', + ' componentwillupdate() { }', + ' componentdidupdate() { }', + ' componentwillUnmount() { }', + ' render() { }', '}' ].join('\n'), parserOptions: parserOptions }, { code: [ - 'class Hello extends React.Component {', - ' shouldComponentUpdate() { }', - ' componentWillReceiveProps() { }', - ' render() {', - ' return
Hello {this.props.name}
;', - ' }', + 'class MyClass {', + ' Componentwillmount() { }', + ' Componentdidmount() { }', + ' Componentwillreceiveprops() { }', + ' Shouldcomponentupdate() { }', + ' Componentwillupdate() { }', + ' Componentdidupdate() { }', + ' ComponentwillUnmount() { }', + ' Render() { }', '}' ].join('\n'), parserOptions: parserOptions @@ -414,7 +429,13 @@ ruleTester.run('no-typos', rule, { }, { code: [ 'class Hello extends React.Component {', + ' ComponentWillMount() { }', + ' ComponentDidMount() { }', + ' ComponentWillReceiveProps() { }', + ' ShouldComponentUpdate() { }', + ' ComponentWillUpdate() { }', ' ComponentDidUpdate() { }', + ' ComponentWillUnmount() { }', ' render() {', ' return
Hello {this.props.name}
;', ' }', @@ -424,12 +445,36 @@ ruleTester.run('no-typos', rule, { errors: [{ message: ERROR_MESSAGE_LIFECYCLE_METHOD, type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' }] }, { code: [ 'class Hello extends React.Component {', - ' componentwillreceiveprops() { }', - ' render() {', + ' Componentwillmount() { }', + ' Componentdidmount() { }', + ' Componentwillreceiveprops() { }', + ' Shouldcomponentupdate() { }', + ' Componentwillupdate() { }', + ' Componentdidupdate() { }', + ' Componentwillunmount() { }', + ' Render() {', ' return
Hello {this.props.name}
;', ' }', '}' @@ -438,12 +483,38 @@ ruleTester.run('no-typos', rule, { errors: [{ message: ERROR_MESSAGE_LIFECYCLE_METHOD, type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' }] }, { code: [ 'class Hello extends React.Component {', - ' componentWillReceiveProps() { }', - ' componentWillupdate() { }', + ' componentwillmount() { }', + ' componentdidmount() { }', + ' componentwillreceiveprops() { }', + ' shouldcomponentupdate() { }', + ' componentwillupdate() { }', + ' componentdidupdate() { }', + ' componentwillunmount() { }', ' render() {', ' return
Hello {this.props.name}
;', ' }', @@ -453,6 +524,24 @@ ruleTester.run('no-typos', rule, { errors: [{ message: ERROR_MESSAGE_LIFECYCLE_METHOD, type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' + }, { + message: ERROR_MESSAGE_LIFECYCLE_METHOD, + type: 'MethodDefinition' }] }] }); From eaf1ee900795158d03f01168ef77d66505570c1f Mon Sep 17 00:00:00 2001 From: Haridu Senadeera Date: Wed, 12 Jul 2017 14:57:48 -0500 Subject: [PATCH 5/5] removed isReturningJSX check --- lib/rules/no-typos.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/no-typos.js b/lib/rules/no-typos.js index 1e732dfed0..60426c4ed6 100644 --- a/lib/rules/no-typos.js +++ b/lib/rules/no-typos.js @@ -78,7 +78,7 @@ module.exports = { }, MethodDefinition: function (node) { - if (!utils.isES6Component(node.parent.parent) || utils.isReturningJSX(node.parent.parent)) { + if (!utils.isES6Component(node.parent.parent)) { return; }