Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6d9725c

Browse filesBrowse files
[babel 8] Inline toSequenceExpression into @babel/traverse (#16057)
1 parent 46ee461 commit 6d9725c
Copy full SHA for 6d9725c

File tree

Expand file treeCollapse file tree

6 files changed

+268
-122
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

6 files changed

+268
-122
lines changed
Open diff view settings
Collapse file

‎packages/babel-traverse/src/path/replacement.ts‎

Copy file name to clipboardExpand all lines: packages/babel-traverse/src/path/replacement.ts
+83-5Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,27 @@ import {
1111
assignmentExpression,
1212
awaitExpression,
1313
blockStatement,
14+
buildUndefinedNode,
1415
callExpression,
1516
cloneNode,
17+
conditionalExpression,
1618
expressionStatement,
19+
getBindingIdentifiers,
1720
identifier,
1821
inheritLeadingComments,
1922
inheritTrailingComments,
2023
inheritsComments,
24+
isBlockStatement,
25+
isEmptyStatement,
2126
isExpression,
27+
isExpressionStatement,
28+
isIfStatement,
2229
isProgram,
2330
isStatement,
31+
isVariableDeclaration,
2432
removeComments,
2533
returnStatement,
26-
toSequenceExpression,
34+
sequenceExpression,
2735
validate,
2836
yieldExpression,
2937
} from "@babel/types";
@@ -229,10 +237,11 @@ export function replaceExpressionWithStatements(
229237
) {
230238
this.resync();
231239

232-
const nodesAsSequenceExpression = toSequenceExpression(nodes, this.scope);
233-
234-
if (nodesAsSequenceExpression) {
235-
return this.replaceWith(nodesAsSequenceExpression)[0].get("expressions");
240+
const declars: t.Identifier[] = [];
241+
const nodesAsSingleExpression = gatherSequenceExpressions(nodes, declars);
242+
if (nodesAsSingleExpression) {
243+
for (const id of declars) this.scope.push({ id });
244+
return this.replaceWith(nodesAsSingleExpression)[0].get("expressions");
236245
}
237246

238247
const functionParent = this.getFunctionParent();
@@ -327,6 +336,75 @@ export function replaceExpressionWithStatements(
327336
return newCallee.get("body.body");
328337
}
329338

339+
function gatherSequenceExpressions(
340+
nodes: ReadonlyArray<t.Node>,
341+
declars: Array<t.Identifier>,
342+
) {
343+
const exprs: t.Expression[] = [];
344+
let ensureLastUndefined = true;
345+
346+
for (const node of nodes) {
347+
// if we encounter emptyStatement before a non-emptyStatement
348+
// we want to disregard that
349+
if (!isEmptyStatement(node)) {
350+
ensureLastUndefined = false;
351+
}
352+
353+
if (isExpression(node)) {
354+
exprs.push(node);
355+
} else if (isExpressionStatement(node)) {
356+
exprs.push(node.expression);
357+
} else if (isVariableDeclaration(node)) {
358+
if (node.kind !== "var") return; // bailed
359+
360+
for (const declar of node.declarations) {
361+
const bindings = getBindingIdentifiers(declar);
362+
for (const key of Object.keys(bindings)) {
363+
declars.push(cloneNode(bindings[key]));
364+
}
365+
366+
if (declar.init) {
367+
exprs.push(assignmentExpression("=", declar.id, declar.init));
368+
}
369+
}
370+
371+
ensureLastUndefined = true;
372+
} else if (isIfStatement(node)) {
373+
const consequent = node.consequent
374+
? gatherSequenceExpressions([node.consequent], declars)
375+
: buildUndefinedNode();
376+
const alternate = node.alternate
377+
? gatherSequenceExpressions([node.alternate], declars)
378+
: buildUndefinedNode();
379+
if (!consequent || !alternate) return; // bailed
380+
381+
exprs.push(conditionalExpression(node.test, consequent, alternate));
382+
} else if (isBlockStatement(node)) {
383+
const body = gatherSequenceExpressions(node.body, declars);
384+
if (!body) return; // bailed
385+
386+
exprs.push(body);
387+
} else if (isEmptyStatement(node)) {
388+
// empty statement so ensure the last item is undefined if we're last
389+
// checks if emptyStatement is first
390+
if (nodes.indexOf(node) === 0) {
391+
ensureLastUndefined = true;
392+
}
393+
} else {
394+
// bailed, we can't turn this statement into an expression
395+
return;
396+
}
397+
}
398+
399+
if (ensureLastUndefined) exprs.push(buildUndefinedNode());
400+
401+
if (exprs.length === 1) {
402+
return exprs[0];
403+
} else {
404+
return sequenceExpression(exprs);
405+
}
406+
}
407+
330408
export function replaceInline(this: NodePath, nodes: t.Node | Array<t.Node>) {
331409
this.resync();
332410

Collapse file

‎packages/babel-traverse/test/replacement.js‎

Copy file name to clipboardExpand all lines: packages/babel-traverse/test/replacement.js
+164Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ import _generate from "@babel/generator";
66
const traverse = _traverse.default || _traverse;
77
const generate = _generate.default || _generate;
88

9+
function getPath(code) {
10+
const ast = parse(code);
11+
let path;
12+
traverse(ast, {
13+
Program: function (_path) {
14+
path = _path.get("body.0");
15+
_path.stop();
16+
},
17+
});
18+
19+
return path;
20+
}
21+
922
describe("path/replacement", function () {
1023
describe("replaceWith", function () {
1124
it("replaces declaration in ExportDefaultDeclaration node", function () {
@@ -188,4 +201,155 @@ describe("path/replacement", function () {
188201
expect(visitCounter).toBe(1);
189202
});
190203
});
204+
describe("replaceExpressionWithStatements", function () {
205+
const undefinedNode = t.expressionStatement(t.identifier("undefined"));
206+
207+
const getExprPath = () => getPath("X;").get("expression");
208+
const parseStmt = code =>
209+
parse(code, { allowReturnOutsideFunction: true }).program.body[0];
210+
211+
it("gathers nodes into sequence", function () {
212+
const path = getExprPath();
213+
const node = t.identifier("a");
214+
path.replaceExpressionWithStatements([undefinedNode, node]);
215+
t.assertSequenceExpression(path.node);
216+
expect(path.node.expressions[0]).toBe(undefinedNode.expression);
217+
expect(path.node.expressions[1]).toBe(node);
218+
});
219+
it("avoids sequence for single node", function () {
220+
const path = getExprPath();
221+
222+
const node = t.identifier("a");
223+
path.replaceExpressionWithStatements([node]);
224+
expect(path.node).toBe(node);
225+
226+
const block = t.blockStatement([t.expressionStatement(node)]);
227+
path.replaceExpressionWithStatements([block]);
228+
expect(path.node).toBe(node);
229+
});
230+
it("gathers expression", function () {
231+
const path = getExprPath();
232+
const node = t.identifier("a");
233+
path.replaceExpressionWithStatements([undefinedNode, node]);
234+
expect(path.node.expressions[1]).toBe(node);
235+
});
236+
it("gathers expression statement", function () {
237+
const path = getExprPath();
238+
const node = t.expressionStatement(t.identifier("a"));
239+
path.replaceExpressionWithStatements([undefinedNode, node]);
240+
expect(path.node.expressions[1]).toBe(node.expression);
241+
});
242+
it("gathers var declarations", function () {
243+
const path = getExprPath();
244+
const node = parseStmt("var a, b = 1;");
245+
path.replaceExpressionWithStatements([undefinedNode, node]);
246+
expect(path.scope.hasOwnBinding("a")).toBe(true);
247+
expect(path.scope.hasOwnBinding("b")).toBe(true);
248+
expect(path.get("expressions.0").toString()).toBe("undefined");
249+
expect(path.get("expressions.1").toString()).toBe("b = 1");
250+
expect(path.get("expressions.2").toString()).toBe("void 0");
251+
});
252+
it("skips undefined if expression after var declaration", function () {
253+
const path = getExprPath();
254+
const node = parseStmt("{ var a, b = 1; true }");
255+
path.replaceExpressionWithStatements([undefinedNode, node]);
256+
expect(path.get("expressions.1").toString()).toBe("b = 1, true");
257+
});
258+
it("bails on let and const declarations", function () {
259+
let path = getExprPath();
260+
261+
let node = parseStmt("let a, b = 1;");
262+
path.replaceExpressionWithStatements([undefinedNode, node]);
263+
t.assertCallExpression(path.node);
264+
t.assertFunction(path.node.callee);
265+
266+
path = getExprPath();
267+
node = parseStmt("const b = 1;");
268+
path.replaceExpressionWithStatements([undefinedNode, node]);
269+
t.assertCallExpression(path.node);
270+
t.assertFunction(path.node.callee);
271+
});
272+
it("gathers if statements", function () {
273+
let path = getExprPath();
274+
let node = parseStmt("if (c) { true }");
275+
path.replaceExpressionWithStatements([undefinedNode, node]);
276+
expect(path.get("expressions.1").toString()).toBe("c ? true : void 0");
277+
278+
path = getExprPath();
279+
node = parseStmt("if (c) { true } else { b }");
280+
path.replaceExpressionWithStatements([undefinedNode, node]);
281+
expect(path.get("expressions.1").toString()).toBe("c ? true : b");
282+
});
283+
it("gathers block statements", function () {
284+
let path = getExprPath();
285+
let node = parseStmt("{ a }");
286+
path.replaceExpressionWithStatements([undefinedNode, node]);
287+
expect(path.get("expressions.1").toString()).toBe("a");
288+
289+
path = getExprPath();
290+
node = parseStmt("{ a; b; }");
291+
path.replaceExpressionWithStatements([undefinedNode, node]);
292+
expect(path.get("expressions.1").toString()).toBe("a, b");
293+
});
294+
it("gathers empty statements if first element", function () {
295+
const path = getExprPath();
296+
const node = parseStmt(";");
297+
path.replaceExpressionWithStatements([undefinedNode, node]);
298+
expect(path.toString()).toBe("undefined");
299+
});
300+
it("skips empty statement if expression afterwards", function () {
301+
const path = getExprPath();
302+
const node = parseStmt("{ ; true }");
303+
path.replaceExpressionWithStatements([undefinedNode, node]);
304+
expect(path.get("expressions.1").toString()).toBe("true");
305+
});
306+
describe("return", function () {
307+
// TODO: These tests veryfy wrong behavior. It's not possible to
308+
// replace an expression with `return`, as wrapping it in a IIFE changes
309+
// semantics.
310+
// They are here because it's how @babel/traverse currently behaves, but
311+
// it should be eventually be made to throw an error.
312+
313+
it("bails in if statements if recurse bails", function () {
314+
let path = getExprPath();
315+
let node = parseStmt("if (true) { return }");
316+
path.replaceExpressionWithStatements([undefinedNode, node]);
317+
expect(path.toString()).toMatchInlineSnapshot(`
318+
"function () {
319+
undefined;
320+
if (true) {
321+
return;
322+
}
323+
}()"
324+
`);
325+
326+
path = getExprPath();
327+
node = parseStmt("if (true) { true } else { return }");
328+
path.replaceExpressionWithStatements([undefinedNode, node]);
329+
expect(path.toString()).toMatchInlineSnapshot(`
330+
"function () {
331+
undefined;
332+
if (true) {
333+
return true;
334+
} else {
335+
return;
336+
}
337+
}()"
338+
`);
339+
});
340+
it("bails in block statements if recurse bails", function () {
341+
const path = getExprPath();
342+
const node = parseStmt("{ return }");
343+
path.replaceExpressionWithStatements([undefinedNode, node]);
344+
expect(path.toString()).toMatchInlineSnapshot(`
345+
"function () {
346+
undefined;
347+
{
348+
return;
349+
}
350+
}()"
351+
`);
352+
});
353+
});
354+
});
191355
});
Collapse file

‎packages/babel-types/src/converters/gatherSequenceExpressions.ts‎

Copy file name to clipboardExpand all lines: packages/babel-types/src/converters/gatherSequenceExpressions.ts
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
// TODO(Babel 8) Remove this file
2+
if (process.env.BABEL_8_BREAKING) {
3+
throw new Error(
4+
"Internal Babel error: This file should only be loaded in Babel 7",
5+
);
6+
}
7+
18
import getBindingIdentifiers from "../retrievers/getBindingIdentifiers.ts";
29
import {
310
isExpression,
Collapse file

‎packages/babel-types/src/converters/toSequenceExpression.ts‎

Copy file name to clipboardExpand all lines: packages/babel-types/src/converters/toSequenceExpression.ts
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
// TODO(Babel 8) Remove this file
2+
if (process.env.BABEL_8_BREAKING) {
3+
throw new Error(
4+
"Internal Babel error: This file should only be loaded in Babel 7",
5+
);
6+
}
7+
18
import gatherSequenceExpressions from "./gatherSequenceExpressions.ts";
29
import type * as t from "../index.ts";
310
import type { DeclarationInfo } from "./gatherSequenceExpressions.ts";
Collapse file

‎packages/babel-types/src/index.ts‎

Copy file name to clipboardExpand all lines: packages/babel-types/src/index.ts
+7-1Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export { default as toComputedKey } from "./converters/toComputedKey.ts";
4444
export { default as toExpression } from "./converters/toExpression.ts";
4545
export { default as toIdentifier } from "./converters/toIdentifier.ts";
4646
export { default as toKeyAlias } from "./converters/toKeyAlias.ts";
47-
export { default as toSequenceExpression } from "./converters/toSequenceExpression.ts";
4847
export { default as toStatement } from "./converters/toStatement.ts";
4948
export { default as valueToNode } from "./converters/valueToNode.ts";
5049

@@ -106,3 +105,10 @@ export type * from "./ast-types/generated/index.ts";
106105

107106
// this is used by @babel/traverse to warn about deprecated visitors
108107
export { default as __internal__deprecationWarning } from "./utils/deprecationWarning.ts";
108+
109+
if (!process.env.BABEL_8_BREAKING && !USE_ESM && !IS_STANDALONE) {
110+
// eslint-disable-next-line no-restricted-globals
111+
exports.toSequenceExpression =
112+
// eslint-disable-next-line no-restricted-globals
113+
require("./converters/toSequenceExpression.js").default;
114+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.