diff --git a/boa_engine/src/syntax/ast/node/declaration/class_decl/tests.rs b/boa_engine/src/syntax/ast/node/declaration/class_decl/tests.rs index e9ff4dd7d19..5b5eb5af9e7 100644 --- a/boa_engine/src/syntax/ast/node/declaration/class_decl/tests.rs +++ b/boa_engine/src/syntax/ast/node/declaration/class_decl/tests.rs @@ -44,6 +44,8 @@ fn class_declaration_elements() { } set e(value) {} get e() {} + set(a, b) {} + get(a, b) {} }; "#, ); diff --git a/boa_engine/src/syntax/parser/expression/identifiers.rs b/boa_engine/src/syntax/parser/expression/identifiers.rs index 24d4c261cc5..d4981d89c1d 100644 --- a/boa_engine/src/syntax/parser/expression/identifiers.rs +++ b/boa_engine/src/syntax/parser/expression/identifiers.rs @@ -111,6 +111,8 @@ where TokenKind::Keyword((Keyword::Await, _)) if !self.allow_await.0 => { Ok(Identifier::new(Sym::AWAIT)) } + TokenKind::Keyword((Keyword::Async, _)) => Ok(Identifier::new(Sym::ASYNC)), + TokenKind::Keyword((Keyword::Of, _)) => Ok(Identifier::new(Sym::OF)), _ => Err(ParseError::unexpected( token.to_string(interner), token.span(), @@ -217,6 +219,8 @@ where )) } TokenKind::Keyword((Keyword::Await, _)) if !self.allow_await.0 => Ok(Sym::AWAIT), + TokenKind::Keyword((Keyword::Async, _)) => Ok(Sym::ASYNC), + TokenKind::Keyword((Keyword::Of, _)) => Ok(Sym::OF), _ => Err(ParseError::expected( ["identifier".to_owned()], next_token.to_string(interner), diff --git a/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs index 3769e68589c..7c3a3d36f2c 100644 --- a/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs @@ -67,9 +67,11 @@ where .ok_or(ParseError::AbruptEnd)? .kind() { - TokenKind::Identifier(_) | TokenKind::Keyword((Keyword::Yield | Keyword::Await, _)) => { - Some(BindingIdentifier::new(false, false).parse(cursor, interner)?) - } + TokenKind::Identifier(_) + | TokenKind::Keyword(( + Keyword::Yield | Keyword::Await | Keyword::Async | Keyword::Of, + _, + )) => Some(BindingIdentifier::new(false, false).parse(cursor, interner)?), _ => self.name, }; diff --git a/boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs b/boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs index 4343251eb4a..c535cbbfbac 100644 --- a/boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs +++ b/boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs @@ -88,3 +88,60 @@ fn check_nested_function_expression() { interner, ); } + +#[test] +fn check_function_non_reserved_keyword() { + let genast = |keyword, interner: &mut Interner| { + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + interner.get_or_intern_static("add"), + Some( + FunctionExpr::new::<_, _, StatementList>( + Some(interner.get_or_intern_static(keyword)), + FormalParameterList::default(), + vec![Return::new::<_, _, Option>(Const::from(1), None).into()].into(), + ) + .into(), + ), + )] + .into(), + ) + .into()] + }; + + let mut interner = Interner::default(); + let ast = genast("as", &mut interner); + check_parser("const add = function as() { return 1; };", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("async", &mut interner); + check_parser("const add = function async() { return 1; };", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("from", &mut interner); + check_parser("const add = function from() { return 1; };", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("get", &mut interner); + check_parser("const add = function get() { return 1; };", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("meta", &mut interner); + check_parser("const add = function meta() { return 1; };", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("of", &mut interner); + check_parser("const add = function of() { return 1; };", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("set", &mut interner); + check_parser("const add = function set() { return 1; };", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("target", &mut interner); + check_parser( + "const add = function target() { return 1; };", + ast, + interner, + ); +} diff --git a/boa_engine/src/syntax/parser/expression/primary/mod.rs b/boa_engine/src/syntax/parser/expression/primary/mod.rs index 81483627f2e..99af2f22bf5 100644 --- a/boa_engine/src/syntax/parser/expression/primary/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/mod.rs @@ -98,11 +98,12 @@ where // TODO: tok currently consumes the token instead of peeking, so the token // isn't passed and consumed by parsers according to spec (EX: GeneratorExpression) let tok = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + let tok_position = tok.span().start(); match tok.kind() { - TokenKind::Keyword((Keyword::This | Keyword::Async, true)) => Err(ParseError::general( + TokenKind::Keyword((Keyword::This, true)) => Err(ParseError::general( "Keyword must not contain escaped characters", - tok.span().start(), + tok_position, )), TokenKind::Keyword((Keyword::This, false)) => { cursor.next(interner).expect("token disappeared"); @@ -126,17 +127,31 @@ where ClassExpression::new(self.name, self.allow_yield, self.allow_await) .parse(cursor, interner) } - TokenKind::Keyword((Keyword::Async, false)) => { - cursor.next(interner).expect("token disappeared"); - let mul_peek = cursor.peek(1, interner)?.ok_or(ParseError::AbruptEnd)?; - if mul_peek.kind() == &TokenKind::Punctuator(Punctuator::Mul) { - AsyncGeneratorExpression::new(self.name) - .parse(cursor, interner) - .map(Node::from) - } else { - AsyncFunctionExpression::new(self.name, self.allow_yield) + TokenKind::Keyword((Keyword::Async, contain_escaped_char)) => { + let contain_escaped_char = *contain_escaped_char; + match cursor.peek(1, interner)?.map(Token::kind) { + Some(TokenKind::Keyword((Keyword::Function, _))) if contain_escaped_char => { + Err(ParseError::general( + "Keyword must not contain escaped characters", + tok_position, + )) + } + Some(TokenKind::Keyword((Keyword::Function, _))) => { + cursor.next(interner).expect("token disappeared"); + match cursor.peek(1, interner)?.map(Token::kind) { + Some(TokenKind::Punctuator(Punctuator::Mul)) => { + AsyncGeneratorExpression::new(self.name) + .parse(cursor, interner) + .map(Node::from) + } + _ => AsyncFunctionExpression::new(self.name, self.allow_yield) + .parse(cursor, interner) + .map(Node::from), + } + } + _ => IdentifierReference::new(self.allow_yield, self.allow_await) .parse(cursor, interner) - .map(Node::from) + .map(Node::from), } } TokenKind::Punctuator(Punctuator::OpenParen) => { @@ -174,11 +189,12 @@ where Ok(Const::Null.into()) } TokenKind::Identifier(_) - | TokenKind::Keyword((Keyword::Let | Keyword::Yield | Keyword::Await, _)) => { - IdentifierReference::new(self.allow_yield, self.allow_await) - .parse(cursor, interner) - .map(Node::from) - } + | TokenKind::Keyword(( + Keyword::Let | Keyword::Yield | Keyword::Await | Keyword::Of, + _, + )) => IdentifierReference::new(self.allow_yield, self.allow_await) + .parse(cursor, interner) + .map(Node::from), TokenKind::StringLiteral(lit) => { let node = Const::from(*lit).into(); cursor.next(interner).expect("token disappeared"); diff --git a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs index 036ed849ae0..a53318f18e3 100644 --- a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -166,15 +166,22 @@ where } //Async [AsyncMethod, AsyncGeneratorMethod] object methods + let is_keyword = !matches!( + cursor + .peek(1, interner)? + .ok_or(ParseError::AbruptEnd)? + .kind(), + TokenKind::Punctuator(Punctuator::OpenParen | Punctuator::Colon) + ); let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; match token.kind() { - TokenKind::Keyword((Keyword::Async, true)) => { + TokenKind::Keyword((Keyword::Async, true)) if is_keyword => { return Err(ParseError::general( "Keyword must not contain escaped characters", token.span().start(), )); } - TokenKind::Keyword((Keyword::Async, false)) => { + TokenKind::Keyword((Keyword::Async, false)) if is_keyword => { cursor.next(interner)?.expect("token disappeared"); cursor.peek_expect_no_lineterminator(0, "Async object methods", interner)?; diff --git a/boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs b/boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs index 7fb129e5ed3..592d21df628 100644 --- a/boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs @@ -3,7 +3,7 @@ use crate::syntax::{ node::{ object::{MethodDefinition, PropertyDefinition}, AsyncFunctionExpr, AsyncGeneratorExpr, Declaration, DeclarationList, FormalParameter, - FormalParameterList, FormalParameterListFlags, FunctionExpr, Identifier, Object, + FormalParameterList, FormalParameterListFlags, FunctionExpr, Identifier, Node, Object, }, Const, }, @@ -447,3 +447,59 @@ fn check_async_gen_method_lineterminator() { ", ); } + +#[test] +fn check_async_ordinary_method() { + let mut interner = Interner::default(); + + let object_properties = vec![PropertyDefinition::method_definition( + MethodDefinition::Ordinary(FunctionExpr::new( + None, + FormalParameterList::default(), + vec![], + )), + Node::Const(Const::from(interner.get_or_intern_static("async"))), + )]; + + check_parser( + "const x = { + async() {} + }; + ", + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + interner.get_or_intern_static("x"), + Some(Object::from(object_properties).into()), + )] + .into(), + ) + .into()], + interner, + ); +} + +#[test] +fn check_async_property() { + let mut interner = Interner::default(); + + let object_properties = vec![PropertyDefinition::property( + Node::Const(Const::from(interner.get_or_intern_static("async"))), + Const::from(true), + )]; + + check_parser( + "const x = { + async: true + }; + ", + vec![DeclarationList::Const( + vec![Declaration::new_with_identifier( + interner.get_or_intern_static("x"), + Some(Object::from(object_properties).into()), + )] + .into(), + ) + .into()], + interner, + ); +} diff --git a/boa_engine/src/syntax/parser/expression/tests.rs b/boa_engine/src/syntax/parser/expression/tests.rs index b990e1acec5..6350479aae0 100644 --- a/boa_engine/src/syntax/parser/expression/tests.rs +++ b/boa_engine/src/syntax/parser/expression/tests.rs @@ -590,3 +590,31 @@ fn check_logical_expressions() { check_invalid("a ?? b || c"); check_invalid("a || b ?? c"); } + +#[track_caller] +fn check_non_reserved_identifier(keyword: &'static str) { + let mut interner = Interner::default(); + check_parser( + format!("({})", keyword).as_str(), + vec![Identifier::new(interner.get_or_intern_static(keyword)).into()], + interner, + ); +} + +#[test] +fn check_non_reserved_identifiers() { + // https://tc39.es/ecma262/#sec-keywords-and-reserved-words + // Those that are always allowed as identifiers, but also appear as + // keywords within certain syntactic productions, at places where + // Identifier is not allowed: as, async, from, get, meta, of, set, + // and target. + + check_non_reserved_identifier("as"); + check_non_reserved_identifier("async"); + check_non_reserved_identifier("from"); + check_non_reserved_identifier("get"); + check_non_reserved_identifier("meta"); + check_non_reserved_identifier("of"); + check_non_reserved_identifier("set"); + check_non_reserved_identifier("target"); +} diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs index f61dbe34e7a..9840ef6c450 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod tests; + use crate::syntax::{ ast::{ node::{ @@ -15,10 +18,7 @@ use crate::syntax::{ AssignmentExpression, AsyncGeneratorMethod, AsyncMethod, BindingIdentifier, GeneratorMethod, LeftHandSideExpression, PropertyName, }, - function::{ - FormalParameter, FormalParameters, FunctionBody, UniqueFormalParameters, - FUNCTION_BREAK_TOKENS, - }, + function::{FormalParameters, FunctionBody, UniqueFormalParameters, FUNCTION_BREAK_TOKENS}, statement::StatementList, AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser, }, @@ -571,11 +571,9 @@ where | TokenKind::NullLiteral | TokenKind::PrivateIdentifier(_) | TokenKind::Punctuator( - Punctuator::OpenBracket - | Punctuator::Mul - | Punctuator::OpenBlock - | Punctuator::Semicolon, + Punctuator::OpenBracket | Punctuator::Mul | Punctuator::OpenBlock, ) => { + // this "static" is a keyword. cursor.next(interner).expect("token disappeared"); true } @@ -585,6 +583,19 @@ where _ => false, }; + let is_keyword = !matches!( + cursor + .peek(1, interner)? + .ok_or(ParseError::AbruptEnd)? + .kind(), + TokenKind::Punctuator( + Punctuator::Assign + | Punctuator::CloseBlock + | Punctuator::OpenParen + | Punctuator::Semicolon + ) + ); + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let position = token.span().start(); let element = match token.kind() { @@ -613,10 +624,6 @@ where return Ok((Some(FunctionExpr::new(self.name, parameters, body)), None)); } - TokenKind::Punctuator(Punctuator::Semicolon) if r#static => { - cursor.next(interner).expect("token disappeared"); - ClassElementNode::FieldDefinition(Sym::STATIC.into(), None) - } TokenKind::Punctuator(Punctuator::OpenBlock) if r#static => { cursor.next(interner).expect("token disappeared"); let statement_list = if cursor @@ -722,13 +729,13 @@ where } } } - TokenKind::Keyword((Keyword::Async, true)) => { + TokenKind::Keyword((Keyword::Async, true)) if is_keyword => { return Err(ParseError::general( "Keyword must not contain escaped characters", token.span().start(), )); } - TokenKind::Keyword((Keyword::Async, false)) => { + TokenKind::Keyword((Keyword::Async, false)) if is_keyword => { cursor.next(interner).expect("token disappeared"); cursor.peek_expect_no_lineterminator(0, "Async object methods", interner)?; let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; @@ -806,64 +813,10 @@ where } } } - TokenKind::Identifier(Sym::GET) => { + TokenKind::Identifier(Sym::GET) if is_keyword => { cursor.next(interner).expect("token disappeared"); let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; match token.kind() { - TokenKind::Punctuator(Punctuator::Assign) => { - cursor.next(interner).expect("token disappeared"); - let strict = cursor.strict_mode(); - cursor.set_strict_mode(true); - let rhs = AssignmentExpression::new( - Sym::GET, - true, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner)?; - cursor.expect_semicolon("expected semicolon", interner)?; - cursor.set_strict_mode(strict); - if r#static { - ClassElementNode::StaticFieldDefinition(Literal(Sym::GET), Some(rhs)) - } else { - ClassElementNode::FieldDefinition(Literal(Sym::GET), Some(rhs)) - } - } - TokenKind::Punctuator(Punctuator::OpenParen) => { - let strict = cursor.strict_mode(); - cursor.set_strict_mode(true); - let params = - UniqueFormalParameters::new(false, false).parse(cursor, interner)?; - cursor.expect( - TokenKind::Punctuator(Punctuator::OpenBlock), - "method definition", - interner, - )?; - let body = FunctionBody::new(false, false).parse(cursor, interner)?; - let token = cursor.expect( - TokenKind::Punctuator(Punctuator::CloseBlock), - "method definition", - interner, - )?; - - // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true - // and IsSimpleParameterList of UniqueFormalParameters is false. - if body.strict() && !params.is_simple() { - return Err(ParseError::lex(LexError::Syntax( - "Illegal 'use strict' directive in function with non-simple parameter list" - .into(), - token.span().start(), - ))); - } - cursor.set_strict_mode(strict); - let method = - MethodDefinition::Ordinary(FunctionExpr::new(None, params, body)); - if r#static { - ClassElementNode::StaticMethodDefinition(Literal(Sym::GET), method) - } else { - ClassElementNode::MethodDefinition(Literal(Sym::GET), method) - } - } TokenKind::PrivateIdentifier(Sym::CONSTRUCTOR) => { return Err(ParseError::general( "class constructor may not be a private method", @@ -975,72 +928,10 @@ where } } } - TokenKind::Identifier(Sym::SET) => { + TokenKind::Identifier(Sym::SET) if is_keyword => { cursor.next(interner).expect("token disappeared"); let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; match token.kind() { - TokenKind::Punctuator(Punctuator::Assign) => { - cursor.next(interner).expect("token disappeared"); - let strict = cursor.strict_mode(); - cursor.set_strict_mode(true); - let rhs = AssignmentExpression::new( - Sym::SET, - true, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner)?; - cursor.expect_semicolon("expected semicolon", interner)?; - cursor.set_strict_mode(strict); - if r#static { - ClassElementNode::StaticFieldDefinition(Literal(Sym::SET), Some(rhs)) - } else { - ClassElementNode::FieldDefinition(Literal(Sym::SET), Some(rhs)) - } - } - TokenKind::Punctuator(Punctuator::OpenParen) => { - cursor.next(interner).expect("token disappeared"); - let strict = cursor.strict_mode(); - cursor.set_strict_mode(true); - let parameters: FormalParameterList = FormalParameter::new(false, false) - .parse(cursor, interner)? - .into(); - cursor.expect( - TokenKind::Punctuator(Punctuator::CloseParen), - "class setter method definition", - interner, - )?; - - cursor.expect( - TokenKind::Punctuator(Punctuator::OpenBlock), - "method definition", - interner, - )?; - let body = FunctionBody::new(false, false).parse(cursor, interner)?; - let token = cursor.expect( - TokenKind::Punctuator(Punctuator::CloseBlock), - "method definition", - interner, - )?; - - // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true - // and IsSimpleParameterList of UniqueFormalParameters is false. - if body.strict() && !parameters.is_simple() { - return Err(ParseError::lex(LexError::Syntax( - "Illegal 'use strict' directive in function with non-simple parameter list" - .into(), - token.span().start(), - ))); - } - cursor.set_strict_mode(strict); - let method = - MethodDefinition::Ordinary(FunctionExpr::new(None, parameters, body)); - if r#static { - ClassElementNode::StaticMethodDefinition(Literal(Sym::SET), method) - } else { - ClassElementNode::MethodDefinition(Literal(Sym::SET), method) - } - } TokenKind::PrivateIdentifier(Sym::CONSTRUCTOR) => { return Err(ParseError::general( "class constructor may not be a private method", diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/tests.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/tests.rs new file mode 100644 index 00000000000..660e9245c9b --- /dev/null +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/tests.rs @@ -0,0 +1,95 @@ +use crate::syntax::{ + ast::{ + node::{ + declaration::class_decl::ClassElement as ClassElementNode, + object::{MethodDefinition, PropertyName}, + Class, FormalParameterList, FunctionExpr, Node, + }, + Const, + }, + parser::tests::check_parser, +}; +use boa_interner::Interner; + +#[test] +fn check_async_ordinary_method() { + let mut interner = Interner::default(); + + let elements = vec![ClassElementNode::MethodDefinition( + PropertyName::Computed(Node::Const(Const::from( + interner.get_or_intern_static("async"), + ))), + MethodDefinition::Ordinary(FunctionExpr::new( + None, + FormalParameterList::default(), + vec![], + )), + )]; + + check_parser( + "class A { + async() { } + } + ", + [Node::ClassDecl(Class::new( + interner.get_or_intern_static("A"), + None, + None, + elements, + ))], + interner, + ); +} + +#[test] +fn check_async_field_initialization() { + let mut interner = Interner::default(); + + let elements = vec![ClassElementNode::FieldDefinition( + PropertyName::Computed(Node::Const(Const::from( + interner.get_or_intern_static("async"), + ))), + Some(Node::Const(Const::from(1))), + )]; + + check_parser( + "class A { + async + = 1 + } + ", + [Node::ClassDecl(Class::new( + interner.get_or_intern_static("A"), + None, + None, + elements, + ))], + interner, + ); +} + +#[test] +fn check_async_field() { + let mut interner = Interner::default(); + + let elements = vec![ClassElementNode::FieldDefinition( + PropertyName::Computed(Node::Const(Const::from( + interner.get_or_intern_static("async"), + ))), + None, + )]; + + check_parser( + "class A { + async + } + ", + [Node::ClassDecl(Class::new( + interner.get_or_intern_static("A"), + None, + None, + elements, + ))], + interner, + ); +} diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs index 4225b363d4e..ff5b5e2a7a4 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs @@ -23,27 +23,52 @@ fn function_declaration() { /// Function declaration parsing with keywords. #[test] fn function_declaration_keywords() { - let mut interner = Interner::default(); - check_parser( - "function yield() {}", + let genast = |keyword, interner: &mut Interner| { vec![FunctionDecl::new( - interner.get_or_intern_static("yield"), + interner.get_or_intern_static(keyword), FormalParameterList::default(), vec![], ) - .into()], - interner, - ); + .into()] + }; let mut interner = Interner::default(); - check_parser( - "function await() {}", - vec![FunctionDecl::new( - interner.get_or_intern_static("await"), - FormalParameterList::default(), - vec![], - ) - .into()], - interner, - ); + let ast = genast("yield", &mut interner); + check_parser("function yield() {}", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("await", &mut interner); + check_parser("function await() {}", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("as", &mut interner); + check_parser("function as() {}", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("async", &mut interner); + check_parser("function async() {}", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("from", &mut interner); + check_parser("function from() {}", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("get", &mut interner); + check_parser("function get() {}", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("meta", &mut interner); + check_parser("function meta() {}", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("of", &mut interner); + check_parser("function of() {}", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("set", &mut interner); + check_parser("function set() {}", ast, interner); + + let mut interner = Interner::default(); + let ast = genast("target", &mut interner); + check_parser("function target() {}", ast, interner); } diff --git a/boa_engine/src/syntax/parser/statement/iteration/for_statement.rs b/boa_engine/src/syntax/parser/statement/iteration/for_statement.rs index 2cb40e972a1..6b702274dab 100644 --- a/boa_engine/src/syntax/parser/statement/iteration/for_statement.rs +++ b/boa_engine/src/syntax/parser/statement/iteration/for_statement.rs @@ -101,6 +101,24 @@ where Declaration::new(self.allow_yield, self.allow_await, false) .parse(cursor, interner)?, ), + TokenKind::Keyword((Keyword::Async, false)) => { + match cursor + .peek(1, interner)? + .ok_or(ParseError::AbruptEnd)? + .kind() + { + TokenKind::Keyword((Keyword::Of, _)) => { + return Err(ParseError::lex(LexError::Syntax( + "invalid left-hand side expression 'async' of a for-of loop".into(), + init_position, + ))); + } + _ => Some( + Expression::new(None, false, self.allow_yield, self.allow_await) + .parse(cursor, interner)?, + ), + } + } TokenKind::Punctuator(Punctuator::Semicolon) => None, _ => Some( Expression::new(None, false, self.allow_yield, self.allow_await) diff --git a/boa_engine/src/syntax/parser/statement/mod.rs b/boa_engine/src/syntax/parser/statement/mod.rs index c29cfd3fd85..b35bfe800bc 100644 --- a/boa_engine/src/syntax/parser/statement/mod.rs +++ b/boa_engine/src/syntax/parser/statement/mod.rs @@ -359,11 +359,21 @@ where match *tok.kind() { TokenKind::Keyword(( - Keyword::Function | Keyword::Async | Keyword::Class | Keyword::Const | Keyword::Let, + Keyword::Function | Keyword::Class | Keyword::Const | Keyword::Let, _, )) => { Declaration::new(self.allow_yield, self.allow_await, true).parse(cursor, interner) } + TokenKind::Keyword((Keyword::Async, _)) => { + match cursor.peek(1, interner)?.map(Token::kind) { + Some(TokenKind::Keyword((Keyword::Function, _))) => { + Declaration::new(self.allow_yield, self.allow_await, true) + .parse(cursor, interner) + } + _ => Statement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor, interner), + } + } _ => Statement::new(self.allow_yield, self.allow_await, self.allow_return) .parse(cursor, interner), } diff --git a/boa_interner/src/sym.rs b/boa_interner/src/sym.rs index 368c4c781aa..c8150e3f4da 100644 --- a/boa_interner/src/sym.rs +++ b/boa_interner/src/sym.rs @@ -91,6 +91,12 @@ impl Sym { /// Symbol for the `"false"` string. pub const FALSE: Self = unsafe { Self::new_unchecked(25) }; + /// Symbol for the `"async"` string. + pub const ASYNC: Self = unsafe { Self::new_unchecked(26) }; + + /// Symbol for the `"of"` string. + pub const OF: Self = unsafe { Self::new_unchecked(27) }; + /// Creates a new [`Sym`] from the provided `value`, or returns `None` if `index` is zero. #[inline] pub(super) fn new(value: usize) -> Option { @@ -153,6 +159,8 @@ pub(super) static COMMON_STRINGS: phf::OrderedSet<&'static str> = { "anonymous", "true", "false", + "async", + "of", }; // A `COMMON_STRINGS` of size `usize::MAX` would cause an overflow on our `Interner` sa::const_assert!(COMMON_STRINGS.len() < usize::MAX);