mirror of
https://github.com/hanzoai/babel.git
synced 2026-09-24 16:56:45 +00:00
Convert babel helpers scripts to TS (#18053)
This commit is contained in:
+1
-1
@@ -1000,7 +1000,7 @@ gulp.task("generate-runtime-helpers", async () => {
|
||||
log("Generating @babel/helpers runtime helpers");
|
||||
|
||||
await generateHelpers(
|
||||
`./packages/babel-helpers/scripts/generate-helpers.js`,
|
||||
`./packages/babel-helpers/scripts/generate-helpers.ts`,
|
||||
`./packages/babel-helpers/src/`,
|
||||
"helpers-generated.ts",
|
||||
"@babel/helpers"
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"@babel/helper-plugin-test-runner": "workspace:^",
|
||||
"@babel/helper-transform-fixture-test-runner": "workspace:^",
|
||||
"@babel/parser": "workspace:^",
|
||||
"@babel/traverse": "workspace:^",
|
||||
"regenerator-runtime": "^0.14.0",
|
||||
"terser": "catalog:dev"
|
||||
},
|
||||
|
||||
+37
-32
@@ -1,38 +1,42 @@
|
||||
// NOTE: This file must be compatible with old Node.js versions, since it runs
|
||||
// during testing.
|
||||
|
||||
/**
|
||||
* @typedef {Object} HelperMetadata
|
||||
* @property {string[]} globals
|
||||
* @property {{ [name: string]: string[] }} locals
|
||||
* @property {{ [name: string]: string[] }} dependencies
|
||||
* @property {string[]} exportBindingAssignments
|
||||
* @property {string} exportName
|
||||
*/
|
||||
import type { NodePath, Visitor } from "@babel/traverse";
|
||||
import type * as t from "@babel/types";
|
||||
|
||||
type HelperMetadata = {
|
||||
globals: string[];
|
||||
locals: Record<string, string[]>;
|
||||
dependencies: Record<string, string[]>;
|
||||
exportBindingAssignments: string[];
|
||||
exportName: string;
|
||||
internal: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a file AST for a given helper, get a bunch of metadata about it so that Babel can quickly render
|
||||
* the helper is whatever context it is needed in.
|
||||
*
|
||||
* @param {typeof import("@babel/core")} babel
|
||||
*
|
||||
* @returns {HelperMetadata}
|
||||
*/
|
||||
export function getHelperMetadata(babel, code, helperName, internal = false) {
|
||||
const globals = new Set();
|
||||
export function getHelperMetadata(
|
||||
babel: any,
|
||||
code: string,
|
||||
helperName: string,
|
||||
internal = false
|
||||
): [string, HelperMetadata] {
|
||||
const globals = new Set<string>();
|
||||
// Maps imported identifier name -> helper name
|
||||
const dependenciesBindings = new Map();
|
||||
|
||||
let exportName;
|
||||
const exportBindingAssignments = [];
|
||||
let exportName: string | null = null;
|
||||
const exportBindingAssignments: string[] = [];
|
||||
// helper name -> reference paths
|
||||
const dependencies = new Map();
|
||||
// local variable name -> reference paths
|
||||
const locals = new Map();
|
||||
|
||||
const spansToRemove = [];
|
||||
const spansToRemove: [start: number, end: number][] = [];
|
||||
|
||||
const validateDefaultExport = decl => {
|
||||
const validateDefaultExport = (decl: NodePath) => {
|
||||
if (exportName) {
|
||||
throw new Error(
|
||||
`Helpers can have only one default export (in ${helperName})`
|
||||
@@ -46,8 +50,7 @@ export function getHelperMetadata(babel, code, helperName, internal = false) {
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {import("@babel/traverse").Visitor} */
|
||||
const dependencyVisitor = {
|
||||
const dependencyVisitor: Visitor = {
|
||||
Program(path) {
|
||||
for (const child of path.get("body")) {
|
||||
if (child.isImportDeclaration()) {
|
||||
@@ -64,7 +67,7 @@ export function getHelperMetadata(babel, code, helperName, internal = false) {
|
||||
child.node.source.value
|
||||
);
|
||||
dependencies.set(child.node.source.value, []);
|
||||
spansToRemove.push([child.node.start, child.node.end]);
|
||||
spansToRemove.push([child.node.start!, child.node.end!]);
|
||||
child.remove();
|
||||
}
|
||||
}
|
||||
@@ -73,27 +76,28 @@ export function getHelperMetadata(babel, code, helperName, internal = false) {
|
||||
const decl = child.get("declaration");
|
||||
validateDefaultExport(decl);
|
||||
|
||||
exportName = decl.node.id.name;
|
||||
spansToRemove.push([child.node.start, decl.node.start]);
|
||||
exportName = (decl as NodePath<t.FunctionDeclaration>).node.id!.name;
|
||||
spansToRemove.push([child.node.start!, decl.node.start!]);
|
||||
child.replaceWith(decl.node);
|
||||
} else if (
|
||||
child.isExportNamedDeclaration() &&
|
||||
child.node.specifiers.length === 1 &&
|
||||
child.get("specifiers.0.exported").isIdentifier({ name: "default" })
|
||||
) {
|
||||
const { name } = child.node.specifiers[0].local;
|
||||
const { name } = (child.node.specifiers[0] as t.ExportSpecifier)
|
||||
.local;
|
||||
|
||||
validateDefaultExport(child.scope.getBinding(name).path);
|
||||
validateDefaultExport(child.scope.getBinding(name)!.path);
|
||||
|
||||
exportName = name;
|
||||
spansToRemove.push([child.node.start, child.node.end]);
|
||||
spansToRemove.push([child.node.start!, child.node.end!]);
|
||||
child.remove();
|
||||
} else if (
|
||||
process.env.IS_BABEL_OLD_E2E &&
|
||||
child.isExportNamedDeclaration() &&
|
||||
child.node.specifiers.length === 0
|
||||
) {
|
||||
spansToRemove.push([child.node.start, child.node.end]);
|
||||
spansToRemove.push([child.node.start!, child.node.end!]);
|
||||
child.remove();
|
||||
} else if (
|
||||
child.isExportAllDeclaration() ||
|
||||
@@ -140,7 +144,8 @@ export function getHelperMetadata(babel, code, helperName, internal = false) {
|
||||
AssignmentExpression(child) {
|
||||
const left = child.get("left");
|
||||
|
||||
if (!(exportName in left.getBindingIdentifiers())) return;
|
||||
if (exportName == null || !(exportName in left.getBindingIdentifiers()))
|
||||
return;
|
||||
|
||||
if (!left.isIdentifier()) {
|
||||
throw new Error(
|
||||
@@ -150,7 +155,7 @@ export function getHelperMetadata(babel, code, helperName, internal = false) {
|
||||
|
||||
const binding = child.scope.getBinding(exportName);
|
||||
|
||||
if (binding && binding.scope.path.isProgram()) {
|
||||
if (binding?.scope.path.isProgram()) {
|
||||
exportBindingAssignments.push(makePath(child));
|
||||
}
|
||||
},
|
||||
@@ -180,13 +185,13 @@ export function getHelperMetadata(babel, code, helperName, internal = false) {
|
||||
locals: Object.fromEntries(locals),
|
||||
dependencies: Object.fromEntries(dependencies),
|
||||
exportBindingAssignments,
|
||||
exportName,
|
||||
exportName: exportName,
|
||||
internal,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function makePath(path) {
|
||||
function makePath(path: NodePath) {
|
||||
const parts = [];
|
||||
|
||||
for (; path.parentPath; path = path.parentPath) {
|
||||
@@ -197,7 +202,7 @@ function makePath(path) {
|
||||
return parts.reverse().join(".");
|
||||
}
|
||||
|
||||
export function stringifyMetadata(metadata) {
|
||||
export function stringifyMetadata(metadata: HelperMetadata) {
|
||||
return `\
|
||||
{
|
||||
globals: ${JSON.stringify(metadata.globals)},
|
||||
+15
-14
@@ -2,13 +2,15 @@ import fs from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { URL, fileURLToPath } from "node:url";
|
||||
import { minify } from "terser";
|
||||
// @ts-expect-error No types for $repo-utils/babel-top-level
|
||||
import { babel, presetTypescript } from "$repo-utils/babel-top-level";
|
||||
import { gzipSync } from "node:zlib";
|
||||
import type { Visitor } from "@babel/traverse";
|
||||
|
||||
import {
|
||||
getHelperMetadata,
|
||||
stringifyMetadata,
|
||||
} from "./build-helper-metadata.js";
|
||||
} from "./build-helper-metadata.ts";
|
||||
|
||||
const HELPERS_FOLDER = new URL("../src/helpers", import.meta.url);
|
||||
const IGNORED_FILES = new Set(["package.json", "tsconfig.json"]);
|
||||
@@ -22,22 +24,22 @@ const IGNORED_FILES = new Set(["package.json", "tsconfig.json"]);
|
||||
* @param {string[]} noMangleFns A list of function names that should not be mangled
|
||||
* @returns
|
||||
*/
|
||||
function BabelPluginProcessHelpersFactory(mangleFns, noMangleFns) {
|
||||
function BabelPluginProcessHelpersFactory(
|
||||
mangleFns: boolean,
|
||||
noMangleFns: string[]
|
||||
) {
|
||||
/**
|
||||
* @param {import("@babel/core").PluginAPI} api
|
||||
* @returns {import("@babel/core").PluginObject}
|
||||
*/
|
||||
return function BabelPluginProcessHelpers() {
|
||||
/**
|
||||
* @type {import("@babel/core").PluginObject}
|
||||
*/
|
||||
const pluginObj = {
|
||||
pre: undefined,
|
||||
post: undefined,
|
||||
visitor: {
|
||||
ImportDeclaration(path) {
|
||||
const source = path.node.source;
|
||||
source.value = source.value.replace(/(^\.\/|\.ts$)/g, "");
|
||||
source.value = source.value.replaceAll(/^\.\/|\.ts$/g, "");
|
||||
},
|
||||
FunctionDeclaration: mangleFns
|
||||
? path => {
|
||||
@@ -46,12 +48,12 @@ function BabelPluginProcessHelpersFactory(mangleFns, noMangleFns) {
|
||||
c.value.includes("@no-mangle")
|
||||
)
|
||||
) {
|
||||
const name = path.node.id.name;
|
||||
const name = path.node.id?.name;
|
||||
if (name) noMangleFns.push(name);
|
||||
}
|
||||
}
|
||||
: () => {},
|
||||
},
|
||||
} satisfies Visitor,
|
||||
};
|
||||
return pluginObj;
|
||||
};
|
||||
@@ -109,18 +111,17 @@ const helpers: Record<string, Helper> = {
|
||||
}
|
||||
|
||||
let code = await fs.promises.readFile(filePath, "utf8");
|
||||
const minVersionMatch = code.match(
|
||||
/^\s*\/\*\s*@minVersion\s+(?<minVersion>\S+)\s*\*\/\s*$/m
|
||||
);
|
||||
const minVersionMatch =
|
||||
/^\s*\/\*\s*@minVersion\s+(?<minVersion>\S+)\s*\*\/\s*$/m.exec(code);
|
||||
if (!minVersionMatch) {
|
||||
throw new Error(`@minVersion number missing in ${filePath}`);
|
||||
}
|
||||
const { minVersion } = minVersionMatch.groups;
|
||||
const { minVersion } = minVersionMatch.groups!;
|
||||
|
||||
const internal = code.includes("@internal");
|
||||
const onlyBabel8 = code.includes("@onlyBabel8");
|
||||
const mangleFns = code.includes("@mangleFns");
|
||||
const noMangleFns = [];
|
||||
const noMangleFns: string[] = [];
|
||||
|
||||
code = babel.transformSync(code, {
|
||||
configFile: false,
|
||||
@@ -156,7 +157,7 @@ const helpers: Record<string, Helper> = {
|
||||
unsafe_proto: true,
|
||||
},
|
||||
})
|
||||
).code;
|
||||
).code!;
|
||||
|
||||
let metadata;
|
||||
// eslint-disable-next-line prefer-const
|
||||
+13
-7
@@ -1,5 +1,5 @@
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import type * as t from "@babel/types";
|
||||
|
||||
const [parse, generate] = await Promise.all([
|
||||
import("@babel/parser").then(ns => ns.parse),
|
||||
@@ -13,7 +13,7 @@ const [parse, generate] = await Promise.all([
|
||||
});
|
||||
|
||||
const REGENERATOR_RUNTIME_IN_FILE = fs.readFileSync(
|
||||
createRequire(import.meta.url).resolve("regenerator-runtime"),
|
||||
import.meta.resolve("regenerator-runtime"),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
@@ -34,7 +34,11 @@ const COPYRIGHT = `/*! regenerator-runtime -- Copyright (c) 2014-present, Facebo
|
||||
export default function generateRegeneratorRuntimeHelper() {
|
||||
const ast = parse(REGENERATOR_RUNTIME_IN_FILE, { sourceType: "script" });
|
||||
|
||||
const factoryFunction = ast.program.body[0].declarations[0].init.callee;
|
||||
const factoryFunction = (
|
||||
(ast.program.body[0] as t.VariableDeclaration).declarations[0]
|
||||
.init as t.CallExpression
|
||||
).callee as t.FunctionExpression;
|
||||
// @ts-expect-error Manipulating the AST
|
||||
factoryFunction.type = "FunctionDeclaration";
|
||||
factoryFunction.id = { type: "Identifier", name: "_regeneratorRuntime" };
|
||||
factoryFunction.params = [];
|
||||
@@ -54,8 +58,10 @@ export default function generateRegeneratorRuntimeHelper() {
|
||||
return HEADER + code;
|
||||
}
|
||||
|
||||
function stmts(code) {
|
||||
return parse(`function _() { ${code} }`, {
|
||||
sourceType: "script",
|
||||
}).program.body[0].body.body;
|
||||
function stmts(code: string) {
|
||||
return (
|
||||
parse(`function _() { ${code} }`, {
|
||||
sourceType: "script",
|
||||
}).program.body[0] as t.FunctionDeclaration
|
||||
).body.body;
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { stringifyMetadata } from "../../../../scripts/build-helper-metadata.js";
|
||||
import { stringifyMetadata } from "../../../../scripts/build-helper-metadata.ts";
|
||||
import defineHelper, {
|
||||
defineHelperAndGetMetadata,
|
||||
} from "../../../helpers/define-helper.js";
|
||||
|
||||
@@ -2,7 +2,7 @@ import path from "node:path";
|
||||
import template from "@babel/template";
|
||||
import helpers from "../../lib/helpers-generated.js";
|
||||
|
||||
import { getHelperMetadata } from "../../scripts/build-helper-metadata.js";
|
||||
import { getHelperMetadata } from "../../scripts/build-helper-metadata.ts";
|
||||
|
||||
function getHelperId(baseURL, name) {
|
||||
const testName = path.basename(new URL(".", baseURL).pathname);
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../../packages/babel-types"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/babel-traverse"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -55,6 +55,9 @@ const packageTypingDependencies = (
|
||||
),
|
||||
]);
|
||||
switch (pkgName) {
|
||||
case "@babel/helpers":
|
||||
dependencies.add("@babel/traverse");
|
||||
break;
|
||||
case "@babel/standalone":
|
||||
Object.keys(pkgJSON.devDependencies ?? {}).forEach(dep =>
|
||||
dependencies.add(dep)
|
||||
|
||||
@@ -1212,6 +1212,7 @@ __metadata:
|
||||
"@babel/helper-transform-fixture-test-runner": "workspace:^"
|
||||
"@babel/parser": "workspace:^"
|
||||
"@babel/template": "workspace:^"
|
||||
"@babel/traverse": "workspace:^"
|
||||
"@babel/types": "workspace:^"
|
||||
regenerator-runtime: "npm:^0.14.0"
|
||||
terser: "catalog:dev"
|
||||
|
||||
Reference in New Issue
Block a user