Skip to main content

probar_js_gen/
builder.rs

1//! Fluent builder API for JavaScript generation.
2//!
3//! Provides a type-safe, ergonomic API for constructing JavaScript code.
4//!
5//! # Example
6//!
7//! ```rust,no_run
8//! use probar_js_gen::prelude::*;
9//!
10//! let js = JsModuleBuilder::new()
11//!     .comment("Generated - DO NOT EDIT")
12//!     .let_decl("x", Expr::num(42)).unwrap()
13//!     .build();
14//! ```
15//!
16//! # References
17//! - Bloch (2008) "Effective Java" - Builder pattern
18//! - Gamma et al. (1994) "Design Patterns" - Builder
19
20use crate::hir::*;
21use crate::Result;
22
23/// Builder for JavaScript modules.
24#[derive(Debug, Default)]
25pub struct JsModuleBuilder {
26    statements: Vec<Stmt>,
27    metadata: Option<GenerationMetadata>,
28}
29
30impl JsModuleBuilder {
31    /// Create a new empty module builder.
32    #[must_use]
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Set generation metadata for immutability tracking.
38    #[must_use]
39    pub fn metadata(mut self, metadata: GenerationMetadata) -> Self {
40        self.metadata = Some(metadata);
41        self
42    }
43
44    /// Add a statement.
45    #[must_use]
46    pub fn stmt(mut self, stmt: Stmt) -> Self {
47        self.statements.push(stmt);
48        self
49    }
50
51    /// Add multiple statements.
52    #[must_use]
53    pub fn stmts(mut self, stmts: impl IntoIterator<Item = Stmt>) -> Self {
54        self.statements.extend(stmts);
55        self
56    }
57
58    /// Add a comment.
59    #[must_use]
60    pub fn comment(self, text: impl Into<String>) -> Self {
61        self.stmt(Stmt::Comment(text.into()))
62    }
63
64    /// Add a let declaration.
65    pub fn let_decl(self, name: impl Into<String>, value: Expr) -> Result<Self> {
66        let ident = Identifier::new(name)?;
67        Ok(self.stmt(Stmt::Let { name: ident, value }))
68    }
69
70    /// Add a const declaration.
71    pub fn const_decl(self, name: impl Into<String>, value: Expr) -> Result<Self> {
72        let ident = Identifier::new(name)?;
73        Ok(self.stmt(Stmt::Const { name: ident, value }))
74    }
75
76    /// Add an assignment.
77    pub fn assign(self, name: impl Into<String>, value: Expr) -> Result<Self> {
78        let ident = Identifier::new(name)?;
79        Ok(self.stmt(Stmt::Assign { name: ident, value }))
80    }
81
82    /// Add an expression statement.
83    #[must_use]
84    pub fn expr(self, e: Expr) -> Self {
85        self.stmt(Stmt::Expr(e))
86    }
87
88    /// Add a class definition.
89    #[must_use]
90    pub fn class(self, class: JsClass) -> Self {
91        self.stmt(Stmt::Class(class))
92    }
93
94    /// Add a registerProcessor call.
95    pub fn register_processor(
96        self,
97        name: impl Into<String>,
98        class_name: impl Into<String>,
99    ) -> Result<Self> {
100        let class = Identifier::new(class_name)?;
101        Ok(self.stmt(Stmt::RegisterProcessor {
102            name: name.into(),
103            class,
104        }))
105    }
106
107    /// Build the module.
108    #[must_use]
109    pub fn build(self) -> JsModule {
110        JsModule {
111            statements: self.statements,
112            metadata: self.metadata,
113        }
114    }
115}
116
117/// Builder for JavaScript classes.
118#[derive(Debug)]
119pub struct JsClassBuilder {
120    name: Identifier,
121    extends: Option<Identifier>,
122    constructor: Option<Vec<Stmt>>,
123    methods: Vec<JsMethod>,
124}
125
126impl JsClassBuilder {
127    /// Create a new class builder.
128    pub fn new(name: impl Into<String>) -> Result<Self> {
129        Ok(Self {
130            name: Identifier::new(name)?,
131            extends: None,
132            constructor: None,
133            methods: Vec::new(),
134        })
135    }
136
137    /// Set the parent class.
138    pub fn extends(mut self, parent: impl Into<String>) -> Result<Self> {
139        self.extends = Some(Identifier::new(parent)?);
140        Ok(self)
141    }
142
143    /// Set the constructor body.
144    #[must_use]
145    pub fn constructor(mut self, body: Vec<Stmt>) -> Self {
146        self.constructor = Some(body);
147        self
148    }
149
150    /// Add a method.
151    pub fn method(
152        mut self,
153        name: impl Into<String>,
154        params: &[&str],
155        body: Vec<Stmt>,
156    ) -> Result<Self> {
157        let name = Identifier::new(name)?;
158        let params = params
159            .iter()
160            .map(|p| Identifier::new(*p))
161            .collect::<Result<Vec<_>>>()?;
162
163        self.methods.push(JsMethod { name, params, body });
164        Ok(self)
165    }
166
167    /// Build the class.
168    #[must_use]
169    pub fn build(self) -> JsClass {
170        JsClass {
171            name: self.name,
172            extends: self.extends,
173            constructor: self.constructor,
174            methods: self.methods,
175        }
176    }
177}
178
179/// Builder for JavaScript switch statements.
180#[derive(Debug)]
181pub struct JsSwitchBuilder {
182    expr: Expr,
183    cases: Vec<(Expr, Vec<Stmt>)>,
184    default: Option<Vec<Stmt>>,
185}
186
187impl JsSwitchBuilder {
188    /// Create a new switch builder.
189    #[must_use]
190    pub fn new(expr: Expr) -> Self {
191        Self {
192            expr,
193            cases: Vec::new(),
194            default: None,
195        }
196    }
197
198    /// Add a case.
199    #[must_use]
200    pub fn case(mut self, value: Expr, body: Vec<Stmt>) -> Self {
201        self.cases.push((value, body));
202        self
203    }
204
205    /// Set the default case.
206    #[must_use]
207    pub fn default(mut self, body: Vec<Stmt>) -> Self {
208        self.default = Some(body);
209        self
210    }
211
212    /// Build the switch.
213    #[must_use]
214    pub fn build(self) -> JsSwitch {
215        JsSwitch {
216            expr: self.expr,
217            cases: self.cases,
218            default: self.default,
219        }
220    }
221}
222
223/// Expression builder helpers.
224impl Expr {
225    /// Create a null literal.
226    #[must_use]
227    pub const fn null() -> Self {
228        Self::Null
229    }
230
231    /// Create a boolean literal.
232    #[must_use]
233    pub const fn bool(v: bool) -> Self {
234        Self::Bool(v)
235    }
236
237    /// Create a number literal.
238    #[must_use]
239    pub fn num(v: impl Into<f64>) -> Self {
240        Self::Num(v.into())
241    }
242
243    /// Create a string literal.
244    #[must_use]
245    pub fn str(s: impl Into<String>) -> Self {
246        Self::Str(s.into())
247    }
248
249    /// Create an identifier reference.
250    pub fn ident(name: impl Into<String>) -> Result<Self> {
251        Ok(Self::Ident(Identifier::new(name)?))
252    }
253
254    /// Create `this` reference.
255    #[must_use]
256    pub const fn this() -> Self {
257        Self::This
258    }
259
260    /// Member access: `self.prop`
261    pub fn dot(self, prop: impl Into<String>) -> Result<Self> {
262        Ok(Self::Member {
263            object: Box::new(self),
264            property: Identifier::new(prop)?,
265        })
266    }
267
268    /// Computed member: `self[index]`
269    #[must_use]
270    pub fn index(self, idx: Expr) -> Self {
271        Self::Index {
272            object: Box::new(self),
273            index: Box::new(idx),
274        }
275    }
276
277    /// Function call.
278    #[must_use]
279    pub fn call(self, args: Vec<Expr>) -> Self {
280        Self::Call {
281            callee: Box::new(self),
282            args,
283        }
284    }
285
286    /// New expression.
287    #[must_use]
288    pub fn new_expr(self, args: Vec<Expr>) -> Self {
289        Self::New {
290            constructor: Box::new(self),
291            args,
292        }
293    }
294
295    /// Await expression.
296    #[must_use]
297    pub fn await_expr(self) -> Self {
298        Self::Await(Box::new(self))
299    }
300
301    /// Dynamic import.
302    #[must_use]
303    pub fn import(path: Expr) -> Self {
304        Self::Import(Box::new(path))
305    }
306
307    // Binary operations
308
309    /// Addition: `self + other`
310    #[must_use]
311    pub fn add(self, other: Expr) -> Self {
312        Self::Binary {
313            left: Box::new(self),
314            op: BinOp::Add,
315            right: Box::new(other),
316        }
317    }
318
319    /// Subtraction: `self - other`
320    #[must_use]
321    pub fn sub(self, other: Expr) -> Self {
322        Self::Binary {
323            left: Box::new(self),
324            op: BinOp::Sub,
325            right: Box::new(other),
326        }
327    }
328
329    /// Multiplication: `self * other`
330    #[must_use]
331    pub fn mul(self, other: Expr) -> Self {
332        Self::Binary {
333            left: Box::new(self),
334            op: BinOp::Mul,
335            right: Box::new(other),
336        }
337    }
338
339    /// Division: `self / other`
340    #[must_use]
341    pub fn div(self, other: Expr) -> Self {
342        Self::Binary {
343            left: Box::new(self),
344            op: BinOp::Div,
345            right: Box::new(other),
346        }
347    }
348
349    /// Modulo: `self % other`
350    #[must_use]
351    pub fn modulo(self, other: Expr) -> Self {
352        Self::Binary {
353            left: Box::new(self),
354            op: BinOp::Mod,
355            right: Box::new(other),
356        }
357    }
358
359    /// Strict equality: `self === other`
360    #[must_use]
361    pub fn eq(self, other: Expr) -> Self {
362        Self::Binary {
363            left: Box::new(self),
364            op: BinOp::EqStrict,
365            right: Box::new(other),
366        }
367    }
368
369    /// Strict inequality: `self !== other`
370    #[must_use]
371    pub fn ne(self, other: Expr) -> Self {
372        Self::Binary {
373            left: Box::new(self),
374            op: BinOp::NeStrict,
375            right: Box::new(other),
376        }
377    }
378
379    /// Less than: `self < other`
380    #[must_use]
381    pub fn lt(self, other: Expr) -> Self {
382        Self::Binary {
383            left: Box::new(self),
384            op: BinOp::Lt,
385            right: Box::new(other),
386        }
387    }
388
389    /// Less than or equal: `self <= other`
390    #[must_use]
391    pub fn le(self, other: Expr) -> Self {
392        Self::Binary {
393            left: Box::new(self),
394            op: BinOp::Le,
395            right: Box::new(other),
396        }
397    }
398
399    /// Greater than: `self > other`
400    #[must_use]
401    pub fn gt(self, other: Expr) -> Self {
402        Self::Binary {
403            left: Box::new(self),
404            op: BinOp::Gt,
405            right: Box::new(other),
406        }
407    }
408
409    /// Greater than or equal: `self >= other`
410    #[must_use]
411    pub fn ge(self, other: Expr) -> Self {
412        Self::Binary {
413            left: Box::new(self),
414            op: BinOp::Ge,
415            right: Box::new(other),
416        }
417    }
418
419    /// Logical and: `self && other`
420    #[must_use]
421    pub fn and(self, other: Expr) -> Self {
422        Self::Binary {
423            left: Box::new(self),
424            op: BinOp::And,
425            right: Box::new(other),
426        }
427    }
428
429    /// Logical or: `self || other`
430    #[must_use]
431    pub fn or(self, other: Expr) -> Self {
432        Self::Binary {
433            left: Box::new(self),
434            op: BinOp::Or,
435            right: Box::new(other),
436        }
437    }
438
439    // Unary operations
440
441    /// Logical not: `!self`
442    #[must_use]
443    pub fn not(self) -> Self {
444        Self::Unary {
445            op: UnaryOp::Not,
446            operand: Box::new(self),
447        }
448    }
449
450    /// Negation: `-self`
451    #[must_use]
452    pub fn neg(self) -> Self {
453        Self::Unary {
454            op: UnaryOp::Neg,
455            operand: Box::new(self),
456        }
457    }
458
459    /// Typeof: `typeof self`
460    #[must_use]
461    pub fn type_of(self) -> Self {
462        Self::Unary {
463            op: UnaryOp::TypeOf,
464            operand: Box::new(self),
465        }
466    }
467
468    /// Ternary: `self ? then : else`
469    #[must_use]
470    pub fn ternary(self, then_expr: Expr, else_expr: Expr) -> Self {
471        Self::Ternary {
472            condition: Box::new(self),
473            then_expr: Box::new(then_expr),
474            else_expr: Box::new(else_expr),
475        }
476    }
477
478    /// Object literal.
479    #[must_use]
480    pub fn object(pairs: Vec<(&str, Expr)>) -> Self {
481        Self::Object(pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
482    }
483
484    /// Array literal.
485    #[must_use]
486    pub fn array(items: Vec<Expr>) -> Self {
487        Self::Array(items)
488    }
489
490    /// Arrow function.
491    pub fn arrow(params: &[&str], body: Expr) -> Result<Self> {
492        let params = params
493            .iter()
494            .map(|p| Identifier::new(*p))
495            .collect::<Result<Vec<_>>>()?;
496        Ok(Self::Arrow {
497            params,
498            body: Box::new(body),
499        })
500    }
501
502    /// Arrow function with block body.
503    pub fn arrow_block(params: &[&str], body: Vec<Stmt>) -> Result<Self> {
504        let params = params
505            .iter()
506            .map(|p| Identifier::new(*p))
507            .collect::<Result<Vec<_>>>()?;
508        Ok(Self::ArrowBlock { params, body })
509    }
510
511    /// Assignment: `self = value`
512    #[must_use]
513    pub fn assign(self, value: Expr) -> Self {
514        Self::Assign {
515            target: Box::new(self),
516            value: Box::new(value),
517        }
518    }
519}
520
521/// Statement builder helpers.
522impl Stmt {
523    /// Create a let declaration.
524    pub fn let_decl(name: impl Into<String>, value: Expr) -> Result<Self> {
525        Ok(Self::Let {
526            name: Identifier::new(name)?,
527            value,
528        })
529    }
530
531    /// Create a const declaration.
532    pub fn const_decl(name: impl Into<String>, value: Expr) -> Result<Self> {
533        Ok(Self::Const {
534            name: Identifier::new(name)?,
535            value,
536        })
537    }
538
539    /// Create an assignment.
540    pub fn assign(name: impl Into<String>, value: Expr) -> Result<Self> {
541        Ok(Self::Assign {
542            name: Identifier::new(name)?,
543            value,
544        })
545    }
546
547    /// Create a member assignment.
548    pub fn member_assign(obj: Expr, member: impl Into<String>, value: Expr) -> Result<Self> {
549        Ok(Self::MemberAssign {
550            object: obj,
551            member: Identifier::new(member)?,
552            value,
553        })
554    }
555
556    /// Create an add-assign.
557    #[must_use]
558    pub fn add_assign(target: Expr, value: Expr) -> Self {
559        Self::AddAssign { target, value }
560    }
561
562    /// Create a post-increment.
563    #[must_use]
564    pub fn post_increment(expr: Expr) -> Self {
565        Self::PostIncrement(expr)
566    }
567
568    /// Create an expression statement.
569    #[must_use]
570    pub fn expr(e: Expr) -> Self {
571        Self::Expr(e)
572    }
573
574    /// Create a return statement.
575    #[must_use]
576    pub fn ret() -> Self {
577        Self::Return(None)
578    }
579
580    /// Create a return with value.
581    #[must_use]
582    pub fn ret_val(e: Expr) -> Self {
583        Self::Return(Some(e))
584    }
585
586    /// Create an if statement.
587    #[must_use]
588    pub fn if_then(cond: Expr, then_body: Vec<Stmt>) -> Self {
589        Self::If {
590            condition: cond,
591            then_branch: then_body,
592            else_branch: None,
593        }
594    }
595
596    /// Create an if-else statement.
597    #[must_use]
598    pub fn if_else(cond: Expr, then_body: Vec<Stmt>, else_body: Vec<Stmt>) -> Self {
599        Self::If {
600            condition: cond,
601            then_branch: then_body,
602            else_branch: Some(else_body),
603        }
604    }
605
606    /// Create a for loop.
607    pub fn for_loop(
608        var: impl Into<String>,
609        start: Expr,
610        end: Expr,
611        body: Vec<Stmt>,
612    ) -> Result<Self> {
613        Ok(Self::For {
614            var: Identifier::new(var)?,
615            start,
616            end,
617            body,
618        })
619    }
620
621    /// Create a while loop.
622    #[must_use]
623    pub fn while_loop(cond: Expr, body: Vec<Stmt>) -> Self {
624        Self::While {
625            condition: cond,
626            body,
627        }
628    }
629
630    /// Create a try-catch.
631    pub fn try_catch(
632        body: Vec<Stmt>,
633        catch_var: impl Into<String>,
634        handler: Vec<Stmt>,
635    ) -> Result<Self> {
636        Ok(Self::TryCatch {
637            body,
638            catch_var: Identifier::new(catch_var)?,
639            handler,
640        })
641    }
642
643    /// Create a comment.
644    #[must_use]
645    pub fn comment(text: impl Into<String>) -> Self {
646        Self::Comment(text.into())
647    }
648
649    /// Create an onmessage handler.
650    #[must_use]
651    pub fn on_message(body: Vec<Stmt>) -> Self {
652        Self::OnMessage(body)
653    }
654
655    /// Create a registerProcessor call.
656    pub fn register_processor(name: impl Into<String>, class: impl Into<String>) -> Result<Self> {
657        Ok(Self::RegisterProcessor {
658            name: name.into(),
659            class: Identifier::new(class)?,
660        })
661    }
662}
663
664#[cfg(test)]
665#[allow(clippy::unwrap_used, clippy::panic)]
666mod tests {
667    use super::*;
668    use crate::hir::GenerationMetadata;
669
670    // ========================================================================
671    // JsModuleBuilder Tests
672    // ========================================================================
673
674    #[test]
675    fn module_builder_basic() {
676        let module = JsModuleBuilder::new()
677            .comment("test")
678            .let_decl("x", Expr::num(42))
679            .unwrap()
680            .build();
681
682        assert_eq!(module.statements.len(), 2);
683    }
684
685    #[test]
686    fn module_builder_metadata() {
687        let meta = GenerationMetadata {
688            tool: "test".into(),
689            version: "1.0".into(),
690            input_hash: "abc123".into(),
691            timestamp: "2024-01-01".into(),
692            regenerate_cmd: "cargo run --bin gen".into(),
693        };
694        let module = JsModuleBuilder::new().metadata(meta).build();
695        assert!(module.metadata.is_some());
696        assert_eq!(module.metadata.unwrap().tool, "test");
697    }
698
699    #[test]
700    fn module_builder_stmts() {
701        let stmts = vec![Stmt::comment("a"), Stmt::comment("b")];
702        let module = JsModuleBuilder::new().stmts(stmts).build();
703        assert_eq!(module.statements.len(), 2);
704    }
705
706    #[test]
707    fn module_builder_const_decl() {
708        let module = JsModuleBuilder::new()
709            .const_decl("GRAVITY", Expr::num(9.81))
710            .unwrap()
711            .build();
712        assert_eq!(module.statements.len(), 1);
713    }
714
715    #[test]
716    fn module_builder_assign() {
717        let module = JsModuleBuilder::new()
718            .assign("x", Expr::num(10))
719            .unwrap()
720            .build();
721        assert_eq!(module.statements.len(), 1);
722    }
723
724    #[test]
725    fn module_builder_expr() {
726        let module = JsModuleBuilder::new()
727            .expr(Expr::ident("foo").unwrap())
728            .build();
729        assert_eq!(module.statements.len(), 1);
730    }
731
732    #[test]
733    fn module_builder_class() {
734        let class = JsClassBuilder::new("Test").unwrap().build();
735        let module = JsModuleBuilder::new().class(class).build();
736        assert_eq!(module.statements.len(), 1);
737    }
738
739    #[test]
740    fn module_builder_register_processor() {
741        let module = JsModuleBuilder::new()
742            .register_processor("my-processor", "MyProcessor")
743            .unwrap()
744            .build();
745        assert_eq!(module.statements.len(), 1);
746    }
747
748    // ========================================================================
749    // JsClassBuilder Tests
750    // ========================================================================
751
752    #[test]
753    fn class_builder_basic() -> Result<()> {
754        let class = JsClassBuilder::new("Foo")?
755            .extends("Bar")?
756            .constructor(vec![])
757            .method("baz", &["x", "y"], vec![Stmt::ret()])?
758            .build();
759
760        assert_eq!(class.name.as_str(), "Foo");
761        assert_eq!(
762            class
763                .extends
764                .as_ref()
765                .map(super::super::hir::Identifier::as_str),
766            Some("Bar")
767        );
768        assert_eq!(class.methods.len(), 1);
769        Ok(())
770    }
771
772    #[test]
773    fn class_builder_no_extends() {
774        let class = JsClassBuilder::new("Simple").unwrap().build();
775        assert!(class.extends.is_none());
776        assert!(class.constructor.is_none());
777    }
778
779    #[test]
780    fn class_builder_invalid_name() {
781        assert!(JsClassBuilder::new("class").is_err());
782    }
783
784    #[test]
785    fn class_builder_invalid_extends() {
786        let result = JsClassBuilder::new("Valid").unwrap().extends("123invalid");
787        assert!(result.is_err());
788    }
789
790    #[test]
791    fn class_builder_invalid_method_name() {
792        let result = JsClassBuilder::new("Valid")
793            .unwrap()
794            .method("class", &[], vec![]);
795        assert!(result.is_err());
796    }
797
798    #[test]
799    fn class_builder_invalid_method_param() {
800        let result = JsClassBuilder::new("Valid")
801            .unwrap()
802            .method("test", &["class"], vec![]);
803        assert!(result.is_err());
804    }
805
806    // ========================================================================
807    // JsSwitchBuilder Tests
808    // ========================================================================
809
810    #[test]
811    fn switch_builder_basic() {
812        let sw = JsSwitchBuilder::new(Expr::ident("x").unwrap())
813            .case(Expr::num(1), vec![Stmt::ret_val(Expr::str("one"))])
814            .case(Expr::num(2), vec![Stmt::ret_val(Expr::str("two"))])
815            .default(vec![Stmt::ret_val(Expr::str("other"))])
816            .build();
817
818        assert_eq!(sw.cases.len(), 2);
819        assert!(sw.default.is_some());
820    }
821
822    #[test]
823    fn switch_builder_no_default() {
824        let sw = JsSwitchBuilder::new(Expr::num(0))
825            .case(Expr::num(0), vec![])
826            .build();
827
828        assert_eq!(sw.cases.len(), 1);
829        assert!(sw.default.is_none());
830    }
831
832    // ========================================================================
833    // Expr Builder Tests
834    // ========================================================================
835
836    #[test]
837    fn expr_builder_chain() -> Result<()> {
838        let expr = Expr::ident("foo")?.dot("bar")?.call(vec![Expr::num(1)]);
839        match expr {
840            Expr::Call { callee, args } => {
841                assert_eq!(args.len(), 1);
842                match *callee {
843                    Expr::Member { property, .. } => {
844                        assert_eq!(property.as_str(), "bar");
845                    }
846                    _ => panic!("expected member"),
847                }
848            }
849            _ => panic!("expected call"),
850        }
851        Ok(())
852    }
853
854    #[test]
855    fn expr_literals() {
856        assert!(matches!(Expr::null(), Expr::Null));
857        assert!(matches!(Expr::bool(true), Expr::Bool(true)));
858        assert!(matches!(Expr::num(42), Expr::Num(n) if (n - 42.0).abs() < f64::EPSILON));
859        assert!(matches!(Expr::str("hi"), Expr::Str(s) if s == "hi"));
860        assert!(matches!(Expr::this(), Expr::This));
861    }
862
863    #[test]
864    fn expr_index() {
865        let expr = Expr::ident("arr").unwrap().index(Expr::num(0));
866        assert!(matches!(expr, Expr::Index { .. }));
867    }
868
869    #[test]
870    fn expr_new() {
871        let expr = Expr::ident("Date").unwrap().new_expr(vec![]);
872        assert!(matches!(expr, Expr::New { .. }));
873    }
874
875    #[test]
876    fn expr_await() {
877        let expr = Expr::ident("promise").unwrap().await_expr();
878        assert!(matches!(expr, Expr::Await(_)));
879    }
880
881    #[test]
882    fn expr_import() {
883        let expr = Expr::import(Expr::str("./module.js"));
884        assert!(matches!(expr, Expr::Import(_)));
885    }
886
887    #[test]
888    fn expr_binary_ops() {
889        let a = Expr::num(1);
890        let b = Expr::num(2);
891
892        assert!(matches!(
893            a.clone().add(b.clone()),
894            Expr::Binary { op: BinOp::Add, .. }
895        ));
896        assert!(matches!(
897            a.clone().sub(b.clone()),
898            Expr::Binary { op: BinOp::Sub, .. }
899        ));
900        assert!(matches!(
901            a.clone().mul(b.clone()),
902            Expr::Binary { op: BinOp::Mul, .. }
903        ));
904        assert!(matches!(
905            a.clone().div(b.clone()),
906            Expr::Binary { op: BinOp::Div, .. }
907        ));
908        assert!(matches!(
909            a.clone().modulo(b.clone()),
910            Expr::Binary { op: BinOp::Mod, .. }
911        ));
912        assert!(matches!(
913            a.clone().eq(b.clone()),
914            Expr::Binary {
915                op: BinOp::EqStrict,
916                ..
917            }
918        ));
919        assert!(matches!(
920            a.clone().ne(b.clone()),
921            Expr::Binary {
922                op: BinOp::NeStrict,
923                ..
924            }
925        ));
926        assert!(matches!(
927            a.clone().lt(b.clone()),
928            Expr::Binary { op: BinOp::Lt, .. }
929        ));
930        assert!(matches!(
931            a.clone().le(b.clone()),
932            Expr::Binary { op: BinOp::Le, .. }
933        ));
934        assert!(matches!(
935            a.clone().gt(b.clone()),
936            Expr::Binary { op: BinOp::Gt, .. }
937        ));
938        assert!(matches!(
939            a.clone().ge(b.clone()),
940            Expr::Binary { op: BinOp::Ge, .. }
941        ));
942        assert!(matches!(
943            a.clone().and(b.clone()),
944            Expr::Binary { op: BinOp::And, .. }
945        ));
946        assert!(matches!(
947            a.clone().or(b),
948            Expr::Binary { op: BinOp::Or, .. }
949        ));
950    }
951
952    #[test]
953    fn expr_unary_ops() {
954        let x = Expr::num(1);
955        assert!(matches!(
956            x.clone().not(),
957            Expr::Unary {
958                op: UnaryOp::Not,
959                ..
960            }
961        ));
962        assert!(matches!(
963            x.clone().neg(),
964            Expr::Unary {
965                op: UnaryOp::Neg,
966                ..
967            }
968        ));
969        assert!(matches!(
970            x.type_of(),
971            Expr::Unary {
972                op: UnaryOp::TypeOf,
973                ..
974            }
975        ));
976    }
977
978    #[test]
979    fn expr_ternary() {
980        let expr = Expr::bool(true).ternary(Expr::num(1), Expr::num(0));
981        assert!(matches!(expr, Expr::Ternary { .. }));
982    }
983
984    #[test]
985    fn expr_object() {
986        let expr = Expr::object(vec![("a", Expr::num(1)), ("b", Expr::num(2))]);
987        match expr {
988            Expr::Object(pairs) => assert_eq!(pairs.len(), 2),
989            _ => panic!("expected object"),
990        }
991    }
992
993    #[test]
994    fn expr_array() {
995        let expr = Expr::array(vec![Expr::num(1), Expr::num(2), Expr::num(3)]);
996        match expr {
997            Expr::Array(items) => assert_eq!(items.len(), 3),
998            _ => panic!("expected array"),
999        }
1000    }
1001
1002    #[test]
1003    fn expr_arrow() {
1004        let expr = Expr::arrow(&["x"], Expr::ident("x").unwrap()).unwrap();
1005        assert!(matches!(expr, Expr::Arrow { .. }));
1006    }
1007
1008    #[test]
1009    fn expr_arrow_block() {
1010        let expr =
1011            Expr::arrow_block(&["x"], vec![Stmt::ret_val(Expr::ident("x").unwrap())]).unwrap();
1012        assert!(matches!(expr, Expr::ArrowBlock { .. }));
1013    }
1014
1015    #[test]
1016    fn expr_arrow_invalid_param() {
1017        assert!(Expr::arrow(&["class"], Expr::null()).is_err());
1018        assert!(Expr::arrow_block(&["class"], vec![]).is_err());
1019    }
1020
1021    #[test]
1022    fn expr_assign() {
1023        let expr = Expr::ident("x").unwrap().assign(Expr::num(10));
1024        assert!(matches!(expr, Expr::Assign { .. }));
1025    }
1026
1027    #[test]
1028    fn expr_dot_invalid() {
1029        let result = Expr::ident("obj").unwrap().dot("class");
1030        assert!(result.is_err());
1031    }
1032
1033    // ========================================================================
1034    // Stmt Builder Tests
1035    // ========================================================================
1036
1037    #[test]
1038    fn stmt_let_const_assign() {
1039        assert!(Stmt::let_decl("x", Expr::num(1)).is_ok());
1040        assert!(Stmt::const_decl("Y", Expr::num(2)).is_ok());
1041        assert!(Stmt::assign("z", Expr::num(3)).is_ok());
1042
1043        // Invalid identifiers
1044        assert!(Stmt::let_decl("class", Expr::num(1)).is_err());
1045        assert!(Stmt::const_decl("class", Expr::num(1)).is_err());
1046        assert!(Stmt::assign("class", Expr::num(1)).is_err());
1047    }
1048
1049    #[test]
1050    fn stmt_member_assign() {
1051        let stmt = Stmt::member_assign(Expr::this(), "prop", Expr::num(42)).unwrap();
1052        assert!(matches!(stmt, Stmt::MemberAssign { .. }));
1053
1054        // Invalid member
1055        assert!(Stmt::member_assign(Expr::this(), "class", Expr::null()).is_err());
1056    }
1057
1058    #[test]
1059    fn stmt_add_assign() {
1060        let stmt = Stmt::add_assign(Expr::ident("x").unwrap(), Expr::num(1));
1061        assert!(matches!(stmt, Stmt::AddAssign { .. }));
1062    }
1063
1064    #[test]
1065    fn stmt_post_increment() {
1066        let stmt = Stmt::post_increment(Expr::ident("i").unwrap());
1067        assert!(matches!(stmt, Stmt::PostIncrement(_)));
1068    }
1069
1070    #[test]
1071    fn stmt_return() {
1072        assert!(matches!(Stmt::ret(), Stmt::Return(None)));
1073        assert!(matches!(Stmt::ret_val(Expr::num(0)), Stmt::Return(Some(_))));
1074    }
1075
1076    #[test]
1077    fn stmt_if() {
1078        let stmt = Stmt::if_then(Expr::bool(true), vec![Stmt::ret()]);
1079        match stmt {
1080            Stmt::If { else_branch, .. } => assert!(else_branch.is_none()),
1081            _ => panic!("expected if"),
1082        }
1083
1084        let stmt = Stmt::if_else(Expr::bool(true), vec![], vec![]);
1085        match stmt {
1086            Stmt::If { else_branch, .. } => assert!(else_branch.is_some()),
1087            _ => panic!("expected if"),
1088        }
1089    }
1090
1091    #[test]
1092    fn stmt_for_loop() {
1093        let stmt = Stmt::for_loop("i", Expr::num(0), Expr::num(10), vec![]).unwrap();
1094        assert!(matches!(stmt, Stmt::For { .. }));
1095
1096        // Invalid var
1097        assert!(Stmt::for_loop("class", Expr::num(0), Expr::num(10), vec![]).is_err());
1098    }
1099
1100    #[test]
1101    fn stmt_while_loop() {
1102        let stmt = Stmt::while_loop(Expr::bool(true), vec![]);
1103        assert!(matches!(stmt, Stmt::While { .. }));
1104    }
1105
1106    #[test]
1107    fn stmt_try_catch() {
1108        let stmt = Stmt::try_catch(vec![], "e", vec![]).unwrap();
1109        assert!(matches!(stmt, Stmt::TryCatch { .. }));
1110
1111        // Invalid catch var
1112        assert!(Stmt::try_catch(vec![], "class", vec![]).is_err());
1113    }
1114
1115    #[test]
1116    fn stmt_comment() {
1117        let stmt = Stmt::comment("test comment");
1118        assert!(matches!(stmt, Stmt::Comment(_)));
1119    }
1120
1121    #[test]
1122    fn stmt_on_message() {
1123        let stmt = Stmt::on_message(vec![]);
1124        assert!(matches!(stmt, Stmt::OnMessage(_)));
1125    }
1126
1127    #[test]
1128    fn stmt_register_processor() {
1129        let stmt = Stmt::register_processor("my-proc", "MyProc").unwrap();
1130        assert!(matches!(stmt, Stmt::RegisterProcessor { .. }));
1131
1132        // Invalid class name
1133        assert!(Stmt::register_processor("proc", "class").is_err());
1134    }
1135
1136    #[test]
1137    fn identifier_validation() {
1138        assert!(Identifier::new("validName").is_ok());
1139        assert!(Identifier::new("class").is_err());
1140        assert!(Identifier::new("123invalid").is_err());
1141    }
1142}