From 27138abd2957b5bbdd68f5a7b07626d03453dede Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Mon, 2 Feb 2015 01:30:06 +1100 Subject: [PATCH] simplify member expression checking, flesh out react component optimiser #653 --- lib/6to5/transformation/helpers/react.js | 21 +++----- .../transformers/optimisation/react/base.js | 48 ++++++++++++++++- .../optimisation/react/create-class.js | 42 ++++++++++++++- .../transformers/optimisation/react/index.js | 8 +-- .../optimisation/react/native-class.js | 34 +++++++++++- .../transformers/other/react.js | 2 +- lib/6to5/types/index.js | 53 +++++++++++++++++++ 7 files changed, 184 insertions(+), 24 deletions(-) diff --git a/lib/6to5/transformation/helpers/react.js b/lib/6to5/transformation/helpers/react.js index 5913f7466a..6c0f2ad99e 100644 --- a/lib/6to5/transformation/helpers/react.js +++ b/lib/6to5/transformation/helpers/react.js @@ -1,16 +1,12 @@ var t = require("../../types"); -exports.isCreateClassCallExpression = function (node) { +var isCreateClassCallExpression = t.buildMatchMemberExpression("React.createClass"); + +exports.isCreateClass = function (node) { if (!node || !t.isCallExpression(node)) return false; - var callee = node.callee; - if (!t.isMemberExpression(callee)) return false; - - // not React call member object - if (!t.isIdentifier(callee.object, { name: "React" })) return false; - - // not createClass call member property - if (!t.isIdentifier(callee.property, { name: "createClass" })) return false; + // not React.createClass call member object + if (!isCreateClassCallExpression(node.callee)) return false; // no call arguments var args = node.arguments; @@ -23,9 +19,4 @@ exports.isCreateClassCallExpression = function (node) { return true; }; -exports.isReactComponentMemberExpression = function (node) { - if (!node || !t.isMemberExpression(node)) return false; - if (!t.isIdentifier(node.object, { name: "React" })) return false; - if (!t.isIdentifier(node.property, { name: "Component" })) return false; - return true; -}; +exports.isReactComponent = t.buildMatchMemberExpression("React.Component"); diff --git a/lib/6to5/transformation/transformers/optimisation/react/base.js b/lib/6to5/transformation/transformers/optimisation/react/base.js index a649b939cb..e458ac0198 100644 --- a/lib/6to5/transformation/transformers/optimisation/react/base.js +++ b/lib/6to5/transformation/transformers/optimisation/react/base.js @@ -1,9 +1,53 @@ module.exports = BaseOptimiser; -function BaseOptimiser() { +var object = require("../../../../helpers/object"); +/** + * Description + * + * @param {Node} node + */ + +function BaseOptimiser(node) { + this.methods = object(); + this.types = object(); + + this.node = node; } -BaseOptimiser.prototype.optimise = function () { +/** + * Description + */ + +BaseOptimiser.prototype.run = function () { + this.getMethods(); + this.getTypes(); +}; + +/** + * Add an `ObjectExpression` `node` that contains `propTypes`. + * + * Search it and match it against the types that we can optimise + * and register it for consumption later. + * + * @param {Node} node + */ + +BaseOptimiser.prototype.addPropTypes = function (node) { + var props = node.properties; + + for (var i = 0; i < props.length; i++) { + this.addPropType(props[i]); + } +}; + +/** + * Register a `Property` node as a prop type. + * + * We'll try and resolve it to a known type if we can and normalise + * it for consumption later. + */ + +BaseOptimiser.prototype.addPropType = function (prop) { }; diff --git a/lib/6to5/transformation/transformers/optimisation/react/create-class.js b/lib/6to5/transformation/transformers/optimisation/react/create-class.js index 423e1cc392..4ae6630152 100644 --- a/lib/6to5/transformation/transformers/optimisation/react/create-class.js +++ b/lib/6to5/transformation/transformers/optimisation/react/create-class.js @@ -4,7 +4,47 @@ var BaseOptimiser = require("./base"); var util = require("../../../../util"); function CreateClassOptimiser() { - + BaseOptimiser.apply(this, arguments); } util.inherits(CreateClassOptimiser, BaseOptimiser); + +/** + * Get all function expressions. + */ + +CreateClassOptimiser.prototype.getMethods = function () { + var props = this.node.properties; + + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + + // irrelevant + if (!t.isFunctionExpression(prop.value)) continue; + + // deopt + if (prop.computed) continue; + + this.methods[prop.key.name] = prop; + } +}; + +/** + * Find a `propTypes` property. + */ + +CreateClassOptimiser.prototype.getTypes = function () { + var props = this.node.properties; + + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + var key = t.toComputedKey(prop, prop.key); + + if (t.isLiteral(key, { value: "propTypes" }) && t.isObjectExpression(prop.value)) { + this.addPropTypes(prop.value); + return; + } + } + + // not found +}; diff --git a/lib/6to5/transformation/transformers/optimisation/react/index.js b/lib/6to5/transformation/transformers/optimisation/react/index.js index 49073065e2..4471c96d7c 100644 --- a/lib/6to5/transformation/transformers/optimisation/react/index.js +++ b/lib/6to5/transformation/transformers/optimisation/react/index.js @@ -5,13 +5,13 @@ var react = require("../../../helpers/react"); exports.optional = true; exports.CallExpression = function (node) { - if (react.isCreateClassCallExpression(node)) { - new CreateClassOptimiser(node.arguments[0]).optimise(); + if (react.isCreateClass(node)) { + new CreateClassOptimiser(node.arguments[0]).run(); } }; exports.CallExpression = function (node) { - if (react.isReactComponentMemberExpression(node.superClass)) { - new NativeClassOptimiser(node).optimise(); + if (react.isReactComponent(node.superClass)) { + new NativeClassOptimiser(node).run(); } }; diff --git a/lib/6to5/transformation/transformers/optimisation/react/native-class.js b/lib/6to5/transformation/transformers/optimisation/react/native-class.js index e86899f362..c51332a1a7 100644 --- a/lib/6to5/transformation/transformers/optimisation/react/native-class.js +++ b/lib/6to5/transformation/transformers/optimisation/react/native-class.js @@ -2,9 +2,41 @@ module.exports = NativeClassOptimiser; var BaseOptimiser = require("./base"); var util = require("../../../../util"); +var t = require("../../../../types"); function NativeClassOptimiser() { - + BaseOptimiser.apply(this, arguments); } util.inherits(NativeClassOptimiser, BaseOptimiser); + +/** + * Get all instance methods. + */ + +NativeClassOptimiser.prototype.getMethods = function () { + var body = this.node.body; + + for (var i = 0; i < body.length; i++) { + var node = body[i]; + + // PrivateDeclaration etc + if (!t.isMethodDefinition(node)) continue; + + // deopt + if (node.computed) continue; + + // irrelevant + if (node.static) continue; + + this.methods[node.key.name] = node; + } +}; + +/** + * Description + */ + +NativeClassOptimiser.prototype.getTypes = function () { + +}; diff --git a/lib/6to5/transformation/transformers/other/react.js b/lib/6to5/transformation/transformers/other/react.js index 09c39b0e12..e2e43dcd2d 100644 --- a/lib/6to5/transformation/transformers/other/react.js +++ b/lib/6to5/transformation/transformers/other/react.js @@ -199,7 +199,7 @@ var cleanJSXElementLiteralChild = function (child, args) { // display names var addDisplayName = function (id, call) { - if (!react.isCreateClassCallExpression(call)) return; + if (!react.isCreateClass(call)) return; var props = call.arguments[0].properties; var safe = true; diff --git a/lib/6to5/types/index.js b/lib/6to5/types/index.js index a8788a9c8b..854eab9cc6 100644 --- a/lib/6to5/types/index.js +++ b/lib/6to5/types/index.js @@ -399,6 +399,59 @@ t.ensureBlock = function (node, key) { node[key] = t.toBlock(node[key], node); }; +/** + * Build a function that when called will return whether or not the + * input `node` `MemberExpression` matches the input `match`. + * + * For example, given the match `React.createClass` it would match the + * parsed nodes of `React.createClass` and `React["createClass"]`. + * + * @param {String} match Dot delimetered string + * @returns {Function} + */ + +t.buildMatchMemberExpression = function (match) { + var parts = match.split("."); + + return function (node) { + // not a member expression + if (!t.isMemberExpression(node)) return false; + + var search = []; + var i = 0; + + while (search.length) { + var node = search.unshift(); + + if (t.isIdentifier(node)) { + // this part doesn't match + if (parts[i] !== node.name) return false; + } else if (t.isLiteral(node)) { + // this part doesn't match + if (parts[i] !== node.value) return false; + } else if (t.isMemberExpression(node)) { + if (node.computed && !t.isLiteral(node.property)) { + // we can't deal with this + return false; + } else { + search.push(node.object); + search.push(node.property); + } + } else { + // we can't deal with this + return false; + } + + // too many parts + if (++i > parts.length) { + return false; + } + } + + return true; + }; +}; + /** * Description *