add ability to explode a list of statements when trying to replace an expression - damn this is powerful and allows some SUPER cool scenarios

This commit is contained in:
Sebastian McKenzie 2015-03-21 15:26:55 +11:00
parent 30b1c0154d
commit 70d068226a
23 changed files with 211 additions and 103 deletions

View File

@ -33,8 +33,7 @@ export default function (exports, opts) {
var nodes = [];
var exploded = explode(node.left, nodes, file, scope);
nodes.push(buildAssignment(exploded.ref, opts.build(exploded.uid, node.right)));
return t.toSequenceExpression(nodes, scope);
return nodes;
};
exports.BinaryExpression = function (node) {

View File

@ -40,6 +40,6 @@ export default function (exports, opts) {
// todo: duplicate expression node
nodes.push(exploded.ref);
return t.toSequenceExpression(nodes, scope);
return nodes;
};
};

View File

@ -5,7 +5,7 @@ import * as t from "../../types";
function isIllegalBareSuper(node, parent) {
if (!t.isSuperExpression(node)) return false;
if (!t.isSuper(node)) return false;
if (t.isMemberExpression(parent, { computed: false })) return false;
if (t.isCallExpression(parent, { callee: node })) return false;
return true;
@ -189,13 +189,13 @@ export default class ReplaceSupers {
looseHandle(path: TraversalPath, getThisReference: Function) {
var node = path.node;
if (path.isSuperExpression()) {
if (path.isSuper()) {
this.hasSuper = true;
return this.getLooseSuperProperty(node, path.parent);
} else if (path.isCallExpression()) {
var callee = node.callee;
if (!t.isMemberExpression(callee)) return;
if (!t.isSuperExpression(callee.object)) return;
if (!t.isSuper(callee.object)) return;
// super.test(); -> objectRef.prototype.MethodName.call(this);
this.hasSuper = true;
@ -224,7 +224,7 @@ export default class ReplaceSupers {
if (t.isCallExpression(node)) {
var callee = node.callee;
if (t.isSuperExpression(callee)) {
if (t.isSuper(callee)) {
// super(); -> _get(Object.getPrototypeOf(objectRef), "MethodName", this).call(this);
property = methodNode.key;
computed = methodNode.computed;
@ -237,17 +237,17 @@ export default class ReplaceSupers {
var methodName = methodNode.key.name || "METHOD_NAME";
throw this.file.errorWithNode(node, messages.get("classesIllegalSuperCall", methodName));
}
} else if (t.isMemberExpression(callee) && t.isSuperExpression(callee.object)) {
} else if (t.isMemberExpression(callee) && t.isSuper(callee.object)) {
// super.test(); -> _get(Object.getPrototypeOf(objectRef.prototype), "test", this).call(this);
property = callee.property;
computed = callee.computed;
args = node.arguments;
}
} else if (t.isMemberExpression(node) && t.isSuperExpression(node.object)) {
} else if (t.isMemberExpression(node) && t.isSuper(node.object)) {
// super.name; -> _get(Object.getPrototypeOf(objectRef.prototype), "name", this);
property = node.property;
computed = node.computed;
} else if (t.isAssignmentExpression(node) && t.isSuperExpression(node.left.object) && methodNode.kind === "set") {
} else if (t.isAssignmentExpression(node) && t.isSuper(node.left.object) && methodNode.kind === "set") {
// super.name = "val"; -> _set(Object.getPrototypeOf(objectRef.prototype), "name", this);
this.hasSuper = true;
return this.setSuperProperty(node.left.property, node.right, node.left.computed, getThisReference());

View File

@ -5,9 +5,8 @@ export var check = t.isArrowFunctionExpression;
export function ArrowFunctionExpression(node) {
t.ensureBlock(node);
node._aliasFunction = "arrow";
node.expression = false;
node.type = "FunctionExpression";
node.type = "ShadowFunctionExpression";
return node;
}

View File

@ -354,8 +354,7 @@ class BlockScoping {
var params = values(outsideRefs);
// build the closure that we're going to wrap the block with
var fn = t.functionExpression(null, params, t.blockStatement(block.body));
fn._aliasFunction = true;
var fn = t.shadowFunctionExpression(null, params, t.blockStatement(block.body));
// replace the current block body with the one we're going to build
block.body = this.body;

View File

@ -33,7 +33,7 @@ var verifyConstructorVisitor = traverse.explode({
CallExpression: {
enter(node, parent, scope, state) {
if (this.get("callee").isSuperExpression()) {
if (this.get("callee").isSuper()) {
state.hasBareSuper = true;
if (!state.hasSuper) {

View File

@ -113,22 +113,13 @@ export function ExpressionStatement(node, parent, scope, file) {
if (!t.isPattern(expr.left)) return;
if (file.isConsequenceExpressionStatement(node)) return;
var nodes = [];
var ref = scope.generateUidIdentifier("ref");
nodes.push(t.variableDeclaration("var", [
t.variableDeclarator(ref, expr.right)
]));
var destructuring = new DestructuringTransformer({
operator: expr.operator,
file: file,
scope: scope,
nodes: nodes
scope: scope,
file: file,
});
destructuring.init(expr.left, ref);
return nodes;
return destructuring.init(expr.left, expr.right);
}
export function AssignmentExpression(node, parent, scope, file) {
@ -138,7 +129,7 @@ export function AssignmentExpression(node, parent, scope, file) {
scope.push({ id: ref });
var nodes = [];
nodes.push(t.assignmentExpression("=", ref, node.right));
nodes.push(t.expressionStatement(t.assignmentExpression("=", ref, node.right)));
var destructuring = new DestructuringTransformer({
operator: node.operator,
@ -148,9 +139,9 @@ export function AssignmentExpression(node, parent, scope, file) {
});
destructuring.init(node.left, ref);
nodes.push(ref);
nodes.push(t.expressionStatement(ref));
return t.toSequenceExpression(nodes, scope);
return nodes;
}
function variableDeclarationHasPattern(node) {
@ -218,20 +209,29 @@ export function VariableDeclaration(node, parent, scope, file) {
return nodes;
}
var hasRest = function (pattern) {
function hasRest(pattern) {
for (var i = 0; i < pattern.elements.length; i++) {
if (t.isRestElement(pattern.elements[i])) {
return true;
}
}
return false;
}
var arrayUnpackVisitor = {
enter(node, parent, scope, state) {
if (this.isReferencedIdentifier() && state.bindings[node.name]) {
state.deopt = true;
this.stop();
}
}
};
class DestructuringTransformer {
constructor(opts) {
this.blockHoist = opts.blockHoist;
this.operator = opts.operator;
this.nodes = opts.nodes;
this.nodes = opts.nodes || [];
this.scope = opts.scope;
this.file = opts.file;
this.kind = opts.kind;
@ -393,7 +393,11 @@ class DestructuringTransformer {
if (!pattern.elements[i]) return false;
}
return true;
// deopt on reference to left side identifiers
var bindings = t.getBindingIdentifiers(pattern);
var state = { deopt: false, bindings };
this.scope.traverse(arr, arrayUnpackVisitor, state);
return !state.deopt;
}
pushUnpackedArrayPattern(pattern, arr) {
@ -474,14 +478,19 @@ class DestructuringTransformer {
// trying to destructure a value that we can't evaluate more than once so we
// need to save it to a variable
if (!t.isArrayExpression(ref) && !t.isMemberExpression(ref) && !t.isIdentifier(ref)) {
var key = this.scope.generateUidBasedOnNode(ref);
this.nodes.push(this.buildVariableDeclaration(key, ref));
ref = key;
var shouldMemoise = true;
if (!t.isArrayExpression(ref) && !t.isMemberExpression(ref)) {
var memo = this.scope.generateMemoisedReference(ref, true);
if (memo) {
this.nodes.push(this.buildVariableDeclaration(memo, ref));
ref = memo;
}
}
//
this.push(pattern, ref);
return this.nodes;
}
}

View File

@ -1,7 +1,7 @@
import ReplaceSupers from "../../helpers/replace-supers";
import * as t from "../../../types";
export var check = t.isSuperExpression;
export var check = t.isSuper;
function Property(node, scope, getObjectRef, file) {
if (!node.method) return;

View File

@ -31,7 +31,7 @@ exports.Function = function (node, parent, scope, file) {
var body = [];
var argsIdentifier = t.identifier("arguments");
argsIdentifier._ignoreAliasFunctions = true;
argsIdentifier._shadowedFunctionLiteral = true;
var lastNonDefaultParam = 0;
@ -91,8 +91,7 @@ exports.Function = function (node, parent, scope, file) {
node.params = node.params.slice(0, lastNonDefaultParam);
if (state.iife) {
var container = t.functionExpression(null, [], node.body, node.generator);
container._aliasFunction = true;
var container = t.shadowFunctionExpression(null, [], node.body, node.generator);
body.push(t.returnStatement(t.callExpression(container, [])));

View File

@ -63,7 +63,7 @@ exports.Function = function (node, parent, scope, file) {
var argsId = t.identifier("arguments");
// otherwise `arguments` will be remapped in arrow functions
argsId._ignoreAliasFunctions = true;
argsId._shadowedFunctionLiteral = true;
// support patterns
if (t.isPattern(rest)) {

View File

@ -104,8 +104,6 @@ export function ObjectExpression(node, parent, scope, file) {
//
var body = [];
var container = t.functionExpression(null, [], t.blockStatement(body));
container._aliasFunction = true;
//
@ -121,7 +119,7 @@ export function ObjectExpression(node, parent, scope, file) {
t.variableDeclarator(objId, t.objectExpression(initProps))
]));
body.push(t.returnStatement(objId));
body.push(t.expressionStatement(objId));
return t.callExpression(container, []);
return body;
}

View File

@ -82,7 +82,7 @@ export function CallExpression(node, parent, scope) {
var callee = node.callee;
if (this.get("callee").isMemberExpression()) {
var temp = scope.generateTempBasedOnNode(callee.object);
var temp = scope.generateMemoisedReference(callee.object);
if (temp) {
callee.object = t.assignmentExpression("=", temp, callee.object);
contextLiteral = temp;

View File

@ -16,8 +16,7 @@ export function ComprehensionExpression(node, parent, scope, file) {
function generator(node) {
var body = [];
var container = t.functionExpression(null, [], t.blockStatement(body), true);
container._aliasFunction = true;
var container = t.shadowFunctionExpression(null, [], t.blockStatement(body), true);
body.push(buildComprehension(node, function () {
return t.expressionStatement(t.yieldExpression(node.body));
@ -32,7 +31,7 @@ function array(node, parent, scope, file) {
var container = util.template("array-comprehension-container", {
KEY: uid
});
container.callee._aliasFunction = true;
container.callee.type = "ShadowFunctionExpression";
var block = container.callee.body;
var body = block.body;

View File

@ -8,9 +8,6 @@ export default {
"validation.undeclaredVariableCheck": require("./validation/undeclared-variable-check"),
"validation.react": require("./validation/react"),
// needs to be before `_aliasFunction`
"es6.arrowFunctions": require("./es6/arrow-functions"),
// this goes at the start so we only transform the original user code
"spec.functionName": require("./spec/function-name"),
@ -22,7 +19,7 @@ export default {
_modules: require("./internal/modules"),
// needs to be before `regenerator` due to generator comprehensions
// needs to be before `_aliasFunction`
// needs to be before `_shadowFunctions`
"es7.comprehensions": require("./es7/comprehensions"),
"es6.classes": require("./es6/classes"),
@ -38,10 +35,10 @@ export default {
"es5.properties.mutators": require("./es5/properties.mutators"),
"es6.properties.shorthand": require("./es6/properties.shorthand"),
// needs to be before `_aliasFunction` due to define property closure
// needs to be before `_shadowFunctions` due to define property closure
"es6.properties.computed": require("./es6/properties.computed"),
"optimisation.es6.forOf": require("./optimisation/flow.for-of"),
"optimisation.flow.forOf": require("./optimisation/flow.for-of"),
"es6.forOf": require("./es6/for-of"),
"es6.regex.sticky": require("./es6/regex.sticky"),
@ -61,7 +58,7 @@ export default {
// needs to be before `es6.blockScoping` as let variables may be produced
"es6.destructuring": require("./es6/destructuring"),
// needs to be before `_aliasFunction` due to block scopes sometimes being wrapped in a
// needs to be before `_shadowFunctions` due to block scopes sometimes being wrapped in a
// closure
"es6.blockScoping": require("./es6/block-scoping"),
@ -88,7 +85,10 @@ export default {
_declarations: require("./internal/declarations"),
_aliasFunctions: require("./internal/alias-functions"),
// needs to be before `_shadowFunctions`
"es6.arrowFunctions": require("./es6/arrow-functions"),
_shadowFunctions: require("./internal/alias-functions"),
"es6.symbols": require("./es6/symbols"),
"spec.undefinedToVoid": require("./spec/undefined-to-void"),

View File

@ -2,11 +2,11 @@ import * as t from "../../../types";
var functionChildrenVisitor = {
enter(node, parent, scope, state) {
if (this.isFunction() && !node._aliasFunction) {
if (this.isFunction()) {
return this.skip();
}
if (node._ignoreAliasFunctions) return this.skip();
if (node._shadowedFunctionLiteral) return this.skip();
var getId;
@ -24,18 +24,18 @@ var functionChildrenVisitor = {
var functionVisitor = {
enter(node, parent, scope, state) {
if (!node._aliasFunction) {
if (this.isFunction()) {
// stop traversal of this node as it'll be hit again by this transformer
return this.skip();
} else {
return;
}
if (this.isFunction()) {
// stop traversal of this node as it'll be hit again by this transformer
return this.skip();
} else if (!this.isShadowFunctionExpression()) {
return;
}
// traverse all child nodes of this function and find `arguments` and `this`
this.traverse(functionChildrenVisitor, state);
node.type = "FunctionExpression";
return this.skip();
}
};

View File

@ -1 +1,2 @@
export { bare as FunctionExpression } from "../../helpers/name-method";
export { bare as ArrowFunctionExpression } from "../../helpers/name-method";

View File

@ -24,13 +24,13 @@ export function AssignmentExpression(node, parent, scope, file) {
var nodes = [];
var left = node.left.object;
var temp = scope.generateTempBasedOnNode(node.left.object);
var temp = scope.generateMemoisedReference(left);
nodes.push(t.expressionStatement(t.assignmentExpression("=", temp, left)));
nodes.push(buildDefaultsCallExpression(node, temp, file));
if (temp) nodes.push(temp);
return t.toSequenceExpression(nodes);
return nodes;
}
export function ExpressionStatement(node, parent, scope, file) {

View File

@ -9,6 +9,36 @@ import extend from "lodash/object/extend";
import Scope from "../scope";
import * as t from "../../types";
var hoistVariablesVisitor = {
enter(node, parent, scope ) {
if (this.isFunction()) {
return this.skip();
}
if (this.isVariableDeclaration() && node.kind === "var") {
var bindings = this.getBindingIdentifiers();
for (var key in bindings) {
scope.push({
id: bindings[key].identifiers
});
}
var exprs = [];
for (var i = 0; i < node.declarations.length; i++) {
var declar = node.declarations[i];
if (declar.init) {
exprs.push(t.expressionStatement(
t.assignmentExpression("=", declar.id, declar.init)
));
}
}
return exprs;
}
}
};
export default class TraversalPath {
constructor(parent, container) {
this.container = container;
@ -86,6 +116,8 @@ export default class TraversalPath {
}
this.setScope(file);
this.type = this.node && this.node.type;
}
remove() {
@ -130,16 +162,28 @@ export default class TraversalPath {
set node(replacement) {
if (!replacement) return this.remove();
var oldNode = this.node;
var isArray = Array.isArray(replacement);
var oldNode = this.node;
var isArray = Array.isArray(replacement);
if (isArray && replacement.length === 1) {
isArray = false;
replacement = replacement[0];
}
var replacements = isArray ? replacement : [replacement];
// inherit comments from original node to the first replacement node
var inheritTo = replacements[0];
if (inheritTo) t.inheritsComments(inheritTo, oldNode);
//
if (t.isStatement(replacements[0]) && t.isType(this.type, "Expression")) {
return this.setStatementsToExpression(replacements);
}
// replace the node
this.container[this.key] = replacement;
this.type = replacement.type;
// potentially create new scope
this.setScope();
@ -162,6 +206,50 @@ export default class TraversalPath {
}
}
getLastStatements(): Array<TraversalPath> {
var paths = [];
var add = function (path) {
paths = paths.concat(path.getLastStatements());
};
if (this.isIfStatement()) {
add(this.get("consequent"));
add(this.get("alternate"));
} else if (this.isFor() || this.isWhile()) {
add(this.get("body"));
} else if (this.isProgram() || this.isBlockStatement()) {
add(this.get("body").pop());
}
return paths;
}
setStatementsToExpression(nodes: Array) {
var toSequenceExpression = t.toSequenceExpression(nodes, this.scope);
if (toSequenceExpression) {
return this.node = toSequenceExpression;
} else {
var container = t.shadowFunctionExpression(null, [], t.blockStatement(nodes));
this.node = t.callExpression(container, []);
// add implicit returns to all ending expression statements
var last = this.getLastStatements();
for (var i = 0; i < last.length; i++) {
var lastNode = last[i];
if (lastNode.isExpressionStatement()) {
lastNode.node = t.returnStatement(lastNode.node.expression);
}
}
this.traverse(hoistVariablesVisitor);
return this.node;
}
}
call(key) {
var node = this.node;
if (!node) return;

View File

@ -198,8 +198,8 @@ export default class Scope {
* Description
*/
generateTempBasedOnNode(node: Object): ?Object {
if (t.isThisExpression(node) || t.isSuperExpression(node)) {
generateMemoisedReference(node: Object, dontPush?: boolean): ?Object {
if (t.isThisExpression(node) || t.isSuper(node)) {
return null;
}
@ -208,10 +208,7 @@ export default class Scope {
}
var id = this.generateUidBasedOnNode(node);
this.push({
key: id.name,
id: id
});
if (!dontPush) this.push({ id });
return id;
}
@ -513,7 +510,7 @@ export default class Scope {
getFunctionParent() {
var scope = this;
while (scope.parent && !t.isFunction(scope.block)) {
while (scope.parent && !t.isFunction(scope.block) && !t.isShadowFunctionExpression(scope.block)) {
scope = scope.parent;
}
return scope;

View File

@ -53,6 +53,7 @@
"ArrayExpression": ["Expression"],
"AssignmentExpression": ["Expression"],
"AwaitExpression": ["Expression"],
"ShadowFunctionExpression": ["Expression"],
"CallExpression": ["Expression"],
"ComprehensionExpression": ["Expression", "Scopable"],
"ConditionalExpression": ["Expression"],
@ -64,7 +65,7 @@
"SequenceExpression": ["Expression"],
"TaggedTemplateExpression": ["Expression"],
"ThisExpression": ["Expression"],
"SuperExpression": ["Expression"],
"Super": ["Expression"],
"UpdateExpression": ["Expression"],
"JSXEmptyExpression": ["Expression"],
"JSXMemberExpression": ["Expression"],

View File

@ -45,6 +45,13 @@
"tokens": null
},
"ShadowFunctionExpression": {
"id": null,
"params": null,
"body": null,
"generator": false
},
"FunctionExpression": {
"id": null,
"params": null,

View File

@ -27,31 +27,46 @@ export function toComputedKey(node: Object, key: Object = node.key || node.prope
*/
export function toSequenceExpression(nodes: Array<Object>, scope: Scope): Object {
var exprs = [];
var declars = [];
var exprs = [];
each(nodes, function (node) {
for (let i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (t.isExpression(node)) {
exprs.push(node);
} if (t.isExpressionStatement(node)) {
} else if (t.isExpressionStatement(node)) {
exprs.push(node.expression);
} else if (t.isVariableDeclaration(node)) {
if (node.kind !== "var") return; // bailed
each(node.declarations, function (declar) {
scope.push({
declars.push({
kind: node.kind,
id: declar.id
});
exprs.push(t.assignmentExpression("=", declar.id, declar.init));
});
} else if (t.isIfStatement(node)) {
return t.conditionalExpression(
exprs.push(t.conditionalExpression(
node.test,
node.consequent ? t.toSequenceExpression([node.consequent]) : t.identifier("undefined"),
node.alternate ? t.toSequenceExpression([node.alternate]) : t.identifier("undefined")
);
));
} else if (t.isBlockStatement(node)) {
return t.toSequenceExpression(node.body);
exprs.push(t.toSequenceExpression(node.body));
} else {
// bailed, we can't understand this
return;
}
});
}
//
for (let i = 0; i < declars.length; i++) {
scope.push(declars[i]);
}
//
if (exprs.length === 1) {
return exprs[0];

View File

@ -24,7 +24,6 @@ function registerType(type: string, skipAliasCheck?: boolean) {
};
}
export var STATEMENT_OR_BLOCK_KEYS = ["consequent", "body", "alternate"];
export var NATIVE_TYPE_NAMES = ["Array", "Object", "Number", "Boolean", "Date", "Array", "String", "Promise", "Set", "Map", "WeakMap", "WeakSet", "Uint16Array", "ArrayBuffer", "DataView", "Int8Array", "Uint8Array", "Uint8ClampedArray", "Uint32Array", "Int32Array", "Float32Array", "Int16Array", "Float64Array"];
export var FLATTENABLE_KEYS = ["body", "expressions"];
@ -65,25 +64,23 @@ export const TYPES = Object.keys(t.VISITOR_KEYS).concat(Object.keys(t.FLIPPED_AL
export function is(type: string, node: Object, opts?: Object, skipAliasCheck?: boolean): boolean {
if (!node) return false;
var typeMatches = type === node.type;
var matches = isType(node.type, type);
if (!matches) return false;
if (!typeMatches && !skipAliasCheck) {
var aliases = t.FLIPPED_ALIAS_KEYS[type];
if (typeof aliases !== "undefined") {
typeMatches = aliases.indexOf(node.type) > -1;
}
}
if (!typeMatches) {
return false;
}
if (typeof opts !== "undefined") {
if (typeof opts === "undefined") {
return true;
} else {
return t.shallowEqual(node, opts);
}
}
return true;
export function isType(nodeType, targetType) {
if (nodeType === targetType) return true;
var aliases = t.FLIPPED_ALIAS_KEYS[targetType];
if (aliases) return aliases.indexOf(nodeType) > -1;
return false;
}
each(t.VISITOR_KEYS, function (keys, type) {