//! Expression-parser tests.
//!
//! Most tests render the parsed AST to a compact S-expression string and
//! compare against an expected shape — this keeps the assertions readable and
//! independent of span numbers.

use super::Parser;
use crate::ast::{
    Argument, ArrayElement, ArrayPatternElement, ArrowBody, BindingTarget, Class, ClassMember,
    ExportDecl, Expr, ForInit, ForLeft, Function, ImportSpecifier, MethodKind, ModuleExportName,
    ObjectMember, Param, Program, PropertyKey, Stmt, TemplateLiteral, VarDeclarator,
};
use alloc::string::String;
use alloc::vec::Vec;

/// Renders a binding target (identifier or destructuring pattern).
fn sexpr_target(t: &BindingTarget) -> String {
    use alloc::format;
    match t {
        BindingTarget::Ident(id) => id.name.clone().into_string(),
        BindingTarget::Array(p) => {
            let parts: Vec<String> = p
                .elements
                .iter()
                .map(|el| match el {
                    ArrayPatternElement::Hole => "hole".into(),
                    ArrayPatternElement::Rest { target, .. } => {
                        format!("...{}", sexpr_target(target))
                    }
                    ArrayPatternElement::Item {
                        target, default, ..
                    } => match default {
                        Some(d) => format!("{}={}", sexpr_target(target), sexpr(d)),
                        None => sexpr_target(target),
                    },
                })
                .collect();
            format!("[{}]", parts.join(" "))
        }
        BindingTarget::Object(p) => {
            let mut parts: Vec<String> = p
                .properties
                .iter()
                .map(|pr| {
                    let k = sexpr_key(&pr.key);
                    let base = if pr.shorthand {
                        k
                    } else {
                        format!("{k}:{}", sexpr_target(&pr.value))
                    };
                    match &pr.default {
                        Some(d) => format!("{base}={}", sexpr(d)),
                        None => base,
                    }
                })
                .collect();
            if let Some(r) = &p.rest {
                parts.push(format!("...{}", sexpr_target(r)));
            }
            format!("{{{}}}", parts.join(" "))
        }
    }
}

/// Parses a single expression (asserting the whole input is consumed).
fn parse(src: &str) -> Expr {
    Parser::parse_expression_entry(src).expect("parse ok")
}

/// Parses and renders to an S-expression.
fn sx(src: &str) -> String {
    sexpr(&parse(src))
}

/// Parses, asserting failure, and returns the error message.
fn perr(src: &str) -> String {
    use alloc::string::ToString;
    Parser::parse_expression_entry(src)
        .expect_err("expected parse error")
        .to_string()
}

/// Renders an expression as an S-expression for terse structural assertions.
fn sexpr(e: &Expr) -> String {
    use alloc::format;
    match e {
        Expr::Null(_) => "null".into(),
        Expr::Bool { value, .. } => format!("{value}"),
        Expr::Number { value, .. } => format!("{value}"),
        Expr::BigInt { digits, .. } => format!("{digits}n"),
        Expr::Str { value, .. } => format!("{:?}", crate::wtf8::to_string_lossy(value)),
        Expr::Regex { pattern, flags, .. } => format!("/{pattern}/{flags}"),
        Expr::Ident(id) => id.name.clone().into_string(),
        Expr::PrivateName(name, _) => format!("#{name}"),
        Expr::This(_) => "this".into(),
        Expr::Super(_) => "super".into(),
        Expr::NewTarget(_) => "new.target".into(),
        Expr::Template(t) => sexpr_template(t),
        Expr::TaggedTemplate { tag, quasi, .. } => {
            format!("(tagged {} {})", sexpr(tag), sexpr_template(quasi))
        }
        Expr::Array { elements, .. } => {
            let mut parts = Vec::new();
            for el in elements {
                parts.push(match el {
                    ArrayElement::Hole => "hole".into(),
                    ArrayElement::Item(e) => sexpr(e),
                    ArrayElement::Spread(e) => format!("(... {})", sexpr(e)),
                });
            }
            format!("(array {})", parts.join(" "))
        }
        Expr::Object { members, .. } => {
            let mut parts = Vec::new();
            for m in members {
                parts.push(match m {
                    ObjectMember::Spread { value, .. } => format!("(... {})", sexpr(value)),
                    ObjectMember::Accessor { is_getter, key, .. } => {
                        let kw = if *is_getter { "get" } else { "set" };
                        format!("({kw} {})", sexpr_key(key))
                    }
                    ObjectMember::Property {
                        key,
                        value,
                        shorthand,
                        ..
                    } => {
                        let k = sexpr_key(key);
                        if *shorthand {
                            format!("(short {k})")
                        } else {
                            format!("({k} {})", sexpr(value))
                        }
                    }
                });
            }
            format!("(object {})", parts.join(" "))
        }
        Expr::Member {
            object,
            property,
            optional,
            ..
        } => {
            let dot = if *optional { "?." } else { "." };
            format!("(member {dot} {} {})", sexpr(object), sexpr_key(property))
        }
        Expr::OptChain { expr, .. } => format!("(optchain {})", sexpr(expr)),
        Expr::Call {
            callee,
            arguments,
            optional,
            ..
        } => {
            let q = if *optional { "?call" } else { "call" };
            format!("({q} {}{})", sexpr(callee), sexpr_args(arguments))
        }
        Expr::New {
            callee, arguments, ..
        } => format!("(new {}{})", sexpr(callee), sexpr_args(arguments)),
        Expr::Unary { op, argument, .. } => format!("({} {})", op.as_str(), sexpr(argument)),
        Expr::Update {
            op,
            prefix,
            argument,
            ..
        } => {
            let pos = if *prefix { "pre" } else { "post" };
            format!("({pos}{} {})", op.as_str(), sexpr(argument))
        }
        Expr::Binary {
            op, left, right, ..
        } => format!("({} {} {})", op.as_str(), sexpr(left), sexpr(right)),
        Expr::Logical {
            op, left, right, ..
        } => format!("({} {} {})", op.as_str(), sexpr(left), sexpr(right)),
        Expr::Conditional {
            test,
            consequent,
            alternate,
            ..
        } => format!(
            "(?: {} {} {})",
            sexpr(test),
            sexpr(consequent),
            sexpr(alternate)
        ),
        Expr::Assign {
            op, target, value, ..
        } => format!("({} {} {})", op.as_str(), sexpr(target), sexpr(value)),
        Expr::Sequence { expressions, .. } => {
            let parts: Vec<String> = expressions.iter().map(sexpr).collect();
            format!("(seq {})", parts.join(" "))
        }
        Expr::Function(f) => sexpr_function(f),
        Expr::Arrow(a) => {
            let body = match &a.body {
                ArrowBody::Expr(e) => sexpr(e),
                ArrowBody::Block(b) => format!("(block {})", stmts(b)),
            };
            let kw = if a.is_async { "async-arrow" } else { "arrow" };
            format!("({kw} ({}) {body})", params(&a.params))
        }
        Expr::Class(c) => sexpr_class(c),
        Expr::Yield {
            argument, delegate, ..
        } => {
            let star = if *delegate { "*" } else { "" };
            match argument {
                Some(a) => format!("(yield{star} {})", sexpr(a)),
                None => format!("(yield{star})"),
            }
        }
        Expr::Await { argument, .. } => format!("(await {})", sexpr(argument)),
    }
}

fn sexpr_class(c: &Class) -> String {
    use alloc::format;
    let mut head = String::from("class");
    if let Some(id) = &c.id {
        head.push(' ');
        head.push_str(&id.name);
    }
    if let Some(s) = &c.super_class {
        head.push_str(&format!(" extends {}", sexpr(s)));
    }
    let members: Vec<String> = c
        .body
        .iter()
        .map(|m| match m {
            ClassMember::StaticBlock { body, .. } => format!("(static-block {})", stmts(body)),
            ClassMember::Field(f) => {
                let st = if f.is_static { "static-" } else { "" };
                let v = f
                    .value
                    .as_ref()
                    .map_or(String::new(), |e| format!(" {}", sexpr(e)));
                format!("({st}field {}{v})", sexpr_key(&f.key))
            }
            ClassMember::Method(m) => {
                let st = if m.is_static { "static-" } else { "" };
                let kind = match m.kind {
                    MethodKind::Method => "method",
                    MethodKind::Get => "get",
                    MethodKind::Set => "set",
                    MethodKind::Constructor => "ctor",
                };
                let mut flags = String::new();
                if m.value.is_async {
                    flags.push_str("async-");
                }
                if m.value.is_generator {
                    flags.push('*');
                }
                format!(
                    "({st}{flags}{kind} {} ({}) (block {}))",
                    sexpr_key(&m.key),
                    params(&m.value.params),
                    stmts(&m.value.body)
                )
            }
        })
        .collect();
    format!("({head} {})", members.join(" "))
}

fn sexpr_function(f: &Function) -> String {
    use alloc::format;
    let name = f.id.as_ref().map_or("", |id| &id.name);
    let mut tag = String::from("fn");
    if f.is_async {
        tag = "async-fn".into();
    }
    if f.is_generator {
        tag.push('*');
    }
    format!(
        "({tag} {name} ({}) (block {}))",
        params(&f.params),
        stmts(&f.body)
    )
}

fn params(ps: &[Param]) -> String {
    use alloc::format;
    let parts: Vec<String> = ps
        .iter()
        .map(|p| {
            let mut s = sexpr_target(&p.target);
            if p.rest {
                s = format!("...{s}");
            }
            if let Some(d) = &p.default {
                s = format!("{s}={}", sexpr(d));
            }
            s
        })
        .collect();
    parts.join(" ")
}

fn sexpr_key(key: &PropertyKey) -> String {
    use alloc::format;
    match key {
        PropertyKey::Ident(n) => n.clone().into_string(),
        PropertyKey::Private(n) => format!("#{n}"),
        PropertyKey::Str(s) => format!("{s:?}"),
        PropertyKey::Number(n) => format!("{n}"),
        PropertyKey::Computed(e) => format!("[{}]", sexpr(e)),
    }
}

fn sexpr_args(args: &[Argument]) -> String {
    use alloc::format;
    let mut s = String::new();
    for a in args {
        s.push(' ');
        match a {
            Argument::Item(e) => s.push_str(&sexpr(e)),
            Argument::Spread(e) => s.push_str(&format!("(... {})", sexpr(e))),
        }
    }
    s
}

fn sexpr_template(t: &TemplateLiteral) -> String {
    use alloc::format;
    let mut parts = Vec::new();
    for (i, q) in t.quasis.iter().enumerate() {
        let cooked = q
            .cooked
            .as_deref()
            .map_or_else(|| String::from("<bad>"), crate::wtf8::to_string_lossy);
        parts.push(format!("{cooked:?}"));
        if let Some(e) = t.expressions.get(i) {
            parts.push(format!("${{{}}}", sexpr(e)));
        }
    }
    format!("(tmpl {})", parts.join(" "))
}

// --- literals -----------------------------------------------------------

#[test]
fn literals() {
    assert_eq!(sx("null"), "null");
    assert_eq!(sx("true"), "true");
    assert_eq!(sx("false"), "false");
    assert_eq!(sx("this"), "this");
    assert_eq!(sx("42"), "42");
    assert_eq!(sx("2.5"), "2.5");
    assert_eq!(sx("0xFF"), "255");
    assert_eq!(sx("123n"), "123n");
    assert_eq!(sx(r#""hi\n""#), r#""hi\n""#);
    assert_eq!(sx("foo"), "foo");
    assert_eq!(sx("/ab+c/gi"), "/ab+c/gi");
}

// --- precedence & associativity ----------------------------------------

#[test]
fn arithmetic_precedence() {
    assert_eq!(sx("1 + 2 * 3"), "(+ 1 (* 2 3))");
    assert_eq!(sx("1 * 2 + 3"), "(+ (* 1 2) 3)");
    assert_eq!(sx("(1 + 2) * 3"), "(* (+ 1 2) 3)");
}

#[test]
fn left_associative_subtraction() {
    assert_eq!(sx("1 - 2 - 3"), "(- (- 1 2) 3)");
}

#[test]
fn exponent_right_associative() {
    assert_eq!(sx("2 ** 3 ** 2"), "(** 2 (** 3 2))");
    assert_eq!(sx("(-2) ** 2"), "(** (- 2) 2)");
    assert_eq!(sx("++x ** 2"), "(** (pre++ x) 2)");
}

#[test]
fn exponent_unary_operand_is_error() {
    assert!(perr("-2 ** 2").contains("parenthesized"));
    assert!(perr("typeof x ** 2").contains("parenthesized"));
}

#[test]
fn comparison_and_logical() {
    assert_eq!(sx("a < b == c"), "(== (< a b) c)");
    assert_eq!(sx("a || b && c"), "(|| a (&& b c))");
    assert_eq!(sx("a && b || c"), "(|| (&& a b) c)");
    assert_eq!(sx("a | b & c ^ d"), "(| a (^ (& b c) d))");
}

#[test]
fn relational_keywords() {
    assert_eq!(sx("x in y"), "(in x y)");
    assert_eq!(sx("a instanceof B"), "(instanceof a B)");
}

#[test]
fn nullish_mixing_requires_parens() {
    assert!(perr("a ?? b || c").contains("??"));
    assert!(perr("a || b ?? c").contains("??"));
    assert!(perr("a ?? b && c").contains("??"));
    // Parenthesized is fine.
    assert_eq!(sx("(a ?? b) || c"), "(|| (?? a b) c)");
    assert_eq!(sx("a ?? b ?? c"), "(?? (?? a b) c)");
}

#[test]
fn conditional_and_assignment() {
    assert_eq!(sx("a ? b : c ? d : e"), "(?: a b (?: c d e))");
    assert_eq!(sx("a = b = c"), "(= a (= b c))");
    assert_eq!(sx("x += 1"), "(+= x 1)");
    assert_eq!(sx("a.b ??= c"), "(??= (member . a b) c)");
}

#[test]
fn invalid_assignment_target() {
    assert!(perr("1 = 2").contains("invalid assignment target"));
    assert!(perr("a + b = c").contains("invalid assignment target"));
}

#[test]
fn sequence() {
    assert_eq!(sx("a, b, c"), "(seq a b c)");
}

// --- unary & update -----------------------------------------------------

#[test]
fn unary_and_update() {
    assert_eq!(sx("!x"), "(! x)");
    assert_eq!(sx("-+x"), "(- (+ x))");
    assert_eq!(sx("typeof x"), "(typeof x)");
    assert_eq!(sx("delete a.b"), "(delete (member . a b))");
    assert_eq!(sx("++x"), "(pre++ x)");
    assert_eq!(sx("x++"), "(post++ x)");
    assert_eq!(sx("x--"), "(post-- x)");
}

#[test]
fn postfix_blocked_by_newline() {
    // ASI: a newline before `++` means it is NOT a postfix operator, so `++b`
    // begins a fresh token the single-expression entry treats as trailing.
    assert!(perr("a\n++b").contains("trailing"));
}

// --- member / call / new ------------------------------------------------

#[test]
fn member_and_call() {
    assert_eq!(sx("a.b.c"), "(member . (member . a b) c)");
    assert_eq!(sx("a[b]"), "(member . a [b])");
    assert_eq!(sx("f(1, 2)"), "(call f 1 2)");
    assert_eq!(sx("a.b(c)"), "(call (member . a b) c)");
    assert_eq!(sx("f(...xs)"), "(call f (... xs))");
    assert_eq!(sx("obj.#priv"), "(member . obj #priv)");
    // Reserved word as a property name.
    assert_eq!(sx("a.class"), "(member . a class)");
}

#[test]
fn new_expression() {
    assert_eq!(sx("new X()"), "(new X)");
    assert_eq!(sx("new X"), "(new X)");
    assert_eq!(sx("new X(1, 2)"), "(new X 1 2)");
    assert_eq!(sx("new a.b.C()"), "(new (member . (member . a b) C))");
    // Call binds outside the `new`: new X().y
    assert_eq!(sx("new X().y"), "(member . (new X) y)");
    assert_eq!(sx("new new X()()"), "(new (new X))");
}

#[test]
fn optional_chaining() {
    // A chain containing `?.` is wrapped in an `optchain` boundary (the
    // short-circuit target); the trailing non-optional links stay inside it.
    assert_eq!(sx("a?.b"), "(optchain (member ?. a b))");
    assert_eq!(sx("a?.[b]"), "(optchain (member ?. a [b]))");
    assert_eq!(sx("a?.(x)"), "(optchain (?call a x))");
    assert_eq!(sx("a?.b.c"), "(optchain (member . (member ?. a b) c))");
    // A plain chain (no `?.`) is not wrapped.
    assert_eq!(sx("a.b.c"), "(member . (member . a b) c)");
    // Parentheses end the chain: the outer `.c` is a fresh, unwrapped access.
    assert_eq!(sx("(a?.b).c"), "(member . (optchain (member ?. a b)) c)");
}

#[test]
fn tagged_template() {
    assert_eq!(sx("tag`hi`"), r#"(tagged tag (tmpl "hi"))"#);
}

// --- arrays / objects / templates --------------------------------------

#[test]
fn array_literals() {
    assert_eq!(sx("[1, 2, 3]"), "(array 1 2 3)");
    assert_eq!(sx("[1, , 3]"), "(array 1 hole 3)");
    assert_eq!(sx("[1, ...xs]"), "(array 1 (... xs))");
    assert_eq!(sx("[,]"), "(array hole)");
    assert_eq!(sx("[1,]"), "(array 1)");
}

#[test]
fn object_literals() {
    assert_eq!(sx("{a: 1, b: 2}"), "(object (a 1) (b 2))");
    assert_eq!(sx("{x}"), "(object (short x))");
    assert_eq!(sx("{[k]: v}"), "(object ([k] v))");
    assert_eq!(sx(r#"{"a-b": 1}"#), r#"(object ("a-b" 1))"#);
    assert_eq!(sx("{0: 1}"), "(object (0 1))");
    assert_eq!(sx("{...rest}"), "(object (... rest))");
    // Reserved word allowed as a `key: value` key but not as shorthand.
    assert_eq!(sx("{if: 1}"), "(object (if 1))");
    assert!(perr("{if}").contains("shorthand"));
    // Getters / setters vs. methods/properties named get/set.
    assert_eq!(sx("{get x() {}, set x(v) {}}"), "(object (get x) (set x))");
    assert_eq!(
        sx("{get: 1, set() {}}"),
        "(object (get 1) (set (fn  () (block ))))"
    );
}

#[test]
fn templates() {
    assert_eq!(sx("`hello`"), r#"(tmpl "hello")"#);
    assert_eq!(sx("`a${x}b`"), r#"(tmpl "a" ${x} "b")"#);
    assert_eq!(sx("`${a + b}`"), r#"(tmpl "" ${(+ a b)} "")"#);
    assert_eq!(sx("`${`${x}`}`"), r#"(tmpl "" ${(tmpl "" ${x} "")} "")"#);
}

// --- deferred features give clear errors -------------------------------

#[test]
fn deferred_features() {
    // `new.target` now parses; only a bare `new.<other>` meta-property is an error.
    assert_eq!(sx("new.target"), "new.target");
    assert!(perr("new.foo").contains("expected `target`"));
    // A non-pattern array element is still an invalid destructuring target.
    assert!(perr("[a + b] = c").contains("invalid assignment target"));
}

#[test]
fn destructuring_assignment_targets_parse() {
    // Array/object literals on the left of `=` are valid destructuring targets.
    assert!(Parser::parse_program("[a, b] = [b, a];").is_ok());
    assert!(Parser::parse_program("[x, , ...rest] = arr;").is_ok());
    assert!(Parser::parse_program("({ a, b: c } = obj);").is_ok());
    assert!(Parser::parse_program("({ a, ...rest } = obj);").is_ok());
    assert!(Parser::parse_program("[a = 1, b = 2] = arr;").is_ok());
}

// === statements =========================================================

/// Parses a program and renders its statements (space-joined).
fn prog(src: &str) -> String {
    let program: Program = Parser::parse_program(src).expect("parse program ok");
    let parts: Vec<String> = program.body.iter().map(sexpr_stmt).collect();
    parts.join(" ")
}

/// Parses a program asserting failure; returns the error message.
fn sperr(src: &str) -> String {
    use alloc::string::ToString;
    Parser::parse_program(src)
        .expect_err("expected parse error")
        .to_string()
}

fn sexpr_stmt(s: &Stmt) -> String {
    use alloc::format;
    match s {
        Stmt::Expr { expression, .. } => format!("(expr {})", sexpr(expression)),
        Stmt::Empty { .. } => "(empty)".into(),
        Stmt::Debugger { .. } => "(debugger)".into(),
        Stmt::Block { body, .. } => format!("(block {})", stmts(body)),
        Stmt::Var(decl) => {
            let ds: Vec<String> = decl.declarations.iter().map(sexpr_declarator).collect();
            format!("({} {})", decl.kind.as_str(), ds.join(" "))
        }
        Stmt::If {
            test,
            consequent,
            alternate,
            ..
        } => match alternate {
            Some(a) => format!(
                "(if {} {} {})",
                sexpr(test),
                sexpr_stmt(consequent),
                sexpr_stmt(a)
            ),
            None => format!("(if {} {})", sexpr(test), sexpr_stmt(consequent)),
        },
        Stmt::While { test, body, .. } => format!("(while {} {})", sexpr(test), sexpr_stmt(body)),
        Stmt::DoWhile { body, test, .. } => {
            format!("(do {} {})", sexpr_stmt(body), sexpr(test))
        }
        Stmt::For {
            init,
            test,
            update,
            body,
            ..
        } => {
            let i = match init {
                None => "_".into(),
                Some(ForInit::Expr(e)) => sexpr(e),
                Some(ForInit::Var(d)) => {
                    let ds: Vec<String> = d.declarations.iter().map(sexpr_declarator).collect();
                    format!("({} {})", d.kind.as_str(), ds.join(" "))
                }
            };
            let t = test.as_ref().map_or("_".into(), |e| sexpr(e));
            let u = update.as_ref().map_or("_".into(), |e| sexpr(e));
            format!("(for {i} {t} {u} {})", sexpr_stmt(body))
        }
        Stmt::ForIn {
            left, right, body, ..
        } => format!(
            "(for-in {} {} {})",
            sexpr_for_left(left),
            sexpr(right),
            sexpr_stmt(body)
        ),
        Stmt::ForOf {
            left, right, body, ..
        } => format!(
            "(for-of {} {} {})",
            sexpr_for_left(left),
            sexpr(right),
            sexpr_stmt(body)
        ),
        Stmt::Switch {
            discriminant,
            cases,
            ..
        } => {
            let cs: Vec<String> = cases
                .iter()
                .map(|c| {
                    let label = c.test.as_ref().map_or("default".into(), sexpr);
                    format!("(case {label} {})", stmts(&c.body))
                })
                .collect();
            format!("(switch {} {})", sexpr(discriminant), cs.join(" "))
        }
        Stmt::Try {
            block,
            handler,
            finalizer,
            ..
        } => {
            let h = handler.as_ref().map_or("_".into(), |c| {
                let p = c.param.as_ref().map_or("_".into(), sexpr_target);
                format!("(catch {p} {})", stmts(&c.body))
            });
            let f = finalizer
                .as_ref()
                .map_or("_".into(), |b| format!("(finally {})", stmts(b)));
            format!("(try ({}) {h} {f})", stmts(block))
        }
        Stmt::Return { argument, .. } => match argument {
            Some(a) => format!("(return {})", sexpr(a)),
            None => "(return)".into(),
        },
        Stmt::Break { label, .. } => match label {
            Some(l) => format!("(break {})", l.name),
            None => "(break)".into(),
        },
        Stmt::Continue { label, .. } => match label {
            Some(l) => format!("(continue {})", l.name),
            None => "(continue)".into(),
        },
        Stmt::Throw { argument, .. } => format!("(throw {})", sexpr(argument)),
        Stmt::Labeled { label, body, .. } => {
            format!("(label {} {})", label.name, sexpr_stmt(body))
        }
        Stmt::With { object, body, .. } => {
            format!("(with {} {})", sexpr(object), sexpr_stmt(body))
        }
        Stmt::Function(f) => sexpr_function(f),
        Stmt::Class(c) => sexpr_class(c),
        Stmt::Import(i) => {
            let specs: Vec<String> = i
                .specifiers
                .iter()
                .map(|sp| match sp {
                    ImportSpecifier::Default(id) => id.name.clone().into_string(),
                    ImportSpecifier::Namespace(id) => format!("*as {}", id.name),
                    ImportSpecifier::Named { imported, local } => {
                        format!("{}as {}", mod_name(imported), local.name)
                    }
                })
                .collect();
            format!("(import [{}] {:?})", specs.join(" "), i.source)
        }
        Stmt::Export(e) => sexpr_export(e),
    }
}

fn mod_name(n: &ModuleExportName) -> String {
    use alloc::format;
    match n {
        ModuleExportName::Ident(s) => s.clone().into_string(),
        ModuleExportName::Str(s) => format!("{s:?}"),
    }
}

fn sexpr_export(e: &ExportDecl) -> String {
    use alloc::format;
    match e {
        ExportDecl::Named {
            specifiers, source, ..
        } => {
            let specs: Vec<String> = specifiers
                .iter()
                .map(|s| {
                    if s.local == s.exported {
                        mod_name(&s.local)
                    } else {
                        format!("{}as {}", mod_name(&s.local), mod_name(&s.exported))
                    }
                })
                .collect();
            match source {
                Some(src) => format!("(export [{}] from {src:?})", specs.join(" ")),
                None => format!("(export [{}])", specs.join(" ")),
            }
        }
        ExportDecl::All {
            exported, source, ..
        } => match exported {
            Some(n) => format!("(export * as {} from {source:?})", mod_name(n)),
            None => format!("(export * from {source:?})"),
        },
        ExportDecl::Default { declaration, .. } => {
            format!("(export-default {})", sexpr_stmt(declaration))
        }
        ExportDecl::Decl { declaration, .. } => {
            format!("(export {})", sexpr_stmt(declaration))
        }
    }
}

fn stmts(body: &[Stmt]) -> String {
    let parts: Vec<String> = body.iter().map(sexpr_stmt).collect();
    parts.join(" ")
}

fn sexpr_declarator(d: &VarDeclarator) -> String {
    use alloc::format;
    let target = sexpr_target(&d.target);
    match &d.init {
        Some(e) => format!("({target} {})", sexpr(e)),
        None => target,
    }
}

fn sexpr_for_left(left: &ForLeft) -> String {
    use alloc::format;
    match left {
        ForLeft::Decl { kind, target, .. } => {
            format!("({} {})", kind.as_str(), sexpr_target(target))
        }
        ForLeft::Target(e) => sexpr(e),
    }
}

#[test]
fn simple_statements() {
    assert_eq!(prog("x;"), "(expr x)");
    assert_eq!(prog(";"), "(empty)");
    assert_eq!(prog("debugger;"), "(debugger)");
    assert_eq!(prog("{ a; b; }"), "(block (expr a) (expr b))");
    assert_eq!(prog("a; b; c;"), "(expr a) (expr b) (expr c)");
}

#[test]
fn declarations() {
    assert_eq!(prog("let x = 1;"), "(let (x 1))");
    assert_eq!(prog("var a, b = 2, c;"), "(var a (b 2) c)");
    assert_eq!(prog("const k = 0;"), "(const (k 0))");
    assert!(sperr("const k;").contains("must be initialized"));
}

#[test]
fn if_else_and_dangling_else() {
    assert_eq!(prog("if (a) b;"), "(if a (expr b))");
    assert_eq!(prog("if (a) b; else c;"), "(if a (expr b) (expr c))");
    // The `else` binds to the inner `if`.
    assert_eq!(
        prog("if (a) if (b) c; else d;"),
        "(if a (if b (expr c) (expr d)))"
    );
}

#[test]
fn loops() {
    assert_eq!(prog("while (a) b;"), "(while a (expr b))");
    assert_eq!(prog("do a; while (b);"), "(do (expr a) b)");
    assert_eq!(prog("for (;;) a;"), "(for _ _ _ (expr a))");
    assert_eq!(
        prog("for (let i = 0; i < n; i++) f(i);"),
        "(for (let (i 0)) (< i n) (post++ i) (expr (call f i)))"
    );
    assert_eq!(
        prog("for (i = 0; i < 3; i += 1) {}"),
        "(for (= i 0) (< i 3) (+= i 1) (block ))"
    );
}

#[test]
fn for_in_and_of() {
    // Declaration head (the `Decl` arm renders `(kind name)`).
    assert_eq!(
        prog("for (const k in obj) f(k);"),
        "(for-in (const k) obj (expr (call f k)))"
    );
    assert_eq!(
        prog("for (let x of xs) g(x);"),
        "(for-of (let x) xs (expr (call g x)))"
    );
    // Bare assignment target (no declaration keyword) renders the expression.
    assert_eq!(prog("for (k in obj) ;"), "(for-in k obj (empty))");
}

#[test]
fn for_in_uses_no_in_rule() {
    // The `in` here introduces a for-in loop rather than a relational operator.
    assert_eq!(prog("for (a in b) ;"), "(for-in a b (empty))");
}

#[test]
fn switch_statement() {
    assert_eq!(
        prog("switch (x) { case 1: a; break; default: b; }"),
        "(switch x (case 1 (expr a) (break)) (case default (expr b)))"
    );
    assert!(sperr("switch (x) { default: ; default: ; }").contains("multiple `default`"));
}

#[test]
fn try_catch_finally() {
    assert_eq!(
        prog("try { a; } catch (e) { b; } finally { c; }"),
        "(try ((expr a)) (catch e (expr b)) (finally (expr c)))"
    );
    assert_eq!(
        prog("try { a; } catch { b; }"),
        "(try ((expr a)) (catch _ (expr b)) _)"
    );
    assert_eq!(
        prog("try { a; } finally { c; }"),
        "(try ((expr a)) _ (finally (expr c)))"
    );
    assert!(sperr("try { a; }").contains("catch"));
}

#[test]
fn jumps_and_labels() {
    // `return` is only valid inside a function body (an early error otherwise),
    // so wrap it to inspect the statement's shape.
    assert_eq!(
        prog("function f() { return; }"),
        "(fn f () (block (return)))"
    );
    assert_eq!(
        prog("function f() { return a + b; }"),
        "(fn f () (block (return (+ a b))))"
    );
    assert_eq!(prog("throw e;"), "(throw e)");
    assert_eq!(
        prog("loop: for (;;) break loop;"),
        "(label loop (for _ _ _ (break loop)))"
    );
    assert_eq!(
        prog("outer: while (a) continue outer;"),
        "(label outer (while a (continue outer)))"
    );
    // `{ a: 1 }` at statement position is a block with a labeled statement.
    assert_eq!(prog("{ a: 1 }"), "(block (label a (expr 1)))");
}

#[test]
fn asi_inserts_semicolons() {
    // Newline-separated statements need no explicit `;`.
    assert_eq!(prog("a\nb\nc"), "(expr a) (expr b) (expr c)");
    // `return` with a newline before its operand returns undefined (restricted
    // production): two statements, not `return a`. (`return` requires a function
    // body, so wrap it.)
    assert_eq!(
        prog("function f() { return\na }"),
        "(fn f () (block (return) (expr a)))"
    );
    // A newline after `throw` is an error.
    assert!(sperr("throw\ne").contains("newline after `throw`"));
    // Missing separator without a newline is an error.
    assert!(sperr("a b").contains("semicolon"));
}

// === functions & arrows =================================================

#[test]
fn function_declarations() {
    assert_eq!(prog("function f() {}"), "(fn f () (block ))");
    assert_eq!(
        prog("function add(a, b) { return a + b; }"),
        "(fn add (a b) (block (return (+ a b))))"
    );
    assert_eq!(prog("function* g() {}"), "(fn* g () (block ))");
    assert_eq!(prog("async function h() {}"), "(async-fn h () (block ))");
    assert!(sperr("function () {}").contains("requires a name"));
}

#[test]
fn parameters() {
    assert_eq!(
        prog("function f(a, b = 1, ...rest) {}"),
        "(fn f (a b=1 ...rest) (block ))"
    );
    // `async\nfunction` is `async;` then a function declaration (ASI).
    assert_eq!(
        prog("async\nfunction f() {}"),
        "(expr async) (fn f () (block ))"
    );
}

#[test]
fn function_expressions() {
    assert_eq!(
        prog("let f = function () {};"),
        "(let (f (fn  () (block ))))"
    );
    assert_eq!(
        prog("let f = function named(x) { return x; };"),
        "(let (f (fn named (x) (block (return x)))))"
    );
    assert_eq!(
        prog("(async function () {})"),
        "(expr (async-fn  () (block )))"
    );
}

#[test]
fn arrow_functions() {
    assert_eq!(sx("x => x + 1"), "(arrow (x) (+ x 1))");
    assert_eq!(sx("() => 1"), "(arrow () 1)");
    assert_eq!(sx("(a, b) => a * b"), "(arrow (a b) (* a b))");
    assert_eq!(sx("(a, b = 2) => a"), "(arrow (a b=2) a)");
    assert_eq!(sx("(...xs) => xs"), "(arrow (...xs) xs)");
    assert_eq!(sx("x => { return x; }"), "(arrow (x) (block (return x)))");
    assert_eq!(sx("async x => x"), "(async-arrow (x) x)");
    assert_eq!(sx("async (a) => a"), "(async-arrow (a) a)");
}

#[test]
fn arrow_cover_grammar() {
    // Parenthesized expression vs. arrow params — only `=>` makes it an arrow.
    assert_eq!(sx("(a + b) * c"), "(* (+ a b) c)");
    assert_eq!(sx("(a)"), "a");
    // Nested / right-associative arrows.
    assert_eq!(sx("a => b => a + b"), "(arrow (a) (arrow (b) (+ a b)))");
    // Arrow as a call argument.
    assert_eq!(sx("f(x => x, 1)"), "(call f (arrow (x) x) 1)");
    // Arrow as a conditional branch.
    assert_eq!(
        sx("c ? x => x : y => y"),
        "(?: c (arrow (x) x) (arrow (y) y))"
    );
}

#[test]
fn iife() {
    assert_eq!(
        prog("(function () { return 1; })();"),
        "(expr (call (fn  () (block (return 1)))))"
    );
    assert_eq!(prog("(() => 1)();"), "(expr (call (arrow () 1)))");
}

// === destructuring patterns ============================================

#[test]
fn array_destructuring() {
    assert_eq!(prog("let [a, b] = xs;"), "(let ([a b] xs))");
    assert_eq!(prog("let [a, , c] = xs;"), "(let ([a hole c] xs))");
    assert_eq!(
        prog("let [a = 1, ...rest] = xs;"),
        "(let ([a=1 ...rest] xs))"
    );
    assert_eq!(prog("const [[a], [b]] = m;"), "(const ([[a] [b]] m))");
}

#[test]
fn object_destructuring() {
    assert_eq!(prog("let {a, b} = o;"), "(let ({a b} o))");
    assert_eq!(prog("let {a: x, b: y} = o;"), "(let ({a:x b:y} o))");
    assert_eq!(prog("let {a = 1} = o;"), "(let ({a=1} o))");
    assert_eq!(prog("let {a, ...rest} = o;"), "(let ({a ...rest} o))");
    assert_eq!(prog("let {[k]: v} = o;"), "(let ({[k]:v} o))");
    // Nested object/array patterns.
    assert_eq!(prog("let {a: [b, c]} = o;"), "(let ({a:[b c]} o))");
}

#[test]
fn destructuring_in_params_and_catch() {
    assert_eq!(
        prog("function f({a, b}, [c]) {}"),
        "(fn f ({a b} [c]) (block ))"
    );
    assert_eq!(sx("({a, b}) => a + b"), "(arrow ({a b}) (+ a b))");
    assert_eq!(
        prog("try {} catch ({message}) {}"),
        "(try () (catch {message} ) _)"
    );
    assert_eq!(
        prog("for (const [k, v] of m) ;"),
        "(for-of (const [k v]) m (empty))"
    );
}

// === classes ============================================================

#[test]
fn empty_and_basic_class() {
    assert_eq!(prog("class C {}"), "(class C )");
    assert_eq!(prog("class C extends B {}"), "(class C extends B )");
    assert!(sperr("class {}").contains("requires a name"));
    assert_eq!(sx("(class {})"), "(class )");
}

#[test]
fn class_methods() {
    assert_eq!(
        prog("class C { m(a) { return a; } }"),
        "(class C (method m (a) (block (return a))))"
    );
    assert_eq!(
        prog("class C { constructor(x) { this.x = x; } }"),
        "(class C (ctor constructor (x) (block (expr (= (member . this x) x)))))"
    );
    assert_eq!(
        prog("class C { static s() {} m() {} }"),
        "(class C (static-method s () (block )) (method m () (block )))"
    );
    assert_eq!(
        prog("class C { *gen() {} async a() {} }"),
        "(class C (*method gen () (block )) (async-method a () (block )))"
    );
}

#[test]
fn class_accessors() {
    assert_eq!(
        prog("class C { get x() { return 1; } set x(v) {} }"),
        "(class C (get x () (block (return 1))) (set x (v) (block )))"
    );
    // `get`/`set`/`static`/`async` are usable as plain method names.
    assert_eq!(
        prog("class C { get() {} static() {} }"),
        "(class C (method get () (block )) (method static () (block )))"
    );
}

#[test]
fn class_fields_and_private() {
    assert_eq!(
        prog("class C { x = 1; y; static z = 2; }"),
        "(class C (field x 1) (field y) (static-field z 2))"
    );
    assert_eq!(
        prog("class C { #count = 0; #inc() { this.#count++; } }"),
        "(class C (field #count 0) (method #inc () (block (expr (post++ (member . this #count))))))"
    );
    assert_eq!(
        prog("class C { static { init(); } }"),
        "(class C (static-block (expr (call init))))"
    );
    // Computed keys.
    assert_eq!(
        prog("class C { [k]() {} }"),
        "(class C (method [k] () (block )))"
    );
}

// === yield / await ======================================================

#[test]
fn yield_in_generators() {
    assert_eq!(
        prog("function* g() { yield 1; yield* xs; yield; }"),
        "(fn* g () (block (expr (yield 1)) (expr (yield* xs)) (expr (yield))))"
    );
    assert_eq!(
        prog("function* g() { let x = yield a; }"),
        "(fn* g () (block (let (x (yield a)))))"
    );
    // Outside a generator, `yield` is not a keyword operator here (reserved →
    // unexpected token).
    assert!(sperr("yield 1;").contains("Yield") || sperr("yield 1;").contains("semicolon"));
}

#[test]
fn await_in_async() {
    assert_eq!(
        prog("async function f() { await g(); }"),
        "(async-fn f () (block (expr (await (call g)))))"
    );
    assert_eq!(sx("async () => await x"), "(async-arrow () (await x))");
    // A plain arrow inside an async function inherits `await`.
    assert_eq!(
        prog("async function f() { let g = () => await x; }"),
        "(async-fn f () (block (let (g (arrow () (await x))))))"
    );
}

#[test]
fn async_generator_method() {
    assert_eq!(
        prog("class C { async *m() { yield await x; } }"),
        "(class C (async-*method m () (block (expr (yield (await x))))))"
    );
}

// === modules ============================================================

#[test]
fn imports() {
    assert_eq!(
        prog("import \"side-effect\";"),
        "(import [] \"side-effect\")"
    );
    assert_eq!(prog("import x from \"m\";"), "(import [x] \"m\")");
    assert_eq!(
        prog("import * as ns from \"m\";"),
        "(import [*as ns] \"m\")"
    );
    assert_eq!(
        prog("import { a, b as c } from \"m\";"),
        "(import [aas a bas c] \"m\")"
    );
    assert_eq!(
        prog("import def, { a } from \"m\";"),
        "(import [def aas a] \"m\")"
    );
    assert_eq!(
        prog("import def, * as ns from \"m\";"),
        "(import [def *as ns] \"m\")"
    );
}

#[test]
fn exports() {
    // `export { a, b as c }` requires `a` and `b` to be declared bindings (a
    // module early error otherwise), so declare them in the program.
    assert_eq!(
        prog("var a, b; export { a, b as c };"),
        "(var a b) (export [a bas c])"
    );
    assert_eq!(prog("export { a } from \"m\";"), "(export [a] from \"m\")");
    assert_eq!(prog("export * from \"m\";"), "(export * from \"m\")");
    assert_eq!(
        prog("export * as ns from \"m\";"),
        "(export * as ns from \"m\")"
    );
    assert_eq!(prog("export const x = 1;"), "(export (const (x 1)))");
    assert_eq!(
        prog("export function f() {}"),
        "(export (fn f () (block )))"
    );
    assert_eq!(
        prog("export default 1 + 2;"),
        "(export-default (expr (+ 1 2)))"
    );
    assert_eq!(
        prog("export default function () {}"),
        "(export-default (fn  () (block )))"
    );
    assert_eq!(prog("export default class {}"), "(export-default (class ))");
}

#[test]
fn module_source_type_inferred() {
    assert_eq!(
        Parser::parse_program("export const x = 1;")
            .unwrap()
            .source_type,
        crate::ast::SourceType::Module
    );
    assert_eq!(
        Parser::parse_program("const x = 1;").unwrap().source_type,
        crate::ast::SourceType::Script
    );
}

// === `let` in single-statement position (Annex B / lookahead) ===========

#[test]
fn sloppy_let_in_single_statement_position_is_expression() {
    // In single-statement position `let` is the identifier `let`, so each of
    // these is valid: `let` (ASI) followed by the next statement.
    assert!(Parser::parse_program("if (false) let\n{}").is_ok());
    assert!(Parser::parse_program("if (false) let\nx = 1;").is_ok());
    assert!(Parser::parse_program("while (false) let\nx = 1;").is_ok());
    assert!(Parser::parse_program("for (;false;) let\n{}").is_ok());
    assert!(Parser::parse_program("with ({}) let\n{}").is_ok());
    assert!(Parser::parse_program("L: let\nx = 1;").is_ok());
    // Same-line member access `let[...]` is *not* the `let [` statement-leading
    // form, because the operand of the `if` is already in expression position.
    assert!(Parser::parse_program("if (x) let.foo;").is_ok());
}

#[test]
fn let_bracket_lookahead_rejected_in_single_statement_position() {
    // `let [` may begin neither an ExpressionStatement (lookahead restriction)
    // nor a declaration (not a Statement) in single-statement position — a
    // newline does not lift the restriction.
    assert!(Parser::parse_program("if (false) let\n[a] = 0;").is_err());
    assert!(Parser::parse_program("while (false) let\n[a] = 0;").is_err());
    assert!(Parser::parse_program("do let\n[x] = 0\nwhile (false);").is_err());
}

#[test]
fn genuine_lexical_declaration_in_single_statement_position_rejected() {
    // A real `let`/`const`/`class` declaration is never a Statement.
    assert!(Parser::parse_program("if (x) let y = 1;").is_err());
    assert!(Parser::parse_program("while (x) const y = 1;").is_err());
    assert!(Parser::parse_program("if (x) class C {}").is_err());
    // …but a lexical declaration is fine at statement-list position.
    assert!(Parser::parse_program("let [a] = b;").is_ok());
    assert!(Parser::parse_program("{ let y = 1; }").is_ok());
}

#[test]
fn annexb_sloppy_function_redeclaration_in_block_and_switch() {
    // Annex B.3.3: duplicate *plain* function declarations are allowed in a
    // sloppy block or switch scope.
    assert!(Parser::parse_program("{ function a() {} function a() {} }").is_ok());
    assert!(
        Parser::parse_program("switch (x) { case 1: function a() {} case 2: function a() {} }")
            .is_ok()
    );
    // But not when strict, not across a generator/async, and not vs let/class.
    assert!(Parser::parse_program("'use strict'; { function a() {} function a() {} }").is_err());
    assert!(Parser::parse_program("{ function a() {} function* a() {} }").is_err());
    assert!(Parser::parse_program("{ async function a() {} function a() {} }").is_err());
    assert!(Parser::parse_program("{ let a; function a() {} }").is_err());
    assert!(Parser::parse_program("{ class a {} function a() {} }").is_err());
}

#[test]
fn dynamic_import_and_import_meta_parse() {
    // Dynamic import is a CallExpression accepted in script code.
    assert!(Parser::parse_program("import('mod');").is_ok());
    assert!(Parser::parse_program("import('mod').then(x => x);").is_ok());
    assert!(Parser::parse_program("import('mod', { with: {} });").is_ok());
    assert!(Parser::parse_program("import('mod',);").is_ok()); // trailing comma
    // `import.meta` parses; it is a member of the (reserved) `import` reference.
    assert!(Parser::parse_program("import x from 'm'; import.meta.url;").is_ok());
    // Invalid import expression forms.
    assert!(Parser::parse_program("import();").is_err()); // no specifier
    assert!(Parser::parse_program("import(...a);").is_err()); // spread
    assert!(Parser::parse_program("new import('x');").is_err()); // not a new callee
    assert!(Parser::parse_program("import.foo;").is_err()); // only `.meta`
    // `import.meta` is module-only; a bare script using it is rejected.
    assert!(Parser::parse_program("import.meta;").is_err());
}

#[test]
fn yield_and_await_as_identifiers_outside_generator_async() {
    // Sloppy, non-generator/non-async: `yield`/`await` are ordinary identifiers.
    assert!(Parser::parse_program("var yield = 1; yield + 1;").is_ok());
    assert!(Parser::parse_program("var await = 1; await + 1;").is_ok());
    assert!(Parser::parse_program("function f(yield, await) { return yield + await; }").is_ok());
    assert!(Parser::parse_program("({ yield, await });").is_ok());
    // Inside a generator, `yield` is reserved (param + reference).
    assert!(Parser::parse_program("function* g(yield) {}").is_err());
    assert!(Parser::parse_program("function* g(x = yield) {}").is_err());
    // Inside async, `await` is reserved.
    assert!(Parser::parse_program("async function f(await) {}").is_err());
    assert!(Parser::parse_program("async function f(x = await 1) {}").is_err());
    // Strict mode forbids `yield` as a binding/reference.
    assert!(Parser::parse_program("'use strict'; var yield = 1;").is_err());
}

#[test]
fn escaped_identifiers_parse_and_cook() {
    // An escaped identifier name cooks to the same AST as the plain spelling.
    assert_eq!(prog(r"var \u0061 = 1;"), prog("var a = 1;"));
    // A reserved word is allowed as an IdentifierName (property key) when escaped.
    assert!(Parser::parse_program(r"var y = { bre\u0061k: 1 }; y.bre\u0061k;").is_ok());
    // ...but not as a binding/reference identifier (the StringValue is reserved).
    assert!(Parser::parse_program(r"var \u0069f = 1;").is_err());
    assert!(Parser::parse_program(r"\u0069f (x) {}").is_err());
    assert!(Parser::parse_program(r"({ \u0069f });").is_err()); // shorthand reference
}

#[test]
fn using_declarations_parse() {
    // `using` / `await using` declarations in blocks / functions.
    assert!(Parser::parse_program("{ using x = res(); }").is_ok());
    assert!(Parser::parse_program("async function f() { await using x = res(); }").is_ok());
    assert!(Parser::parse_program("function f() { for (using x of xs) {} }").is_ok());
    // `using` remains an identifier when not a declaration head.
    assert!(Parser::parse_program("var using = 1; using + using;").is_ok());
    assert!(Parser::parse_program("using;").is_ok());
    assert!(Parser::parse_program("using\nx;").is_ok()); // ASI: `using` is an ident
    // Restrictions.
    assert!(Parser::parse_program("{ using x; }").is_err()); // initializer required
    assert!(Parser::parse_program("using x = 1;").is_err()); // not at script top level
}

#[test]
fn untagged_template_invalid_escape_is_early_error() {
    // An *untagged* template literal with an invalid escape is a parse error.
    assert!(Parser::parse_program("`\\x0`;").is_err()); // truncated \x
    assert!(Parser::parse_program("`\\u0`;").is_err()); // truncated \u
    assert!(Parser::parse_program("`\\u`;").is_err()); // \u with no digits
    assert!(Parser::parse_program("`\\u{}`;").is_err()); // empty code point
    assert!(Parser::parse_program("`\\u{110000}`;").is_err()); // code point > 0x10FFFF
    assert!(Parser::parse_program("`\\u{1F_639}`;").is_err()); // separator in \u{}
    assert!(Parser::parse_program("`\\00`;").is_err()); // legacy octal
    assert!(Parser::parse_program("`\\8`;").is_err()); // \8
    assert!(Parser::parse_program("`\\9`;").is_err()); // \9
    assert!(Parser::parse_program("`a${1}\\x0`;").is_err()); // bad escape in tail segment

    // Valid untagged templates are unaffected.
    assert!(Parser::parse_program("`\\x41`;").is_ok());
    assert!(Parser::parse_program("`\\u{1F600}`;").is_ok());
    assert!(Parser::parse_program("`\\u{D800}`;").is_ok()); // lone surrogate is legal
    assert!(Parser::parse_program("`\\0`;").is_ok()); // \0 not followed by a digit
    assert!(Parser::parse_program("`\\n\\t\\\\`;").is_ok());
    assert!(Parser::parse_program("`a${1}b`;").is_ok());

    // A *tagged* template tolerates every otherwise-invalid escape (the cooked
    // value becomes `undefined`; only `.raw` is observable).
    assert!(Parser::parse_program("tag`\\x0`;").is_ok());
    assert!(Parser::parse_program("tag`\\u`;").is_ok());
    assert!(Parser::parse_program("tag`\\8`;").is_ok());
    assert!(Parser::parse_program("tag`\\00`;").is_ok());
    assert!(Parser::parse_program("tag`\\u{110000}`;").is_ok());
    assert!(Parser::parse_program("tag`a${1}\\x0`;").is_ok());
}

#[test]
fn unterminated_block_comment_is_error() {
    assert!(Parser::parse_program("/*CHECK#1/").is_err());
    assert!(Parser::parse_program("/* never closed").is_err());
    assert!(Parser::parse_program("var x = 1; /* trailing").is_err());
    // A `/*/` is an *unterminated* comment, not a closed one (`/` then EOF).
    assert!(Parser::parse_program("/*/").is_err());
    // Properly closed comments still parse.
    assert!(Parser::parse_program("/* ok */ var x = 1;").is_ok());
    assert!(Parser::parse_program("/**/").is_ok());
    assert!(Parser::parse_program("/*/ */").is_ok()); // `/` inside, then closed
}

#[test]
fn private_name_only_valid_as_in_lhs() {
    // The `#x in obj` brand check is the only legal bare-private-name position.
    assert!(Parser::parse_program("class C { #x; m(o){ return #x in o; } }").is_ok());
    assert!(
        Parser::parse_program("class C { #a; #b; m(o){ return #a in o && #b in o; } }").is_ok()
    );
    assert!(Parser::parse_program("class C { #a; m(o){ return (#a in o) in {}; } }").is_ok());
    assert!(Parser::parse_program("class C { #x = 1; m(){ return this.#x; } }").is_ok());

    // `#a in #b in this` parses as `(#a in #b) in this`: the inner `#b` is the
    // right operand of `in`, where a bare private name is illegal.
    assert!(
        Parser::parse_program("class C { #field; constructor(){ #field in #field in this; } }")
            .is_err()
    );
    // A standalone private-name reference is illegal.
    assert!(Parser::parse_program("class C { #x; m(){ return #x; } }").is_err());
    // An undeclared private name in the (otherwise legal) `in` LHS still errors.
    assert!(Parser::parse_program("class C { m(o){ return #nope in o; } }").is_err());
}
