Hoist toSequenceExpression's convert helper (#5693)
* Hoist toSequenceExpression's convert helper * Adds tests * lint * dev-depend on babel-generator
This commit is contained in:
parent
412180e203
commit
5f866f2d92
@ -14,6 +14,7 @@
|
||||
"to-fast-properties": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-generator": "^6.22.0",
|
||||
"babylon": "^6.8.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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`.
|
||||
*
|
||||
@ -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 {
|
||||
|
||||
@ -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 () {
|
||||
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user