Skip to content

Commit 14b8d01

Browse files
authored
Merge pull request #1681 from bvaughn/update-no-unused-prop-types
Update no-unused-prop-types rule for new React class component lifecycles
2 parents bda82a3 + c0c7ce0 commit 14b8d01

File tree

2 files changed

+136
-13
lines changed

2 files changed

+136
-13
lines changed

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

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const DIRECT_PROPS_REGEX = /^props\s*(\.|\[)/;
2323
const DIRECT_NEXT_PROPS_REGEX = /^nextProps\s*(\.|\[)/;
2424
const DIRECT_PREV_PROPS_REGEX = /^prevProps\s*(\.|\[)/;
2525
const LIFE_CYCLE_METHODS = ['componentWillReceiveProps', 'shouldComponentUpdate', 'componentWillUpdate', 'componentDidUpdate'];
26+
const ASYNC_SAFE_LIFE_CYCLE_METHODS = ['getDerivedStateFromProps', 'UNSAFE_componentWillReceiveProps', 'UNSAFE_componentWillUpdate'];
2627

2728
// ------------------------------------------------------------------------------
2829
// Rule Definition
@@ -61,6 +62,7 @@ module.exports = {
6162
const skipShapeProps = configuration.skipShapeProps;
6263
const customValidators = configuration.customValidators || [];
6364
const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
65+
const checkAsyncSafeLifeCycles = versionUtil.testReactVersion(context, '16.3.0');
6466

6567
// Used to track the type annotations in scope.
6668
// Necessary because babel's scopes do not track type annotations.
@@ -91,12 +93,14 @@ module.exports = {
9193
function inLifeCycleMethod() {
9294
let scope = context.getScope();
9395
while (scope) {
94-
if (
95-
scope.block && scope.block.parent &&
96-
scope.block.parent.key &&
97-
LIFE_CYCLE_METHODS.indexOf(scope.block.parent.key.name) >= 0
98-
) {
99-
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+
}
100104
}
101105
scope = scope.upper;
102106
}
@@ -250,13 +254,16 @@ module.exports = {
250254
*/
251255
function isNodeALifeCycleMethod(node) {
252256
const nodeKeyName = (node.key || {}).name;
253-
return (
254-
node.kind === 'constructor' ||
255-
nodeKeyName === 'componentWillReceiveProps' ||
256-
nodeKeyName === 'shouldComponentUpdate' ||
257-
nodeKeyName === 'componentWillUpdate' ||
258-
nodeKeyName === 'componentDidUpdate'
259-
);
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;
260267
}
261268

262269
/**

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

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2825,6 +2825,61 @@ ruleTester.run('no-unused-prop-types', rule, {
28252825
}
28262826
MyComponent.propTypes = { * other() {} };
28272827
`
2828+
}, {
2829+
// Sanity test coverage for new UNSAFE_componentWillReceiveProps lifecycles
2830+
code: [`
2831+
class Hello extends Component {
2832+
static propTypes = {
2833+
something: PropTypes.bool
2834+
};
2835+
UNSAFE_componentWillReceiveProps (nextProps) {
2836+
const {something} = nextProps;
2837+
doSomething(something);
2838+
}
2839+
}
2840+
`].join('\n'),
2841+
settings: {react: {version: '16.3.0'}},
2842+
parser: 'babel-eslint'
2843+
}, {
2844+
// Destructured props in the `UNSAFE_componentWillUpdate` method shouldn't throw errors
2845+
code: [`
2846+
class Hello extends Component {
2847+
static propTypes = {
2848+
something: PropTypes.bool
2849+
};
2850+
UNSAFE_componentWillUpdate (nextProps, nextState) {
2851+
const {something} = nextProps;
2852+
return something;
2853+
}
2854+
}
2855+
`].join('\n'),
2856+
settings: {react: {version: '16.3.0'}},
2857+
parser: 'babel-eslint'
2858+
}, {
2859+
// Simple test of new static getDerivedStateFromProps lifecycle
2860+
code: [`
2861+
class MyComponent extends React.Component {
2862+
static propTypes = {
2863+
defaultValue: 'bar'
2864+
};
2865+
state = {
2866+
currentValue: null
2867+
};
2868+
static getDerivedStateFromProps(nextProps, prevState) {
2869+
if (prevState.currentValue === null) {
2870+
return {
2871+
currentValue: nextProps.defaultValue,
2872+
}
2873+
}
2874+
return null;
2875+
}
2876+
render() {
2877+
return <div>{ this.state.currentValue }</div>
2878+
}
2879+
}
2880+
`].join('\n'),
2881+
settings: {react: {version: '16.3.0'}},
2882+
parser: 'babel-eslint'
28282883
}
28292884
],
28302885

@@ -4394,6 +4449,67 @@ ruleTester.run('no-unused-prop-types', rule, {
43944449
errors: [{
43954450
message: '\'lastname\' PropType is defined but prop is never used'
43964451
}]
4452+
}, {
4453+
code: [`
4454+
class Hello extends Component {
4455+
static propTypes = {
4456+
something: PropTypes.bool
4457+
};
4458+
UNSAFE_componentWillReceiveProps (nextProps) {
4459+
const {something} = nextProps;
4460+
doSomething(something);
4461+
}
4462+
}
4463+
`].join('\n'),
4464+
settings: {react: {version: '16.2.0'}},
4465+
parser: 'babel-eslint',
4466+
errors: [{
4467+
message: '\'something\' PropType is defined but prop is never used'
4468+
}]
4469+
}, {
4470+
code: [`
4471+
class Hello extends Component {
4472+
static propTypes = {
4473+
something: PropTypes.bool
4474+
};
4475+
UNSAFE_componentWillUpdate (nextProps, nextState) {
4476+
const {something} = nextProps;
4477+
return something;
4478+
}
4479+
}
4480+
`].join('\n'),
4481+
settings: {react: {version: '16.2.0'}},
4482+
parser: 'babel-eslint',
4483+
errors: [{
4484+
message: '\'something\' PropType is defined but prop is never used'
4485+
}]
4486+
}, {
4487+
code: [`
4488+
class MyComponent extends React.Component {
4489+
static propTypes = {
4490+
defaultValue: 'bar'
4491+
};
4492+
state = {
4493+
currentValue: null
4494+
};
4495+
static getDerivedStateFromProps(nextProps, prevState) {
4496+
if (prevState.currentValue === null) {
4497+
return {
4498+
currentValue: nextProps.defaultValue,
4499+
}
4500+
}
4501+
return null;
4502+
}
4503+
render() {
4504+
return <div>{ this.state.currentValue }</div>
4505+
}
4506+
}
4507+
`].join('\n'),
4508+
settings: {react: {version: '16.2.0'}},
4509+
parser: 'babel-eslint',
4510+
errors: [{
4511+
message: '\'defaultValue\' PropType is defined but prop is never used'
4512+
}]
43974513
}
43984514

43994515
/* , {

0 commit comments

Comments
 (0)