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 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 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(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 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
116fn 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 if stmts.len() == 2 {
149 let second = stmts.pop().unwrap();
150 *stmt = stmts.pop().unwrap();
151 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 if let Expression::CallExpression(callee_call) = &mut call.callee {
308 process_call_expression(callee_call, stmt_idx, ctx)?;
309 }
310
311 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
368fn 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
452fn 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 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 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 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 stmts.push(build_closure_assignment(
653 &ast,
654 react_name,
655 closure_vars,
656 ctx.allocator,
657 ));
658
659 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 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 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
791fn 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
928fn 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
1143fn 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
1174fn 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
1188fn 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
1237fn process_context_object<'a>(obj: &mut ObjectExpression<'a>, allocator: &'a Allocator) {
1239 let ast = AstBuilder::new(allocator);
1240
1241 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 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 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
1299fn 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 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
1372fn 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 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 if is_implicit_context_object(obj) {
1543 add_context_object_marker(obj, ast);
1545 } else {
1546 process_worklet_aggregator_object(obj, ast, allocator);
1548 }
1549 }
1550 _ => {}
1551 }
1552}
1553
1554fn 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 fn visit_function(&mut self, _: &Function<'a>, _flags: ScopeFlags) {}
1568 fn visit_arrow_function_expression(&mut self, arrow: &ArrowFunctionExpression<'a>) {
1569 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 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
1597fn add_context_object_marker<'a>(obj: &mut ObjectExpression<'a>, ast: &AstBuilder<'a>) {
1599 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
1623fn 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
1637fn 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
1649fn add_worklet_class_marker<'a>(class: &mut Class<'a>, ast: &AstBuilder<'a>) {
1651 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
1673fn 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_worklet_class_marker(class);
1708
1709 let class_clone = class.clone_in(ctx.allocator);
1711 let class_decl = Statement::ClassDeclaration(ast.alloc(class_clone));
1712
1713 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 let return_stmt = ast.statement_return(SPAN, Some(ast.expression_identifier(SPAN, class_name)));
1731
1732 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 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
1778fn 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 if let Expression::FunctionExpression(func) = &mut p.value {
1790 add_worklet_directive_to_func_body(func, ast);
1791 }
1792 } else {
1793 add_worklet_directive_to_expr(&mut p.value, ast, allocator);
1795 }
1796 }
1797 }
1798}