fob_gen/
builder.rs

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