summaryrefslogtreecommitdiffstats
path: root/third_party/rust/jsparagus-parser/src/tests.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/jsparagus-parser/src/tests.rs
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/jsparagus-parser/src/tests.rs')
-rw-r--r--third_party/rust/jsparagus-parser/src/tests.rs875
1 files changed, 875 insertions, 0 deletions
diff --git a/third_party/rust/jsparagus-parser/src/tests.rs b/third_party/rust/jsparagus-parser/src/tests.rs
new file mode 100644
index 0000000000..7953dd7554
--- /dev/null
+++ b/third_party/rust/jsparagus-parser/src/tests.rs
@@ -0,0 +1,875 @@
+use std::iter;
+
+use crate::lexer::Lexer;
+use crate::parser::Parser;
+use crate::{parse_script, ParseOptions};
+use ast::source_atom_set::SourceAtomSet;
+use ast::source_slice_list::SourceSliceList;
+use ast::{arena, source_location::SourceLocation, types::*};
+use bumpalo::{self, Bump};
+use generated_parser::{self, AstBuilder, ParseError, Result, TerminalId};
+use std::cell::RefCell;
+use std::rc::Rc;
+
+#[cfg(all(feature = "unstable", test))]
+mod benchmarks {
+ extern crate test;
+
+ use std::fs::File;
+ use std::io::Read;
+ use test::Bencher;
+
+ use crate::lexer::Lexer;
+ use crate::parse_script;
+
+ #[bench]
+ fn bench_parse_grammar(b: &mut Bencher) {
+ let mut buffer = fs::read_to_string("../vue.js").expect("reading test file");
+ b.iter(|| {
+ let lexer = Lexer::new(buffer.chars());
+ parse_script(lexer).unwrap();
+ });
+ }
+}
+
+trait IntoChunks<'a> {
+ type Chunks: Iterator<Item = &'a str>;
+ fn into_chunks(self) -> Self::Chunks;
+}
+
+impl<'a> IntoChunks<'a> for &'a str {
+ type Chunks = iter::Once<&'a str>;
+ fn into_chunks(self) -> Self::Chunks {
+ iter::once(self)
+ }
+}
+
+impl<'a> IntoChunks<'a> for &'a Vec<&'a str> {
+ type Chunks = iter::Cloned<std::slice::Iter<'a, &'a str>>;
+ fn into_chunks(self) -> Self::Chunks {
+ self.iter().cloned()
+ }
+}
+
+// Glue all the chunks together. XXX TODO Once the lexer supports chunks,
+// we'll reimplement this to feed the code to the lexer one chunk at a time.
+fn chunks_to_string<'a, T: IntoChunks<'a>>(code: T) -> String {
+ let mut buf = String::new();
+ for chunk in code.into_chunks() {
+ buf.push_str(chunk);
+ }
+ buf
+}
+
+fn try_parse<'alloc, 'source, Source>(
+ allocator: &'alloc Bump,
+ code: Source,
+) -> Result<'alloc, arena::Box<'alloc, Script<'alloc>>>
+where
+ Source: IntoChunks<'source>,
+{
+ let buf = arena::alloc_str(allocator, &chunks_to_string(code));
+ let options = ParseOptions::new();
+ let atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
+ let slices = Rc::new(RefCell::new(SourceSliceList::new()));
+ parse_script(allocator, &buf, &options, atoms, slices)
+}
+
+fn assert_parses<'alloc, T: IntoChunks<'alloc>>(code: T) {
+ let allocator = &Bump::new();
+ try_parse(allocator, code).unwrap();
+}
+
+fn assert_error<'alloc, T: IntoChunks<'alloc>>(code: T) {
+ let allocator = &Bump::new();
+ assert!(match try_parse(allocator, code).map_err(|e| *e) {
+ Err(ParseError::NotImplemented(_)) => panic!("expected error, got NotImplemented"),
+ Err(_) => true,
+ Ok(ast) => panic!("assertion failed: SUCCESS error: {:?}", ast),
+ });
+}
+
+fn assert_syntax_error<'alloc, T: IntoChunks<'alloc>>(code: T) {
+ let allocator = &Bump::new();
+ assert!(match try_parse(allocator, code).map_err(|e| *e) {
+ Err(ParseError::SyntaxError(_)) => true,
+ Err(other) => panic!("unexpected error: {:?}", other),
+ Ok(ast) => panic!("assertion failed: SUCCESS error: {:?}", ast),
+ });
+}
+
+fn assert_not_implemented<'alloc, T: IntoChunks<'alloc>>(code: T) {
+ let allocator = &Bump::new();
+ assert!(match try_parse(allocator, code).map_err(|e| *e) {
+ Err(ParseError::NotImplemented(_)) => true,
+ Err(other) => panic!("unexpected error: {:?}", other),
+ Ok(ast) => panic!("assertion failed: SUCCESS error: {:?}", ast),
+ });
+}
+
+fn assert_illegal_character<'alloc, T: IntoChunks<'alloc>>(code: T) {
+ let allocator = &Bump::new();
+ assert!(match try_parse(allocator, code).map_err(|e| *e) {
+ Err(ParseError::IllegalCharacter(_)) => true,
+ Err(other) => panic!("unexpected error: {:?}", other),
+ Ok(ast) => panic!("assertion failed: SUCCESS error: {:?}", ast),
+ });
+}
+
+fn assert_error_eq<'alloc, T: IntoChunks<'alloc>>(code: T, expected: ParseError) {
+ let allocator = &Bump::new();
+ let result = try_parse(allocator, code);
+ assert!(result.is_err());
+ assert_eq!(*result.unwrap_err(), expected);
+}
+
+fn assert_incomplete<'alloc, T: IntoChunks<'alloc>>(code: T) {
+ let allocator = &Bump::new();
+ let result = try_parse(allocator, code);
+ assert!(result.is_err());
+ assert_eq!(*result.unwrap_err(), ParseError::UnexpectedEnd);
+}
+
+// Assert that `left` and `right`, when parsed as ES Modules, consist of the
+// same sequence of tokens (although possibly at different offsets).
+fn assert_same_tokens<'alloc>(left: &str, right: &str) {
+ let allocator = &Bump::new();
+ let left_atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
+ let left_slices = Rc::new(RefCell::new(SourceSliceList::new()));
+ let right_atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
+ let right_slices = Rc::new(RefCell::new(SourceSliceList::new()));
+ let mut left_lexer = Lexer::new(
+ allocator,
+ left.chars(),
+ left_atoms.clone(),
+ left_slices.clone(),
+ );
+ let mut right_lexer = Lexer::new(
+ allocator,
+ right.chars(),
+ right_atoms.clone(),
+ right_slices.clone(),
+ );
+
+ let mut left_parser = Parser::new(
+ AstBuilder::new(allocator, left_atoms, left_slices),
+ generated_parser::START_STATE_MODULE,
+ );
+ let mut right_parser = Parser::new(
+ AstBuilder::new(allocator, right_atoms, right_slices),
+ generated_parser::START_STATE_MODULE,
+ );
+
+ loop {
+ let left_token = left_lexer
+ .next(&left_parser)
+ .expect("error parsing left string");
+ let right_token = right_lexer
+ .next(&right_parser)
+ .expect("error parsing right string");
+ assert_eq!(
+ left_token.terminal_id, right_token.terminal_id,
+ "at offset {} in {:?} / {} in {:?}",
+ left_token.loc.start, left, right_token.loc.start, right,
+ );
+ assert_eq!(
+ left_token.value, right_token.value,
+ "at offsets {} / {}",
+ left_token.loc.start, right_token.loc.start
+ );
+
+ if left_token.terminal_id == TerminalId::End {
+ break;
+ }
+ left_parser.write_token(left_token).unwrap();
+ right_parser.write_token(right_token).unwrap();
+ }
+ left_parser.close(left_lexer.offset()).unwrap();
+ right_parser.close(left_lexer.offset()).unwrap();
+}
+
+fn assert_can_close_after<'alloc, T: IntoChunks<'alloc>>(code: T) {
+ let allocator = &Bump::new();
+ let buf = chunks_to_string(code);
+ let atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
+ let slices = Rc::new(RefCell::new(SourceSliceList::new()));
+ let mut lexer = Lexer::new(allocator, buf.chars(), atoms.clone(), slices.clone());
+ let mut parser = Parser::new(
+ AstBuilder::new(allocator, atoms, slices),
+ generated_parser::START_STATE_SCRIPT,
+ );
+ loop {
+ let t = lexer.next(&parser).expect("lexer error");
+ if t.terminal_id == TerminalId::End {
+ break;
+ }
+ parser.write_token(t).unwrap();
+ }
+ assert!(parser.can_close());
+}
+
+fn assert_same_number(code: &str, expected: f64) {
+ let allocator = &Bump::new();
+ let script = try_parse(allocator, code).unwrap().unbox();
+ match &script.statements[0] {
+ Statement::ExpressionStatement(expression) => match &**expression {
+ Expression::LiteralNumericExpression(num) => {
+ assert_eq!(num.value, expected, "{}", code);
+ }
+ _ => panic!("expected LiteralNumericExpression"),
+ },
+ _ => panic!("expected ExpressionStatement"),
+ }
+}
+
+#[test]
+fn test_asi_at_end() {
+ assert_parses("3 + 4");
+ assert_syntax_error("3 4");
+ assert_incomplete("3 +");
+ assert_incomplete("{");
+ assert_incomplete("{;");
+}
+
+#[test]
+fn test_asi_at_block_end() {
+ assert_parses("{ doCrimes() }");
+ assert_parses("function f() { ok }");
+}
+
+#[test]
+fn test_asi_after_line_terminator() {
+ assert_parses(
+ "switch (value) {
+ case 1: break
+ case 2: console.log('2');
+ }",
+ );
+ assert_syntax_error("switch (value) { case 1: break case 2: console.log('2'); }");
+
+ // "[T]he presence or absence of single-line comments does not affect the
+ // process of automatic semicolon insertion[...]."
+ // <https://tc39.es/ecma262/#sec-comments>
+ assert_parses("x = 1 // line break here\ny = 2");
+ assert_parses("x = 1 // line break here\r\ny = 2");
+ assert_parses("x = 1 /* no line break in here */ //\ny = 2");
+ assert_parses("x = 1<!-- line break here\ny = 2");
+
+ assert_syntax_error("x = 1 /* no line break in here */ y = 2");
+ assert_parses("x = 1 /* line break \n there */y = 2");
+}
+
+#[test]
+fn test_asi_suppressed() {
+ // The specification says ASI does not happen in the production
+ // EmptyStatement : `;`.
+ // TODO - assert_syntax_error("if (true)");
+ assert_syntax_error("{ for (;;) }");
+
+ // ASI does not happen in for(;;) loops.
+ assert_syntax_error("for ( \n ; ) {}");
+ assert_syntax_error("for ( ; \n ) {}");
+ assert_syntax_error("for ( \n \n ) {}");
+ assert_syntax_error("for (var i = 0 \n i < 9; i++) {}");
+ assert_syntax_error("for (var i = 0; i < 9 \n i++) {}");
+ assert_syntax_error("for (i = 0 \n i < 9; i++) {}");
+ assert_syntax_error("for (i = 0; i < 9 \n i++) {}");
+ assert_syntax_error("for (const i = 0 \n i < 9; i++) {}");
+
+ // ASI is suppressed in the production ClassElement[Yield, Await] : `;`
+ // to prevent an infinite loop of ASI. lol
+ assert_syntax_error("class Fail { \n +1; }");
+}
+
+#[test]
+fn test_if_else() {
+ assert_parses("if (x) f();");
+ assert_incomplete("if (x)");
+ assert_parses("if (x) f(); else g();");
+ assert_incomplete("if (x) f(); else");
+ assert_parses("if (x) if (y) g(); else h();");
+ assert_parses("if (x) if (y) g(); else h(); else j();");
+}
+
+#[test]
+fn test_lexer_decimal() {
+ assert_parses("0.");
+ assert_parses(".5");
+ assert_syntax_error(".");
+}
+
+#[test]
+fn test_numbers() {
+ assert_same_number("0", 0.0);
+ assert_same_number("1", 1.0);
+ assert_same_number("10", 10.0);
+
+ assert_error_eq("0a", ParseError::IllegalCharacter('a'));
+ assert_error_eq("1a", ParseError::IllegalCharacter('a'));
+
+ assert_error_eq("1.0a", ParseError::IllegalCharacter('a'));
+ assert_error_eq(".0a", ParseError::IllegalCharacter('a'));
+ assert_error_eq("1.a", ParseError::IllegalCharacter('a'));
+
+ assert_same_number("1.0", 1.0);
+ assert_same_number("1.", 1.0);
+ assert_same_number("0.", 0.0);
+
+ assert_same_number("1.0e0", 1.0);
+ assert_same_number("1.e0", 1.0);
+ assert_same_number(".0e0", 0.0);
+
+ assert_same_number("1.0e+0", 1.0);
+ assert_same_number("1.e+0", 1.0);
+ assert_same_number(".0e+0", 0.0);
+
+ assert_same_number("1.0e-0", 1.0);
+ assert_same_number("1.e-0", 1.0);
+ assert_same_number(".0e-0", 0.0);
+
+ assert_error_eq("1.0e", ParseError::UnexpectedEnd);
+ assert_error_eq("1.e", ParseError::UnexpectedEnd);
+ assert_error_eq(".0e", ParseError::UnexpectedEnd);
+
+ assert_error_eq("1.0e+", ParseError::UnexpectedEnd);
+ assert_error_eq("1.0e-", ParseError::UnexpectedEnd);
+ assert_error_eq(".0e+", ParseError::UnexpectedEnd);
+ assert_error_eq(".0e-", ParseError::UnexpectedEnd);
+
+ assert_same_number("1.0E0", 1.0);
+ assert_same_number("1.E0", 1.0);
+ assert_same_number(".0E0", 0.0);
+
+ assert_same_number("1.0E+0", 1.0);
+ assert_same_number("1.E+0", 1.0);
+ assert_same_number(".0E+0", 0.0);
+
+ assert_same_number("1.0E-0", 1.0);
+ assert_same_number("1.E-0", 1.0);
+ assert_same_number(".0E-0", 0.0);
+
+ assert_error_eq("1.0E", ParseError::UnexpectedEnd);
+ assert_error_eq("1.E", ParseError::UnexpectedEnd);
+ assert_error_eq(".0E", ParseError::UnexpectedEnd);
+
+ assert_error_eq("1.0E+", ParseError::UnexpectedEnd);
+ assert_error_eq("1.0E-", ParseError::UnexpectedEnd);
+ assert_error_eq(".0E+", ParseError::UnexpectedEnd);
+ assert_error_eq(".0E-", ParseError::UnexpectedEnd);
+
+ assert_same_number(".0", 0.0);
+ assert_parses("");
+
+ assert_same_number("0b0", 0.0);
+
+ assert_same_number("0b1", 1.0);
+ assert_same_number("0B01", 1.0);
+ assert_error_eq("0b", ParseError::UnexpectedEnd);
+ assert_error_eq("0b ", ParseError::IllegalCharacter(' '));
+ assert_error_eq("0b2", ParseError::IllegalCharacter('2'));
+
+ assert_same_number("0o0", 0.0);
+ assert_same_number("0o7", 7.0);
+ assert_same_number("0O01234567", 0o01234567 as f64);
+ assert_error_eq("0o", ParseError::UnexpectedEnd);
+ assert_error_eq("0o ", ParseError::IllegalCharacter(' '));
+ assert_error_eq("0o8", ParseError::IllegalCharacter('8'));
+
+ assert_same_number("0x0", 0.0);
+ assert_same_number("0xf", 15.0);
+ assert_not_implemented("0X0123456789abcdef");
+ assert_not_implemented("0X0123456789ABCDEF");
+ assert_error_eq("0x", ParseError::UnexpectedEnd);
+ assert_error_eq("0x ", ParseError::IllegalCharacter(' '));
+ assert_error_eq("0xg", ParseError::IllegalCharacter('g'));
+
+ assert_parses("1..x");
+
+ assert_same_number("1_1", 11.0);
+ assert_same_number("0b1_1", 3.0);
+ assert_same_number("0o1_1", 9.0);
+ assert_same_number("0x1_1", 17.0);
+
+ assert_same_number("1_1.1_1", 11.11);
+ assert_same_number("1_1.1_1e+1_1", 11.11e11);
+
+ assert_error_eq("1_", ParseError::UnexpectedEnd);
+ assert_error_eq("1._1", ParseError::IllegalCharacter('_'));
+ assert_error_eq("1.1_", ParseError::UnexpectedEnd);
+ assert_error_eq("1.1e1_", ParseError::UnexpectedEnd);
+ assert_error_eq("1.1e_1", ParseError::IllegalCharacter('_'));
+}
+
+#[test]
+fn test_numbers_large() {
+ assert_same_number("4294967295", 4294967295.0);
+ assert_same_number("4294967296", 4294967296.0);
+ assert_same_number("4294967297", 4294967297.0);
+
+ assert_same_number("9007199254740991", 9007199254740991.0);
+ assert_same_number("9007199254740992", 9007199254740992.0);
+ assert_same_number("9007199254740993", 9007199254740992.0);
+
+ assert_same_number("18446744073709553664", 18446744073709552000.0);
+ assert_same_number("18446744073709553665", 18446744073709556000.0);
+
+ assert_same_number("0b11111111111111111111111111111111", 4294967295.0);
+ assert_same_number("0b100000000000000000000000000000000", 4294967296.0);
+ assert_same_number("0b100000000000000000000000000000001", 4294967297.0);
+
+ assert_same_number(
+ "0b11111111111111111111111111111111111111111111111111111",
+ 9007199254740991.0,
+ );
+ assert_not_implemented("0b100000000000000000000000000000000000000000000000000000");
+
+ assert_same_number("0o77777777777777777", 2251799813685247.0);
+ assert_not_implemented("0o100000000000000000");
+
+ assert_same_number("0xfffffffffffff", 4503599627370495.0);
+ assert_not_implemented("0x10000000000000");
+
+ assert_same_number("4.9406564584124654417656879286822e-324", 5e-324);
+}
+
+#[test]
+fn test_bigint() {
+ assert_not_implemented("0n");
+ /*
+ assert_parses("0n");
+ assert_parses("1n");
+ assert_parses("10n");
+
+ assert_error_eq("0na", ParseError::IllegalCharacter('a'));
+ assert_error_eq("1na", ParseError::IllegalCharacter('a'));
+
+ assert_error_eq("1.0n", ParseError::IllegalCharacter('n'));
+ assert_error_eq(".0n", ParseError::IllegalCharacter('n'));
+ assert_error_eq("1.n", ParseError::IllegalCharacter('n'));
+
+ assert_error_eq("1e0n", ParseError::IllegalCharacter('n'));
+ assert_error_eq("1e+0n", ParseError::IllegalCharacter('n'));
+ assert_error_eq("1e-0n", ParseError::IllegalCharacter('n'));
+ assert_error_eq("1E0n", ParseError::IllegalCharacter('n'));
+ assert_error_eq("1E+0n", ParseError::IllegalCharacter('n'));
+ assert_error_eq("1E-0n", ParseError::IllegalCharacter('n'));
+
+ assert_parses("0b0n");
+
+ assert_parses("0b1n");
+ assert_parses("0B01n");
+ assert_error_eq("0bn", ParseError::IllegalCharacter('n'));
+
+ assert_parses("0o0n");
+ assert_parses("0o7n");
+ assert_parses("0O01234567n");
+ assert_error_eq("0on", ParseError::IllegalCharacter('n'));
+
+ assert_parses("0x0n");
+ assert_parses("0xfn");
+ assert_parses("0X0123456789abcdefn");
+ assert_parses("0X0123456789ABCDEFn");
+ assert_error_eq("0xn", ParseError::IllegalCharacter('n'));
+
+ assert_parses("1_1n");
+ assert_parses("0b1_1n");
+ assert_parses("0o1_1n");
+ assert_parses("0x1_1n");
+
+ assert_error_eq("1_1.1_1n", ParseError::IllegalCharacter('n'));
+ assert_error_eq("1_1.1_1e1_1n", ParseError::IllegalCharacter('n'));
+
+ assert_error_eq("1_n", ParseError::IllegalCharacter('n'));
+ assert_error_eq("1.1_n", ParseError::IllegalCharacter('n'));
+ assert_error_eq("1.1e1_n", ParseError::IllegalCharacter('n'));
+ */
+}
+
+#[test]
+fn test_arrow() {
+ assert_parses("x => x");
+ assert_parses("f = x => x;");
+ assert_parses("(x, y) => [y, x]");
+ assert_parses("f = (x, y) => {}");
+ assert_syntax_error("(x, y) => {x: x, y: y}");
+}
+
+#[test]
+fn test_illegal_character() {
+ assert_illegal_character("\0");
+ assert_illegal_character("—x;");
+ assert_illegal_character("const ONE_THIRD = 1 ÷ 3;");
+}
+
+#[test]
+fn test_identifier() {
+ // U+00B7 MIDDLE DOT is an IdentifierPart.
+ assert_parses("_·_ = {_·_:'·_·'};");
+
+ // <ZWJ> and <ZWNJ> match IdentifierPart but not IdentifierStart.
+ assert_parses("var x\u{200c};"); // <ZWNJ>
+ assert_parses("_\u{200d}();"); // <ZWJ>
+ assert_parses("_\u{200d}__();"); // <ZWJ>
+ assert_parses("_\u{200d}\u{200c}();"); // <ZWJ>
+ assert_illegal_character("var \u{200c};"); // <ZWNJ>
+ assert_illegal_character("x = \u{200d};"); // <ZWJ>
+
+ // Other_ID_Start for backward compat.
+ assert_parses("\u{309B}();");
+ assert_parses("\u{309C}();");
+ assert_parses("_\u{309B}();");
+ assert_parses("_\u{309C}();");
+
+ // Non-BMP.
+ assert_parses("\u{10000}();");
+ assert_parses("_\u{10000}();");
+ assert_illegal_character("\u{1000c}();");
+ assert_illegal_character("_\u{1000c}();");
+}
+
+#[test]
+fn test_regexp() {
+ assert_parses(r"/\w/");
+ assert_parses("/[A-Z]/");
+ assert_parses("/[//]/");
+ assert_parses("/a*a/");
+ assert_parses("/**//x*/");
+ assert_same_tokens("/**//x*/", "/x*/");
+ assert_parses("{} /x/");
+ assert_parses("of / 2");
+}
+
+#[test]
+fn test_html_comments() {
+ assert_same_tokens("x<!--y;", "x");
+ assert_same_tokens("x<!-y;", "x < ! - y ;");
+ assert_same_tokens("x<!y", "x < ! y");
+
+ assert_same_tokens("--> hello world\nok", "ok");
+ assert_same_tokens("/* ignore */ --> also ignore\nok", "ok");
+ assert_same_tokens("/* ignore *//**/--> also ignore\nok", "ok");
+ assert_same_tokens("x-->y\nz", "x -- > y\nz");
+}
+
+#[test]
+fn test_incomplete_comments() {
+ assert_error("/*");
+ assert_error("/* hello world");
+ assert_error("/* hello world *");
+
+ assert_parses(&vec!["/* hello\n", " world */"]);
+ assert_parses(&vec!["// oawfeoiawj", "ioawefoawjie"]);
+ assert_parses(&vec!["// oawfeoiawj", "ioawefoawjie\n ok();"]);
+ assert_parses(&vec!["// oawfeoiawj", "ioawefoawjie", "jiowaeawojefiw"]);
+ assert_parses(&vec![
+ "// oawfeoiawj",
+ "ioawefoawjie",
+ "jiowaeawojefiw\n ok();",
+ ]);
+}
+
+#[test]
+fn test_strings() {
+ assert_parses("f(\"\",\"\")");
+ assert_parses("f(\"\")");
+ assert_parses("(\"\")");
+ assert_parses("f('','')");
+ assert_parses("f('')");
+ assert_parses("('')");
+}
+
+#[test]
+fn test_awkward_chunks() {
+ assert_parses(&vec!["const", "ructor.length = 1;"]);
+ assert_parses(&vec!["const", " x = 1;"]);
+
+ // Try feeding one character at a time to the parser.
+ let chars: Vec<&str> = "function f() { ok(); }".split("").collect();
+ assert_parses(&chars);
+
+ // XXX TODO
+ //assertEqual(
+ // self.parse(&vec!["/xyzzy/", "g;"]),
+ // ('Script',
+ // ('ScriptBody',
+ // ('StatementList 0',
+ // ('ExpressionStatement',
+ // ('PrimaryExpression 10', '/xyzzy/g'))))));
+
+ let allocator = &Bump::new();
+ let actual = try_parse(allocator, &vec!["x/", "=2;"]).unwrap();
+ let atoms = Rc::new(RefCell::new(SourceAtomSet::new()));
+ let expected = Script {
+ directives: arena::Vec::new_in(allocator),
+ statements: bumpalo::vec![
+ in allocator;
+ Statement::ExpressionStatement(arena::alloc(
+ allocator,
+ Expression::CompoundAssignmentExpression {
+ operator: CompoundAssignmentOperator::Div {
+ loc: SourceLocation::new(1, 3),
+ },
+ binding: SimpleAssignmentTarget::AssignmentTargetIdentifier(
+ AssignmentTargetIdentifier {
+ name: Identifier {
+ value: atoms.borrow_mut().insert("x"),
+ loc: SourceLocation::new(0, 1),
+ },
+ loc: SourceLocation::new(0, 1),
+ },
+ ),
+ expression: arena::alloc(
+ allocator,
+ Expression::LiteralNumericExpression(NumericLiteral {
+ value: 2.0,
+ loc: SourceLocation::new(3, 4),
+ }),
+ ),
+ loc: SourceLocation::new(0, 4),
+ },
+ ))
+ ],
+ loc: SourceLocation::new(0, 4),
+ };
+ assert_eq!(format!("{:?}", actual), format!("{:?}", expected));
+}
+
+#[test]
+fn test_can_close() {
+ let empty: Vec<&str> = vec![];
+ assert_can_close_after(&empty);
+ assert_can_close_after("");
+ assert_can_close_after("2 + 2;\n");
+ assert_can_close_after("// seems ok\n");
+}
+
+#[test]
+fn test_regex() {
+ assert_parses("/x/");
+ assert_parses("x = /x/");
+ assert_parses("x = /x/g");
+
+ // FIXME: Unexpected flag
+ // assert_parses("x = /x/wow_flags_can_be_$$anything$$");
+ assert_not_implemented("x = /x/wow_flags_can_be_$$anything$$");
+
+ // TODO: Should the lexer running out of input throw an incomplete error, or a lexer error?
+ assert_error_eq("/x", ParseError::UnterminatedRegExp);
+ assert_incomplete("x = //"); // comment
+ assert_error_eq("x = /*/", ParseError::UnterminatedMultiLineComment); /*/ comment */
+ assert_error_eq("x =/= 2", ParseError::UnterminatedRegExp);
+ assert_parses("x /= 2");
+ assert_parses("x = /[]/");
+ assert_parses("x = /[^x]/");
+ assert_parses("x = /+=351*/");
+ assert_parses("x = /^\\s*function (\\w+)/;");
+ assert_parses("const regexp = /this is fine: [/] dont @ me/;");
+}
+
+#[test]
+fn test_arrow_parameters() {
+ assert_error_eq(
+ "({a:a, ...b, c:c}) => {}",
+ ParseError::ObjectPatternWithNonFinalRest,
+ );
+ assert_error_eq(
+ "(a, [...zero, one]) => {}",
+ ParseError::ArrayPatternWithNonFinalRest,
+ );
+ assert_error_eq(
+ "(a, {items: [...zero, one]}) => {}",
+ ParseError::ArrayPatternWithNonFinalRest,
+ );
+}
+
+#[test]
+fn test_invalid_assignment_targets() {
+ assert_syntax_error("2 + 2 = x;");
+ assert_error_eq("(2 + 2) = x;", ParseError::InvalidAssignmentTarget);
+ assert_error_eq("++-x;", ParseError::InvalidAssignmentTarget);
+ assert_error_eq("(x && y)--;", ParseError::InvalidAssignmentTarget);
+}
+
+#[test]
+fn test_can_close_with_asi() {
+ assert_can_close_after("2 + 2\n");
+}
+
+#[test]
+fn test_conditional_keywords() {
+ // property names
+ assert_parses("const obj = {if: 3, function: 4};");
+ assert_parses("const obj = {true: 1, false: 0, null: NaN};");
+ assert_parses("assert(obj.if == 3);");
+ assert_parses("assert(obj.true + obj.false + obj.null == NaN);");
+
+ // method names
+ assert_parses(
+ "
+ class C {
+ if() {}
+ function() {}
+ }
+ ",
+ );
+
+ // FIXME: let (multitoken lookahead):
+ assert_not_implemented("let a = 1;");
+ /*
+ // let as identifier
+ assert_parses("var let = [new Date];");
+ // let as keyword, then identifier
+ assert_parses("let v = let;");
+ // `let .` -> ExpressionStatement
+ assert_parses("let.length;");
+ // `let [` -> LexicalDeclaration
+ assert_syntax_error("let[0].getYear();");
+ */
+
+ assert_parses(
+ "
+ var of = [1, 2, 3];
+ for (of of of) console.log(of); // logs 1, 2, 3
+ ",
+ );
+
+ // Not implemented:
+ // assert_parses("var of, let, private, target;");
+
+ assert_parses("class X { get y() {} }");
+
+ // Not implemented:
+ // assert_parses("async: { break async; }");
+
+ assert_parses("var get = { get get() {}, set get(v) {}, set: 3 };");
+
+ // Not implemented (requires hack; grammar is not LR(1)):
+ // assert_parses("for (async of => {};;) {}");
+ // assert_parses("for (async of []) {}");
+}
+
+#[test]
+fn test_async_arrows() {
+ // FIXME: async (multiple lookahead)
+ assert_not_implemented("const a = async a => 1;");
+ /*
+ assert_parses("let f = async arg => body;");
+ assert_parses("f = async (a1, a2) => {};");
+ assert_parses("f = async (a1 = b + c, ...a2) => {};");
+
+ assert_error_eq("f = async (a, b + c) => {};", ParseError::InvalidParameter);
+ assert_error_eq(
+ "f = async (...a1, a2) => {};",
+ ParseError::ArrowParametersWithNonFinalRest,
+ );
+ assert_error_eq("obj.async() => {}", ParseError::ArrowHeadInvalid);
+ */
+
+ assert_error_eq("foo(a, b) => {}", ParseError::ArrowHeadInvalid);
+}
+
+#[test]
+fn test_binary() {
+ assert_parses("1 == 2");
+ assert_parses("1 != 2");
+ assert_parses("1 === 2");
+ assert_parses("1 !== 2");
+ assert_parses("1 < 2");
+ assert_parses("1 <= 2");
+ assert_parses("1 > 2");
+ assert_parses("1 >= 2");
+ assert_parses("1 in 2");
+ assert_parses("1 instanceof 2");
+ assert_parses("1 << 2");
+ assert_parses("1 >> 2");
+ assert_parses("1 >>> 2");
+ assert_parses("1 + 2");
+ assert_parses("1 - 2");
+ assert_parses("1 * 2");
+ assert_parses("1 / 2");
+ assert_parses("1 % 2");
+ assert_parses("1 ** 2");
+ assert_parses("1 , 2");
+ assert_parses("1 || 2");
+ assert_parses("1 && 2");
+ assert_parses("1 | 2");
+ assert_parses("1 ^ 2");
+ assert_parses("1 & 2");
+}
+
+#[test]
+fn test_coalesce() {
+ assert_parses("const f = options.prop ?? 0;");
+ assert_syntax_error("if (options.prop ?? 0 || options.prop > 1000) {}");
+}
+
+#[test]
+fn test_no_line_terminator_here() {
+ // Parse `code` as a Script and compute some function of the resulting AST.
+ fn parse_then<F, R>(code: &str, f: F) -> R
+ where
+ F: FnOnce(&Script) -> R,
+ {
+ let allocator = &Bump::new();
+ match try_parse(allocator, code) {
+ Err(err) => {
+ panic!("Failed to parse code {:?}: {}", code, err);
+ }
+ Ok(script) => f(&*script),
+ }
+ }
+
+ // Parse `code` as a Script and return the number of top-level
+ // StatementListItems.
+ fn count_items(code: &str) -> usize {
+ parse_then(code, |script| script.statements.len())
+ }
+
+ // Without a newline, labelled `break` in loop. But a line break changes
+ // the meaning -- then it's a plain `break` statement, followed by
+ // ExpressionStatement `LOOP;`
+ assert_eq!(count_items("LOOP: while (true) break LOOP;"), 1);
+ assert_eq!(count_items("LOOP: while (true) break \n LOOP;"), 2);
+
+ // The same, but for `continue`.
+ assert_eq!(count_items("LOOP: while (true) continue LOOP;"), 1);
+ assert_eq!(count_items("LOOP: while (true) continue \n LOOP;"), 2);
+
+ // Parse `code` as a Script, expected to contain a single function
+ // declaration, and return the number of statements in the function body.
+ fn count_statements_in_function(code: &str) -> usize {
+ parse_then(code, |script| {
+ assert_eq!(
+ script.statements.len(),
+ 1,
+ "expected function declaration, got {:?}",
+ script
+ );
+ match &script.statements[0] {
+ Statement::FunctionDeclaration(func) => func.body.statements.len(),
+ _ => panic!("expected function declaration, got {:?}", script),
+ }
+ })
+ }
+
+ assert_eq!(
+ count_statements_in_function("function f() { return x; }"),
+ 1
+ );
+ assert_eq!(
+ count_statements_in_function("function f() { return\n x; }"),
+ 2
+ );
+
+ assert_parses("x++");
+ assert_incomplete("x\n++");
+
+ assert_parses("throw fit;");
+ assert_syntax_error("throw\nfit;");
+
+ // Alternative ways of spelling LineTerminator
+ assert_syntax_error("throw//\nfit;");
+ assert_syntax_error("throw/*\n*/fit;");
+ assert_syntax_error("throw\rfit;");
+ assert_syntax_error("throw\r\nfit;");
+}