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..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 @@ -483,6 +483,25 @@ 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 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 f70a24b3e95e..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(); @@ -520,13 +522,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); + } }, }, };