use super::Parser;
use crate::{
context::ContextBuilder,
syntax::ast::{
node::{
field::GetConstField, object::PropertyDefinition, ArrowFunctionDecl, Assign, BinOp,
Call, Declaration, DeclarationList, FormalParameter, FormalParameterList,
FormalParameterListFlags, FunctionDecl, Identifier, If, New, Node, Object, Return,
StatementList, UnaryOp,
},
op::{self, CompOp, LogOp, NumOp},
Const,
},
Context,
};
use boa_interner::Interner;
#[allow(clippy::unwrap_used)]
#[track_caller]
pub(super) fn check_parser<L>(js: &str, expr: L, interner: Interner)
where
L: Into<Box<[Node]>>,
{
let mut context = ContextBuilder::default().interner(interner).build();
assert_eq!(
Parser::new(js.as_bytes())
.parse_all(&mut context)
.expect("failed to parse"),
StatementList::from(expr)
);
}
#[track_caller]
pub(super) fn check_invalid(js: &str) {
let mut context = Context::default();
assert!(Parser::new(js.as_bytes()).parse_all(&mut context).is_err());
}
#[test]
fn check_construct_call_precedence() {
let mut interner = Interner::default();
check_parser(
"new Date().getTime()",
vec![Node::from(Call::new(
GetConstField::new(
New::from(Call::new(
Identifier::new(interner.get_or_intern_static("Date")),
vec![],
)),
interner.get_or_intern_static("getTime"),
),
vec![],
))],
interner,
);
}
#[test]
fn assign_operator_precedence() {
let mut interner = Interner::default();
check_parser(
"a = a + 1",
vec![Assign::new(
Identifier::new(interner.get_or_intern_static("a")),
BinOp::new(
NumOp::Add,
Identifier::new(interner.get_or_intern_static("a")),
Const::from(1),
),
)
.into()],
interner,
);
}
#[test]
fn hoisting() {
let mut interner = Interner::default();
let hello = interner.get_or_intern_static("hello");
let a = interner.get_or_intern_static("a");
check_parser(
r"
var a = hello();
a++;
function hello() { return 10 }",
vec![
FunctionDecl::new(
hello,
FormalParameterList::default(),
vec![Return::new(Const::from(10), None).into()],
)
.into(),
DeclarationList::Var(
vec![Declaration::new_with_identifier(
a,
Node::from(Call::new(Identifier::new(hello), vec![])),
)]
.into(),
)
.into(),
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::new(a)).into(),
],
interner,
);
let mut interner = Interner::default();
let a = interner.get_or_intern_static("a");
check_parser(
r"
a = 10;
a++;
var a;",
vec![
Assign::new(Identifier::new(a), Const::from(10)).into(),
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::new(a)).into(),
DeclarationList::Var(vec![Declaration::new_with_identifier(a, None)].into()).into(),
],
interner,
);
}
#[test]
fn ambigous_regex_divide_expression() {
let s = "1 / a === 1 / b";
let mut interner = Interner::default();
check_parser(
s,
vec![BinOp::new(
CompOp::StrictEqual,
BinOp::new(
NumOp::Div,
Const::Int(1),
Identifier::new(interner.get_or_intern_static("a")),
),
BinOp::new(
NumOp::Div,
Const::Int(1),
Identifier::new(interner.get_or_intern_static("b")),
),
)
.into()],
interner,
);
}
#[test]
fn two_divisions_in_expression() {
let s = "a !== 0 || 1 / a === 1 / b;";
let mut interner = Interner::default();
let a = interner.get_or_intern_static("a");
check_parser(
s,
vec![BinOp::new(
LogOp::Or,
BinOp::new(CompOp::StrictNotEqual, Identifier::new(a), Const::Int(0)),
BinOp::new(
CompOp::StrictEqual,
BinOp::new(NumOp::Div, Const::Int(1), Identifier::new(a)),
BinOp::new(
NumOp::Div,
Const::Int(1),
Identifier::new(interner.get_or_intern_static("b")),
),
),
)
.into()],
interner,
);
}
#[test]
fn comment_semi_colon_insertion() {
let s = r#"
let a = 10 // Comment
let b = 20;
"#;
let mut interner = Interner::default();
check_parser(
s,
vec![
DeclarationList::Let(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("a"),
Some(Const::Int(10).into()),
)]
.into(),
)
.into(),
DeclarationList::Let(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("b"),
Some(Const::Int(20).into()),
)]
.into(),
)
.into(),
],
interner,
);
}
#[test]
fn multiline_comment_semi_colon_insertion() {
let s = r#"
let a = 10 /* Test
Multiline
Comment
*/ let b = 20;
"#;
let mut interner = Interner::default();
check_parser(
s,
vec![
DeclarationList::Let(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("a"),
Some(Const::Int(10).into()),
)]
.into(),
)
.into(),
DeclarationList::Let(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("b"),
Some(Const::Int(20).into()),
)]
.into(),
)
.into(),
],
interner,
);
}
#[test]
fn multiline_comment_no_lineterminator() {
let s = r#"
let a = 10; /* Test comment */ let b = 20;
"#;
let mut interner = Interner::default();
check_parser(
s,
vec![
DeclarationList::Let(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("a"),
Some(Const::Int(10).into()),
)]
.into(),
)
.into(),
DeclarationList::Let(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("b"),
Some(Const::Int(20).into()),
)]
.into(),
)
.into(),
],
interner,
);
}
#[test]
fn assignment_line_terminator() {
let s = r#"
let a = 3;
a =
5;
"#;
let mut interner = Interner::default();
check_parser(
s,
vec![
DeclarationList::Let(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("a"),
Some(Const::Int(3).into()),
)]
.into(),
)
.into(),
Assign::new(
Identifier::new(interner.get_or_intern_static("a")),
Const::from(5),
)
.into(),
],
interner,
);
}
#[test]
fn assignment_multiline_terminator() {
let s = r#"
let a = 3;
a =
5;
"#;
let mut interner = Interner::default();
let a = interner.get_or_intern_static("a");
check_parser(
s,
vec![
DeclarationList::Let(
vec![Declaration::new_with_identifier(
a,
Some(Const::Int(3).into()),
)]
.into(),
)
.into(),
Assign::new(Identifier::new(a), Const::from(5)).into(),
],
interner,
);
}
#[test]
fn bracketed_expr() {
let s = r#"(b)"#;
let mut interner = Interner::default();
check_parser(
s,
vec![Identifier::new(interner.get_or_intern_static("b")).into()],
interner,
);
}
#[test]
fn increment_in_comma_op() {
let s = r#"(b++, b)"#;
let mut interner = Interner::default();
let b = interner.get_or_intern_static("b");
check_parser(
s,
vec![BinOp::new::<_, Node, Node>(
op::BinOp::Comma,
UnaryOp::new::<Node>(op::UnaryOp::IncrementPost, Identifier::new(b).into()).into(),
Identifier::new(b).into(),
)
.into()],
interner,
);
}
#[test]
fn spread_in_object() {
let s = r#"
let x = {
a: 1,
...b,
}
"#;
let mut interner = Interner::default();
let object_properties = vec![
PropertyDefinition::property(interner.get_or_intern_static("a"), Const::from(1)),
PropertyDefinition::spread_object(Identifier::new(interner.get_or_intern_static("b"))),
];
check_parser(
s,
vec![DeclarationList::Let(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("x"),
Some(Object::from(object_properties).into()),
)]
.into(),
)
.into()],
interner,
);
}
#[test]
fn spread_in_arrow_function() {
let s = r#"
(...b) => {
b
}
"#;
let mut interner = Interner::default();
let b = interner.get_or_intern_static("b");
check_parser(
s,
vec![ArrowFunctionDecl::new(
None,
FormalParameterList {
parameters: Box::new([FormalParameter::new(
Declaration::new_with_identifier(b, None),
true,
)]),
flags: FormalParameterListFlags::empty()
.union(FormalParameterListFlags::HAS_REST_PARAMETER),
length: 0,
},
vec![Identifier::from(b).into()],
)
.into()],
interner,
);
}
#[test]
fn empty_statement() {
let mut interner = Interner::default();
let a = interner.get_or_intern_static("a");
check_parser(
r"
;;var a = 10;
if(a) ;
",
vec![
Node::Empty,
DeclarationList::Var(
vec![Declaration::new_with_identifier(
a,
Node::from(Const::from(10)),
)]
.into(),
)
.into(),
Node::If(If::new::<_, _, Node, _>(
Identifier::new(a),
Node::Empty,
None,
)),
],
interner,
);
}
#[test]
fn hashbang_use_strict_no_with() {
check_parser(
r#"#!\"use strict"
"#,
vec![],
Interner::default(),
);
}
#[test]
#[ignore]
fn hashbang_use_strict_with_with_statement() {
check_parser(
r#"#!\"use strict"
with({}) {}
"#,
vec![],
Interner::default(),
);
}
#[test]
fn hashbang_comment() {
check_parser(r"#!Comment Here", vec![], Interner::default());
}