Skip to content

Fix #1200: Quote hyphenated JSX attribute names in babel plugin getContent#1203

Open
mariusc83 wants to merge 3 commits intodevelopfrom
mariusc83/1200/babel-plugin-unescaped-content
Open

Fix #1200: Quote hyphenated JSX attribute names in babel plugin getContent#1203
mariusc83 wants to merge 3 commits intodevelopfrom
mariusc83/1200/babel-plugin-unescaped-content

Conversation

@mariusc83
Copy link
Member

@mariusc83 mariusc83 commented Mar 13, 2026

Issue

Fixes #1200: Babel plugin: error with unescaped content when using useContent: true

GitHub Issue: #1200

Root Cause

jsxChildToRuntimeCall in the babel plugin used t.identifier() for JSX attribute names when generating the getContent closure. This produces unquoted object keys in the generated JavaScript. While valid for simple names like style or accessible, hyphenated attribute names like aria-hidden, aria-label, data-testid, or bg-$backgroundDefault become invalid JavaScript identifiers — causing Hermes to fail with ':' expected in property initialization.

Changes

  • Changed attribute key generation in jsxChildToRuntimeCall from conditional t.identifier() / t.stringLiteral() to always use t.stringLiteral() for all attribute keys
  • This matches the existing safe pattern in tap.ts and is semantically identical in JavaScript ({foo: v} === {"foo": v})
  • Added 3 test cases covering hyphenated attributes, multiple hyphenated attributes, and non-hyphenated attribute consistency

Files Changed

File Change
packages/react-native-babel-plugin/src/actions/rum/index.ts Use t.stringLiteral() for all attribute keys (line 250)
packages/react-native-babel-plugin/test/plugin.test.ts Add 3 tests for hyphenated JSX attribute names in getContent

Tests

  • Unit tests added: 3
  • Module test suite: PASS (167 tests, 119 snapshots)
  • TDD iterations: 1/5

Regression & Impact Analysis

Runtime chain

The getContent closure flows through: wrapRumAction()getTargetName()__ddExtractText(jsx(...), []) → returns string[] used as RUM action name → ddRum.addAction() → native SDK bridge → Datadog intake.

The change only affects how attribute keys are quoted in the transpiled source code. At runtime, {style: v} and {"style": v} produce the exact same JavaScript object — __ddExtractText walks the React element tree by accessing props.children, props.label, props.title, and props.text via standard property access, which is identical for both forms.

Stakeholder impact

Consumer Sees the change? Impact
Hermes parser Yes — this is the fix (hyphenated keys now valid) Critical fix
Metro bundler Yes — minifies quotes away for valid identifiers None (0 bytes difference in minified output)
__ddExtractText runtime No — runtime objects are identical None
Native SDK bridge No — only receives final action name string None
RUM backend/intake No — no change to event format None
Session Replay No — separate pipeline, doesn't consume getContent None
Existing snapshot tests No — none cover child elements with attrs in getContent None
Other monorepo packages No — no API change, transpiler output only None

Impact on existing RUM events and queries

None. Two cases:

  • Apps without hyphenated attrs in tracked children: Runtime objects are identical before and after. __ddExtractText returns the same text. RUM action names are unchanged. No dashboards, monitors, or queries are affected.
  • Apps with hyphenated attrs (the bug): Hermes failed to parse the bundle — the app crashed on startup before any RUM events were sent. No existing RUM events use the old buggy path. After this fix, these apps will start generating RUM data for the first time.

Consistency

tap.ts in the same package already uses t.stringLiteral() for all object keys. This change aligns jsxChildToRuntimeCall with that established pattern.

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
Copilot AI review requested due to automatic review settings March 13, 2026 13:43
@mariusc83 mariusc83 requested a review from a team as a code owner March 13, 2026 13:43
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a Hermes compilation error caused by the React Native Babel plugin generating invalid JS object keys inside the getContent closure when JSX attributes are hyphenated (e.g., aria-hidden, data-testid, bg-$backgroundDefault).

Changes:

  • Update jsxChildToRuntimeCall to always emit JSX attribute keys as string literals (quoted) when building the props object for getContent.
  • Add unit tests covering hyphenated attributes, multiple hyphenated attributes, and consistency for non-hyphenated attributes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
packages/react-native-babel-plugin/src/actions/rum/index.ts Always uses t.stringLiteral(...) for JSX attribute keys when generating runtime calls for getContent, ensuring valid JS for hyphenated names.
packages/react-native-babel-plugin/test/plugin.test.ts Adds tests asserting hyphenated attribute keys are quoted in the generated getContent code path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

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*\}/);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — loosened the regex to tolerate whitespace variations around structural tokens (:, =>, (), {, return) in fb40e48. The helper still scopes assertions to the getContent body (important to avoid matching the outer JSX), but is now resilient to formatting changes.

mariusc83 and others added 2 commits March 16, 2026 09:10
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 <noreply@anthropic.com>
- 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) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 16, 2026 08:41
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a Babel plugin codegen bug where hyphenated JSX attribute names inside the generated getContent closure were emitted as bare object keys (invalid JS identifiers), causing Hermes parse failures. It aligns the RUM plugin’s JSX-to-runtime conversion with the already-safe quoting approach used elsewhere.

Changes:

  • Always emit JSX attribute keys as string literals in jsxChildToRuntimeCall to support hyphenated names (e.g., aria-hidden, data-testid, bg-$...).
  • Add unit tests covering hyphenated attributes in getContent generation (single, multiple, and consistency for non-hyphenated attrs).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
packages/react-native-babel-plugin/src/actions/rum/index.ts Changes JSX attribute object key emission to always use quoted string keys.
packages/react-native-babel-plugin/test/plugin.test.ts Adds tests asserting quoted attribute keys in the getContent closure output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

return '';
}
const match = output.match(
/"getContent"\s*:\s*\(\)\s*=>\s*\{[\s\S]*?return\s+([\s\S]*?);\s*\}/
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) || '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the suggested fix works, we should not be quoting every key if not needed, I would do something like this instead:

const key =
  t.isJSXIdentifier(attr.name) &&
  t.isValidIdentifier(attr.name.name)
    ? t.identifier(attr.name.name)
    : t.stringLiteral(getNodeName(t, attr.name) || '');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Babel plugin: error with unescaped content when using useContent: true

3 participants