Guard private syntax preservation by transform profile (#58253)

Summary:
`customTransformOptions.unstable_preserveClassPrivate` currently disables private-field and private-method transforms for every profile. With `hermes-legacy`, the preset still lowers the surrounding class syntax, and Babel then aborts because its class transform requires the private transforms.

Only preserve private syntax when the selected profile also preserves class syntax. Stable and canary Hermes profiles keep their existing experimental behavior; legacy profiles continue lowering private fields and methods together with classes instead of crashing.

## Changelog:

[GENERAL] [FIXED] - Keep private class transforms enabled for profiles that lower classes.

Pull Request resolved: https://github.com/react/react-native/pull/58253

Test Plan:
- Added a focused `hermes-legacy` regression containing both a private field and private method with the preservation option enabled.
- Exact baseline throws Babel's private-method transform error; the fixed preset compiles and emits the normal private-field helpers.
- Full preset Jest passes: 4/4 suites, 111/111 tests, 16 snapshots.
- Fresh Flow check reports 0 errors.
- Targeted no-ignore ESLint, Prettier, and `git diff --check` pass.

No breaking change: stable/canary preservation is unchanged, while an invalid legacy configuration now compiles correctly.

Reviewed By: javache

Differential Revision: D118438778

Pulled By: vzaidman

fbshipit-source-id: 650a70d8759b135cb06f9bd3f2b45b0ee766762b
This commit is contained in:
Oskar Eichler
2026-09-03 04:52:49 -07:00
committed by meta-codesync[bot]
parent bbdeb7dc1d
commit 0c0e9f0840
2 changed files with 26 additions and 4 deletions
@@ -490,6 +490,25 @@ describe('react-native-babel-preset transform snapshots', () => {
expect(result).toContain('_classPrivateFieldLooseKey');
});
it('transforms private class fields when the profile lowers classes', () => {
const code = `
class Counter {
#count = 0;
#privateMethod() { return this.#count; }
}
`;
const result = transformCode(code, {
dev: false,
unstable_transformProfile: 'hermes-legacy',
customTransformOptions: {
unstable_preserveClassPrivate: true,
},
});
expect(result).not.toContain('#count');
expect(result).not.toContain('#privateMethod');
expect(result).toContain('_classPrivateFieldLooseKey');
});
it('preserves async/await with unstable_preserveAsync', () => {
const code = `
async function fetchData() {
+7 -4
View File
@@ -98,10 +98,13 @@ const getPreset = (src, options, babel) => {
// Preserve class syntax and related features for Hermes V1 profiles.
const preserveClasses = isHermesProfile;
// Preserve private class fields and methods if the experiment is enabled.
const preserveClassPrivate = TRUE_VALS.has(
options.customTransformOptions?.unstable_preserveClassPrivate,
);
// Private fields can only be preserved when the surrounding class syntax is
// also preserved. Babel's class transform requires the private transforms.
const preserveClassPrivate =
preserveClasses &&
TRUE_VALS.has(
options.customTransformOptions?.unstable_preserveClassPrivate,
);
// Preserve async/await syntax if the experiment is enabled.
const preserveAsync = TRUE_VALS.has(