diff --git a/core/ast/src/expression/literal/object.rs b/core/ast/src/expression/literal/object.rs index 700a3612991..8786354ef3b 100644 --- a/core/ast/src/expression/literal/object.rs +++ b/core/ast/src/expression/literal/object.rs @@ -41,6 +41,7 @@ use core::{fmt::Write as _, ops::ControlFlow}; #[derive(Clone, Debug, PartialEq)] pub struct ObjectLiteral { properties: Box<[PropertyDefinition]>, + has_trailing_comma_spread: bool, span: Span, } @@ -48,12 +49,13 @@ impl ObjectLiteral { /// Create a new [`ObjectLiteral`]. #[inline] #[must_use] - pub fn new(properties: T, span: Span) -> Self + pub fn new(properties: T, has_trailing_comma_spread: bool, span: Span) -> Self where T: Into>, { Self { properties: properties.into(), + has_trailing_comma_spread, span, } } @@ -65,9 +67,21 @@ impl ObjectLiteral { &self.properties } + /// Indicates if a spread property in the object literal is followed by a trailing comma. + /// This is a syntax error if the object literal is converted into a pattern. + #[inline] + #[must_use] + pub const fn has_trailing_comma_spread(&self) -> bool { + self.has_trailing_comma_spread + } + /// Converts the object literal into an [`ObjectPattern`]. #[must_use] pub fn to_pattern(&self, strict: bool) -> Option { + if self.has_trailing_comma_spread() { + return None; + } + let mut bindings = Vec::new(); for (i, property) in self.properties.iter().enumerate() { match property { diff --git a/core/parser/src/parser/expression/primary/object_initializer/mod.rs b/core/parser/src/parser/expression/primary/object_initializer/mod.rs index a7cc16a98e9..68e92068e5e 100644 --- a/core/parser/src/parser/expression/primary/object_initializer/mod.rs +++ b/core/parser/src/parser/expression/primary/object_initializer/mod.rs @@ -85,6 +85,7 @@ where let mut has_proto = false; let mut duplicate_proto_position = None; + let mut has_trailing_comma_spread = false; let end = loop { if let Some(token) = cursor.next_if(Punctuator::CloseBlock, interner)? { @@ -106,6 +107,7 @@ where } } + let is_spread = matches!(property, PropertyDefinitionNode::SpreadObject(_)); elements.push(property); if let Some(token) = cursor.next_if(Punctuator::CloseBlock, interner)? { @@ -121,6 +123,14 @@ where "object literal", )); } + + if is_spread + && cursor.peek(0, interner)?.is_some_and(|token| { + token.kind() == &TokenKind::Punctuator(Punctuator::CloseBlock) + }) + { + has_trailing_comma_spread = true; + } }; if let Some(position) = duplicate_proto_position @@ -136,7 +146,11 @@ where } let start = open_block_token.span().start(); - Ok(literal::ObjectLiteral::new(elements, Span::new(start, end))) + Ok(literal::ObjectLiteral::new( + elements, + has_trailing_comma_spread, + Span::new(start, end), + )) } } diff --git a/core/parser/src/parser/expression/primary/object_initializer/tests.rs b/core/parser/src/parser/expression/primary/object_initializer/tests.rs index f26ec4a0eff..c1cdf6807cd 100644 --- a/core/parser/src/parser/expression/primary/object_initializer/tests.rs +++ b/core/parser/src/parser/expression/primary/object_initializer/tests.rs @@ -53,7 +53,10 @@ fn check_object_literal() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (4, 2))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (4, 2))) + .into(), + ), )] .try_into() .unwrap(), @@ -105,7 +108,10 @@ fn check_object_short_function() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (4, 2))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (4, 2))) + .into(), + ), )] .try_into() .unwrap(), @@ -171,7 +177,10 @@ fn check_object_short_function_arguments() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (4, 2))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (4, 2))) + .into(), + ), )] .try_into() .unwrap(), @@ -222,7 +231,10 @@ fn check_object_getter() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (4, 2))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (4, 2))) + .into(), + ), )] .try_into() .unwrap(), @@ -287,7 +299,10 @@ fn check_object_setter() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (4, 2))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (4, 2))) + .into(), + ), )] .try_into() .unwrap(), @@ -325,7 +340,10 @@ fn check_object_short_function_get() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (3, 2))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (3, 2))) + .into(), + ), )] .try_into() .unwrap(), @@ -363,7 +381,10 @@ fn check_object_short_function_set() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (3, 2))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (3, 2))) + .into(), + ), )] .try_into() .unwrap(), @@ -407,7 +428,10 @@ fn check_object_shorthand_property_names() { interner.get_or_intern_static("x", utf16!("x")), Span::new((2, 7), (2, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((2, 11), (2, 16))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((2, 11), (2, 16))) + .into(), + ), )] .try_into() .unwrap(), @@ -470,7 +494,10 @@ fn check_object_shorthand_multiple_properties() { interner.get_or_intern_static("x", utf16!("x")), Span::new((3, 7), (3, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((3, 11), (3, 20))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((3, 11), (3, 20))) + .into(), + ), )] .try_into() .unwrap(), @@ -512,7 +539,10 @@ fn check_object_spread() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (1, 25))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (1, 25))) + .into(), + ), )] .try_into() .unwrap(), @@ -554,7 +584,10 @@ fn check_async_method() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (3, 2))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (3, 2))) + .into(), + ), )] .try_into() .unwrap(), @@ -596,7 +629,10 @@ fn check_async_generator_method() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (3, 2))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (3, 2))) + .into(), + ), )] .try_into() .unwrap(), @@ -660,7 +696,10 @@ fn check_async_ordinary_method() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (3, 2))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (3, 2))) + .into(), + ), )] .try_into() .unwrap(), @@ -697,7 +736,10 @@ fn check_async_property() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 7), (1, 8)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 11), (3, 2))).into()), + Some( + ObjectLiteral::new(object_properties, false, Span::new((1, 11), (3, 2))) + .into(), + ), )] .try_into() .unwrap(), diff --git a/core/parser/src/parser/tests/mod.rs b/core/parser/src/parser/tests/mod.rs index d88c845f29b..deb2ee514e3 100644 --- a/core/parser/src/parser/tests/mod.rs +++ b/core/parser/src/parser/tests/mod.rs @@ -623,7 +623,10 @@ fn spread_in_object() { interner.get_or_intern_static("x", utf16!("x")), Span::new((1, 5), (1, 6)), ), - Some(ObjectLiteral::new(object_properties, Span::new((1, 9), (4, 2))).into()), + Some( + ObjectLiteral::new(object_properties, true, Span::new((1, 9), (4, 2))) + .into(), + ), )] .try_into() .unwrap(), @@ -762,7 +765,7 @@ fn hashbang_use_strict_with_with_statement() { "#}, vec![ Statement::With(With::new( - ObjectLiteral::new([], Span::new((2, 6), (2, 8))).into(), + ObjectLiteral::new([], false, Span::new((2, 6), (2, 8))).into(), Block::from(StatementList::new([], LinearPosition::new(27), false)).into(), )) .into(), @@ -825,6 +828,22 @@ fn debugger_statement() { check_invalid_script("debugger + debugger"); } +/// An object rest element cannot be followed by a comma in an assignment pattern. +#[test] +fn object_rest_trailing_comma_in_assignment_pattern() { + check_invalid_script("var rest; ({...rest,} = {});"); + check_invalid_script("var rest; [{...rest,}] = [{}];"); + check_invalid_script("var rest; for ({...rest,} in {});"); + check_invalid_script("var rest; for ({...rest,} of [{}]);"); + + // The comma is fine where the literal stays an object literal. + assert!( + Parser::new(Source::from_bytes("var rest; ({...rest,});")) + .parse_script(&Scope::new_global(), &mut Interner::default()) + .is_ok() + ); +} + #[test] fn invalid_arrow_function() { check_invalid_script(r#"(!()=>"#);