From 4f93a6794e3b5ce8364a1a8602446638a8a810ae Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Fri, 13 Mar 2026 14:43:07 +0100 Subject: [PATCH 1/3] Fix #1200: Quote hyphenated JSX attribute names in getContent Root cause: jsxChildToRuntimeCall used t.identifier() for JSX attribute names, producing unquoted object keys. Hyphenated names like aria-hidden or bg-$backgroundDefault became invalid JS identifiers, causing Hermes parse errors ("':' expected in property initialization"). Changes: - Use t.stringLiteral() for all attribute keys in jsxChildToRuntimeCall - This matches the existing pattern in tap.ts and is semantically identical ({foo: v} === {"foo": v} in JavaScript) - Add tests for hyphenated attribute names in getContent child elements Tests: 3 unit Performance: SUCCESS --- .../src/actions/rum/index.ts | 4 +- .../test/plugin.test.ts | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/packages/react-native-babel-plugin/src/actions/rum/index.ts b/packages/react-native-babel-plugin/src/actions/rum/index.ts index 7454ef0dc..5fec7110c 100644 --- a/packages/react-native-babel-plugin/src/actions/rum/index.ts +++ b/packages/react-native-babel-plugin/src/actions/rum/index.ts @@ -247,9 +247,7 @@ function jsxChildToRuntimeCall( t.isJSXAttribute(attr) ) .map(attr => { - const key = t.isJSXIdentifier(attr.name) - ? t.identifier(attr.name.name) - : t.stringLiteral(getNodeName(t, attr.name) || ''); + const key = t.stringLiteral(getNodeName(t, attr.name) || ''); let value: Babel.types.Expression; if (!attr.value) { value = t.booleanLiteral(true); diff --git a/packages/react-native-babel-plugin/test/plugin.test.ts b/packages/react-native-babel-plugin/test/plugin.test.ts index 657f8bbb5..94251df6a 100644 --- a/packages/react-native-babel-plugin/test/plugin.test.ts +++ b/packages/react-native-babel-plugin/test/plugin.test.ts @@ -1644,3 +1644,79 @@ describe('Babel plugin: wrap interaction handlers for RUM ( with memoization )', `); }); }); + +describe('Babel plugin: hyphenated JSX attribute names in getContent', () => { + function extractGetContent(output: string | null | undefined): string { + if (!output) return ''; + const match = output.match(/"getContent": \(\) => \{[\s\S]*?return ([\s\S]*?);\s*\}/); + return match ? match[1] : ''; + } + + it('should quote hyphenated attribute names in getContent child elements to produce valid JS', () => { + const input = ` + import { TouchableOpacity, View, Text } from 'react-native'; + + function MyComponent() { + return ( + {}}> + + Hello + + + ); + } + `; + + const output = transformCode(input); + const getContent = extractGetContent(output); + // Inside getContent, the generated _jsx call must use "aria-hidden" (quoted string) + // not aria-hidden (bare identifier) because aria-hidden is not a valid JS identifier. + // Bare identifiers with hyphens cause Hermes parse errors: ':' expected in property initialization + expect(getContent).toContain('"aria-hidden"'); + expect(getContent).not.toMatch(/\baria-hidden\b(?!":)/); + }); + + it('should quote multiple hyphenated attribute names in getContent child elements', () => { + const input = ` + import { TouchableOpacity, View, Text } from 'react-native'; + + function MyComponent() { + return ( + {}}> + + Hello + + + ); + } + `; + + const output = transformCode(input); + const getContent = extractGetContent(output); + expect(getContent).toContain('"aria-hidden"'); + expect(getContent).toContain('"aria-label"'); + expect(getContent).toContain('"data-testid"'); + }); + + it('should quote all attribute names consistently in getContent child elements', () => { + const input = ` + import { TouchableOpacity, View, Text } from 'react-native'; + + function MyComponent() { + return ( + {}}> + + Hello + + + ); + } + `; + + const output = transformCode(input); + const getContent = extractGetContent(output); + // All attribute names are quoted for consistency and safety + expect(getContent).toContain('"style"'); + expect(getContent).toContain('"accessible"'); + }); +}); From fb40e48c4df62b8142544dbc50a8b54760cb83c1 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Mon, 16 Mar 2026 09:10:53 +0100 Subject: [PATCH 2/3] Make extractGetContent regex resilient to whitespace changes Loosen the regex in the test helper to tolerate whitespace variations around structural tokens (`:`, `=>`, `()`, `{`, `return`), addressing PR review feedback about brittleness to codegen formatting changes. Co-Authored-By: Claude Opus 4.6 --- packages/react-native-babel-plugin/test/plugin.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-babel-plugin/test/plugin.test.ts b/packages/react-native-babel-plugin/test/plugin.test.ts index 94251df6a..9dfba1d03 100644 --- a/packages/react-native-babel-plugin/test/plugin.test.ts +++ b/packages/react-native-babel-plugin/test/plugin.test.ts @@ -1648,7 +1648,7 @@ describe('Babel plugin: wrap interaction handlers for RUM ( with memoization )', describe('Babel plugin: hyphenated JSX attribute names in getContent', () => { function extractGetContent(output: string | null | undefined): string { if (!output) return ''; - const match = output.match(/"getContent": \(\) => \{[\s\S]*?return ([\s\S]*?);\s*\}/); + const match = output.match(/"getContent"\s*:\s*\(\)\s*=>\s*\{[\s\S]*?return\s+([\s\S]*?);\s*\}/); return match ? match[1] : ''; } From 656f7feeabfdea749210a1fcb1dbc817cd6e2a6e Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Mon, 16 Mar 2026 09:40:58 +0100 Subject: [PATCH 3/3] Fix lint errors in extractGetContent helper - Add braces around single-line if block (curly rule) - Wrap long regex across multiple lines (prettier rule) Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/react-native-babel-plugin/test/plugin.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/react-native-babel-plugin/test/plugin.test.ts b/packages/react-native-babel-plugin/test/plugin.test.ts index 9dfba1d03..f45dc37e1 100644 --- a/packages/react-native-babel-plugin/test/plugin.test.ts +++ b/packages/react-native-babel-plugin/test/plugin.test.ts @@ -1647,8 +1647,12 @@ describe('Babel plugin: wrap interaction handlers for RUM ( with memoization )', describe('Babel plugin: hyphenated JSX attribute names in getContent', () => { function extractGetContent(output: string | null | undefined): string { - if (!output) return ''; - const match = output.match(/"getContent"\s*:\s*\(\)\s*=>\s*\{[\s\S]*?return\s+([\s\S]*?);\s*\}/); + if (!output) { + return ''; + } + const match = output.match( + /"getContent"\s*:\s*\(\)\s*=>\s*\{[\s\S]*?return\s+([\s\S]*?);\s*\}/ + ); return match ? match[1] : ''; }