Skip to content

Commit c1ed90e

Browse files
bsonntagljharb
authored andcommitted
[New] no-typos: check static lifecycle methods
This updates the `no-typos` rule so that it checks for lifecycle methods that should be static, like `getDerivedStateFromProps`.
1 parent 3824dc9 commit c1ed90e

File tree

3 files changed

+102
-59
lines changed

3 files changed

+102
-59
lines changed

docs/rules/no-typos.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ class MyComponent extends React.Component {
8686
}
8787
}
8888

89+
class MyComponent extends React.Component {
90+
getDerivedStateFromProps() {}
91+
}
8992
```
9093

9194
The following patterns are **not** considered warnings:

lib/rules/no-typos.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const docsUrl = require('../util/docsUrl');
1313
// ------------------------------------------------------------------------------
1414

1515
const STATIC_CLASS_PROPERTIES = ['propTypes', 'contextTypes', 'childContextTypes', 'defaultProps'];
16+
const STATIC_LIFECYCLE_METHODS = ['getDerivedStateFromProps'];
1617
const LIFECYCLE_METHODS = [
1718
'getDerivedStateFromProps',
1819
'componentWillMount',
@@ -145,11 +146,21 @@ module.exports = {
145146
}
146147

147148
function reportErrorIfLifecycleMethodCasingTypo(node) {
148-
LIFECYCLE_METHODS.forEach((method) => {
149-
let nodeKeyName = node.key.name;
150-
if (node.key.type === 'Literal') {
151-
nodeKeyName = node.key.value;
149+
let nodeKeyName = node.key.name;
150+
if (node.key.type === 'Literal') {
151+
nodeKeyName = node.key.value;
152+
}
153+
154+
STATIC_LIFECYCLE_METHODS.forEach((method) => {
155+
if (!node.static && nodeKeyName.toLowerCase() === method.toLowerCase()) {
156+
context.report({
157+
node,
158+
message: `Lifecycle method should be static: ${nodeKeyName}`
159+
});
152160
}
161+
});
162+
163+
LIFECYCLE_METHODS.forEach((method) => {
153164
if (method.toLowerCase() === nodeKeyName.toLowerCase() && method !== nodeKeyName) {
154165
context.report({
155166
node,

tests/lib/rules/no-typos.js

Lines changed: 84 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ const parserOptions = {
2727

2828
const ERROR_MESSAGE = 'Typo in static class property declaration';
2929
const ERROR_MESSAGE_ES5 = 'Typo in property declaration';
30-
const ERROR_MESSAGE_LIFECYCLE_METHOD = ({actual, expected}) => `Typo in component lifecycle method declaration: ${actual} should be ${expected}`;
30+
const ERROR_MESSAGE_LIFECYCLE_METHOD = (actual, expected) => `Typo in component lifecycle method declaration: ${actual} should be ${expected}`;
31+
const ERROR_MESSAGE_STATIC = method => `Lifecycle method should be static: ${method}`;
3132

3233
const ruleTester = new RuleTester();
3334
ruleTester.run('no-typos', rule, {
@@ -190,6 +191,7 @@ ruleTester.run('no-typos', rule, {
190191
}, {
191192
code: `
192193
class Hello extends React.Component {
194+
static getDerivedStateFromProps() { }
193195
componentWillMount() { }
194196
componentDidMount() { }
195197
componentWillReceiveProps() { }
@@ -840,43 +842,43 @@ ruleTester.run('no-typos', rule, {
840842
`,
841843
parserOptions,
842844
errors: [{
843-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'GetDerivedStateFromProps', expected: 'getDerivedStateFromProps'}),
845+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('GetDerivedStateFromProps', 'getDerivedStateFromProps'),
844846
type: 'MethodDefinition'
845847
}, {
846-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillMount', expected: 'componentWillMount'}),
848+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'),
847849
type: 'MethodDefinition'
848850
}, {
849-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_ComponentWillMount', expected: 'UNSAFE_componentWillMount'}),
851+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillMount', 'UNSAFE_componentWillMount'),
850852
type: 'MethodDefinition'
851853
}, {
852-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidMount', expected: 'componentDidMount'}),
854+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'),
853855
type: 'MethodDefinition'
854856
}, {
855-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillReceiveProps', expected: 'componentWillReceiveProps'}),
857+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'),
856858
type: 'MethodDefinition'
857859
}, {
858-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_ComponentWillReceiveProps', expected: 'UNSAFE_componentWillReceiveProps'}),
860+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillReceiveProps', 'UNSAFE_componentWillReceiveProps'),
859861
type: 'MethodDefinition'
860862
}, {
861-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ShouldComponentUpdate', expected: 'shouldComponentUpdate'}),
863+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'),
862864
type: 'MethodDefinition'
863865
}, {
864-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUpdate', expected: 'componentWillUpdate'}),
866+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'),
865867
type: 'MethodDefinition'
866868
}, {
867-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_ComponentWillUpdate', expected: 'UNSAFE_componentWillUpdate'}),
869+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_ComponentWillUpdate', 'UNSAFE_componentWillUpdate'),
868870
type: 'MethodDefinition'
869871
}, {
870-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'GetSnapshotBeforeUpdate', expected: 'getSnapshotBeforeUpdate'}),
872+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('GetSnapshotBeforeUpdate', 'getSnapshotBeforeUpdate'),
871873
type: 'MethodDefinition'
872874
}, {
873-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidUpdate', expected: 'componentDidUpdate'}),
875+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'),
874876
type: 'MethodDefinition'
875877
}, {
876-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidCatch', expected: 'componentDidCatch'}),
878+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidCatch', 'componentDidCatch'),
877879
type: 'MethodDefinition'
878880
}, {
879-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUnmount', expected: 'componentWillUnmount'}),
881+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUnmount', 'componentWillUnmount'),
880882
type: 'MethodDefinition'
881883
}]
882884
}, {
@@ -902,46 +904,46 @@ ruleTester.run('no-typos', rule, {
902904
`,
903905
parserOptions,
904906
errors: [{
905-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Getderivedstatefromprops', expected: 'getDerivedStateFromProps'}),
907+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('Getderivedstatefromprops', 'getDerivedStateFromProps'),
906908
type: 'MethodDefinition'
907909
}, {
908-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillmount', expected: 'componentWillMount'}),
910+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillmount', 'componentWillMount'),
909911
type: 'MethodDefinition'
910912
}, {
911-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_Componentwillmount', expected: 'UNSAFE_componentWillMount'}),
913+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillmount', 'UNSAFE_componentWillMount'),
912914
type: 'MethodDefinition'
913915
}, {
914-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentdidmount', expected: 'componentDidMount'}),
916+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidmount', 'componentDidMount'),
915917
type: 'MethodDefinition'
916918
}, {
917-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillreceiveprops', expected: 'componentWillReceiveProps'}),
919+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillreceiveprops', 'componentWillReceiveProps'),
918920
type: 'MethodDefinition'
919921
}, {
920-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_Componentwillreceiveprops', expected: 'UNSAFE_componentWillReceiveProps'}),
922+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillreceiveprops', 'UNSAFE_componentWillReceiveProps'),
921923
type: 'MethodDefinition'
922924
}, {
923-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Shouldcomponentupdate', expected: 'shouldComponentUpdate'}),
925+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('Shouldcomponentupdate', 'shouldComponentUpdate'),
924926
type: 'MethodDefinition'
925927
}, {
926-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillupdate', expected: 'componentWillUpdate'}),
928+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillupdate', 'componentWillUpdate'),
927929
type: 'MethodDefinition'
928930
}, {
929-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'UNSAFE_Componentwillupdate', expected: 'UNSAFE_componentWillUpdate'}),
931+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('UNSAFE_Componentwillupdate', 'UNSAFE_componentWillUpdate'),
930932
type: 'MethodDefinition'
931933
}, {
932-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Getsnapshotbeforeupdate', expected: 'getSnapshotBeforeUpdate'}),
934+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('Getsnapshotbeforeupdate', 'getSnapshotBeforeUpdate'),
933935
type: 'MethodDefinition'
934936
}, {
935-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentdidupdate', expected: 'componentDidUpdate'}),
937+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidupdate', 'componentDidUpdate'),
936938
type: 'MethodDefinition'
937939
}, {
938-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentdidcatch', expected: 'componentDidCatch'}),
940+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentdidcatch', 'componentDidCatch'),
939941
type: 'MethodDefinition'
940942
}, {
941-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Componentwillunmount', expected: 'componentWillUnmount'}),
943+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('Componentwillunmount', 'componentWillUnmount'),
942944
type: 'MethodDefinition'
943945
}, {
944-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'Render', expected: 'render'}),
946+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('Render', 'render'),
945947
nodeType: 'MethodDefinition'
946948
}]
947949
}, {
@@ -967,43 +969,43 @@ ruleTester.run('no-typos', rule, {
967969
`,
968970
parserOptions,
969971
errors: [{
970-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'getderivedstatefromprops', expected: 'getDerivedStateFromProps'}),
972+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('getderivedstatefromprops', 'getDerivedStateFromProps'),
971973
type: 'MethodDefinition'
972974
}, {
973-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillmount', expected: 'componentWillMount'}),
975+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillmount', 'componentWillMount'),
974976
type: 'MethodDefinition'
975977
}, {
976-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'unsafe_componentwillmount', expected: 'UNSAFE_componentWillMount'}),
978+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillmount', 'UNSAFE_componentWillMount'),
977979
type: 'MethodDefinition'
978980
}, {
979-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentdidmount', expected: 'componentDidMount'}),
981+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidmount', 'componentDidMount'),
980982
type: 'MethodDefinition'
981983
}, {
982-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillreceiveprops', expected: 'componentWillReceiveProps'}),
984+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillreceiveprops', 'componentWillReceiveProps'),
983985
type: 'MethodDefinition'
984986
}, {
985-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'unsafe_componentwillreceiveprops', expected: 'UNSAFE_componentWillReceiveProps'}),
987+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillreceiveprops', 'UNSAFE_componentWillReceiveProps'),
986988
type: 'MethodDefinition'
987989
}, {
988-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'shouldcomponentupdate', expected: 'shouldComponentUpdate'}),
990+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('shouldcomponentupdate', 'shouldComponentUpdate'),
989991
type: 'MethodDefinition'
990992
}, {
991-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillupdate', expected: 'componentWillUpdate'}),
993+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillupdate', 'componentWillUpdate'),
992994
type: 'MethodDefinition'
993995
}, {
994-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'unsafe_componentwillupdate', expected: 'UNSAFE_componentWillUpdate'}),
996+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('unsafe_componentwillupdate', 'UNSAFE_componentWillUpdate'),
995997
type: 'MethodDefinition'
996998
}, {
997-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'getsnapshotbeforeupdate', expected: 'getSnapshotBeforeUpdate'}),
999+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('getsnapshotbeforeupdate', 'getSnapshotBeforeUpdate'),
9981000
type: 'MethodDefinition'
9991001
}, {
1000-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentdidupdate', expected: 'componentDidUpdate'}),
1002+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidupdate', 'componentDidUpdate'),
10011003
type: 'MethodDefinition'
10021004
}, {
1003-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentdidcatch', expected: 'componentDidCatch'}),
1005+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentdidcatch', 'componentDidCatch'),
10041006
type: 'MethodDefinition'
10051007
}, {
1006-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'componentwillunmount', expected: 'componentWillUnmount'}),
1008+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('componentwillunmount', 'componentWillUnmount'),
10071009
type: 'MethodDefinition'
10081010
}]
10091011
}, {
@@ -1583,27 +1585,54 @@ ruleTester.run('no-typos', rule, {
15831585
message: ERROR_MESSAGE_ES5,
15841586
type: 'Identifier'
15851587
}, {
1586-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillMount', expected: 'componentWillMount'}),
1588+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'),
15871589
type: 'Property'
15881590
}, {
1589-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidMount', expected: 'componentDidMount'}),
1591+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'),
15901592
type: 'Property'
15911593
}, {
1592-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillReceiveProps', expected: 'componentWillReceiveProps'}),
1594+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'),
15931595
type: 'Property'
15941596
}, {
1595-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ShouldComponentUpdate', expected: 'shouldComponentUpdate'}),
1597+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'),
15961598
type: 'Property'
15971599
}, {
1598-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUpdate', expected: 'componentWillUpdate'}),
1600+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'),
15991601
type: 'Property'
16001602
}, {
1601-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidUpdate', expected: 'componentDidUpdate'}),
1603+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'),
16021604
type: 'Property'
16031605
}, {
1604-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUnmount', expected: 'componentWillUnmount'}),
1606+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUnmount', 'componentWillUnmount'),
16051607
type: 'Property'
16061608
}]
1609+
}, {
1610+
code: `
1611+
class Hello extends React.Component {
1612+
getDerivedStateFromProps() { }
1613+
}
1614+
`,
1615+
parser: parsers.BABEL_ESLINT,
1616+
parserOptions,
1617+
errors: [{
1618+
message: ERROR_MESSAGE_STATIC('getDerivedStateFromProps'),
1619+
nodeType: 'MethodDefinition'
1620+
}]
1621+
}, {
1622+
code: `
1623+
class Hello extends React.Component {
1624+
GetDerivedStateFromProps() { }
1625+
}
1626+
`,
1627+
parser: parsers.BABEL_ESLINT,
1628+
parserOptions,
1629+
errors: [{
1630+
message: ERROR_MESSAGE_STATIC('GetDerivedStateFromProps'),
1631+
nodeType: 'MethodDefinition'
1632+
}, {
1633+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('GetDerivedStateFromProps', 'getDerivedStateFromProps'),
1634+
nodeType: 'MethodDefinition'
1635+
}]
16071636
}, {
16081637
code: `
16091638
import React from 'react';
@@ -1635,25 +1664,25 @@ ruleTester.run('no-typos', rule, {
16351664
message: ERROR_MESSAGE_ES5,
16361665
type: 'Identifier'
16371666
}, {
1638-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillMount', expected: 'componentWillMount'}),
1667+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillMount', 'componentWillMount'),
16391668
type: 'Property'
16401669
}, {
1641-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidMount', expected: 'componentDidMount'}),
1670+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidMount', 'componentDidMount'),
16421671
type: 'Property'
16431672
}, {
1644-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillReceiveProps', expected: 'componentWillReceiveProps'}),
1673+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillReceiveProps', 'componentWillReceiveProps'),
16451674
type: 'Property'
16461675
}, {
1647-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ShouldComponentUpdate', expected: 'shouldComponentUpdate'}),
1676+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ShouldComponentUpdate', 'shouldComponentUpdate'),
16481677
type: 'Property'
16491678
}, {
1650-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUpdate', expected: 'componentWillUpdate'}),
1679+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUpdate', 'componentWillUpdate'),
16511680
type: 'Property'
16521681
}, {
1653-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentDidUpdate', expected: 'componentDidUpdate'}),
1682+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentDidUpdate', 'componentDidUpdate'),
16541683
type: 'Property'
16551684
}, {
1656-
message: ERROR_MESSAGE_LIFECYCLE_METHOD({actual: 'ComponentWillUnmount', expected: 'componentWillUnmount'}),
1685+
message: ERROR_MESSAGE_LIFECYCLE_METHOD('ComponentWillUnmount', 'componentWillUnmount'),
16571686
type: 'Property'
16581687
}]
16591688
/*

0 commit comments

Comments
 (0)