diff --git a/boa/src/syntax/ast/node/array/mod.rs b/boa/src/syntax/ast/node/array/mod.rs index 8bb7f5ac32..d9ce793584 100644 --- a/boa/src/syntax/ast/node/array/mod.rs +++ b/boa/src/syntax/ast/node/array/mod.rs @@ -12,6 +12,9 @@ use std::fmt; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests; + /// An array is an ordered collection of data (either primitive or object depending upon the /// language). /// diff --git a/boa/src/syntax/ast/node/array/tests.rs b/boa/src/syntax/ast/node/array/tests.rs new file mode 100644 index 0000000000..bc14e7b6f8 --- /dev/null +++ b/boa/src/syntax/ast/node/array/tests.rs @@ -0,0 +1,9 @@ +#[test] +fn fmt() { + super::super::test_formatting( + r#" + let a = [1, 2, 3, "words", "more words"]; + let b = []; + "#, + ); +} diff --git a/boa/src/syntax/ast/node/await_expr/mod.rs b/boa/src/syntax/ast/node/await_expr/mod.rs index f527dd0f06..ab1291f18e 100644 --- a/boa/src/syntax/ast/node/await_expr/mod.rs +++ b/boa/src/syntax/ast/node/await_expr/mod.rs @@ -8,6 +8,9 @@ use std::fmt; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests; + /// An await expression is used within an async function to pause execution and wait for a /// promise to resolve. /// @@ -31,14 +34,6 @@ impl Executable for AwaitExpr { } } -impl AwaitExpr { - /// Implements the display formatting with indentation. - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { - writeln!(f, "await ")?; - self.expr.display(f, indentation) - } -} - impl From for AwaitExpr where T: Into>, @@ -50,7 +45,8 @@ where impl fmt::Display for AwaitExpr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.display(f, 0) + write!(f, "await ")?; + self.expr.display(f, 0) } } diff --git a/boa/src/syntax/ast/node/await_expr/tests.rs b/boa/src/syntax/ast/node/await_expr/tests.rs new file mode 100644 index 0000000000..b1b30504ae --- /dev/null +++ b/boa/src/syntax/ast/node/await_expr/tests.rs @@ -0,0 +1,9 @@ +#[test] +fn fmt() { + // TODO: `let a = await fn()` is invalid syntax as of writing. It should be tested here once implemented. + super::super::test_formatting( + r#" + await function_call(); + "#, + ); +} diff --git a/boa/src/syntax/ast/node/block/mod.rs b/boa/src/syntax/ast/node/block/mod.rs index 25abdc3369..0dcd654389 100644 --- a/boa/src/syntax/ast/node/block/mod.rs +++ b/boa/src/syntax/ast/node/block/mod.rs @@ -13,6 +13,9 @@ use std::fmt; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests; + /// A `block` statement (or compound statement in other languages) is used to group zero or /// more statements. /// diff --git a/boa/src/syntax/ast/node/block/tests.rs b/boa/src/syntax/ast/node/block/tests.rs new file mode 100644 index 0000000000..b81cba9b84 --- /dev/null +++ b/boa/src/syntax/ast/node/block/tests.rs @@ -0,0 +1,22 @@ +#[test] +fn fmt() { + super::super::test_formatting( + r#" + { + let a = function_call(); + console.log("hello"); + } + another_statement(); + "#, + ); + // TODO: Once block labels are implemtned, this should be tested: + // super::super::test_formatting( + // r#" + // block_name: { + // let a = function_call(); + // console.log("hello"); + // } + // another_statement(); + // "#, + // ); +} diff --git a/boa/src/syntax/ast/node/break_node/tests.rs b/boa/src/syntax/ast/node/break_node/tests.rs index 194c22c2b8..20487ee399 100644 --- a/boa/src/syntax/ast/node/break_node/tests.rs +++ b/boa/src/syntax/ast/node/break_node/tests.rs @@ -17,3 +17,34 @@ fn check_post_state() { &InterpreterState::Break(Some("label".into())) ); } + +#[test] +fn fmt() { + // Blocks do not store their label, so we cannot test with + // the outer block having a label. + // + // TODO: Once block labels are implemented, this test should + // include them: + // + // ``` + // outer: { + // while (true) { + // break outer; + // } + // skipped_call(); + // } + // ``` + super::super::test_formatting( + r#" + { + while (true) { + break outer; + } + skipped_call(); + } + while (true) { + break; + } + "#, + ); +} diff --git a/boa/src/syntax/ast/node/call/mod.rs b/boa/src/syntax/ast/node/call/mod.rs index d6f1b6eba3..d065d76a84 100644 --- a/boa/src/syntax/ast/node/call/mod.rs +++ b/boa/src/syntax/ast/node/call/mod.rs @@ -12,6 +12,9 @@ use std::fmt; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests; + /// Calling the function actually performs the specified actions with the indicated parameters. /// /// Defining a function does not execute it. Defining it simply names the function and diff --git a/boa/src/syntax/ast/node/call/tests.rs b/boa/src/syntax/ast/node/call/tests.rs new file mode 100644 index 0000000000..e3dd74d85a --- /dev/null +++ b/boa/src/syntax/ast/node/call/tests.rs @@ -0,0 +1,10 @@ +#[test] +fn fmt() { + super::super::test_formatting( + r#" + call_1(1, 2, 3); + call_2("argument here"); + call_3(); + "#, + ); +} diff --git a/boa/src/syntax/ast/node/conditional/mod.rs b/boa/src/syntax/ast/node/conditional/mod.rs index fdfe8ea81f..68b1d424db 100644 --- a/boa/src/syntax/ast/node/conditional/mod.rs +++ b/boa/src/syntax/ast/node/conditional/mod.rs @@ -4,3 +4,6 @@ pub mod conditional_op; pub mod if_node; pub use self::{conditional_op::ConditionalOp, if_node::If}; + +#[cfg(test)] +mod tests; diff --git a/boa/src/syntax/ast/node/conditional/tests.rs b/boa/src/syntax/ast/node/conditional/tests.rs new file mode 100644 index 0000000000..d190016464 --- /dev/null +++ b/boa/src/syntax/ast/node/conditional/tests.rs @@ -0,0 +1,13 @@ +#[test] +fn fmt() { + super::super::test_formatting( + r#" + let a = true ? 5 : 6; + if (false) { + a = 10; + } else { + a = 20; + } + "#, + ); +} diff --git a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs index 6646a9542d..43c57b482a 100644 --- a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs @@ -61,8 +61,13 @@ impl ArrowFunctionDecl { ) -> fmt::Result { write!(f, "(")?; join_nodes(f, &self.params)?; - f.write_str(") => ")?; - self.body.display(f, indentation) + if self.body().is_empty() { + f.write_str(") => {}") + } else { + f.write_str(") => {\n")?; + self.body.display(f, indentation + 1)?; + write!(f, "{}}}", " ".repeat(indentation)) + } } } diff --git a/boa/src/syntax/ast/node/declaration/async_function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/async_function_decl/mod.rs index 5078b5b294..8416843de5 100644 --- a/boa/src/syntax/ast/node/declaration/async_function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/async_function_decl/mod.rs @@ -64,19 +64,17 @@ impl AsyncFunctionDecl { indentation: usize, ) -> fmt::Result { match &self.name { - Some(name) => { - write!(f, "async function {}(", name)?; - } - None => { - write!(f, "async function (")?; - } + Some(name) => write!(f, "async function {}(", name)?, + None => write!(f, "async function (")?, } join_nodes(f, &self.parameters)?; - f.write_str(") {{")?; - - self.body.display(f, indentation + 1)?; - - writeln!(f, "}}") + if self.body().is_empty() { + f.write_str(") {}") + } else { + f.write_str(") {\n")?; + self.body.display(f, indentation + 1)?; + write!(f, "{}}}", " ".repeat(indentation)) + } } } diff --git a/boa/src/syntax/ast/node/declaration/async_function_expr/mod.rs b/boa/src/syntax/ast/node/declaration/async_function_expr/mod.rs index 056976f961..2bc9dd3ccc 100644 --- a/boa/src/syntax/ast/node/declaration/async_function_expr/mod.rs +++ b/boa/src/syntax/ast/node/declaration/async_function_expr/mod.rs @@ -64,17 +64,19 @@ impl AsyncFunctionExpr { f: &mut fmt::Formatter<'_>, indentation: usize, ) -> fmt::Result { - f.write_str("function")?; + f.write_str("async function")?; if let Some(ref name) = self.name { write!(f, " {}", name)?; } f.write_str("(")?; join_nodes(f, &self.parameters)?; - f.write_str(") {{")?; - - self.body.display(f, indentation + 1)?; - - writeln!(f, "}}") + if self.body().is_empty() { + f.write_str(") {}") + } else { + f.write_str(") {\n")?; + self.body.display(f, indentation + 1)?; + write!(f, "{}}}", " ".repeat(indentation)) + } } } diff --git a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs index e3b68bc767..88eb70baae 100644 --- a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs @@ -75,11 +75,13 @@ impl FunctionDecl { ) -> fmt::Result { write!(f, "function {}(", self.name)?; join_nodes(f, &self.parameters)?; - f.write_str(") {{")?; - - self.body.display(f, indentation + 1)?; - - writeln!(f, "}}") + if self.body().is_empty() { + f.write_str(") {}") + } else { + f.write_str(") {\n")?; + self.body.display(f, indentation + 1)?; + write!(f, "{}}}", " ".repeat(indentation)) + } } } diff --git a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs index 6f56d54cf6..e5c80c7d99 100644 --- a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs @@ -76,11 +76,24 @@ impl FunctionExpr { } f.write_str("(")?; join_nodes(f, &self.parameters)?; - f.write_str(") {{")?; - - self.body.display(f, indentation + 1)?; + f.write_str(") ")?; + self.display_block(f, indentation) + } - writeln!(f, "}}") + /// Displays the function's body. This includes the curly braces at the start and end. + /// This will not indent the first brace, but will indent the last brace. + pub(in crate::syntax::ast::node) fn display_block( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { + if self.body().is_empty() { + f.write_str("{}") + } else { + f.write_str("{\n")?; + self.body.display(f, indentation + 1)?; + write!(f, "{}}}", " ".repeat(indentation)) + } } } diff --git a/boa/src/syntax/ast/node/declaration/tests.rs b/boa/src/syntax/ast/node/declaration/tests.rs index a98ac86f52..7c6dec9a95 100644 --- a/boa/src/syntax/ast/node/declaration/tests.rs +++ b/boa/src/syntax/ast/node/declaration/tests.rs @@ -10,3 +10,32 @@ fn duplicate_function_name() { assert_eq!(&exec(scenario), "12"); } + +#[test] +fn fmt() { + super::super::test_formatting( + r#" + function func(a, b) { + console.log(a); + }; + function func_2(a, b) {}; + let arrow_func = (a, b) => { + console.log("in multi statement arrow"); + console.log(b); + }; + async function async_func(a, b) { + console.log(a); + }; + pass_async_func(async function(a, b) { + console.log("in async callback", a); + }); + pass_func(function(a, b) { + console.log("in callback", a); + }); + let arrow_func_2 = (a, b) => {}; + async function async_func_2(a, b) {}; + pass_async_func(async function(a, b) {}); + pass_func(function(a, b) {}); + "#, + ); +} diff --git a/boa/src/syntax/ast/node/field/mod.rs b/boa/src/syntax/ast/node/field/mod.rs index 8c52719e8f..54455ff5df 100644 --- a/boa/src/syntax/ast/node/field/mod.rs +++ b/boa/src/syntax/ast/node/field/mod.rs @@ -4,3 +4,6 @@ pub mod get_const_field; pub mod get_field; pub use self::{get_const_field::GetConstField, get_field::GetField}; + +#[cfg(test)] +mod tests; diff --git a/boa/src/syntax/ast/node/field/tests.rs b/boa/src/syntax/ast/node/field/tests.rs new file mode 100644 index 0000000000..3c4cb69ce9 --- /dev/null +++ b/boa/src/syntax/ast/node/field/tests.rs @@ -0,0 +1,10 @@ +#[test] +fn fmt() { + super::super::test_formatting( + r#" + a.field_name; + a[5]; + a["other_field_name"]; + "#, + ); +} diff --git a/boa/src/syntax/ast/node/iteration/continue_node/mod.rs b/boa/src/syntax/ast/node/iteration/continue_node/mod.rs index f42864339d..9322d48310 100644 --- a/boa/src/syntax/ast/node/iteration/continue_node/mod.rs +++ b/boa/src/syntax/ast/node/iteration/continue_node/mod.rs @@ -57,15 +57,11 @@ impl Executable for Continue { impl fmt::Display for Continue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "continue{}", - if let Some(label) = self.label() { - format!(" {}", label) - } else { - String::new() - } - ) + write!(f, "continue")?; + if let Some(label) = self.label() { + write!(f, " {}", label)?; + } + Ok(()) } } diff --git a/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs b/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs index a5d8fcd01e..7659cefe60 100644 --- a/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs @@ -64,9 +64,12 @@ impl DoWhileLoop { f: &mut fmt::Formatter<'_>, indentation: usize, ) -> fmt::Result { - write!(f, "do")?; + if let Some(ref label) = self.label { + write!(f, "{}: ", label)?; + } + write!(f, "do ")?; self.body().display(f, indentation)?; - write!(f, "while ({})", self.cond()) + write!(f, " while ({})", self.cond()) } } diff --git a/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs index 628c60bd89..b2b0c91f5d 100644 --- a/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs @@ -59,9 +59,11 @@ impl ForInLoop { } pub fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { - write!(f, "for ({} in {}) {{", self.variable, self.expr,)?; - self.body().display(f, indentation + 1)?; - f.write_str("}") + if let Some(ref label) = self.label { + write!(f, "{}: ", label)?; + } + write!(f, "for ({} in {}) ", self.variable, self.expr)?; + self.body().display(f, indentation) } } diff --git a/boa/src/syntax/ast/node/iteration/for_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_loop/mod.rs index f33c14e137..8d1dbd435d 100644 --- a/boa/src/syntax/ast/node/iteration/for_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_loop/mod.rs @@ -69,23 +69,23 @@ impl ForLoop { f: &mut fmt::Formatter<'_>, indentation: usize, ) -> fmt::Result { + if let Some(ref label) = self.label { + write!(f, "{}: ", label)?; + } f.write_str("for (")?; if let Some(init) = self.init() { fmt::Display::fmt(init, f)?; } - f.write_str(";")?; + f.write_str("; ")?; if let Some(condition) = self.condition() { fmt::Display::fmt(condition, f)?; } - f.write_str(";")?; + f.write_str("; ")?; if let Some(final_expr) = self.final_expr() { fmt::Display::fmt(final_expr, f)?; } - writeln!(f, ") {{")?; - - self.inner.body().display(f, indentation + 1)?; - - write!(f, "}}") + write!(f, ") ")?; + self.inner.body().display(f, indentation) } pub fn label(&self) -> Option<&str> { diff --git a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs index bbdc954726..48e56cb8ed 100644 --- a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs @@ -59,9 +59,11 @@ impl ForOfLoop { } pub fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { - write!(f, "for ({} of {}) {{", self.variable, self.iterable)?; - self.body().display(f, indentation + 1)?; - f.write_str("}") + if let Some(ref label) = self.label { + write!(f, "{}: ", label)?; + } + write!(f, "for ({} of {}) ", self.variable, self.iterable)?; + self.body().display(f, indentation) } } diff --git a/boa/src/syntax/ast/node/iteration/tests.rs b/boa/src/syntax/ast/node/iteration/tests.rs index 9dc9bb6154..36154b8bdd 100644 --- a/boa/src/syntax/ast/node/iteration/tests.rs +++ b/boa/src/syntax/ast/node/iteration/tests.rs @@ -502,3 +502,76 @@ fn for_in_continue_label() { "#; assert_eq!(&exec(scenario), "\"00\"") } + +#[test] +fn fmt() { + // Labeled and unlabeled for in loops + super::super::test_formatting( + r#" + var str = ""; + outer: for (let i in [1, 2]) { + for (let b in [2, 3, 4]) { + if (b === "1") { + continue outer; + } + str = str + b; + }; + str = str + i; + }; + str; + "#, + ); + // Labeled and unlabeled for loops + super::super::test_formatting( + r#" + var str = ""; + outer: for (let i = 0; i < 10; ++i) { + for (let j = 3; j < 6; ++j) { + if (j === "1") { + continue outer; + } + str = str + j; + }; + str = str + i; + }; + str; + "#, + ); + // Labeled and unlabeled for of loops + super::super::test_formatting( + r#" + for (i of [1, 2, 3]) { + if (false) { + break; + } + }; + label: for (i of [1, 2, 3]) { + if (false) { + break label; + } + }; + "#, + ); + // Labeled and unlabeled do while loops + super::super::test_formatting( + r#" + do { + break; + } while (true); + label: do { + break label; + } while (true); + "#, + ); + // Labeled and unlabeled while loops + super::super::test_formatting( + r#" + while (true) { + break; + } + label: while (true) { + break label; + } + "#, + ); +} diff --git a/boa/src/syntax/ast/node/iteration/while_loop/mod.rs b/boa/src/syntax/ast/node/iteration/while_loop/mod.rs index 4323a8b7b6..3c6afbbd97 100644 --- a/boa/src/syntax/ast/node/iteration/while_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/while_loop/mod.rs @@ -63,6 +63,9 @@ impl WhileLoop { f: &mut fmt::Formatter<'_>, indentation: usize, ) -> fmt::Result { + if let Some(ref label) = self.label { + write!(f, "{}: ", label)?; + } write!(f, "while ({}) ", self.cond())?; self.expr().display(f, indentation) } diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 6f38bb22ed..6545a5f013 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -239,14 +239,28 @@ impl Node { Self::This } - /// Implements the display formatting with indentation. + /// Displays the value of the node with the given indentation. For example, an indent + /// level of 2 would produce this: + /// + /// ```js + /// function hello() { + /// console.log("hello"); + /// } + /// hello(); + /// a = 2; + /// ``` fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { let indent = " ".repeat(indentation); match *self { Self::Block(_) => {} _ => write!(f, "{}", indent)?, } + self.display_no_indent(f, indentation) + } + /// Implements the display formatting with indentation. This will not prefix the value with + /// any indentation. If you want to prefix this with proper indents, use [`display`](Self::display). + fn display_no_indent(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { match *self { Self::Call(ref expr) => Display::fmt(expr, f), Self::Const(ref c) => write!(f, "{}", c), @@ -285,7 +299,7 @@ impl Node { Self::ConstDeclList(ref decl) => Display::fmt(decl, f), 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::AwaitExpr(ref expr) => Display::fmt(expr, f), Self::Empty => write!(f, ";"), } } @@ -588,3 +602,38 @@ pub enum MethodDefinitionKind { unsafe impl Trace for MethodDefinitionKind { empty_trace!(); } + +/// This parses the given source code, and then makes sure that +/// the resulting StatementList is formatted in the same manner +/// as the source code. This is expected to have a preceding +/// newline. +/// +/// This is a utility function for tests. It was made in case people +/// are using different indents in their source files. This fixes +/// any strings which may have been changed in a different indent +/// level. +#[cfg(test)] +fn test_formatting(source: &'static str) { + // Remove preceding newline. + let source = &source[1..]; + + // Find out how much the code is indented + let first_line = &source[..source.find('\n').unwrap()]; + let trimmed_first_line = first_line.trim(); + let characters_to_remove = first_line.len() - trimmed_first_line.len(); + + let scenario = source + .lines() + .map(|l| &l[characters_to_remove..]) // Remove preceding whitespace from each line + .collect::>() + .join("\n"); + let result = format!("{}", crate::parse(&scenario, false).unwrap()); + if scenario != result { + eprint!("========= Expected:\n{}", scenario); + eprint!("========= Got:\n{}", result); + // Might be helpful to find differing whitespace + eprintln!("========= Expected: {:?}", scenario); + eprintln!("========= Got: {:?}", result); + panic!("parsing test did not give the correct result (see above)"); + } +} diff --git a/boa/src/syntax/ast/node/new/mod.rs b/boa/src/syntax/ast/node/new/mod.rs index 37a0100551..59ce787ee7 100644 --- a/boa/src/syntax/ast/node/new/mod.rs +++ b/boa/src/syntax/ast/node/new/mod.rs @@ -11,6 +11,9 @@ use std::fmt; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests; + /// The `new` operator lets developers create an instance of a user-defined object type or of /// one of the built-in object types that has a constructor function. /// diff --git a/boa/src/syntax/ast/node/new/tests.rs b/boa/src/syntax/ast/node/new/tests.rs new file mode 100644 index 0000000000..bd9a233f7c --- /dev/null +++ b/boa/src/syntax/ast/node/new/tests.rs @@ -0,0 +1,9 @@ +#[test] +fn fmt() { + super::super::test_formatting( + r#" + function MyClass() {}; + let inst = new MyClass(); + "#, + ); +} diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index 8a4fcc3724..0bdb93147d 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -4,7 +4,7 @@ use crate::{ exec::Executable, gc::{Finalize, Trace}, property::{AccessorDescriptor, Attribute, DataDescriptor, PropertyDescriptor}, - syntax::ast::node::{MethodDefinitionKind, Node, PropertyDefinition}, + syntax::ast::node::{join_nodes, MethodDefinitionKind, Node, PropertyDefinition}, BoaProfiler, Context, Result, Value, }; use std::fmt; @@ -15,6 +15,9 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "vm")] use crate::vm::{compilation::CodeGen, Compiler, Instruction}; +#[cfg(test)] +mod tests; + /// Objects in JavaScript may be defined as an unordered collection of related data, of /// primitive or reference types, in the form of “key: value” pairs. /// @@ -53,24 +56,36 @@ impl Object { indent: usize, ) -> fmt::Result { f.write_str("{\n")?; + let indentation = " ".repeat(indent + 1); for property in self.properties().iter() { match property { PropertyDefinition::IdentifierReference(key) => { - write!(f, "{} {},", indent, key)?; + writeln!(f, "{}{},", indentation, key)?; } PropertyDefinition::Property(key, value) => { - write!(f, "{} {}: {},", indent, key, value)?; + write!(f, "{}{}: ", indentation, key,)?; + value.display_no_indent(f, indent + 1)?; + writeln!(f, ",")?; } PropertyDefinition::SpreadObject(key) => { - write!(f, "{} ...{},", indent, key)?; + writeln!(f, "{}...{},", indentation, key)?; } - PropertyDefinition::MethodDefinition(_kind, _key, _node) => { - // TODO: Implement display for PropertyDefinition::MethodDefinition. - unimplemented!("Display for PropertyDefinition::MethodDefinition"); + PropertyDefinition::MethodDefinition(kind, key, node) => { + write!(f, "{}", indentation)?; + match &kind { + MethodDefinitionKind::Get => write!(f, "get ")?, + MethodDefinitionKind::Set => write!(f, "set ")?, + MethodDefinitionKind::Ordinary => (), + } + write!(f, "{}(", key)?; + join_nodes(f, &node.parameters())?; + write!(f, ") ")?; + node.display_block(f, indent + 1)?; + writeln!(f, ",")?; } } } - f.write_str("}") + write!(f, "{}}}", " ".repeat(indent)) } } diff --git a/boa/src/syntax/ast/node/object/tests.rs b/boa/src/syntax/ast/node/object/tests.rs new file mode 100644 index 0000000000..64b4594ba9 --- /dev/null +++ b/boa/src/syntax/ast/node/object/tests.rs @@ -0,0 +1,34 @@ +#[test] +fn fmt() { + super::super::test_formatting( + r#" + let other = { + c: 10, + }; + let inst = { + val: 5, + b: "hello world", + nested: { + a: 5, + b: 6, + }, + ...other, + say_hi: function() { + console.log("hello!"); + }, + get a() { + return this.val + 1; + }, + set a(new_value) { + this.val = new_value; + }, + say_hello(msg) { + console.log("hello " + msg); + }, + }; + inst.a = 20; + inst.a; + inst.say_hello("humans"); + "#, + ); +} diff --git a/boa/src/syntax/ast/node/operator/tests.rs b/boa/src/syntax/ast/node/operator/tests.rs index 3fbc80086a..f5f9815f46 100644 --- a/boa/src/syntax/ast/node/operator/tests.rs +++ b/boa/src/syntax/ast/node/operator/tests.rs @@ -113,3 +113,28 @@ fn logical_assignment() { assert_eq!(&exec(scenario), "20"); } + +#[test] +fn fmt() { + super::super::test_formatting( + r#" + let a = 20; + a += 10; + a -= 10; + a *= 10; + a **= 10; + a /= 10; + a %= 10; + a &= 10; + a |= 10; + a ^= 10; + a <<= 10; + a >>= 10; + a >>>= 10; + a &&= 10; + a ||= 10; + a ??= 10; + a; + "#, + ); +} diff --git a/boa/src/syntax/ast/node/return_smt/mod.rs b/boa/src/syntax/ast/node/return_smt/mod.rs index 6201624b52..adb0f71f4a 100644 --- a/boa/src/syntax/ast/node/return_smt/mod.rs +++ b/boa/src/syntax/ast/node/return_smt/mod.rs @@ -9,6 +9,9 @@ use std::fmt; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests; + /// The `return` statement ends function execution and specifies a value to be returned to the /// function caller. /// diff --git a/boa/src/syntax/ast/node/return_smt/tests.rs b/boa/src/syntax/ast/node/return_smt/tests.rs new file mode 100644 index 0000000000..8e4ecb1f5d --- /dev/null +++ b/boa/src/syntax/ast/node/return_smt/tests.rs @@ -0,0 +1,16 @@ +#[test] +fn fmt() { + super::super::test_formatting( + r#" + function say_hello(msg) { + if (msg === "") { + return 0; + } + console.log("hello " + msg); + return; + }; + say_hello(""); + say_hello("world"); + "#, + ); +} diff --git a/boa/src/syntax/ast/node/spread/tests.rs b/boa/src/syntax/ast/node/spread/tests.rs index 4da380c622..04d554d65e 100644 --- a/boa/src/syntax/ast/node/spread/tests.rs +++ b/boa/src/syntax/ast/node/spread/tests.rs @@ -29,3 +29,19 @@ fn spread_with_call() { "#; assert_eq!(&exec(scenario), r#""message""#); } + +#[test] +fn fmt() { + super::super::test_formatting( + r#" + function f(m) { + return m; + }; + function g(...args) { + return f(...args); + }; + let a = g("message"); + a; + "#, + ); +} diff --git a/boa/src/syntax/ast/node/statement_list/mod.rs b/boa/src/syntax/ast/node/statement_list/mod.rs index 9f7b4eafbd..076998367b 100644 --- a/boa/src/syntax/ast/node/statement_list/mod.rs +++ b/boa/src/syntax/ast/node/statement_list/mod.rs @@ -41,11 +41,10 @@ impl StatementList { f: &mut fmt::Formatter<'_>, indentation: usize, ) -> fmt::Result { - let indent = " ".repeat(indentation); // Print statements for node in self.items.iter() { - f.write_str(&indent)?; - node.display(f, indentation + 1)?; + // We rely on the node to add the correct indent. + node.display(f, indentation)?; match node { Node::Block(_) | Node::If(_) | Node::Switch(_) | Node::WhileLoop(_) => {} diff --git a/boa/src/syntax/ast/node/switch/mod.rs b/boa/src/syntax/ast/node/switch/mod.rs index 41ba6a372f..ba817b2b47 100644 --- a/boa/src/syntax/ast/node/switch/mod.rs +++ b/boa/src/syntax/ast/node/switch/mod.rs @@ -105,19 +105,20 @@ impl Switch { pub(in crate::syntax::ast::node) fn display( &self, f: &mut fmt::Formatter<'_>, - indent: usize, + indentation: usize, ) -> fmt::Result { + let indent = " ".repeat(indentation); writeln!(f, "switch ({}) {{", self.val())?; for e in self.cases().iter() { - writeln!(f, "{}case {}:", indent, e.condition())?; - e.body().display(f, indent)?; + writeln!(f, "{} case {}:", indent, e.condition())?; + e.body().display(f, indentation + 2)?; } if let Some(ref default) = self.default { - writeln!(f, "{}default:", indent)?; - default.display(f, indent + 1)?; + writeln!(f, "{} default:", indent)?; + default.display(f, indentation + 2)?; } - writeln!(f, "{}}}", indent) + write!(f, "{}}}", indent) } } diff --git a/boa/src/syntax/ast/node/switch/tests.rs b/boa/src/syntax/ast/node/switch/tests.rs index ead275bc83..4d52794d00 100644 --- a/boa/src/syntax/ast/node/switch/tests.rs +++ b/boa/src/syntax/ast/node/switch/tests.rs @@ -206,3 +206,39 @@ fn bigger_switch_example() { assert_eq!(&exec(&scenario), val); } } + +#[test] +fn fmt() { + super::super::test_formatting( + r#" + let a = 3; + let b = "unknown"; + switch (a) { + case 0: + b = "Mon"; + break; + case 1: + b = "Tue"; + break; + case 2: + b = "Wed"; + break; + case 3: + b = "Thurs"; + break; + case 4: + b = "Fri"; + break; + case 5: + b = "Sat"; + break; + case 6: + b = "Sun"; + break; + default: + b = "Unknown"; + } + b; + "#, + ); +} diff --git a/boa/src/syntax/ast/node/template/tests.rs b/boa/src/syntax/ast/node/template/tests.rs index 9f9a299ee6..1696c29fcc 100644 --- a/boa/src/syntax/ast/node/template/tests.rs +++ b/boa/src/syntax/ast/node/template/tests.rs @@ -29,3 +29,20 @@ fn tagged_template() { r#"[ "result: ", " & ", "", "result: ", " \x26 ", "", 10, 20 ]"# ); } + +#[test] +fn fmt() { + super::super::test_formatting( + r#" + function tag(t, ...args) { + let a = []; + a = a.concat([t[0], t[1], t[2]]); + a = a.concat([t.raw[0], t.raw[1], t.raw[2]]); + a = a.concat([args[0], args[1]]); + return a; + }; + let a = 10; + tag`result: ${a} \x26 ${a + 10}`; + "#, + ); +} diff --git a/boa/src/syntax/ast/node/throw/mod.rs b/boa/src/syntax/ast/node/throw/mod.rs index 486b512943..e430e0a018 100644 --- a/boa/src/syntax/ast/node/throw/mod.rs +++ b/boa/src/syntax/ast/node/throw/mod.rs @@ -9,6 +9,9 @@ use std::fmt; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests; + /// The `throw` statement throws a user-defined exception. /// /// Syntax: `throw expression;` diff --git a/boa/src/syntax/ast/node/throw/tests.rs b/boa/src/syntax/ast/node/throw/tests.rs new file mode 100644 index 0000000000..076352822f --- /dev/null +++ b/boa/src/syntax/ast/node/throw/tests.rs @@ -0,0 +1,12 @@ +#[test] +fn fmt() { + super::super::test_formatting( + r#" + try { + throw "hello"; + } catch(e) { + console.log(e); + }; + "#, + ); +} diff --git a/boa/src/syntax/ast/node/try_node/tests.rs b/boa/src/syntax/ast/node/try_node/tests.rs index e5d2af85d3..87654c81a4 100644 --- a/boa/src/syntax/ast/node/try_node/tests.rs +++ b/boa/src/syntax/ast/node/try_node/tests.rs @@ -93,3 +93,23 @@ fn catch_binding_finally() { "#; assert_eq!(&exec(scenario), "30"); } + +#[test] +fn fmt() { + super::super::test_formatting( + r#" + try { + throw "hello"; + } catch(e) { + console.log(e); + } finally { + console.log("things"); + }; + try { + throw "hello"; + } catch { + console.log("something went wrong"); + }; + "#, + ); +}