diff --git a/lib/fs-readdir-recursive.d.ts b/lib/fs-readdir-recursive.d.ts new file mode 100644 index 000000000000..f71f53c2d5d5 --- /dev/null +++ b/lib/fs-readdir-recursive.d.ts @@ -0,0 +1,7 @@ +declare module "fs-readdir-recursive" { + function read( + root: string, + filter?: (filename: string, index: number, dir: string) => boolean + ): string[]; + export = read; +} diff --git a/lib/kexec.d.ts b/lib/kexec.d.ts new file mode 100644 index 000000000000..6ab60516ce7f --- /dev/null +++ b/lib/kexec.d.ts @@ -0,0 +1,4 @@ +declare module "kexec" { + function execvp(cmd: string, args?: string[]): string; + export = execvp; +} diff --git a/packages/babel-cli/src/babel/dir.ts b/packages/babel-cli/src/babel/dir.ts index 2585951af31a..971e7a3faad6 100644 --- a/packages/babel-cli/src/babel/dir.ts +++ b/packages/babel-cli/src/babel/dir.ts @@ -129,7 +129,7 @@ export default async function ({ } let compiledFiles = 0; - let startTime = null; + let startTime: [number, number] | null = null; const logSuccess = util.debounce(function () { if (startTime === null) { @@ -178,7 +178,7 @@ export default async function ({ // when we are sure that all the files have been compiled. let processing = 0; const { filenames } = cliOptions; - let getBase; + let getBase: (filename: string) => string | null; if (filenames.length === 1) { // fast path: If there is only one filenames, we know it must be the base const base = filenames[0]; diff --git a/packages/babel-cli/src/babel/file.ts b/packages/babel-cli/src/babel/file.ts index 83f2912d44d3..63222479cfbd 100644 --- a/packages/babel-cli/src/babel/file.ts +++ b/packages/babel-cli/src/babel/file.ts @@ -8,9 +8,16 @@ import * as util from "./util"; import type { CmdOptions } from "./options"; import * as watcher from "./watcher"; +import type { + SectionedSourceMap, + SourceMapInput, + TraceMap, + DecodedSourceMap, +} from "@jridgewell/trace-mapping"; + type CompilationOutput = { code: string; - map: any; + map: SourceMapInput; }; export default async function ({ @@ -18,7 +25,7 @@ export default async function ({ babelOptions, }: CmdOptions): Promise { function buildResult(fileResults: Array): CompilationOutput { - const mapSections = []; + const mapSections: SectionedSourceMap["sections"] = []; let code = ""; let offset = 0; @@ -70,7 +77,7 @@ export default async function ({ } return count; } - function emptyMap() { + function emptyMap(): DecodedSourceMap { return { version: 3, names: [], @@ -89,7 +96,10 @@ export default async function ({ if (babelOptions.sourceMaps && babelOptions.sourceMaps !== "inline") { const mapLoc = cliOptions.outFile + ".map"; result.code = util.addSourceMappingUrl(result.code, mapLoc); - fs.writeFileSync(mapLoc, JSON.stringify(encodedMap(result.map))); + fs.writeFileSync( + mapLoc, + JSON.stringify(encodedMap(result.map as TraceMap)), + ); } fs.writeFileSync(cliOptions.outFile, result.code); @@ -129,7 +139,7 @@ export default async function ({ } async function walk(filenames: Array): Promise { - const _filenames = []; + const _filenames: string[] = []; filenames.forEach(function (filename) { if (!fs.existsSync(filename)) return; diff --git a/packages/babel-cli/src/babel/options.ts b/packages/babel-cli/src/babel/options.ts index da35ca377b5a..38d35f9a07fa 100644 --- a/packages/babel-cli/src/babel/options.ts +++ b/packages/babel-cli/src/babel/options.ts @@ -4,6 +4,8 @@ import commander from "commander"; import { version, DEFAULT_EXTENSIONS } from "@babel/core"; import glob from "glob"; +import type { InputOptions } from "@babel/core"; + // Standard Babel input configs. commander.option( "-f, --filename [filename]", @@ -179,17 +181,35 @@ commander.usage("[options] "); commander.action(() => {}); export type CmdOptions = { - babelOptions: any; - cliOptions: any; + babelOptions: InputOptions; + cliOptions: { + filename: string; + filenames: string[]; + extensions: string[]; + keepFileExtension: boolean; + outFileExtension: string; + watch: boolean; + skipInitialBuild: boolean; + outFile: string; + outDir: string; + relative: boolean; + copyFiles: boolean; + copyIgnored: boolean; + includeDotfiles: boolean; + verbose: boolean; + quiet: boolean; + deleteDirOnStart: boolean; + sourceMapTarget: string; + }; }; export default function parseArgv(args: Array): CmdOptions | null { // commander.parse(args); - const errors = []; + const errors: string[] = []; - let filenames = commander.args.reduce(function (globbed, input) { + let filenames = commander.args.reduce(function (globbed: string[], input) { let files = glob.sync(input); if (!files.length) files = [input]; globbed.push(...files); @@ -264,7 +284,7 @@ export default function parseArgv(args: Array): CmdOptions | null { const opts = commander.opts(); - const babelOptions = { + const babelOptions: InputOptions = { presets: opts.presets, plugins: opts.plugins, rootMode: opts.rootMode, @@ -303,7 +323,9 @@ export default function parseArgv(args: Array): CmdOptions | null { // new options for @babel/core, we'll potentially get option validation errors from // @babel/core. To avoid that, we delete undefined options, so @babel/core will only // give the error if users actually pass an unsupported CLI option. - for (const key of Object.keys(babelOptions)) { + for (const key of Object.keys(babelOptions) as Array< + keyof typeof babelOptions + >) { if (babelOptions[key] === undefined) { delete babelOptions[key]; } diff --git a/packages/babel-cli/src/babel/util.ts b/packages/babel-cli/src/babel/util.ts index de2657ebce80..d75eb51d26ed 100644 --- a/packages/babel-cli/src/babel/util.ts +++ b/packages/babel-cli/src/babel/util.ts @@ -20,7 +20,7 @@ export function readdir( includeDotfiles: boolean, filter?: ReaddirFilter, ): Array { - return readdirRecursive(dirname, (filename, _index, currentDirectory) => { + return readdirRecursive(dirname, (filename, index, currentDirectory) => { const stat = fs.statSync(path.join(currentDirectory, filename)); if (stat.isDirectory()) return true; @@ -134,7 +134,7 @@ export function withExtension(filename: string, ext: string = ".js") { } export function debounce(fn: () => void, time: number) { - let timer; + let timer: NodeJS.Timeout; function debounced() { clearTimeout(timer); timer = setTimeout(fn, time); diff --git a/packages/babel-cli/src/babel/watcher.ts b/packages/babel-cli/src/babel/watcher.ts index be4ff00b921f..f1f9cd643249 100644 --- a/packages/babel-cli/src/babel/watcher.ts +++ b/packages/babel-cli/src/babel/watcher.ts @@ -1,11 +1,12 @@ import { createRequire } from "module"; import path from "path"; +import type { WatchOptions, FSWatcher } from "chokidar"; const fileToDeps = new Map>(); const depToFiles = new Map>(); let isWatchMode = false; -let watcher; +let watcher: FSWatcher; const watchQueue = new Set(); let hasStarted = false; @@ -14,7 +15,7 @@ export function enable({ enableGlobbing }: { enableGlobbing: boolean }) { const { FSWatcher } = requireChokidar(); - watcher = new FSWatcher({ + const options: WatchOptions = { disableGlobbing: !enableGlobbing, persistent: true, ignoreInitial: true, @@ -22,7 +23,8 @@ export function enable({ enableGlobbing }: { enableGlobbing: boolean }) { stabilityThreshold: 50, pollInterval: 10, }, - }); + }; + watcher = new FSWatcher(options); watcher.on("unlink", unwatchFile); } @@ -94,16 +96,18 @@ export function updateExternalDependencies( Array.from(dependencies, dep => path.resolve(dep)), ); - if (fileToDeps.has(absFilename)) { - for (const dep of fileToDeps.get(absFilename)) { + const deps = fileToDeps.get(absFilename); + if (deps) { + for (const dep of deps) { if (!absDependencies.has(dep)) { removeFileDependency(absFilename, dep); } } } for (const dep of absDependencies) { - if (!depToFiles.has(dep)) { - depToFiles.set(dep, new Set()); + let deps = depToFiles.get(dep); + if (!deps) { + depToFiles.set(dep, (deps = new Set())); if (!hasStarted) { watchQueue.add(dep); @@ -111,16 +115,18 @@ export function updateExternalDependencies( watcher.add(dep); } } - depToFiles.get(dep).add(absFilename); + + deps.add(absFilename); } fileToDeps.set(absFilename, absDependencies); } function removeFileDependency(filename: string, dep: string) { - depToFiles.get(dep).delete(filename); + const deps = depToFiles.get(dep) as Set; + deps.delete(filename); - if (depToFiles.get(dep).size === 0) { + if (deps.size === 0) { depToFiles.delete(dep); if (!hasStarted) { @@ -132,16 +138,16 @@ function removeFileDependency(filename: string, dep: string) { } function unwatchFile(filename: string) { - if (!fileToDeps.has(filename)) return; + const deps = fileToDeps.get(filename); + if (!deps) return; - for (const dep of fileToDeps.get(filename)) { + for (const dep of deps) { removeFileDependency(filename, dep); } fileToDeps.delete(filename); } function requireChokidar(): any { - // @ts-expect-error - TS is not configured to support import.meta. const require = createRequire(import.meta.url); try { diff --git a/packages/babel-node/package.json b/packages/babel-node/package.json index 5af679af3cd8..a55039687db2 100644 --- a/packages/babel-node/package.json +++ b/packages/babel-node/package.json @@ -37,6 +37,7 @@ "@babel/core": "workspace:^", "@babel/helper-fixtures": "workspace:^", "@babel/runtime": "workspace:^", + "@types/v8flags": "^3.1.1", "fs-readdir-recursive": "^1.0.0", "make-dir": "condition:BABEL_8_BREAKING ? : ^2.1.0", "rimraf": "^3.0.0" diff --git a/packages/babel-node/src/_babel-node.ts b/packages/babel-node/src/_babel-node.ts index ce9be2a90012..2c3302b5400e 100644 --- a/packages/babel-node/src/_babel-node.ts +++ b/packages/babel-node/src/_babel-node.ts @@ -7,15 +7,19 @@ import * as babel from "@babel/core"; import vm from "vm"; import "core-js/stable/index"; import "regenerator-runtime/runtime"; +// @ts-expect-error @babel/register is a CommonJS module import register from "@babel/register"; import { fileURLToPath } from "url"; - import { createRequire } from "module"; + +import type { PluginAPI, PluginObject } from "@babel/core"; +import type { REPLEval } from "repl"; + const require = createRequire(import.meta.url); const program = new commander.Command("babel-node"); -function collect(value, previousValue): Array { +function collect(value: unknown, previousValue: string[]): Array { // If the user passed the option with no value, like "babel-node file.js --presets", do nothing. if (typeof value !== "string") return previousValue; @@ -91,7 +95,9 @@ const babelOptions = { babelrc: program.babelrc === true ? undefined : program.babelrc, }; -for (const key of Object.keys(babelOptions)) { +for (const key of Object.keys(babelOptions) as Array< + keyof typeof babelOptions +>) { if (babelOptions[key] === undefined) { delete babelOptions[key]; } @@ -99,7 +105,7 @@ for (const key of Object.keys(babelOptions)) { register(babelOptions); -const replPlugin = ({ types: t }) => ({ +const replPlugin = ({ types: t }: PluginAPI): PluginObject => ({ visitor: { ModuleDeclaration(path) { throw path.buildCodeFrameError("Modules aren't supported in the REPL"); @@ -126,7 +132,7 @@ const replPlugin = ({ types: t }) => ({ }, }); -const _eval = function (code, filename) { +const _eval = function (code: string, filename: string) { code = code.trim(); if (!code) return undefined; @@ -145,26 +151,18 @@ if (program.eval || program.print) { let code = program.eval; if (!code || code === true) code = program.print; - // @ts-expect-error todo(flow->ts) global.__filename = "[eval]"; - // @ts-expect-error todo(flow->ts) global.__dirname = process.cwd(); - // @ts-expect-error todo(flow->ts) const module = new Module(global.__filename); - // @ts-expect-error todo(flow->ts) module.filename = global.__filename; // @ts-expect-error todo(flow->ts) module.paths = Module._nodeModulePaths(global.__dirname); - // @ts-expect-error todo(flow->ts) global.exports = module.exports; - // @ts-expect-error todo(flow->ts) global.module = module; - // @ts-expect-error todo(flow->ts) global.require = module.require.bind(module); - // @ts-expect-error todo(flow->ts) const result = _eval(code, global.__filename); if (program.print) { const output = typeof result === "string" ? result : inspect(result); @@ -184,7 +182,7 @@ if (program.eval || program.print) { } if (arg[0] === "-") { - const parsedOption = program.options.find(option => { + const parsedOption = program.options.find((option: any) => { return option.long === arg || option.short === arg; }); if (parsedOption === undefined) { @@ -230,23 +228,7 @@ function requireArgs() { } } -function replStart() { - const replServer = repl.start({ - prompt: "babel > ", - input: process.stdin, - output: process.stdout, - eval: replEval, - useGlobal: true, - preview: true, - }); - if (process.env.BABEL_8_BREAKING) { - replServer.setupHistory(process.env.NODE_REPL_HISTORY, () => {}); - } else { - replServer.setupHistory?.(process.env.NODE_REPL_HISTORY, () => {}); - } -} - -function replEval(code, context, filename, callback) { +const replEval: REPLEval = function (code, context, filename, callback) { let err; let result; @@ -261,4 +243,21 @@ function replEval(code, context, filename, callback) { } callback(err, result); +}; + +function replStart() { + const replServer = repl.start({ + prompt: "babel > ", + input: process.stdin, + output: process.stdout, + eval: replEval, + useGlobal: true, + preview: true, + }); + const NODE_REPL_HISTORY = process.env.NODE_REPL_HISTORY as string; + if (process.env.BABEL_8_BREAKING) { + replServer.setupHistory(NODE_REPL_HISTORY, () => {}); + } else { + replServer.setupHistory?.(NODE_REPL_HISTORY, () => {}); + } } diff --git a/packages/babel-node/src/babel-node.ts b/packages/babel-node/src/babel-node.ts index 90bf8012bf56..66cada747e4f 100644 --- a/packages/babel-node/src/babel-node.ts +++ b/packages/babel-node/src/babel-node.ts @@ -13,7 +13,7 @@ const args = [ ]; let babelArgs = process.argv.slice(2); -let userArgs; +let userArgs: string[]; // separate node arguments from script arguments const argSeparator = babelArgs.indexOf("--"); @@ -27,7 +27,7 @@ if (argSeparator > -1) { * Also ensure that if the arg contains a value (e.g. --arg=true) * that only the flag is returned. */ -function getNormalizedV8Flag(arg) { +function getNormalizedV8Flag(arg: string) { // v8 uses the "no" prefix to negate boolean flags (e.g. --nolazy), // but they are not listed by v8flags const matches = arg.match(/--(?:no)?(.+)/); @@ -54,7 +54,7 @@ getV8Flags(async function (err, v8Flags) { args.push(flag); args.push(babelArgs[++i]); } else if (aliases.has(flag)) { - args.unshift(aliases.get(flag)); + args.unshift(aliases.get(flag) as string); } else if ( flag === "debug" || // node debug foo.js flag === "inspect" || @@ -96,7 +96,7 @@ getV8Flags(async function (err, v8Flags) { if (signal) { process.kill(process.pid, signal); } else { - process.exitCode = code; + process.exitCode = code ?? undefined; } }); }); diff --git a/scripts/generators/tsconfig.js b/scripts/generators/tsconfig.js index ad9d0d203b75..20324a450925 100644 --- a/scripts/generators/tsconfig.js +++ b/scripts/generators/tsconfig.js @@ -74,14 +74,20 @@ function getTsPkgs(subRoot) { subExports, }; }) - .filter( - ({ name, relative }) => - // @babel/register is special-cased because its entry point is a js file + .filter(({ name, relative }) => { + const ret = + // They are special-cased because them dose not have a index.ts name === "@babel/register" || + name === "@babel/cli" || + name === "@babel/node" || // @babel/compat-data is used by preset-env name === "@babel/compat-data" || - fs.existsSync(new URL(relative + "/src/index.ts", root)) - ); + fs.existsSync(new URL(relative + "/src/index.ts", root)); + if (!ret) { + console.log(`Skipping ${name} for tsconfig.json`); + } + return ret; + }); } const tsPkgs = [ @@ -123,6 +129,9 @@ fs.writeFileSync( "to-fast-properties", ["./node_modules/to-fast-properties-BABEL_8_BREAKING-true"], ], + ["slash", ["./node_modules/slash-BABEL_8_BREAKING-true"]], + ["fs-readdir-recursive", ["./lib/fs-readdir-recursive.d.ts"]], + ["kexec", ["./lib/kexec.d.ts"]], ]), }, }, diff --git a/tsconfig.json b/tsconfig.json index d2eba9b804cf..c23494bc71ec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ { "extends": "./tsconfig.base.json", "include": [ + "./packages/babel-cli/src/**/*.ts", "./packages/babel-code-frame/src/**/*.ts", "./packages/babel-compat-data/src/**/*.ts", "./packages/babel-core/src/**/*.ts", @@ -36,6 +37,7 @@ "./packages/babel-helper-wrap-function/src/**/*.ts", "./packages/babel-helpers/src/**/*.ts", "./packages/babel-highlight/src/**/*.ts", + "./packages/babel-node/src/**/*.ts", "./packages/babel-plugin-bugfix-safari-id-destructuring-collision-in-function-expression/src/**/*.ts", "./packages/babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining/src/**/*.ts", "./packages/babel-plugin-external-helpers/src/**/*.ts", @@ -150,6 +152,9 @@ ], "compilerOptions": { "paths": { + "@babel/cli": [ + "./packages/babel-cli/src" + ], "@babel/code-frame": [ "./packages/babel-code-frame/src" ], @@ -267,6 +272,9 @@ "@babel/highlight": [ "./packages/babel-highlight/src" ], + "@babel/node": [ + "./packages/babel-node/src" + ], "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": [ "./packages/babel-plugin-bugfix-safari-id-destructuring-collision-in-function-expression/src" ], @@ -710,6 +718,15 @@ ], "to-fast-properties": [ "./node_modules/to-fast-properties-BABEL_8_BREAKING-true" + ], + "slash": [ + "./node_modules/slash-BABEL_8_BREAKING-true" + ], + "fs-readdir-recursive": [ + "./lib/fs-readdir-recursive.d.ts" + ], + "kexec": [ + "./lib/kexec.d.ts" ] } } diff --git a/yarn.lock b/yarn.lock index a82f687aa2e3..51ecc1c2dee9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1041,6 +1041,7 @@ __metadata: "@babel/helper-fixtures": "workspace:^" "@babel/register": "workspace:^" "@babel/runtime": "workspace:^" + "@types/v8flags": ^3.1.1 commander: ^4.0.1 core-js: ^3.22.1 fs-readdir-recursive: ^1.0.0 @@ -4528,6 +4529,13 @@ __metadata: languageName: node linkType: hard +"@types/v8flags@npm:^3.1.1": + version: 3.1.1 + resolution: "@types/v8flags@npm:3.1.1" + checksum: 5a38e85cefdf3cc6c38448425ba2c726b62da3ad454209955a780b4eb68b38f98adc95063654668c3d642418e2cf860ad3129d989994aaf7e72fbf29dca94bbc + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 15.0.0 resolution: "@types/yargs-parser@npm:15.0.0"