From 076150d7b6032a3faf3d7e1dccb2bc172246244e Mon Sep 17 00:00:00 2001 From: Maximilian Krause Date: Tue, 15 Sep 2026 02:03:10 -0700 Subject: [PATCH] Preserve Platform.select initializer side effects (#1929) Summary: Metro replaces `Platform.select({...})` with the selected property during production transforms. JavaScript evaluates every object property initializer before calling `Platform.select`, so this can silently remove side effects from non-selected properties. For example: ```js Platform.select({ ios: selected(), android: discarded(), }); ``` OR ```js Platform.select({ ios() { selected() }, android: discarded(), }); ``` JavaScript would run both side effects, `selected()` and `discarded()`. The plugin's current behavior removes `discarded` on iOS however. This fix preserves both side effects by testing for purity. `findProperty` also clones the selected `ObjectMethod` before calling `t.toExpression`, since `toExpression` mutates the node in place and bailing out on an impure sibling would otherwise leave the original AST mutated (e.g. `ios() {}` becoming `function () {}`), producing invalid output. Changelog: ``` - **[Fix]**: Preserve side effects from discarded `Platform.select` property initializers ``` Test Plan: Added regression tests, and ensured existing tests, linter and formatter pass. js1 test inline-plugin-test.js -- 64 passed. Verified the ObjectMethod bailout case emits the source unchanged instead of invalid JS. Differential Revision: D119919582 Pulled By: vzaidman --- .../src/__tests__/inline-plugin-test.js | 28 +++++++++++++++++++ .../src/inline-plugin.js | 17 +++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/metro-transform-plugins/src/__tests__/inline-plugin-test.js b/packages/metro-transform-plugins/src/__tests__/inline-plugin-test.js index fe2fb8d154..77708a5356 100644 --- a/packages/metro-transform-plugins/src/__tests__/inline-plugin-test.js +++ b/packages/metro-transform-plugins/src/__tests__/inline-plugin-test.js @@ -410,6 +410,34 @@ describe('inline constants', () => { }); }); + test('does not discard impure Platform.select initializers', () => { + const code = ` + var value = Platform.select({ + ios: selected(), + android: discarded(), + }); + `; + + compare([inlinePlugin], code, code, { + inlinePlatform: true, + platform: 'ios', + }); + }); + + test('does not mutate ObjectMethod when bailing out on impure initializers', () => { + const code = ` + var value = Platform.select({ + ios() { return 1; }, + android: sideEffect(), + }); + `; + + compare([inlinePlugin], code, code, { + inlinePlatform: true, + platform: 'ios', + }); + }); + test('inlines Platform.select in the code when using an ObjectMethod', () => { const code = ` function a() { diff --git a/packages/metro-transform-plugins/src/inline-plugin.js b/packages/metro-transform-plugins/src/inline-plugin.js index 4fc5dfb49d..7faf68d48b 100644 --- a/packages/metro-transform-plugins/src/inline-plugin.js +++ b/packages/metro-transform-plugins/src/inline-plugin.js @@ -138,7 +138,9 @@ export default function inlinePlugin( if (isObjectProperty(p)) { return p.value; } else if (isObjectMethod(p)) { - return t.toExpression(p); + // Clone: toExpression mutates in place, e.g. `ios() {}` would be + // left mutated if the purity check below bails out. + return t.toExpression(t.cloneNode(p)); } } } @@ -200,8 +202,17 @@ export default function inlinePlugin( findProperty(arg, 'native', () => findProperty(arg, 'default', () => t.identifier('undefined')), ); - - path.replaceWith(findProperty(arg, opts.platform, fallback)); + const selected = findProperty(arg, opts.platform, fallback); + + if ( + arg.properties.every( + property => + (isObjectProperty(property) && property.value === selected) || + scope.isPure(property), + ) + ) { + path.replaceWith(selected); + } } } },