Skip to content

Commit d0765dd

Browse files
authored
Merge pull request #1829 from alexzherdev/1759-state-parameter-names
Don't depend on state parameter name in no-unused-state
2 parents b77be96 + 1ecb622 commit d0765dd

File tree

2 files changed

+127
-14
lines changed

2 files changed

+127
-14
lines changed

lib/rules/no-unused-state.js

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,38 @@ module.exports = {
7777
// JSX attributes), then this is again set to null.
7878
let classInfo = null;
7979

80-
// Returns true if the given node is possibly a reference to `this.state`, `prevState` or `nextState`.
80+
function isStateParameterReference(node) {
81+
const classMethods = [
82+
'shouldComponentUpdate',
83+
'componentWillUpdate',
84+
'UNSAFE_componentWillUpdate',
85+
'getSnapshotBeforeUpdate',
86+
'componentDidUpdate'
87+
];
88+
89+
let scope = context.getScope();
90+
while (scope) {
91+
const parent = scope.block && scope.block.parent;
92+
if (
93+
parent &&
94+
parent.type === 'MethodDefinition' && (
95+
parent.static && parent.key.name === 'getDerivedStateFromProps' ||
96+
classMethods.indexOf(parent.key.name !== -1)
97+
) &&
98+
parent.value.type === 'FunctionExpression' &&
99+
parent.value.params[1] &&
100+
parent.value.params[1].name === node.name
101+
) {
102+
return true;
103+
}
104+
scope = scope.upper;
105+
}
106+
107+
return false;
108+
}
109+
110+
// Returns true if the given node is possibly a reference to `this.state` or the state parameter of
111+
// a lifecycle method.
81112
function isStateReference(node) {
82113
node = uncast(node);
83114

@@ -91,15 +122,7 @@ module.exports = {
91122
classInfo.aliases &&
92123
classInfo.aliases.has(node.name);
93124

94-
const isPrevStateReference =
95-
node.type === 'Identifier' &&
96-
node.name === 'prevState';
97-
98-
const isNextStateReference =
99-
node.type === 'Identifier' &&
100-
node.name === 'nextState';
101-
102-
return isDirectStateReference || isAliasedStateReference || isPrevStateReference || isNextStateReference;
125+
return isDirectStateReference || isAliasedStateReference || isStateParameterReference(node);
103126
}
104127

105128
// Takes an ObjectExpression node and adds all named Property nodes to the

tests/lib/rules/no-unused-state.js

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -492,15 +492,15 @@ eslintTester.run('no-unused-state', rule, {
492492
parser: 'babel-eslint'
493493
},
494494
{
495-
code: `class ESLintExample extends Component {
495+
code: `class GetDerivedStateFromPropsTest extends Component {
496496
constructor(props) {
497497
super(props);
498498
this.state = {
499499
id: 123,
500500
};
501501
}
502-
static getDerivedStateFromProps(nextProps, prevState) {
503-
if (prevState.id === nextProps.id) {
502+
static getDerivedStateFromProps(nextProps, otherState) {
503+
if (otherState.id === nextProps.id) {
504504
return {
505505
selected: true,
506506
};
@@ -516,7 +516,29 @@ eslintTester.run('no-unused-state', rule, {
516516
parser: 'babel-eslint'
517517
},
518518
{
519-
code: `class ESLintExample extends Component {
519+
code: `class ComponentDidUpdateTest extends Component {
520+
constructor(props) {
521+
super(props);
522+
this.state = {
523+
id: 123,
524+
};
525+
}
526+
527+
componentDidUpdate(someProps, someState) {
528+
if (someState.id === someProps.id) {
529+
doStuff();
530+
}
531+
}
532+
render() {
533+
return (
534+
<h1>{this.state.selected ? 'Selected' : 'Not selected'}</h1>
535+
);
536+
}
537+
}`,
538+
parser: 'babel-eslint'
539+
},
540+
{
541+
code: `class ShouldComponentUpdateTest extends Component {
520542
constructor(props) {
521543
super(props);
522544
this.state = {
@@ -533,6 +555,27 @@ eslintTester.run('no-unused-state', rule, {
533555
}
534556
}`,
535557
parser: 'babel-eslint'
558+
},
559+
{
560+
code: `class NestedScopesTest extends Component {
561+
constructor(props) {
562+
super(props);
563+
this.state = {
564+
id: 123,
565+
};
566+
}
567+
shouldComponentUpdate(nextProps, nextState) {
568+
return (function() {
569+
return nextState.id === nextProps.id;
570+
})();
571+
}
572+
render() {
573+
return (
574+
<h1>{this.state.selected ? 'Selected' : 'Not selected'}</h1>
575+
);
576+
}
577+
}`,
578+
parser: 'babel-eslint'
536579
}
537580
],
538581

@@ -824,6 +867,53 @@ eslintTester.run('no-unused-state', rule, {
824867
}`,
825868
errors: getErrorMessages(['bar']),
826869
parser: 'babel-eslint'
870+
},
871+
{
872+
code: `class FakePrevStateVariableTest extends Component {
873+
constructor(props) {
874+
super(props);
875+
this.state = {
876+
id: 123,
877+
foo: 456
878+
};
879+
}
880+
881+
componentDidUpdate(someProps, someState) {
882+
if (someState.id === someProps.id) {
883+
const prevState = { foo: 789 };
884+
console.log(prevState.foo);
885+
}
886+
}
887+
render() {
888+
return (
889+
<h1>{this.state.selected ? 'Selected' : 'Not selected'}</h1>
890+
);
891+
}
892+
}`,
893+
errors: getErrorMessages(['foo']),
894+
parser: 'babel-eslint'
895+
},
896+
{
897+
code: `class MissingStateParameterTest extends Component {
898+
constructor(props) {
899+
super(props);
900+
this.state = {
901+
id: 123
902+
};
903+
}
904+
905+
componentDidUpdate(someProps) {
906+
const prevState = { id: 456 };
907+
console.log(prevState.id);
908+
}
909+
render() {
910+
return (
911+
<h1>{this.state.selected ? 'Selected' : 'Not selected'}</h1>
912+
);
913+
}
914+
}`,
915+
errors: getErrorMessages(['id']),
916+
parser: 'babel-eslint'
827917
}
828918
]
829919
});

0 commit comments

Comments
 (0)