Skip to content

Commit

Permalink
Fix expression parsing in export default statements (#417)
Browse files Browse the repository at this point in the history
* Fix expression parsing in export default statements

* Fix a few related parenthesizing issues in AST to JS conversion
  • Loading branch information
adams85 authored Nov 4, 2023
1 parent 74035ad commit 4d80582
Show file tree
Hide file tree
Showing 13 changed files with 835 additions and 13 deletions.
4 changes: 1 addition & 3 deletions src/Esprima/JavaScriptParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5421,9 +5421,7 @@ private ExportDeclaration ParseExportDeclaration()
// export default {};
// export default [];
// export default (1 + 2);
var declaration = Match("{")
? ParseObjectInitializer()
: Match("[") ? ParseArrayInitializer() : ParseAssignmentExpression();
var declaration = ParseAssignmentExpression();

ConsumeSemicolon();
exportDeclaration = Finalize(node, new ExportDefaultDeclaration(declaration));
Expand Down
4 changes: 3 additions & 1 deletion src/Esprima/Utils/AstToJavaScriptConverter.Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ protected internal enum ExpressionFlags

IsMethod = 1 << 17,

IsInsideExportDefaultExpression = 1 << 21, // automatically propagated to sub-expressions

IsInsideDecorator = 1 << 22, // automatically propagated to sub-expressions

IsInAmbiguousInOperatorContext = 1 << 24, // automatically propagated to sub-expressions
Expand All @@ -56,6 +58,6 @@ protected internal enum ExpressionFlags

IsInsideStatementExpression = 1 << 31, // automatically propagated to sub-expressions

IsInPotentiallyAmbiguousContext = IsInAmbiguousInOperatorContext | IsInsideArrowFunctionBody | IsInsideDecorator | IsInsideNewCallee | IsInsideLeftHandSideExpression | IsInsideStatementExpression,
IsInPotentiallyAmbiguousContext = IsInAmbiguousInOperatorContext | IsInsideArrowFunctionBody | IsInsideDecorator | IsInsideNewCallee | IsInsideLeftHandSideExpression | IsInsideStatementExpression | IsInsideExportDefaultExpression,
}
}
33 changes: 26 additions & 7 deletions src/Esprima/Utils/AstToJavaScriptConverter.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,21 @@ protected ExpressionFlags DisambiguateExpression(Expression expression, Expressi
// Puts the left-most expression in brackets if necessary (in cases where it would be interpreted differently without brackets).
if ((flags & ExpressionFlags.IsInPotentiallyAmbiguousContext) != 0)
{
if (flags.HasFlagFast(ExpressionFlags.IsInsideStatementExpression | ExpressionFlags.IsLeftMost) && ExpressionIsAmbiguousAsStatementExpression(expression) ||
var isAmbiguousExpression = flags.HasFlag(ExpressionFlags.IsLeftMost) &&
(flags.HasFlagFast(ExpressionFlags.IsInsideStatementExpression) && ExpressionIsAmbiguousAsStatementExpression(expression) ||
flags.HasFlagFast(ExpressionFlags.IsInsideExportDefaultExpression) && ExpressionIsAmbiguousAsExportDefaultExpression(expression) ||
flags.HasFlagFast(ExpressionFlags.IsInsideDecorator) && DecoratorLeftMostExpressionIsParenthesized(expression, isRoot: flags.HasFlagFast(ExpressionFlags.IsRootExpression)));

isAmbiguousExpression = isAmbiguousExpression ||
flags.HasFlagFast(ExpressionFlags.IsInsideArrowFunctionBody | ExpressionFlags.IsLeftMostInArrowFunctionBody) && ExpressionIsAmbiguousAsArrowFunctionBody(expression) ||
flags.HasFlagFast(ExpressionFlags.IsInsideNewCallee | ExpressionFlags.IsLeftMostInNewCallee) && ExpressionIsAmbiguousAsNewCallee(expression) ||
flags.HasFlagFast(ExpressionFlags.IsInsideLeftHandSideExpression | ExpressionFlags.IsLeftMostInLeftHandSideExpression) && LeftHandSideExpressionIsParenthesized(expression) ||
flags.HasFlagFast(ExpressionFlags.IsInsideDecorator | ExpressionFlags.IsLeftMost) && DecoratorLeftMostExpressionIsParenthesized(expression, isRoot: flags.HasFlagFast(ExpressionFlags.IsRootExpression)))
{
return (flags | ExpressionFlags.NeedsBrackets) & ~ExpressionFlags.IsInAmbiguousInOperatorContext;
}
flags.HasFlagFast(ExpressionFlags.IsInsideLeftHandSideExpression | ExpressionFlags.IsLeftMostInLeftHandSideExpression) && LeftHandSideExpressionIsParenthesized(expression);

// Edge case: for (var a = b = (c in d in e) in x);
else if (flags.HasFlagFast(ExpressionFlags.IsInAmbiguousInOperatorContext) && expression is BinaryExpression { Operator: BinaryOperator.In })
isAmbiguousExpression = isAmbiguousExpression ||
flags.HasFlagFast(ExpressionFlags.IsInAmbiguousInOperatorContext) && expression is BinaryExpression { Operator: BinaryOperator.In };

if (isAmbiguousExpression)
{
return (flags | ExpressionFlags.NeedsBrackets) & ~ExpressionFlags.IsInAmbiguousInOperatorContext;
}
Expand Down Expand Up @@ -276,6 +281,20 @@ protected virtual bool ExpressionIsAmbiguousAsStatementExpression(Expression exp
return false;
}

protected virtual bool ExpressionIsAmbiguousAsExportDefaultExpression(Expression expression)
{
switch (expression.Type)
{
case Nodes.ClassExpression:
case Nodes.FunctionExpression:
case Nodes.Identifier when Scanner.IsStrictModeReservedWord(expression.As<Identifier>().Name):
case Nodes.SequenceExpression:
return true;
}

return false;
}

protected virtual bool ExpressionIsAmbiguousAsArrowFunctionBody(Expression expression)
{
switch (expression.Type)
Expand Down
4 changes: 2 additions & 2 deletions src/Esprima/Utils/AstToJavaScriptConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ public void Convert(Node node)
Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);

_writeContext.SetNodeProperty(nameof(assignmentPattern.Right), static node => node.As<AssignmentPattern>().Right);
VisitRootExpression(assignmentPattern.Right, RootExpressionFlags(needsBrackets: false));
VisitRootExpression(assignmentPattern.Right, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(assignmentPattern.Right)));

return assignmentPattern;
}
Expand Down Expand Up @@ -640,7 +640,7 @@ public void Convert(Node node)
}
else
{
VisitRootExpression(exportDefaultDeclaration.Declaration.As<Expression>(), ExpressionFlags.IsInsideStatementExpression | RootExpressionFlags(needsBrackets: false));
VisitRootExpression(exportDefaultDeclaration.Declaration.As<Expression>(), ExpressionFlags.IsInsideExportDefaultExpression | RootExpressionFlags(needsBrackets: false));

StatementNeedsSemicolon();
}
Expand Down
21 changes: 21 additions & 0 deletions test/Esprima.Tests/AstToJavascriptTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,27 @@ public void ToJavaScriptTest_NullishCoalescingMixedWithLogicalAndOr_ShouldBePare
}
}

[Theory]
[InlineData("[a = b, c] = [];\n", false)]
[InlineData("[a = (b, c)] = [];\n", false)]
[InlineData("export default a, b;\n", true)]
[InlineData("export default (a, b);\n", false)]
public void ToJavaScriptTest_AmbiguousSequenceExpression_ShouldBeParenthesized(string source, bool expectParseError)
{
source = source.Replace("\n", Environment.NewLine);
var parser = new JavaScriptParser();
if (!expectParseError)
{
var program = parser.ParseModule(source);
var code = AstToJavaScript.ToJavaScriptString(program, format: true);
Assert.Equal(source, code);
}
else
{
Assert.Throws<ParserException>(() => parser.ParseExpression(source));
}
}

[Theory]
[InlineData(true,
@"<>AAA <el attr1=""a"" attr2='b' attr3={x ? 'c' : 'd'} {...(x + 2, [y])}> &lt; {} &gt; </el> BBB <c.el {...[z]}>member</c.el> <ns:el>member</ns:el> DDD </>",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default [
].map((e, i) => ({
...e,
index: i,
}))
Loading

0 comments on commit 4d80582

Please sign in to comment.