fob_gen/
program_builder.rs

1//! Comprehensive program builder for JavaScript code generation
2
3use crate::error::Result;
4use crate::format::FormatOptions;
5use oxc_allocator::Allocator;
6use oxc_ast::ast::*;
7use oxc_ast::{AstBuilder, NONE};
8use oxc_codegen::Codegen;
9use oxc_span::{Atom, SPAN, SourceType};
10use std::io::Write;
11
12/// Comprehensive program builder for JavaScript code generation.
13///
14/// This builder provides a high-level, ergonomic API for generating JavaScript code,
15/// combining statement-level construction with whole-program generation.
16pub struct ProgramBuilder<'a> {
17    ast: AstBuilder<'a>,
18    body: Vec<Statement<'a>>,
19    source_type: SourceType,
20}
21
22impl<'a> ProgramBuilder<'a> {
23    /// Create a new program builder
24    pub fn new(allocator: &'a Allocator) -> Self {
25        Self {
26            ast: AstBuilder::new(allocator),
27            body: Vec::new(),
28            source_type: SourceType::mjs(),
29        }
30    }
31
32    /// Create a new program builder with specific source type
33    pub fn with_source_type(allocator: &'a Allocator, source_type: SourceType) -> Self {
34        Self {
35            ast: AstBuilder::new(allocator),
36            body: Vec::new(),
37            source_type,
38        }
39    }
40
41    /// Get the underlying allocator (for advanced usage)
42    pub fn allocator(&self) -> &'a Allocator {
43        self.ast.allocator
44    }
45
46    /// Get the underlying AstBuilder (for advanced usage)
47    pub fn ast(&self) -> &AstBuilder<'a> {
48        &self.ast
49    }
50
51    // ===== CORE PRIMITIVES =====
52
53    /// Create an identifier expression: `name`
54    pub fn ident(&self, name: impl Into<Atom<'a>>) -> Expression<'a> {
55        self.ast.expression_identifier(SPAN, name)
56    }
57
58    /// Create a string literal: `"value"`
59    pub fn string(&self, value: impl Into<Atom<'a>>) -> Expression<'a> {
60        self.ast.expression_string_literal(SPAN, value, None)
61    }
62
63    /// Create a number literal: `42`
64    pub fn number(&self, value: f64) -> Expression<'a> {
65        self.ast
66            .expression_numeric_literal(SPAN, value, None, NumberBase::Decimal)
67    }
68
69    /// Create null: `null`
70    pub fn null(&self) -> Expression<'a> {
71        self.ast.expression_null_literal(SPAN)
72    }
73
74    /// Create boolean: `true` or `false`
75    pub fn bool(&self, value: bool) -> Expression<'a> {
76        self.ast.expression_boolean_literal(SPAN, value)
77    }
78
79    // ===== COMPLEX EXPRESSIONS =====
80
81    /// Create a member expression: `obj.prop`
82    pub fn member(&self, object: Expression<'a>, property: impl Into<Atom<'a>>) -> Expression<'a> {
83        let prop_name = self.ast.identifier_name(SPAN, property);
84        let member = self
85            .ast
86            .member_expression_static(SPAN, object, prop_name, false);
87        Expression::from(member)
88    }
89
90    /// Create a computed member expression: `obj[expr]`
91    ///
92    /// This is critical for array indexing: `arr[0]`, `pageRoutes[idx]`, etc.
93    pub fn computed_member(&self, object: Expression<'a>, index: Expression<'a>) -> Expression<'a> {
94        let member = self
95            .ast
96            .member_expression_computed(SPAN, object, index, false);
97        Expression::from(member)
98    }
99
100    /// Create a call expression: `callee(args...)`
101    pub fn call(&self, callee: Expression<'a>, args: Vec<Argument<'a>>) -> Expression<'a> {
102        let args_vec = self.ast.vec_from_iter(args);
103        let call = self
104            .ast
105            .call_expression(SPAN, callee, NONE, args_vec, false);
106        Expression::CallExpression(self.ast.alloc(call))
107    }
108
109    /// Create an argument from an expression
110    pub fn arg(&self, expr: Expression<'a>) -> Argument<'a> {
111        Argument::from(expr)
112    }
113
114    /// Create an object expression: `{ key1: value1, key2: value2 }`
115    pub fn object(&self, props: Vec<ObjectPropertyKind<'a>>) -> Expression<'a> {
116        let props_vec = self.ast.vec_from_iter(props);
117        self.ast.expression_object(SPAN, props_vec)
118    }
119
120    /// Create an object property: `key: value`
121    pub fn prop(&self, key: impl Into<Atom<'a>>, value: Expression<'a>) -> ObjectPropertyKind<'a> {
122        let key_name = self.ast.identifier_name(SPAN, key);
123        let property = self.ast.object_property(
124            SPAN,
125            PropertyKind::Init,
126            PropertyKey::StaticIdentifier(self.ast.alloc(key_name)),
127            value,
128            false,
129            false,
130            false,
131        );
132        ObjectPropertyKind::ObjectProperty(self.ast.alloc(property))
133    }
134
135    /// Create an array expression: `[elem1, elem2, ...]`
136    pub fn array(&self, elements: Vec<Expression<'a>>) -> Expression<'a> {
137        let array_elements: Vec<_> = elements
138            .into_iter()
139            .map(ArrayExpressionElement::from)
140            .collect();
141        let elements_vec = self.ast.vec_from_iter(array_elements);
142        self.ast.expression_array(SPAN, elements_vec)
143    }
144
145    /// Create an arrow function with expression body: `(params) => expr`
146    pub fn arrow_fn(&self, params: Vec<&'a str>, body: Expression<'a>) -> Expression<'a> {
147        let param_items: Vec<_> = params
148            .into_iter()
149            .map(|name| {
150                let pattern = self.ast.binding_pattern(
151                    self.ast.binding_pattern_kind_binding_identifier(SPAN, name),
152                    NONE,
153                    false,
154                );
155                self.ast
156                    .formal_parameter(SPAN, self.ast.vec(), pattern, None, false, false)
157            })
158            .collect();
159
160        let items_vec = self.ast.vec_from_iter(param_items);
161        let formal_params = self.ast.formal_parameters(
162            SPAN,
163            FormalParameterKind::ArrowFormalParameters,
164            items_vec,
165            NONE,
166        );
167
168        // Create a FunctionBody with just an expression (return statement)
169        let return_stmt = self.ast.statement_return(SPAN, Some(body));
170        let stmts = self.ast.vec1(return_stmt);
171        let function_body = self.ast.function_body(SPAN, self.ast.vec(), stmts);
172
173        self.ast.expression_arrow_function(
174            SPAN,
175            false, // expression = false because we're using a block
176            false, // async
177            NONE,
178            formal_params,
179            NONE,
180            self.ast.alloc(function_body),
181        )
182    }
183
184    /// Create an arrow function with block body: `(params) => { stmts }`
185    pub fn arrow_fn_block(
186        &self,
187        params: Vec<&'a str>,
188        stmts: Vec<Statement<'a>>,
189    ) -> Expression<'a> {
190        let param_items: Vec<_> = params
191            .into_iter()
192            .map(|name| {
193                let pattern = self.ast.binding_pattern(
194                    self.ast.binding_pattern_kind_binding_identifier(SPAN, name),
195                    NONE,
196                    false,
197                );
198                self.ast
199                    .formal_parameter(SPAN, self.ast.vec(), pattern, None, false, false)
200            })
201            .collect();
202
203        let items_vec = self.ast.vec_from_iter(param_items);
204        let formal_params = self.ast.formal_parameters(
205            SPAN,
206            FormalParameterKind::ArrowFormalParameters,
207            items_vec,
208            NONE,
209        );
210
211        let body_stmts = self.ast.vec_from_iter(stmts);
212        let function_body = self.ast.function_body(SPAN, self.ast.vec(), body_stmts);
213
214        self.ast.expression_arrow_function(
215            SPAN,
216            false, // block body (not expression)
217            false, // async
218            Option::<TSTypeParameterDeclaration>::None,
219            formal_params,
220            Option::<TSTypeAnnotation>::None,
221            self.ast.alloc(function_body),
222        )
223    }
224
225    // ===== STATEMENTS =====
226
227    /// Create a const declaration: `const name = init;`
228    pub fn const_decl(&self, name: impl Into<Atom<'a>>, init: Expression<'a>) -> Statement<'a> {
229        let pattern = self.ast.binding_pattern(
230            self.ast.binding_pattern_kind_binding_identifier(SPAN, name),
231            NONE,
232            false,
233        );
234        let declarator = self.ast.variable_declarator(
235            SPAN,
236            VariableDeclarationKind::Const,
237            pattern,
238            Some(init),
239            false,
240        );
241        let declarations = self.ast.vec1(declarator);
242        let var_decl = self.ast.variable_declaration(
243            SPAN,
244            VariableDeclarationKind::Const,
245            declarations,
246            false,
247        );
248        Statement::VariableDeclaration(self.ast.alloc(var_decl))
249    }
250
251    /// Create a let declaration: `let name = init;`
252    pub fn let_decl(
253        &self,
254        name: impl Into<Atom<'a>>,
255        init: Option<Expression<'a>>,
256    ) -> Statement<'a> {
257        let pattern = self.ast.binding_pattern(
258            self.ast.binding_pattern_kind_binding_identifier(SPAN, name),
259            NONE,
260            false,
261        );
262        let declarator =
263            self.ast
264                .variable_declarator(SPAN, VariableDeclarationKind::Let, pattern, init, false);
265        let declarations = self.ast.vec1(declarator);
266        let var_decl =
267            self.ast
268                .variable_declaration(SPAN, VariableDeclarationKind::Let, declarations, false);
269        Statement::VariableDeclaration(self.ast.alloc(var_decl))
270    }
271
272    /// Create an expression statement
273    pub fn expr_stmt(&self, expr: Expression<'a>) -> Statement<'a> {
274        self.ast.statement_expression(SPAN, expr)
275    }
276
277    /// Create an if statement: `if (test) { consequent } else { alternate }`
278    pub fn if_stmt(
279        &self,
280        test: Expression<'a>,
281        consequent: Vec<Statement<'a>>,
282        alternate: Option<Vec<Statement<'a>>>,
283    ) -> Statement<'a> {
284        let consequent_stmts = self.ast.vec_from_iter(consequent);
285        let consequent_block = self.ast.statement_block(SPAN, consequent_stmts);
286
287        let alternate_block = alternate.map(|stmts| {
288            let alt_stmts = self.ast.vec_from_iter(stmts);
289            self.ast.statement_block(SPAN, alt_stmts)
290        });
291
292        Statement::IfStatement(self.ast.alloc(IfStatement {
293            span: SPAN,
294            test,
295            consequent: consequent_block,
296            alternate: alternate_block,
297        }))
298    }
299
300    /// Create a return statement: `return expr;`
301    pub fn return_stmt(&self, expr: Option<Expression<'a>>) -> Statement<'a> {
302        Statement::ReturnStatement(self.ast.alloc(ReturnStatement {
303            span: SPAN,
304            argument: expr,
305        }))
306    }
307
308    /// Create a throw statement: `throw expr;`
309    pub fn throw(&self, expr: Expression<'a>) -> Statement<'a> {
310        Statement::ThrowStatement(self.ast.alloc(ThrowStatement {
311            span: SPAN,
312            argument: expr,
313        }))
314    }
315
316    // ===== IMPORTS & EXPORTS =====
317
318    /// Create default import: `import local from 'source';`
319    pub fn import_default(
320        &self,
321        local: impl Into<Atom<'a>>,
322        source: impl Into<Atom<'a>>,
323    ) -> ModuleDeclaration<'a> {
324        let binding = self.ast.binding_identifier(SPAN, local);
325        let specifier = self.ast.import_default_specifier(SPAN, binding);
326        let specifiers = self
327            .ast
328            .vec1(ImportDeclarationSpecifier::ImportDefaultSpecifier(
329                self.ast.alloc(specifier),
330            ));
331        let source_literal = self.ast.string_literal(SPAN, source, None);
332        self.ast.module_declaration_import_declaration(
333            SPAN,
334            Some(specifiers),
335            source_literal,
336            None, // phase
337            NONE, // with_clause
338            ImportOrExportKind::Value,
339        )
340    }
341
342    /// Create side-effect import: `import 'source';`
343    pub fn import_side_effect(&self, source: impl Into<Atom<'a>>) -> ModuleDeclaration<'a> {
344        let source_literal = self.ast.string_literal(SPAN, source, None);
345        self.ast.module_declaration_import_declaration(
346            SPAN,
347            None, // no specifiers for side-effect imports
348            source_literal,
349            None, // phase
350            NONE, // with_clause
351            ImportOrExportKind::Value,
352        )
353    }
354
355    /// Create named imports: `import { name1, name2 } from 'source';`
356    pub fn import_named(
357        &self,
358        names: Vec<impl Into<Atom<'a>>>,
359        source: impl Into<Atom<'a>>,
360    ) -> ModuleDeclaration<'a> {
361        let specifiers: Vec<_> = names
362            .into_iter()
363            .map(|name| {
364                let atom = name.into();
365                let imported_name = self.ast.identifier_name(SPAN, atom);
366                let local_binding = self.ast.binding_identifier(SPAN, atom);
367                let specifier = self.ast.import_specifier(
368                    SPAN,
369                    ModuleExportName::IdentifierName(imported_name),
370                    local_binding,
371                    ImportOrExportKind::Value,
372                );
373                ImportDeclarationSpecifier::ImportSpecifier(self.ast.alloc(specifier))
374            })
375            .collect();
376
377        let specifiers_vec = self.ast.vec_from_iter(specifiers);
378        let source_literal = self.ast.string_literal(SPAN, source, None);
379        self.ast.module_declaration_import_declaration(
380            SPAN,
381            Some(specifiers_vec),
382            source_literal,
383            None, // phase
384            NONE, // with_clause
385            ImportOrExportKind::Value,
386        )
387    }
388
389    /// Create default export: `export default expr;`
390    pub fn export_default(&self, expr: Expression<'a>) -> ModuleDeclaration<'a> {
391        // ExportDefaultDeclarationKind inherits Expression variants, so we can convert
392        let kind: ExportDefaultDeclarationKind = expr.into();
393        self.ast
394            .module_declaration_export_default_declaration(SPAN, kind)
395    }
396
397    /// Create named export of variable: `export const name = init;`
398    pub fn export_const(
399        &self,
400        name: impl Into<Atom<'a>>,
401        init: Expression<'a>,
402    ) -> ModuleDeclaration<'a> {
403        let decl = self.const_decl(name, init);
404        let declaration = match decl {
405            Statement::VariableDeclaration(var_decl) => Declaration::VariableDeclaration(var_decl),
406            _ => unreachable!(),
407        };
408        ModuleDeclaration::ExportNamedDeclaration(self.ast.alloc(ExportNamedDeclaration {
409            span: SPAN,
410            declaration: Some(declaration),
411            specifiers: self.ast.vec(),
412            source: None,
413            export_kind: ImportOrExportKind::Value,
414            with_clause: None,
415        }))
416    }
417
418    // ===== PROGRAM-LEVEL OPERATIONS =====
419
420    /// Add a statement to the program
421    pub fn push(&mut self, stmt: Statement<'a>) {
422        self.body.push(stmt);
423    }
424
425    /// Add multiple statements to the program
426    pub fn extend(&mut self, stmts: impl IntoIterator<Item = Statement<'a>>) {
427        self.body.extend(stmts);
428    }
429
430    /// Get the current number of statements
431    pub fn len(&self) -> usize {
432        self.body.len()
433    }
434
435    /// Check if the builder is empty
436    pub fn is_empty(&self) -> bool {
437        self.body.is_empty()
438    }
439
440    /// Write the program to a writer with formatting options
441    ///
442    /// Consumes the builder since statements are moved into the program.
443    pub fn write_to<W: Write>(self, writer: &mut W, _opts: &FormatOptions) -> Result<()> {
444        let body_vec = self.ast.vec_from_iter(self.body);
445        let program = self.ast.program(
446            SPAN,
447            self.source_type,
448            "",
449            self.ast.vec(), // imports/exports
450            None,           // hashbang
451            self.ast.vec(), // directives
452            body_vec,
453        );
454
455        let codegen = Codegen::new();
456        let result = codegen.build(&program);
457
458        writer.write_all(result.code.as_bytes()).map_err(|e| {
459            crate::error::GenError::CodegenFailed {
460                context: "Write error".to_string(),
461                reason: Some(e.to_string()),
462            }
463        })?;
464
465        Ok(())
466    }
467
468    /// Generate the complete program as a string
469    ///
470    /// Consumes the builder since statements are moved into the program.
471    pub fn generate(self, _opts: &FormatOptions) -> Result<String> {
472        let body_vec = self.ast.vec_from_iter(self.body);
473        let program = self.ast.program(
474            SPAN,
475            self.source_type,
476            "",
477            self.ast.vec(), // imports/exports
478            None,           // hashbang
479            self.ast.vec(), // directives
480            body_vec,
481        );
482
483        let codegen = Codegen::new();
484        let result = codegen.build(&program);
485
486        Ok(result.code)
487    }
488
489    /// Build the program AST (for advanced usage)
490    ///
491    /// Consumes the builder since statements are moved into the program.
492    pub fn build_program(self) -> Program<'a> {
493        let body_vec = self.ast.vec_from_iter(self.body);
494        self.ast.program(
495            SPAN,
496            self.source_type,
497            "",
498            self.ast.vec(), // imports/exports
499            None,           // hashbang
500            self.ast.vec(), // directives
501            body_vec,
502        )
503    }
504
505    // ===== HELPER METHODS =====
506
507    /// Unary not operator: `!expr`
508    pub fn not(&self, expr: Expression<'a>) -> Expression<'a> {
509        self.ast
510            .expression_unary(SPAN, UnaryOperator::LogicalNot, expr)
511    }
512
513    /// Binary expression: `left op right`
514    pub fn binary(
515        &self,
516        left: Expression<'a>,
517        op: BinaryOperator,
518        right: Expression<'a>,
519    ) -> Expression<'a> {
520        self.ast.expression_binary(SPAN, left, op, right)
521    }
522
523    /// Logical expression: `left && right` or `left || right`
524    pub fn logical(
525        &self,
526        left: Expression<'a>,
527        op: LogicalOperator,
528        right: Expression<'a>,
529    ) -> Expression<'a> {
530        self.ast.expression_logical(SPAN, left, op, right)
531    }
532
533    /// Conditional/ternary expression: `test ? consequent : alternate`
534    pub fn conditional(
535        &self,
536        test: Expression<'a>,
537        consequent: Expression<'a>,
538        alternate: Expression<'a>,
539    ) -> Expression<'a> {
540        self.ast
541            .expression_conditional(SPAN, test, consequent, alternate)
542    }
543
544    /// New expression: `new Ctor(args)`
545    pub fn new_expr(&self, callee: Expression<'a>, args: Vec<Argument<'a>>) -> Expression<'a> {
546        let args_vec = self.ast.vec_from_iter(args);
547        self.ast.expression_new(SPAN, callee, NONE, args_vec)
548    }
549
550    /// Template literal: backtick string with expressions
551    /// For simple strings without interpolation, use `string()` instead
552    ///
553    /// # Example
554    /// ```ignore
555    /// // Generates: `Hello, ${name}!`
556    /// js.template_literal(
557    ///     vec!["Hello, ", "!"],
558    ///     vec![js.ident("name")],
559    /// )
560    /// ```
561    pub fn template_literal(
562        &self,
563        parts: Vec<impl Into<Atom<'a>>>,
564        expressions: Vec<Expression<'a>>,
565    ) -> Expression<'a> {
566        // In `a${x}b${y}c`, parts = ["a", "b", "c"], expressions = [x, y]
567        // The last quasi (index == expressions.len()) has tail = true
568        let quasis: Vec<_> = parts
569            .into_iter()
570            .enumerate()
571            .map(|(i, part)| {
572                let atom = part.into();
573                let tail = i == expressions.len(); // Last quasi element
574                let value = TemplateElementValue {
575                    raw: atom,
576                    cooked: Some(atom),
577                };
578                TemplateElement {
579                    span: SPAN,
580                    value,
581                    tail,
582                    lone_surrogates: false,
583                }
584            })
585            .collect();
586
587        let quasis_vec = self.ast.vec_from_iter(quasis);
588        let expressions_vec = self.ast.vec_from_iter(expressions);
589
590        self.ast
591            .expression_template_literal(SPAN, quasis_vec, expressions_vec)
592    }
593
594    /// Spread element for arrays or function calls: `...expr`
595    ///
596    /// # Example
597    /// ```ignore
598    /// // In array: [...items]
599    /// js.array(vec![js.spread(js.ident("items"))])
600    /// ```
601    pub fn spread(&self, expr: Expression<'a>) -> SpreadElement<'a> {
602        SpreadElement {
603            span: SPAN,
604            argument: expr,
605        }
606    }
607}