diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index e95bbd729f..7ee43f05c9 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -1519,3 +1519,13 @@ fn test_strict_mode_dup_func_parameters() { assert!(string.starts_with("Uncaught \"SyntaxError\": ")); } + +#[test] +fn test_empty_statement() { + let src = r#" + ;;;let a = 10;; + if(a) ; + a + "#; + assert_eq!(&exec(src), "10"); +} diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 06119dc9fd..01daf908eb 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -196,6 +196,18 @@ pub enum Node { /// A 'while {...}' node. [More information](./iteration/struct.WhileLoop.html). WhileLoop(WhileLoop), + + /// A empty node. + /// + /// Empty statement do nothing, just return undefined. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-EmptyStatement + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/Empty + Empty, } impl Display for Node { @@ -274,6 +286,7 @@ impl Node { Self::AsyncFunctionDecl(ref decl) => decl.display(f, indentation), Self::AsyncFunctionExpr(ref expr) => expr.display(f, indentation), Self::AwaitExpr(ref expr) => expr.display(f, indentation), + Self::Empty => write!(f, ";"), } } } @@ -338,6 +351,7 @@ impl Executable for Node { Node::Try(ref try_node) => try_node.run(context), Node::Break(ref break_node) => break_node.run(context), Node::Continue(ref continue_node) => continue_node.run(context), + Node::Empty => Ok(Value::Undefined), } } } diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index e284b354e3..2489ac58fb 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -178,6 +178,11 @@ where .parse(cursor) .map(Node::from) } + TokenKind::Punctuator(Punctuator::Semicolon) => { + // parse the EmptyStatement + cursor.next().expect("semicolon disappeared"); + Ok(Node::Empty) + } TokenKind::Identifier(_) => { // Labelled Statement check cursor.set_goal(InputElement::Div); diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs index e9eb353a04..9ae73be897 100644 --- a/boa/src/syntax/parser/tests.rs +++ b/boa/src/syntax/parser/tests.rs @@ -4,8 +4,8 @@ use super::Parser; use crate::syntax::ast::{ node::{ field::GetConstField, ArrowFunctionDecl, Assign, BinOp, Call, FormalParameter, - FunctionDecl, Identifier, LetDecl, LetDeclList, New, Node, Return, StatementList, UnaryOp, - VarDecl, VarDeclList, + FunctionDecl, Identifier, If, LetDecl, LetDeclList, New, Node, Return, StatementList, + UnaryOp, VarDecl, VarDeclList, }, op::{self, CompOp, LogOp, NumOp}, Const, @@ -209,7 +209,7 @@ fn assignment_line_terminator() { let s = r#" let a = 3; - a = + a = 5; "#; @@ -232,7 +232,7 @@ fn assignment_multiline_terminator() { let a = 3; - a = + a = 5; @@ -292,3 +292,22 @@ fn spread_in_arrow_function() { ], ); } + +#[test] +fn empty_statement() { + check_parser( + r" + ;;var a = 10; + if(a) ; + ", + vec![ + Node::Empty, + VarDeclList::from(vec![VarDecl::new("a", Node::from(Const::from(10)))]).into(), + Node::If(If::new::<_, _, Node, _>( + Identifier::from("a"), + Node::Empty, + None, + )), + ], + ); +}