[ESLint] Suggest to destructure props when they are only used as members (#14993)

* Suggest to destructure props when they are only used as members

* Add more tests

* Fix a bug
This commit is contained in:
Dan Abramov
2019-03-01 19:48:48 +00:00
committed by GitHub
parent 59ef28437a
commit e1e45fb367
2 changed files with 223 additions and 1 deletions
@@ -2224,7 +2224,189 @@ const tests = {
}
`,
errors: [
// TODO: make this message clearer since it's not obvious why.
"React Hook useEffect has a missing dependency: 'props'. " +
'Either include it or remove the dependency array. ' +
'Alternatively, destructure the necessary props outside the callback.',
],
},
{
code: `
function MyComponent(props) {
useEffect(() => {
function play() {
props.onPlay();
}
function pause() {
props.onPause();
}
}, []);
}
`,
output: `
function MyComponent(props) {
useEffect(() => {
function play() {
props.onPlay();
}
function pause() {
props.onPause();
}
}, [props]);
}
`,
errors: [
"React Hook useEffect has a missing dependency: 'props'. " +
'Either include it or remove the dependency array. ' +
'Alternatively, destructure the necessary props outside the callback.',
],
},
{
code: `
function MyComponent(props) {
useEffect(() => {
if (props.foo.onChange) {
props.foo.onChange();
}
}, []);
}
`,
output: `
function MyComponent(props) {
useEffect(() => {
if (props.foo.onChange) {
props.foo.onChange();
}
}, [props.foo]);
}
`,
errors: [
"React Hook useEffect has a missing dependency: 'props.foo'. " +
'Either include it or remove the dependency array.',
],
},
{
code: `
function MyComponent(props) {
useEffect(() => {
props.onChange();
if (props.foo.onChange) {
props.foo.onChange();
}
}, []);
}
`,
output: `
function MyComponent(props) {
useEffect(() => {
props.onChange();
if (props.foo.onChange) {
props.foo.onChange();
}
}, [props]);
}
`,
errors: [
"React Hook useEffect has a missing dependency: 'props'. " +
'Either include it or remove the dependency array. ' +
'Alternatively, destructure the necessary props outside the callback.',
],
},
{
code: `
function MyComponent(props) {
const [skillsCount] = useState();
useEffect(() => {
if (skillsCount === 0 && !props.isEditMode) {
props.toggleEditMode();
}
}, [skillsCount, props.isEditMode, props.toggleEditMode]);
}
`,
output: `
function MyComponent(props) {
const [skillsCount] = useState();
useEffect(() => {
if (skillsCount === 0 && !props.isEditMode) {
props.toggleEditMode();
}
}, [skillsCount, props.isEditMode, props.toggleEditMode, props]);
}
`,
errors: [
"React Hook useEffect has a missing dependency: 'props'. " +
'Either include it or remove the dependency array. ' +
'Alternatively, destructure the necessary props outside the callback.',
],
},
{
code: `
function MyComponent(props) {
const [skillsCount] = useState();
useEffect(() => {
if (skillsCount === 0 && !props.isEditMode) {
props.toggleEditMode();
}
}, []);
}
`,
output: `
function MyComponent(props) {
const [skillsCount] = useState();
useEffect(() => {
if (skillsCount === 0 && !props.isEditMode) {
props.toggleEditMode();
}
}, [props, skillsCount]);
}
`,
errors: [
"React Hook useEffect has missing dependencies: 'props' and 'skillsCount'. " +
'Either include them or remove the dependency array. ' +
'Alternatively, destructure the necessary props outside the callback.',
],
},
{
code: `
function MyComponent(props) {
useEffect(() => {
externalCall(props);
props.onChange();
}, []);
}
`,
output: `
function MyComponent(props) {
useEffect(() => {
externalCall(props);
props.onChange();
}, [props]);
}
`,
// Don't suggest to destructure props here since you can't.
errors: [
"React Hook useEffect has a missing dependency: 'props'. " +
'Either include it or remove the dependency array.',
],
},
{
code: `
function MyComponent(props) {
useEffect(() => {
props.onChange();
externalCall(props);
}, []);
}
`,
output: `
function MyComponent(props) {
useEffect(() => {
props.onChange();
externalCall(props);
}, [props]);
}
`,
// Don't suggest to destructure props here since you can't.
errors: [
"React Hook useEffect has a missing dependency: 'props'. " +
'Either include it or remove the dependency array.',
],
@@ -449,6 +449,46 @@ export default {
}
}
// `props.foo()` marks `props` as a dependency because it has
// a `this` value. This warning can be confusing.
// So if we're going to show it, append a clarification.
if (!extraWarning && missingDependencies.has('props')) {
let propDep = dependencies.get('props');
if (propDep == null) {
return;
}
const refs = propDep.reference.resolved.references;
if (!Array.isArray(refs)) {
return;
}
let isPropsOnlyUsedInMembers = true;
for (let i = 0; i < refs.length; i++) {
const ref = refs[i];
const id = fastFindReferenceWithParent(
componentScope.block,
ref.identifier,
);
if (!id) {
isPropsOnlyUsedInMembers = false;
break;
}
const parent = id.parent;
if (parent == null) {
isPropsOnlyUsedInMembers = false;
break;
}
if (parent.type !== 'MemberExpression') {
isPropsOnlyUsedInMembers = false;
break;
}
}
if (isPropsOnlyUsedInMembers) {
extraWarning =
' Alternatively, destructure the necessary props ' +
'outside the callback.';
}
}
context.report({
node: declaredDependenciesNode,
message: