Skip to content

Commit 1cddfb5

Browse files
authored
consistent-destructuring: Fix false positive for nested rest destructuring (#2894)
1 parent 1359726 commit 1cddfb5

2 files changed

Lines changed: 117 additions & 8 deletions

File tree

rules/consistent-destructuring.js

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,46 @@ const getRootIdentifier = expression => {
4242
return expression?.type === 'Identifier' ? expression : undefined;
4343
};
4444

45+
const hasRestElement = pattern => {
46+
switch (pattern?.type) {
47+
case 'RestElement': {
48+
return true;
49+
}
50+
51+
case 'AssignmentPattern': {
52+
return hasRestElement(pattern.left);
53+
}
54+
55+
case 'ObjectPattern': {
56+
return pattern.properties.some(property =>
57+
hasRestElement(property.type === 'Property' ? property.value : property),
58+
);
59+
}
60+
61+
case 'ArrayPattern': {
62+
return pattern.elements.some(element =>
63+
hasRestElement(element),
64+
);
65+
}
66+
67+
default: {
68+
return false;
69+
}
70+
}
71+
};
72+
73+
const isIdentifierProperty = property =>
74+
property.type === 'Property'
75+
&& property.key.type === 'Identifier';
76+
77+
const isMemberDestructuredInNestedPatternWithRest = (objectPattern, memberName) =>
78+
objectPattern.properties.some(property =>
79+
isIdentifierProperty(property)
80+
&& property.key.name === memberName
81+
&& property.value.type !== 'Identifier'
82+
&& hasRestElement(property.value),
83+
);
84+
4585
const isRootVariableReassigned = (declaration, memberExpressionNode, memberScope, sourceCode) => {
4686
if (!declaration.rootVariable) {
4787
return false;
@@ -135,24 +175,31 @@ const create = context => {
135175
return;
136176
}
137177

138-
const destructurings = objectPattern.properties.filter(property =>
139-
property.type === 'Property'
140-
&& property.key.type === 'Identifier'
178+
const member = sourceCode.getText(node.property);
179+
180+
// If the member is already destructured via a nested pattern with rest,
181+
// don't suggest adding a separate top-level destructuring for the same member.
182+
const memberDestructuredInNestedPattern = isMemberDestructuredInNestedPatternWithRest(objectPattern, member);
183+
184+
const destructuredProperties = objectPattern.properties.filter(property =>
185+
isIdentifierProperty(property)
141186
&& property.value.type === 'Identifier',
142187
);
143-
const lastProperty = objectPattern.properties.at(-1);
144-
145-
const hasRest = lastProperty && lastProperty.type === 'RestElement';
146188

189+
const lastProperty = objectPattern.properties.at(-1);
190+
const hasRest = lastProperty?.type === 'RestElement';
147191
const expression = sourceCode.getText(node);
148-
const member = sourceCode.getText(node.property);
149192

150193
// Member might already be destructured
151-
const destructuredMember = destructurings.find(property =>
194+
const destructuredMember = destructuredProperties.find(property =>
152195
property.key.name === member,
153196
);
154197

155198
if (!destructuredMember) {
199+
if (memberDestructuredInNestedPattern) {
200+
return;
201+
}
202+
156203
// Don't destructure additional members when rest is used
157204
if (hasRest) {
158205
return;

test/consistent-destructuring.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,46 @@ test({
165165
} = foo;
166166
console.log(b.a);
167167
`,
168+
outdent`
169+
const t = {a: {b: {c: 0, d: 1, e: 2}}};
170+
const {
171+
a: {
172+
b: {c, ...other}
173+
}
174+
} = t;
175+
const tt = {...t, a: {...t.a, b: other}};
176+
`,
177+
outdent`
178+
const t = {
179+
get a() {
180+
return {b: {c: 0, d: 1, e: 2}};
181+
}
182+
};
183+
const {
184+
a: {
185+
b: {c, ...other}
186+
}
187+
} = t;
188+
console.log(t.a, other);
189+
`,
190+
outdent`
191+
const {
192+
a: {
193+
b,
194+
...other
195+
} = fallback
196+
} = foo;
197+
console.log(foo.a, b, other);
198+
`,
199+
outdent`
200+
const {
201+
a: [
202+
b,
203+
...other
204+
]
205+
} = foo;
206+
console.log(foo.a, b, other);
207+
`,
168208
outdent`
169209
function bar() {
170210
const {a} = foo;
@@ -372,6 +412,28 @@ test({
372412
console.log(a);
373413
`],
374414
}),
415+
invalidTestCase({
416+
code: outdent`
417+
const {
418+
a: {
419+
b,
420+
...other
421+
},
422+
a
423+
} = foo;
424+
console.log(foo.a);
425+
`,
426+
suggestions: [outdent`
427+
const {
428+
a: {
429+
b,
430+
...other
431+
},
432+
a
433+
} = foo;
434+
console.log(a);
435+
`],
436+
}),
375437
invalidTestCase({
376438
code: outdent`
377439
const {

0 commit comments

Comments
 (0)