Skip to content

Commit

Permalink
Rewrite transform-block-scoping plugin (#15200)
Browse files Browse the repository at this point in the history
* Add test for `const` with `tdz`

* Rewrite block-scoping plugin

* Update block-scoping fixtures

* Update other fixtures

* Fix `let` shadowing variable read in a sibling scope

* Update fixtures

* Update Babel 8 fixtures

* Add minNodeVersion

* Fix block scoped vars shadowing globals

* Review by JLHwung

* minNodeVersion to class fields test
  • Loading branch information
nicolo-ribaudo committed Dec 14, 2022
1 parent ce37692 commit 6757a60
Show file tree
Hide file tree
Showing 102 changed files with 1,538 additions and 1,575 deletions.
Expand Up @@ -14,14 +14,12 @@ var Foo = /*#__PURE__*/function () {
var _bar = babelHelpers.asyncToGenerator( /*#__PURE__*/babelHelpers.regeneratorRuntime().mark(function _callee() {
var baz;
return babelHelpers.regeneratorRuntime().wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
baz = 0;
case 1:
case "end":
return _context.stop();
}
while (1) switch (_context.prev = _context.next) {
case 0:
baz = 0;
case 1:
case "end":
return _context.stop();
}
}, _callee);
}));
Expand All @@ -41,33 +39,29 @@ function _foo() {
_foo = babelHelpers.asyncToGenerator( /*#__PURE__*/babelHelpers.regeneratorRuntime().mark(function _callee3() {
var bar, _bar2;
return babelHelpers.regeneratorRuntime().wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
_bar2 = function _bar4() {
_bar2 = babelHelpers.asyncToGenerator( /*#__PURE__*/babelHelpers.regeneratorRuntime().mark(function _callee2() {
var baz;
return babelHelpers.regeneratorRuntime().wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
baz = {};
case 1:
case "end":
return _context2.stop();
}
}
}, _callee2);
}));
return _bar2.apply(this, arguments);
};
bar = function _bar3() {
return _bar2.apply(this, arguments);
};
case 2:
case "end":
return _context3.stop();
}
while (1) switch (_context3.prev = _context3.next) {
case 0:
_bar2 = function _bar4() {
_bar2 = babelHelpers.asyncToGenerator( /*#__PURE__*/babelHelpers.regeneratorRuntime().mark(function _callee2() {
var baz;
return babelHelpers.regeneratorRuntime().wrap(function _callee2$(_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
baz = {};
case 1:
case "end":
return _context2.stop();
}
}, _callee2);
}));
return _bar2.apply(this, arguments);
};
bar = function _bar3() {
return _bar2.apply(this, arguments);
};
case 2:
case "end":
return _context3.stop();
}
}, _callee3);
}));
Expand Down
Expand Up @@ -12,9 +12,7 @@ var D = /*#__PURE__*/function () {
babelHelpers.createClass(D, [{
key: "f",
value: function f() {
for (var el of babelHelpers.classPrivateFieldGet(this, _arr)) {
;
}
for (var el of babelHelpers.classPrivateFieldGet(this, _arr));
}
}]);
return D;
Expand All @@ -33,9 +31,7 @@ var C = /*#__PURE__*/function () {
babelHelpers.createClass(C, [{
key: "m",
value: function m() {
for (babelHelpers.classPrivateFieldDestructureSet(this, _p).value of []) {
;
}
for (babelHelpers.classPrivateFieldDestructureSet(this, _p).value of []);
}
}]);
return C;
Expand All @@ -54,9 +50,7 @@ var E = /*#__PURE__*/function () {
babelHelpers.createClass(E, [{
key: "f",
value: function f() {
for (babelHelpers.classPrivateFieldDestructureSet(this, _arr2).value of [1, 2]) {
;
}
for (babelHelpers.classPrivateFieldDestructureSet(this, _arr2).value of [1, 2]);
}
}]);
return E;
Expand All @@ -75,9 +69,7 @@ var F = /*#__PURE__*/function () {
babelHelpers.createClass(F, [{
key: "g",
value: function g() {
for (babelHelpers.classPrivateFieldDestructureSet(this, _ar).value in [1, 2, 3]) {
;
}
for (babelHelpers.classPrivateFieldDestructureSet(this, _ar).value in [1, 2, 3]);
}
}]);
return F;
Expand Down
127 changes: 127 additions & 0 deletions packages/babel-plugin-transform-block-scoping/src/annex-B_3_3.ts
@@ -0,0 +1,127 @@
import { types as t } from "@babel/core";
import type { NodePath, Visitor, Scope } from "@babel/traverse";

// Whenever a function declaration in a nested block scope
// doesn't conflict with a block-scoped binding from an outer
// scope, we transform it to a variable declaration.
//
// This implements the Annex B.3.3 behavior.
//
// TODO(Babel 8): Figure out how this should interact with
// the transform-block-scoped functions plugin (it feels
// wrong to handle this transform here), and what we want
// to do with Anned B behavior in general.

// To avoid confusing block-scoped variables transformed to
// var with original vars, this transformation happens in two
// different places:
// 1. For functions that "conflict" with var-variables, in
// the VariableDeclaration visitor.
// 2. For functions that don't conflict with any variable,
// in the FunctionDeclaration visitor.

export const annexB33FunctionsVisitor: Visitor = {
VariableDeclaration(path) {
if (isStrict(path)) return;
if (path.node.kind !== "var") return;

const varScope =
path.scope.getFunctionParent() || path.scope.getProgramParent();
// eslint-disable-next-line @typescript-eslint/no-use-before-define
varScope.path.traverse(functionsToVarVisitor, {
names: Object.keys(path.getBindingIdentifiers()),
});
},

// NOTE: These two visitors target the same nodes as the
// block-scoped-functions plugin

BlockStatement(path) {
if (isStrict(path)) return;
if (t.isFunction(path.parent, { body: path.node })) return;
transformStatementList(path.get("body"));
},

SwitchCase(path) {
if (isStrict(path)) return;
transformStatementList(path.get("consequent"));
},
};

function transformStatementList(paths: NodePath<t.Statement>[]) {
outer: for (const path of paths) {
if (!path.isFunctionDeclaration()) continue;
// Annex B.3.3 only applies to plain functions.
if (path.node.async || path.node.generator) return;

const { scope } = path.parentPath;
if (isVarScope(scope)) return;

const { name } = path.node.id;
let currScope = scope;
do {
if (currScope.parent.hasOwnBinding(name)) continue outer;
currScope = currScope.parent;
} while (!isVarScope(currScope));

maybeTransformBlockScopedFunction(path);
}
}

function maybeTransformBlockScopedFunction(
path: NodePath<t.FunctionDeclaration>,
) {
const {
node,
parentPath: { scope },
} = path;

const { id } = node;
scope.removeOwnBinding(id.name);
node.id = null;

const varNode = t.variableDeclaration("var", [
t.variableDeclarator(id, t.toExpression(node)),
]);
// @ts-expect-error undocumented property
varNode._blockHoist = 2;

const [varPath] = path.replaceWith(varNode);
scope.registerDeclaration(varPath);
}

const functionsToVarVisitor: Visitor<{ names: string[] }> = {
Scope(path, { names }) {
for (const name of names) {
const binding = path.scope.getOwnBinding(name);
if (binding && binding.kind === "hoisted") {
maybeTransformBlockScopedFunction(
binding.path as NodePath<t.FunctionDeclaration>,
);
}
}
},
"Expression|Declaration"(path) {
path.skip();
},
};

export function isVarScope(scope: Scope) {
return scope.path.isFunctionParent() || scope.path.isProgram();
}

function isStrict(path: NodePath) {
return !!path.find(({ node }) => {
if (t.isProgram(node)) {
if (node.sourceType === "module") return true;
} else if (t.isClass(node)) {
return true;
} else if (!t.isBlockStatement(node)) {
return false;
}

return node.directives?.some(
directive => directive.value.value === "use strict",
);
});
}

0 comments on commit 6757a60

Please sign in to comment.