Skip to content

Commit a19eec6

Browse files
authored
Merge pull request #1294 from haridusenadeera/master
Add lifecycle methods check to no-typos rule
2 parents ed72341 + eaf1ee9 commit a19eec6

File tree

3 files changed

+249
-6
lines changed

3 files changed

+249
-6
lines changed

docs/rules/no-typos.md

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
# Prevents common casing typos (react/no-typos)
22

3-
Ensure no casing typos were made declaring static class properties
3+
Ensure no casing typos were made declaring static class properties and lifecycle methods.
44

55
## Rule Details
66

7-
This rule checks whether the declared static class properties related to React components
8-
do not contain any typos. It currently makes sure that the following class properties have
7+
This rule checks whether the declared static class properties and lifecycle methods related to React components
8+
do not contain any typos.
9+
10+
It currently makes sure that the following class properties have
911
no casing typos:
1012

1113
* propTypes
1214
* contextTypes
1315
* childContextTypes
1416
* defaultProps
1517

18+
and the following react lifecycle methods:
19+
20+
* componentWillMount
21+
* componentDidMount
22+
* componentWillReceiveProps
23+
* shouldComponentUpdate
24+
* componentWillUpdate
25+
* componentDidUpdate
26+
* componentWillUnmount
27+
* render
28+
29+
1630
The following patterns are considered warnings:
1731

1832
```js
@@ -47,6 +61,18 @@ class MyComponent extends React.Component {
4761
class MyComponent extends React.Component {
4862
static defaultprops = {}
4963
}
64+
65+
class MyComponent extends React.Component {
66+
componentwillMount() {}
67+
}
68+
69+
class MyComponent extends React.Component {
70+
ComponentWillReceiveProps() {}
71+
}
72+
73+
class MyComponent extends React.Component {
74+
componentdidupdate() {}
75+
}
5076
```
5177

5278
The following patterns are not considered warnings:
@@ -67,4 +93,16 @@ class MyComponent extends React.Component {
6793
class MyComponent extends React.Component {
6894
static defaultProps = {}
6995
}
96+
97+
class MyComponent extends React.Component {
98+
componentWillMount() {}
99+
}
100+
101+
class MyComponent extends React.Component {
102+
componentWillReceiveProps() {}
103+
}
104+
105+
class MyComponent extends React.Component {
106+
componentDidUpdate() {}
107+
}
70108
```

lib/rules/no-typos.js

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ const Components = require('../util/Components');
1010
// ------------------------------------------------------------------------------
1111

1212
const STATIC_CLASS_PROPERTIES = ['propTypes', 'contextTypes', 'childContextTypes', 'defaultProps'];
13+
const LIFECYCLE_METHODS = [
14+
'componentWillMount',
15+
'componentDidMount',
16+
'componentWillReceiveProps',
17+
'shouldComponentUpdate',
18+
'componentWillUpdate',
19+
'componentDidUpdate',
20+
'componentWillUnmount',
21+
'render'
22+
];
1323

1424
module.exports = {
1525
meta: {
@@ -22,7 +32,7 @@ module.exports = {
2232
},
2333

2434
create: Components.detect((context, components, utils) => {
25-
function reportErrorIfCasingTypo(node, propertyName) {
35+
function reportErrorIfClassPropertyCasingTypo(node, propertyName) {
2636
STATIC_CLASS_PROPERTIES.forEach(CLASS_PROP => {
2737
if (propertyName && CLASS_PROP.toLowerCase() === propertyName.toLowerCase() && CLASS_PROP !== propertyName) {
2838
context.report({
@@ -33,6 +43,17 @@ module.exports = {
3343
});
3444
}
3545

46+
function reportErrorIfLifecycleMethodCasingTypo(node) {
47+
LIFECYCLE_METHODS.forEach(method => {
48+
if (method.toLowerCase() === node.key.name.toLowerCase() && method !== node.key.name) {
49+
context.report({
50+
node: node,
51+
message: 'Typo in component lifecycle method declaration'
52+
});
53+
}
54+
});
55+
}
56+
3657
return {
3758
ClassProperty: function(node) {
3859
if (!node.static || !utils.isES6Component(node.parent.parent)) {
@@ -41,7 +62,7 @@ module.exports = {
4162

4263
const tokens = context.getFirstTokens(node, 2);
4364
const propertyName = tokens[1].value;
44-
reportErrorIfCasingTypo(node, propertyName);
65+
reportErrorIfClassPropertyCasingTypo(node, propertyName);
4566
},
4667

4768
MemberExpression: function(node) {
@@ -52,8 +73,16 @@ module.exports = {
5273
(utils.isES6Component(relatedComponent.node) || utils.isReturningJSX(relatedComponent.node))
5374
) {
5475
const propertyName = node.property.name;
55-
reportErrorIfCasingTypo(node, propertyName);
76+
reportErrorIfClassPropertyCasingTypo(node, propertyName);
77+
}
78+
},
79+
80+
MethodDefinition: function (node) {
81+
if (!utils.isES6Component(node.parent.parent)) {
82+
return;
5683
}
84+
85+
reportErrorIfLifecycleMethodCasingTypo(node);
5786
}
5887
};
5988
})

tests/lib/rules/no-typos.js

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const parserOptions = {
2222
// -----------------------------------------------------------------------------
2323

2424
const ERROR_MESSAGE = 'Typo in static class property declaration';
25+
const ERROR_MESSAGE_LIFECYCLE_METHOD = 'Typo in component lifecycle method declaration';
2526

2627
const ruleTester = new RuleTester();
2728
ruleTester.run('no-typos', rule, {
@@ -181,6 +182,64 @@ ruleTester.run('no-typos', rule, {
181182
'First[defautProps] = {};'
182183
].join('\n'),
183184
parserOptions: parserOptions
185+
}, {
186+
code: [
187+
'class Hello extends React.Component {',
188+
' componentWillMount() { }',
189+
' componentDidMount() { }',
190+
' componentWillReceiveProps() { }',
191+
' shouldComponentUpdate() { }',
192+
' componentWillUpdate() { }',
193+
' componentDidUpdate() { }',
194+
' componentWillUnmount() { }',
195+
' render() {',
196+
' return <div>Hello {this.props.name}</div>;',
197+
' }',
198+
'}'
199+
].join('\n'),
200+
parserOptions: parserOptions
201+
}, {
202+
code: [
203+
'class MyClass {',
204+
' componentWillMount() { }',
205+
' componentDidMount() { }',
206+
' componentWillReceiveProps() { }',
207+
' shouldComponentUpdate() { }',
208+
' componentWillUpdate() { }',
209+
' componentDidUpdate() { }',
210+
' componentWillUnmount() { }',
211+
' render() { }',
212+
'}'
213+
].join('\n'),
214+
parserOptions: parserOptions
215+
}, {
216+
code: [
217+
'class MyClass {',
218+
' componentwillmount() { }',
219+
' componentdidmount() { }',
220+
' componentwillreceiveprops() { }',
221+
' shouldcomponentupdate() { }',
222+
' componentwillupdate() { }',
223+
' componentdidupdate() { }',
224+
' componentwillUnmount() { }',
225+
' render() { }',
226+
'}'
227+
].join('\n'),
228+
parserOptions: parserOptions
229+
}, {
230+
code: [
231+
'class MyClass {',
232+
' Componentwillmount() { }',
233+
' Componentdidmount() { }',
234+
' Componentwillreceiveprops() { }',
235+
' Shouldcomponentupdate() { }',
236+
' Componentwillupdate() { }',
237+
' Componentdidupdate() { }',
238+
' ComponentwillUnmount() { }',
239+
' Render() { }',
240+
'}'
241+
].join('\n'),
242+
parserOptions: parserOptions
184243
}],
185244

186245
invalid: [{
@@ -367,5 +426,122 @@ ruleTester.run('no-typos', rule, {
367426
].join('\n'),
368427
parserOptions: parserOptions,
369428
errors: [{message: ERROR_MESSAGE}]
429+
}, {
430+
code: [
431+
'class Hello extends React.Component {',
432+
' ComponentWillMount() { }',
433+
' ComponentDidMount() { }',
434+
' ComponentWillReceiveProps() { }',
435+
' ShouldComponentUpdate() { }',
436+
' ComponentWillUpdate() { }',
437+
' ComponentDidUpdate() { }',
438+
' ComponentWillUnmount() { }',
439+
' render() {',
440+
' return <div>Hello {this.props.name}</div>;',
441+
' }',
442+
'}'
443+
].join('\n'),
444+
parserOptions: parserOptions,
445+
errors: [{
446+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
447+
type: 'MethodDefinition'
448+
}, {
449+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
450+
type: 'MethodDefinition'
451+
}, {
452+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
453+
type: 'MethodDefinition'
454+
}, {
455+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
456+
type: 'MethodDefinition'
457+
}, {
458+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
459+
type: 'MethodDefinition'
460+
}, {
461+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
462+
type: 'MethodDefinition'
463+
}, {
464+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
465+
type: 'MethodDefinition'
466+
}]
467+
}, {
468+
code: [
469+
'class Hello extends React.Component {',
470+
' Componentwillmount() { }',
471+
' Componentdidmount() { }',
472+
' Componentwillreceiveprops() { }',
473+
' Shouldcomponentupdate() { }',
474+
' Componentwillupdate() { }',
475+
' Componentdidupdate() { }',
476+
' Componentwillunmount() { }',
477+
' Render() {',
478+
' return <div>Hello {this.props.name}</div>;',
479+
' }',
480+
'}'
481+
].join('\n'),
482+
parserOptions: parserOptions,
483+
errors: [{
484+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
485+
type: 'MethodDefinition'
486+
}, {
487+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
488+
type: 'MethodDefinition'
489+
}, {
490+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
491+
type: 'MethodDefinition'
492+
}, {
493+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
494+
type: 'MethodDefinition'
495+
}, {
496+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
497+
type: 'MethodDefinition'
498+
}, {
499+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
500+
type: 'MethodDefinition'
501+
}, {
502+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
503+
type: 'MethodDefinition'
504+
}, {
505+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
506+
type: 'MethodDefinition'
507+
}]
508+
}, {
509+
code: [
510+
'class Hello extends React.Component {',
511+
' componentwillmount() { }',
512+
' componentdidmount() { }',
513+
' componentwillreceiveprops() { }',
514+
' shouldcomponentupdate() { }',
515+
' componentwillupdate() { }',
516+
' componentdidupdate() { }',
517+
' componentwillunmount() { }',
518+
' render() {',
519+
' return <div>Hello {this.props.name}</div>;',
520+
' }',
521+
'}'
522+
].join('\n'),
523+
parserOptions: parserOptions,
524+
errors: [{
525+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
526+
type: 'MethodDefinition'
527+
}, {
528+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
529+
type: 'MethodDefinition'
530+
}, {
531+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
532+
type: 'MethodDefinition'
533+
}, {
534+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
535+
type: 'MethodDefinition'
536+
}, {
537+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
538+
type: 'MethodDefinition'
539+
}, {
540+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
541+
type: 'MethodDefinition'
542+
}, {
543+
message: ERROR_MESSAGE_LIFECYCLE_METHOD,
544+
type: 'MethodDefinition'
545+
}]
370546
}]
371547
});

0 commit comments

Comments
 (0)