From 8c3db9782e3c6b37b08c05d8dc1666adb843d255 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Sun, 5 Jun 2016 05:47:26 -0700 Subject: [PATCH] inline `Platform.select` Summary: We are already inlining `Platform.OS`. This diff adds support to inline calls to `Platform.select` with an object literal as first argument. The transform will replace the call with the property value corresponding to the platform, or `undefined` if it does not exist. Reviewed By: frantic Differential Revision: D3385391 fbshipit-source-id: bb068d17948ed84e381707faeaa0450399c2f306 --- .../worker/__tests__/inline-test.js | 107 ++++++++++++++++++ .../src/JSTransformer/worker/inline.js | 32 ++++++ 2 files changed, 139 insertions(+) diff --git a/packager/react-packager/src/JSTransformer/worker/__tests__/inline-test.js b/packager/react-packager/src/JSTransformer/worker/__tests__/inline-test.js index 102e39ab7..b52e07c05 100644 --- a/packager/react-packager/src/JSTransformer/worker/__tests__/inline-test.js +++ b/packager/react-packager/src/JSTransformer/worker/__tests__/inline-test.js @@ -141,6 +141,113 @@ describe('inline constants', () => { normalize(code.replace(/require\('react-native'\)\.Platform\.OS/, '"android"'))); }); + it('inlines Platform.select in the code if Platform is a global and the argument is an object literal', () => { + const code = `function a() { + var a = Platform.select({ios: 1, android: 2}); + var b = a.Platform.select({ios: 1, android: 2}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '1'))); + }); + + it('replaces Platform.select in the code if Platform is a top level import', () => { + const code = ` + var Platform = require('Platform'); + function a() { + Platform.select({ios: 1, android: 2}); + var b = a.Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2'))); + }); + + it('replaces Platform.select in the code if Platform is a top level import from react-native', () => { + const code = ` + var Platform = require('react-native').Platform; + function a() { + Platform.select({ios: 1, android: 2}); + var b = a.Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '1'))); + }); + + it('replaces require("Platform").select in the code', () => { + const code = `function a() { + var a = require('Platform').select({ios: 1, android: 2}); + var b = a.require('Platform').select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2'))); + }); + + it('replaces React.Platform.select in the code if React is a global', () => { + const code = `function a() { + var a = React.Platform.select({ios: 1, android: 2}); + var b = a.React.Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.select[^;]+/, '1'))); + }); + + it('replaces ReactNative.Platform.select in the code if ReactNative is a global', () => { + const code = `function a() { + var a = ReactNative.Platform.select({ios: 1, android: 2}); + var b = a.ReactNative.Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative\.Platform\.select[^;]+/, '1'))); + }); + + it('replaces React.Platform.select in the code if React is a top level import', () => { + const code = ` + var React = require('React'); + function a() { + var a = React.Platform.select({ios: 1, android: 2}); + var b = a.React.Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); + expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.select[^;]+/, '1'))); + }); + + it('replaces require("React").Platform.select in the code', () => { + const code = `function a() { + var a = require('React').Platform.select({ios: 1, android: 2}); + var b = a.require('React').Platform.select({}); + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual( + normalize(code.replace(/require\('React'\)\.Platform\.select[^;]+/, '2'))); + }); + + it('replaces ReactNative.Platform.select in the code if ReactNative is a top level import', () => { + const code = ` + var ReactNative = require('react-native'); + function a() { + var a = ReactNative.Plaftform.select({ios: 1, android: 2}); + var b = a.ReactNative.Platform.select; + }`; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative.Platform\.select[^;]+/, '2'))); + }); + + it('replaces require("react-native").Platform.select in the code', () => { + const code = ` + var a = require('react-native').Platform.select({ios: 1, android: 2}); + var b = a.require('react-native').Platform.select({}); + `; + const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); + expect(toString(ast)).toEqual( + normalize(code.replace(/require\('react-native'\)\.Platform\.select[^;]+/, '2'))); + }); + + it('replaces non-existing properties with `undefined`', () => { + const code = 'var a = Platform.select({ios: 1, android: 2})'; + const {ast} = inline('arbitrary.js', {code}, {platform: 'doesnotexist'}); + expect(toString(ast)).toEqual( + normalize(code.replace(/Platform\.select[^;]+/, 'undefined'))); + }); + it('replaces process.env.NODE_ENV in the code', () => { const code = `function a() { if (process.env.NODE_ENV === 'production') { diff --git a/packager/react-packager/src/JSTransformer/worker/inline.js b/packager/react-packager/src/JSTransformer/worker/inline.js index 13e20b0e2..adc1f71d1 100644 --- a/packager/react-packager/src/JSTransformer/worker/inline.js +++ b/packager/react-packager/src/JSTransformer/worker/inline.js @@ -15,6 +15,7 @@ const React = {name: 'React'}; const ReactNative = {name: 'ReactNative'}; const platform = {name: 'Platform'}; const os = {name: 'OS'}; +const select = {name: 'select'}; const requirePattern = {name: 'require'}; const env = {name: 'env'}; @@ -63,11 +64,29 @@ const isProcessEnvNodeEnv = (node, scope) => t.isIdentifier(node.object.object, processId) && isGlobal(scope.getBinding(processId.name)); +const isPlatformSelect = (node, scope) => + t.isMemberExpression(node.callee) && + t.isIdentifier(node.callee.object, platform) && + t.isIdentifier(node.callee.property, select) && + isImportOrGlobal(node.callee.object, scope, [platform]); + +const isReactPlatformSelect = (node, scope) => + t.isMemberExpression(node.callee) && + t.isIdentifier(node.callee.property, select) && + t.isMemberExpression(node.callee.object) && + t.isIdentifier(node.callee.object.property, platform) && + isImportOrGlobal(node.callee.object.object, scope, [React, ReactNative]); + const isDev = (node, parent, scope) => t.isIdentifier(node, dev) && isGlobal(scope.getBinding(dev.name)) && !(t.isMemberExpression(parent)); +function findProperty(objectExpression, key) { + const property = objectExpression.properties.find(p => p.key.name === key); + return property ? property.value : t.identifier('undefined'); +} + const inlinePlugin = { visitor: { Identifier(path, state) { @@ -86,6 +105,19 @@ const inlinePlugin = { t.stringLiteral(state.opts.dev ? 'development' : 'production')); } }, + CallExpression(path, state) { + const node = path.node; + const scope = path.scope; + const arg = node.arguments[0]; + + if (isPlatformSelect(node, scope) || isReactPlatformSelect(node, scope)) { + const replacement = t.isObjectExpression(arg) + ? findProperty(arg, state.opts.platform) + : node; + + path.replaceWith(replacement); + } + } }, };