@reckona/mreact-compiler 0.0.97 → 0.0.98

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.
Files changed (50) hide show
  1. package/dist/diagnostics.d.ts +1 -0
  2. package/dist/diagnostics.d.ts.map +1 -1
  3. package/dist/diagnostics.js +8 -0
  4. package/dist/diagnostics.js.map +1 -1
  5. package/dist/emit-client.js +14 -9
  6. package/dist/emit-client.js.map +1 -1
  7. package/dist/emit-compat.js +5 -1
  8. package/dist/emit-compat.js.map +1 -1
  9. package/dist/emit-server-stream.js +52 -3
  10. package/dist/emit-server-stream.js.map +1 -1
  11. package/dist/emit-server.js +51 -10
  12. package/dist/emit-server.js.map +1 -1
  13. package/dist/ir.d.ts +1 -0
  14. package/dist/ir.d.ts.map +1 -1
  15. package/dist/ir.js.map +1 -1
  16. package/dist/oxc-child-analysis.d.ts.map +1 -1
  17. package/dist/oxc-child-analysis.js +41 -7
  18. package/dist/oxc-child-analysis.js.map +1 -1
  19. package/dist/oxc-component-detection.d.ts +5 -2
  20. package/dist/oxc-component-detection.d.ts.map +1 -1
  21. package/dist/oxc-component-detection.js +80 -3
  22. package/dist/oxc-component-detection.js.map +1 -1
  23. package/dist/oxc-component-props.d.ts +1 -1
  24. package/dist/oxc-component-props.d.ts.map +1 -1
  25. package/dist/oxc-component-props.js +1 -1
  26. package/dist/oxc-component-props.js.map +1 -1
  27. package/dist/oxc-component-references.js +2 -2
  28. package/dist/oxc-component-references.js.map +1 -1
  29. package/dist/oxc-runtime-emit.d.ts.map +1 -1
  30. package/dist/oxc-runtime-emit.js +10 -2
  31. package/dist/oxc-runtime-emit.js.map +1 -1
  32. package/dist/oxc.d.ts.map +1 -1
  33. package/dist/oxc.js +109 -20
  34. package/dist/oxc.js.map +1 -1
  35. package/dist/transform.js +29 -11
  36. package/dist/transform.js.map +1 -1
  37. package/package.json +2 -2
  38. package/src/diagnostics.ts +10 -0
  39. package/src/emit-client.ts +20 -10
  40. package/src/emit-compat.ts +6 -1
  41. package/src/emit-server-stream.ts +67 -3
  42. package/src/emit-server.ts +64 -12
  43. package/src/ir.ts +1 -0
  44. package/src/oxc-child-analysis.ts +63 -18
  45. package/src/oxc-component-detection.ts +145 -2
  46. package/src/oxc-component-props.ts +2 -1
  47. package/src/oxc-component-references.ts +2 -2
  48. package/src/oxc-runtime-emit.ts +12 -2
  49. package/src/oxc.ts +167 -5
  50. package/src/transform.ts +42 -10
@@ -2,6 +2,7 @@ import {
2
2
  invalidJsxExpressionDiagnostic,
3
3
  unserializableAwaitValueDiagnostic,
4
4
  unsupportedComponentReferenceDiagnostic,
5
+ unsupportedJsxSpreadChildDiagnostic,
5
6
  } from "./diagnostics.js";
6
7
  import type { AsyncBoundaryIr, JsxElementIr, JsxNodeIr } from "./ir.js";
7
8
  import type { OxcBodyStatementJsxMode } from "./oxc-analysis-types.js";
@@ -135,7 +136,14 @@ export function analyzeOxcJsxNode(
135
136
  const analyzeJsxNode = (
136
137
  child: Record<string, unknown>,
137
138
  childBodyStatementJsx: OxcBodyStatementJsxMode = bodyStatementJsx,
138
- ) => analyzeOxcJsxNode(code, child, context, childBodyStatementJsx);
139
+ shadowNames: readonly string[] = [],
140
+ ) =>
141
+ analyzeOxcJsxNode(
142
+ code,
143
+ child,
144
+ shadowOxcReactiveAliases(context, shadowNames),
145
+ childBodyStatementJsx,
146
+ );
139
147
  const consumerRenderProp = tagName.endsWith(".Consumer")
140
148
  ? readOxcConsumerRenderProp(
141
149
  code,
@@ -344,6 +352,11 @@ export function analyzeOxcChildren(
344
352
  );
345
353
  }
346
354
 
355
+ if (object.type === "JSXSpreadChild") {
356
+ context.diagnostics.push(unsupportedJsxSpreadChildDiagnostic(getOxcLocation(code, object)));
357
+ return [];
358
+ }
359
+
347
360
  return [];
348
361
  });
349
362
  }
@@ -401,17 +414,22 @@ export function analyzeOxcExpressionChild(
401
414
  bodyStatementJsx,
402
415
  );
403
416
 
417
+ const leftExpression = readObject(unwrappedExpression.left);
418
+ const conditionValueName = logicalConditionValueName(leftExpression);
419
+
404
420
  if (unwrappedExpression.operator === "&&") {
405
421
  return [
406
422
  {
407
423
  kind: "conditional",
408
- conditionCode: readOxcReactiveExpressionCode(
409
- code,
410
- readObject(unwrappedExpression.left),
411
- context,
412
- ),
424
+ conditionCode: readOxcReactiveExpressionCode(code, leftExpression, context),
425
+ conditionValueName,
413
426
  whenTrue: rightBranch,
414
- whenFalse: [],
427
+ whenFalse: [
428
+ {
429
+ kind: "expr",
430
+ code: renderableFalsyConditionValueCode(conditionValueName),
431
+ },
432
+ ],
415
433
  },
416
434
  ];
417
435
  }
@@ -420,19 +438,12 @@ export function analyzeOxcExpressionChild(
420
438
  return [
421
439
  {
422
440
  kind: "conditional",
423
- conditionCode: readOxcReactiveExpressionCode(
424
- code,
425
- readObject(unwrappedExpression.left),
426
- context,
427
- ),
441
+ conditionCode: readOxcReactiveExpressionCode(code, leftExpression, context),
442
+ conditionValueName,
428
443
  whenTrue: [
429
444
  {
430
445
  kind: "expr",
431
- code: readOxcReactiveExpressionCode(
432
- code,
433
- readObject(unwrappedExpression.left),
434
- context,
435
- ),
446
+ code: conditionValueName,
436
447
  },
437
448
  ],
438
449
  whenFalse: rightBranch,
@@ -613,7 +624,11 @@ function analyzeOxcListExpression(
613
624
  const itemName = String(readObject(readArray(renderer.params)[0]).name ?? "_item");
614
625
  const indexName = readObject(readArray(renderer.params)[1]).name;
615
626
  const arrayName = readObject(readArray(renderer.params)[2]).name;
616
- const rendererBody = analyzeOxcListRenderer(code, renderer, context, bodyStatementJsx);
627
+ const rendererContext = shadowOxcReactiveAliases(
628
+ context,
629
+ [itemName, indexName, arrayName].filter((name): name is string => typeof name === "string"),
630
+ );
631
+ const rendererBody = analyzeOxcListRenderer(code, renderer, rendererContext, bodyStatementJsx);
617
632
 
618
633
  if (rendererBody === undefined) {
619
634
  return undefined;
@@ -802,6 +817,36 @@ function resolveOxcBodyStatementJsx(context: OxcChildAnalysisContext): OxcBodySt
802
817
  return context.bodyStatementJsx ?? (context.target === "server" ? "server-string" : "dom-node");
803
818
  }
804
819
 
820
+ function logicalConditionValueName(expression: Record<string, unknown>): string {
821
+ return `__mreactLogical_${typeof expression.start === "number" ? expression.start : "value"}`;
822
+ }
823
+
824
+ function renderableFalsyConditionValueCode(name: string): string {
825
+ return `((typeof ${name} === "number" || typeof ${name} === "bigint") ? ${name} : null)`;
826
+ }
827
+
828
+ function shadowOxcReactiveAliases(
829
+ context: OxcChildAnalysisContext,
830
+ names: readonly string[],
831
+ ): OxcChildAnalysisContext {
832
+ if (context.reactiveAliasBindings === undefined || names.length === 0) {
833
+ return context;
834
+ }
835
+
836
+ let aliases: Map<string, string> | undefined;
837
+
838
+ for (const name of names) {
839
+ if (!context.reactiveAliasBindings.has(name)) {
840
+ continue;
841
+ }
842
+
843
+ aliases ??= new Map(context.reactiveAliasBindings);
844
+ aliases.delete(name);
845
+ }
846
+
847
+ return aliases === undefined ? context : { ...context, reactiveAliasBindings: aliases };
848
+ }
849
+
805
850
  function isOxcJsxCommentExpression(code: string, expression: Record<string, unknown>): boolean {
806
851
  if (typeof expression.start !== "number" || typeof expression.end !== "number") {
807
852
  return false;
@@ -34,6 +34,44 @@ export function collectOxcPlainComponentNames(program: unknown): string[] {
34
34
  });
35
35
  }
36
36
 
37
+ export function collectOxcLocalJsxReturnFunctionNames(program: unknown): Set<string> {
38
+ const names = new Set<string>();
39
+
40
+ for (const statement of readArray(readObject(program).body)) {
41
+ const object = readObject(statement);
42
+
43
+ if (object.type === "FunctionDeclaration" && hasOxcFunctionLikeJsxReturn(object)) {
44
+ const id = readObject(object.id);
45
+ if (typeof id.name === "string") {
46
+ names.add(id.name);
47
+ }
48
+ continue;
49
+ }
50
+
51
+ if (object.type !== "VariableDeclaration") {
52
+ continue;
53
+ }
54
+
55
+ for (const declarator of readArray(object.declarations)) {
56
+ const declaratorObject = readObject(declarator);
57
+ const id = readObject(declaratorObject.id);
58
+ const initializer = unwrapOxcComponentFunctionLikeInitializer(
59
+ readObject(declaratorObject.init),
60
+ );
61
+
62
+ if (
63
+ typeof id.name === "string" &&
64
+ initializer !== undefined &&
65
+ hasOxcFunctionLikeJsxReturn(initializer)
66
+ ) {
67
+ names.add(id.name);
68
+ }
69
+ }
70
+ }
71
+
72
+ return names;
73
+ }
74
+
37
75
  export function collectOxcExportedComponents(program: unknown): string[] {
38
76
  const body = readArray(readObject(program).body);
39
77
  const components: string[] = [];
@@ -133,8 +171,15 @@ export function isOxcExportedJsxComponent(statement: unknown): boolean {
133
171
  );
134
172
  }
135
173
 
136
- export function isOxcJsxComponentStatement(statement: unknown): boolean {
137
- return isOxcExportedJsxComponent(statement) || readOxcPlainComponent(statement) !== undefined;
174
+ export function isOxcJsxComponentStatement(
175
+ statement: unknown,
176
+ localJsxReturnFunctionNames: ReadonlySet<string> = new Set(),
177
+ ): boolean {
178
+ return (
179
+ isOxcExportedJsxComponent(statement) ||
180
+ isOxcExportedFunctionReturningLocalJsxHelper(statement, localJsxReturnFunctionNames) ||
181
+ readOxcPlainComponent(statement) !== undefined
182
+ );
138
183
  }
139
184
 
140
185
  export function isOxcExportedFunctionLike(statement: unknown): boolean {
@@ -159,6 +204,7 @@ export function isOxcExportedFunctionLike(statement: unknown): boolean {
159
204
  export function isOxcUnsupportedExportedFunction(
160
205
  statement: unknown,
161
206
  options?: AnalyzeModuleOptions,
207
+ localJsxReturnFunctionNames: ReadonlySet<string> = new Set(),
162
208
  ): boolean {
163
209
  if (options?.compatReactNodeReturn === true) {
164
210
  return false;
@@ -178,10 +224,29 @@ export function isOxcUnsupportedExportedFunction(
178
224
  typeof id.name === "string" &&
179
225
  /^[A-Z]/.test(id.name) &&
180
226
  !hasComponentReturn(declaration.body) &&
227
+ !hasLocalJsxHelperCallReturn(declaration.body, localJsxReturnFunctionNames) &&
181
228
  !hasOnlyNullReturns(declaration.body)
182
229
  );
183
230
  }
184
231
 
232
+ function isOxcExportedFunctionReturningLocalJsxHelper(
233
+ statement: unknown,
234
+ localJsxReturnFunctionNames: ReadonlySet<string>,
235
+ ): boolean {
236
+ const object = readObject(statement);
237
+
238
+ if (object.type !== "ExportNamedDeclaration") {
239
+ return false;
240
+ }
241
+
242
+ const declaration = readObject(object.declaration);
243
+
244
+ return (
245
+ declaration.type === "FunctionDeclaration" &&
246
+ hasLocalJsxHelperCallReturn(declaration.body, localJsxReturnFunctionNames)
247
+ );
248
+ }
249
+
185
250
  export function readOxcVariableComponentDeclaration(
186
251
  declaration: Record<string, unknown>,
187
252
  ): { name: string; initializer: Record<string, unknown> } | undefined {
@@ -305,6 +370,45 @@ export function hasComponentCallReturn(body: unknown): boolean {
305
370
  });
306
371
  }
307
372
 
373
+ export function hasLocalJsxHelperCallReturn(
374
+ body: unknown,
375
+ localJsxReturnFunctionNames: ReadonlySet<string>,
376
+ ): boolean {
377
+ if (localJsxReturnFunctionNames.size === 0) {
378
+ return false;
379
+ }
380
+
381
+ return readArray(readObject(body).body).some((statement) => {
382
+ const object = readObject(statement);
383
+
384
+ if (object.type === "ReturnStatement") {
385
+ return isOxcLocalJsxHelperCallExpression(
386
+ unwrapOxcParentheses(readObject(object.argument)),
387
+ localJsxReturnFunctionNames,
388
+ );
389
+ }
390
+
391
+ return hasNestedLocalJsxHelperCallReturn(object, localJsxReturnFunctionNames);
392
+ });
393
+ }
394
+
395
+ export function isOxcLocalJsxHelperCallExpression(
396
+ expression: Record<string, unknown>,
397
+ localJsxReturnFunctionNames: ReadonlySet<string>,
398
+ ): boolean {
399
+ if (expression.type !== "CallExpression") {
400
+ return false;
401
+ }
402
+
403
+ const callee = unwrapOxcParentheses(readObject(expression.callee));
404
+
405
+ return (
406
+ callee.type === "Identifier" &&
407
+ typeof callee.name === "string" &&
408
+ localJsxReturnFunctionNames.has(callee.name)
409
+ );
410
+ }
411
+
308
412
  function hasNestedJsxReturn(statement: Record<string, unknown>): boolean {
309
413
  if (statement.type === "SwitchStatement") {
310
414
  return readArray(statement.cases).some((switchCase) =>
@@ -359,6 +463,45 @@ function hasNestedComponentCallReturn(statement: Record<string, unknown>): boole
359
463
  return false;
360
464
  }
361
465
 
466
+ function hasNestedLocalJsxHelperCallReturn(
467
+ statement: Record<string, unknown>,
468
+ localJsxReturnFunctionNames: ReadonlySet<string>,
469
+ ): boolean {
470
+ if (statement.type === "SwitchStatement") {
471
+ return readArray(statement.cases).some((switchCase) =>
472
+ readArray(readObject(switchCase).consequent).some((child) => {
473
+ const object = readObject(child);
474
+ return (
475
+ object.type === "ReturnStatement" &&
476
+ isOxcLocalJsxHelperCallExpression(
477
+ unwrapOxcParentheses(readObject(object.argument)),
478
+ localJsxReturnFunctionNames,
479
+ )
480
+ );
481
+ }),
482
+ );
483
+ }
484
+
485
+ if (statement.type === "IfStatement") {
486
+ return (
487
+ hasLocalJsxHelperCallReturn(
488
+ { body: [statement.consequent] },
489
+ localJsxReturnFunctionNames,
490
+ ) ||
491
+ hasLocalJsxHelperCallReturn(
492
+ { body: [statement.alternate] },
493
+ localJsxReturnFunctionNames,
494
+ )
495
+ );
496
+ }
497
+
498
+ if (statement.type === "BlockStatement") {
499
+ return hasLocalJsxHelperCallReturn(statement, localJsxReturnFunctionNames);
500
+ }
501
+
502
+ return false;
503
+ }
504
+
362
505
  function collectReturnArguments(statement: Record<string, unknown>): Record<string, unknown>[] {
363
506
  if (statement.type === "ReturnStatement") {
364
507
  return [readObject(statement.argument)];
@@ -16,6 +16,7 @@ import type { Diagnostic } from "./types.js";
16
16
  export type AnalyzeOxcJsxNodeCallback = (
17
17
  node: Record<string, unknown>,
18
18
  bodyStatementJsx?: OxcBodyStatementJsxMode,
19
+ shadowNames?: readonly string[],
19
20
  ) => JsxNodeIr;
20
21
 
21
22
  export function analyzeOxcComponentProp(
@@ -159,7 +160,7 @@ export function analyzeOxcArrowJsxRenderer(
159
160
  if (body.type === "JSXElement" || body.type === "JSXFragment") {
160
161
  return {
161
162
  valueName,
162
- children: [analyzeJsxNode(body, bodyStatementJsx)],
163
+ children: [analyzeJsxNode(body, bodyStatementJsx, [valueName])],
163
164
  };
164
165
  }
165
166
 
@@ -1,8 +1,8 @@
1
1
  import type { ClientReferenceIr, JsxNodeIr } from "./ir.js";
2
2
  import { readArray, readObject } from "./oxc-node-utils.js";
3
3
 
4
- const routerEntryCompatRuntimeExports = new Set(["Link"]);
5
- const routerLinkCompatRuntimeExports = new Set(["Link"]);
4
+ const routerEntryCompatRuntimeExports = new Set<string>();
5
+ const routerLinkCompatRuntimeExports = new Set<string>();
6
6
  const unknownCompatReference: ClientReferenceIr = { moduleId: "", exportName: "default" };
7
7
 
8
8
  interface ClientReferenceAliasState {
@@ -22,7 +22,12 @@ function emitOxcServerStringNode(node: JsxNodeIr): string {
22
22
  }
23
23
 
24
24
  if (node.kind === "conditional") {
25
- return `((${node.conditionCode}) ? ${emitOxcServerStringChildren(node.whenTrue)} : ${emitOxcServerStringChildren(node.whenFalse)})`;
25
+ const whenTrue = emitOxcServerStringChildren(node.whenTrue);
26
+ const whenFalse = emitOxcServerStringChildren(node.whenFalse);
27
+
28
+ return node.conditionValueName === undefined
29
+ ? `((${node.conditionCode}) ? ${whenTrue} : ${whenFalse})`
30
+ : `(() => { const ${node.conditionValueName} = (${node.conditionCode}); return ${node.conditionValueName} ? ${whenTrue} : ${whenFalse}; })()`;
26
31
  }
27
32
 
28
33
  if (node.kind === "list") {
@@ -99,7 +104,12 @@ function emitOxcCompatObjectNode(node: JsxNodeIr): string {
99
104
  }
100
105
 
101
106
  if (node.kind === "conditional") {
102
- return `(${node.conditionCode}) ? ${emitOxcCompatObjectChildren(node.whenTrue)} : ${emitOxcCompatObjectChildren(node.whenFalse)}`;
107
+ const whenTrue = emitOxcCompatObjectChildren(node.whenTrue);
108
+ const whenFalse = emitOxcCompatObjectChildren(node.whenFalse);
109
+
110
+ return node.conditionValueName === undefined
111
+ ? `(${node.conditionCode}) ? ${whenTrue} : ${whenFalse}`
112
+ : `(() => { const ${node.conditionValueName} = (${node.conditionCode}); return ${node.conditionValueName} ? ${whenTrue} : ${whenFalse}; })()`;
103
113
  }
104
114
 
105
115
  if (node.kind === "list") {
package/src/oxc.ts CHANGED
@@ -43,11 +43,14 @@ import {
43
43
  collectOxcAsyncComponentNames,
44
44
  collectOxcExportedComponents,
45
45
  collectOxcExportedFunctionNames,
46
+ collectOxcLocalJsxReturnFunctionNames,
46
47
  collectOxcPlainComponentNames,
47
48
  hasComponentReturn,
49
+ hasLocalJsxHelperCallReturn,
48
50
  hasOxcFunctionLikeComponentReturn,
49
51
  isOxcExportedFunctionLike,
50
52
  isOxcComponentCallExpression,
53
+ isOxcLocalJsxHelperCallExpression,
51
54
  isJsxRoot,
52
55
  isOxcJsxComponentStatement,
53
56
  isOxcUnsupportedExportedFunction,
@@ -242,6 +245,12 @@ function analyzeOxcToIr(
242
245
  options?.compatReactNodeReturnRenderMode === "react-node"
243
246
  ? collectOxcCompatReactNodeComponentReferences(program)
244
247
  : undefined;
248
+ const localJsxReturnFunctionNames =
249
+ target === "server" ? collectOxcLocalJsxReturnFunctionNames(program) : new Set<string>();
250
+ const localJsxHelperHtmlParameters =
251
+ target === "server"
252
+ ? collectLocalJsxHelperHtmlParameters(program, localJsxReturnFunctionNames)
253
+ : new Map<string, Set<number>>();
245
254
  const bodyLowerers = createOxcBodyLowerers(compatRuntimeImports);
246
255
  const moduleRenderValueBindings = collectOxcBodyJsxBindingNames(body);
247
256
  const reactiveDerivedFunctionNames = collectOxcReactiveDerivedFunctionNames(body);
@@ -263,7 +272,7 @@ function analyzeOxcToIr(
263
272
  }
264
273
 
265
274
  if (
266
- isOxcJsxComponentStatement(statement) ||
275
+ isOxcJsxComponentStatement(statement, localJsxReturnFunctionNames) ||
267
276
  (options?.compatReactNodeReturn === true && isOxcExportedFunctionLike(statement))
268
277
  ) {
269
278
  const declaration = readObject(readObject(statement).declaration);
@@ -275,7 +284,7 @@ function analyzeOxcToIr(
275
284
  }
276
285
  continue;
277
286
  } else {
278
- if (isOxcUnsupportedExportedFunction(statement, options)) {
287
+ if (isOxcUnsupportedExportedFunction(statement, options, localJsxReturnFunctionNames)) {
279
288
  diagnostics.push({
280
289
  level: "error",
281
290
  code: "MR_UNSUPPORTED_COMPONENT_RETURN",
@@ -333,6 +342,8 @@ function analyzeOxcToIr(
333
342
  componentCallNames,
334
343
  bodyLowerers,
335
344
  reactiveDerivedFunctionNames,
345
+ localJsxReturnFunctionNames,
346
+ localJsxHelperHtmlParameters,
336
347
  ),
337
348
  );
338
349
 
@@ -387,6 +398,88 @@ function componentCallNamesFromProgram(program: unknown): Set<string> {
387
398
  ]);
388
399
  }
389
400
 
401
+ function collectLocalJsxHelperHtmlParameters(
402
+ program: unknown,
403
+ localJsxReturnFunctionNames: ReadonlySet<string>,
404
+ ): Map<string, Set<number>> {
405
+ const parameters = new Map<string, Set<number>>();
406
+
407
+ if (localJsxReturnFunctionNames.size === 0) {
408
+ return parameters;
409
+ }
410
+
411
+ for (const statement of readArray(readObject(program).body)) {
412
+ const object = readObject(statement);
413
+ const declaration =
414
+ object.type === "ExportNamedDeclaration" || object.type === "ExportDefaultDeclaration"
415
+ ? readObject(object.declaration)
416
+ : object;
417
+ const body = readObject(declaration.body);
418
+
419
+ if (body.type !== "BlockStatement") {
420
+ continue;
421
+ }
422
+
423
+ for (const returnExpression of collectOxcReturnExpressions(body)) {
424
+ const callExpression = unwrapOxcParentheses(returnExpression);
425
+ if (callExpression.type !== "CallExpression") {
426
+ continue;
427
+ }
428
+
429
+ const callee = unwrapOxcParentheses(readObject(callExpression.callee));
430
+ if (
431
+ callee.type !== "Identifier" ||
432
+ typeof callee.name !== "string" ||
433
+ !localJsxReturnFunctionNames.has(callee.name)
434
+ ) {
435
+ continue;
436
+ }
437
+ const calleeName = callee.name;
438
+
439
+ readArray(callExpression.arguments).forEach((argument, index) => {
440
+ if (!containsOxcJsxSyntax(readObject(argument))) {
441
+ return;
442
+ }
443
+
444
+ const indexes = parameters.get(calleeName) ?? new Set<number>();
445
+ indexes.add(index);
446
+ parameters.set(calleeName, indexes);
447
+ });
448
+ }
449
+ }
450
+
451
+ return parameters;
452
+ }
453
+
454
+ function collectOxcReturnExpressions(statement: Record<string, unknown>): Record<string, unknown>[] {
455
+ if (statement.type === "ReturnStatement") {
456
+ return [unwrapOxcParentheses(readObject(statement.argument))];
457
+ }
458
+
459
+ if (statement.type === "BlockStatement") {
460
+ return readArray(statement.body).flatMap((child) =>
461
+ collectOxcReturnExpressions(readObject(child)),
462
+ );
463
+ }
464
+
465
+ if (statement.type === "IfStatement") {
466
+ return [
467
+ ...collectOxcReturnExpressions(readObject(statement.consequent)),
468
+ ...collectOxcReturnExpressions(readObject(statement.alternate)),
469
+ ];
470
+ }
471
+
472
+ if (statement.type === "SwitchStatement") {
473
+ return readArray(statement.cases).flatMap((switchCase) =>
474
+ readArray(readObject(switchCase).consequent).flatMap((child) =>
475
+ collectOxcReturnExpressions(readObject(child)),
476
+ ),
477
+ );
478
+ }
479
+
480
+ return [];
481
+ }
482
+
390
483
  function analyzeOxcComponent(
391
484
  code: string,
392
485
  statement: unknown,
@@ -400,6 +493,8 @@ function analyzeOxcComponent(
400
493
  componentCallNames: Set<string> | undefined,
401
494
  bodyLowerers: OxcBodyLowerers,
402
495
  reactiveDerivedFunctionNames: ReadonlySet<string>,
496
+ localJsxReturnFunctionNames: ReadonlySet<string>,
497
+ localJsxHelperHtmlParameters: ReadonlyMap<string, ReadonlySet<number>>,
403
498
  ): ComponentIr[] {
404
499
  const object = readObject(statement);
405
500
 
@@ -428,6 +523,8 @@ function analyzeOxcComponent(
428
523
  componentCallNames,
429
524
  bodyLowerers,
430
525
  reactiveDerivedFunctionNames,
526
+ localJsxReturnFunctionNames,
527
+ localJsxHelperHtmlParameters,
431
528
  true,
432
529
  ),
433
530
  ];
@@ -457,6 +554,8 @@ function analyzeOxcComponent(
457
554
  componentCallNames,
458
555
  bodyLowerers,
459
556
  reactiveDerivedFunctionNames,
557
+ localJsxReturnFunctionNames,
558
+ localJsxHelperHtmlParameters,
460
559
  ),
461
560
  exported: false,
462
561
  },
@@ -488,13 +587,17 @@ function analyzeOxcComponent(
488
587
  componentCallNames,
489
588
  bodyLowerers,
490
589
  reactiveDerivedFunctionNames,
590
+ localJsxReturnFunctionNames,
591
+ localJsxHelperHtmlParameters,
491
592
  ),
492
593
  ];
493
594
  }
494
595
 
495
596
  if (
496
597
  declaration.type !== "FunctionDeclaration" ||
497
- (!compatReactNodeReturn && !hasComponentReturn(declaration.body))
598
+ (!compatReactNodeReturn &&
599
+ !hasComponentReturn(declaration.body) &&
600
+ !hasLocalJsxHelperCallReturn(declaration.body, localJsxReturnFunctionNames))
498
601
  ) {
499
602
  return [];
500
603
  }
@@ -521,10 +624,40 @@ function analyzeOxcComponent(
521
624
  componentCallNames,
522
625
  bodyLowerers,
523
626
  reactiveDerivedFunctionNames,
627
+ localJsxReturnFunctionNames,
628
+ localJsxHelperHtmlParameters,
524
629
  ),
525
630
  ];
526
631
  }
527
632
 
633
+ function lowerOxcLocalJsxHelperCallExpressionCode(
634
+ code: string,
635
+ expression: Record<string, unknown>,
636
+ componentNames: Set<string>,
637
+ target: CompileTarget,
638
+ diagnostics: Diagnostic[],
639
+ bodyLowerers: OxcBodyLowerers,
640
+ ): string {
641
+ if (expression.type !== "CallExpression") {
642
+ return readSource(code, expression);
643
+ }
644
+
645
+ const args = readArray(expression.arguments).map((argument) => {
646
+ const object = unwrapOxcParentheses(readObject(argument));
647
+ return containsOxcJsxSyntax(object)
648
+ ? (bodyLowerers.lowerServerStringExpression(
649
+ code,
650
+ object,
651
+ componentNames,
652
+ target,
653
+ diagnostics,
654
+ ) ?? readSource(code, argument))
655
+ : readSource(code, argument);
656
+ });
657
+
658
+ return `${readSource(code, readObject(expression.callee))}(${args.join(", ")})`;
659
+ }
660
+
528
661
  function analyzeOxcFunctionLikeComponent(
529
662
  code: string,
530
663
  name: string,
@@ -540,6 +673,8 @@ function analyzeOxcFunctionLikeComponent(
540
673
  componentCallNames: Set<string> | undefined,
541
674
  bodyLowerers: OxcBodyLowerers,
542
675
  reactiveDerivedFunctionNames: ReadonlySet<string>,
676
+ localJsxReturnFunctionNames: ReadonlySet<string>,
677
+ localJsxHelperHtmlParameters: ReadonlyMap<string, ReadonlySet<number>>,
543
678
  exportDefault = false,
544
679
  ): ComponentIr {
545
680
  const functionBody = readObject(functionLike.body);
@@ -563,6 +698,15 @@ function analyzeOxcFunctionLikeComponent(
563
698
  const parameters = readArray(functionLike.params).map((param) =>
564
699
  readOxcParameterName(code, param),
565
700
  );
701
+ const htmlParameterNames = new Set(
702
+ [...(localJsxHelperHtmlParameters.get(name) ?? [])]
703
+ .map((index) => parameters[index])
704
+ .filter((parameter): parameter is string => parameter !== undefined),
705
+ );
706
+ const bodyComponentNames =
707
+ /^[a-z]/.test(name) && componentNames.has(name)
708
+ ? new Set([...componentNames].filter((componentName) => componentName !== name))
709
+ : componentNames;
566
710
  const bodyStatements = body
567
711
  .filter(
568
712
  (bodyStatement) =>
@@ -576,7 +720,7 @@ function analyzeOxcFunctionLikeComponent(
576
720
  lowerOxcBodyStatementJsx(
577
721
  code,
578
722
  bodyStatement,
579
- componentNames,
723
+ bodyComponentNames,
580
724
  target,
581
725
  diagnostics,
582
726
  bodyStatementJsx,
@@ -590,7 +734,7 @@ function analyzeOxcFunctionLikeComponent(
590
734
  reactiveDerivedFunctionNames,
591
735
  );
592
736
  const childAnalysisContext = createOxcChildAnalysisContext(
593
- componentNames,
737
+ bodyComponentNames,
594
738
  target,
595
739
  diagnostics,
596
740
  bodyStatementJsx,
@@ -617,6 +761,23 @@ function analyzeOxcFunctionLikeComponent(
617
761
  ? analyzeOxcJsxNode(code, returnExpression, childAnalysisContext)
618
762
  : isOxcComponentCallExpression(returnExpression)
619
763
  ? analyzeOxcComponentCallExpression(code, returnExpression)
764
+ : isOxcLocalJsxHelperCallExpression(returnExpression, localJsxReturnFunctionNames)
765
+ ? {
766
+ kind: "expr" as const,
767
+ code: normalizeOxcExpressionCode(
768
+ bodyStatementJsx === "server-string"
769
+ ? lowerOxcLocalJsxHelperCallExpressionCode(
770
+ code,
771
+ returnExpression,
772
+ bodyComponentNames,
773
+ target,
774
+ diagnostics,
775
+ bodyLowerers,
776
+ )
777
+ : readSource(code, returnExpression),
778
+ ),
779
+ renderMode: "html" as const,
780
+ }
620
781
  : analyzeOxcDynamicRootReturn(
621
782
  code,
622
783
  returnExpression,
@@ -654,6 +815,7 @@ function analyzeOxcFunctionLikeComponent(
654
815
  bodyStatement !== earlyIfRootReturn?.fallthroughStatement,
655
816
  ),
656
817
  ),
818
+ ...htmlParameterNames,
657
819
  ]),
658
820
  bodyStatementJsx === "server-string" ? "html" : "dynamic",
659
821
  );