Skip to content

Commit 4d80582

Browse files
authored
Fix expression parsing in export default statements (#417)
* Fix expression parsing in export default statements * Fix a few related parenthesizing issues in AST to JS conversion
1 parent 74035ad commit 4d80582

13 files changed

+835
-13
lines changed

src/Esprima/JavaScriptParser.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5421,9 +5421,7 @@ private ExportDeclaration ParseExportDeclaration()
54215421
// export default {};
54225422
// export default [];
54235423
// export default (1 + 2);
5424-
var declaration = Match("{")
5425-
? ParseObjectInitializer()
5426-
: Match("[") ? ParseArrayInitializer() : ParseAssignmentExpression();
5424+
var declaration = ParseAssignmentExpression();
54275425

54285426
ConsumeSemicolon();
54295427
exportDeclaration = Finalize(node, new ExportDefaultDeclaration(declaration));

src/Esprima/Utils/AstToJavaScriptConverter.Enums.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ protected internal enum ExpressionFlags
4040

4141
IsMethod = 1 << 17,
4242

43+
IsInsideExportDefaultExpression = 1 << 21, // automatically propagated to sub-expressions
44+
4345
IsInsideDecorator = 1 << 22, // automatically propagated to sub-expressions
4446

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

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

59-
IsInPotentiallyAmbiguousContext = IsInAmbiguousInOperatorContext | IsInsideArrowFunctionBody | IsInsideDecorator | IsInsideNewCallee | IsInsideLeftHandSideExpression | IsInsideStatementExpression,
61+
IsInPotentiallyAmbiguousContext = IsInAmbiguousInOperatorContext | IsInsideArrowFunctionBody | IsInsideDecorator | IsInsideNewCallee | IsInsideLeftHandSideExpression | IsInsideStatementExpression | IsInsideExportDefaultExpression,
6062
}
6163
}

src/Esprima/Utils/AstToJavaScriptConverter.Helpers.cs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,16 +138,21 @@ protected ExpressionFlags DisambiguateExpression(Expression expression, Expressi
138138
// Puts the left-most expression in brackets if necessary (in cases where it would be interpreted differently without brackets).
139139
if ((flags & ExpressionFlags.IsInPotentiallyAmbiguousContext) != 0)
140140
{
141-
if (flags.HasFlagFast(ExpressionFlags.IsInsideStatementExpression | ExpressionFlags.IsLeftMost) && ExpressionIsAmbiguousAsStatementExpression(expression) ||
141+
var isAmbiguousExpression = flags.HasFlag(ExpressionFlags.IsLeftMost) &&
142+
(flags.HasFlagFast(ExpressionFlags.IsInsideStatementExpression) && ExpressionIsAmbiguousAsStatementExpression(expression) ||
143+
flags.HasFlagFast(ExpressionFlags.IsInsideExportDefaultExpression) && ExpressionIsAmbiguousAsExportDefaultExpression(expression) ||
144+
flags.HasFlagFast(ExpressionFlags.IsInsideDecorator) && DecoratorLeftMostExpressionIsParenthesized(expression, isRoot: flags.HasFlagFast(ExpressionFlags.IsRootExpression)));
145+
146+
isAmbiguousExpression = isAmbiguousExpression ||
142147
flags.HasFlagFast(ExpressionFlags.IsInsideArrowFunctionBody | ExpressionFlags.IsLeftMostInArrowFunctionBody) && ExpressionIsAmbiguousAsArrowFunctionBody(expression) ||
143148
flags.HasFlagFast(ExpressionFlags.IsInsideNewCallee | ExpressionFlags.IsLeftMostInNewCallee) && ExpressionIsAmbiguousAsNewCallee(expression) ||
144-
flags.HasFlagFast(ExpressionFlags.IsInsideLeftHandSideExpression | ExpressionFlags.IsLeftMostInLeftHandSideExpression) && LeftHandSideExpressionIsParenthesized(expression) ||
145-
flags.HasFlagFast(ExpressionFlags.IsInsideDecorator | ExpressionFlags.IsLeftMost) && DecoratorLeftMostExpressionIsParenthesized(expression, isRoot: flags.HasFlagFast(ExpressionFlags.IsRootExpression)))
146-
{
147-
return (flags | ExpressionFlags.NeedsBrackets) & ~ExpressionFlags.IsInAmbiguousInOperatorContext;
148-
}
149+
flags.HasFlagFast(ExpressionFlags.IsInsideLeftHandSideExpression | ExpressionFlags.IsLeftMostInLeftHandSideExpression) && LeftHandSideExpressionIsParenthesized(expression);
150+
149151
// Edge case: for (var a = b = (c in d in e) in x);
150-
else if (flags.HasFlagFast(ExpressionFlags.IsInAmbiguousInOperatorContext) && expression is BinaryExpression { Operator: BinaryOperator.In })
152+
isAmbiguousExpression = isAmbiguousExpression ||
153+
flags.HasFlagFast(ExpressionFlags.IsInAmbiguousInOperatorContext) && expression is BinaryExpression { Operator: BinaryOperator.In };
154+
155+
if (isAmbiguousExpression)
151156
{
152157
return (flags | ExpressionFlags.NeedsBrackets) & ~ExpressionFlags.IsInAmbiguousInOperatorContext;
153158
}
@@ -276,6 +281,20 @@ protected virtual bool ExpressionIsAmbiguousAsStatementExpression(Expression exp
276281
return false;
277282
}
278283

284+
protected virtual bool ExpressionIsAmbiguousAsExportDefaultExpression(Expression expression)
285+
{
286+
switch (expression.Type)
287+
{
288+
case Nodes.ClassExpression:
289+
case Nodes.FunctionExpression:
290+
case Nodes.Identifier when Scanner.IsStrictModeReservedWord(expression.As<Identifier>().Name):
291+
case Nodes.SequenceExpression:
292+
return true;
293+
}
294+
295+
return false;
296+
}
297+
279298
protected virtual bool ExpressionIsAmbiguousAsArrowFunctionBody(Expression expression)
280299
{
281300
switch (expression.Type)

src/Esprima/Utils/AstToJavaScriptConverter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ public void Convert(Node node)
290290
Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
291291

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

295295
return assignmentPattern;
296296
}
@@ -640,7 +640,7 @@ public void Convert(Node node)
640640
}
641641
else
642642
{
643-
VisitRootExpression(exportDefaultDeclaration.Declaration.As<Expression>(), ExpressionFlags.IsInsideStatementExpression | RootExpressionFlags(needsBrackets: false));
643+
VisitRootExpression(exportDefaultDeclaration.Declaration.As<Expression>(), ExpressionFlags.IsInsideExportDefaultExpression | RootExpressionFlags(needsBrackets: false));
644644

645645
StatementNeedsSemicolon();
646646
}

test/Esprima.Tests/AstToJavascriptTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,27 @@ public void ToJavaScriptTest_NullishCoalescingMixedWithLogicalAndOr_ShouldBePare
788788
}
789789
}
790790

791+
[Theory]
792+
[InlineData("[a = b, c] = [];\n", false)]
793+
[InlineData("[a = (b, c)] = [];\n", false)]
794+
[InlineData("export default a, b;\n", true)]
795+
[InlineData("export default (a, b);\n", false)]
796+
public void ToJavaScriptTest_AmbiguousSequenceExpression_ShouldBeParenthesized(string source, bool expectParseError)
797+
{
798+
source = source.Replace("\n", Environment.NewLine);
799+
var parser = new JavaScriptParser();
800+
if (!expectParseError)
801+
{
802+
var program = parser.ParseModule(source);
803+
var code = AstToJavaScript.ToJavaScriptString(program, format: true);
804+
Assert.Equal(source, code);
805+
}
806+
else
807+
{
808+
Assert.Throws<ParserException>(() => parser.ParseExpression(source));
809+
}
810+
}
811+
791812
[Theory]
792813
[InlineData(true,
793814
@"<>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 </>",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default [
2+
].map((e, i) => ({
3+
...e,
4+
index: i,
5+
}))

0 commit comments

Comments
 (0)