Skip to content

Commit c0c7ce0

Browse files
committed
Update no-unused-prop-types rule to be aware of 16.3 lifecycle changes
1 parent 90e3767 commit c0c7ce0

File tree

2 files changed

+85
-19
lines changed

2 files changed

+85
-19
lines changed

lib/rules/no-unused-prop-types.js

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,8 @@ const docsUrl = require('../util/docsUrl');
2222
const DIRECT_PROPS_REGEX = /^props\s*(\.|\[)/;
2323
const DIRECT_NEXT_PROPS_REGEX = /^nextProps\s*(\.|\[)/;
2424
const DIRECT_PREV_PROPS_REGEX = /^prevProps\s*(\.|\[)/;
25-
const LIFE_CYCLE_METHODS = [
26-
'componentDidUpdate',
27-
'componentWillReceiveProps',
28-
'componentWillUpdate',
29-
'getDerivedStateFromProps',
30-
'shouldComponentUpdate',
31-
'UNSAFE_componentWillReceiveProps',
32-
'UNSAFE_componentWillUpdate'
33-
];
25+
const LIFE_CYCLE_METHODS = ['componentWillReceiveProps', 'shouldComponentUpdate', 'componentWillUpdate', 'componentDidUpdate'];
26+
const ASYNC_SAFE_LIFE_CYCLE_METHODS = ['getDerivedStateFromProps', 'UNSAFE_componentWillReceiveProps', 'UNSAFE_componentWillUpdate'];
3427

3528
// ------------------------------------------------------------------------------
3629
// Rule Definition
@@ -69,6 +62,7 @@ module.exports = {
6962
const skipShapeProps = configuration.skipShapeProps;
7063
const customValidators = configuration.customValidators || [];
7164
const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
65+
const checkAsyncSafeLifeCycles = versionUtil.testReactVersion(context, '16.3.0');
7266

7367
// Used to track the type annotations in scope.
7468
// Necessary because babel's scopes do not track type annotations.
@@ -99,12 +93,14 @@ module.exports = {
9993
function inLifeCycleMethod() {
10094
let scope = context.getScope();
10195
while (scope) {
102-
if (
103-
scope.block && scope.block.parent &&
104-
scope.block.parent.key &&
105-
LIFE_CYCLE_METHODS.indexOf(scope.block.parent.key.name) >= 0
106-
) {
107-
return true;
96+
if (scope.block && scope.block.parent && scope.block.parent.key) {
97+
const name = scope.block.parent.key.name;
98+
99+
if (LIFE_CYCLE_METHODS.indexOf(name) >= 0) {
100+
return true;
101+
} else if (checkAsyncSafeLifeCycles && ASYNC_SAFE_LIFE_CYCLE_METHODS.indexOf(name) >= 0) {
102+
return true;
103+
}
108104
}
109105
scope = scope.upper;
110106
}
@@ -258,10 +254,16 @@ module.exports = {
258254
*/
259255
function isNodeALifeCycleMethod(node) {
260256
const nodeKeyName = (node.key || {}).name;
261-
return (
262-
node.kind === 'constructor' ||
263-
LIFE_CYCLE_METHODS.indexOf(nodeKeyName) >= 0
264-
);
257+
258+
if (node.kind === 'constructor') {
259+
return true;
260+
} else if (LIFE_CYCLE_METHODS.indexOf(nodeKeyName) >= 0) {
261+
return true;
262+
} else if (checkAsyncSafeLifeCycles && ASYNC_SAFE_LIFE_CYCLE_METHODS.indexOf(nodeKeyName) >= 0) {
263+
return true;
264+
}
265+
266+
return false;
265267
}
266268

267269
/**

tests/lib/rules/no-unused-prop-types.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2838,6 +2838,7 @@ ruleTester.run('no-unused-prop-types', rule, {
28382838
}
28392839
}
28402840
`].join('\n'),
2841+
settings: {react: {version: '16.3.0'}},
28412842
parser: 'babel-eslint'
28422843
}, {
28432844
// Destructured props in the `UNSAFE_componentWillUpdate` method shouldn't throw errors
@@ -2852,6 +2853,7 @@ ruleTester.run('no-unused-prop-types', rule, {
28522853
}
28532854
}
28542855
`].join('\n'),
2856+
settings: {react: {version: '16.3.0'}},
28552857
parser: 'babel-eslint'
28562858
}, {
28572859
// Simple test of new static getDerivedStateFromProps lifecycle
@@ -2876,6 +2878,7 @@ ruleTester.run('no-unused-prop-types', rule, {
28762878
}
28772879
}
28782880
`].join('\n'),
2881+
settings: {react: {version: '16.3.0'}},
28792882
parser: 'babel-eslint'
28802883
}
28812884
],
@@ -4424,6 +4427,67 @@ ruleTester.run('no-unused-prop-types', rule, {
44244427
errors: [{
44254428
message: '\'lastname\' PropType is defined but prop is never used'
44264429
}]
4430+
}, {
4431+
code: [`
4432+
class Hello extends Component {
4433+
static propTypes = {
4434+
something: PropTypes.bool
4435+
};
4436+
UNSAFE_componentWillReceiveProps (nextProps) {
4437+
const {something} = nextProps;
4438+
doSomething(something);
4439+
}
4440+
}
4441+
`].join('\n'),
4442+
settings: {react: {version: '16.2.0'}},
4443+
parser: 'babel-eslint',
4444+
errors: [{
4445+
message: '\'something\' PropType is defined but prop is never used'
4446+
}]
4447+
}, {
4448+
code: [`
4449+
class Hello extends Component {
4450+
static propTypes = {
4451+
something: PropTypes.bool
4452+
};
4453+
UNSAFE_componentWillUpdate (nextProps, nextState) {
4454+
const {something} = nextProps;
4455+
return something;
4456+
}
4457+
}
4458+
`].join('\n'),
4459+
settings: {react: {version: '16.2.0'}},
4460+
parser: 'babel-eslint',
4461+
errors: [{
4462+
message: '\'something\' PropType is defined but prop is never used'
4463+
}]
4464+
}, {
4465+
code: [`
4466+
class MyComponent extends React.Component {
4467+
static propTypes = {
4468+
defaultValue: 'bar'
4469+
};
4470+
state = {
4471+
currentValue: null
4472+
};
4473+
static getDerivedStateFromProps(nextProps, prevState) {
4474+
if (prevState.currentValue === null) {
4475+
return {
4476+
currentValue: nextProps.defaultValue,
4477+
}
4478+
}
4479+
return null;
4480+
}
4481+
render() {
4482+
return <div>{ this.state.currentValue }</div>
4483+
}
4484+
}
4485+
`].join('\n'),
4486+
settings: {react: {version: '16.2.0'}},
4487+
parser: 'babel-eslint',
4488+
errors: [{
4489+
message: '\'defaultValue\' PropType is defined but prop is never used'
4490+
}]
44274491
}
44284492

44294493
/* , {

0 commit comments

Comments
 (0)