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..8c170fc21 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,12 @@ 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.isJSXIdentifier(attr.name) && + t.isValidIdentifier(attr.name.name) + ? t.identifier(attr.name.name) + : 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..c9bf29d8c 100644 --- a/packages/react-native-babel-plugin/test/plugin.test.ts +++ b/packages/react-native-babel-plugin/test/plugin.test.ts @@ -1644,3 +1644,86 @@ 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*\}/ + ); + 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 not quote valid JS identifier 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); + // Valid JS identifiers like "style" and "accessible" should remain bare (unquoted) + expect(getContent).toMatch(/\bstyle:/); + expect(getContent).toMatch(/\baccessible:/); + // They should NOT be quoted as string literals + expect(getContent).not.toContain('"style"'); + expect(getContent).not.toContain('"accessible"'); + }); +});