From 69eccc9326b80d07d552795696d9461e9aebacb3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 11:03:23 +0000
Subject: [PATCH 1/7] Initial plan
From 28af96c10539b5d8be66d0af980dba39da085419 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 11:10:51 +0000
Subject: [PATCH 2/7] Fix Context.Provider support by handling
PropertyAccessExpression in JSX tags
Co-authored-by: evilbocchi <71329833+evilbocchi@users.noreply.github.com>
---
transformer/src/analyzer.ts | 22 ++++++++++++--
transformer/src/index.ts | 14 +++++++++
transformer/src/transformer.ts | 30 ++++++++++++++++---
.../test/transformer.integration.test.ts | 22 ++++++++++++++
4 files changed, 82 insertions(+), 6 deletions(-)
diff --git a/transformer/src/analyzer.ts b/transformer/src/analyzer.ts
index d6d8399..dedc58d 100644
--- a/transformer/src/analyzer.ts
+++ b/transformer/src/analyzer.ts
@@ -62,8 +62,12 @@ export class BlockAnalyzer {
// because they can have internal state, effects, hooks, etc.
if (tagName[0] && tagName[0] === tagName[0].toUpperCase()) {
blockInfo.isStatic = false;
- // Add the component itself as a dependency if it's an identifier
- blockInfo.dependencies.push(tagName);
+ // Add the component itself as a dependency
+ // For PropertyAccessExpression like Ctx.Provider, we only need the base identifier (Ctx)
+ const baseDependency = tagName.split(".")[0];
+ if (baseDependency) {
+ blockInfo.dependencies.push(baseDependency);
+ }
}
// Analyze attributes/props
@@ -492,6 +496,20 @@ export class BlockAnalyzer {
return tagName.text;
}
+ // Handle PropertyAccessExpression (e.g., Ctx.Provider, React.Fragment)
+ if (ts.isPropertyAccessExpression(tagName)) {
+ // Return a string representation for ID generation
+ const getText = (expr: ts.Expression): string => {
+ if (ts.isIdentifier(expr)) {
+ return expr.text;
+ } else if (ts.isPropertyAccessExpression(expr)) {
+ return getText(expr.expression) + "." + expr.name.text;
+ }
+ return "Unknown";
+ };
+ return getText(tagName.expression) + "." + tagName.name.text;
+ }
+
return "UnknownTag";
}
diff --git a/transformer/src/index.ts b/transformer/src/index.ts
index 1d045dc..97833b8 100644
--- a/transformer/src/index.ts
+++ b/transformer/src/index.ts
@@ -209,6 +209,20 @@ function getTagName(node: ts.JsxElement | ts.JsxSelfClosingElement): string {
return tagName.text;
}
+ // Handle PropertyAccessExpression (e.g., Ctx.Provider, React.Fragment)
+ if (ts.isPropertyAccessExpression(tagName)) {
+ // Return a string representation for logging
+ const getText = (expr: ts.Expression): string => {
+ if (ts.isIdentifier(expr)) {
+ return expr.text;
+ } else if (ts.isPropertyAccessExpression(expr)) {
+ return getText(expr.expression) + "." + expr.name.text;
+ }
+ return "Unknown";
+ };
+ return getText(tagName.expression) + "." + tagName.name.text;
+ }
+
return "UnknownTag";
}
diff --git a/transformer/src/transformer.ts b/transformer/src/transformer.ts
index 4c33b6b..ad11489 100644
--- a/transformer/src/transformer.ts
+++ b/transformer/src/transformer.ts
@@ -17,8 +17,14 @@ import type { OptimizationContext, PropInfo, StaticElementInfo, TransformResult,
* Creates the appropriate tag reference for React.createElement
* - Lowercase tags (frame, textlabel) become string literals
* - PascalCase tags (Counter, MyComponent) become identifiers
+ * - Property access (Ctx.Provider) preserves the expression
*/
-function createTagReference(tagName: string): ts.Expression {
+function createTagReference(tagName: string | ts.Expression): ts.Expression {
+ // If already an expression (e.g., PropertyAccessExpression), use it as-is
+ if (typeof tagName !== "string") {
+ return tagName;
+ }
+
// Check if tag name starts with uppercase (PascalCase component)
if (tagName[0] && tagName[0] === tagName[0].toUpperCase()) {
// React component - use identifier
@@ -29,6 +35,13 @@ function createTagReference(tagName: string): ts.Expression {
}
}
+/**
+ * Extracts the tag name expression from a JSX element
+ */
+function getTagExpression(node: ts.JsxElement | ts.JsxSelfClosingElement): ts.JsxTagNameExpression {
+ return ts.isJsxElement(node) ? node.openingElement.tagName : node.tagName;
+}
+
function sanitizeDependencyType(
dep: string,
typeNode: ts.TypeNode | undefined,
@@ -366,6 +379,9 @@ function generateFinePatchBlock(
// Generate patch instructions
const finePatchInfo = context.blockAnalyzer!.generatePatchInstructions(node);
+ // Get the actual tag expression (handles PropertyAccessExpression like Ctx.Provider)
+ const tagExpression = getTagExpression(node);
+
// Create the React.createElement call inside the arrow function
const createElementCall = ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
@@ -374,7 +390,7 @@ function generateFinePatchBlock(
),
undefined,
[
- createTagReference(tagName),
+ createTagReference(tagExpression),
allProps.length > 0 ? createPropsObject(allProps) : ts.factory.createIdentifier("undefined"),
...children,
],
@@ -626,6 +642,9 @@ function generateMemoizedBlock(
const allProps = extractPropsFromJsx(node);
const children = extractOptimizedChildren(node, context);
+ // Get the actual tag expression (handles PropertyAccessExpression like Ctx.Provider)
+ const tagExpression = getTagExpression(node);
+
// Create the React.createElement call inside the arrow function
const createElementCall = ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
@@ -634,7 +653,7 @@ function generateMemoizedBlock(
),
undefined,
[
- createTagReference(tagName),
+ createTagReference(tagExpression),
allProps.length > 0 ? createPropsObject(allProps) : ts.factory.createIdentifier("undefined"),
...children,
],
@@ -754,13 +773,16 @@ function generateOptimizedElement(
const propsArg = allProps.length > 0 ? createPropsObject(allProps) : ts.factory.createIdentifier("undefined");
+ // Get the actual tag expression (handles PropertyAccessExpression like Ctx.Provider)
+ const tagExpression = getTagExpression(node);
+
const element = ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createIdentifier("React"),
ts.factory.createIdentifier("createElement"),
),
undefined,
- [createTagReference(tagName), propsArg, ...children],
+ [createTagReference(tagExpression), propsArg, ...children],
);
return {
diff --git a/transformer/test/transformer.integration.test.ts b/transformer/test/transformer.integration.test.ts
index 69d36da..eaf0218 100644
--- a/transformer/test/transformer.integration.test.ts
+++ b/transformer/test/transformer.integration.test.ts
@@ -228,4 +228,26 @@ export function UsesState() {
const reactIndex = output.indexOf("@rbxts/react");
expect(runtimeIndex).toBeLessThan(reactIndex);
});
+
+ it("handles Context.Provider components correctly", () => {
+ const source = `
+import React, { createContext, useContext } from "@rbxts/react";
+
+const layerContext = createContext(0);
+
+export default () => {
+ const depth = useContext(layerContext);
+
+ return ;
+};
+`;
+ const output = transformSource(source);
+
+ // Should not contain UnknownTag
+ expect(output).not.toContain("UnknownTag");
+
+ // Should generate React.createElement with layerContext.Provider
+ expect(output).toContain("React.createElement");
+ expect(output).toContain("layerContext.Provider");
+ });
});
From a8aa770f77ec6e721e455fa43842d1d37ee105fe Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 11:12:47 +0000
Subject: [PATCH 3/7] Fix TypeScript compilation errors for
JsxTagNameExpression handling
Co-authored-by: evilbocchi <71329833+evilbocchi@users.noreply.github.com>
---
transformer/src/transformer.ts | 30 +++++++++++++++++++-----------
1 file changed, 19 insertions(+), 11 deletions(-)
diff --git a/transformer/src/transformer.ts b/transformer/src/transformer.ts
index ad11489..32e4f93 100644
--- a/transformer/src/transformer.ts
+++ b/transformer/src/transformer.ts
@@ -18,21 +18,29 @@ import type { OptimizationContext, PropInfo, StaticElementInfo, TransformResult,
* - Lowercase tags (frame, textlabel) become string literals
* - PascalCase tags (Counter, MyComponent) become identifiers
* - Property access (Ctx.Provider) preserves the expression
+ * - Namespaced names (ns:tag) are converted to string literals
*/
-function createTagReference(tagName: string | ts.Expression): ts.Expression {
- // If already an expression (e.g., PropertyAccessExpression), use it as-is
- if (typeof tagName !== "string") {
- return tagName;
+function createTagReference(tagName: string | ts.JsxTagNameExpression): ts.Expression {
+ // If it's a string, convert it to the appropriate form
+ if (typeof tagName === "string") {
+ // Check if tag name starts with uppercase (PascalCase component)
+ if (tagName[0] && tagName[0] === tagName[0].toUpperCase()) {
+ // React component - use identifier
+ return ts.factory.createIdentifier(tagName);
+ } else {
+ // HTML-like element - use string literal
+ return ts.factory.createStringLiteral(tagName);
+ }
}
- // Check if tag name starts with uppercase (PascalCase component)
- if (tagName[0] && tagName[0] === tagName[0].toUpperCase()) {
- // React component - use identifier
- return ts.factory.createIdentifier(tagName);
- } else {
- // HTML-like element - use string literal
- return ts.factory.createStringLiteral(tagName);
+ // Handle JsxNamespacedName (e.g., ) - convert to string literal
+ if (ts.isJsxNamespacedName(tagName)) {
+ return ts.factory.createStringLiteral(`${tagName.namespace.text}:${tagName.name.text}`);
}
+
+ // For Identifier, PropertyAccessExpression, or ThisExpression, use them as-is
+ // These are all valid Expression types that can be used in React.createElement
+ return tagName as ts.Expression;
}
/**
From b9a1218e75215e51cd8f15f3e5fcf8710c00c169 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 11:16:00 +0000
Subject: [PATCH 4/7] Refactor: Extract shared utility function to eliminate
code duplication
Co-authored-by: evilbocchi <71329833+evilbocchi@users.noreply.github.com>
---
demo/src/context-test.tsx | 9 +++++++++
transformer/src/analyzer.ts | 11 ++---------
transformer/src/index.ts | 11 ++---------
transformer/src/utils.ts | 18 ++++++++++++++++++
4 files changed, 31 insertions(+), 18 deletions(-)
create mode 100644 demo/src/context-test.tsx
create mode 100644 transformer/src/utils.ts
diff --git a/demo/src/context-test.tsx b/demo/src/context-test.tsx
new file mode 100644
index 0000000..a04a4af
--- /dev/null
+++ b/demo/src/context-test.tsx
@@ -0,0 +1,9 @@
+import React, { createContext, useContext } from "@rbxts/react";
+
+const layerContext = createContext(0);
+
+export default () => {
+ const depth = useContext(layerContext);
+
+ return ;
+};
diff --git a/transformer/src/analyzer.ts b/transformer/src/analyzer.ts
index dedc58d..56ac8b5 100644
--- a/transformer/src/analyzer.ts
+++ b/transformer/src/analyzer.ts
@@ -2,6 +2,7 @@ import * as ts from "typescript";
import { robloxStaticDetector } from "./roblox-bridge";
import type { DependencyInfo, PropEdit, ChildEdit, PatchInstruction, FinePatchBlockInfo } from "./types";
import { EditType } from "./types";
+import { jsxTagExpressionToString } from "./utils";
const BAILOUT_PROP_NAMES = new Set(["ref", "key", "children"]);
@@ -499,15 +500,7 @@ export class BlockAnalyzer {
// Handle PropertyAccessExpression (e.g., Ctx.Provider, React.Fragment)
if (ts.isPropertyAccessExpression(tagName)) {
// Return a string representation for ID generation
- const getText = (expr: ts.Expression): string => {
- if (ts.isIdentifier(expr)) {
- return expr.text;
- } else if (ts.isPropertyAccessExpression(expr)) {
- return getText(expr.expression) + "." + expr.name.text;
- }
- return "Unknown";
- };
- return getText(tagName.expression) + "." + tagName.name.text;
+ return jsxTagExpressionToString(tagName.expression) + "." + tagName.name.text;
}
return "UnknownTag";
diff --git a/transformer/src/index.ts b/transformer/src/index.ts
index 97833b8..46ee669 100644
--- a/transformer/src/index.ts
+++ b/transformer/src/index.ts
@@ -9,6 +9,7 @@ import {
transformJsxElementWithFinePatch,
} from "./transformer";
import type { OptimizationContext, PropInfo, StaticElementInfo } from "./types";
+import { jsxTagExpressionToString } from "./utils";
/**
* Configuration options for the Decillion transformer
@@ -212,15 +213,7 @@ function getTagName(node: ts.JsxElement | ts.JsxSelfClosingElement): string {
// Handle PropertyAccessExpression (e.g., Ctx.Provider, React.Fragment)
if (ts.isPropertyAccessExpression(tagName)) {
// Return a string representation for logging
- const getText = (expr: ts.Expression): string => {
- if (ts.isIdentifier(expr)) {
- return expr.text;
- } else if (ts.isPropertyAccessExpression(expr)) {
- return getText(expr.expression) + "." + expr.name.text;
- }
- return "Unknown";
- };
- return getText(tagName.expression) + "." + tagName.name.text;
+ return jsxTagExpressionToString(tagName.expression) + "." + tagName.name.text;
}
return "UnknownTag";
diff --git a/transformer/src/utils.ts b/transformer/src/utils.ts
new file mode 100644
index 0000000..624d571
--- /dev/null
+++ b/transformer/src/utils.ts
@@ -0,0 +1,18 @@
+import * as ts from "typescript";
+
+/**
+ * Shared utility functions for JSX transformation
+ */
+
+/**
+ * Converts a JSX tag name expression to a string representation.
+ * Handles PropertyAccessExpression recursively (e.g., Ctx.Provider -> "Ctx.Provider")
+ */
+export function jsxTagExpressionToString(expr: ts.Expression): string {
+ if (ts.isIdentifier(expr)) {
+ return expr.text;
+ } else if (ts.isPropertyAccessExpression(expr)) {
+ return jsxTagExpressionToString(expr.expression) + "." + expr.name.text;
+ }
+ return "Unknown";
+}
From 170c0ca344e35890516a784cc6d63e3e42a28e8a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 11:18:09 +0000
Subject: [PATCH 5/7] Improve utility function and fix edge cases per code
review feedback
Co-authored-by: evilbocchi <71329833+evilbocchi@users.noreply.github.com>
---
transformer/src/analyzer.ts | 25 +++++++++++--------------
transformer/src/index.ts | 16 +++++-----------
transformer/src/utils.ts | 12 ++++++++++--
3 files changed, 26 insertions(+), 27 deletions(-)
diff --git a/transformer/src/analyzer.ts b/transformer/src/analyzer.ts
index 56ac8b5..093a043 100644
--- a/transformer/src/analyzer.ts
+++ b/transformer/src/analyzer.ts
@@ -65,9 +65,12 @@ export class BlockAnalyzer {
blockInfo.isStatic = false;
// Add the component itself as a dependency
// For PropertyAccessExpression like Ctx.Provider, we only need the base identifier (Ctx)
- const baseDependency = tagName.split(".")[0];
- if (baseDependency) {
- blockInfo.dependencies.push(baseDependency);
+ // Skip if tagName is UnknownTag or other invalid values
+ if (tagName !== "UnknownTag" && tagName !== "Unknown") {
+ const baseDependency = tagName.split(".")[0];
+ if (baseDependency) {
+ blockInfo.dependencies.push(baseDependency);
+ }
}
}
@@ -493,17 +496,11 @@ export class BlockAnalyzer {
getJsxTagName(node: ts.JsxElement | ts.JsxSelfClosingElement): string {
const tagName = ts.isJsxElement(node) ? node.openingElement.tagName : node.tagName;
- if (ts.isIdentifier(tagName)) {
- return tagName.text;
- }
-
- // Handle PropertyAccessExpression (e.g., Ctx.Provider, React.Fragment)
- if (ts.isPropertyAccessExpression(tagName)) {
- // Return a string representation for ID generation
- return jsxTagExpressionToString(tagName.expression) + "." + tagName.name.text;
- }
-
- return "UnknownTag";
+ // Use the utility function to convert tag expression to string
+ const tagString = jsxTagExpressionToString(tagName);
+
+ // Return UnknownTag only if the utility function couldn't identify the tag
+ return tagString !== "Unknown" ? tagString : "UnknownTag";
}
/**
diff --git a/transformer/src/index.ts b/transformer/src/index.ts
index 46ee669..0cb079e 100644
--- a/transformer/src/index.ts
+++ b/transformer/src/index.ts
@@ -206,17 +206,11 @@ function shouldSkipFile(file: ts.SourceFile, debug: boolean): boolean {
function getTagName(node: ts.JsxElement | ts.JsxSelfClosingElement): string {
const tagName = ts.isJsxElement(node) ? node.openingElement.tagName : node.tagName;
- if (ts.isIdentifier(tagName)) {
- return tagName.text;
- }
-
- // Handle PropertyAccessExpression (e.g., Ctx.Provider, React.Fragment)
- if (ts.isPropertyAccessExpression(tagName)) {
- // Return a string representation for logging
- return jsxTagExpressionToString(tagName.expression) + "." + tagName.name.text;
- }
-
- return "UnknownTag";
+ // Use the utility function to convert tag expression to string
+ const tagString = jsxTagExpressionToString(tagName);
+
+ // Return UnknownTag only if the utility function couldn't identify the tag
+ return tagString !== "Unknown" ? tagString : "UnknownTag";
}
/**
diff --git a/transformer/src/utils.ts b/transformer/src/utils.ts
index 624d571..3fd10f2 100644
--- a/transformer/src/utils.ts
+++ b/transformer/src/utils.ts
@@ -7,12 +7,20 @@ import * as ts from "typescript";
/**
* Converts a JSX tag name expression to a string representation.
* Handles PropertyAccessExpression recursively (e.g., Ctx.Provider -> "Ctx.Provider")
+ * Handles ThisExpression (e.g., -> "this.Component")
*/
-export function jsxTagExpressionToString(expr: ts.Expression): string {
+export function jsxTagExpressionToString(expr: ts.JsxTagNameExpression): string {
if (ts.isIdentifier(expr)) {
return expr.text;
} else if (ts.isPropertyAccessExpression(expr)) {
- return jsxTagExpressionToString(expr.expression) + "." + expr.name.text;
+ const base = jsxTagExpressionToString(expr.expression as ts.JsxTagNameExpression);
+ return base + "." + expr.name.text;
+ } else if ((expr as ts.Node).kind === ts.SyntaxKind.ThisKeyword) {
+ // Handle ThisExpression
+ return "this";
+ } else if (ts.isJsxNamespacedName(expr)) {
+ // Handle namespaced names (e.g., )
+ return expr.namespace.text + ":" + expr.name.text;
}
return "Unknown";
}
From f28d351e0e119baa5883b3cd006081d0de2778fa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 11:19:45 +0000
Subject: [PATCH 6/7] Improve code safety and remove unnecessary check
Co-authored-by: evilbocchi <71329833+evilbocchi@users.noreply.github.com>
---
transformer/src/analyzer.ts | 4 ++--
transformer/src/utils.ts | 22 ++++++++++++++++++++--
2 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/transformer/src/analyzer.ts b/transformer/src/analyzer.ts
index 093a043..93c931c 100644
--- a/transformer/src/analyzer.ts
+++ b/transformer/src/analyzer.ts
@@ -65,8 +65,8 @@ export class BlockAnalyzer {
blockInfo.isStatic = false;
// Add the component itself as a dependency
// For PropertyAccessExpression like Ctx.Provider, we only need the base identifier (Ctx)
- // Skip if tagName is UnknownTag or other invalid values
- if (tagName !== "UnknownTag" && tagName !== "Unknown") {
+ // Skip if tagName is UnknownTag
+ if (tagName !== "UnknownTag") {
const baseDependency = tagName.split(".")[0];
if (baseDependency) {
blockInfo.dependencies.push(baseDependency);
diff --git a/transformer/src/utils.ts b/transformer/src/utils.ts
index 3fd10f2..cbef2c0 100644
--- a/transformer/src/utils.ts
+++ b/transformer/src/utils.ts
@@ -13,8 +13,26 @@ export function jsxTagExpressionToString(expr: ts.JsxTagNameExpression): string
if (ts.isIdentifier(expr)) {
return expr.text;
} else if (ts.isPropertyAccessExpression(expr)) {
- const base = jsxTagExpressionToString(expr.expression as ts.JsxTagNameExpression);
- return base + "." + expr.name.text;
+ // For PropertyAccessExpression, recursively process the base expression
+ // In JSX context, expr.expression should be another valid JSX tag name expression
+ // (Identifier, PropertyAccessExpression, or ThisExpression)
+ const baseExpr = expr.expression;
+ let baseString: string;
+
+ if (ts.isIdentifier(baseExpr)) {
+ baseString = baseExpr.text;
+ } else if (ts.isPropertyAccessExpression(baseExpr)) {
+ // Recursively handle nested property access (e.g., a.b.c)
+ // Type assertion is safe here because we know it's a PropertyAccessExpression
+ baseString = jsxTagExpressionToString(baseExpr as ts.JsxTagNameExpression);
+ } else if ((baseExpr as ts.Node).kind === ts.SyntaxKind.ThisKeyword) {
+ baseString = "this";
+ } else {
+ // Fallback for unexpected expression types
+ baseString = "Unknown";
+ }
+
+ return baseString + "." + expr.name.text;
} else if ((expr as ts.Node).kind === ts.SyntaxKind.ThisKeyword) {
// Handle ThisExpression
return "this";
From 225e81015c38201140fdf04f7729cc7af6b2c97f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Feb 2026 11:39:15 +0000
Subject: [PATCH 7/7] Add tests for React.Fragment and Provider with dynamic
props
Co-authored-by: evilbocchi <71329833+evilbocchi@users.noreply.github.com>
---
.../test/transformer.integration.test.ts | 53 +++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/transformer/test/transformer.integration.test.ts b/transformer/test/transformer.integration.test.ts
index eaf0218..21daf01 100644
--- a/transformer/test/transformer.integration.test.ts
+++ b/transformer/test/transformer.integration.test.ts
@@ -250,4 +250,57 @@ export default () => {
expect(output).toContain("React.createElement");
expect(output).toContain("layerContext.Provider");
});
+
+ it("handles React.Fragment correctly", () => {
+ const source = `
+import React from "@rbxts/react";
+
+export function FragmentExample({ items }: { items: string[] }) {
+ return (
+
+ {items.map((item) => (
+
+ ))}
+
+ );
+}
+`;
+ const output = transformSource(source);
+
+ // Should not contain UnknownTag
+ expect(output).not.toContain("UnknownTag");
+
+ // Should generate React.createElement with React.Fragment
+ expect(output).toContain("React.createElement");
+ expect(output).toContain("React.Fragment");
+ });
+
+ it("does not treat Provider with dynamic props as static", () => {
+ const source = `
+import React, { createContext } from "@rbxts/react";
+
+const ThemeContext = createContext("light");
+
+export function ThemeProvider({ theme, children }: { theme: string; children: React.ReactNode }) {
+ return {children};
+}
+`;
+ const output = transformSource(source);
+
+ // Should not contain UnknownTag
+ expect(output).not.toContain("UnknownTag");
+
+ // Should generate React.createElement with ThemeContext.Provider
+ expect(output).toContain("React.createElement");
+ expect(output).toContain("ThemeContext.Provider");
+
+ // Should NOT call createStaticElement with the Provider tag
+ // (it may appear in imports, but should not be called for this component)
+ expect(output).not.toMatch(/createStaticElement\([^)]*ThemeContext\.Provider/);
+
+ // Should use useFinePatchBlock or regular createElement for dynamic props
+ const hasFinePatch = output.includes("useFinePatchBlock");
+ const hasCreateElement = output.includes("React.createElement(ThemeContext.Provider");
+ expect(hasFinePatch || hasCreateElement).toBe(true);
+ });
});