Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update no-unused-prop-types rule for new React class component lifecycles #1681

Merged
merged 2 commits into from Mar 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 20 additions & 13 deletions lib/rules/no-unused-prop-types.js
Expand Up @@ -23,6 +23,7 @@ const DIRECT_PROPS_REGEX = /^props\s*(\.|\[)/;
const DIRECT_NEXT_PROPS_REGEX = /^nextProps\s*(\.|\[)/;
const DIRECT_PREV_PROPS_REGEX = /^prevProps\s*(\.|\[)/;
const LIFE_CYCLE_METHODS = ['componentWillReceiveProps', 'shouldComponentUpdate', 'componentWillUpdate', 'componentDidUpdate'];
const ASYNC_SAFE_LIFE_CYCLE_METHODS = ['getDerivedStateFromProps', 'UNSAFE_componentWillReceiveProps', 'UNSAFE_componentWillUpdate'];

// ------------------------------------------------------------------------------
// Rule Definition
Expand Down Expand Up @@ -61,6 +62,7 @@ module.exports = {
const skipShapeProps = configuration.skipShapeProps;
const customValidators = configuration.customValidators || [];
const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
const checkAsyncSafeLifeCycles = versionUtil.testReactVersion(context, '16.3.0');

// Used to track the type annotations in scope.
// Necessary because babel's scopes do not track type annotations.
Expand Down Expand Up @@ -91,12 +93,14 @@ module.exports = {
function inLifeCycleMethod() {
let scope = context.getScope();
while (scope) {
if (
scope.block && scope.block.parent &&
scope.block.parent.key &&
LIFE_CYCLE_METHODS.indexOf(scope.block.parent.key.name) >= 0
) {
return true;
if (scope.block && scope.block.parent && scope.block.parent.key) {
const name = scope.block.parent.key.name;

if (LIFE_CYCLE_METHODS.indexOf(name) >= 0) {
return true;
} else if (checkAsyncSafeLifeCycles && ASYNC_SAFE_LIFE_CYCLE_METHODS.indexOf(name) >= 0) {
return true;
}
}
scope = scope.upper;
}
Expand Down Expand Up @@ -250,13 +254,16 @@ module.exports = {
*/
function isNodeALifeCycleMethod(node) {
const nodeKeyName = (node.key || {}).name;
return (
node.kind === 'constructor' ||
nodeKeyName === 'componentWillReceiveProps' ||
nodeKeyName === 'shouldComponentUpdate' ||
nodeKeyName === 'componentWillUpdate' ||
nodeKeyName === 'componentDidUpdate'
);

if (node.kind === 'constructor') {
return true;
} else if (LIFE_CYCLE_METHODS.indexOf(nodeKeyName) >= 0) {
return true;
} else if (checkAsyncSafeLifeCycles && ASYNC_SAFE_LIFE_CYCLE_METHODS.indexOf(nodeKeyName) >= 0) {
return true;
}

return false;
}

/**
Expand Down
116 changes: 116 additions & 0 deletions tests/lib/rules/no-unused-prop-types.js
Expand Up @@ -2825,6 +2825,61 @@ ruleTester.run('no-unused-prop-types', rule, {
}
MyComponent.propTypes = { * other() {} };
`
}, {
// Sanity test coverage for new UNSAFE_componentWillReceiveProps lifecycles
code: [`
class Hello extends Component {
static propTypes = {
something: PropTypes.bool
};
UNSAFE_componentWillReceiveProps (nextProps) {
const {something} = nextProps;
doSomething(something);
}
}
`].join('\n'),
settings: {react: {version: '16.3.0'}},
parser: 'babel-eslint'
}, {
// Destructured props in the `UNSAFE_componentWillUpdate` method shouldn't throw errors
code: [`
class Hello extends Component {
static propTypes = {
something: PropTypes.bool
};
UNSAFE_componentWillUpdate (nextProps, nextState) {
const {something} = nextProps;
return something;
}
}
`].join('\n'),
settings: {react: {version: '16.3.0'}},
parser: 'babel-eslint'
}, {
// Simple test of new static getDerivedStateFromProps lifecycle
code: [`
class MyComponent extends React.Component {
static propTypes = {
defaultValue: 'bar'
};
state = {
currentValue: null
};
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.currentValue === null) {
return {
currentValue: nextProps.defaultValue,
}
}
return null;
}
render() {
return <div>{ this.state.currentValue }</div>
}
}
`].join('\n'),
settings: {react: {version: '16.3.0'}},
parser: 'babel-eslint'
}
],

Expand Down Expand Up @@ -4372,6 +4427,67 @@ ruleTester.run('no-unused-prop-types', rule, {
errors: [{
message: '\'lastname\' PropType is defined but prop is never used'
}]
}, {
code: [`
class Hello extends Component {
static propTypes = {
something: PropTypes.bool
};
UNSAFE_componentWillReceiveProps (nextProps) {
const {something} = nextProps;
doSomething(something);
}
}
`].join('\n'),
settings: {react: {version: '16.2.0'}},
parser: 'babel-eslint',
errors: [{
message: '\'something\' PropType is defined but prop is never used'
}]
}, {
code: [`
class Hello extends Component {
static propTypes = {
something: PropTypes.bool
};
UNSAFE_componentWillUpdate (nextProps, nextState) {
const {something} = nextProps;
return something;
}
}
`].join('\n'),
settings: {react: {version: '16.2.0'}},
parser: 'babel-eslint',
errors: [{
message: '\'something\' PropType is defined but prop is never used'
}]
}, {
code: [`
class MyComponent extends React.Component {
static propTypes = {
defaultValue: 'bar'
};
state = {
currentValue: null
};
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.currentValue === null) {
return {
currentValue: nextProps.defaultValue,
}
}
return null;
}
render() {
return <div>{ this.state.currentValue }</div>
}
}
`].join('\n'),
settings: {react: {version: '16.2.0'}},
parser: 'babel-eslint',
errors: [{
message: '\'defaultValue\' PropType is defined but prop is never used'
}]
}

/* , {
Expand Down