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: do-statementlist behavior #11724
Conversation
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/24439/ |
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit f469bdf:
|
@@ -67,13 +68,16 @@ export default function gatherSequenceExpressions( | |||
} else if (isEmptyStatement(node)) { | |||
// empty statement so ensure the last item is undefined if we're last | |||
ensureLastUndefined = true; |
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.
If all of nodes
are empty statements, it should ensureLastUndefined
. i.e. both eval("{ ; }")
and eval("{ ;;; }")
returns undefined
. Note that you can mentally think of do { ; }
as eval("{ ; }")
.
However, when empty statements follow any value producing statements, it should not ensureLastUndefined
. i.e. eval("{ 3;; }")
returns 3
.
Therefore we should check
- If the empty statement is the first of
nodes
, we shouldensureLastUndefined
, otherwise it is a no-op because it can always be omitted. Conceptually it is more likeensureFirstUndefined
.
I don't think we need to track multiColons
because
- the specific value of multiColons is not used as a number later
- Transformers operates on AST level (
EmptyStatement
v.s.colon
) while colon is a grammar structure and should be addressed by tokenizer instead
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.
yup that makes sense, just have a question: if accessing the path
isn't available to check if it's inList
or key
is there a way to do it with the array of nodes
being passed to the function?
I've also explored the scope
that gets passed and trying to use it's path
but inList
and key
are undefined
for it.
packages/babel-plugin-proposal-do-expressions/test/fixtures/do-expressions/semicolons.js
Show resolved
Hide resolved
// checks if node is first | ||
// checks if input is `;` or `;;` which will return `()` in where | ||
// the length will always be 2 | ||
if (nodes.indexOf(node) === 0 || nodes.length === 2) { |
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 don't think we need to check specifically nodes.length === 2
? i.e. do { ;;; }
should also return undefined
.
To mark non-leading EmptyStatement as no-op, we can reset ensureLastUndefined = false
only if node
is not an EmptyStatement.
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.
yeah i added the node.length
because ()
always threw an error regardless of the amount of ;
given.
just to clarify what does no-op mean ?
and do we need to explicitly mark it false
since we're in the condition that it is true
?
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.
() always threw an error regardless of the amount of ; given.
It is syntax error if there is only empty statement in the ()
, i.e. ()
, (;)
, (;;)
should throw.
just to clarify what does no-op mean ?
no-op means we do not change ensureLastUndefined
, as you can see it is reset as false
in the beginning of loop. We can check empty statement before validating.
Let eLU
be ensureLastUndefined
, we can illustrate how eLU
is changed between EmptyStatements ant other statements.
; ;;;;; 42 ;;;;;
|--> eLU = true |--> eLU = (previous eLU) = true |--> eLU = false |--> eLU= (previous eLU) = false
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.
if ()
, (;)
, (;;)
should throw would it be a different issue because checking if node
is first fails the cases just mentioned?
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.
Babel currently throws syntax error for ()
, (;)
, which is expected. Generally when you are working on AST, you may assume that it is always valid combinations of AST nodes, in this case, it is EmptyStatement
nested in BlockStatement
. The combinations of punctuators, i.e. (
, )
should be cared by the parser, and by the time when you have a valid AST, it can not be a representation of the source like ()
.
Generally parentheses do not affect the semantic meaning, it is a valid combination only if there is an expression wrapped inside parentheses: https://tc39.es/ecma262/#prod-ParenthesizedExpression
ParenthesizedExpression[Yield, Await]:
( Expression[+In, ?Yield, ?Await] )
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.
right, ok the changes i just did keeps the checking if node
is first and letting it throw if ()
, (;)
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 I understand what you mean by ()
and (;)
In the example { ;; }
, the parse AST is
[ EmptyStatement, EmptyStatement ]
In the first loop, ensureLastUndefined
is set to true
.
In the second loop, ensureLastUndefined
is set to false
in the beginning of the loop body:
ensureLastUndefined = false; |
Since exprs
is an empty array, ensureLastUndefined
is false
, sequenceExpression(exprs)
will be printed as ()
, which will throw syntax error because sequence expression can not be empty.
We can reset ensureLastUndefined
only if node
is not an EmptyStatement
.
for (const node of nodes) {
if (!isEmptyStatement(node)) {
ensureLastUndefined = false;
}
// ...
}
So this ensures that when we are processing an EmptyStatement following any statements, it becomes an no-op, which does not change ensureLastUndefined
.
If we only check node is first of nodes && nodes.length === 2
, it can not handle do { ;;; }
: When processing the third empty statement, ensureLastUndefined
will still be reset to false
and gatherSequenceExpressions
will still generate an empty sequence expression.
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 makes so much sense, thank u!
expect(() => { | ||
generateCode(sequence.expressions[1]); | ||
}).toThrow("Cannot read property '1' of undefined"); | ||
// expect(generateCode(sequence.expressions[1])).toBe("undefined"); |
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.
The test is failing because the test assumption does not hold if the empty statement is following a value producing statement, in this test case, undefined ;
will not produce a sequence expression because the empty statement is not the leading statement. Therefore sequence.expression
is undefined
.
However, asserting an error thrown because it is in fact not an sequence expression can be confusing to other contributors. Moreover, we know that it still gathers empty statement only if the EmptyStatement
is the first element:
So we need to revise the test and clearly state its new behaviour
it("gathers leading empty statement", function() {
const node = parseCode(";");
const sequence = t.toSequenceExpression([node, t.cloneNode(node)], scope);
expect(generateCode(sequence)).toBe("undefined");
});
@@ -320,7 +320,7 @@ describe("converters", function () { | |||
it("gathers empty statements", function () { |
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 also change the test title here? Because we have changed the behaviour.
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.
The e2e CI error has been fixed on |
Added check for multi-use of semicolons where it was returning
void 0
before.Input
Output
The parsing issue addressed in #9404 seems to be correct and ok (reference: 9404 comment)