Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/jsparagus-parser/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 28 kB image not shown  

Quelle  tests.rs   Sprache: unbekannt

 
Spracherkennung für: .rs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

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;");
}

[ Dauer der Verarbeitung: 0.37 Sekunden  ]