Skip to main content

oxc_plugin_worklets/
lib.rs

1mod autoworkletization;
2mod closure;
3mod gesture_handler_autoworkletization;
4mod globals;
5mod layout_animation_autoworkletization;
6mod options;
7mod types;
8mod worklet_factory;
9use std::collections::HashSet;
10
11pub use options::PluginOptions;
12
13use oxc::allocator::{Allocator, Box as OxcBox, CloneIn};
14use oxc::ast::ast::*;
15use oxc::ast::AstBuilder;
16use oxc::codegen::{Codegen, CodegenOptions};
17use oxc::span::{SourceType, SPAN};
18
19use crate::autoworkletization::{
20    get_args_to_workletize, is_reanimated_function_hook, is_reanimated_object_hook,
21};
22use crate::closure::{get_closure, get_closure_arrow};
23use crate::gesture_handler_autoworkletization::is_gesture_object_event_callback_method;
24use crate::globals::build_globals;
25use crate::layout_animation_autoworkletization::is_layout_animation_callback_method;
26use crate::types::{
27    CONTEXT_OBJECT_MARKER, PLUGIN_VERSION, WORKLET_CLASS_FACTORY_SUFFIX, WORKLET_CLASS_MARKER,
28};
29use crate::worklet_factory::{hash, make_worklet_name};
30
31#[derive(Debug)]
32pub struct WorkletsError(pub String);
33
34impl std::fmt::Display for WorkletsError {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        write!(f, "Transform error: {}", self.0)
37    }
38}
39
40impl std::error::Error for WorkletsError {}
41
42pub struct WorkletsVisitor<'a> {
43    allocator: &'a Allocator,
44    options: PluginOptions,
45    globals: HashSet<String>,
46    filename: String,
47    worklet_number: u32,
48    pending_insertions: Vec<(usize, Statement<'a>)>,
49}
50
51impl<'a> WorkletsVisitor<'a> {
52    pub fn new(allocator: &'a Allocator, options: PluginOptions) -> Self {
53        let globals = if options.strict_global {
54            HashSet::new()
55        } else {
56            build_globals(&options.globals)
57        };
58        let filename = options
59            .filename
60            .as_deref()
61            .unwrap_or("/dev/null")
62            .to_string();
63        Self {
64            allocator,
65            options,
66            globals,
67            filename,
68            worklet_number: 1,
69            pending_insertions: Vec::new(),
70        }
71    }
72
73    pub fn visit_program(&mut self, program: &mut Program<'a>) -> Result<(), WorkletsError> {
74        // Check for file-level 'worklet' directive
75        let has_file_worklet = program
76            .directives
77            .iter()
78            .any(|d| d.expression.value == "worklet");
79
80        if has_file_worklet {
81            program
82                .directives
83                .retain(|d| d.expression.value != "worklet");
84            add_worklet_directives_to_top_level(program, self.allocator);
85        }
86
87        // Pre-pass: collect names that are passed as identifier arguments to auto-workletize hooks,
88        // then add 'worklet' directives to the referenced definitions.
89        let names_to_workletize = collect_referenced_worklet_names(&program.body);
90        if !names_to_workletize.is_empty() {
91            add_worklet_directives_to_referenced(program, self.allocator, &names_to_workletize);
92        }
93
94        // Process context objects (ObjectExpressions with __workletContextObject marker)
95        process_context_objects(program, self.allocator);
96
97        let mut i = 0;
98        while i < program.body.len() {
99            process_statement(&mut program.body[i], i, self)?;
100            i += 1;
101        }
102
103        // Insert pending init_data declarations (sorted descending to maintain indices)
104        if !self.pending_insertions.is_empty() {
105            self.pending_insertions.sort_by(|a, b| b.0.cmp(&a.0));
106            let insertions = std::mem::take(&mut self.pending_insertions);
107            for (idx, stmt) in insertions {
108                program.body.insert(idx, stmt);
109            }
110        }
111
112        Ok(())
113    }
114}
115
116// --- Statement processing ---
117
118fn process_statement<'a>(
119    stmt: &mut Statement<'a>,
120    stmt_idx: usize,
121    ctx: &mut WorkletsVisitor<'a>,
122) -> Result<(), WorkletsError> {
123    match stmt {
124        Statement::FunctionDeclaration(func) => {
125            if has_worklet_directive(func.body.as_ref()) {
126                process_inner_worklets_in_function(func, stmt_idx, ctx)?;
127                let replacement = transform_worklet_function(func, stmt_idx, ctx)?;
128                *stmt = replacement;
129            }
130        }
131        Statement::VariableDeclaration(var_decl) => {
132            for declarator in var_decl.declarations.iter_mut() {
133                if let Some(init) = &mut declarator.init {
134                    process_expression(init, stmt_idx, ctx)?;
135                }
136            }
137        }
138        Statement::ExpressionStatement(expr_stmt) => {
139            process_expression(&mut expr_stmt.expression, stmt_idx, ctx)?;
140        }
141        Statement::ClassDeclaration(class) => {
142            if !ctx.options.disable_worklet_classes && is_worklet_class(class) {
143                let replacement = transform_worklet_class(class, stmt_idx, ctx)?;
144                let ast = AstBuilder::new(ctx.allocator);
145                let mut stmts = ast.vec_from_iter(replacement);
146                // Replace the current statement with the first one,
147                // we'll need to handle the second one differently
148                if stmts.len() == 2 {
149                    let second = stmts.pop().unwrap();
150                    *stmt = stmts.pop().unwrap();
151                    // We need to insert the second statement after the current one.
152                    // Use a special approach: store it as a pending insertion at stmt_idx + 1
153                    ctx.pending_insertions.push((stmt_idx + 1, second));
154                } else if stmts.len() == 1 {
155                    *stmt = stmts.pop().unwrap();
156                }
157            }
158        }
159        Statement::ExportDefaultDeclaration(export) => {
160            if let ExportDefaultDeclarationKind::FunctionDeclaration(func) = &mut export.declaration
161            {
162                if has_worklet_directive(func.body.as_ref()) {
163                    process_inner_worklets_in_function(func, stmt_idx, ctx)?;
164                    let func_name = func.id.as_ref().map(|id| id.name.as_str());
165                    let factory_call = build_factory_call(func, func_name, stmt_idx, ctx)?;
166                    let ast = AstBuilder::new(ctx.allocator);
167                    let call_expr = Expression::CallExpression(ast.alloc(factory_call));
168                    export.declaration = ExportDefaultDeclarationKind::from(call_expr);
169                }
170            }
171        }
172        Statement::ExportNamedDeclaration(export) => {
173            if let Some(decl) = &mut export.declaration {
174                match decl {
175                    Declaration::FunctionDeclaration(func) => {
176                        if has_worklet_directive(func.body.as_ref()) {
177                            process_inner_worklets_in_function(func, stmt_idx, ctx)?;
178                            let func_name = func.id.as_ref().map(|id| id.name.as_str());
179                            let name: &'a str = func
180                                .id
181                                .as_ref()
182                                .map(|id| id.name.as_str())
183                                .unwrap_or("_unnamed");
184                            let factory_call = build_factory_call(func, func_name, stmt_idx, ctx)?;
185                            let ast = AstBuilder::new(ctx.allocator);
186                            let call_expr = Expression::CallExpression(ast.alloc(factory_call));
187                            let var_decl = build_const_declaration(&ast, name, call_expr);
188                            *decl = Declaration::VariableDeclaration(ast.alloc(var_decl));
189                        }
190                    }
191                    Declaration::VariableDeclaration(var_decl) => {
192                        for declarator in var_decl.declarations.iter_mut() {
193                            if let Some(init) = &mut declarator.init {
194                                process_expression(init, stmt_idx, ctx)?;
195                            }
196                        }
197                    }
198                    _ => {}
199                }
200            }
201        }
202        _ => {}
203    }
204    Ok(())
205}
206
207fn process_expression<'a>(
208    expr: &mut Expression<'a>,
209    stmt_idx: usize,
210    ctx: &mut WorkletsVisitor<'a>,
211) -> Result<(), WorkletsError> {
212    match expr {
213        Expression::ArrowFunctionExpression(arrow) => {
214            if has_worklet_directive_fn_body(&arrow.body) {
215                process_inner_worklets_in_arrow(arrow, stmt_idx, ctx)?;
216                let replacement = transform_worklet_arrow(arrow, stmt_idx, ctx)?;
217                *expr = replacement;
218                return Ok(());
219            }
220            process_inner_worklets_in_arrow(arrow, stmt_idx, ctx)?;
221        }
222        Expression::FunctionExpression(func) => {
223            if has_worklet_directive(func.body.as_ref()) {
224                process_inner_worklets_in_function(func, stmt_idx, ctx)?;
225                let func_name = func.id.as_ref().map(|id| id.name.as_str());
226                let factory_call = build_factory_call(func, func_name, stmt_idx, ctx)?;
227                let ast = AstBuilder::new(ctx.allocator);
228                *expr = Expression::CallExpression(ast.alloc(factory_call));
229                return Ok(());
230            }
231            process_inner_worklets_in_function(func, stmt_idx, ctx)?;
232        }
233        Expression::CallExpression(call) => {
234            process_call_expression(call, stmt_idx, ctx)?;
235        }
236        Expression::AssignmentExpression(assign) => {
237            process_expression(&mut assign.right, stmt_idx, ctx)?;
238        }
239        Expression::SequenceExpression(seq) => {
240            for inner in seq.expressions.iter_mut() {
241                process_expression(inner, stmt_idx, ctx)?;
242            }
243        }
244        Expression::ConditionalExpression(cond) => {
245            process_expression(&mut cond.consequent, stmt_idx, ctx)?;
246            process_expression(&mut cond.alternate, stmt_idx, ctx)?;
247        }
248        Expression::LogicalExpression(logical) => {
249            process_expression(&mut logical.right, stmt_idx, ctx)?;
250        }
251        Expression::ObjectExpression(obj) => {
252            for prop in obj.properties.iter_mut() {
253                if let ObjectPropertyKind::ObjectProperty(p) = prop {
254                    process_expression(&mut p.value, stmt_idx, ctx)?;
255                }
256            }
257        }
258        _ => {}
259    }
260    Ok(())
261}
262
263fn process_call_expression<'a>(
264    call: &mut CallExpression<'a>,
265    stmt_idx: usize,
266    ctx: &mut WorkletsVisitor<'a>,
267) -> Result<(), WorkletsError> {
268    let callee_name = get_callee_name(&call.callee).map(|s| s.to_string());
269    let mut handled = false;
270
271    if let Some(ref name) = callee_name {
272        let is_func = is_reanimated_function_hook(name);
273        let is_obj = is_reanimated_object_hook(name);
274
275        if is_func || is_obj {
276            if let Some(arg_indices) = get_args_to_workletize(name) {
277                let indices: Vec<usize> = arg_indices.to_vec();
278                for idx in indices {
279                    if idx < call.arguments.len() {
280                        process_autoworkletize_arg(
281                            &mut call.arguments[idx],
282                            stmt_idx,
283                            ctx,
284                            is_func,
285                            is_obj,
286                        )?;
287                    }
288                }
289                handled = true;
290            }
291        }
292    }
293
294    if !handled && is_gesture_object_event_callback_method(&call.callee) {
295        for arg in call.arguments.iter_mut() {
296            process_autoworkletize_arg(arg, stmt_idx, ctx, true, true)?;
297        }
298    }
299
300    if is_layout_animation_callback_method(&call.callee) {
301        for arg in call.arguments.iter_mut() {
302            process_autoworkletize_arg(arg, stmt_idx, ctx, true, false)?;
303        }
304    }
305
306    // Recurse into callee for chained calls
307    if let Expression::CallExpression(callee_call) = &mut call.callee {
308        process_call_expression(callee_call, stmt_idx, ctx)?;
309    }
310
311    // Recurse into arguments for nested calls
312    for arg in call.arguments.iter_mut() {
313        if let Argument::CallExpression(inner) = arg {
314            process_call_expression(inner, stmt_idx, ctx)?;
315        }
316    }
317
318    Ok(())
319}
320
321fn process_autoworkletize_arg<'a>(
322    arg: &mut Argument<'a>,
323    stmt_idx: usize,
324    ctx: &mut WorkletsVisitor<'a>,
325    accept_function: bool,
326    accept_object: bool,
327) -> Result<(), WorkletsError> {
328    match arg {
329        Argument::ArrowFunctionExpression(arrow) if accept_function => {
330            process_inner_worklets_in_arrow(arrow, stmt_idx, ctx)?;
331            let replacement = transform_worklet_arrow(arrow, stmt_idx, ctx)?;
332            *arg = Argument::from(replacement);
333        }
334        Argument::FunctionExpression(func) if accept_function => {
335            process_inner_worklets_in_function(func, stmt_idx, ctx)?;
336            let func_name = func.id.as_ref().map(|id| id.name.as_str());
337            let factory_call = build_factory_call(func, func_name, stmt_idx, ctx)?;
338            let ast = AstBuilder::new(ctx.allocator);
339            *arg = Argument::from(Expression::CallExpression(ast.alloc(factory_call)));
340        }
341        Argument::ObjectExpression(obj) if accept_object => {
342            process_workletizable_object(obj, stmt_idx, ctx, accept_function)?;
343        }
344        _ => {}
345    }
346    Ok(())
347}
348
349fn get_callee_name<'a>(callee: &'a Expression<'a>) -> Option<&'a str> {
350    match callee {
351        Expression::Identifier(id) => Some(id.name.as_str()),
352        Expression::StaticMemberExpression(member) => Some(member.property.name.as_str()),
353        Expression::SequenceExpression(seq) => seq.expressions.last().and_then(get_callee_name),
354        _ => None,
355    }
356}
357
358fn has_worklet_directive(body: Option<&OxcBox<'_, FunctionBody<'_>>>) -> bool {
359    body.is_some_and(|b| has_worklet_directive_fn_body(b))
360}
361
362fn has_worklet_directive_fn_body(body: &FunctionBody) -> bool {
363    body.directives
364        .iter()
365        .any(|d| d.expression.value == "worklet")
366}
367
368// --- Inner worklet processing ---
369
370fn process_inner_worklets_in_function<'a>(
371    func: &mut Function<'a>,
372    stmt_idx: usize,
373    ctx: &mut WorkletsVisitor<'a>,
374) -> Result<(), WorkletsError> {
375    if let Some(body) = &mut func.body {
376        for stmt in body.statements.iter_mut() {
377            process_inner_stmt(stmt, stmt_idx, ctx)?;
378        }
379    }
380    Ok(())
381}
382
383fn process_inner_worklets_in_arrow<'a>(
384    arrow: &mut ArrowFunctionExpression<'a>,
385    stmt_idx: usize,
386    ctx: &mut WorkletsVisitor<'a>,
387) -> Result<(), WorkletsError> {
388    for stmt in arrow.body.statements.iter_mut() {
389        process_inner_stmt(stmt, stmt_idx, ctx)?;
390    }
391    Ok(())
392}
393
394fn process_inner_stmt<'a>(
395    stmt: &mut Statement<'a>,
396    stmt_idx: usize,
397    ctx: &mut WorkletsVisitor<'a>,
398) -> Result<(), WorkletsError> {
399    match stmt {
400        Statement::VariableDeclaration(v) => {
401            for d in v.declarations.iter_mut() {
402                if let Some(init) = &mut d.init {
403                    process_expression(init, stmt_idx, ctx)?;
404                }
405            }
406        }
407        Statement::ExpressionStatement(es) => {
408            process_expression(&mut es.expression, stmt_idx, ctx)?;
409        }
410        Statement::FunctionDeclaration(func) => {
411            if has_worklet_directive(func.body.as_ref()) {
412                process_inner_worklets_in_function(func, stmt_idx, ctx)?;
413                let func_name = func.id.as_ref().map(|id| id.name.as_str());
414                let factory_call = build_factory_call(func, func_name, stmt_idx, ctx)?;
415                let name: &'a str = func
416                    .id
417                    .as_ref()
418                    .map(|id| id.name.as_str())
419                    .unwrap_or("_unnamed");
420                let ast = AstBuilder::new(ctx.allocator);
421                let vd = build_const_declaration(
422                    &ast,
423                    name,
424                    Expression::CallExpression(ast.alloc(factory_call)),
425                );
426                *stmt = Statement::VariableDeclaration(ast.alloc(vd));
427            } else {
428                process_inner_worklets_in_function(func, stmt_idx, ctx)?;
429            }
430        }
431        Statement::ReturnStatement(ret) => {
432            if let Some(arg) = &mut ret.argument {
433                process_expression(arg, stmt_idx, ctx)?;
434            }
435        }
436        Statement::IfStatement(ifs) => {
437            process_inner_stmt(&mut ifs.consequent, stmt_idx, ctx)?;
438            if let Some(alt) = &mut ifs.alternate {
439                process_inner_stmt(alt, stmt_idx, ctx)?;
440            }
441        }
442        Statement::BlockStatement(block) => {
443            for s in block.body.iter_mut() {
444                process_inner_stmt(s, stmt_idx, ctx)?;
445            }
446        }
447        _ => {}
448    }
449    Ok(())
450}
451
452// --- Worklet transformation ---
453
454fn transform_worklet_function<'a>(
455    func: &mut Function<'a>,
456    stmt_idx: usize,
457    ctx: &mut WorkletsVisitor<'a>,
458) -> Result<Statement<'a>, WorkletsError> {
459    let func_name = func.id.as_ref().map(|id| id.name.as_str());
460    let factory_call = build_factory_call(func, func_name, stmt_idx, ctx)?;
461    let name: &'a str = func
462        .id
463        .as_ref()
464        .map(|id| id.name.as_str())
465        .unwrap_or("_unnamed");
466    let ast = AstBuilder::new(ctx.allocator);
467    let vd = build_const_declaration(
468        &ast,
469        name,
470        Expression::CallExpression(ast.alloc(factory_call)),
471    );
472    Ok(Statement::VariableDeclaration(ast.alloc(vd)))
473}
474
475fn transform_worklet_arrow<'a>(
476    arrow: &mut ArrowFunctionExpression<'a>,
477    stmt_idx: usize,
478    ctx: &mut WorkletsVisitor<'a>,
479) -> Result<Expression<'a>, WorkletsError> {
480    arrow
481        .body
482        .directives
483        .retain(|d| d.expression.value != "worklet");
484
485    let closure_vars = get_closure_arrow(arrow, &ctx.globals, ctx.options.strict_global);
486
487    let wn = ctx.worklet_number;
488    ctx.worklet_number += 1;
489    let (worklet_name, react_name) = make_worklet_name(None, &ctx.filename, wn);
490
491    let code_string =
492        generate_worklet_code_string_from_arrow(ctx.allocator, arrow, &worklet_name, &closure_vars);
493    let worklet_hash = hash(&code_string);
494
495    let ast = AstBuilder::new(ctx.allocator);
496    let params = arrow.params.clone_in(ctx.allocator);
497    let body = arrow.body.clone_in(ctx.allocator);
498
499    let func_expr = ast.alloc(ast.function(
500        SPAN,
501        FunctionType::FunctionExpression,
502        None::<BindingIdentifier>,
503        false,
504        arrow.r#async,
505        false,
506        None::<TSTypeParameterDeclaration>,
507        None::<TSThisParameter>,
508        params,
509        None::<TSTypeAnnotation>,
510        Some(body),
511    ));
512
513    // Arena-allocate the strings
514    let react_name = ctx.allocator.alloc_str(&react_name);
515    let worklet_name = ctx.allocator.alloc_str(&worklet_name);
516    let code_string = ctx.allocator.alloc_str(&code_string);
517
518    let factory_call = build_factory_call_inner(
519        ctx,
520        func_expr,
521        react_name,
522        worklet_name,
523        code_string,
524        worklet_hash,
525        &closure_vars,
526        stmt_idx,
527    )?;
528
529    let ast = AstBuilder::new(ctx.allocator);
530    Ok(Expression::CallExpression(ast.alloc(factory_call)))
531}
532
533fn build_factory_call<'a>(
534    func: &mut Function<'a>,
535    func_name: Option<&str>,
536    stmt_idx: usize,
537    ctx: &mut WorkletsVisitor<'a>,
538) -> Result<CallExpression<'a>, WorkletsError> {
539    if let Some(body) = &mut func.body {
540        body.directives.retain(|d| d.expression.value != "worklet");
541    }
542
543    let closure_vars = get_closure(func, &ctx.globals, ctx.options.strict_global);
544
545    let wn = ctx.worklet_number;
546    ctx.worklet_number += 1;
547    let (worklet_name, react_name) = make_worklet_name(func_name, &ctx.filename, wn);
548
549    let code_string = generate_worklet_code_string_from_function(
550        ctx.allocator,
551        func,
552        &worklet_name,
553        &closure_vars,
554        func_name,
555    );
556    let worklet_hash = hash(&code_string);
557
558    let ast = AstBuilder::new(ctx.allocator);
559    let params = func.params.clone_in(ctx.allocator);
560    let body = func.body.clone_in(ctx.allocator);
561
562    let func_expr = ast.alloc(ast.function(
563        SPAN,
564        FunctionType::FunctionExpression,
565        None::<BindingIdentifier>,
566        func.generator,
567        func.r#async,
568        false,
569        None::<TSTypeParameterDeclaration>,
570        None::<TSThisParameter>,
571        params,
572        None::<TSTypeAnnotation>,
573        body,
574    ));
575
576    // Arena-allocate the strings
577    let react_name = ctx.allocator.alloc_str(&react_name);
578    let worklet_name = ctx.allocator.alloc_str(&worklet_name);
579    let code_string = ctx.allocator.alloc_str(&code_string);
580
581    build_factory_call_inner(
582        ctx,
583        func_expr,
584        react_name,
585        worklet_name,
586        code_string,
587        worklet_hash,
588        &closure_vars,
589        stmt_idx,
590    )
591}
592
593#[allow(clippy::too_many_arguments)]
594fn build_factory_call_inner<'a>(
595    ctx: &mut WorkletsVisitor<'a>,
596    func_expr: OxcBox<'a, Function<'a>>,
597    react_name: &'a str,
598    worklet_name: &'a str,
599    code_string: &'a str,
600    worklet_hash: u64,
601    closure_vars: &[String],
602    stmt_idx: usize,
603) -> Result<CallExpression<'a>, WorkletsError> {
604    let ast = AstBuilder::new(ctx.allocator);
605    let is_release = ctx.options.is_release;
606    let should_include_init_data = !ctx.options.omit_native_only_data;
607    let init_data_name_string = format!("_worklet_{}_init_data", worklet_hash);
608    let init_data_name: &'a str = ctx.allocator.alloc_str(&init_data_name_string);
609
610    if should_include_init_data {
611        let init_stmt = build_init_data_declaration(
612            &ast,
613            init_data_name,
614            code_string,
615            &ctx.filename,
616            is_release,
617            &ctx.options,
618            ctx.allocator,
619        );
620        ctx.pending_insertions.push((stmt_idx, init_stmt));
621    }
622
623    let mut stmts = ast.vec();
624
625    if !is_release {
626        let line_offset = if closure_vars.is_empty() {
627            1.0
628        } else {
629            1.0 - (closure_vars.len() as f64) - 2.0
630        };
631        stmts.push(build_error_stmt(&ast, line_offset));
632    }
633
634    // const reactName = <func>
635    stmts.push(Statement::from(Declaration::VariableDeclaration(
636        ast.alloc(ast.variable_declaration(
637            SPAN,
638            VariableDeclarationKind::Const,
639            ast.vec1(ast.variable_declarator(
640                SPAN,
641                VariableDeclarationKind::Const,
642                ast.binding_pattern_binding_identifier(SPAN, react_name),
643                None::<TSTypeAnnotation>,
644                Some(Expression::FunctionExpression(func_expr)),
645                false,
646            )),
647            false,
648        )),
649    )));
650
651    // reactName.__closure = { ... }
652    stmts.push(build_closure_assignment(
653        &ast,
654        react_name,
655        closure_vars,
656        ctx.allocator,
657    ));
658
659    // reactName.__workletHash = hash
660    stmts.push(build_member_assign_number(
661        &ast,
662        react_name,
663        "__workletHash",
664        worklet_hash as f64,
665    ));
666
667    if !is_release {
668        stmts.push(build_member_assign_string(
669            &ast,
670            react_name,
671            "__pluginVersion",
672            PLUGIN_VERSION,
673        ));
674    }
675
676    if should_include_init_data {
677        stmts.push(build_member_assign_ident(
678            &ast,
679            react_name,
680            "__initData",
681            init_data_name,
682        ));
683    }
684
685    if !is_release {
686        stmts.push(build_member_assign_ident(
687            &ast,
688            react_name,
689            "__stackDetails",
690            "_e",
691        ));
692    }
693
694    stmts.push(ast.statement_return(SPAN, Some(ast.expression_identifier(SPAN, react_name))));
695
696    // Build factory params: ({ initDataName, ...closureVars })
697    let mut param_props = ast.vec();
698    if should_include_init_data {
699        param_props.push(ast.binding_property(
700            SPAN,
701            ast.property_key_static_identifier(SPAN, init_data_name),
702            ast.binding_pattern_binding_identifier(SPAN, init_data_name),
703            true,
704            false,
705        ));
706    }
707    for var in closure_vars {
708        let var_str: &'a str = ctx.allocator.alloc_str(var.as_str());
709        param_props.push(ast.binding_property(
710            SPAN,
711            ast.property_key_static_identifier(SPAN, var_str),
712            ast.binding_pattern_binding_identifier(SPAN, var_str),
713            true,
714            false,
715        ));
716    }
717
718    let obj_pattern =
719        ast.binding_pattern_object_pattern(SPAN, param_props, None::<BindingRestElement>);
720    let factory_params = ast.formal_parameters(
721        SPAN,
722        FormalParameterKind::FormalParameter,
723        ast.vec1(ast.formal_parameter(
724            SPAN,
725            ast.vec(),
726            obj_pattern,
727            None::<TSTypeAnnotation>,
728            None::<Expression>,
729            false,
730            None,
731            false,
732            false,
733        )),
734        None::<FormalParameterRest>,
735    );
736
737    let factory_body = ast.function_body(SPAN, ast.vec(), stmts);
738    let factory_name_string = format!("{}Factory", worklet_name);
739    let factory_name: &'a str = ctx.allocator.alloc_str(&factory_name_string);
740
741    let factory = ast.function(
742        SPAN,
743        FunctionType::FunctionExpression,
744        Some(ast.binding_identifier(SPAN, factory_name)),
745        false,
746        false,
747        false,
748        None::<TSTypeParameterDeclaration>,
749        None::<TSThisParameter>,
750        factory_params,
751        None::<TSTypeAnnotation>,
752        Some(factory_body),
753    );
754
755    // Build call arg: ({ initDataName, ...closureVars })
756    let mut call_props = ast.vec();
757    if should_include_init_data {
758        call_props.push(ast.object_property_kind_object_property(
759            SPAN,
760            PropertyKind::Init,
761            ast.property_key_static_identifier(SPAN, init_data_name),
762            ast.expression_identifier(SPAN, init_data_name),
763            false,
764            true,
765            false,
766        ));
767    }
768    for var in closure_vars {
769        let var_str: &'a str = ctx.allocator.alloc_str(var.as_str());
770        call_props.push(ast.object_property_kind_object_property(
771            SPAN,
772            PropertyKind::Init,
773            ast.property_key_static_identifier(SPAN, var_str),
774            ast.expression_identifier(SPAN, var_str),
775            false,
776            true,
777            false,
778        ));
779    }
780    let call_arg = ast.expression_object(SPAN, call_props);
781
782    Ok(ast.call_expression(
783        SPAN,
784        Expression::FunctionExpression(ast.alloc(factory)),
785        None::<TSTypeParameterInstantiation>,
786        ast.vec1(Argument::from(call_arg)),
787        false,
788    ))
789}
790
791// --- Code generation ---
792
793fn generate_worklet_code_string_from_function(
794    _allocator: &Allocator,
795    func: &Function<'_>,
796    worklet_name: &str,
797    closure_vars: &[String],
798    original_name: Option<&str>,
799) -> String {
800    let temp_alloc = Allocator::default();
801    let ast = AstBuilder::new(&temp_alloc);
802
803    let params = func.params.clone_in(&temp_alloc);
804    let body = func.body.clone_in(&temp_alloc);
805
806    let wf = ast.function(
807        SPAN,
808        FunctionType::FunctionExpression,
809        Some(ast.binding_identifier(SPAN, temp_alloc.alloc_str(worklet_name))),
810        func.generator,
811        func.r#async,
812        false,
813        None::<TSTypeParameterDeclaration>,
814        None::<TSThisParameter>,
815        params,
816        None::<TSTypeAnnotation>,
817        body,
818    );
819
820    let expr = Expression::FunctionExpression(ast.alloc(wf));
821    let stmt = ast.statement_expression(SPAN, expr);
822    let program = ast.program(
823        SPAN,
824        SourceType::mjs(),
825        "",
826        ast.vec(),
827        None,
828        ast.vec(),
829        ast.vec1(stmt),
830    );
831
832    let code = Codegen::new()
833        .with_options(CodegenOptions::minify())
834        .build(&program)
835        .code;
836    let code = code.trim_end_matches(';');
837
838    inject_closure_and_recursion(code, closure_vars, original_name)
839}
840
841fn generate_worklet_code_string_from_arrow(
842    _allocator: &Allocator,
843    arrow: &ArrowFunctionExpression<'_>,
844    worklet_name: &str,
845    closure_vars: &[String],
846) -> String {
847    let temp_alloc = Allocator::default();
848    let ast = AstBuilder::new(&temp_alloc);
849
850    let params = arrow.params.clone_in(&temp_alloc);
851    let body = arrow.body.clone_in(&temp_alloc);
852
853    let wf = ast.function(
854        SPAN,
855        FunctionType::FunctionExpression,
856        Some(ast.binding_identifier(SPAN, temp_alloc.alloc_str(worklet_name))),
857        false,
858        arrow.r#async,
859        false,
860        None::<TSTypeParameterDeclaration>,
861        None::<TSThisParameter>,
862        params,
863        None::<TSTypeAnnotation>,
864        Some(body),
865    );
866
867    let expr = Expression::FunctionExpression(ast.alloc(wf));
868    let stmt = ast.statement_expression(SPAN, expr);
869    let program = ast.program(
870        SPAN,
871        SourceType::mjs(),
872        "",
873        ast.vec(),
874        None,
875        ast.vec(),
876        ast.vec1(stmt),
877    );
878
879    let code = Codegen::new()
880        .with_options(CodegenOptions::minify())
881        .build(&program)
882        .code;
883    let code = code.trim_end_matches(';');
884
885    inject_closure_and_recursion(code, closure_vars, None)
886}
887
888fn inject_closure_and_recursion(
889    code: &str,
890    closure_vars: &[String],
891    original_name: Option<&str>,
892) -> String {
893    if closure_vars.is_empty() && original_name.is_none() {
894        return code.to_string();
895    }
896
897    if let Some(brace_pos) = code.find('{') {
898        let mut result = String::with_capacity(code.len() + 100);
899        result.push_str(&code[..=brace_pos]);
900
901        if !closure_vars.is_empty() {
902            result.push_str("const{");
903            for (i, var) in closure_vars.iter().enumerate() {
904                if i > 0 {
905                    result.push(',');
906                }
907                result.push_str(var);
908            }
909            result.push_str("}=this.__closure;");
910        }
911
912        if let Some(name) = original_name {
913            let body_part = &code[brace_pos + 1..];
914            if body_part.contains(name) {
915                result.push_str("const ");
916                result.push_str(name);
917                result.push_str("=this._recur;");
918            }
919        }
920
921        result.push_str(&code[brace_pos + 1..]);
922        result
923    } else {
924        code.to_string()
925    }
926}
927
928// --- AST helper builders ---
929
930fn build_const_declaration<'a>(
931    ast: &AstBuilder<'a>,
932    name: &'a str,
933    init: Expression<'a>,
934) -> VariableDeclaration<'a> {
935    ast.variable_declaration(
936        SPAN,
937        VariableDeclarationKind::Const,
938        ast.vec1(ast.variable_declarator(
939            SPAN,
940            VariableDeclarationKind::Const,
941            ast.binding_pattern_binding_identifier(SPAN, name),
942            None::<TSTypeAnnotation>,
943            Some(init),
944            false,
945        )),
946        false,
947    )
948}
949
950fn build_init_data_declaration<'a>(
951    ast: &AstBuilder<'a>,
952    init_data_name: &'a str,
953    code_string: &'a str,
954    filename: &str,
955    is_release: bool,
956    options: &PluginOptions,
957    allocator: &'a Allocator,
958) -> Statement<'a> {
959    let mut props = ast.vec();
960    props.push(ast.object_property_kind_object_property(
961        SPAN,
962        PropertyKind::Init,
963        ast.property_key_static_identifier(SPAN, "code"),
964        ast.expression_string_literal(SPAN, code_string, None),
965        false,
966        false,
967        false,
968    ));
969
970    if !is_release {
971        let location = if options.relative_source_location {
972            if let Some(cwd) = &options.cwd {
973                std::path::Path::new(filename)
974                    .strip_prefix(cwd)
975                    .map(|p| p.to_string_lossy().to_string())
976                    .unwrap_or_else(|_| filename.to_string())
977            } else {
978                filename.to_string()
979            }
980        } else {
981            filename.to_string()
982        };
983        let location: &'a str = allocator.alloc_str(&location);
984        props.push(ast.object_property_kind_object_property(
985            SPAN,
986            PropertyKind::Init,
987            ast.property_key_static_identifier(SPAN, "location"),
988            ast.expression_string_literal(SPAN, location, None),
989            false,
990            false,
991            false,
992        ));
993    }
994
995    let obj = ast.expression_object(SPAN, props);
996    let decl = ast.variable_declaration(
997        SPAN,
998        VariableDeclarationKind::Const,
999        ast.vec1(ast.variable_declarator(
1000            SPAN,
1001            VariableDeclarationKind::Const,
1002            ast.binding_pattern_binding_identifier(SPAN, init_data_name),
1003            None::<TSTypeAnnotation>,
1004            Some(obj),
1005            false,
1006        )),
1007        false,
1008    );
1009    Statement::from(Declaration::VariableDeclaration(ast.alloc(decl)))
1010}
1011
1012fn build_error_stmt<'a>(ast: &AstBuilder<'a>, line_offset: f64) -> Statement<'a> {
1013    let global_error = Expression::StaticMemberExpression(ast.alloc(ast.static_member_expression(
1014        SPAN,
1015        ast.expression_identifier(SPAN, "global"),
1016        ast.identifier_name(SPAN, "Error"),
1017        false,
1018    )));
1019
1020    let none_type_params: Option<OxcBox<'_, TSTypeParameterInstantiation<'_>>> = None;
1021    let array = ast.expression_array(
1022        SPAN,
1023        ast.vec_from_iter([
1024            ArrayExpressionElement::from(ast.expression_new(
1025                SPAN,
1026                global_error,
1027                none_type_params,
1028                ast.vec(),
1029            )),
1030            ArrayExpressionElement::from(ast.expression_numeric_literal(
1031                SPAN,
1032                line_offset,
1033                None,
1034                NumberBase::Decimal,
1035            )),
1036            ArrayExpressionElement::from(ast.expression_unary(
1037                SPAN,
1038                UnaryOperator::UnaryNegation,
1039                ast.expression_numeric_literal(SPAN, 27.0, None, NumberBase::Decimal),
1040            )),
1041        ]),
1042    );
1043
1044    Statement::from(Declaration::VariableDeclaration(ast.alloc(
1045        ast.variable_declaration(
1046            SPAN,
1047            VariableDeclarationKind::Const,
1048            ast.vec1(ast.variable_declarator(
1049                SPAN,
1050                VariableDeclarationKind::Const,
1051                ast.binding_pattern_binding_identifier(SPAN, "_e"),
1052                None::<TSTypeAnnotation>,
1053                Some(array),
1054                false,
1055            )),
1056            false,
1057        ),
1058    )))
1059}
1060
1061fn build_closure_assignment<'a>(
1062    ast: &AstBuilder<'a>,
1063    react_name: &'a str,
1064    closure_vars: &[String],
1065    allocator: &'a Allocator,
1066) -> Statement<'a> {
1067    let props = ast.vec_from_iter(closure_vars.iter().map(|var| {
1068        let var_str: &'a str = allocator.alloc_str(var.as_str());
1069        ast.object_property_kind_object_property(
1070            SPAN,
1071            PropertyKind::Init,
1072            ast.property_key_static_identifier(SPAN, var_str),
1073            ast.expression_identifier(SPAN, var_str),
1074            false,
1075            true,
1076            false,
1077        )
1078    }));
1079    let obj = ast.expression_object(SPAN, props);
1080    build_member_assign(ast, react_name, "__closure", obj)
1081}
1082
1083fn build_member_assign<'a>(
1084    ast: &AstBuilder<'a>,
1085    obj_name: &'a str,
1086    prop_name: &'a str,
1087    value: Expression<'a>,
1088) -> Statement<'a> {
1089    let target = AssignmentTarget::StaticMemberExpression(ast.alloc(ast.static_member_expression(
1090        SPAN,
1091        ast.expression_identifier(SPAN, obj_name),
1092        ast.identifier_name(SPAN, prop_name),
1093        false,
1094    )));
1095    ast.statement_expression(
1096        SPAN,
1097        ast.expression_assignment(SPAN, AssignmentOperator::Assign, target, value),
1098    )
1099}
1100
1101fn build_member_assign_number<'a>(
1102    ast: &AstBuilder<'a>,
1103    obj_name: &'a str,
1104    prop_name: &'a str,
1105    value: f64,
1106) -> Statement<'a> {
1107    build_member_assign(
1108        ast,
1109        obj_name,
1110        prop_name,
1111        ast.expression_numeric_literal(SPAN, value, None, NumberBase::Decimal),
1112    )
1113}
1114
1115fn build_member_assign_string<'a>(
1116    ast: &AstBuilder<'a>,
1117    obj_name: &'a str,
1118    prop_name: &'a str,
1119    value: &'a str,
1120) -> Statement<'a> {
1121    build_member_assign(
1122        ast,
1123        obj_name,
1124        prop_name,
1125        ast.expression_string_literal(SPAN, value, None),
1126    )
1127}
1128
1129fn build_member_assign_ident<'a>(
1130    ast: &AstBuilder<'a>,
1131    obj_name: &'a str,
1132    prop_name: &'a str,
1133    value_name: &'a str,
1134) -> Statement<'a> {
1135    build_member_assign(
1136        ast,
1137        obj_name,
1138        prop_name,
1139        ast.expression_identifier(SPAN, value_name),
1140    )
1141}
1142
1143/// Process workletizable object: workletize each property value that is a function.
1144fn process_workletizable_object<'a>(
1145    obj: &mut ObjectExpression<'a>,
1146    stmt_idx: usize,
1147    ctx: &mut WorkletsVisitor<'a>,
1148    accept_function: bool,
1149) -> Result<(), WorkletsError> {
1150    for prop in obj.properties.iter_mut() {
1151        if let ObjectPropertyKind::ObjectProperty(p) = prop {
1152            if accept_function {
1153                match &mut p.value {
1154                    Expression::ArrowFunctionExpression(arrow) => {
1155                        process_inner_worklets_in_arrow(arrow, stmt_idx, ctx)?;
1156                        let r = transform_worklet_arrow(arrow, stmt_idx, ctx)?;
1157                        p.value = r;
1158                    }
1159                    Expression::FunctionExpression(func) => {
1160                        process_inner_worklets_in_function(func, stmt_idx, ctx)?;
1161                        let n = func.id.as_ref().map(|id| id.name.as_str());
1162                        let fc = build_factory_call(func, n, stmt_idx, ctx)?;
1163                        let ast = AstBuilder::new(ctx.allocator);
1164                        p.value = Expression::CallExpression(ast.alloc(fc));
1165                    }
1166                    _ => {}
1167                }
1168            }
1169        }
1170    }
1171    Ok(())
1172}
1173
1174// --- Context Object processing ---
1175
1176/// Check if an ObjectExpression has the __workletContextObject marker property.
1177fn is_context_object(obj: &ObjectExpression) -> bool {
1178    obj.properties.iter().any(|prop| {
1179        if let ObjectPropertyKind::ObjectProperty(p) = prop {
1180            if let PropertyKey::StaticIdentifier(id) = &p.key {
1181                return id.name == CONTEXT_OBJECT_MARKER;
1182            }
1183        }
1184        false
1185    })
1186}
1187
1188/// Process all context objects in the program.
1189/// For each ObjectExpression with __workletContextObject:
1190/// 1. Remove the marker property
1191/// 2. Add a __workletContextObjectFactory property (a function returning the object with 'worklet' directive)
1192fn process_context_objects<'a>(program: &mut Program<'a>, allocator: &'a Allocator) {
1193    for stmt in program.body.iter_mut() {
1194        match stmt {
1195            Statement::VariableDeclaration(var_decl) => {
1196                for d in var_decl.declarations.iter_mut() {
1197                    if let Some(Expression::ObjectExpression(obj)) = &mut d.init {
1198                        if is_context_object(obj) {
1199                            process_context_object(obj, allocator);
1200                        }
1201                    }
1202                }
1203            }
1204            Statement::ExpressionStatement(es) => {
1205                if let Expression::AssignmentExpression(assign) = &mut es.expression {
1206                    if let Expression::ObjectExpression(obj) = &mut assign.right {
1207                        if is_context_object(obj) {
1208                            process_context_object(obj, allocator);
1209                        }
1210                    }
1211                }
1212            }
1213            Statement::ExportNamedDeclaration(export) => {
1214                if let Some(Declaration::VariableDeclaration(var_decl)) = &mut export.declaration {
1215                    for d in var_decl.declarations.iter_mut() {
1216                        if let Some(Expression::ObjectExpression(obj)) = &mut d.init {
1217                            if is_context_object(obj) {
1218                                process_context_object(obj, allocator);
1219                            }
1220                        }
1221                    }
1222                }
1223            }
1224            Statement::ExportDefaultDeclaration(export) => {
1225                if let ExportDefaultDeclarationKind::ObjectExpression(obj) = &mut export.declaration
1226                {
1227                    if is_context_object(obj) {
1228                        process_context_object(obj, allocator);
1229                    }
1230                }
1231            }
1232            _ => {}
1233        }
1234    }
1235}
1236
1237/// Transform a context object: remove marker and add factory function property.
1238fn process_context_object<'a>(obj: &mut ObjectExpression<'a>, allocator: &'a Allocator) {
1239    let ast = AstBuilder::new(allocator);
1240
1241    // Remove the __workletContextObject marker property
1242    obj.properties.retain(|prop| {
1243        if let ObjectPropertyKind::ObjectProperty(p) = prop {
1244            if let PropertyKey::StaticIdentifier(id) = &p.key {
1245                return id.name != CONTEXT_OBJECT_MARKER;
1246            }
1247        }
1248        true
1249    });
1250
1251    // Clone the object to create the return value of the factory
1252    let cloned_obj = obj.clone_in(allocator);
1253    let return_stmt = ast.statement_return(
1254        SPAN,
1255        Some(Expression::ObjectExpression(ast.alloc(cloned_obj))),
1256    );
1257
1258    let factory_body = ast.function_body(
1259        SPAN,
1260        ast.vec1(ast.directive(SPAN, ast.string_literal(SPAN, "worklet", None), "worklet")),
1261        ast.vec1(return_stmt),
1262    );
1263
1264    let factory_func = ast.function(
1265        SPAN,
1266        FunctionType::FunctionExpression,
1267        None::<BindingIdentifier>,
1268        false,
1269        false,
1270        false,
1271        None::<TSTypeParameterDeclaration>,
1272        None::<TSThisParameter>,
1273        ast.formal_parameters(
1274            SPAN,
1275            FormalParameterKind::FormalParameter,
1276            ast.vec(),
1277            None::<FormalParameterRest>,
1278        ),
1279        None::<TSTypeAnnotation>,
1280        Some(factory_body),
1281    );
1282
1283    let factory_name = format!("{}Factory", CONTEXT_OBJECT_MARKER);
1284    let factory_name: &'a str = allocator.alloc_str(&factory_name);
1285
1286    // Add __workletContextObjectFactory property to the object
1287    obj.properties
1288        .push(ast.object_property_kind_object_property(
1289            SPAN,
1290            PropertyKind::Init,
1291            ast.property_key_static_identifier(SPAN, factory_name),
1292            Expression::FunctionExpression(ast.alloc(factory_func)),
1293            false,
1294            false,
1295            false,
1296        ));
1297}
1298
1299// --- Referenced Worklet Collection ---
1300
1301/// Collect names of identifiers that are passed as arguments to auto-workletize hooks.
1302/// These need to have 'worklet' directives added to their definitions.
1303fn collect_referenced_worklet_names(stmts: &[Statement]) -> HashSet<String> {
1304    let mut names = HashSet::new();
1305    for stmt in stmts {
1306        collect_worklet_names_from_stmt(stmt, &mut names);
1307    }
1308    names
1309}
1310
1311fn collect_worklet_names_from_stmt(stmt: &Statement, names: &mut HashSet<String>) {
1312    match stmt {
1313        Statement::VariableDeclaration(var_decl) => {
1314            for d in &var_decl.declarations {
1315                if let Some(init) = &d.init {
1316                    collect_worklet_names_from_expr(init, names);
1317                }
1318            }
1319        }
1320        Statement::ExpressionStatement(es) => {
1321            collect_worklet_names_from_expr(&es.expression, names);
1322        }
1323        Statement::ExportNamedDeclaration(export) => {
1324            if let Some(decl) = &export.declaration {
1325                if let Declaration::VariableDeclaration(var_decl) = decl {
1326                    for d in &var_decl.declarations {
1327                        if let Some(init) = &d.init {
1328                            collect_worklet_names_from_expr(init, names);
1329                        }
1330                    }
1331                }
1332            }
1333        }
1334        _ => {}
1335    }
1336}
1337
1338fn collect_worklet_names_from_expr(expr: &Expression, names: &mut HashSet<String>) {
1339    match expr {
1340        Expression::CallExpression(call) => {
1341            let callee_name = get_callee_name(&call.callee).map(|s| s.to_string());
1342            if let Some(ref name) = callee_name {
1343                let is_func = is_reanimated_function_hook(name);
1344                let is_obj = is_reanimated_object_hook(name);
1345                if is_func || is_obj {
1346                    if let Some(arg_indices) = get_args_to_workletize(name) {
1347                        for &idx in arg_indices {
1348                            if idx < call.arguments.len() {
1349                                if let Argument::Identifier(ident) = &call.arguments[idx] {
1350                                    names.insert(ident.name.to_string());
1351                                }
1352                            }
1353                        }
1354                    }
1355                }
1356            }
1357            // Recurse into callee for chained calls
1358            if let Expression::CallExpression(inner) = &call.callee {
1359                collect_worklet_names_from_expr(
1360                    &Expression::CallExpression(inner.clone_in(&Allocator::default())),
1361                    names,
1362                );
1363            }
1364        }
1365        Expression::AssignmentExpression(assign) => {
1366            collect_worklet_names_from_expr(&assign.right, names);
1367        }
1368        _ => {}
1369    }
1370}
1371
1372/// Add 'worklet' directives to the definitions of referenced worklet names.
1373fn add_worklet_directives_to_referenced<'a>(
1374    program: &mut Program<'a>,
1375    allocator: &'a Allocator,
1376    names: &HashSet<String>,
1377) {
1378    let ast = AstBuilder::new(allocator);
1379    for stmt in program.body.iter_mut() {
1380        match stmt {
1381            Statement::FunctionDeclaration(func) => {
1382                if let Some(id) = &func.id {
1383                    if names.contains(id.name.as_str()) {
1384                        add_worklet_directive_to_func_body(func, &ast);
1385                    }
1386                }
1387            }
1388            Statement::VariableDeclaration(var_decl) => {
1389                for d in var_decl.declarations.iter_mut() {
1390                    if let BindingPattern::BindingIdentifier(id) = &d.id {
1391                        if names.contains(id.name.as_str()) {
1392                            if let Some(init) = &mut d.init {
1393                                add_worklet_directive_to_expr(init, &ast, allocator);
1394                            }
1395                        }
1396                    }
1397                }
1398            }
1399            Statement::ExportNamedDeclaration(export) => {
1400                if let Some(decl) = &mut export.declaration {
1401                    match decl {
1402                        Declaration::FunctionDeclaration(func) => {
1403                            if let Some(id) = &func.id {
1404                                if names.contains(id.name.as_str()) {
1405                                    add_worklet_directive_to_func_body(func, &ast);
1406                                }
1407                            }
1408                        }
1409                        Declaration::VariableDeclaration(var_decl) => {
1410                            for d in var_decl.declarations.iter_mut() {
1411                                if let BindingPattern::BindingIdentifier(id) = &d.id {
1412                                    if names.contains(id.name.as_str()) {
1413                                        if let Some(init) = &mut d.init {
1414                                            add_worklet_directive_to_expr(init, &ast, allocator);
1415                                        }
1416                                    }
1417                                }
1418                            }
1419                        }
1420                        _ => {}
1421                    }
1422                }
1423            }
1424            _ => {}
1425        }
1426        // Also check assignment expressions (for reassigned variables)
1427        if let Statement::ExpressionStatement(es) = stmt {
1428            if let Expression::AssignmentExpression(assign) = &mut es.expression {
1429                if let AssignmentTarget::AssignmentTargetIdentifier(id) = &assign.left {
1430                    if names.contains(id.name.as_str()) {
1431                        add_worklet_directive_to_expr(&mut assign.right, &ast, allocator);
1432                    }
1433                }
1434            }
1435        }
1436    }
1437}
1438
1439fn add_worklet_directives_to_top_level<'a>(program: &mut Program<'a>, allocator: &'a Allocator) {
1440    let ast = AstBuilder::new(allocator);
1441    for stmt in program.body.iter_mut() {
1442        match stmt {
1443            Statement::FunctionDeclaration(func) => {
1444                add_worklet_directive_to_func_body(func, &ast);
1445            }
1446            Statement::VariableDeclaration(var_decl) => {
1447                for d in var_decl.declarations.iter_mut() {
1448                    if let Some(init) = &mut d.init {
1449                        add_worklet_directive_to_expr(init, &ast, allocator);
1450                    }
1451                }
1452            }
1453            Statement::ClassDeclaration(class) => {
1454                add_worklet_class_marker(class, &ast);
1455            }
1456            Statement::ExportNamedDeclaration(export) => {
1457                if let Some(decl) = &mut export.declaration {
1458                    match decl {
1459                        Declaration::FunctionDeclaration(func) => {
1460                            add_worklet_directive_to_func_body(func, &ast);
1461                        }
1462                        Declaration::VariableDeclaration(var_decl) => {
1463                            for d in var_decl.declarations.iter_mut() {
1464                                if let Some(init) = &mut d.init {
1465                                    add_worklet_directive_to_expr(init, &ast, allocator);
1466                                }
1467                            }
1468                        }
1469                        Declaration::ClassDeclaration(class) => {
1470                            add_worklet_class_marker(class, &ast);
1471                        }
1472                        _ => {}
1473                    }
1474                }
1475            }
1476            Statement::ExportDefaultDeclaration(export) => match &mut export.declaration {
1477                ExportDefaultDeclarationKind::FunctionDeclaration(func) => {
1478                    add_worklet_directive_to_func_body(func, &ast);
1479                }
1480                ExportDefaultDeclarationKind::ClassDeclaration(class) => {
1481                    add_worklet_class_marker(class, &ast);
1482                }
1483                _ => {}
1484            },
1485            _ => {}
1486        }
1487    }
1488}
1489
1490fn add_worklet_directive_to_func_body<'a>(func: &mut Function<'a>, ast: &AstBuilder<'a>) {
1491    if let Some(body) = &mut func.body {
1492        if !body
1493            .directives
1494            .iter()
1495            .any(|d| d.expression.value == "worklet")
1496        {
1497            body.directives.push(ast.directive(
1498                SPAN,
1499                ast.string_literal(SPAN, "worklet", None),
1500                "worklet",
1501            ));
1502        }
1503    }
1504}
1505
1506fn add_worklet_directive_to_expr<'a>(
1507    expr: &mut Expression<'a>,
1508    ast: &AstBuilder<'a>,
1509    allocator: &'a Allocator,
1510) {
1511    match expr {
1512        Expression::ArrowFunctionExpression(arrow) => {
1513            if arrow.expression {
1514                if let Some(Statement::ExpressionStatement(es)) = arrow.body.statements.first() {
1515                    let ret_expr = es.expression.clone_in(allocator);
1516                    arrow.body.statements.clear();
1517                    arrow
1518                        .body
1519                        .statements
1520                        .push(ast.statement_return(SPAN, Some(ret_expr)));
1521                    arrow.expression = false;
1522                }
1523            }
1524            if !arrow
1525                .body
1526                .directives
1527                .iter()
1528                .any(|d| d.expression.value == "worklet")
1529            {
1530                arrow.body.directives.push(ast.directive(
1531                    SPAN,
1532                    ast.string_literal(SPAN, "worklet", None),
1533                    "worklet",
1534                ));
1535            }
1536        }
1537        Expression::FunctionExpression(func) => {
1538            add_worklet_directive_to_func_body(func, ast);
1539        }
1540        Expression::ObjectExpression(obj) => {
1541            // Check if any object method uses `this` — if so, it's an implicit context object
1542            if is_implicit_context_object(obj) {
1543                // Add __workletContextObject marker (will be processed later by process_context_objects)
1544                add_context_object_marker(obj, ast);
1545            } else {
1546                // Otherwise, process each property individually
1547                process_worklet_aggregator_object(obj, ast, allocator);
1548            }
1549        }
1550        _ => {}
1551    }
1552}
1553
1554/// Check if an ObjectExpression has any ObjectMethod that uses `this`.
1555fn is_implicit_context_object(obj: &ObjectExpression) -> bool {
1556    use oxc::ast_visit::Visit;
1557    use oxc::syntax::scope::ScopeFlags;
1558
1559    struct ThisFinder {
1560        found: bool,
1561    }
1562    impl<'a> Visit<'a> for ThisFinder {
1563        fn visit_this_expression(&mut self, _: &ThisExpression) {
1564            self.found = true;
1565        }
1566        // Don't recurse into nested functions (they have their own `this`)
1567        fn visit_function(&mut self, _: &Function<'a>, _flags: ScopeFlags) {}
1568        fn visit_arrow_function_expression(&mut self, arrow: &ArrowFunctionExpression<'a>) {
1569            // Arrow functions inherit `this`, so DO recurse
1570            for stmt in &arrow.body.statements {
1571                self.visit_statement(stmt);
1572            }
1573        }
1574    }
1575
1576    for prop in &obj.properties {
1577        if let ObjectPropertyKind::ObjectProperty(p) = prop {
1578            if p.method {
1579                // It's an object method shorthand like bar() {}
1580                if let Expression::FunctionExpression(func) = &p.value {
1581                    let mut finder = ThisFinder { found: false };
1582                    if let Some(body) = &func.body {
1583                        for stmt in &body.statements {
1584                            finder.visit_statement(stmt);
1585                        }
1586                    }
1587                    if finder.found {
1588                        return true;
1589                    }
1590                }
1591            }
1592        }
1593    }
1594    false
1595}
1596
1597/// Add __workletContextObject: true marker to an object expression.
1598fn add_context_object_marker<'a>(obj: &mut ObjectExpression<'a>, ast: &AstBuilder<'a>) {
1599    // Check if already has marker
1600    let has_marker = obj.properties.iter().any(|prop| {
1601        if let ObjectPropertyKind::ObjectProperty(p) = prop {
1602            if let PropertyKey::StaticIdentifier(id) = &p.key {
1603                return id.name == CONTEXT_OBJECT_MARKER;
1604            }
1605        }
1606        false
1607    });
1608    if has_marker {
1609        return;
1610    }
1611    obj.properties
1612        .push(ast.object_property_kind_object_property(
1613            SPAN,
1614            PropertyKind::Init,
1615            ast.property_key_static_identifier(SPAN, CONTEXT_OBJECT_MARKER),
1616            ast.expression_boolean_literal(SPAN, true),
1617            false,
1618            false,
1619            false,
1620        ));
1621}
1622
1623// --- Worklet Class processing ---
1624
1625/// Check if a class has the __workletClass marker property.
1626fn is_worklet_class(class: &Class) -> bool {
1627    class.body.body.iter().any(|element| {
1628        if let ClassElement::PropertyDefinition(prop) = element {
1629            if let PropertyKey::StaticIdentifier(id) = &prop.key {
1630                return id.name == WORKLET_CLASS_MARKER;
1631            }
1632        }
1633        false
1634    })
1635}
1636
1637/// Remove the __workletClass marker from a class body.
1638fn remove_worklet_class_marker(class: &mut Class) {
1639    class.body.body.retain(|element| {
1640        if let ClassElement::PropertyDefinition(prop) = element {
1641            if let PropertyKey::StaticIdentifier(id) = &prop.key {
1642                return id.name != WORKLET_CLASS_MARKER;
1643            }
1644        }
1645        true
1646    });
1647}
1648
1649/// Add __workletClass marker to a class (used by file-level worklet directive).
1650fn add_worklet_class_marker<'a>(class: &mut Class<'a>, ast: &AstBuilder<'a>) {
1651    // Check if already has marker
1652    if is_worklet_class(class) {
1653        return;
1654    }
1655    class.body.body.push(ast.class_element_property_definition(
1656        SPAN,
1657        PropertyDefinitionType::PropertyDefinition,
1658        ast.vec(),
1659        ast.property_key_static_identifier(SPAN, WORKLET_CLASS_MARKER),
1660        None::<TSTypeAnnotation>,
1661        Some(ast.expression_boolean_literal(SPAN, true)),
1662        false,
1663        false,
1664        false,
1665        false,
1666        false,
1667        false,
1668        false,
1669        None::<TSAccessibility>,
1670    ));
1671}
1672
1673/// Transform a worklet class into a factory function + const declaration.
1674///
1675/// Input:
1676/// ```js
1677/// class Foo { __workletClass = true; method() {} }
1678/// ```
1679///
1680/// Output:
1681/// ```js
1682/// function Foo__classFactory() {
1683///     'worklet';
1684///     class Foo { method() {} }
1685///     Foo.__classFactory = Foo__classFactory;
1686///     return Foo;
1687/// }
1688/// const Foo = Foo__classFactory();
1689/// ```
1690fn transform_worklet_class<'a>(
1691    class: &mut Class<'a>,
1692    _stmt_idx: usize,
1693    ctx: &mut WorkletsVisitor<'a>,
1694) -> Result<Vec<Statement<'a>>, WorkletsError> {
1695    let class_name: &'a str = class
1696        .id
1697        .as_ref()
1698        .map(|id| id.name.as_str())
1699        .ok_or_else(|| WorkletsError("Worklet class must have a name".into()))?;
1700
1701    let factory_name_string = format!("{}{}", class_name, WORKLET_CLASS_FACTORY_SUFFIX);
1702    let factory_name: &'a str = ctx.allocator.alloc_str(&factory_name_string);
1703
1704    let ast = AstBuilder::new(ctx.allocator);
1705
1706    // Remove __workletClass marker
1707    remove_worklet_class_marker(class);
1708
1709    // Clone the class into a new class declaration statement
1710    let class_clone = class.clone_in(ctx.allocator);
1711    let class_decl = Statement::ClassDeclaration(ast.alloc(class_clone));
1712
1713    // ClassName.__classFactory = ClassName__classFactory;
1714    let assign_factory = ast.statement_expression(
1715        SPAN,
1716        ast.expression_assignment(
1717            SPAN,
1718            AssignmentOperator::Assign,
1719            AssignmentTarget::StaticMemberExpression(ast.alloc(ast.static_member_expression(
1720                SPAN,
1721                ast.expression_identifier(SPAN, class_name),
1722                ast.identifier_name(SPAN, WORKLET_CLASS_FACTORY_SUFFIX),
1723                false,
1724            ))),
1725            ast.expression_identifier(SPAN, factory_name),
1726        ),
1727    );
1728
1729    // return ClassName;
1730    let return_stmt = ast.statement_return(SPAN, Some(ast.expression_identifier(SPAN, class_name)));
1731
1732    // Factory function body with 'worklet' directive
1733    let factory_body = ast.function_body(
1734        SPAN,
1735        ast.vec1(ast.directive(SPAN, ast.string_literal(SPAN, "worklet", None), "worklet")),
1736        ast.vec_from_iter([class_decl, assign_factory, return_stmt]),
1737    );
1738
1739    let factory_func = ast.function(
1740        SPAN,
1741        FunctionType::FunctionDeclaration,
1742        Some(ast.binding_identifier(SPAN, factory_name)),
1743        false,
1744        false,
1745        false,
1746        None::<TSTypeParameterDeclaration>,
1747        None::<TSThisParameter>,
1748        ast.formal_parameters(
1749            SPAN,
1750            FormalParameterKind::FormalParameter,
1751            ast.vec(),
1752            None::<FormalParameterRest>,
1753        ),
1754        None::<TSTypeAnnotation>,
1755        Some(factory_body),
1756    );
1757
1758    let factory_decl = Statement::FunctionDeclaration(ast.alloc(factory_func));
1759
1760    // const ClassName = ClassName__classFactory();
1761    let call_factory = ast.call_expression(
1762        SPAN,
1763        ast.expression_identifier(SPAN, factory_name),
1764        None::<TSTypeParameterInstantiation>,
1765        ast.vec(),
1766        false,
1767    );
1768    let const_decl = build_const_declaration(
1769        &ast,
1770        class_name,
1771        Expression::CallExpression(ast.alloc(call_factory)),
1772    );
1773    let const_stmt = Statement::VariableDeclaration(ast.alloc(const_decl));
1774
1775    Ok(vec![factory_decl, const_stmt])
1776}
1777
1778/// For non-context objects in file-level worklet: add 'worklet' to each method body
1779/// and recursively process property values.
1780fn process_worklet_aggregator_object<'a>(
1781    obj: &mut ObjectExpression<'a>,
1782    ast: &AstBuilder<'a>,
1783    allocator: &'a Allocator,
1784) {
1785    for prop in obj.properties.iter_mut() {
1786        if let ObjectPropertyKind::ObjectProperty(p) = prop {
1787            if p.method {
1788                // Object method shorthand: add 'worklet' directive to function body
1789                if let Expression::FunctionExpression(func) = &mut p.value {
1790                    add_worklet_directive_to_func_body(func, ast);
1791                }
1792            } else {
1793                // Regular property: process value
1794                add_worklet_directive_to_expr(&mut p.value, ast, allocator);
1795            }
1796        }
1797    }
1798}