Skip to content
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

Closed

Conversation

VictorHom
Copy link
Member

(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?

@eslintbot
Copy link

LGTM

2 similar comments
@eslintbot
Copy link

LGTM

@eslintbot
Copy link

LGTM

Copy link
Member

@not-an-aardvark not-an-aardvark left a 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.

{
code: "let predicate; [typeNode.returnType, predicate] = foo();",
options: [{ destructuring: "all" }]
},
Copy link
Member

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();

@@ -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();",
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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?

{
code: "let predicate; [typeNode.returnType, predicate] = foo();",
options: [{ destructuring: "all" }]
},
Copy link
Member

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);

{
code: "let predicate; [typeNode.returnType, predicate] = foo();",
options: [{ destructuring: "all" }]
},
Copy link
Member

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;

@eslintbot
Copy link

LGTM

1 similar comment
@eslintbot
Copy link

LGTM

@VictorHom VictorHom changed the title (WIP) Fix: handle array-destructuring with obj assignment in prefer const Fix: handle array-destructuring with obj assignment in prefer const Jul 29, 2017
@@ -199,6 +200,28 @@ function groupByDestructuring(variables, ignoreReadBeforeAssign) {
}
}

if (!checkingMixedDestructuring) {
Copy link
Member

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.

const destructureElement = destructureGroup[0];
let elementsInHost;

if (destructureElement && destructureElement.parent) {
Copy link
Member

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.

Copy link
Member Author

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.

}
}

if (elementsInHost && elementsInHost.length && elementsInHost.length !== destructureGroup.length) {
Copy link
Member

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.

@not-an-aardvark not-an-aardvark added accepted There is consensus among the team that this change meets the criteria for inclusion bug ESLint is working incorrectly rule Relates to ESLint's core rules labels Aug 1, 2017
@kaicataldo
Copy link
Member

Friendly ping - is there anything we can help with here?

@VictorHom
Copy link
Member Author

totally forgot about this pr. I will take another look from comments.

@eslintbot
Copy link

LGTM

@VictorHom
Copy link
Member Author

@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") {
Copy link
Member

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) {
Copy link
Member

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) {
Copy link
Member

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.
Copy link
Member

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);
Copy link
Member

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.

@not-an-aardvark
Copy link
Member

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).

@VictorHom
Copy link
Member Author

@not-an-aardvark I think you looking into it when you have time will help immensely 👍

@platinumazure
Copy link
Member

@not-an-aardvark Where are we on this?

@not-an-aardvark
Copy link
Member

I still plan to look into it, sorry about the delay.

@VictorHom VictorHom closed this May 26, 2018
@VictorHom VictorHom deleted the destructuring_prefer_const_err branch May 26, 2018 22:16
@eslint-deprecated eslint-deprecated bot locked and limited conversation to collaborators Nov 24, 2018
@eslint-deprecated eslint-deprecated bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Nov 24, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion bug ESLint is working incorrectly rule Relates to ESLint's core rules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

False-positive in prefer-const and array-destructuring
5 participants