Skip to content

Commit

Permalink
Hoist toSequenceExpression's convert helper (babel#5693)
Browse files Browse the repository at this point in the history
  • Loading branch information
jridgewell committed May 19, 2017
1 parent 8cd4a62 commit 58216f5
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 70 deletions.
1 change: 1 addition & 0 deletions packages/babel-types/package.json
Expand Up @@ -13,6 +13,7 @@
"to-fast-properties": "^1.0.1"
},
"devDependencies": {
"babel-generator": "7.0.0-alpha.9",
"babylon": "^7.0.0-beta.8"
}
}
139 changes: 69 additions & 70 deletions packages/babel-types/src/converters.js
Expand Up @@ -10,6 +10,71 @@ export function toComputedKey(node: Object, key: Object = node.key || node.prope
return key;
}


function gatherSequenceExpressions(nodes: Array<Object>, scope: Scope, declars: Array<Object>): ?Object {
const exprs = [];
let ensureLastUndefined = true;

for (const node of nodes) {
ensureLastUndefined = false;

if (t.isExpression(node)) {
exprs.push(node);
} else if (t.isExpressionStatement(node)) {
exprs.push(node.expression);
} else if (t.isVariableDeclaration(node)) {
if (node.kind !== "var") return; // bailed

for (const declar of (node.declarations: Array)) {
const bindings = t.getBindingIdentifiers(declar);
for (const key in bindings) {
declars.push({
kind: node.kind,
id: bindings[key],
});
}

if (declar.init) {
exprs.push(t.assignmentExpression("=", declar.id, declar.init));
}
}

ensureLastUndefined = true;
} else if (t.isIfStatement(node)) {
const consequent = node.consequent ?
gatherSequenceExpressions([node.consequent], scope, declars) :
scope.buildUndefinedNode();
const alternate = node.alternate ?
gatherSequenceExpressions([node.alternate], scope, declars) :
scope.buildUndefinedNode();
if (!consequent || !alternate) return; // bailed

exprs.push(t.conditionalExpression(node.test, consequent, alternate));
} else if (t.isBlockStatement(node)) {
const body = gatherSequenceExpressions(node.body, scope, declars);
if (!body) return; // bailed

exprs.push(body);
} else if (t.isEmptyStatement(node)) {
// empty statement so ensure the last item is undefined if we're last
ensureLastUndefined = true;
} else {
// bailed, we can't turn this statement into an expression
return;
}
}

if (ensureLastUndefined) {
exprs.push(scope.buildUndefinedNode());
}

if (exprs.length === 1) {
return exprs[0];
} else {
return t.sequenceExpression(exprs);
}
}

/**
* Turn an array of statement `nodes` into a `SequenceExpression`.
*
Expand All @@ -23,80 +88,14 @@ export function toSequenceExpression(nodes: Array<Object>, scope: Scope): ?Objec
if (!nodes || !nodes.length) return;

const declars = [];
let bailed = false;

const result = convert(nodes);
if (bailed) return;
const result = gatherSequenceExpressions(nodes, scope, declars);
if (!result) return;

for (let i = 0; i < declars.length; i++) {
scope.push(declars[i]);
for (const declar of declars) {
scope.push(declar);
}

return result;

function convert(nodes) {
let ensureLastUndefined = false;
const exprs = [];

for (const node of (nodes: Array)) {
if (t.isExpression(node)) {
exprs.push(node);
} else if (t.isExpressionStatement(node)) {
exprs.push(node.expression);
} else if (t.isVariableDeclaration(node)) {
if (node.kind !== "var") return bailed = true; // bailed

for (const declar of (node.declarations: Array)) {
const bindings = t.getBindingIdentifiers(declar);
for (const key in bindings) {
declars.push({
kind: node.kind,
id: bindings[key],
});
}

if (declar.init) {
exprs.push(t.assignmentExpression("=", declar.id, declar.init));
}
}

ensureLastUndefined = true;
continue;
} else if (t.isIfStatement(node)) {
const consequent = node.consequent ? convert([node.consequent]) : scope.buildUndefinedNode();
const alternate = node.alternate ? convert([node.alternate]) : scope.buildUndefinedNode();

if (consequent === true || alternate === true) {
return bailed = true;
}

exprs.push(t.conditionalExpression(node.test, consequent, alternate));
} else if (t.isBlockStatement(node)) {
exprs.push(convert(node.body));
} else if (t.isEmptyStatement(node)) {
// empty statement so ensure the last item is undefined if we're last
ensureLastUndefined = true;
continue;
} else {
// bailed, we can't turn this statement into an expression
return bailed = true;
}

ensureLastUndefined = false;
}

if (ensureLastUndefined || exprs.length === 0) {
exprs.push(scope.buildUndefinedNode());
}

//

if (exprs.length === 1) {
return exprs[0];
} else {
return t.sequenceExpression(exprs);
}
}
}

export function toKeyAlias(node: Object, key: Object = node.key): string {
Expand Down
112 changes: 112 additions & 0 deletions packages/babel-types/test/converters.js
@@ -1,5 +1,16 @@
import * as t from "../lib";
import { assert } from "chai";
import { parse } from "babylon";
import generate from "babel-generator";

function parseCode(string) {
return parse(string, {
allowReturnOutsideFunction: true,
}).program.body[0];
}
function generateCode(node) {
return generate(node).code;
}

describe("converters", function () {
it("toIdentifier", function () {
Expand Down Expand Up @@ -149,4 +160,105 @@ describe("converters", function () {
t.assertProgram(node);
});
});
describe("toSequenceExpression", function () {
let scope;
const undefinedNode = t.identifier("undefined");
beforeEach(function () {
scope = [];
scope.buildUndefinedNode = function () {
return undefinedNode;
};
});
it("gathers nodes into sequence", function () {
const node = t.identifier("a");
const sequence = t.toSequenceExpression([undefinedNode, node], scope);
t.assertSequenceExpression(sequence);
assert.equal(sequence.expressions[0], undefinedNode);
assert.equal(sequence.expressions[1], node);
t.assertIdentifier(node);
});
it("avoids sequence for single node", function () {
const node = t.identifier("a");
let sequence = t.toSequenceExpression([node], scope);
assert.equal(sequence, node);

const block = t.blockStatement([t.expressionStatement(node)]);
sequence = t.toSequenceExpression([block], scope);
assert.equal(sequence, node);
});
it("gathers expression", function () {
const node = t.identifier("a");
const sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.equal(sequence.expressions[1], node);
});
it("gathers expression statement", function () {
const node = t.expressionStatement(t.identifier("a"));
const sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.equal(sequence.expressions[1], node.expression);
});
it("gathers var declarations", function () {
const node = parseCode("var a, b = 1;");
const sequence = t.toSequenceExpression([undefinedNode, node], scope);
t.assertIdentifier(scope[0].id, { name: "a" });
t.assertIdentifier(scope[1].id, { name: "b" });
assert.equal(generateCode(sequence.expressions[1]), "b = 1");
assert.equal(generateCode(sequence.expressions[2]), "undefined");
});
it("skips undefined if expression after var declaration", function () {
const node = parseCode("{ var a, b = 1; true }");
const sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.equal(generateCode(sequence.expressions[1]), "b = 1, true");
});
it("bails on let and const declarations", function () {
let node = parseCode("let a, b = 1;");
let sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.isUndefined(sequence);

node = parseCode("const b = 1;");
sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.isUndefined(sequence);
});
it("gathers if statements", function () {
let node = parseCode("if (true) { true }");
let sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.equal(generateCode(sequence.expressions[1]), "true ? true : undefined");

node = parseCode("if (true) { true } else { b }");
sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.equal(generateCode(sequence.expressions[1]), "true ? true : b");
});
it("bails in if statements if recurse bails", function () {
let node = parseCode("if (true) { return }");
let sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.isUndefined(sequence);

node = parseCode("if (true) { true } else { return }");
sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.isUndefined(sequence);
});
it("gathers block statements", function () {
let node = parseCode("{ a }");
let sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.equal(generateCode(sequence.expressions[1]), "a");

node = parseCode("{ a; b; }");
sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.equal(generateCode(sequence.expressions[1]), "a, b");
});
it("bails in block statements if recurse bails", function () {
const node = parseCode("{ return }");
const sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.isUndefined(sequence);
});
it("gathers empty statements", function () {
const node = parseCode(";");
const sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.equal(generateCode(sequence.expressions[1]), "undefined");
});
it("skips empty statement if expression afterwards", function () {
const node = parseCode("{ ; true }");
const sequence = t.toSequenceExpression([undefinedNode, node], scope);
assert.equal(generateCode(sequence.expressions[1]), "true");
});
});
});

0 comments on commit 58216f5

Please sign in to comment.