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

[parser] validation for parentheses in the left-hand side of assignment expressions #10576

Merged
merged 2 commits into from Dec 10, 2019

Conversation

boweihan
Copy link
Contributor

@boweihan boweihan commented Oct 18, 2019

Q                       A
Fixed Issues? Fixes #8796, fixes #9899
Patch: Bug Fix? Yes
Major: Breaking Change? No
Minor: New Feature? No
Tests Added + Pass? Yes
Documentation PR Link No
Any Dependency Changes? No
License MIT

@nicolo-ribaudo has a good description of the problem in the linked issue. Basically, the parser is missing some validation for parentheses in the left-hand side of assignment expressions.

This means that expressions such as [a = 1] = t and [ { x: y = 3 } ] = t are valid (and correctly so!).

The issue with the current behaviour is that expressions such as 1:(a = 1) = t and 2:[(a = 1)] = t are treated as valid. The left-hand side of assignment expressions should be invalid if there are any parenthesized child nodes which are not of type Identifier or MemberExpression.

The fix in this pr involves:

  • adding the parens check to toAssignable
  • updating / adding test cases to reflect new behavior

@boweihan boweihan changed the title [parser] validation for assignment patterns in the left-hand side of expressions [parser] validation for left-hand side assignments in assignments Oct 19, 2019
@buildsize
Copy link

buildsize bot commented Oct 19, 2019

File name Previous Size New Size Change
babel-preset-env.js 2.77 MB 2.77 MB -78 bytes (0%)
babel-preset-env.min.js 1.67 MB 1.67 MB -13 bytes (0%)
babel.js 2.95 MB 2.95 MB -78 bytes (0%)
babel.min.js 1.63 MB 1.63 MB -13 bytes (0%)
test262.tap 4.84 MB [deleted]

@boweihan boweihan changed the title [parser] validation for left-hand side assignments in assignments [parser] validation for left-hand side assignments in assignment expressions Oct 19, 2019
@nicolo-ribaudo nicolo-ribaudo added pkg: parser PR: Spec Compliance 👓 A type of pull request used for our changelog categories labels Oct 19, 2019
prop,
isBinding,
isLast,
allowAssign,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it passed down? Inside an object property, assignments are always allowed (for default props)

Copy link
Contributor Author

@boweihan boweihan Oct 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original approach needed to pass it down to "remember" that there had been a parenthesis somewhere up the stack - to invalidate expressions such as [({ a: [b = 2]})] = t. It's become more clear to me now that the presence of certain parenthesis in the left-hand side of an assignment expression is the problem so I'll clean this up.

Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While playing in the repl, I found another case which is deeply related to this one:

[([x])] = y;

I know that I wrote the hint you followed to implement this PR, but I now think that there could be a better way of doing it 😅
Since parenthesized assignment target are the exception, and they should only be allowed in a top-level target (i.e. not inside a destructuring), I think that we can pass a isParenthesized flag instead of allowAssign and:

  1. If it isn't set, you should set it to node.extra?.parenthesized using a default param
  2. If isParenthesized is true and the current node type is not MemberExpression or Identifier, throw an error (e.g. Invalid parenthesized assignment pattern).

Note that the isParenthesized parameter should be set to true only when converting the child a ParenthesizedExpression

@boweihan boweihan force-pushed the issue/8796 branch 4 times, most recently from 6807302 to 7e158a1 Compare October 19, 2019 19:54
@@ -1,3 +1,3 @@
{
"throws": "You're trying to assign to a parenthesized expression, eg. instead of `([a]) = 0` use `([a] = 0)` (1:1)"
Copy link
Contributor Author

@boweihan boweihan Oct 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We lose this specific error message by applying a more general fix but I think it's probably worth it in terms of code cleanliness - messages for specific cases can be added as an extension.

@boweihan
Copy link
Contributor Author

@nicolo-ribaudo makes sense - I've updated the code. I think we can get away with not having an extra parameter in toAssignable.

The new logic covers most of the parens validation needed except for when the createParenthesizedExpressions parser option is set to true - github.com//pull/8025. There is separate validation for that code path which I have kept as-is (unwrapParenthesizedExpression in parser/expression.js).

@boweihan boweihan changed the title [parser] validation for left-hand side assignments in assignment expressions [parser] validation for parentheses in the left-hand side of assignment expressions Oct 19, 2019
@JLHwung JLHwung self-requested a review November 1, 2019 15:45
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new logic covers most of the parens validation needed except for when the createParenthesizedExpressions parser option is set to true

So it still doesn't throw the error in that case?

packages/babel-parser/src/parser/lval.js Outdated Show resolved Hide resolved
@boweihan
Copy link
Contributor Author

boweihan commented Nov 3, 2019

@nicolo-ribaudo @JLHwung originally I thought there might need to be a separate flow for handling the createParenthesizedExpressions parser option but I've figured out a more straightforward approach that works for both. Have added tests to cover each case.

Edit: looks like tests may be failing due to a memory issue - ENOMEM: not enough memory, read

@@ -19,6 +19,12 @@ import { isStrictBindReservedWord } from "../util/identifier";
import { NodeUtils } from "./node";
import { type BindingTypes, BIND_NONE } from "../util/scopeflags";

const unwrapParenthesizedExpression = (node: Node) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is needed to handle nested parenthesis when createParenthesizedExpressions is enabled. i.e. [(((x)))]

Copy link
Contributor

@JLHwung JLHwung Nov 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For nested parenthesis, toAssignable will be called with parenthesizee recursively. Therefore we may check if node.expression is one of Identifier, MemberExpression and ParenthesizedExpression.

@JLHwung
Copy link
Contributor

JLHwung commented Nov 3, 2019

@boweihan Circle CI is flaky. I have rerun the build.

@JLHwung JLHwung merged commit 20e43ad into babel:master Dec 10, 2019
@JLHwung
Copy link
Contributor

JLHwung commented Dec 10, 2019

@boweihan Well done! Thanks.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
outdated A closed issue/PR that is archived due to age. Recommended to make a new issue pkg: parser PR: Spec Compliance 👓 A type of pull request used for our changelog categories
Projects
None yet
3 participants