From cb7e3d310a9d5b712de794e6bac8db5d2148d72f Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Wed, 9 Sep 2026 02:17:56 -0700 Subject: [PATCH 1/2] Generated from a GitHub Pull Request. Run 'jf sync' on this diff to load the correct commit data. Differential Revision: D119065685 --- .../__tests__/inline-platform-plugin-test.js | 10 +++++++++ .../src/inline-platform-plugin.js | 22 ++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js b/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js index 2e182e5f763b..8c3d53dba1ab 100644 --- a/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js +++ b/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js @@ -483,6 +483,16 @@ describe('Platform.select', () => { expect(select('{ios: 1, ios: 2}')).toContain('const value=2'); }); + test('does not discard impure initializers', () => { + expectUnchanged(` + const value = require('react-native').Platform.select({ + ios: first(), + android: android(), + ios: last(), + }); + `); + }); + test('does not inline computed keys', () => { expect(select('{[key]: 1, default: 2}')).toContain('Platform.select'); }); diff --git a/packages/react-native-babel-preset/src/inline-platform-plugin.js b/packages/react-native-babel-preset/src/inline-platform-plugin.js index f70a24b3e95e..3981c94787d7 100644 --- a/packages/react-native-babel-preset/src/inline-platform-plugin.js +++ b/packages/react-native-babel-preset/src/inline-platform-plugin.js @@ -520,13 +520,25 @@ module.exports = function inlinePlatformPlugin( return; } - path.replaceWith( - findProperty(spec, platform, () => - findProperty(spec, 'native', () => - findProperty(spec, 'default', () => t.identifier('undefined')), - ), + const replacement = findProperty(spec, platform, () => + findProperty(spec, 'native', () => + findProperty(spec, 'default', () => t.identifier('undefined')), ), ); + // Inlining must not drop side effects from discarded property values. + // Assess the property itself: an ObjectMethod has no `.value`, so + // checking `property.value` would wrongly treat every method as + // impure and skip inlining. + if ( + spec.properties.every( + property => + (t.isObjectProperty(property) && + property.value === replacement) || + path.scope.isPure(property), + ) + ) { + path.replaceWith(replacement); + } }, }, }; From eafde44ef0f37b9cad5a868d937c1ce825b51505 Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Wed, 9 Sep 2026 08:07:22 -0700 Subject: [PATCH 2/2] Clone ObjectMethod before toExpression in preset (#58424) Summary: Pull Request resolved: https://github.com/react/react-native/pull/58424 The React Native Babel preset replaces `Platform.select({...})` with the selected property during production transforms. When the selected property is an object method, converting it to an expression mutates the node in place. Since the replacement is computed before the purity check, bailing out on an impure sibling leaves the mutated method behind, producing invalid output. Clone the method before converting it. Also assess purity on the property itself: an `ObjectMethod` has no value, so checking the value treated every method as impure and skipped inlining. ## Changelog: [GENERAL] [FIXED] - Fix Platform.select inlining producing invalid output for object methods when discarding impure initializers is skipped Reviewed By: GijsWeterings Differential Revision: D119305530 --- .../src/__tests__/inline-platform-plugin-test.js | 9 +++++++++ .../src/inline-platform-plugin.js | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js b/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js index 8c3d53dba1ab..fd217bb5258d 100644 --- a/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js +++ b/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js @@ -493,6 +493,15 @@ describe('Platform.select', () => { `); }); + test('does not mutate object methods when bailing out on impure initializers', () => { + expectUnchanged(` + const value = require('react-native').Platform.select({ + ios() { return 1; }, + android: sideEffect(), + }); + `); + }); + test('does not inline computed keys', () => { expect(select('{[key]: 1, default: 2}')).toContain('Platform.select'); }); diff --git a/packages/react-native-babel-preset/src/inline-platform-plugin.js b/packages/react-native-babel-preset/src/inline-platform-plugin.js index 3981c94787d7..2bba88342fa7 100644 --- a/packages/react-native-babel-preset/src/inline-platform-plugin.js +++ b/packages/react-native-babel-preset/src/inline-platform-plugin.js @@ -443,7 +443,9 @@ module.exports = function inlinePlatformPlugin( if (t.isObjectProperty(property)) { return property.value; } - return t.toExpression(property); + // Clone: toExpression mutates in place, e.g. `ios() {}` would be + // left mutated if the purity check below bails out. + return t.toExpression(t.cloneNode(property)); } } return fallback();