From d49656d6c5f3fadaf9fea0e14c73bfbdc817747c Mon Sep 17 00:00:00 2001 From: Juan Date: Fri, 28 Apr 2023 22:19:02 +0000 Subject: [PATCH] Bugfix/new.target is not understood by the parser as an expression #2793 (#2878) This Pull Request fixes/closes #2793. It changes the following: - Added a condition to the boa_parser/src/parser/expression/left_hand_side/member.rs parse function match operation for the new token that allows for the operation to continue evaluating more tokens when the TARGET keyword follows it. - Added a test to validate the fix. (Could not figure out the structure of the test suite so it's commented for now). All other tests pass. Please let me know if there's anything else I can do to improve the fix. Co-authored-by: jedel1043 --- .../expression/left_hand_side/member.rs | 29 ++++--- .../declaration/hoistable/class_decl/tests.rs | 82 ++++++++++++++++++- 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/boa_parser/src/parser/expression/left_hand_side/member.rs b/boa_parser/src/parser/expression/left_hand_side/member.rs index f0ad8bf4ad..e42c34fe72 100644 --- a/boa_parser/src/parser/expression/left_hand_side/member.rs +++ b/boa_parser/src/parser/expression/left_hand_side/member.rs @@ -82,7 +82,7 @@ where TokenKind::Keyword((Keyword::New, false)) => { cursor.advance(interner); - if cursor.next_if(Punctuator::Dot, interner)?.is_some() { + let lhs_new_target = if cursor.next_if(Punctuator::Dot, interner)?.is_some() { let token = cursor.next(interner).or_abrupt()?; match token.kind() { TokenKind::IdentifierName((Sym::TARGET, ContainsEscapeSequence(true))) => { @@ -92,7 +92,7 @@ where )); } TokenKind::IdentifierName((Sym::TARGET, ContainsEscapeSequence(false))) => { - return Ok(ast::Expression::NewTarget) + ast::Expression::NewTarget } _ => { return Err(Error::general( @@ -101,19 +101,22 @@ where )); } } - } + } else { + let lhs_inner = self.parse(cursor, interner)?; + let args = match cursor.peek(0, interner)? { + Some(next) + if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) => + { + Arguments::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)? + } + _ => Box::new([]), + }; + let call_node = Call::new(lhs_inner, args); - let lhs = self.parse(cursor, interner)?; - let args = match cursor.peek(0, interner)? { - Some(next) if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) => { - Arguments::new(self.allow_yield, self.allow_await) - .parse(cursor, interner)? - } - _ => Box::new([]), + ast::Expression::from(New::from(call_node)) }; - let call_node = Call::new(lhs, args); - - ast::Expression::from(New::from(call_node)) + lhs_new_target } TokenKind::Keyword((Keyword::Super, _)) => { cursor.advance(interner); diff --git a/boa_parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs b/boa_parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs index 4bf8afbcde..68b5d97521 100644 --- a/boa_parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs +++ b/boa_parser/src/parser/statement/declaration/hoistable/class_decl/tests.rs @@ -1,9 +1,14 @@ use crate::parser::tests::check_script_parser; use boa_ast::{ - expression::literal::Literal, + declaration::{LexicalDeclaration, Variable, VariableList}, + expression::{ + access::{PropertyAccess, SimplePropertyAccess}, + literal::Literal, + Call, Identifier, + }, function::{Class, ClassElement, FormalParameterList, Function}, property::{MethodDefinition, PropertyName}, - Declaration, StatementList, + Declaration, Expression, Statement, StatementList, StatementListItem, }; use boa_interner::Interner; use boa_macros::utf16; @@ -90,3 +95,76 @@ fn check_async_field() { interner, ); } + +#[test] +fn check_new_target_with_property_access() { + let interner = &mut Interner::default(); + + let new_target = Expression::PropertyAccess( + SimplePropertyAccess::new( + Expression::NewTarget, + interner.get_or_intern_static("name", utf16!("name")), + ) + .into(), + ); + + let console = Expression::Call(Call::new( + PropertyAccess::Simple(SimplePropertyAccess::new( + Identifier::from(interner.get_or_intern_static("console", utf16!("console"))).into(), + interner.get_or_intern_static("log", utf16!("log")), + )) + .into(), + [new_target].into(), + )); + + let constructor = Function::new( + Some(interner.get_or_intern_static("A", utf16!("A")).into()), + FormalParameterList::default(), + StatementList::new([Statement::Expression(console).into()], false), + ); + + let class = Class::new( + Some(interner.get("A").unwrap().into()), + None, + Some(constructor), + Box::default(), + true, + ); + + let instantiation = Expression::New( + Call::new( + Identifier::from(interner.get("A").unwrap()).into(), + Box::default(), + ) + .into(), + ); + + let const_decl = LexicalDeclaration::Const( + VariableList::new( + [Variable::from_identifier( + interner.get_or_intern_static("a", utf16!("a")).into(), + Some(instantiation), + )] + .into(), + ) + .unwrap(), + ); + + let script = [ + StatementListItem::Declaration(class.into()), + StatementListItem::Declaration(const_decl.into()), + ]; + + check_script_parser( + r#" + class A { + constructor() { + console.log(new.target.name); + } + } + const a = new A(); + "#, + script, + interner, + ); +}