From 4f143e24451e7aff6aa1b64ad4e01a72734075a7 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Tue, 2 May 2017 23:10:49 -0400 Subject: [PATCH 1/4] Hoist toSequenceExpression's convert helper --- packages/babel-types/src/converters.js | 136 +++++++++++++------------ 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/packages/babel-types/src/converters.js b/packages/babel-types/src/converters.js index d18ea38af7ec..404774251289 100644 --- a/packages/babel-types/src/converters.js +++ b/packages/babel-types/src/converters.js @@ -10,6 +10,71 @@ export function toComputedKey(node: Object, key: Object = node.key || node.prope return key; } + +function gatherSequenceExpressions(nodes: Array, scope: Scope, declars: Array): ?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, 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 { From acfb1ce213bd7242cb9782800d605689d944c053 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Tue, 2 May 2017 23:40:24 -0400 Subject: [PATCH 2/4] Adds tests --- packages/babel-types/test/converters.js | 112 ++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/packages/babel-types/test/converters.js b/packages/babel-types/test/converters.js index b89e6dbe3f08..93c9c3f6cf77 100644 --- a/packages/babel-types/test/converters.js +++ b/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 () { @@ -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 () { + let node = parseCode("{ return }"); + let sequence = t.toSequenceExpression([undefinedNode, node], scope); + assert.isUndefined(sequence); + }); + it("gathers empty statements", function () { + let node = parseCode(";"); + let 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"); + }); + }); }); From e685a25716056c2985b26459f3b92a8606e06490 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Tue, 2 May 2017 23:48:13 -0400 Subject: [PATCH 3/4] lint --- packages/babel-types/test/converters.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/babel-types/test/converters.js b/packages/babel-types/test/converters.js index 93c9c3f6cf77..58131e425e73 100644 --- a/packages/babel-types/test/converters.js +++ b/packages/babel-types/test/converters.js @@ -195,8 +195,8 @@ describe("converters", function () { 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"}) + 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"); }); @@ -242,13 +242,13 @@ describe("converters", function () { assert.equal(generateCode(sequence.expressions[1]), "a, b"); }); it("bails in block statements if recurse bails", function () { - let node = parseCode("{ return }"); - let sequence = t.toSequenceExpression([undefinedNode, node], scope); + const node = parseCode("{ return }"); + const sequence = t.toSequenceExpression([undefinedNode, node], scope); assert.isUndefined(sequence); }); it("gathers empty statements", function () { - let node = parseCode(";"); - let sequence = t.toSequenceExpression([undefinedNode, node], scope); + 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 () { From f265860c29fb220b33f3e82c7e6d2d5af0be2272 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Wed, 3 May 2017 01:39:46 -0400 Subject: [PATCH 4/4] dev-depend on babel-generator --- packages/babel-types/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/babel-types/package.json b/packages/babel-types/package.json index 5a3001f1e7a1..76a3735e07e2 100644 --- a/packages/babel-types/package.json +++ b/packages/babel-types/package.json @@ -14,6 +14,7 @@ "to-fast-properties": "^1.0.1" }, "devDependencies": { + "babel-generator": "^6.22.0", "babylon": "^6.8.2" } }