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
This commit is contained in:
David Aurelio 2016-06-05 05:47:26 -07:00 committed by Facebook Github Bot 5
parent b52ba13950
commit dee7aadd28
2 changed files with 139 additions and 0 deletions

View File

@ -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') {

View File

@ -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);
}
}
},
};