Skip to content

Commit

Permalink
[Fix] no-unused-state: TS: support getDerivedStateFromProps as an…
Browse files Browse the repository at this point in the history
… arrow function

Fixes #2061
  • Loading branch information
ljharb committed Feb 12, 2022
1 parent 53e0722 commit 9b227aa
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -21,6 +21,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`sort-prop-types`]: avoid repeated warnings of the same node/reason ([#519][] @ljharb)
* [`jsx-indent`]: Fix indent handling for closing parentheses ([#620][] @stefanbuck])
* [`prop-types`/`propTypes`]: follow a returned identifier to see if it is JSX ([#1046][] @ljharb)
* [`no-unused-state`]: TS: support `getDerivedStateFromProps` as an arrow function ([#2061][] @ljharb)

### Changed
* [readme] change [`jsx-runtime`] link from branch to sha ([#3160][] @tatsushitoji)
Expand Down Expand Up @@ -51,6 +52,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
[#3133]: https://github.com/yannickcr/eslint-plugin-react/pull/3133
[#2921]: https://github.com/yannickcr/eslint-plugin-react/pull/2921
[#2753]: https://github.com/yannickcr/eslint-plugin-react/pull/2753
[#2061]: https://github.com/yannickcr/eslint-plugin-react/issues/2061
[#1817]: https://github.com/yannickcr/eslint-plugin-react/issues/1817
[#1815]: https://github.com/yannickcr/eslint-plugin-react/issues/1815
[#1754]: https://github.com/yannickcr/eslint-plugin-react/issues/1754
Expand Down
45 changes: 41 additions & 4 deletions lib/rules/no-unused-state.js
Expand Up @@ -243,6 +243,18 @@ module.exports = {
classInfo = null;
}

function isGDSFP(node) {
const name = getName(node.key);
if (
!node.static
|| name !== 'getDerivedStateFromProps'
|| node.value.params.length < 2 // no `state` argument
) {
return false;
}
return true;
}

return {
ClassDeclaration: handleES6ComponentEnter,

Expand Down Expand Up @@ -307,15 +319,13 @@ module.exports = {
},

'ClassProperty, PropertyDefinition'(node) {
if (!classInfo) {
return;
}
// If we see state being assigned as a class property using an object
// expression, record all the fields of that object as state fields.
const unwrappedValueNode = ast.unwrapTSAsExpression(node.value);

const name = getName(node.key);
if (
getName(node.key) === 'state'
name === 'state'
&& !node.static
&& unwrappedValueNode
&& unwrappedValueNode.type === 'ObjectExpression'
Expand Down Expand Up @@ -345,12 +355,39 @@ module.exports = {
}
},

'PropertyDefinition, ClassProperty'(node) {
if (!isGDSFP(node)) {
return;
}

const childScope = context.getScope().childScopes.find((x) => x.block === node.value);
if (!childScope) {
return;
}
const scope = childScope.variableScope.childScopes.find((x) => x.block === node.value);
const stateArg = node.value.params[1]; // probably "state"
if (!scope.variables) {
return;
}
const argVar = scope.variables.find((x) => x.name === stateArg.name);

const stateRefs = argVar.references;

stateRefs.forEach((ref) => {
const identifier = ref.identifier;
if (identifier && identifier.parent && identifier.parent.type === 'MemberExpression') {
addUsedStateField(identifier.parent.property);
}
});
},

'PropertyDefinition:exit'(node) {
if (
classInfo
&& !node.static
&& node.value
&& node.value.type === 'ArrowFunctionExpression'
&& !isGDSFP(node)
) {
// Forget our set of local aliases.
classInfo.aliases = null;
Expand Down
30 changes: 28 additions & 2 deletions tests/lib/rules/no-unused-state.js
Expand Up @@ -4,7 +4,9 @@

'use strict';

const semver = require('semver');
const RuleTester = require('eslint').RuleTester;
const tsEslintVersion = require('@typescript-eslint/parser/package.json').version;
const rule = require('../../../lib/rules/no-unused-state');

const parsers = require('../../helpers/parsers');
Expand All @@ -26,7 +28,7 @@ function getErrorMessages(unusedFields) {
}

eslintTester.run('no-unused-state', rule, {
valid: parsers.all([
valid: parsers.all([].concat(
{
code: `
function StatelessFnUnaffectedTest(props) {
Expand Down Expand Up @@ -984,7 +986,31 @@ eslintTester.run('no-unused-state', rule, {
`,
features: ['ts', 'no-babel'],
},
]),
semver.satisfies(tsEslintVersion, '>= 5') ? {
code: `
interface Props {}
interface State {
flag: boolean;
}
export default class RuleTest extends React.Component<Props, State> {
readonly state: State = {
flag: false,
};
static getDerivedStateFromProps = (props: Props, state: State) => {
const newState: Partial<State> = {};
if (!state.flag) {
newState.flag = true;
}
return newState;
};
}
`,
features: ['ts', 'no-babel-old', 'no-ts-old'],
} : []
)),

invalid: parsers.all([
{
Expand Down

0 comments on commit 9b227aa

Please sign in to comment.