Skip to content

Commit

Permalink
Merge pull request #1294 from haridusenadeera/master
Browse files Browse the repository at this point in the history
Add lifecycle methods check to no-typos rule
  • Loading branch information
ljharb committed Jul 30, 2017
2 parents ed72341 + eaf1ee9 commit a19eec6
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 6 deletions.
44 changes: 41 additions & 3 deletions docs/rules/no-typos.md
@@ -1,18 +1,32 @@
# 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
* contextTypes
* childContextTypes
* defaultProps

and the following react lifecycle methods:

* componentWillMount
* componentDidMount
* componentWillReceiveProps
* shouldComponentUpdate
* componentWillUpdate
* componentDidUpdate
* componentWillUnmount
* render


The following patterns are considered warnings:

```js
Expand Down Expand Up @@ -47,6 +61,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:
Expand All @@ -67,4 +93,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() {}
}
```
35 changes: 32 additions & 3 deletions lib/rules/no-typos.js
Expand Up @@ -10,6 +10,16 @@ const Components = require('../util/Components');
// ------------------------------------------------------------------------------

const STATIC_CLASS_PROPERTIES = ['propTypes', 'contextTypes', 'childContextTypes', 'defaultProps'];
const LIFECYCLE_METHODS = [
'componentWillMount',
'componentDidMount',
'componentWillReceiveProps',
'shouldComponentUpdate',
'componentWillUpdate',
'componentDidUpdate',
'componentWillUnmount',
'render'
];

module.exports = {
meta: {
Expand All @@ -22,7 +32,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({
Expand All @@ -33,6 +43,17 @@ module.exports = {
});
}

function reportErrorIfLifecycleMethodCasingTypo(node) {
LIFECYCLE_METHODS.forEach(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)) {
Expand All @@ -41,7 +62,7 @@ module.exports = {

const tokens = context.getFirstTokens(node, 2);
const propertyName = tokens[1].value;
reportErrorIfCasingTypo(node, propertyName);
reportErrorIfClassPropertyCasingTypo(node, propertyName);
},

MemberExpression: function(node) {
Expand All @@ -52,8 +73,16 @@ module.exports = {
(utils.isES6Component(relatedComponent.node) || utils.isReturningJSX(relatedComponent.node))
) {
const propertyName = node.property.name;
reportErrorIfCasingTypo(node, propertyName);
reportErrorIfClassPropertyCasingTypo(node, propertyName);
}
},

MethodDefinition: function (node) {
if (!utils.isES6Component(node.parent.parent)) {
return;
}

reportErrorIfLifecycleMethodCasingTypo(node);
}
};
})
Expand Down
176 changes: 176 additions & 0 deletions tests/lib/rules/no-typos.js
Expand Up @@ -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, {
Expand Down Expand Up @@ -181,6 +182,64 @@ ruleTester.run('no-typos', rule, {
'First[defautProps] = {};'
].join('\n'),
parserOptions: parserOptions
}, {
code: [
'class Hello extends React.Component {',
' componentWillMount() { }',
' componentDidMount() { }',
' componentWillReceiveProps() { }',
' shouldComponentUpdate() { }',
' componentWillUpdate() { }',
' componentDidUpdate() { }',
' componentWillUnmount() { }',
' render() {',
' return <div>Hello {this.props.name}</div>;',
' }',
'}'
].join('\n'),
parserOptions: parserOptions
}, {
code: [
'class MyClass {',
' componentWillMount() { }',
' componentDidMount() { }',
' componentWillReceiveProps() { }',
' shouldComponentUpdate() { }',
' componentWillUpdate() { }',
' componentDidUpdate() { }',
' componentWillUnmount() { }',
' render() { }',
'}'
].join('\n'),
parserOptions: parserOptions
}, {
code: [
'class MyClass {',
' componentwillmount() { }',
' componentdidmount() { }',
' componentwillreceiveprops() { }',
' shouldcomponentupdate() { }',
' componentwillupdate() { }',
' componentdidupdate() { }',
' componentwillUnmount() { }',
' render() { }',
'}'
].join('\n'),
parserOptions: parserOptions
}, {
code: [
'class MyClass {',
' Componentwillmount() { }',
' Componentdidmount() { }',
' Componentwillreceiveprops() { }',
' Shouldcomponentupdate() { }',
' Componentwillupdate() { }',
' Componentdidupdate() { }',
' ComponentwillUnmount() { }',
' Render() { }',
'}'
].join('\n'),
parserOptions: parserOptions
}],

invalid: [{
Expand Down Expand Up @@ -367,5 +426,122 @@ ruleTester.run('no-typos', rule, {
].join('\n'),
parserOptions: parserOptions,
errors: [{message: ERROR_MESSAGE}]
}, {
code: [
'class Hello extends React.Component {',
' ComponentWillMount() { }',
' ComponentDidMount() { }',
' ComponentWillReceiveProps() { }',
' ShouldComponentUpdate() { }',
' ComponentWillUpdate() { }',
' ComponentDidUpdate() { }',
' ComponentWillUnmount() { }',
' render() {',
' return <div>Hello {this.props.name}</div>;',
' }',
'}'
].join('\n'),
parserOptions: parserOptions,
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 {',
' Componentwillmount() { }',
' Componentdidmount() { }',
' Componentwillreceiveprops() { }',
' Shouldcomponentupdate() { }',
' Componentwillupdate() { }',
' Componentdidupdate() { }',
' Componentwillunmount() { }',
' Render() {',
' return <div>Hello {this.props.name}</div>;',
' }',
'}'
].join('\n'),
parserOptions: parserOptions,
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 {',
' componentwillmount() { }',
' componentdidmount() { }',
' componentwillreceiveprops() { }',
' shouldcomponentupdate() { }',
' componentwillupdate() { }',
' componentdidupdate() { }',
' componentwillunmount() { }',
' render() {',
' return <div>Hello {this.props.name}</div>;',
' }',
'}'
].join('\n'),
parserOptions: parserOptions,
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'
}]
}]
});

0 comments on commit a19eec6

Please sign in to comment.