1use std::{fmt::Debug, hash::Hash, sync::Arc};
2
3use anyhow::{Result, bail};
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6use swc_core::{
7 atoms::{Atom, atom},
8 base::SwcComments,
9 common::{Mark, SourceMap, comments::Comments},
10 ecma::{
11 ast::{
12 ArrowExpr, ArrowFunctionBody, Expr, ExprStmt, Function, Lit, ModuleItem, Pass, Program,
13 Stmt,
14 },
15 preset_env::{self, Feature, FeatureOrModule, Targets},
16 transforms::{
17 base::{
18 assumptions::Assumptions,
19 helpers::{HELPERS, HelperData, Helpers},
20 },
21 react::react,
22 typescript::{Config, typescript},
23 },
24 utils::IsDirective,
25 visit::{Visit, VisitWith},
26 },
27 quote,
28};
29use turbo_rcstr::{RcStr, rcstr};
30use turbo_tasks::{ResolvedVc, Vc};
31use turbo_tasks_fs::FileSystemPath;
32use turbopack_core::{
33 environment::Environment,
34 issue::{Issue, IssueSeverity, IssueSource, IssueStage, StyledString},
35 source::Source,
36};
37
38use crate::runtime_functions::{TURBOPACK_MODULE, TURBOPACK_REFRESH};
39
40#[turbo_tasks::value(shared)]
46#[derive(Default, Clone, Debug)]
47pub struct PresetEnvConfig {
48 pub mode: Option<RcStr>,
51 pub core_js: Option<RcStr>,
53 pub skip: Option<Vec<RcStr>>,
55 pub include: Option<Vec<RcStr>>,
57 pub exclude: Option<Vec<RcStr>>,
59 pub shipped_proposals: Option<bool>,
61 pub force_all_transforms: Option<bool>,
63 pub debug: Option<bool>,
65 pub loose: Option<bool>,
67}
68
69#[turbo_tasks::value]
70#[derive(Debug, Clone, Hash)]
71pub enum EcmascriptInputTransform {
72 Plugin(ResolvedVc<TransformPlugin>),
73 PresetEnv(ResolvedVc<Environment>, ResolvedVc<PresetEnvConfig>),
74 React {
75 development: bool,
76 refresh: bool,
77 import_source: ResolvedVc<Option<RcStr>>,
79 runtime: ResolvedVc<Option<RcStr>>,
81 },
82 TypeScript {
85 use_define_for_class_fields: bool,
86 verbatim_module_syntax: bool,
87 },
88 Decorators {
89 is_legacy: bool,
90 is_ecma: bool,
91 emit_decorators_metadata: bool,
92 use_define_for_class_fields: bool,
93 },
94 ReactCompilerRust {
95 options: ReactCompilerTransformOptions,
96 target: ReactCompilerTarget,
97 },
98}
99
100#[turbo_tasks::value(shared, operation)]
101#[derive(Default, Debug, Clone, Copy, Hash, Serialize, Deserialize)]
102#[serde(rename_all = "camelCase")]
103pub struct ReactCompilerTransformOptions {
104 #[serde(default)]
105 pub compilation_mode: ReactCompilerCompilationMode,
106 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub enable_preserve_existing_memoization_guarantees: Option<bool>,
108}
109
110#[turbo_tasks::value(shared, operation)]
111#[derive(Default, Debug, Clone, Copy, Hash, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub enum ReactCompilerCompilationMode {
114 #[default]
115 Infer,
116 Annotation,
117 All,
118}
119
120impl ReactCompilerCompilationMode {
121 pub fn as_str(self) -> &'static str {
122 match self {
123 ReactCompilerCompilationMode::Infer => "infer",
124 ReactCompilerCompilationMode::Annotation => "annotation",
125 ReactCompilerCompilationMode::All => "all",
126 }
127 }
128}
129
130#[turbo_tasks::value(transparent)]
131pub struct OptionReactCompilerTransformOptions(Option<ReactCompilerTransformOptions>);
132
133#[turbo_tasks::value(shared, operation)]
134#[derive(Default, Debug, Clone, Copy, Hash, Serialize, Deserialize)]
135pub enum ReactCompilerTarget {
136 #[default]
137 #[serde(rename = "19")]
138 React19,
139 #[serde(rename = "18")]
140 React18,
141}
142
143impl ReactCompilerTarget {
144 pub fn as_str(self) -> &'static str {
145 match self {
146 ReactCompilerTarget::React19 => "19",
147 ReactCompilerTarget::React18 => "18",
148 }
149 }
150}
151
152#[async_trait]
155pub trait CustomTransformer: Debug {
156 async fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()>;
157}
158
159#[turbo_tasks::value(transparent, serialization = "skip", eq = "manual", cell = "new")]
162#[derive(Debug)]
163pub struct TransformPlugin(#[turbo_tasks(unsafe_ignore)] Box<dyn CustomTransformer + Send + Sync>);
164
165#[async_trait]
166impl CustomTransformer for TransformPlugin {
167 async fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()> {
168 self.0.transform(program, ctx).await
169 }
170}
171
172#[turbo_tasks::value(transparent)]
173#[derive(Debug, Clone, Hash)]
174pub struct EcmascriptInputTransforms(Vec<EcmascriptInputTransform>);
175
176#[turbo_tasks::value_impl]
177impl EcmascriptInputTransforms {
178 #[turbo_tasks::function]
179 pub fn empty() -> Vc<Self> {
180 Vc::cell(Vec::new())
181 }
182
183 #[turbo_tasks::function]
184 pub async fn extend(self: Vc<Self>, other: Vc<EcmascriptInputTransforms>) -> Result<Vc<Self>> {
185 let mut transforms = self.owned().await?;
186 transforms.extend(other.owned().await?);
187 Ok(Vc::cell(transforms))
188 }
189}
190
191pub struct TransformContext<'a> {
192 pub comments: &'a SwcComments,
193 pub top_level_mark: Mark,
194 pub unresolved_mark: Mark,
195 pub source_map: &'a Arc<SourceMap>,
196 pub file_path_str: &'a str,
197 pub file_name_str: &'a str,
198 pub file_name_hash: u128,
199 pub query_str: RcStr,
200 pub file_path: FileSystemPath,
201 pub source: ResolvedVc<Box<dyn Source>>,
202 pub source_text: &'a str,
205 pub node_env: RcStr,
208}
209
210impl EcmascriptInputTransform {
211 pub async fn apply(
212 &self,
213 program: &mut Program,
214 ctx: &TransformContext<'_>,
215 helpers: HelperData,
216 ) -> Result<HelperData> {
217 let &TransformContext {
218 comments,
219 source_map,
220 top_level_mark,
221 unresolved_mark,
222 ..
223 } = ctx;
224
225 Ok(match self {
226 EcmascriptInputTransform::React {
227 development,
228 refresh,
229 import_source,
230 runtime,
231 } => {
232 use swc_core::ecma::transforms::react::{Options, Runtime};
233 let runtime = if let Some(runtime) = &*runtime.await? {
234 match runtime.as_str() {
235 "classic" => Runtime::Classic,
236 "automatic" => Runtime::Automatic,
237 _ => {
238 bail!(
239 "Invalid value for swc.jsc.transform.react.runtime: {}",
240 runtime
241 );
242 }
243 }
244 } else {
245 Runtime::Automatic
246 };
247
248 let config = Options {
249 runtime: Some(runtime),
250 development: Some(*development),
251 import_source: import_source.await?.as_deref().map(Atom::from),
252 refresh: if *refresh {
253 debug_assert_eq!(TURBOPACK_REFRESH.full, "__turbopack_context__.k");
254 Some(swc_core::ecma::transforms::react::RefreshOptions {
255 refresh_reg: atom!("__turbopack_context__.k.register"),
256 refresh_sig: atom!("__turbopack_context__.k.signature"),
257 ..Default::default()
258 })
259 } else {
260 None
261 },
262 ..Default::default()
263 };
264
265 let helpers = apply_transform(
268 program,
269 helpers,
270 react::<&dyn Comments>(
271 source_map.clone(),
272 Some(&comments),
273 config,
274 top_level_mark,
275 unresolved_mark,
276 ),
277 );
278
279 if *refresh {
280 debug_assert_eq!(TURBOPACK_REFRESH.full, "__turbopack_context__.k");
281 debug_assert_eq!(TURBOPACK_MODULE.full, "__turbopack_context__.m");
282 let stmt = quote!(
283 "if (typeof globalThis.$RefreshHelpers$ === 'object' && \
285 globalThis.$RefreshHelpers !== null) { \
286 __turbopack_context__.k.registerExports(__turbopack_context__.m, \
287 globalThis.$RefreshHelpers$); }" as Stmt
288 );
289
290 match program {
291 Program::Module(module) => {
292 module.body.push(ModuleItem::Stmt(stmt));
293 }
294 Program::Script(script) => {
295 script.body.push(stmt);
296 }
297 }
298 }
299
300 helpers
301 }
302 EcmascriptInputTransform::PresetEnv(env, preset_env_config) => {
303 let versions = env.runtime_versions().await?;
304 let extra = preset_env_config.await?;
305
306 let mode = match extra.mode.as_deref() {
307 Some("usage") => Some(preset_env::Mode::Usage),
308 Some("entry") => Some(preset_env::Mode::Entry),
309 _ => None,
310 };
311
312 let core_js = extra.core_js.as_ref().and_then(|v| {
313 let parts: Vec<&str> = v.split('.').collect();
314 Some(preset_env::Version {
315 major: parts.first()?.parse().ok()?,
316 minor: parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0),
317 patch: parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0),
318 })
319 });
320
321 let skip = extra
322 .skip
323 .as_ref()
324 .map(|v| v.iter().map(|s| Atom::from(s.as_str())).collect())
325 .unwrap_or_default();
326
327 let parse_feature_or_module = |s: &str| -> FeatureOrModule {
328 if let Ok(feature) = s.parse::<Feature>() {
329 FeatureOrModule::Feature(feature)
330 } else {
331 FeatureOrModule::CoreJsModule(s.to_string())
332 }
333 };
334
335 let include: Vec<FeatureOrModule> = extra
336 .include
337 .as_ref()
338 .map(|v| v.iter().map(|s| parse_feature_or_module(s)).collect())
339 .unwrap_or_default();
340
341 let mut exclude: Vec<FeatureOrModule> = vec![
344 FeatureOrModule::Feature(Feature::ReservedWords),
345 FeatureOrModule::Feature(Feature::MemberExpressionLiterals),
346 FeatureOrModule::Feature(Feature::PropertyLiterals),
347 ];
348 if let Some(user_exclude) = &extra.exclude {
349 for s in user_exclude {
350 exclude.push(parse_feature_or_module(s));
351 }
352 }
353
354 let config = swc_core::ecma::preset_env::EnvConfig::from(
355 swc_core::ecma::preset_env::Config {
356 targets: Some(Targets::Versions(*versions)),
357 mode,
358 core_js,
359 skip,
360 include,
361 exclude,
362 shipped_proposals: extra.shipped_proposals.unwrap_or(false),
363 force_all_transforms: extra.force_all_transforms.unwrap_or(false),
364 debug: extra.debug.unwrap_or(false),
365 loose: extra.loose.unwrap_or(false),
366 ..Default::default()
367 },
368 );
369
370 apply_transform(
373 program,
374 helpers,
375 preset_env::transform_from_env::<&'_ dyn Comments>(
376 unresolved_mark,
377 Some(&comments),
378 config,
379 Assumptions::default(),
380 ),
381 )
382 }
383 EcmascriptInputTransform::TypeScript {
384 use_define_for_class_fields: _use_define_for_class_fields,
386 verbatim_module_syntax,
387 } => {
388 let config = Config {
389 verbatim_module_syntax: *verbatim_module_syntax,
390 ..Default::default()
391 };
392 apply_transform(
393 program,
394 helpers,
395 typescript(config, unresolved_mark, top_level_mark),
396 )
397 }
398 EcmascriptInputTransform::Decorators {
399 is_legacy,
400 is_ecma: _,
401 emit_decorators_metadata,
402 use_define_for_class_fields: _use_define_for_class_fields,
404 } => {
405 use swc_core::ecma::transforms::proposal::decorators::{Config, decorators};
406 let config = Config {
407 legacy: *is_legacy,
408 emit_metadata: *emit_decorators_metadata,
409 ..Default::default()
410 };
411
412 apply_transform(program, helpers, decorators(config))
413 }
414 EcmascriptInputTransform::ReactCompilerRust { options, target } => {
415 apply_rust_react_compiler(program, ctx, helpers, *options, *target).await?
416 }
417 EcmascriptInputTransform::Plugin(transform) => {
418 transform.await?.transform(program, ctx).await?;
420 helpers
421 }
422 })
423 }
424}
425
426#[turbo_tasks::value]
427struct ReactCompilerIssue {
428 source: IssueSource,
429 message: RcStr,
430 severity: IssueSeverity,
431}
432
433#[async_trait]
434#[turbo_tasks::value_impl]
435impl Issue for ReactCompilerIssue {
436 fn severity(&self) -> IssueSeverity {
437 self.severity
438 }
439
440 async fn file_path(&self) -> anyhow::Result<FileSystemPath> {
441 self.source.file_path().await
442 }
443
444 fn source(&self) -> Option<IssueSource> {
445 Some(self.source)
446 }
447
448 fn stage(&self) -> IssueStage {
449 IssueStage::Transform
450 }
451
452 async fn title(&self) -> anyhow::Result<StyledString> {
453 Ok(StyledString::Text(rcstr!("React Compiler")))
454 }
455
456 async fn description(&self) -> anyhow::Result<Option<StyledString>> {
457 Ok(Some(StyledString::Text(self.message.clone())))
458 }
459}
460
461fn has_react_compiler_opt_in_directive(statements: &[Stmt]) -> bool {
465 for statement in statements {
466 if !statement.directive_continue() {
467 break;
468 }
469
470 let Stmt::Expr(expression) = statement else {
471 continue;
472 };
473 let Expr::Lit(Lit::Str(value)) = &*expression.expr else {
474 continue;
475 };
476 if value
477 .value
478 .as_str()
479 .is_some_and(|value| matches!(value, "use memo" | "use forget"))
480 {
481 return true;
482 }
483 }
484
485 false
486}
487
488#[derive(Default)]
489struct ReactCompilerAnnotationFinder {
490 found: bool,
491}
492
493impl Visit for ReactCompilerAnnotationFinder {
494 fn visit_arrow_expr(&mut self, node: &ArrowExpr) {
495 if self.found {
496 return;
497 }
498 if let ArrowFunctionBody::FunctionBody(body) = &*node.body
499 && has_react_compiler_opt_in_directive(&body.stmts)
500 {
501 self.found = true;
502 return;
503 }
504
505 node.visit_children_with(self);
506 }
507
508 fn visit_function(&mut self, node: &Function) {
509 if self.found {
510 return;
511 }
512 if node
513 .body
514 .as_ref()
515 .is_some_and(|body| has_react_compiler_opt_in_directive(&body.stmts))
516 {
517 self.found = true;
518 return;
519 }
520
521 node.visit_children_with(self);
522 }
523}
524
525fn has_react_compiler_annotation(program: &Program) -> bool {
526 let mut finder = ReactCompilerAnnotationFinder::default();
527 finder.visit_program(program);
528 finder.found
529}
530
531fn should_run_rust_react_compiler(
532 program: &Program,
533 compilation_mode: ReactCompilerCompilationMode,
534) -> bool {
535 match compilation_mode {
536 ReactCompilerCompilationMode::Infer => {
537 swc_ecma_react_compiler::fast_check::is_required(program)
538 }
539 ReactCompilerCompilationMode::Annotation => has_react_compiler_annotation(program),
540 ReactCompilerCompilationMode::All => true,
541 }
542}
543
544async fn apply_rust_react_compiler(
545 program: &mut Program,
546 ctx: &TransformContext<'_>,
547 helpers: HelperData,
548 options: ReactCompilerTransformOptions,
549 target: ReactCompilerTarget,
550) -> Result<HelperData> {
551 let Program::Module(_) = program else {
552 return Ok(helpers);
553 };
554
555 if !should_run_rust_react_compiler(program, options.compilation_mode) {
559 return Ok(helpers);
560 }
561
562 let single_threaded_comments =
563 crate::swc_comments::swc_comments_to_single_threaded(ctx.comments);
564 let result = swc_ecma_react_compiler::transform(
565 program,
566 swc_ecma_react_compiler::SourceType::from_program(program),
567 ctx.source_text,
568 Some(&single_threaded_comments),
569 react_compiler_options(ctx.node_env.as_str(), ctx.file_name_str, options, target),
570 );
571
572 if let Some(compiled_program) = result.program {
577 *program = compiled_program;
578
579 program.mutate(swc_core::ecma::transforms::base::resolver(
585 ctx.unresolved_mark,
586 ctx.top_level_mark,
587 true,
588 ));
589 }
590
591 Ok(helpers)
592}
593
594fn react_compiler_options(
595 node_env: &str,
596 file_name: &str,
597 options: ReactCompilerTransformOptions,
598 target: ReactCompilerTarget,
599) -> react_compiler::entrypoint::plugin_options::PluginOptions {
600 use react_compiler::entrypoint::plugin_options::{CompilerTarget, PluginOptions};
601
602 let mut plugin_options = PluginOptions {
603 should_compile: true,
604 enable_reanimated: false,
605 is_dev: node_env != "production",
606 filename: Some(file_name.to_string()),
607 compilation_mode: options.compilation_mode.as_str().to_string(),
608 panic_threshold: "none".to_string(),
609 target: CompilerTarget::Version(target.as_str().to_string()),
610 gating: None,
611 dynamic_gating: None,
612 no_emit: false,
613 output_mode: None,
614 eslint_suppression_rules: None,
615 flow_suppressions: false,
616 ignore_use_no_forget: false,
617 custom_opt_out_directives: None,
618 environment: Default::default(),
619 source_code: None,
620 profiling: false,
621 debug: false,
622 };
623 if let Some(enable_preserve_existing_memoization_guarantees) =
624 options.enable_preserve_existing_memoization_guarantees
625 {
626 plugin_options
627 .environment
628 .enable_preserve_existing_memoization_guarantees =
629 enable_preserve_existing_memoization_guarantees;
630 }
631
632 plugin_options
633}
634
635fn apply_transform(program: &mut Program, helpers: HelperData, op: impl Pass) -> HelperData {
636 let helpers = Helpers::from_data(helpers);
637 HELPERS.set(&helpers, || {
638 program.mutate(op);
639 });
640 helpers.data()
641}
642
643pub fn remove_shebang(program: &mut Program) {
644 match program {
645 Program::Module(m) => {
646 m.shebang = None;
647 }
648 Program::Script(s) => {
649 s.shebang = None;
650 }
651 }
652}
653
654pub fn remove_directives(program: &mut Program) {
655 match program {
656 Program::Module(module) => {
657 let directive_count = module
658 .body
659 .iter()
660 .take_while(|i| match i {
661 ModuleItem::Stmt(stmt) => stmt.directive_continue(),
662 ModuleItem::ModuleDecl(_) => false,
663 })
664 .take_while(|i| match i {
665 ModuleItem::Stmt(stmt) => match stmt {
666 Stmt::Expr(ExprStmt { expr, .. }) => expr
667 .as_lit()
668 .and_then(|lit| lit.as_str())
669 .and_then(|str| str.raw.as_ref())
670 .is_some_and(|raw| {
671 raw.starts_with("\"use ") || raw.starts_with("'use ")
672 }),
673 _ => false,
674 },
675 ModuleItem::ModuleDecl(_) => false,
676 })
677 .count();
678 module.body.drain(0..directive_count);
679 }
680 Program::Script(script) => {
681 let directive_count = script
682 .body
683 .iter()
684 .take_while(|stmt| stmt.directive_continue())
685 .take_while(|stmt| match stmt {
686 Stmt::Expr(ExprStmt { expr, .. }) => expr
687 .as_lit()
688 .and_then(|lit| lit.as_str())
689 .and_then(|str| str.raw.as_ref())
690 .is_some_and(|raw| raw.starts_with("\"use ") || raw.starts_with("'use ")),
691 _ => false,
692 })
693 .count();
694 script.body.drain(0..directive_count);
695 }
696 }
697}
698
699#[cfg(test)]
700mod react_compiler_tests {
701 use swc_core::{
702 common::{DUMMY_SP, FileName, GLOBALS, SourceMap},
703 ecma::{
704 ast::{EsVersion, Module},
705 parser::{Syntax, TsSyntax, parse_file_as_program},
706 },
707 };
708
709 use super::*;
710
711 #[test]
712 fn preserves_default_environment_without_explicit_option() {
713 let options = react_compiler_options(
714 "production",
715 "input.js",
716 ReactCompilerTransformOptions::default(),
717 ReactCompilerTarget::React19,
718 );
719
720 assert!(
721 options
722 .environment
723 .enable_preserve_existing_memoization_guarantees
724 );
725 }
726
727 #[test]
728 fn forwards_explicit_memoization_preservation_option() {
729 let options = react_compiler_options(
730 "production",
731 "input.js",
732 ReactCompilerTransformOptions {
733 compilation_mode: ReactCompilerCompilationMode::Infer,
734 enable_preserve_existing_memoization_guarantees: Some(false),
735 },
736 ReactCompilerTarget::React19,
737 );
738
739 assert!(
740 !options
741 .environment
742 .enable_preserve_existing_memoization_guarantees
743 );
744 }
745
746 fn parse_program(source: &str) -> Program {
747 GLOBALS.set(&Default::default(), || {
748 let cm = SourceMap::default();
749 let fm = cm.new_source_file(
750 FileName::Custom("test.tsx".into()).into(),
751 source.to_owned(),
752 );
753 let mut errors = Vec::new();
754 let program = parse_file_as_program(
755 &fm,
756 Syntax::Typescript(TsSyntax {
757 tsx: true,
758 ..Default::default()
759 }),
760 EsVersion::EsNext,
761 None,
762 &mut errors,
763 )
764 .expect("test fixture should parse");
765 assert!(errors.is_empty(), "test fixture should not recover errors");
766 program
767 })
768 }
769
770 #[test]
771 fn compilation_modes_use_their_respective_fast_checks() {
772 let program = Program::Module(Module {
773 span: DUMMY_SP,
774 body: Vec::new(),
775 shebang: None,
776 });
777
778 for mode in [
779 ReactCompilerCompilationMode::Infer,
780 ReactCompilerCompilationMode::Annotation,
781 ] {
782 assert!(!should_run_rust_react_compiler(&program, mode));
783 }
784 assert!(should_run_rust_react_compiler(
785 &program,
786 ReactCompilerCompilationMode::All,
787 ));
788 }
789
790 #[test]
791 fn infer_mode_uses_upstream_conservative_fast_check() {
792 for source in [
793 "const Button = React.forwardRef((props, ref) => <button ref={ref} />);",
794 "function useCounter() { return React.useState(0); }",
795 "function helper() { 'use memo'; return 1; }",
796 ] {
797 assert!(should_run_rust_react_compiler(
798 &parse_program(source),
799 ReactCompilerCompilationMode::Infer
800 ));
801 }
802
803 for source in [
804 "export const answer = 42;",
805 "const user = getUser();",
806 "function helper() { log(); 'use memo'; }",
807 ] {
808 assert!(!should_run_rust_react_compiler(
809 &parse_program(source),
810 ReactCompilerCompilationMode::Infer
811 ));
812 }
813 }
814
815 #[test]
816 fn annotation_mode_only_runs_for_function_opt_in_directives() {
817 for source in [
818 "function helper() { 'use memo'; return 1; }",
819 "const helper = () => { 'use forget'; return 1; };",
820 "function outer() { function inner() { 'use memo'; return 1; } }",
821 ] {
822 assert!(should_run_rust_react_compiler(
823 &parse_program(source),
824 ReactCompilerCompilationMode::Annotation,
825 ));
826 }
827
828 for source in [
829 "function Component() { return <div />; }",
830 "function useCounter() { return useState(0); }",
831 "function helper() { log(); 'use memo'; }",
832 "'use memo'; export const answer = 42;",
833 "function helper() { 'use memo if(featureFlag)'; return 1; }",
834 "function helper() { 'use no memo'; return 1; }",
835 ] {
836 assert!(!should_run_rust_react_compiler(
837 &parse_program(source),
838 ReactCompilerCompilationMode::Annotation,
839 ));
840 }
841 }
842
843 #[test]
844 fn all_mode_remains_unconditional() {
845 for source in [
846 "export const answer = 42;",
847 "function helper() { return 1; }",
848 ] {
849 assert!(should_run_rust_react_compiler(
850 &parse_program(source),
851 ReactCompilerCompilationMode::All,
852 ));
853 }
854 }
855}