@reckona/mreact-compiler 0.0.161 → 0.0.163

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ import { readArray, readObject, readSource, unwrapOxcParentheses } from "./oxc-n
8
8
  // interpreter semantics never get half-applied.
9
9
 
10
10
  const COMPAT_CREATE_ELEMENT_SOURCES = new Set(["react", "@reckona/mreact-compat"]);
11
+ const COMPAT_RENDER_TO_STRING_SOURCES = new Set(["@reckona/mreact-compat"]);
11
12
 
12
13
  export function collectCompatCreateElementNames(program: unknown): Set<string> {
13
14
  const names = new Set<string>();
@@ -44,6 +45,41 @@ export function collectCompatCreateElementNames(program: unknown): Set<string> {
44
45
  return names;
45
46
  }
46
47
 
48
+ export function collectCompatRenderToStringNames(program: unknown): Set<string> {
49
+ const names = new Set<string>();
50
+
51
+ for (const statement of readArray(readObject(program).body)) {
52
+ const declaration = readObject(statement);
53
+
54
+ if (declaration.type !== "ImportDeclaration") {
55
+ continue;
56
+ }
57
+
58
+ const source = readObject(declaration.source);
59
+
60
+ if (typeof source.value !== "string" || !COMPAT_RENDER_TO_STRING_SOURCES.has(source.value)) {
61
+ continue;
62
+ }
63
+
64
+ for (const specifier of readArray(declaration.specifiers)) {
65
+ const specifierObject = readObject(specifier);
66
+
67
+ if (specifierObject.type !== "ImportSpecifier") {
68
+ continue;
69
+ }
70
+
71
+ const imported = readObject(specifierObject.imported);
72
+ const local = readObject(specifierObject.local);
73
+
74
+ if (imported.name === "renderToString" && typeof local.name === "string") {
75
+ names.add(local.name);
76
+ }
77
+ }
78
+ }
79
+
80
+ return names;
81
+ }
82
+
47
83
  // Mirrors packages/react-compat/src/server-render.ts attribute serialization
48
84
  // for compile-time-known prop values. Keep these tables in sync with the
49
85
  // interpreter; parity tests compare emitted bytes against renderToString.
@@ -363,6 +399,40 @@ export function analyzeCompatCreateElementRoot(
363
399
  return lowerCreateElementCall(code, unwrapOxcParentheses(expression), scope);
364
400
  }
365
401
 
402
+ export function analyzeCompatCreateElementFunctionRoot(
403
+ code: string,
404
+ functionLike: Record<string, unknown>,
405
+ names: ReadonlySet<string>,
406
+ ): JsxNodeIr | undefined {
407
+ if (names.size === 0) {
408
+ return undefined;
409
+ }
410
+
411
+ const shadowed = collectFunctionShadowedNames(functionLike, names);
412
+ const scope: CompatCreateElementScope = { names, shadowed };
413
+ const body = unwrapOxcParentheses(readObject(functionLike.body));
414
+
415
+ if (body.type !== "BlockStatement") {
416
+ return lowerCreateElementCall(code, body, scope);
417
+ }
418
+
419
+ for (const statement of readArray(body.body)) {
420
+ const statementObject = readObject(statement);
421
+
422
+ if (statementObject.type !== "ReturnStatement") {
423
+ continue;
424
+ }
425
+
426
+ return lowerCreateElementCall(
427
+ code,
428
+ unwrapOxcParentheses(readObject(statementObject.argument)),
429
+ scope,
430
+ );
431
+ }
432
+
433
+ return undefined;
434
+ }
435
+
366
436
  function lowerCreateElementCall(
367
437
  code: string,
368
438
  expression: Record<string, unknown>,
@@ -493,6 +563,45 @@ function lowerCreateElementChild(
493
563
  }
494
564
  }
495
565
 
566
+ if (child.type === "ConditionalExpression") {
567
+ const whenTrue = lowerCreateElementDynamicBranch(code, readObject(child.consequent), scope);
568
+ const whenFalse = lowerCreateElementDynamicBranch(code, readObject(child.alternate), scope);
569
+
570
+ if (whenTrue !== undefined && whenFalse !== undefined) {
571
+ return [
572
+ {
573
+ kind: "conditional",
574
+ conditionCode: readSource(code, readObject(child.test)),
575
+ whenTrue,
576
+ whenFalse,
577
+ },
578
+ ];
579
+ }
580
+ }
581
+
582
+ if (child.type === "LogicalExpression" && child.operator === "&&") {
583
+ const whenTrue = lowerCreateElementDynamicBranch(code, readObject(child.right), scope);
584
+
585
+ if (whenTrue !== undefined) {
586
+ const conditionValueName = logicalConditionValueName(readObject(child.left));
587
+
588
+ return [
589
+ {
590
+ kind: "conditional",
591
+ conditionCode: readSource(code, readObject(child.left)),
592
+ conditionValueName,
593
+ whenTrue,
594
+ whenFalse: [
595
+ {
596
+ kind: "expr",
597
+ code: renderableFalsyConditionValueCode(conditionValueName),
598
+ },
599
+ ],
600
+ },
601
+ ];
602
+ }
603
+ }
604
+
496
605
  // Provably-string expressions escape directly (and stay batchable); the
497
606
  // interpreter would emit the same escaped bytes for them.
498
607
  if (isProvablyStringExpression(child, scope)) {
@@ -505,6 +614,28 @@ function lowerCreateElementChild(
505
614
  return [{ kind: "expr", code: readSource(code, child), renderMode: "compat-child" }];
506
615
  }
507
616
 
617
+ function lowerCreateElementDynamicBranch(
618
+ code: string,
619
+ expression: Record<string, unknown>,
620
+ scope: CompatCreateElementScope,
621
+ ): JsxNodeIr[] | undefined {
622
+ const unwrapped = unwrapOxcParentheses(expression);
623
+
624
+ if (unwrapped.type === "Literal" && (unwrapped.value === null || unwrapped.value === false)) {
625
+ return [];
626
+ }
627
+
628
+ return lowerCreateElementChild(code, unwrapped, scope);
629
+ }
630
+
631
+ function logicalConditionValueName(expression: Record<string, unknown>): string {
632
+ return `__mreactLogical_${typeof expression.start === "number" ? expression.start : "value"}`;
633
+ }
634
+
635
+ function renderableFalsyConditionValueCode(name: string): string {
636
+ return `((typeof ${name} === "number" || typeof ${name} === "bigint") ? ${name} : null)`;
637
+ }
638
+
508
639
  function isProvablyStringExpression(
509
640
  expression: Record<string, unknown>,
510
641
  scope: CompatCreateElementScope,
@@ -623,31 +754,7 @@ export function hasLowerableCompatCreateElementReturn(
623
754
  functionLike: Record<string, unknown>,
624
755
  names: ReadonlySet<string>,
625
756
  ): boolean {
626
- if (names.size === 0) {
627
- return false;
628
- }
629
-
630
- const shadowed = collectFunctionShadowedNames(functionLike, names);
631
- const scope: CompatCreateElementScope = { names, shadowed };
632
- const body = unwrapOxcParentheses(readObject(functionLike.body));
633
-
634
- if (body.type !== "BlockStatement") {
635
- return lowerCreateElementCall(code, body, scope) !== undefined;
636
- }
637
-
638
- for (const statement of readArray(body.body)) {
639
- const statementObject = readObject(statement);
640
-
641
- if (statementObject.type !== "ReturnStatement") {
642
- continue;
643
- }
644
-
645
- const argument = unwrapOxcParentheses(readObject(statementObject.argument));
646
-
647
- return lowerCreateElementCall(code, argument, scope) !== undefined;
648
- }
649
-
650
- return false;
757
+ return analyzeCompatCreateElementFunctionRoot(code, functionLike, names) !== undefined;
651
758
  }
652
759
 
653
760
  export function collectFunctionShadowedNames(
package/src/oxc.ts CHANGED
@@ -71,8 +71,10 @@ import {
71
71
  type OxcChildAnalysisContext,
72
72
  } from "./oxc-child-analysis.js";
73
73
  import {
74
+ analyzeCompatCreateElementFunctionRoot,
74
75
  analyzeCompatCreateElementRoot,
75
76
  collectCompatCreateElementNames,
77
+ collectCompatRenderToStringNames,
76
78
  collectFunctionShadowedNames,
77
79
  hasLowerableCompatCreateElementReturn,
78
80
  } from "./oxc-compat-create-element.js";
@@ -249,11 +251,23 @@ function analyzeOxcToIr(
249
251
  : undefined;
250
252
  const localJsxReturnFunctionNames =
251
253
  target === "server" ? collectOxcLocalJsxReturnFunctionNames(program) : new Set<string>();
252
- // Stream emit keeps interpreting compat trees for now; only the string
253
- // pipeline lowers createElement calls.
254
254
  const compatCreateElementNames =
255
- target === "server" && options?.serverOutput !== "stream"
256
- ? collectCompatCreateElementNames(program)
255
+ target === "server" ? collectCompatCreateElementNames(program) : new Set<string>();
256
+ const compatRenderToStringNames =
257
+ target === "server" ? collectCompatRenderToStringNames(program) : new Set<string>();
258
+ const compatCreateElementLocalFunctionLikes =
259
+ target === "server"
260
+ ? collectCompatCreateElementLocalFunctionLikes(program)
261
+ : new Map<string, Record<string, unknown>>();
262
+ const compatRenderToStringLowerableTargets =
263
+ target === "server"
264
+ ? collectCompatRenderToStringLowerableTargets(
265
+ code,
266
+ body,
267
+ compatCreateElementNames,
268
+ compatRenderToStringNames,
269
+ compatCreateElementLocalFunctionLikes,
270
+ )
257
271
  : new Set<string>();
258
272
  const localJsxHelperHtmlParameters =
259
273
  target === "server"
@@ -281,7 +295,15 @@ function analyzeOxcToIr(
281
295
 
282
296
  if (
283
297
  isOxcJsxComponentStatement(statement, localJsxReturnFunctionNames) ||
284
- isCompatCreateElementComponentStatement(code, statement, compatCreateElementNames) ||
298
+ isCompatCreateElementComponentStatement(
299
+ code,
300
+ statement,
301
+ compatCreateElementNames,
302
+ compatRenderToStringNames,
303
+ compatCreateElementLocalFunctionLikes,
304
+ compatRenderToStringLowerableTargets,
305
+ options?.serverOutput,
306
+ ) ||
285
307
  (options?.compatReactNodeReturn === true && isOxcExportedFunctionLike(statement))
286
308
  ) {
287
309
  const declaration = readObject(readObject(statement).declaration);
@@ -346,6 +368,9 @@ function analyzeOxcToIr(
346
368
  diagnostics,
347
369
  options?.bodyStatementJsx ?? "dom-node",
348
370
  compatCreateElementNames,
371
+ compatRenderToStringNames,
372
+ compatCreateElementLocalFunctionLikes,
373
+ compatRenderToStringLowerableTargets,
349
374
  moduleRenderValueBindings,
350
375
  options?.compatReactNodeReturn === true,
351
376
  options?.serverOutput,
@@ -557,32 +582,292 @@ function readCompatCreateElementPlainComponent(
557
582
  return undefined;
558
583
  }
559
584
 
585
+ function collectCompatCreateElementLocalFunctionLikes(
586
+ program: unknown,
587
+ ): Map<string, Record<string, unknown>> {
588
+ const functionLikes = new Map<string, Record<string, unknown>>();
589
+
590
+ for (const statement of readArray(readObject(program).body)) {
591
+ const object = readObject(statement);
592
+ const declaration =
593
+ object.type === "ExportNamedDeclaration" ? readObject(object.declaration) : object;
594
+
595
+ if (declaration.type === "FunctionDeclaration") {
596
+ const id = readObject(declaration.id);
597
+
598
+ if (typeof id.name === "string") {
599
+ functionLikes.set(id.name, declaration);
600
+ }
601
+ continue;
602
+ }
603
+
604
+ if (declaration.type !== "VariableDeclaration") {
605
+ continue;
606
+ }
607
+
608
+ for (const declarator of readArray(declaration.declarations)) {
609
+ const declaratorObject = readObject(declarator);
610
+ const id = readObject(declaratorObject.id);
611
+ const initializer = unwrapOxcComponentFunctionLikeInitializer(
612
+ readObject(declaratorObject.init),
613
+ );
614
+
615
+ if (typeof id.name === "string" && initializer !== undefined) {
616
+ functionLikes.set(id.name, initializer);
617
+ }
618
+ }
619
+ }
620
+
621
+ return functionLikes;
622
+ }
623
+
624
+ function collectCompatRenderToStringLowerableTargets(
625
+ code: string,
626
+ body: readonly unknown[],
627
+ createElementNames: ReadonlySet<string>,
628
+ renderToStringNames: ReadonlySet<string>,
629
+ localFunctionLikes: ReadonlyMap<string, Record<string, unknown>>,
630
+ ): Set<string> {
631
+ const targets = new Set<string>();
632
+
633
+ if (createElementNames.size === 0 || renderToStringNames.size === 0) {
634
+ return targets;
635
+ }
636
+
637
+ for (const statement of body) {
638
+ const functionLike = unwrapOxcStatementFunctionLike(statement);
639
+
640
+ if (functionLike === undefined) {
641
+ continue;
642
+ }
643
+
644
+ const targetName = readCompatRenderToStringWrapperTargetName(
645
+ code,
646
+ functionLike,
647
+ renderToStringNames,
648
+ );
649
+ const targetFunctionLike =
650
+ targetName === undefined ? undefined : localFunctionLikes.get(targetName);
651
+
652
+ if (
653
+ targetName !== undefined &&
654
+ targetFunctionLike !== undefined &&
655
+ analyzeCompatCreateElementFunctionRoot(code, targetFunctionLike, createElementNames) !==
656
+ undefined
657
+ ) {
658
+ targets.add(targetName);
659
+ }
660
+ }
661
+
662
+ return targets;
663
+ }
664
+
665
+ function unwrapOxcStatementFunctionLike(statement: unknown): Record<string, unknown> | undefined {
666
+ const object = readObject(statement);
667
+ const declaration =
668
+ object.type === "ExportNamedDeclaration" || object.type === "ExportDefaultDeclaration"
669
+ ? readObject(object.declaration)
670
+ : object;
671
+
672
+ if (declaration.type === "FunctionDeclaration") {
673
+ return declaration;
674
+ }
675
+
676
+ if (declaration.type !== "VariableDeclaration") {
677
+ return unwrapOxcComponentFunctionLikeInitializer(declaration);
678
+ }
679
+
680
+ for (const declarator of readArray(declaration.declarations)) {
681
+ const initializer = unwrapOxcComponentFunctionLikeInitializer(
682
+ readObject(readObject(declarator).init),
683
+ );
684
+
685
+ if (initializer !== undefined) {
686
+ return initializer;
687
+ }
688
+ }
689
+
690
+ return undefined;
691
+ }
692
+
693
+ function readCompatRenderToStringWrapperTargetName(
694
+ code: string,
695
+ functionLike: Record<string, unknown>,
696
+ renderToStringNames: ReadonlySet<string>,
697
+ ): string | undefined {
698
+ const expression = readCompatRenderToStringWrapperReturnExpression(functionLike);
699
+
700
+ if (expression === undefined) {
701
+ return undefined;
702
+ }
703
+
704
+ return readCompatRenderToStringTargetName(
705
+ expression,
706
+ renderToStringNames,
707
+ collectFunctionShadowedNames(functionLike, renderToStringNames),
708
+ );
709
+ }
710
+
711
+ function readCompatRenderToStringWrapperReturnExpression(
712
+ functionLike: Record<string, unknown>,
713
+ ): Record<string, unknown> | undefined {
714
+ const body = unwrapOxcParentheses(readObject(functionLike.body));
715
+
716
+ if (body.type !== "BlockStatement") {
717
+ return body;
718
+ }
719
+
720
+ for (const statement of readArray(body.body)) {
721
+ const statementObject = readObject(statement);
722
+
723
+ if (statementObject.type === "ReturnStatement") {
724
+ return unwrapOxcParentheses(readObject(statementObject.argument));
725
+ }
726
+ }
727
+
728
+ return undefined;
729
+ }
730
+
731
+ function readCompatRenderToStringTargetName(
732
+ expression: Record<string, unknown>,
733
+ renderToStringNames: ReadonlySet<string>,
734
+ shadowedNames: ReadonlySet<string>,
735
+ ): string | undefined {
736
+ if (
737
+ renderToStringNames.size === 0 ||
738
+ expression.type !== "CallExpression" ||
739
+ expression.optional === true
740
+ ) {
741
+ return undefined;
742
+ }
743
+
744
+ const callee = unwrapOxcParentheses(readObject(expression.callee));
745
+
746
+ if (
747
+ callee.type !== "Identifier" ||
748
+ typeof callee.name !== "string" ||
749
+ !renderToStringNames.has(callee.name) ||
750
+ shadowedNames.has(callee.name)
751
+ ) {
752
+ return undefined;
753
+ }
754
+
755
+ const args = readArray(expression.arguments);
756
+
757
+ if (args.length !== 1) {
758
+ return undefined;
759
+ }
760
+
761
+ const target = unwrapOxcParentheses(readObject(args[0]));
762
+
763
+ return target.type === "Identifier" && typeof target.name === "string" ? target.name : undefined;
764
+ }
765
+
766
+ function hasCompatRenderToStringWrapperReturn(
767
+ code: string,
768
+ functionLike: Record<string, unknown>,
769
+ renderToStringNames: ReadonlySet<string>,
770
+ ): boolean {
771
+ return (
772
+ readCompatRenderToStringWrapperTargetName(code, functionLike, renderToStringNames) !== undefined
773
+ );
774
+ }
775
+
776
+ function analyzeCompatRenderToStringWrapperRoot(
777
+ code: string,
778
+ functionLike: Record<string, unknown>,
779
+ returnExpression: Record<string, unknown>,
780
+ createElementNames: ReadonlySet<string>,
781
+ renderToStringNames: ReadonlySet<string>,
782
+ localFunctionLikes: ReadonlyMap<string, Record<string, unknown>>,
783
+ ): ComponentIr["root"] | undefined {
784
+ const targetName = readCompatRenderToStringTargetName(
785
+ returnExpression,
786
+ renderToStringNames,
787
+ collectFunctionShadowedNames(functionLike, renderToStringNames),
788
+ );
789
+
790
+ if (targetName === undefined) {
791
+ return undefined;
792
+ }
793
+
794
+ const targetFunctionLike = localFunctionLikes.get(targetName);
795
+ const lowered =
796
+ targetFunctionLike === undefined
797
+ ? undefined
798
+ : analyzeCompatCreateElementFunctionRoot(code, targetFunctionLike, createElementNames);
799
+
800
+ if (lowered !== undefined) {
801
+ return lowered;
802
+ }
803
+
804
+ return {
805
+ kind: "expr",
806
+ code: normalizeOxcExpressionCode(readSource(code, returnExpression)),
807
+ renderMode: "html",
808
+ };
809
+ }
810
+
560
811
  function isCompatCreateElementComponentStatement(
561
812
  code: string,
562
813
  statement: unknown,
563
814
  names: ReadonlySet<string>,
815
+ renderToStringNames: ReadonlySet<string>,
816
+ localFunctionLikes: ReadonlyMap<string, Record<string, unknown>>,
817
+ renderToStringLowerableTargets: ReadonlySet<string>,
818
+ serverOutput?: AnalyzeModuleOptions["serverOutput"],
564
819
  ): boolean {
565
- if (names.size === 0) {
820
+ if (names.size === 0 && renderToStringNames.size === 0) {
566
821
  return false;
567
822
  }
568
823
 
569
824
  const object = readObject(statement);
570
825
 
571
826
  if (object.type === "ExportDefaultDeclaration") {
572
- return readCompatCreateElementFunctionLike(code, readObject(object.declaration), names) !== undefined;
827
+ const functionLike = unwrapOxcComponentFunctionLikeInitializer(readObject(object.declaration));
828
+
829
+ return (
830
+ readCompatCreateElementFunctionLike(code, readObject(object.declaration), names) !==
831
+ undefined ||
832
+ (functionLike !== undefined &&
833
+ hasCompatRenderToStringWrapperReturn(code, functionLike, renderToStringNames))
834
+ );
573
835
  }
574
836
 
575
837
  if (object.type === "ExportNamedDeclaration") {
576
838
  const declaration = readObject(object.declaration);
577
839
 
578
840
  if (declaration.type === "FunctionDeclaration") {
579
- return hasLowerableCompatCreateElementReturn(code, declaration, names);
841
+ return (
842
+ hasLowerableCompatCreateElementReturn(code, declaration, names) ||
843
+ hasCompatRenderToStringWrapperReturn(code, declaration, renderToStringNames)
844
+ );
580
845
  }
581
846
 
582
847
  return readCompatCreateElementPlainComponent(code, declaration, names) !== undefined;
583
848
  }
584
849
 
585
- return readCompatCreateElementPlainComponent(code, statement, names) !== undefined;
850
+ const plainComponent = readCompatCreateElementPlainComponent(code, statement, names);
851
+
852
+ if (serverOutput === "stream") {
853
+ return (
854
+ plainComponent !== undefined &&
855
+ renderToStringLowerableTargets.has(plainComponent.name) &&
856
+ localFunctionLikes.get(plainComponent.name) === plainComponent.initializer
857
+ );
858
+ }
859
+
860
+ if (plainComponent !== undefined) {
861
+ return true;
862
+ }
863
+
864
+ const functionLike = unwrapOxcStatementFunctionLike(statement);
865
+
866
+ if (functionLike === undefined) {
867
+ return false;
868
+ }
869
+
870
+ return hasCompatRenderToStringWrapperReturn(code, functionLike, renderToStringNames);
586
871
  }
587
872
 
588
873
  function analyzeOxcComponent(
@@ -593,6 +878,9 @@ function analyzeOxcComponent(
593
878
  diagnostics: Diagnostic[],
594
879
  bodyStatementJsx: OxcBodyStatementJsxMode,
595
880
  compatCreateElementNames: ReadonlySet<string>,
881
+ compatRenderToStringNames: ReadonlySet<string>,
882
+ compatCreateElementLocalFunctionLikes: ReadonlyMap<string, Record<string, unknown>>,
883
+ compatRenderToStringLowerableTargets: ReadonlySet<string>,
596
884
  moduleRenderValueBindings: Set<string>,
597
885
  compatReactNodeReturn: boolean,
598
886
  serverOutput: AnalyzeModuleOptions["serverOutput"],
@@ -610,7 +898,8 @@ function analyzeOxcComponent(
610
898
  if (
611
899
  declaration === undefined ||
612
900
  (!hasOxcFunctionLikeComponentReturn(declaration) &&
613
- !hasLowerableCompatCreateElementReturn(code, declaration, compatCreateElementNames))
901
+ !hasLowerableCompatCreateElementReturn(code, declaration, compatCreateElementNames) &&
902
+ !hasCompatRenderToStringWrapperReturn(code, declaration, compatRenderToStringNames))
614
903
  ) {
615
904
  return [];
616
905
  }
@@ -628,6 +917,8 @@ function analyzeOxcComponent(
628
917
  diagnostics,
629
918
  bodyStatementJsx,
630
919
  compatCreateElementNames,
920
+ compatRenderToStringNames,
921
+ compatCreateElementLocalFunctionLikes,
631
922
  moduleRenderValueBindings,
632
923
  compatReactNodeReturn,
633
924
  serverOutput,
@@ -644,12 +935,18 @@ function analyzeOxcComponent(
644
935
  if (object.type !== "ExportNamedDeclaration") {
645
936
  const plainComponent =
646
937
  readOxcPlainComponent(statement) ??
647
- readCompatCreateElementPlainComponent(code, statement, compatCreateElementNames);
938
+ (serverOutput === "stream"
939
+ ? undefined
940
+ : readCompatCreateElementPlainComponent(code, statement, compatCreateElementNames));
648
941
 
649
942
  if (plainComponent === undefined) {
650
943
  return [];
651
944
  }
652
945
 
946
+ if (compatRenderToStringLowerableTargets.has(plainComponent.name)) {
947
+ return [];
948
+ }
949
+
653
950
  return [
654
951
  {
655
952
  ...analyzeOxcFunctionLikeComponent(
@@ -662,6 +959,8 @@ function analyzeOxcComponent(
662
959
  diagnostics,
663
960
  bodyStatementJsx,
664
961
  compatCreateElementNames,
962
+ compatRenderToStringNames,
963
+ compatCreateElementLocalFunctionLikes,
665
964
  moduleRenderValueBindings,
666
965
  compatReactNodeReturn,
667
966
  serverOutput,
@@ -698,6 +997,8 @@ function analyzeOxcComponent(
698
997
  diagnostics,
699
998
  bodyStatementJsx,
700
999
  compatCreateElementNames,
1000
+ compatRenderToStringNames,
1001
+ compatCreateElementLocalFunctionLikes,
701
1002
  moduleRenderValueBindings,
702
1003
  compatReactNodeReturn,
703
1004
  serverOutput,
@@ -715,7 +1016,8 @@ function analyzeOxcComponent(
715
1016
  (!compatReactNodeReturn &&
716
1017
  !hasComponentReturn(declaration.body) &&
717
1018
  !hasLocalJsxHelperCallReturn(declaration.body, localJsxReturnFunctionNames) &&
718
- !hasLowerableCompatCreateElementReturn(code, declaration, compatCreateElementNames))
1019
+ !hasLowerableCompatCreateElementReturn(code, declaration, compatCreateElementNames) &&
1020
+ !hasCompatRenderToStringWrapperReturn(code, declaration, compatRenderToStringNames))
719
1021
  ) {
720
1022
  return [];
721
1023
  }
@@ -737,6 +1039,8 @@ function analyzeOxcComponent(
737
1039
  diagnostics,
738
1040
  bodyStatementJsx,
739
1041
  compatCreateElementNames,
1042
+ compatRenderToStringNames,
1043
+ compatCreateElementLocalFunctionLikes,
740
1044
  moduleRenderValueBindings,
741
1045
  compatReactNodeReturn,
742
1046
  serverOutput,
@@ -787,6 +1091,8 @@ function analyzeOxcFunctionLikeComponent(
787
1091
  diagnostics: Diagnostic[],
788
1092
  bodyStatementJsx: OxcBodyStatementJsxMode,
789
1093
  compatCreateElementNames: ReadonlySet<string>,
1094
+ compatRenderToStringNames: ReadonlySet<string>,
1095
+ compatCreateElementLocalFunctionLikes: ReadonlyMap<string, Record<string, unknown>>,
790
1096
  moduleRenderValueBindings: Set<string>,
791
1097
  compatReactNodeReturn: boolean,
792
1098
  serverOutput: AnalyzeModuleOptions["serverOutput"],
@@ -873,6 +1179,14 @@ function analyzeOxcFunctionLikeComponent(
873
1179
  names: compatCreateElementNames,
874
1180
  shadowed: collectFunctionShadowedNames(functionLike, compatCreateElementNames),
875
1181
  })) ??
1182
+ analyzeCompatRenderToStringWrapperRoot(
1183
+ code,
1184
+ functionLike,
1185
+ returnExpression,
1186
+ compatCreateElementNames,
1187
+ compatRenderToStringNames,
1188
+ compatCreateElementLocalFunctionLikes,
1189
+ ) ??
876
1190
  (isJsxRoot(returnExpression.type) || returnExpression.type === "JSXFragment"
877
1191
  ? analyzeOxcJsxNode(code, returnExpression, childAnalysisContext)
878
1192
  : isOxcComponentCallExpression(returnExpression)