convert @babel/plugin-transform-block-scoping to typescript (#13219)

This commit is contained in:
Bogdan Savluk 2021-08-10 20:53:18 +02:00 committed by GitHub
parent e721f61110
commit d48a5cb55c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 129 additions and 37 deletions

View File

@ -24,7 +24,8 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "workspace:*", "@babel/core": "workspace:*",
"@babel/helper-plugin-test-runner": "workspace:*" "@babel/helper-plugin-test-runner": "workspace:*",
"@babel/traverse": "workspace:*"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"

View File

@ -1,8 +1,9 @@
import { declare } from "@babel/helper-plugin-utils"; import { declare } from "@babel/helper-plugin-utils";
import type NodePath from "@babel/traverse"; import type { NodePath, Visitor, Scope } from "@babel/traverse";
import type Scope from "@babel/traverse";
import { visitor as tdzVisitor } from "./tdz"; import { visitor as tdzVisitor } from "./tdz";
import type { TDZVisitorState } from "./tdz";
import { traverse, template, types as t } from "@babel/core"; import { traverse, template, types as t } from "@babel/core";
import type { File } from "@babel/core";
const DONE = new WeakSet(); const DONE = new WeakSet();
@ -26,8 +27,9 @@ export default declare((api, opts) => {
if (!isBlockScoped(node)) return; if (!isBlockScoped(node)) return;
convertBlockScopedToVar(path, null, parent, scope, true); convertBlockScopedToVar(path, null, parent, scope, true);
// @ts-expect-error todo(flow->ts): avoid mutations
if (node._tdzThis) { if (node._tdzThis) {
const nodes = [node]; const nodes: t.Node[] = [node];
for (let i = 0; i < node.declarations.length; i++) { for (let i = 0; i < node.declarations.length; i++) {
const decl = node.declarations[i]; const decl = node.declarations[i];
@ -36,11 +38,13 @@ export default declare((api, opts) => {
t.cloneNode(decl.id), t.cloneNode(decl.id),
decl.init || scope.buildUndefinedNode(), decl.init || scope.buildUndefinedNode(),
); );
// @ts-expect-error todo(flow->ts): avoid mutations
assign._ignoreBlockScopingTDZ = true; assign._ignoreBlockScopingTDZ = true;
nodes.push(t.expressionStatement(assign)); nodes.push(t.expressionStatement(assign));
decl.init = this.addHelper("temporalUndefined"); decl.init = this.addHelper("temporalUndefined");
} }
// @ts-expect-error todo(flow->ts): avoid mutations
node._blockHoist = 2; node._blockHoist = 2;
if (path.isCompletionRecord()) { if (path.isCompletionRecord()) {
@ -83,7 +87,10 @@ export default declare((api, opts) => {
blockScoping.run(); blockScoping.run();
}, },
"BlockStatement|SwitchStatement|Program"(path, state) { "BlockStatement|SwitchStatement|Program"(
path: NodePath<t.BlockStatement | t.SwitchStatement | t.Program>,
state,
) {
if (!ignoreBlock(path)) { if (!ignoreBlock(path)) {
const blockScoping = new BlockScoping( const blockScoping = new BlockScoping(
null, null,
@ -97,7 +104,7 @@ export default declare((api, opts) => {
blockScoping.run(); blockScoping.run();
} }
}, },
}, } as Visitor<File>,
}; };
}); });
@ -161,10 +168,18 @@ function convertBlockScopedToVar(
} }
} }
function isVar(node) { function isVar(node): node is t.VariableDeclaration {
return t.isVariableDeclaration(node, { kind: "var" }) && !isBlockScoped(node); return t.isVariableDeclaration(node, { kind: "var" }) && !isBlockScoped(node);
} }
interface LetReferenceVisitorState extends TDZVisitorState {
tdzEnabled: boolean;
loopDepth: number;
addHelper: (name) => any;
letReferences: any;
closurify: boolean;
}
const letReferenceBlockVisitor = traverse.visitors.merge([ const letReferenceBlockVisitor = traverse.visitors.merge([
{ {
Loop: { Loop: {
@ -188,7 +203,7 @@ const letReferenceBlockVisitor = traverse.visitors.merge([
}, },
}, },
tdzVisitor, tdzVisitor,
]); ] as Visitor<LetReferenceVisitorState>[]);
const letReferenceFunctionVisitor = traverse.visitors.merge([ const letReferenceFunctionVisitor = traverse.visitors.merge([
{ {
@ -207,14 +222,13 @@ const letReferenceFunctionVisitor = traverse.visitors.merge([
}, },
}, },
tdzVisitor, tdzVisitor,
]); ] as Visitor<LetReferenceVisitorState>[]);
const hoistVarDeclarationsVisitor = { const hoistVarDeclarationsVisitor: Visitor<BlockScoping> = {
enter(path, self) { enter(path, self) {
const { node, parent } = path;
if (path.isForStatement()) { if (path.isForStatement()) {
if (isVar(node.init, node)) { const { node } = path;
if (isVar(node.init)) {
const nodes = self.pushDeclar(node.init); const nodes = self.pushDeclar(node.init);
if (nodes.length === 1) { if (nodes.length === 1) {
node.init = nodes[0]; node.init = nodes[0];
@ -222,14 +236,15 @@ const hoistVarDeclarationsVisitor = {
node.init = t.sequenceExpression(nodes); node.init = t.sequenceExpression(nodes);
} }
} }
} else if (path.isFor()) { } else if (path.isForInStatement() || path.isForOfStatement()) {
if (isVar(node.left, node)) { const { node } = path;
if (isVar(node.left)) {
self.pushDeclar(node.left); self.pushDeclar(node.left);
node.left = node.left.declarations[0].id; node.left = node.left.declarations[0].id;
} }
} else if (isVar(node, parent)) { } else if (isVar(path.node)) {
path.replaceWithMultiple( path.replaceWithMultiple(
self.pushDeclar(node).map(expr => t.expressionStatement(expr)), self.pushDeclar(path.node).map(expr => t.expressionStatement(expr)),
); );
} else if (path.isFunction()) { } else if (path.isFunction()) {
return path.skip(); return path.skip();
@ -237,13 +252,30 @@ const hoistVarDeclarationsVisitor = {
}, },
}; };
const loopLabelVisitor = { interface LoopVisitorState {
inSwitchCase: boolean;
hasBreakContinue: boolean;
innerLabels: any[];
hasReturn: boolean;
ignoreLabeless: boolean;
LOOP_IGNORE: symbol;
isLoop: boolean;
map: any;
}
const loopLabelVisitor: Visitor<LoopVisitorState> = {
LabeledStatement({ node }, state) { LabeledStatement({ node }, state) {
state.innerLabels.push(node.label.name); state.innerLabels.push(node.label.name);
}, },
}; };
const continuationVisitor = { interface ContinuationVisitorState {
returnStatements: NodePath<t.ReturnStatement>[];
reassignments: { [k: string]: boolean | undefined };
outsideReferences: Map<string, t.Identifier>;
}
const continuationVisitor: Visitor<ContinuationVisitorState> = {
enter(path, state) { enter(path, state) {
if (path.isAssignmentExpression() || path.isUpdateExpression()) { if (path.isAssignmentExpression() || path.isUpdateExpression()) {
for (const name of Object.keys(path.getBindingIdentifiers())) { for (const name of Object.keys(path.getBindingIdentifiers())) {
@ -269,7 +301,7 @@ function loopNodeTo(node) {
} }
} }
const loopVisitor = { const loopVisitor: Visitor<LoopVisitorState> = {
Loop(path, state) { Loop(path, state) {
const oldIgnoreLabeless = state.ignoreLabeless; const oldIgnoreLabeless = state.ignoreLabeless;
state.ignoreLabeless = true; state.ignoreLabeless = true;
@ -290,7 +322,10 @@ const loopVisitor = {
path.skip(); path.skip();
}, },
"BreakStatement|ContinueStatement|ReturnStatement"(path, state) { "BreakStatement|ContinueStatement|ReturnStatement"(
path: NodePath<t.BreakStatement | t.ContinueStatement | t.ReturnStatement>,
state,
) {
const { node, scope } = path; const { node, scope } = path;
if (node[this.LOOP_IGNORE]) return; if (node[this.LOOP_IGNORE]) return;
@ -298,6 +333,11 @@ const loopVisitor = {
let loopText = loopNodeTo(node); let loopText = loopNodeTo(node);
if (loopText) { if (loopText) {
if (t.isReturnStatement(node)) {
throw new Error(
"Internal error: unexpected return statement with `loopText`",
);
}
if (node.label) { if (node.label) {
// we shouldn't be transforming this because it exists somewhere inside // we shouldn't be transforming this because it exists somewhere inside
if (state.innerLabels.indexOf(node.label.name) >= 0) { if (state.innerLabels.indexOf(node.label.name) >= 0) {
@ -319,7 +359,7 @@ const loopVisitor = {
replace = t.stringLiteral(loopText); replace = t.stringLiteral(loopText);
} }
if (path.isReturnStatement()) { if (t.isReturnStatement(node)) {
state.hasReturn = true; state.hasReturn = true;
replace = t.objectExpression([ replace = t.objectExpression([
t.objectProperty( t.objectProperty(
@ -351,14 +391,31 @@ function isStrict(path) {
} }
class BlockScoping { class BlockScoping {
private parent: any;
private state: any;
private scope: Scope;
private throwIfClosureRequired: boolean;
private tdzEnabled: boolean;
private blockPath: NodePath;
private block: t.Node;
private outsideLetReferences: Map<string, t.Identifier>;
private hasLetReferences: boolean;
private letReferences: any;
private body: any[];
// todo(flow->ts) add more specific type
private loopParent: t.Node;
private loopLabel: t.Identifier;
private loopPath: NodePath<t.Loop>;
private loop: t.Loop;
private has: any;
constructor( constructor(
loopPath?: NodePath, loopPath: NodePath<t.Loop> | undefined | null,
blockPath: NodePath, blockPath: NodePath,
parent: Object, parent: any,
scope: Scope, scope: Scope,
throwIfClosureRequired: boolean, throwIfClosureRequired: boolean,
tdzEnabled: boolean, tdzEnabled: boolean,
state: Object, state: any,
) { ) {
this.parent = parent; this.parent = parent;
this.scope = scope; this.scope = scope;
@ -426,7 +483,7 @@ class BlockScoping {
const binding = scope.bindings[name]; const binding = scope.bindings[name];
if (binding.kind !== "const") continue; if (binding.kind !== "const") continue;
for (const violation of (binding.constantViolations: Array)) { for (const violation of binding.constantViolations) {
const readOnlyError = state.addHelper("readOnlyError"); const readOnlyError = state.addHelper("readOnlyError");
const throwNode = t.callExpression(readOnlyError, [ const throwNode = t.callExpression(readOnlyError, [
t.stringLiteral(name), t.stringLiteral(name),
@ -441,6 +498,7 @@ class BlockScoping {
} else if (["&&=", "||=", "??="].includes(operator)) { } else if (["&&=", "||=", "??="].includes(operator)) {
violation.replaceWith( violation.replaceWith(
t.logicalExpression( t.logicalExpression(
// @ts-expect-error todo(flow->ts)
operator.slice(0, -1), operator.slice(0, -1),
violation.get("left").node, violation.get("left").node,
t.sequenceExpression([violation.get("right").node, throwNode]), t.sequenceExpression([violation.get("right").node, throwNode]),
@ -450,6 +508,7 @@ class BlockScoping {
violation.replaceWith( violation.replaceWith(
t.sequenceExpression([ t.sequenceExpression([
t.binaryExpression( t.binaryExpression(
// @ts-expect-error todo(flow->ts)
operator.slice(0, -1), operator.slice(0, -1),
violation.get("left").node, violation.get("left").node,
violation.get("right").node, violation.get("right").node,
@ -476,13 +535,14 @@ class BlockScoping {
), ),
]), ]),
); );
// @ts-expect-error todo(flow->ts): possible bug "for(…) switch(){}"
violation.node.body.body.unshift(t.expressionStatement(throwNode)); violation.node.body.body.unshift(t.expressionStatement(throwNode));
} }
} }
} }
} }
updateScopeInfo(wrappedInClosure) { updateScopeInfo(wrappedInClosure?: boolean) {
const blockScope = this.blockPath.scope; const blockScope = this.blockPath.scope;
const parentScope = const parentScope =
@ -530,7 +590,9 @@ class BlockScoping {
const parentBinding = scope.parent.getOwnBinding(key); const parentBinding = scope.parent.getOwnBinding(key);
if ( if (
binding.kind === "hoisted" && binding.kind === "hoisted" &&
// @ts-expect-error todo(flow->ts)
!binding.path.node.async && !binding.path.node.async &&
// @ts-expect-error todo(flow->ts)
!binding.path.node.generator && !binding.path.node.generator &&
(!parentBinding || isVar(parentBinding.path.parent)) && (!parentBinding || isVar(parentBinding.path.parent)) &&
!isStrict(binding.path.parentPath) !isStrict(binding.path.parentPath)
@ -608,13 +670,15 @@ class BlockScoping {
const fn = t.functionExpression( const fn = t.functionExpression(
null, null,
params, params,
// @ts-expect-error todo(flow->ts) improve block type annotations
t.blockStatement(isSwitch ? [block] : block.body), t.blockStatement(isSwitch ? [block] : block.body),
); );
// continuation // continuation
this.addContinuations(fn); this.addContinuations(fn);
let call = t.callExpression(t.nullLiteral(), args); let call: t.CallExpression | t.YieldExpression | t.AwaitExpression =
t.callExpression(t.nullLiteral(), args);
let basePath = ".callee"; let basePath = ".callee";
// handle generators // handle generators
@ -669,6 +733,7 @@ class BlockScoping {
this.blockPath.replaceWithMultiple(this.body); this.blockPath.replaceWithMultiple(this.body);
callPath = parentPath.get(listKey)[key + index]; callPath = parentPath.get(listKey)[key + index];
} else { } else {
// @ts-expect-error todo(flow->ts) improve block type annotations
block.body = this.body; block.body = this.body;
callPath = this.blockPath.get("body")[index]; callPath = this.blockPath.get("body")[index];
} }
@ -704,7 +769,7 @@ class BlockScoping {
*/ */
addContinuations(fn) { addContinuations(fn) {
const state = { const state: ContinuationVisitorState = {
reassignments: {}, reassignments: {},
returnStatements: [], returnStatements: [],
outsideReferences: this.outsideLetReferences, outsideReferences: this.outsideLetReferences,
@ -753,6 +818,7 @@ class BlockScoping {
const declarators = []; const declarators = [];
if (this.loop) { if (this.loop) {
// @ts-expect-error todo(flow->ts) add check for loop type
const init = this.loop.left || this.loop.init; const init = this.loop.left || this.loop.init;
if (isBlockScoped(init)) { if (isBlockScoped(init)) {
declarators.push(init); declarators.push(init);
@ -786,17 +852,22 @@ class BlockScoping {
} }
}; };
// // @ts-expect-error todo(flow->ts) check block node type instead
if (block.body) { if (block.body) {
const declarPaths = this.blockPath.get("body"); const declarPaths = this.blockPath.get("body");
// @ts-expect-error todo(flow->ts)
for (let i = 0; i < block.body.length; i++) { for (let i = 0; i < block.body.length; i++) {
// @ts-expect-error todo(flow->ts)
addDeclarationsFromChild(declarPaths[i]); addDeclarationsFromChild(declarPaths[i]);
} }
} }
// @ts-expect-error todo(flow->ts) check block node type instead
if (block.cases) { if (block.cases) {
const declarPaths = this.blockPath.get("cases"); const declarPaths = this.blockPath.get("cases");
// @ts-expect-error todo(flow->ts)
for (let i = 0; i < block.cases.length; i++) { for (let i = 0; i < block.cases.length; i++) {
// @ts-expect-error todo(flow->ts)
const consequents = block.cases[i].consequent; const consequents = block.cases[i].consequent;
for (let j = 0; j < consequents.length; j++) { for (let j = 0; j < consequents.length; j++) {
@ -823,7 +894,7 @@ class BlockScoping {
// no let references so we can just quit // no let references so we can just quit
if (!this.hasLetReferences) return; if (!this.hasLetReferences) return;
const state = { const state: LetReferenceVisitorState = {
letReferences: this.letReferences, letReferences: this.letReferences,
closurify: false, closurify: false,
loopDepth: 0, loopDepth: 0,
@ -849,8 +920,8 @@ class BlockScoping {
* later on. * later on.
*/ */
checkLoop(): Object { checkLoop(): any {
const state = { const state: LoopVisitorState = {
hasBreakContinue: false, hasBreakContinue: false,
ignoreLabeless: false, ignoreLabeless: false,
inSwitchCase: false, inSwitchCase: false,
@ -881,7 +952,7 @@ class BlockScoping {
* their declarations hoisted to before the closure wrapper. * their declarations hoisted to before the closure wrapper.
*/ */
pushDeclar(node: { type: "VariableDeclaration" }): Array<Object> { pushDeclar(node: t.VariableDeclaration): Array<t.AssignmentExpression> {
const declars = []; const declars = [];
const names = t.getBindingIdentifiers(node); const names = t.getBindingIdentifiers(node);
for (const name of Object.keys(names)) { for (const name of Object.keys(names)) {

View File

@ -1,4 +1,5 @@
import { types as t, template } from "@babel/core"; import { types as t, template } from "@babel/core";
import type { Visitor } from "@babel/traverse";
function getTDZStatus(refPath, bindingPath) { function getTDZStatus(refPath, bindingPath) {
const executionStatus = bindingPath._guessExecutionStatusRelativeTo(refPath); const executionStatus = bindingPath._guessExecutionStatusRelativeTo(refPath);
@ -29,7 +30,12 @@ function isReference(node, scope, state) {
const visitedMaybeTDZNodes = new WeakSet(); const visitedMaybeTDZNodes = new WeakSet();
export const visitor = { export interface TDZVisitorState {
tdzEnabled: boolean;
addHelper: (name) => any;
}
export const visitor: Visitor<TDZVisitorState> = {
ReferencedIdentifier(path, state) { ReferencedIdentifier(path, state) {
if (!state.tdzEnabled) return; if (!state.tdzEnabled) return;
@ -53,16 +59,22 @@ export const visitor = {
const assert = buildTDZAssert(node, state); const assert = buildTDZAssert(node, state);
// add tdzThis to parent variable declarator so it's exploded // add tdzThis to parent variable declarator so it's exploded
// @ts-expect-error todo(flow->ts): avoid mutations
bindingPath.parent._tdzThis = true; bindingPath.parent._tdzThis = true;
if (path.parentPath.isUpdateExpression()) { if (path.parentPath.isUpdateExpression()) {
// @ts-expect-error todo(flow->ts): avoid node mutations
if (parent._ignoreBlockScopingTDZ) return; if (parent._ignoreBlockScopingTDZ) return;
path.parentPath.replaceWith(t.sequenceExpression([assert, parent])); path.parentPath.replaceWith(
t.sequenceExpression([assert, parent as t.UpdateExpression]),
);
} else { } else {
path.replaceWith(assert); path.replaceWith(assert);
} }
} else if (status === "inside") { } else if (status === "inside") {
path.replaceWith(template.ast`${state.addHelper("tdz")}("${node.name}")`); path.replaceWith(
template.ast`${state.addHelper("tdz")}("${node.name}")` as t.Statement,
);
} }
}, },
@ -71,6 +83,8 @@ export const visitor = {
if (!state.tdzEnabled) return; if (!state.tdzEnabled) return;
const { node } = path; const { node } = path;
// @ts-expect-error todo(flow->ts): avoid node mutations
if (node._ignoreBlockScopingTDZ) return; if (node._ignoreBlockScopingTDZ) return;
const nodes = []; const nodes = [];
@ -85,6 +99,7 @@ export const visitor = {
} }
if (nodes.length) { if (nodes.length) {
// @ts-expect-error todo(flow->ts): avoid mutations
node._ignoreBlockScopingTDZ = true; node._ignoreBlockScopingTDZ = true;
nodes.push(node); nodes.push(node);
path.replaceWithMultiple(nodes.map(n => t.expressionStatement(n))); path.replaceWithMultiple(nodes.map(n => t.expressionStatement(n)));

View File

@ -28,6 +28,7 @@
"./packages/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining/src/**/*.ts", "./packages/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining/src/**/*.ts",
"./packages/babel-plugin-proposal-async-do-expressions/src/**/*.ts", "./packages/babel-plugin-proposal-async-do-expressions/src/**/*.ts",
"./packages/babel-plugin-syntax-async-do-expressions/src/**/*.ts", "./packages/babel-plugin-syntax-async-do-expressions/src/**/*.ts",
"./packages/babel-plugin-transform-block-scoping/src/**/*.ts",
"./packages/babel-plugin-transform-classes/src/**/*.ts", "./packages/babel-plugin-transform-classes/src/**/*.ts",
"./packages/babel-plugin-transform-react-jsx/src/**/*.ts", "./packages/babel-plugin-transform-react-jsx/src/**/*.ts",
"./packages/babel-plugin-transform-runtime/src/**/*.ts", "./packages/babel-plugin-transform-runtime/src/**/*.ts",
@ -118,6 +119,9 @@
"@babel/plugin-syntax-async-do-expressions": [ "@babel/plugin-syntax-async-do-expressions": [
"./packages/babel-plugin-syntax-async-do-expressions/src" "./packages/babel-plugin-syntax-async-do-expressions/src"
], ],
"@babel/plugin-transform-block-scoping": [
"./packages/babel-plugin-transform-block-scoping/src"
],
"@babel/plugin-transform-classes": [ "@babel/plugin-transform-classes": [
"./packages/babel-plugin-transform-classes/src" "./packages/babel-plugin-transform-classes/src"
], ],

View File

@ -2099,6 +2099,7 @@ __metadata:
"@babel/core": "workspace:*" "@babel/core": "workspace:*"
"@babel/helper-plugin-test-runner": "workspace:*" "@babel/helper-plugin-test-runner": "workspace:*"
"@babel/helper-plugin-utils": "workspace:^7.14.5" "@babel/helper-plugin-utils": "workspace:^7.14.5"
"@babel/traverse": "workspace:*"
peerDependencies: peerDependencies:
"@babel/core": ^7.0.0-0 "@babel/core": ^7.0.0-0
languageName: unknown languageName: unknown