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
Fix: handle array-destructuring with obj assignment in prefer const #8994
Conversation
LGTM |
2 similar comments
LGTM |
LGTM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! I left a few suggestions for additional tests -- there are some cases that I think aren't handled correctly right now.
tests/lib/rules/prefer-const.js
Outdated
{ | ||
code: "let predicate; [typeNode.returnType, predicate] = foo();", | ||
options: [{ destructuring: "all" }] | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test where the MemberExpression
is not the first value in the destructuring group? From looking at the code, I'm not sure this case is handled correctly.
let predicate;
[predicate, typeNode.returnType] = foo();
tests/lib/rules/prefer-const.js
Outdated
@@ -56,6 +56,12 @@ ruleTester.run("prefer-const", rule, { | |||
"let a; function foo() { if (a) {} a = bar(); }", | |||
"let a; function foo() { a = a || bar(); baz(a); }", | |||
"let a; function foo() { bar(++a); }", | |||
"let predicate; [typeNode.returnType, predicate] = foo();", | |||
"let predicate; let rest; [typeNode.returnType, predicate, ...rest] = foo();", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't these be invalid since they have the default destructuring: "any"
option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point @not-an-aardvark
In that case, would it make sense to say that the example in #8308 is actually behaving as expected? If destructuring: "any"
is on by default, it should report that predicate is not reassigned then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, it seems like you're right. However, the bug still exists with destructuring: all
, right?
tests/lib/rules/prefer-const.js
Outdated
{ | ||
code: "let predicate; [typeNode.returnType, predicate] = foo();", | ||
options: [{ destructuring: "all" }] | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test where the destructuring uses an ObjectPattern
rather than an ArrayPattern
? From looking at the code, I'm not sure this case is handled correctly.
let foo;
({ x: bar.baz, y: foo } = qux);
tests/lib/rules/prefer-const.js
Outdated
{ | ||
code: "let predicate; [typeNode.returnType, predicate] = foo();", | ||
options: [{ destructuring: "all" }] | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test where the declarations use nested destructuring? From looking at the code, I'm not sure this case is handled correctly.
let foo;
[foo, [bar.baz]] = qux;
LGTM |
1 similar comment
LGTM |
lib/rules/prefer-const.js
Outdated
@@ -199,6 +200,28 @@ function groupByDestructuring(variables, ignoreReadBeforeAssign) { | |||
} | |||
} | |||
|
|||
if (!checkingMixedDestructuring) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move this out of the groupByDestructuring
function (maybe into a different helper function)? It seems like a separate concern from grouping by destructuring.
lib/rules/prefer-const.js
Outdated
const destructureElement = destructureGroup[0]; | ||
let elementsInHost; | ||
|
||
if (destructureElement && destructureElement.parent) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this if
statement necessary? It seems like destructureElement
and destructureElement.parent
will always exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like one test case let {a = 0, b} = obj, c = a; b = a;
has destrucutreGroup as [null]
Not clear on the reason.
lib/rules/prefer-const.js
Outdated
} | ||
} | ||
|
||
if (elementsInHost && elementsInHost.length && elementsInHost.length !== destructureGroup.length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this strategy will fail for things like
let foo; [foo, []] = qux;
or like this:
let foo, bar; [foo, [bar, baz.qux]] = qux;
With the desctructuring: all
option, the first case should be invalid, and the second case should be valid.
I think this is going to be a problem with the approach of checking the number of elements in a destructuring pattern, because it won't be able to account for nested destructuring.
Friendly ping - is there anything we can help with here? |
totally forgot about this pr. I will take another look from comments. |
f9f6582
to
695b006
Compare
LGTM |
@not-an-aardvark when you have some time, could you review this? I updated base on your feedback by going through the nested structure recursively. Let me know if that could work |
const numberOfElements = node.elements.length; | ||
|
||
for (let i = 0; i < numberOfElements; i++) { | ||
if (!node.elements[i].elements && node.elements[i].type === "Identifier") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused about this line -- is the !node.elements[i].elements
part redundant? It seems like an Identifier
node would never have an elements
property.
|
||
verifyAllDestructuring(identifierMap, checkingMixedDestructuring, destructureCount, destructureIdentifier).forEach(checkGroup); | ||
}, | ||
"ExpressionStatement[expression.left.type = /ArrayPattern|ObjectPattern/]"(node) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be AssignmentExpression
rather than ExpressionStatement
? Ostensibly we also want to catch assignments that aren't in an ExpressionStatement
, e.g. foo([bar] = 1)
.
* in IdentifierMap | ||
* @returns {Map<ASTNode, ASTNode[]>} Grouped identifier nodes. | ||
*/ | ||
function verifyAllDestructuring(identifierMap, checkingMixedDestructuring, destructureCount, destructureIdentifier) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible for this function to return a new Map
instead? In general I don't think it's a good idea for functions to mutate their arguments.
* @param {integer|null} destructureCount count of destructure terms. | ||
* @param {ASTNode[]|null} destructureIdentifier list of Identifier nodes to check | ||
* in IdentifierMap | ||
* @returns {Map<ASTNode, ASTNode[]>} Grouped identifier nodes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make this description of the return type more detailed? For example, how are the nodes grouped?
groupByDestructuring(variables, ignoreReadBeforeAssign).forEach(checkGroup); | ||
const identifierMap = groupByDestructuring(variables, ignoreReadBeforeAssign); | ||
|
||
verifyAllDestructuring(identifierMap, checkingMixedDestructuring, destructureCount, destructureIdentifier).forEach(checkGroup); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem right to me -- destructureCount
is getting reassigned on every ExpressionStatement
, so only the final value is getting used in the Program:exit
listener. It seems like it doesn't make sense for the previous values of destructureCount
to get discarded.
Thanks! This mostly seems good -- I left a few more comments. If it would be helpful, I'm planning to look into this a bit more deeply when I have time (so that I can make more concrete implementation suggestions rather than pointing out things that look incorrect). |
@not-an-aardvark I think you looking into it when you have time will help immensely 👍 |
@not-an-aardvark Where are we on this? |
I still plan to look into it, sorry about the delay. |
(fixes #8308)
NPM version: 3.10.10
node: v6.9.5
What parser (default, Babel-ESLint, etc.) are you using?
default
What is the purpose of this pull request? (put an "X" next to item)
[X] Bug fix (template)
What changes did you make? (Give an overview)
Added a check in prefer_const where it counts the number of nodes in a destructure group and checks with the number of elements in the parent of the first element in the group. This is how I am checking if everything else in the group can be converted to
const
.Is there anything you'd like reviewers to focus on?