Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hoist toSequenceExpression's convert helper #5693

Merged
merged 4 commits into from May 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/babel-types/package.json
Expand Up @@ -14,6 +14,7 @@
"to-fast-properties": "^1.0.1"
},
"devDependencies": {
"babel-generator": "^6.22.0",
"babylon": "^6.8.2"
}
}
136 changes: 69 additions & 67 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,77 +88,14 @@ export function toSequenceExpression(nodes: Array<Object>, scope: Scope): ?Objec
if (!nodes || !nodes.length) return;

const declars = [];
let bailed = false;
const result = gatherSequenceExpressions(nodes, scope, declars);
if (!result) return;

const result = convert(nodes);
if (bailed) return;

for (let i = 0; i < declars.length; i++) {
scope.push(declars[i]);
for (const declar of (declars: Array)) {
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 || !alternate) 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 () {
describe("valueToNode", function () {
Expand Down Expand Up @@ -145,4 +156,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");
});
});
});