diff --git a/codemods/babel-plugin-codemod-object-assign-to-object-spread/src/index.ts b/codemods/babel-plugin-codemod-object-assign-to-object-spread/src/index.ts index cb7ae05910d8..50a18d9bd0b0 100644 --- a/codemods/babel-plugin-codemod-object-assign-to-object-spread/src/index.ts +++ b/codemods/babel-plugin-codemod-object-assign-to-object-spread/src/index.ts @@ -1,6 +1,7 @@ import syntaxObjectRestSpread from "@babel/plugin-syntax-object-rest-spread"; +import type { PluginAPI, PluginObject } from "@babel/core"; -export default function ({ types: t }) { +export default function ({ types: t }: PluginAPI): PluginObject { return { inherits: syntaxObjectRestSpread.default, @@ -21,10 +22,15 @@ export default function ({ types: t }) { const arg = args[i]; const { node } = arg; - if (arg.isObjectExpression()) { + if (t.isObjectExpression(node)) { properties.push(...node.properties); } else { - properties.push(t.spreadElement(node)); + properties.push( + t.spreadElement( + // @ts-expect-error fixme + node, + ), + ); } } diff --git a/codemods/babel-plugin-codemod-optional-catch-binding/src/index.ts b/codemods/babel-plugin-codemod-optional-catch-binding/src/index.ts index d4873469bb7a..9cf2954e0e58 100644 --- a/codemods/babel-plugin-codemod-optional-catch-binding/src/index.ts +++ b/codemods/babel-plugin-codemod-optional-catch-binding/src/index.ts @@ -1,6 +1,7 @@ import syntaxOptionalCatchBinding from "@babel/plugin-syntax-optional-catch-binding"; +import type { PluginAPI, PluginObject } from "@babel/core"; -export default function ({ types: t }) { +export default function ({ types: t }: PluginAPI): PluginObject { return { inherits: syntaxOptionalCatchBinding.default, diff --git a/lib/regexpu-core.d.ts b/lib/regexpu-core.d.ts new file mode 100644 index 000000000000..415800a5196c --- /dev/null +++ b/lib/regexpu-core.d.ts @@ -0,0 +1,17 @@ +declare module "regexpu-core" { + type RegexpuOptions = { + unicodeFlag?: "transform" | false; + unicodeSetsFlag?: "transform" | "parse" | false; + dotAllFlag?: "transform" | false; + unicodePropertyEscapes?: "transform" | false; + namedGroups?: "transform" | false; + onNamedGroup?: (name: string, index: number) => void; + }; + function rewritePattern( + pattern: string, + flags: string, + options: RegexpuOptions + ): string; + export = rewritePattern; + export { RegexpuOptions }; +} diff --git a/packages/babel-code-frame/src/index.ts b/packages/babel-code-frame/src/index.ts index 35c26501c5bc..db5b7de65c2e 100644 --- a/packages/babel-code-frame/src/index.ts +++ b/packages/babel-code-frame/src/index.ts @@ -1,5 +1,7 @@ import highlight, { shouldHighlight, getChalk } from "@babel/highlight"; +type Chalk = ReturnType; + let deprecationWarningShown = false; type Location = { @@ -37,7 +39,7 @@ export interface Options { /** * Chalk styles for code frame token types. */ -function getDefs(chalk) { +function getDefs(chalk: Chalk) { return { gutter: chalk.grey, marker: chalk.red.bold, @@ -55,6 +57,8 @@ const NEWLINE = /\r\n|[\n\r\u2028\u2029]/; * Extract what lines should be marked and highlighted. */ +type MarkerLines = Record; + function getMarkerLines( loc: NodeLocation, source: Array, @@ -62,7 +66,7 @@ function getMarkerLines( ): { start: number; end: number; - markerLines: any; + markerLines: MarkerLines; } { const startLoc: Location = { column: 0, @@ -91,7 +95,7 @@ function getMarkerLines( } const lineDiff = endLine - startLine; - const markerLines = {}; + const markerLines: MarkerLines = {}; if (lineDiff) { for (let i = 0; i <= lineDiff; i++) { @@ -135,7 +139,7 @@ export function codeFrameColumns( (opts.highlightCode || opts.forceColor) && shouldHighlight(opts); const chalk = getChalk(opts); const defs = getDefs(chalk); - const maybeHighlight = (chalkFn, string) => { + const maybeHighlight = (chalkFn: Chalk, string: string) => { return highlighted ? chalkFn(string) : string; }; const lines = rawLines.split(NEWLINE); diff --git a/packages/babel-core/src/config/cache-contexts.ts b/packages/babel-core/src/config/cache-contexts.ts index b487d22dfcb3..1c7e1553bac3 100644 --- a/packages/babel-core/src/config/cache-contexts.ts +++ b/packages/babel-core/src/config/cache-contexts.ts @@ -16,7 +16,7 @@ export type FullPlugin = { // process 'ignore'/'only' and other filename-based logic. export type SimpleConfig = { envName: string; - caller: CallerMetadata | void; + caller: CallerMetadata | undefined; }; export type SimplePreset = { targets: Targets; diff --git a/packages/babel-core/src/config/caching.ts b/packages/babel-core/src/config/caching.ts index 2cd59d5f1c02..5575ff78b231 100644 --- a/packages/babel-core/src/config/caching.ts +++ b/packages/babel-core/src/config/caching.ts @@ -348,7 +348,7 @@ class CacheConfigurator { function makeSimpleConfigurator( cache: CacheConfigurator, ): SimpleCacheConfigurator { - function cacheFn(val) { + function cacheFn(val: any) { if (typeof val === "boolean") { if (val) cache.forever(); else cache.never(); @@ -359,8 +359,10 @@ function makeSimpleConfigurator( } cacheFn.forever = () => cache.forever(); cacheFn.never = () => cache.never(); - cacheFn.using = cb => cache.using(() => assertSimpleType(cb())); - cacheFn.invalidate = cb => cache.invalidate(() => assertSimpleType(cb())); + cacheFn.using = (cb: { (): SimpleType }) => + cache.using(() => assertSimpleType(cb())); + cacheFn.invalidate = (cb: { (): SimpleType }) => + cache.invalidate(() => assertSimpleType(cb())); return cacheFn as any; } diff --git a/packages/babel-core/src/config/config-chain.ts b/packages/babel-core/src/config/config-chain.ts index 059d38034b52..b3f8e4139400 100644 --- a/packages/babel-core/src/config/config-chain.ts +++ b/packages/babel-core/src/config/config-chain.ts @@ -55,7 +55,7 @@ export type ConfigContext = { cwd: string; root: string; envName: string; - caller: CallerMetadata | void; + caller: CallerMetadata | undefined; showConfig: boolean; }; @@ -383,7 +383,12 @@ const loadFileChainWalker = makeChainWalker({ buildFileLogger(file.filepath, context, baseLogger), }); -function* loadFileChain(input, context, files, baseLogger) { +function* loadFileChain( + input: ValidatedFile, + context: ConfigContext, + files: Set, + baseLogger: ConfigPrinter, +) { const chain = yield* loadFileChainWalker(input, context, files, baseLogger); if (chain) { chain.files.add(input.filepath); @@ -443,11 +448,23 @@ function buildFileLogger( }); } -function buildRootDescriptors({ dirname, options }, alias, descriptors) { +function buildRootDescriptors( + { dirname, options }: Partial, + alias: string, + descriptors: ( + dirname: string, + options: ValidatedOptions, + alias: string, + ) => OptionsAndDescriptors, +) { return descriptors(dirname, options, alias); } -function buildProgrammaticLogger(_, context, baseLogger: ConfigPrinter | void) { +function buildProgrammaticLogger( + _: unknown, + context: ConfigContext, + baseLogger: ConfigPrinter | void, +) { if (!baseLogger) { return () => {}; } @@ -457,20 +474,28 @@ function buildProgrammaticLogger(_, context, baseLogger: ConfigPrinter | void) { } function buildEnvDescriptors( - { dirname, options }, - alias, - descriptors, - envName, + { dirname, options }: Partial, + alias: string, + descriptors: ( + dirname: string, + options: ValidatedOptions, + alias: string, + ) => OptionsAndDescriptors, + envName: string, ) { const opts = options.env && options.env[envName]; return opts ? descriptors(dirname, opts, `${alias}.env["${envName}"]`) : null; } function buildOverrideDescriptors( - { dirname, options }, - alias, - descriptors, - index, + { dirname, options }: Partial, + alias: string, + descriptors: ( + dirname: string, + options: ValidatedOptions, + alias: string, + ) => OptionsAndDescriptors, + index: number, ) { const opts = options.overrides && options.overrides[index]; if (!opts) throw new Error("Assertion failure - missing override"); @@ -479,11 +504,15 @@ function buildOverrideDescriptors( } function buildOverrideEnvDescriptors( - { dirname, options }, - alias, - descriptors, - index, - envName, + { dirname, options }: Partial, + alias: string, + descriptors: ( + dirname: string, + options: ValidatedOptions, + alias: string, + ) => OptionsAndDescriptors, + index: number, + envName: string, ) { const override = options.overrides && options.overrides[index]; if (!override) throw new Error("Assertion failure - missing override"); @@ -850,9 +879,9 @@ function matchesPatterns( } function matchPattern( - pattern, - dirname, - pathToTest, + pattern: IgnoreItem, + dirname: string, + pathToTest: unknown, context: ConfigContext, ): boolean { if (typeof pattern === "function") { diff --git a/packages/babel-core/src/config/config-descriptors.ts b/packages/babel-core/src/config/config-descriptors.ts index 9fe9df54a027..fe033eccc4aa 100644 --- a/packages/babel-core/src/config/config-descriptors.ts +++ b/packages/babel-core/src/config/config-descriptors.ts @@ -126,8 +126,8 @@ export function createUncachedDescriptors( // The returned result here is cached to represent a config object in // memory, so we build and memoize the descriptors to ensure the same // values are returned consistently. - let plugins; - let presets; + let plugins: UnloadedDescriptor[]; + let presets: UnloadedDescriptor[]; return { options: optionsWithResolvedBrowserslistConfigFile(options, dirname), diff --git a/packages/babel-core/src/config/files/configuration.ts b/packages/babel-core/src/config/files/configuration.ts index 713e0c355ab2..23104ea7ce4e 100644 --- a/packages/babel-core/src/config/files/configuration.ts +++ b/packages/babel-core/src/config/files/configuration.ts @@ -57,7 +57,7 @@ export function findConfigUpwards(rootDir: string): string | null { export function* findRelativeConfig( packageData: FilePackageData, envName: string, - caller: CallerMetadata | void, + caller: CallerMetadata | undefined, ): Handler { let config = null; let ignore = null; @@ -93,7 +93,7 @@ export function* findRelativeConfig( export function findRootConfig( dirname: string, envName: string, - caller: CallerMetadata | void, + caller: CallerMetadata | undefined, ): Handler { return loadOneConfig(ROOT_CONFIG_FILENAMES, dirname, envName, caller); } @@ -102,7 +102,7 @@ function* loadOneConfig( names: string[], dirname: string, envName: string, - caller: CallerMetadata | void, + caller: CallerMetadata | undefined, previousConfig: ConfigFile | null = null, ): Handler { const configs = yield* gensync.all( @@ -133,7 +133,7 @@ export function* loadConfig( name: string, dirname: string, envName: string, - caller: CallerMetadata | void, + caller: CallerMetadata | undefined, ): Handler { const filepath = require.resolve(name, { paths: [dirname] }); @@ -150,7 +150,11 @@ export function* loadConfig( * Read the given config file, returning the result. Returns null if no config was found, but will * throw if there are parsing errors while loading a config. */ -function readConfig(filepath, envName, caller): Handler { +function readConfig( + filepath: string, + envName: string, + caller: CallerMetadata | undefined, +): Handler { const ext = path.extname(filepath); return ext === ".js" || ext === ".cjs" || ext === ".mjs" ? readConfigJS(filepath, { envName, caller }) @@ -163,7 +167,7 @@ const readConfigJS = makeStrongCache(function* readConfigJS( filepath: string, cache: CacheConfigurator<{ envName: string; - caller: CallerMetadata | void; + caller: CallerMetadata | undefined; }>, ): Handler { if (!nodeFs.existsSync(filepath)) { diff --git a/packages/babel-core/src/config/files/import-meta-resolve.ts b/packages/babel-core/src/config/files/import-meta-resolve.ts index 11c9731aa912..591aec4d9de4 100644 --- a/packages/babel-core/src/config/files/import-meta-resolve.ts +++ b/packages/babel-core/src/config/files/import-meta-resolve.ts @@ -34,7 +34,8 @@ const importMetaResolveP: Promise = // it throws because it's a module, will fallback to import(). process.execArgv.includes("--experimental-import-meta-resolve") ? import_("data:text/javascript,export default import.meta.resolve").then( - m => m.default || polyfill, + (m: { default: ImportMeta["resolve"] | undefined }) => + m.default || polyfill, () => polyfill, ) : Promise.resolve(polyfill); diff --git a/packages/babel-core/src/config/files/index-browser.ts b/packages/babel-core/src/config/files/index-browser.ts index ac615d9a1583..08f91f6a6751 100644 --- a/packages/babel-core/src/config/files/index-browser.ts +++ b/packages/babel-core/src/config/files/index-browser.ts @@ -35,7 +35,7 @@ export function* findRelativeConfig( // eslint-disable-next-line @typescript-eslint/no-unused-vars envName: string, // eslint-disable-next-line @typescript-eslint/no-unused-vars - caller: CallerMetadata | void, + caller: CallerMetadata | undefined, ): Handler { return { config: null, ignore: null }; } @@ -47,7 +47,7 @@ export function* findRootConfig( // eslint-disable-next-line @typescript-eslint/no-unused-vars envName: string, // eslint-disable-next-line @typescript-eslint/no-unused-vars - caller: CallerMetadata | void, + caller: CallerMetadata | undefined, ): Handler { return null; } @@ -59,7 +59,7 @@ export function* loadConfig( // eslint-disable-next-line @typescript-eslint/no-unused-vars envName: string, // eslint-disable-next-line @typescript-eslint/no-unused-vars - caller: CallerMetadata | void, + caller: CallerMetadata | undefined, ): Handler { throw new Error(`Cannot load ${name} relative to ${dirname} in a browser`); } @@ -72,7 +72,7 @@ export function* resolveShowConfigPath( return null; } -export const ROOT_CONFIG_FILENAMES = []; +export const ROOT_CONFIG_FILENAMES: string[] = []; // eslint-disable-next-line @typescript-eslint/no-unused-vars export function resolvePlugin(name: string, dirname: string): string | null { diff --git a/packages/babel-core/src/config/files/module-types.ts b/packages/babel-core/src/config/files/module-types.ts index 711c0f60079d..7ece0a7a8906 100644 --- a/packages/babel-core/src/config/files/module-types.ts +++ b/packages/babel-core/src/config/files/module-types.ts @@ -7,7 +7,7 @@ import semver from "semver"; const require = createRequire(import.meta.url); -let import_; +let import_: ((specifier: string | URL) => any) | undefined; try { // Old Node.js versions don't support import() syntax. import_ = require("./import").default; diff --git a/packages/babel-core/src/config/files/types.ts b/packages/babel-core/src/config/files/types.ts index a2e61f7ef7e5..3edf65b58f69 100644 --- a/packages/babel-core/src/config/files/types.ts +++ b/packages/babel-core/src/config/files/types.ts @@ -1,7 +1,9 @@ +import type { InputOptions } from ".."; + export type ConfigFile = { filepath: string; dirname: string; - options: {}; + options: InputOptions & { babel?: unknown }; }; export type IgnoreFile = { diff --git a/packages/babel-core/src/config/full.ts b/packages/babel-core/src/config/full.ts index 7121d4892902..abb022927652 100644 --- a/packages/babel-core/src/config/full.ts +++ b/packages/babel-core/src/config/full.ts @@ -181,7 +181,7 @@ export default gensync<(inputOpts: unknown) => ResolvedConfig | null>( pluginDescriptorsByPass[0].unshift(...initialPluginsDescriptors); for (const descs of pluginDescriptorsByPass) { - const pass = []; + const pass: Plugin[] = []; passes.push(pass); for (let i = 0; i < descs.length; i++) { @@ -223,8 +223,8 @@ export default gensync<(inputOpts: unknown) => ResolvedConfig | null>( }, ); -function enhanceError(context, fn: T): T { - return function* (arg1, arg2) { +function enhanceError(context: ConfigContext, fn: T): T { + return function* (arg1: unknown, arg2: unknown) { try { return yield* fn(arg1, arg2); } catch (e) { @@ -376,7 +376,7 @@ const instantiatePlugin = makeWeakCache(function* ( } if (plugin.inherits) { - const inheritsDescriptor = { + const inheritsDescriptor: UnloadedDescriptor = { name: undefined, alias: `${alias}$inherits`, value: plugin.inherits, @@ -490,11 +490,14 @@ const instantiatePreset = makeWeakCacheSync( }, ); -function chain(a, b) { +function chain( + a: undefined | ((...args: Args) => void), + b: undefined | ((...args: Args) => void), +) { const fns = [a, b].filter(Boolean); if (fns.length <= 1) return fns[0]; - return function (this: unknown, ...args) { + return function (this: unknown, ...args: unknown[]) { for (const fn of fns) { fn.apply(this, args); } diff --git a/packages/babel-core/src/config/helpers/config-api.ts b/packages/babel-core/src/config/helpers/config-api.ts index b8f8ba833c3b..724fdc431873 100644 --- a/packages/babel-core/src/config/helpers/config-api.ts +++ b/packages/babel-core/src/config/helpers/config-api.ts @@ -21,10 +21,10 @@ type EnvFunction = { }; type CallerFactory = ( - extractor: (callerMetadata: CallerMetadata | void) => unknown, + extractor: (callerMetadata: CallerMetadata | undefined) => unknown, ) => SimpleType; type TargetsFunction = () => Targets; -type AssumptionFunction = (name: AssumptionName) => boolean | void; +type AssumptionFunction = (name: AssumptionName) => boolean | undefined; export type ConfigAPI = { version: string; @@ -47,23 +47,29 @@ export type PluginAPI = { export function makeConfigAPI( cache: CacheConfigurator, ): ConfigAPI { - const env: any = value => + // TODO(@nicolo-ribaudo): If we remove the explicit type from `value` + // and the `as any` type cast, TypeScript crashes in an infinite + // recursion. After upgrading to TS4.7 and finishing the noImplicitAny + // PR, we should check if it still crashes and report it to the TS team. + const env: EnvFunction = (( + value: string | string[] | ((babelEnv: string) => T), + ) => cache.using(data => { if (typeof value === "undefined") return data.envName; if (typeof value === "function") { return assertSimpleType(value(data.envName)); } - if (!Array.isArray(value)) value = [value]; - - return value.some((entry: unknown) => { + return (Array.isArray(value) ? value : [value]).some(entry => { if (typeof entry !== "string") { throw new Error("Unexpected non-string value"); } return entry === data.envName; }); - }); + })) as any; - const caller = cb => cache.using(data => assertSimpleType(cb(data.caller))); + const caller = (cb: { + (CallerMetadata: CallerMetadata | undefined): SimpleType; + }) => cache.using(data => assertSimpleType(cb(data.caller))); return { version: coreVersion, @@ -98,7 +104,8 @@ export function makePluginAPI( cache: CacheConfigurator, externalDependencies: Array, ): PluginAPI { - const assumption = name => cache.using(data => data.assumptions[name]); + const assumption = (name: string) => + cache.using(data => data.assumptions[name]); return { ...makePresetAPI(cache, externalDependencies), assumption }; } diff --git a/packages/babel-core/src/config/index.ts b/packages/babel-core/src/config/index.ts index d231a8ec6d59..c01b90ca1c83 100644 --- a/packages/babel-core/src/config/index.ts +++ b/packages/babel-core/src/config/index.ts @@ -1,4 +1,5 @@ import gensync from "gensync"; +import type { Gensync } from "gensync"; export type { ResolvedConfig, @@ -18,9 +19,12 @@ type PluginAPI = basePluginAPI & typeof import(".."); type PresetAPI = basePresetAPI & typeof import(".."); export type { PluginAPI, PresetAPI }; // todo: may need to refine PresetObject to be a subset of ValidatedOptions -export type { ValidatedOptions as PresetObject } from "./validation/options"; +export type { + CallerMetadata, + ValidatedOptions as PresetObject, +} from "./validation/options"; -import loadFullConfig from "./full"; +import loadFullConfig, { type ResolvedConfig } from "./full"; import { loadPartialConfig as loadPartialConfigRunner } from "./partial"; export { loadFullConfig as default }; @@ -29,24 +33,28 @@ export type { PartialConfig } from "./partial"; import { createConfigItem as createConfigItemImpl } from "./item"; import type { ConfigItem } from "./item"; -const loadOptionsRunner = gensync<(opts: unknown) => any>(function* (opts) { - const config = yield* loadFullConfig(opts); - // NOTE: We want to return "null" explicitly, while ?. alone returns undefined - return config?.options ?? null; -}); +const loadOptionsRunner = gensync<(opts: unknown) => ResolvedConfig | null>( + function* (opts) { + const config = yield* loadFullConfig(opts); + // NOTE: We want to return "null" explicitly, while ?. alone returns undefined + return config?.options ?? null; + }, +); const createConfigItemRunner = gensync<(...args: Parameters) => ConfigItem>( createConfigItemImpl, ); -const maybeErrback = runner => (opts: unknown, callback?: Function) => { - if (callback === undefined && typeof opts === "function") { - callback = opts; - opts = undefined; - } - return callback ? runner.errback(opts, callback) : runner.sync(opts); -}; +const maybeErrback = + (runner: Gensync<(...args: any) => any>) => + (opts: unknown, callback?: any) => { + if (callback === undefined && typeof opts === "function") { + callback = opts; + opts = undefined; + } + return callback ? runner.errback(opts, callback) : runner.sync(opts); + }; export const loadPartialConfig = maybeErrback(loadPartialConfigRunner); export const loadPartialConfigSync = loadPartialConfigRunner.sync; @@ -60,7 +68,7 @@ export const createConfigItemSync = createConfigItemRunner.sync; export const createConfigItemAsync = createConfigItemRunner.async; export function createConfigItem( target: PluginTarget, - options: any, + options: Parameters[1], callback?: (err: Error, val: ConfigItem | null) => void, ) { if (callback !== undefined) { diff --git a/packages/babel-core/src/config/pattern-to-regex.ts b/packages/babel-core/src/config/pattern-to-regex.ts index a5078365fdcd..714207f8700e 100644 --- a/packages/babel-core/src/config/pattern-to-regex.ts +++ b/packages/babel-core/src/config/pattern-to-regex.ts @@ -11,7 +11,7 @@ const starPatLast = `(?:${substitution}${endSep})`; const starStarPat = `${starPat}*?`; const starStarPatLast = `${starPat}*?${starPatLast}?`; -function escapeRegExp(string) { +function escapeRegExp(string: string) { return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&"); } diff --git a/packages/babel-core/src/config/plugin.ts b/packages/babel-core/src/config/plugin.ts index ea10bb8c7eaa..782b57ec6175 100644 --- a/packages/babel-core/src/config/plugin.ts +++ b/packages/babel-core/src/config/plugin.ts @@ -5,9 +5,9 @@ import type { PluginObject } from "./validation/plugins"; export default class Plugin { key: string | undefined | null; manipulateOptions?: (options: unknown, parserOpts: unknown) => void; - post?: Function; - pre?: Function; - visitor: {}; + post?: PluginObject["post"]; + pre?: PluginObject["pre"]; + visitor: PluginObject["visitor"]; parserOverride?: Function; generatorOverride?: Function; diff --git a/packages/babel-core/src/config/resolve-targets-browser.ts b/packages/babel-core/src/config/resolve-targets-browser.ts index 2d91c9216975..60745dd7dca6 100644 --- a/packages/babel-core/src/config/resolve-targets-browser.ts +++ b/packages/babel-core/src/config/resolve-targets-browser.ts @@ -1,5 +1,7 @@ import type { ValidatedOptions } from "./validation/options"; -import getTargets from "@babel/helper-compilation-targets"; +import getTargets, { + type InputTargets, +} from "@babel/helper-compilation-targets"; import type { Targets } from "@babel/helper-compilation-targets"; @@ -17,13 +19,18 @@ export function resolveTargets( // eslint-disable-next-line @typescript-eslint/no-unused-vars root: string, ): Targets { - // todo(flow->ts) remove any and refactor to not assign different types into same variable - let targets: any = options.targets; - if (typeof targets === "string" || Array.isArray(targets)) { - targets = { browsers: targets }; - } - if (targets && targets.esmodules) { - targets = { ...targets, esmodules: "intersect" }; + const optTargets = options.targets; + let targets: InputTargets; + + if (typeof optTargets === "string" || Array.isArray(optTargets)) { + targets = { browsers: optTargets }; + } else if (optTargets) { + if ("esmodules" in optTargets) { + targets = { ...optTargets, esmodules: "intersect" }; + } else { + // https://github.com/microsoft/TypeScript/issues/17002 + targets = optTargets as InputTargets; + } } return getTargets(targets, { diff --git a/packages/babel-core/src/config/resolve-targets.ts b/packages/babel-core/src/config/resolve-targets.ts index 90a443ed3387..a7d9a790e99a 100644 --- a/packages/babel-core/src/config/resolve-targets.ts +++ b/packages/babel-core/src/config/resolve-targets.ts @@ -7,7 +7,9 @@ type nodeType = typeof import("./resolve-targets"); import type { ValidatedOptions } from "./validation/options"; import path from "path"; -import getTargets from "@babel/helper-compilation-targets"; +import getTargets, { + type InputTargets, +} from "@babel/helper-compilation-targets"; import type { Targets } from "@babel/helper-compilation-targets"; @@ -22,13 +24,18 @@ export function resolveTargets( options: ValidatedOptions, root: string, ): Targets { - // todo(flow->ts) remove any and refactor to not assign different types into same variable - let targets: any = options.targets; - if (typeof targets === "string" || Array.isArray(targets)) { - targets = { browsers: targets }; - } - if (targets && targets.esmodules) { - targets = { ...targets, esmodules: "intersect" }; + const optTargets = options.targets; + let targets: InputTargets; + + if (typeof optTargets === "string" || Array.isArray(optTargets)) { + targets = { browsers: optTargets }; + } else if (optTargets) { + if ("esmodules" in optTargets) { + targets = { ...optTargets, esmodules: "intersect" }; + } else { + // https://github.com/microsoft/TypeScript/issues/17002 + targets = optTargets as InputTargets; + } } const { browserslistConfigFile } = options; diff --git a/packages/babel-core/src/config/util.ts b/packages/babel-core/src/config/util.ts index 2bdce13b7071..76c7a7db7171 100644 --- a/packages/babel-core/src/config/util.ts +++ b/packages/babel-core/src/config/util.ts @@ -13,16 +13,18 @@ export function mergeOptions( const targetObj = target[k] || (target[k] = {}); mergeDefaultFields(targetObj, parserOpts); } else { + //@ts-expect-error const val = source[k]; + //@ts-expect-error if (val !== undefined) target[k] = val as any; } } } function mergeDefaultFields(target: T, source: T) { - for (const k of Object.keys(source)) { + for (const k of Object.keys(source) as (keyof T)[]) { const val = source[k]; - if (val !== undefined) target[k] = val as any; + if (val !== undefined) target[k] = val; } } diff --git a/packages/babel-core/src/config/validation/option-assertions.ts b/packages/babel-core/src/config/validation/option-assertions.ts index 8f141a1c160a..50c40c63633b 100644 --- a/packages/babel-core/src/config/validation/option-assertions.ts +++ b/packages/babel-core/src/config/validation/option-assertions.ts @@ -33,7 +33,7 @@ export type ValidatorSet = { export type Validator = (loc: OptionPath, value: unknown) => T; -export function msg(loc: NestingPath | GeneralPath) { +export function msg(loc: NestingPath | GeneralPath): string { switch (loc.type) { case "root": return ``; @@ -135,7 +135,7 @@ export function assertSourceType( export function assertCallerMetadata( loc: OptionPath, value: unknown, -): CallerMetadata | void { +): CallerMetadata | undefined { const obj = assertObject(loc, value); if (obj) { if (typeof obj.name !== "string") { @@ -446,7 +446,7 @@ function assertBrowserVersion(loc: GeneralPath, value: unknown) { export function assertAssumptions( loc: GeneralPath, - value: unknown, + value: { [key: string]: unknown }, ): { [name: string]: boolean } | void { if (value === undefined) return; diff --git a/packages/babel-core/src/config/validation/options.ts b/packages/babel-core/src/config/validation/options.ts index 2b256fd2b8e5..588892994bda 100644 --- a/packages/babel-core/src/config/validation/options.ts +++ b/packages/babel-core/src/config/validation/options.ts @@ -296,7 +296,7 @@ export function validate(type: OptionsSource, opts: {}): ValidatedOptions { ); } -function validateNested(loc: NestingPath, opts: {}) { +function validateNested(loc: NestingPath, opts: { [key: string]: unknown }) { const type = getSource(loc); assertNoDuplicateSourcemap(opts); diff --git a/packages/babel-core/src/config/validation/plugins.ts b/packages/babel-core/src/config/validation/plugins.ts index 9364758f153b..0e29a78364b8 100644 --- a/packages/babel-core/src/config/validation/plugins.ts +++ b/packages/babel-core/src/config/validation/plugins.ts @@ -14,7 +14,7 @@ import type { import type { ParserOptions } from "@babel/parser"; import type { Visitor } from "@babel/traverse"; import type { ValidatedOptions } from "./options"; -import type { File, PluginPass } from "../../.."; +import type { File, PluginPass } from "../../index"; // Note: The casts here are just meant to be static assertions to make sure // that the assertion functions actually assert that the value's type matches @@ -92,7 +92,9 @@ export type PluginObject = { generatorOverride?: Function; }; -export function validatePluginObject(obj: {}): PluginObject { +export function validatePluginObject(obj: { + [key: string]: unknown; +}): PluginObject { const rootPath: RootPath = { type: "root", source: "plugin", diff --git a/packages/babel-core/src/gensync-utils/async.ts b/packages/babel-core/src/gensync-utils/async.ts index 7c5cc1da8101..af3e3da742b6 100644 --- a/packages/babel-core/src/gensync-utils/async.ts +++ b/packages/babel-core/src/gensync-utils/async.ts @@ -3,7 +3,7 @@ import gensync from "gensync"; import type { Gensync, Handler } from "gensync"; type MaybePromise = T | Promise; -const id = x => x; +const id = (x: T): T => x; const runGenerator: { sync(gen: Generator): Return; diff --git a/packages/babel-core/src/index.ts b/packages/babel-core/src/index.ts index 3a05f67240ce..9698e3fd1a6f 100644 --- a/packages/babel-core/src/index.ts +++ b/packages/babel-core/src/index.ts @@ -32,13 +32,20 @@ export { } from "./config"; export type { + CallerMetadata, + InputOptions, PluginAPI, PluginObject, PresetAPI, PresetObject, } from "./config"; -export { transform, transformSync, transformAsync } from "./transform"; +export { + transform, + transformSync, + transformAsync, + type FileResult, +} from "./transform"; export { transformFile, transformFileSync, diff --git a/packages/babel-core/src/parse.ts b/packages/babel-core/src/parse.ts index 38765f06eebf..49a7587a0781 100644 --- a/packages/babel-core/src/parse.ts +++ b/packages/babel-core/src/parse.ts @@ -5,10 +5,11 @@ import type { InputOptions } from "./config"; import parser from "./parser"; import type { ParseResult } from "./parser"; import normalizeOptions from "./transformation/normalize-opts"; +import type { ValidatedOptions } from "./config/validation/options"; type FileParseCallback = { - (err: Error, ast: null): any; - (err: null, ast: ParseResult | null): any; + (err: Error, ast: null): void; + (err: null, ast: ParseResult | null): void; }; type Parse = { @@ -33,10 +34,14 @@ const parseRunner = gensync< return yield* parser(config.passes, normalizeOptions(config), code); }); -export const parse: Parse = function parse(code, opts?, callback?) { +export const parse: Parse = function parse( + code, + opts?, + callback?: FileParseCallback, +) { if (typeof opts === "function") { callback = opts; - opts = undefined; + opts = undefined as ValidatedOptions; } if (callback === undefined) { diff --git a/packages/babel-core/src/parser/util/missing-plugin-helper.ts b/packages/babel-core/src/parser/util/missing-plugin-helper.ts index 32f31b7201bd..7726dcd6aaf2 100644 --- a/packages/babel-core/src/parser/util/missing-plugin-helper.ts +++ b/packages/babel-core/src/parser/util/missing-plugin-helper.ts @@ -1,4 +1,7 @@ -const pluginNameMap = { +const pluginNameMap: Record< + string, + Partial>> +> = { asyncDoExpressions: { syntax: { name: "@babel/plugin-syntax-async-do-expressions", @@ -291,7 +294,8 @@ const pluginNameMap = { //todo: we don't have plugin-syntax-private-property-in-object pluginNameMap.privateIn.syntax = pluginNameMap.privateIn.transform; -const getNameURLCombination = ({ name, url }) => `${name} (${url})`; +const getNameURLCombination = ({ name, url }: { name: string; url: string }) => + `${name} (${url})`; /* Returns a string of the format: diff --git a/packages/babel-core/src/tools/build-external-helpers.ts b/packages/babel-core/src/tools/build-external-helpers.ts index ab7610bb9161..0c9f61e23bc3 100644 --- a/packages/babel-core/src/tools/build-external-helpers.ts +++ b/packages/babel-core/src/tools/build-external-helpers.ts @@ -24,10 +24,11 @@ import { } from "@babel/types"; import type * as t from "@babel/types"; import File from "../transformation/file/file"; +import type { PublicReplacements } from "@babel/template/src/options"; // Wrapped to avoid wasting time parsing this when almost no-one uses // build-external-helpers. -const buildUmdWrapper = replacements => +const buildUmdWrapper = (replacements: PublicReplacements) => template.statement` (function (root, factory) { if (typeof define === "function" && define.amd) { @@ -145,6 +146,17 @@ function buildVar(allowlist?: Array) { return tree; } +function buildHelpers( + body: t.Statement[], + namespace: t.Expression, + allowlist?: Array, +): Record; +function buildHelpers( + body: t.Statement[], + namespace: null, + allowlist?: Array, +): Record; + function buildHelpers( body: t.Statement[], namespace: t.Expression | null, @@ -156,7 +168,7 @@ function buildHelpers( : identifier(`_${name}`); }; - const refs = {}; + const refs: { [key: string]: t.Identifier | t.MemberExpression } = {}; helpers.list.forEach(function (name) { if (allowlist && allowlist.indexOf(name) < 0) return; diff --git a/packages/babel-core/src/transform-ast.ts b/packages/babel-core/src/transform-ast.ts index d42dbb79913f..6e16d52b0cb8 100644 --- a/packages/babel-core/src/transform-ast.ts +++ b/packages/babel-core/src/transform-ast.ts @@ -37,12 +37,17 @@ const transformFromAstRunner = gensync< export const transformFromAst: TransformFromAst = function transformFromAst( ast, code, - opts, - callback?, + optsOrCallback?: InputOptions | null | undefined | FileResultCallback, + maybeCallback?: FileResultCallback, ) { - if (typeof opts === "function") { - callback = opts; + let opts: InputOptions | undefined | null; + let callback: FileResultCallback | undefined; + if (typeof optsOrCallback === "function") { + callback = optsOrCallback; opts = undefined; + } else { + opts = optsOrCallback; + callback = maybeCallback; } if (callback === undefined) { diff --git a/packages/babel-core/src/transform-file-browser.ts b/packages/babel-core/src/transform-file-browser.ts index 1adbcd649b52..f316cb432e7c 100644 --- a/packages/babel-core/src/transform-file-browser.ts +++ b/packages/babel-core/src/transform-file-browser.ts @@ -1,13 +1,17 @@ // duplicated from transform-file so we do not have to import anything here type TransformFile = { - (filename: string, callback: Function): void; - (filename: string, opts: any, callback: Function): void; + (filename: string, callback: (error: Error, file: null) => void): void; + ( + filename: string, + opts: any, + callback: (error: Error, file: null) => void, + ): void; }; export const transformFile: TransformFile = function transformFile( filename, opts, - callback?, + callback?: (error: Error, file: null) => void, ) { if (typeof opts === "function") { callback = opts; diff --git a/packages/babel-core/src/transform.ts b/packages/babel-core/src/transform.ts index 668b20de5ab7..310070bc7805 100644 --- a/packages/babel-core/src/transform.ts +++ b/packages/babel-core/src/transform.ts @@ -6,6 +6,8 @@ import { run } from "./transformation"; import type { FileResult, FileResultCallback } from "./transformation"; +export type { FileResult } from "./transformation"; + type Transform = { (code: string, callback: FileResultCallback): void; ( @@ -25,10 +27,19 @@ const transformRunner = gensync< return yield* run(config, code); }); -export const transform: Transform = function transform(code, opts?, callback?) { - if (typeof opts === "function") { - callback = opts; +export const transform: Transform = function transform( + code, + optsOrCallback?: InputOptions | null | undefined | FileResultCallback, + maybeCallback?: FileResultCallback, +) { + let opts: InputOptions | undefined | null; + let callback: FileResultCallback | undefined; + if (typeof optsOrCallback === "function") { + callback = optsOrCallback; opts = undefined; + } else { + opts = optsOrCallback; + callback = maybeCallback; } if (callback === undefined) { diff --git a/packages/babel-core/src/transformation/block-hoist-plugin.ts b/packages/babel-core/src/transformation/block-hoist-plugin.ts index 445e1866de8d..54b1eef5d31f 100644 --- a/packages/babel-core/src/transformation/block-hoist-plugin.ts +++ b/packages/babel-core/src/transformation/block-hoist-plugin.ts @@ -1,4 +1,6 @@ import traverse from "@babel/traverse"; +import type { Statement } from "@babel/types"; +import type { PluginObject } from "../config"; import Plugin from "../config/plugin"; let LOADED_PLUGIN: Plugin | void; @@ -17,14 +19,14 @@ export default function loadBlockHoistPlugin(): Plugin { return LOADED_PLUGIN; } -function priority(bodyNode) { +function priority(bodyNode: Statement & { _blockHoist?: number | true }) { const priority = bodyNode?._blockHoist; if (priority == null) return 1; if (priority === true) return 2; return priority; } -function stableSort(body) { +function stableSort(body: Statement[]) { // By default, we use priorities of 0-4. const buckets = Object.create(null); @@ -54,7 +56,7 @@ function stableSort(body) { return body; } -const blockHoistPlugin = { +const blockHoistPlugin: PluginObject = { /** * [Please add a description.] * diff --git a/packages/babel-core/src/transformation/file/file.ts b/packages/babel-core/src/transformation/file/file.ts index d44b64799941..ad3a5524718c 100644 --- a/packages/babel-core/src/transformation/file/file.ts +++ b/packages/babel-core/src/transformation/file/file.ts @@ -1,6 +1,6 @@ import * as helpers from "@babel/helpers"; import { NodePath, Scope } from "@babel/traverse"; -import type { HubInterface } from "@babel/traverse"; +import type { HubInterface, Visitor } from "@babel/traverse"; import { codeFrameColumns } from "@babel/code-frame"; import traverse from "@babel/traverse"; import { cloneNode, interpreterDirective } from "@babel/types"; @@ -10,7 +10,7 @@ import semver from "semver"; import type { NormalizedFile } from "../normalize-file"; -const errorVisitor = { +const errorVisitor: Visitor<{ loc: NodeLocation["loc"] | null }> = { enter(path, state) { const loc = path.node.loc; if (loc) { @@ -44,15 +44,15 @@ export type NodeLocation = { }; export default class File { - _map: Map = new Map(); - opts: any; - declarations: any = {}; - path: NodePath = null; - ast: any = {}; + _map: Map = new Map(); + opts: { [key: string]: any }; + declarations: { [key: string]: t.Identifier } = {}; + path: NodePath; + ast: t.File; scope: Scope; - metadata: {} = {}; + metadata: { [key: string]: any } = {}; code: string = ""; - inputMap: any | null = null; + inputMap: any; hub: HubInterface & { file: File } = { // keep it for the usage in babel-core, ex: path.hub.file.opts.filename @@ -191,7 +191,7 @@ export default class File { const uid = (this.declarations[name] = this.scope.generateUidIdentifier(name)); - const dependencies = {}; + const dependencies: { [key: string]: t.Identifier } = {}; for (const dep of helpers.getDependencies(name)) { dependencies[dep] = this.addHelper(dep); } @@ -239,7 +239,7 @@ export default class File { let loc = node && (node.loc || node._loc); if (!loc && node) { - const state = { + const state: { loc?: NodeLocation["loc"] | null } = { loc: null, }; traverse(node as t.Node, errorVisitor, this.scope, state); diff --git a/packages/babel-core/src/transformation/index.ts b/packages/babel-core/src/transformation/index.ts index 4d83285e9735..78d587c8e265 100644 --- a/packages/babel-core/src/transformation/index.ts +++ b/packages/babel-core/src/transformation/index.ts @@ -16,17 +16,17 @@ import type File from "./file/file"; import { flattenToSet } from "../config/helpers/deep-array"; export type FileResultCallback = { - (err: Error, file: null): any; - (err: null, file: FileResult | null): any; + (err: Error, file: null): void; + (err: null, file: FileResult | null): void; }; export type FileResult = { - metadata: {}; - options: {}; - ast: {} | null; + metadata: { [key: string]: any }; + options: { [key: string]: any }; + ast: t.File | null; code: string | null; map: SourceMap | null; - sourceType: "string" | "module"; + sourceType: "script" | "module"; externalDependencies: Set; }; diff --git a/packages/babel-core/src/transformation/normalize-file.ts b/packages/babel-core/src/transformation/normalize-file.ts index e1e730565068..ce9c0ae2df1e 100644 --- a/packages/babel-core/src/transformation/normalize-file.ts +++ b/packages/babel-core/src/transformation/normalize-file.ts @@ -16,13 +16,13 @@ const LARGE_INPUT_SOURCEMAP_THRESHOLD = 3_000_000; export type NormalizedFile = { code: string; - ast: {}; + ast: t.File; inputMap: Converter | null; }; export default function* normalizeFile( pluginPasses: PluginPasses, - options: any, + options: { [key: string]: any }, code: string, ast?: t.File | t.Program | null, ): Handler { @@ -36,7 +36,7 @@ export default function* normalizeFile( } if (options.cloneInputAst) { - ast = cloneDeep(ast); + ast = cloneDeep(ast) as t.File; } } else { ast = yield* parser(pluginPasses, options, code); @@ -105,7 +105,11 @@ const INLINE_SOURCEMAP_REGEX = const EXTERNAL_SOURCEMAP_REGEX = /^[@#][ \t]+sourceMappingURL=([^\s'"`]+)[ \t]*$/; -function extractCommentsFromList(regex, comments, lastComment) { +function extractCommentsFromList( + regex: RegExp, + comments: t.Comment[], + lastComment: string | null, +): [t.Comment[], string | null] { if (comments) { comments = comments.filter(({ value }) => { if (regex.test(value)) { @@ -118,8 +122,8 @@ function extractCommentsFromList(regex, comments, lastComment) { return [comments, lastComment]; } -function extractComments(regex, ast) { - let lastComment = null; +function extractComments(regex: RegExp, ast: t.Node) { + let lastComment: string = null; traverseFast(ast, node => { [node.leadingComments, lastComment] = extractCommentsFromList( regex, diff --git a/packages/babel-generator/src/buffer.ts b/packages/babel-generator/src/buffer.ts index 9070b2997187..f097e4fe2070 100644 --- a/packages/babel-generator/src/buffer.ts +++ b/packages/babel-generator/src/buffer.ts @@ -1,12 +1,11 @@ import type SourceMap from "./source-map"; -import type * as t from "@babel/types"; import * as charcodes from "charcodes"; -type Pos = { +export type Pos = { line: number; column: number; }; -type Loc = { +export type Loc = { start?: Pos; end?: Pos; identifierName?: string; @@ -63,14 +62,18 @@ export default class Buffer { // Encoding the sourcemap is moderately CPU expensive. get map() { - return (result.map = map ? map.get() : null); + const resultMap = map ? map.get() : null; + result.map = resultMap; + return resultMap; }, set map(value) { Object.defineProperty(result, "map", { value, writable: true }); }, // Retrieving the raw mappings is very memory intensive. get rawMappings() { - return (result.rawMappings = map?.getRawMappings()); + const mappings = map?.getRawMappings(); + result.rawMappings = mappings; + return mappings; }, set rawMappings(value) { Object.defineProperty(result, "rawMappings", { value, writable: true }); @@ -241,7 +244,7 @@ export default class Buffer { * With this line, there will be one mapping range over "mod" and another * over "();", where previously it would have been a single mapping. */ - exactSource(loc: any, cb: () => void) { + exactSource(loc: Loc | undefined, cb: () => void) { this.source("start", loc); cb(); @@ -262,7 +265,7 @@ export default class Buffer { * will be given this position in the sourcemap. */ - source(prop: string, loc: t.SourceLocation): void { + source(prop: "start" | "end", loc: Loc | undefined): void { if (prop && !loc) return; // Since this is called extremely often, we re-use the same _sourcePosition @@ -274,7 +277,7 @@ export default class Buffer { * Call a callback with a specific source location and restore on completion. */ - withSource(prop: string, loc: t.SourceLocation, cb: () => void): void { + withSource(prop: "start" | "end", loc: Loc, cb: () => void): void { if (!this._map) return cb(); // Use the call stack to manage a stack of "source location" data because @@ -309,14 +312,14 @@ export default class Buffer { * sourcemap output, so that certain printers can be sure that the * "end" location that they set is actually treated as the end position. */ - _disallowPop(prop: string, loc: t.SourceLocation) { + _disallowPop(prop: "start" | "end", loc: Loc) { if (prop && !loc) return; this._disallowedPop = this._normalizePosition(prop, loc, SourcePos()); } _normalizePosition( - prop: string, + prop: "start" | "end", loc: Loc | undefined | null, targetObj: SourcePos, ) { diff --git a/packages/babel-generator/src/generators/base.ts b/packages/babel-generator/src/generators/base.ts index 30253db2e5f7..ae53b83d0fc1 100644 --- a/packages/babel-generator/src/generators/base.ts +++ b/packages/babel-generator/src/generators/base.ts @@ -58,7 +58,7 @@ const unescapedDoubleQuoteRE = /(?:^|[^\\])(?:\\\\)*"/; export function DirectiveLiteral(this: Printer, node: t.DirectiveLiteral) { const raw = this.getPossibleRaw(node); - if (!this.format.minified && raw != null) { + if (!this.format.minified && raw !== undefined) { this.token(raw); return; } diff --git a/packages/babel-generator/src/generators/classes.ts b/packages/babel-generator/src/generators/classes.ts index 77eea7ea2664..0212bd9d4737 100644 --- a/packages/babel-generator/src/generators/classes.ts +++ b/packages/babel-generator/src/generators/classes.ts @@ -9,7 +9,7 @@ import * as charCodes from "charcodes"; export function ClassDeclaration( this: Printer, node: t.ClassDeclaration, - parent: any, + parent: t.Node, ) { if ( !this.format.decoratorsBeforeExport || @@ -86,7 +86,7 @@ export function ClassProperty(this: Printer, node: t.ClassProperty) { // between member modifiers and the property key. this.source("end", node.key.loc); - this.tsPrintClassMemberModifiers(node, /* isField */ true); + this.tsPrintClassMemberModifiers(node); if (node.computed) { this.token("["); @@ -125,7 +125,8 @@ export function ClassAccessorProperty( // between member modifiers and the property key. this.source("end", node.key.loc); - this.tsPrintClassMemberModifiers(node, /* isField */ true); + // TS does not support class accessor property yet + this.tsPrintClassMemberModifiers(node); this.word("accessor"); this.printInnerComments(node); @@ -136,6 +137,7 @@ export function ClassAccessorProperty( this.print(node.key, node); this.token("]"); } else { + // Todo: Flow does not support class accessor property yet. this._variance(node); this.print(node.key, node); } @@ -190,12 +192,15 @@ export function ClassPrivateMethod(this: Printer, node: t.ClassPrivateMethod) { this.print(node.body, node); } -export function _classMethodHead(this: Printer, node) { +export function _classMethodHead( + this: Printer, + node: t.ClassMethod | t.ClassPrivateMethod | t.TSDeclareMethod, +) { this.printJoin(node.decorators, node); // catch up to method key, avoid line break // between member modifiers/method heads and the method key. this.source("end", node.key.loc); - this.tsPrintClassMemberModifiers(node, /* isField */ false); + this.tsPrintClassMemberModifiers(node); this._methodHead(node); } diff --git a/packages/babel-generator/src/generators/expressions.ts b/packages/babel-generator/src/generators/expressions.ts index 311346cd123c..c79bf17c9ad3 100644 --- a/packages/babel-generator/src/generators/expressions.ts +++ b/packages/babel-generator/src/generators/expressions.ts @@ -49,9 +49,7 @@ export function UpdateExpression(this: Printer, node: t.UpdateExpression) { this.token(node.operator); this.print(node.argument, node); } else { - this.startTerminatorless(true); - this.print(node.argument, node); - this.endTerminatorless(); + this.printTerminatorless(node.argument, node, true); this.token(node.operator); } } @@ -74,7 +72,7 @@ export function ConditionalExpression( export function NewExpression( this: Printer, node: t.NewExpression, - parent: any, + parent: t.Node, ) { this.word("new"); this.space(); @@ -116,7 +114,7 @@ export function Super(this: Printer) { function isDecoratorMemberExpression( node: t.Expression | t.V8IntrinsicIdentifier, -) { +): boolean { switch (node.type) { case "Identifier": return true; @@ -218,25 +216,27 @@ export function Import(this: Printer) { this.word("import"); } -function buildYieldAwait(keyword: string) { - return function (this: Printer, node: any) { - this.word(keyword); - - if (node.delegate) { - this.token("*"); - } +export function AwaitExpression(this: Printer, node: t.AwaitExpression) { + this.word("await"); - if (node.argument) { - this.space(); - const terminatorState = this.startTerminatorless(); - this.print(node.argument, node); - this.endTerminatorless(terminatorState); - } - }; + if (node.argument) { + this.space(); + this.printTerminatorless(node.argument, node, false); + } } -export const YieldExpression = buildYieldAwait("yield"); -export const AwaitExpression = buildYieldAwait("await"); +export function YieldExpression(this: Printer, node: t.YieldExpression) { + this.word("yield"); + + if (node.delegate) { + this.token("*"); + } + + if (node.argument) { + this.space(); + this.printTerminatorless(node.argument, node, false); + } +} export function EmptyStatement(this: Printer) { this.semicolon(true /* force */); @@ -265,7 +265,7 @@ export function AssignmentPattern(this: Printer, node: t.AssignmentPattern) { export function AssignmentExpression( this: Printer, node: t.AssignmentExpression, - parent: any, + parent: t.Node, ) { // Somewhere inside a for statement `init` node but doesn't usually // needs a paren except for `in` expressions: `for (a in b ? a : b;;)` diff --git a/packages/babel-generator/src/generators/flow.ts b/packages/babel-generator/src/generators/flow.ts index f2ca2a6700c5..2a7af18ac267 100644 --- a/packages/babel-generator/src/generators/flow.ts +++ b/packages/babel-generator/src/generators/flow.ts @@ -48,7 +48,7 @@ export function DeclareClass( export function DeclareFunction( this: Printer, node: t.DeclareFunction, - parent: any, + parent: t.Node, ) { if (!isDeclareExportDeclaration(parent)) { this.word("declare"); @@ -118,7 +118,7 @@ export function DeclareTypeAlias(this: Printer, node: t.DeclareTypeAlias) { export function DeclareOpaqueType( this: Printer, node: t.DeclareOpaqueType, - parent: any, + parent: t.Node, ) { if (!isDeclareExportDeclaration(parent)) { this.word("declare"); @@ -130,7 +130,7 @@ export function DeclareOpaqueType( export function DeclareVariable( this: Printer, node: t.DeclareVariable, - parent: any, + parent: t.Node, ) { if (!isDeclareExportDeclaration(parent)) { this.word("declare"); @@ -174,7 +174,7 @@ export function EnumDeclaration(this: Printer, node: t.EnumDeclaration) { } function enumExplicitType( - context: any, + context: Printer, name: string, hasExplicitType: boolean, ) { @@ -187,7 +187,7 @@ function enumExplicitType( context.space(); } -function enumBody(context: any, node: any) { +function enumBody(context: Printer, node: t.EnumBody) { const { members } = node; context.token("{"); context.indent(); @@ -236,7 +236,10 @@ export function EnumDefaultedMember( this.token(","); } -function enumInitializedMember(context: any, node: any) { +function enumInitializedMember( + context: Printer, + node: t.EnumBooleanMember | t.EnumNumberMember | t.EnumStringMember, +) { const { id, init } = node; context.print(id, node); context.space(); @@ -258,7 +261,10 @@ export function EnumStringMember(this: Printer, node: t.EnumStringMember) { enumInitializedMember(this, node); } -function FlowExportDeclaration(this: Printer, node: any) { +function FlowExportDeclaration( + this: Printer, + node: t.DeclareExportDeclaration, +) { if (node.declaration) { const declar = node.declaration; this.print(declar, node); @@ -384,7 +390,16 @@ export function _interfaceish( this.print(node.body, node); } -export function _variance(this: Printer, node) { +export function _variance( + this: Printer, + node: + | t.TypeParameter + | t.ObjectTypeIndexer + | t.ObjectTypeProperty + | t.ClassProperty + | t.ClassPrivateProperty + | t.ClassAccessorProperty, +) { if (node.variance) { if (node.variance.kind === "plus") { this.token("+"); diff --git a/packages/babel-generator/src/generators/jsx.ts b/packages/babel-generator/src/generators/jsx.ts index fb3fd8356b85..3919626f08cf 100644 --- a/packages/babel-generator/src/generators/jsx.ts +++ b/packages/babel-generator/src/generators/jsx.ts @@ -54,7 +54,7 @@ export function JSXSpreadChild(this: Printer, node: t.JSXSpreadChild) { export function JSXText(this: Printer, node: t.JSXText) { const raw = this.getPossibleRaw(node); - if (raw != null) { + if (raw !== undefined) { this.token(raw); } else { this.token(node.value); diff --git a/packages/babel-generator/src/generators/methods.ts b/packages/babel-generator/src/generators/methods.ts index 42c6d3d5b20f..d86c676bdf3b 100644 --- a/packages/babel-generator/src/generators/methods.ts +++ b/packages/babel-generator/src/generators/methods.ts @@ -2,7 +2,10 @@ import type Printer from "../printer"; import { isIdentifier } from "@babel/types"; import type * as t from "@babel/types"; -export function _params(this: Printer, node: any) { +export function _params( + this: Printer, + node: t.Function | t.TSDeclareMethod | t.TSDeclareFunction, +) { this.print(node.typeParameters, node); this.token("("); this._parameters(node.params, node); @@ -11,7 +14,17 @@ export function _params(this: Printer, node: any) { this.print(node.returnType, node); } -export function _parameters(this: Printer, parameters, parent) { +export function _parameters( + this: Printer, + parameters: t.Function["params"], + parent: + | t.Function + | t.TSIndexSignature + | t.TSDeclareMethod + | t.TSDeclareFunction + | t.TSFunctionType + | t.TSConstructorType, +) { for (let i = 0; i < parameters.length; i++) { this._param(parameters[i], parent); @@ -22,14 +35,34 @@ export function _parameters(this: Printer, parameters, parent) { } } -export function _param(this: Printer, parameter, parent?) { +export function _param( + this: Printer, + parameter: t.Identifier | t.RestElement | t.Pattern | t.TSParameterProperty, + parent?: + | t.Function + | t.TSIndexSignature + | t.TSDeclareMethod + | t.TSDeclareFunction + | t.TSFunctionType + | t.TSConstructorType, +) { this.printJoin(parameter.decorators, parameter); this.print(parameter, parent); - if (parameter.optional) this.token("?"); // TS / flow - this.print(parameter.typeAnnotation, parameter); // TS / flow + if ( + // @ts-expect-error optional is not in TSParameterProperty + parameter.optional + ) { + this.token("?"); // TS / flow + } + + this.print( + // @ts-expect-error typeAnnotation is not in TSParameterProperty + parameter.typeAnnotation, + parameter, + ); // TS / flow } -export function _methodHead(this: Printer, node: any) { +export function _methodHead(this: Printer, node: t.Method | t.TSDeclareMethod) { const kind = node.kind; const key = node.key; @@ -45,7 +78,11 @@ export function _methodHead(this: Printer, node: any) { this.space(); } - if (kind === "method" || kind === "init") { + if ( + kind === "method" || + // @ts-ignore Fixme: kind: "init" is not defined + kind === "init" + ) { if (node.generator) { this.token("*"); } @@ -59,7 +96,10 @@ export function _methodHead(this: Printer, node: any) { this.print(key, node); } - if (node.optional) { + if ( + // @ts-expect-error optional is not in ObjectMethod + node.optional + ) { // TS this.token("?"); } @@ -67,7 +107,13 @@ export function _methodHead(this: Printer, node: any) { this._params(node); } -export function _predicate(this: Printer, node: any) { +export function _predicate( + this: Printer, + node: + | t.FunctionDeclaration + | t.FunctionExpression + | t.ArrowFunctionExpression, +) { if (node.predicate) { if (!node.returnType) { this.token(":"); @@ -77,7 +123,10 @@ export function _predicate(this: Printer, node: any) { } } -export function _functionHead(this: Printer, node: any) { +export function _functionHead( + this: Printer, + node: t.FunctionDeclaration | t.FunctionExpression | t.TSDeclareFunction, +) { if (node.async) { this.word("async"); this.space(); @@ -92,7 +141,9 @@ export function _functionHead(this: Printer, node: any) { } this._params(node); - this._predicate(node); + if (node.type !== "TSDeclareFunction") { + this._predicate(node); + } } export function FunctionExpression(this: Printer, node: t.FunctionExpression) { diff --git a/packages/babel-generator/src/generators/modules.ts b/packages/babel-generator/src/generators/modules.ts index aeaec676f2ba..ccd6c382d6cf 100644 --- a/packages/babel-generator/src/generators/modules.ts +++ b/packages/babel-generator/src/generators/modules.ts @@ -98,28 +98,6 @@ export function ExportNamedDeclaration( this.word("export"); this.space(); - ExportDeclaration.apply(this, arguments); -} - -export function ExportDefaultDeclaration( - this: Printer, - node: t.ExportDefaultDeclaration, -) { - if ( - this.format.decoratorsBeforeExport && - isClassDeclaration(node.declaration) - ) { - this.printJoin(node.declaration.decorators, node); - } - - this.word("export"); - this.space(); - this.word("default"); - this.space(); - ExportDeclaration.apply(this, arguments); -} - -function ExportDeclaration(this: Printer, node: any) { if (node.declaration) { const declar = node.declaration; this.print(declar, node); @@ -173,6 +151,26 @@ function ExportDeclaration(this: Printer, node: any) { } } +export function ExportDefaultDeclaration( + this: Printer, + node: t.ExportDefaultDeclaration, +) { + if ( + this.format.decoratorsBeforeExport && + isClassDeclaration(node.declaration) + ) { + this.printJoin(node.declaration.decorators, node); + } + + this.word("export"); + this.space(); + this.word("default"); + this.space(); + const declar = node.declaration; + this.print(declar, node); + if (!isStatement(declar)) this.semicolon(); +} + export function ImportDeclaration(this: Printer, node: t.ImportDeclaration) { this.word("import"); this.space(); diff --git a/packages/babel-generator/src/generators/statements.ts b/packages/babel-generator/src/generators/statements.ts index 52b1eea6bf19..aa0fad48867e 100644 --- a/packages/babel-generator/src/generators/statements.ts +++ b/packages/babel-generator/src/generators/statements.ts @@ -50,9 +50,14 @@ export function IfStatement(this: Printer, node: t.IfStatement) { } // Recursively get the last statement. -function getLastStatement(statement) { - if (!isStatement(statement.body)) return statement; - return getLastStatement(statement.body); +function getLastStatement(statement: t.Statement): t.Statement { + // @ts-ignore: If statement.body is empty or not a Node, isStatement will return false + const { body } = statement; + if (isStatement(body) === false) { + return statement; + } + + return getLastStatement(body); } export function ForStatement(this: Printer, node: t.ForStatement) { @@ -89,27 +94,26 @@ export function WhileStatement(this: Printer, node: t.WhileStatement) { this.printBlock(node); } -const buildForXStatement = function (op) { - return function (this: Printer, node: any) { - this.word("for"); - this.space(); - if (op === "of" && node.await) { - this.word("await"); - this.space(); - } - this.token("("); - this.print(node.left, node); - this.space(); - this.word(op); +function ForXStatement(this: Printer, node: t.ForXStatement) { + this.word("for"); + this.space(); + const isForOf = node.type === "ForOfStatement"; + if (isForOf && node.await) { + this.word("await"); this.space(); - this.print(node.right, node); - this.token(")"); - this.printBlock(node); - }; -}; + } + this.token("("); + this.print(node.left, node); + this.space(); + this.word(isForOf ? "of" : "in"); + this.space(); + this.print(node.right, node); + this.token(")"); + this.printBlock(node); +} -export const ForInStatement = buildForXStatement("in"); -export const ForOfStatement = buildForXStatement("of"); +export const ForInStatement = ForXStatement; +export const ForOfStatement = ForXStatement; export function DoWhileStatement(this: Printer, node: t.DoWhileStatement) { this.word("do"); @@ -124,27 +128,39 @@ export function DoWhileStatement(this: Printer, node: t.DoWhileStatement) { this.semicolon(); } -function buildLabelStatement(prefix, key = "label") { - return function (this: Printer, node: any) { - this.word(prefix); - - const label = node[key]; - if (label) { - this.space(); - const isLabel = key == "label"; - const terminatorState = this.startTerminatorless(isLabel); - this.print(label, node); - this.endTerminatorless(terminatorState); - } +function printStatementAfterKeyword( + printer: Printer, + node: t.Node, + parent: t.Node, + isLabel: boolean, +) { + if (node) { + printer.space(); + printer.printTerminatorless(node, parent, isLabel); + } - this.semicolon(); - }; + printer.semicolon(); } -export const ContinueStatement = buildLabelStatement("continue"); -export const ReturnStatement = buildLabelStatement("return", "argument"); -export const BreakStatement = buildLabelStatement("break"); -export const ThrowStatement = buildLabelStatement("throw", "argument"); +export function BreakStatement(this: Printer, node: t.ContinueStatement) { + this.word("break"); + printStatementAfterKeyword(this, node.label, node, true); +} + +export function ContinueStatement(this: Printer, node: t.ContinueStatement) { + this.word("continue"); + printStatementAfterKeyword(this, node.label, node, true); +} + +export function ReturnStatement(this: Printer, node: t.ReturnStatement) { + this.word("return"); + printStatementAfterKeyword(this, node.argument, node, false); +} + +export function ThrowStatement(this: Printer, node: t.ThrowStatement) { + this.word("throw"); + printStatementAfterKeyword(this, node.argument, node, false); +} export function LabeledStatement(this: Printer, node: t.LabeledStatement) { this.print(node.label, node); diff --git a/packages/babel-generator/src/generators/template-literals.ts b/packages/babel-generator/src/generators/template-literals.ts index 14e5a4e03b7f..48c9e5589ebb 100644 --- a/packages/babel-generator/src/generators/template-literals.ts +++ b/packages/babel-generator/src/generators/template-literals.ts @@ -13,7 +13,7 @@ export function TaggedTemplateExpression( export function TemplateElement( this: Printer, node: t.TemplateElement, - parent: any, + parent: t.TemplateLiteral, ) { const isFirst = parent.quasis[0] === node; const isLast = parent.quasis[parent.quasis.length - 1] === node; diff --git a/packages/babel-generator/src/generators/types.ts b/packages/babel-generator/src/generators/types.ts index 779df2d69f8c..552b81faade6 100644 --- a/packages/babel-generator/src/generators/types.ts +++ b/packages/babel-generator/src/generators/types.ts @@ -201,7 +201,7 @@ export function NumericLiteral(this: Printer, node: t.NumericLiteral) { export function StringLiteral(this: Printer, node: t.StringLiteral) { const raw = this.getPossibleRaw(node); - if (!this.format.minified && raw != null) { + if (!this.format.minified && raw !== undefined) { this.token(raw); return; } @@ -221,7 +221,7 @@ export function StringLiteral(this: Printer, node: t.StringLiteral) { export function BigIntLiteral(this: Printer, node: t.BigIntLiteral) { const raw = this.getPossibleRaw(node); - if (!this.format.minified && raw != null) { + if (!this.format.minified && raw !== undefined) { this.word(raw); return; } @@ -230,7 +230,7 @@ export function BigIntLiteral(this: Printer, node: t.BigIntLiteral) { export function DecimalLiteral(this: Printer, node: t.DecimalLiteral) { const raw = this.getPossibleRaw(node); - if (!this.format.minified && raw != null) { + if (!this.format.minified && raw !== undefined) { this.word(raw); return; } diff --git a/packages/babel-generator/src/generators/typescript.ts b/packages/babel-generator/src/generators/typescript.ts index 4aa982e8741d..0ffce6004dd3 100644 --- a/packages/babel-generator/src/generators/typescript.ts +++ b/packages/babel-generator/src/generators/typescript.ts @@ -131,7 +131,10 @@ export function TSPropertySignature( this.token(";"); } -export function tsPrintPropertyOrMethodName(this: Printer, node) { +export function tsPrintPropertyOrMethodName( + this: Printer, + node: t.TSPropertySignature | t.TSMethodSignature, +) { if (node.computed) { this.token("["); } @@ -232,13 +235,14 @@ export function TSConstructorType(this: Printer, node: t.TSConstructorType) { export function tsPrintFunctionOrConstructorType( this: Printer, - // todo: missing type FunctionOrConstructorType - node: any, + node: t.TSFunctionType | t.TSConstructorType, ) { const { typeParameters } = node; const parameters = process.env.BABEL_8_BREAKING - ? node.params - : node.parameters; + ? // @ts-ignore Babel 8 AST shape + node.params + : // @ts-ignore Babel 7 AST shape + node.parameters; this.print(typeParameters, node); this.token("("); this._parameters(parameters, node); @@ -247,8 +251,10 @@ export function tsPrintFunctionOrConstructorType( this.token("=>"); this.space(); const returnType = process.env.BABEL_8_BREAKING - ? node.returnType - : node.typeAnnotation; + ? // @ts-ignore Babel 8 AST shape + node.returnType + : // @ts-ignore Babel 7 AST shape + node.typeAnnotation; this.print(returnType.typeAnnotation, node); } @@ -287,26 +293,26 @@ export function TSTypeLiteral(this: Printer, node: t.TSTypeLiteral) { export function tsPrintTypeLiteralOrInterfaceBody( this: Printer, - members, - node, + members: t.TSTypeElement[], + node: t.TSType | t.TSInterfaceBody, ) { - this.tsPrintBraced(members, node); + tsPrintBraced(this, members, node); } -export function tsPrintBraced(this: Printer, members, node) { - this.token("{"); +function tsPrintBraced(printer: Printer, members: t.Node[], node: t.Node) { + printer.token("{"); if (members.length) { - this.indent(); - this.newline(); + printer.indent(); + printer.newline(); for (const member of members) { - this.print(member, node); + printer.print(member, node); //this.token(sep); - this.newline(); + printer.newline(); } - this.dedent(); - this.rightBrace(); + printer.dedent(); + printer.rightBrace(); } else { - this.token("}"); + printer.token("}"); } } @@ -340,15 +346,19 @@ export function TSNamedTupleMember(this: Printer, node: t.TSNamedTupleMember) { } export function TSUnionType(this: Printer, node: t.TSUnionType) { - this.tsPrintUnionOrIntersectionType(node, "|"); + tsPrintUnionOrIntersectionType(this, node, "|"); } export function TSIntersectionType(this: Printer, node: t.TSIntersectionType) { - this.tsPrintUnionOrIntersectionType(node, "&"); + tsPrintUnionOrIntersectionType(this, node, "&"); } -export function tsPrintUnionOrIntersectionType(this: Printer, node: any, sep) { - this.printJoin(node.types, node, { +function tsPrintUnionOrIntersectionType( + printer: Printer, + node: t.TSUnionType | t.TSIntersectionType, + sep: "|" | "&", +) { + printer.printJoin(node.types, node, { separator() { this.space(); this.token(sep); @@ -445,7 +455,7 @@ export function TSMappedType(this: Printer, node: t.TSMappedType) { this.token("}"); } -function tokenIfPlusMinus(self, tok) { +function tokenIfPlusMinus(self: Printer, tok: true | "+" | "-") { if (tok !== true) { self.token(tok); } @@ -550,7 +560,7 @@ export function TSEnumDeclaration(this: Printer, node: t.TSEnumDeclaration) { this.space(); this.print(id, node); this.space(); - this.tsPrintBraced(members, node); + tsPrintBraced(this, members, node); } export function TSEnumMember(this: Printer, node: t.TSEnumMember) { @@ -599,7 +609,7 @@ export function TSModuleDeclaration( } export function TSModuleBlock(this: Printer, node: t.TSModuleBlock) { - this.tsPrintBraced(node.body, node); + tsPrintBraced(this, node.body, node); } export function TSImportType(this: Printer, node: t.TSImportType) { @@ -690,7 +700,17 @@ export function tsPrintSignatureDeclarationBase(this: Printer, node: any) { this.print(returnType, node); } -export function tsPrintClassMemberModifiers(this: Printer, node: any, isField) { +export function tsPrintClassMemberModifiers( + this: Printer, + node: + | t.ClassProperty + | t.ClassAccessorProperty + | t.ClassMethod + | t.ClassPrivateMethod + | t.TSDeclareMethod, +) { + const isField = + node.type === "ClassAccessorProperty" || node.type === "ClassProperty"; if (isField && node.declare) { this.word("declare"); this.space(); diff --git a/packages/babel-generator/src/index.ts b/packages/babel-generator/src/index.ts index 67d299411bff..c6f609c7e831 100644 --- a/packages/babel-generator/src/index.ts +++ b/packages/babel-generator/src/index.ts @@ -3,6 +3,10 @@ import Printer from "./printer"; import type * as t from "@babel/types"; import type { Opts as jsescOptions } from "jsesc"; import type { Format } from "./printer"; +import type { + RecordAndTuplePluginOptions, + PipelineOperatorPluginOptions, +} from "@babel/parser"; import type { DecodedSourceMap, Mapping } from "@jridgewell/gen-mapping"; /** @@ -13,12 +17,8 @@ import type { DecodedSourceMap, Mapping } from "@jridgewell/gen-mapping"; class Generator extends Printer { constructor( ast: t.Node, - opts: { - sourceFileName?: string; - sourceMaps?: boolean; - sourceRoot?: string; - } = {}, - code, + opts: GeneratorOptions = {}, + code: string | { [filename: string]: string }, ) { const format = normalizeOptions(code, opts); const map = opts.sourceMaps ? new SourceMap(opts, code) : null; @@ -47,7 +47,10 @@ class Generator extends Printer { * - If `opts.compact = "auto"` and the code is over 500KB, `compact` will be set to `true`. */ -function normalizeOptions(code, opts): Format { +function normalizeOptions( + code: string | { [filename: string]: string }, + opts: GeneratorOptions, +): Format { const format: Format = { auxiliaryCommentBefore: opts.auxiliaryCommentBefore, auxiliaryCommentAfter: opts.auxiliaryCommentAfter, @@ -183,6 +186,7 @@ export interface GeneratorOptions { /** * Set to true to run jsesc with "json": true to print "\u00A9" vs. "©"; + * @deprecated use `jsescOptions: { json: true }` instead */ jsonCompatibleStrings?: boolean; @@ -200,24 +204,24 @@ export interface GeneratorOptions { /** * For use with the recordAndTuple token. */ - recordAndTupleSyntaxType?: "hash" | "bar"; + recordAndTupleSyntaxType?: RecordAndTuplePluginOptions["syntaxType"]; /** * For use with the Hack-style pipe operator. * Changes what token is used for pipe bodies’ topic references. */ - topicToken?: "^^" | "@@" | "^" | "%" | "#"; + topicToken?: PipelineOperatorPluginOptions["topicToken"]; } export interface GeneratorResult { code: string; map: { version: number; - sources: string[]; - names: string[]; + sources: readonly string[]; + names: readonly string[]; sourceRoot?: string; - sourcesContent?: string[]; + sourcesContent?: readonly string[]; mappings: string; - file: string; + file?: string; } | null; decodedMap: DecodedSourceMap | undefined; rawMappings: Mapping[] | undefined; diff --git a/packages/babel-generator/src/node/index.ts b/packages/babel-generator/src/node/index.ts index e6b7184f6dc4..09e2bbdc79cd 100644 --- a/packages/babel-generator/src/node/index.ts +++ b/packages/babel-generator/src/node/index.ts @@ -7,11 +7,28 @@ import { isMemberExpression, isNewExpression, } from "@babel/types"; - -function expandAliases(obj) { - const newObj = {}; - - function add(type, func) { +import type * as t from "@babel/types"; +import type { WhitespaceObject } from "./whitespace"; + +export type NodeHandlers = { + [K in string]?: ( + node: K extends t.Node["type"] ? Extract : t.Node, + // todo: + // node: K extends keyof typeof t + // ? Extract + // : t.Node, + parent: t.Node, + stack: t.Node[], + ) => R; +}; + +function expandAliases(obj: NodeHandlers) { + const newObj: NodeHandlers = {}; + + function add( + type: string, + func: (node: t.Node, parent: t.Node, stack: t.Node[]) => R, + ) { const fn = newObj[type]; newObj[type] = fn ? function (node, parent, stack) { @@ -42,12 +59,17 @@ const expandedParens = expandAliases(parens); const expandedWhitespaceNodes = expandAliases(whitespace.nodes); const expandedWhitespaceList = expandAliases(whitespace.list); -function find(obj, node, parent, printStack?) { +function find( + obj: NodeHandlers, + node: t.Node, + parent: t.Node, + printStack?: t.Node[], +): R | null { const fn = obj[node.type]; return fn ? fn(node, parent, printStack) : null; } -function isOrHasCallExpression(node) { +function isOrHasCallExpression(node: t.Node): boolean { if (isCallExpression(node)) { return true; } @@ -55,14 +77,22 @@ function isOrHasCallExpression(node) { return isMemberExpression(node) && isOrHasCallExpression(node.object); } -export function needsWhitespace(node, parent, type) { - if (!node) return 0; +export function needsWhitespace( + node: t.Node, + parent: t.Node, + type: "before" | "after", +): boolean { + if (!node) return false; if (isExpressionStatement(node)) { node = node.expression; } - let linesInfo = find(expandedWhitespaceNodes, node, parent); + let linesInfo: WhitespaceObject | null | boolean = find( + expandedWhitespaceNodes, + node, + parent, + ); if (!linesInfo) { const items = find(expandedWhitespaceList, node, parent); @@ -75,21 +105,25 @@ export function needsWhitespace(node, parent, type) { } if (typeof linesInfo === "object" && linesInfo !== null) { - return linesInfo[type] || 0; + return linesInfo[type] || false; } - return 0; + return false; } -export function needsWhitespaceBefore(node, parent) { +export function needsWhitespaceBefore(node: t.Node, parent: t.Node) { return needsWhitespace(node, parent, "before"); } -export function needsWhitespaceAfter(node, parent) { +export function needsWhitespaceAfter(node: t.Node, parent: t.Node) { return needsWhitespace(node, parent, "after"); } -export function needsParens(node, parent, printStack?) { +export function needsParens( + node: t.Node, + parent: t.Node, + printStack?: t.Node[], +) { if (!parent) return false; if (isNewExpression(parent) && parent.callee === node) { diff --git a/packages/babel-generator/src/node/parentheses.ts b/packages/babel-generator/src/node/parentheses.ts index df255d85c61a..89ad23b18479 100644 --- a/packages/babel-generator/src/node/parentheses.ts +++ b/packages/babel-generator/src/node/parentheses.ts @@ -7,7 +7,7 @@ import { isBinaryExpression, isUpdateExpression, isCallExpression, - isClassDeclaration, + isClass, isClassExpression, isConditional, isConditionalExpression, @@ -54,6 +54,7 @@ import type * as t from "@babel/types"; const PRECEDENCE = { "||": 0, "??": 0, + "|>": 0, "&&": 1, "|": 2, "^": 3, @@ -79,11 +80,12 @@ const PRECEDENCE = { "**": 10, }; -const isClassExtendsClause = (node: any, parent: any): boolean => - (isClassDeclaration(parent) || isClassExpression(parent)) && - parent.superClass === node; +const isClassExtendsClause = ( + node: t.Node, + parent: t.Node, +): parent is t.Class => isClass(parent, { superClass: node }); -const hasPostfixPart = (node: any, parent: any) => +const hasPostfixPart = (node: t.Node, parent: t.Node) => ((isMemberExpression(parent) || isOptionalMemberExpression(parent)) && parent.object === node) || ((isCallExpression(parent) || @@ -93,14 +95,17 @@ const hasPostfixPart = (node: any, parent: any) => (isTaggedTemplateExpression(parent) && parent.tag === node) || isTSNonNullExpression(parent); -export function NullableTypeAnnotation(node: any, parent: any): boolean { +export function NullableTypeAnnotation( + node: t.NullableTypeAnnotation, + parent: t.Node, +): boolean { return isArrayTypeAnnotation(parent); } export function FunctionTypeAnnotation( - node: any, - parent: any, - printStack: Array, + node: t.FunctionTypeAnnotation, + parent: t.Node, + printStack: Array, ): boolean { return ( // (() => A) | (() => B) @@ -116,14 +121,17 @@ export function FunctionTypeAnnotation( ); } -export function UpdateExpression(node: any, parent: any): boolean { +export function UpdateExpression( + node: t.UpdateExpression, + parent: t.Node, +): boolean { return hasPostfixPart(node, parent) || isClassExtendsClause(node, parent); } export function ObjectExpression( - node: any, - parent: any, - printStack: Array, + node: t.ObjectExpression, + parent: t.Node, + printStack: Array, ): boolean { return isFirstInContext(printStack, { expressionStatement: true, @@ -132,9 +140,9 @@ export function ObjectExpression( } export function DoExpression( - node: any, - parent: any, - printStack: Array, + node: t.DoExpression, + parent: t.Node, + printStack: Array, ): boolean { // `async do` can start an expression statement return ( @@ -142,7 +150,7 @@ export function DoExpression( ); } -export function Binary(node: any, parent: any): boolean { +export function Binary(node: t.BinaryExpression, parent: t.Node): boolean { if ( node.operator === "**" && isBinaryExpression(parent, { operator: "**" }) @@ -181,7 +189,10 @@ export function Binary(node: any, parent: any): boolean { } } -export function UnionTypeAnnotation(node: any, parent: any): boolean { +export function UnionTypeAnnotation( + node: t.UnionTypeAnnotation, + parent: t.Node, +): boolean { return ( isArrayTypeAnnotation(parent) || isNullableTypeAnnotation(parent) || @@ -192,7 +203,10 @@ export function UnionTypeAnnotation(node: any, parent: any): boolean { export { UnionTypeAnnotation as IntersectionTypeAnnotation }; -export function OptionalIndexedAccessType(node: any, parent: any): boolean { +export function OptionalIndexedAccessType( + node: t.OptionalIndexedAccessType, + parent: t.Node, +): boolean { return isIndexedAccessType(parent, { objectType: node }); } @@ -204,7 +218,7 @@ export function TSTypeAssertion() { return true; } -export function TSUnionType(node: any, parent: any): boolean { +export function TSUnionType(node: t.TSUnionType, parent: t.Node): boolean { return ( isTSArrayType(parent) || isTSOptionalType(parent) || @@ -216,7 +230,7 @@ export function TSUnionType(node: any, parent: any): boolean { export { TSUnionType as TSIntersectionType }; -export function TSInferType(node: any, parent: any): boolean { +export function TSInferType(node: t.TSInferType, parent: t.Node): boolean { return isTSArrayType(parent) || isTSOptionalType(parent); } @@ -233,7 +247,10 @@ export function TSInstantiationExpression( ); } -export function BinaryExpression(node: any, parent: any): boolean { +export function BinaryExpression( + node: t.BinaryExpression, + parent: t.Node, +): boolean { // let i = (1 in []); // for ((1 in []);;); return ( @@ -241,7 +258,10 @@ export function BinaryExpression(node: any, parent: any): boolean { ); } -export function SequenceExpression(node: any, parent: any): boolean { +export function SequenceExpression( + node: t.SequenceExpression, + parent: t.Node, +): boolean { if ( // Although parentheses wouldn"t hurt around sequence // expressions in the head of for loops, traditional style @@ -264,7 +284,10 @@ export function SequenceExpression(node: any, parent: any): boolean { return true; } -export function YieldExpression(node: any, parent: any): boolean { +export function YieldExpression( + node: t.YieldExpression, + parent: t.Node, +): boolean { return ( isBinary(parent) || isUnaryLike(parent) || @@ -278,9 +301,9 @@ export function YieldExpression(node: any, parent: any): boolean { export { YieldExpression as AwaitExpression }; export function ClassExpression( - node: any, - parent: any, - printStack: Array, + node: t.ClassExpression, + parent: t.Node, + printStack: Array, ): boolean { return isFirstInContext(printStack, { expressionStatement: true, @@ -288,7 +311,14 @@ export function ClassExpression( }); } -export function UnaryLike(node: any, parent: any): boolean { +export function UnaryLike( + node: + | t.UnaryLike + | t.ArrowFunctionExpression + | t.ConditionalExpression + | t.AssignmentExpression, + parent: t.Node, +): boolean { return ( hasPostfixPart(node, parent) || isBinaryExpression(parent, { operator: "**", left: node }) || @@ -297,9 +327,9 @@ export function UnaryLike(node: any, parent: any): boolean { } export function FunctionExpression( - node: any, - parent: any, - printStack: Array, + node: t.FunctionExpression, + parent: t.Node, + printStack: Array, ): boolean { return isFirstInContext(printStack, { expressionStatement: true, @@ -307,11 +337,20 @@ export function FunctionExpression( }); } -export function ArrowFunctionExpression(node: any, parent: any): boolean { +export function ArrowFunctionExpression( + node: t.ArrowFunctionExpression, + parent: t.Node, +): boolean { return isExportDeclaration(parent) || ConditionalExpression(node, parent); } -export function ConditionalExpression(node: any, parent?): boolean { +export function ConditionalExpression( + node: + | t.ConditionalExpression + | t.ArrowFunctionExpression + | t.AssignmentExpression, + parent?: t.Node, +): boolean { if ( isUnaryLike(parent) || isBinary(parent) || @@ -326,7 +365,10 @@ export function ConditionalExpression(node: any, parent?): boolean { return UnaryLike(node, parent); } -export function OptionalMemberExpression(node: any, parent: any): boolean { +export function OptionalMemberExpression( + node: t.OptionalMemberExpression, + parent: t.Node, +): boolean { return ( isCallExpression(parent, { callee: node }) || isMemberExpression(parent, { object: node }) @@ -335,7 +377,10 @@ export function OptionalMemberExpression(node: any, parent: any): boolean { export { OptionalMemberExpression as OptionalCallExpression }; -export function AssignmentExpression(node: any, parent: any): boolean { +export function AssignmentExpression( + node: t.AssignmentExpression, + parent: t.Node, +): boolean { if (isObjectPattern(node.left)) { return true; } else { @@ -343,7 +388,10 @@ export function AssignmentExpression(node: any, parent: any): boolean { } } -export function LogicalExpression(node: any, parent: any): boolean { +export function LogicalExpression( + node: t.LogicalExpression, + parent: t.Node, +): boolean { switch (node.operator) { case "||": if (!isLogicalExpression(parent)) return false; diff --git a/packages/babel-generator/src/node/whitespace.ts b/packages/babel-generator/src/node/whitespace.ts index c9a83a2207e0..8bd234b8045e 100644 --- a/packages/babel-generator/src/node/whitespace.ts +++ b/packages/babel-generator/src/node/whitespace.ts @@ -15,8 +15,10 @@ import { isStringLiteral, } from "@babel/types"; +import type { NodeHandlers } from "./index"; + import type * as t from "@babel/types"; -type WhitespaceObject = { +export type WhitespaceObject = { before?: boolean; after?: boolean; }; @@ -72,7 +74,7 @@ function isHelper(node: t.Node): boolean { } } -function isType(node) { +function isType(node: t.Node) { return ( isLiteral(node) || isObjectExpression(node) || @@ -86,16 +88,7 @@ function isType(node) { * Tests for node types that need whitespace. */ -export const nodes: { - [K in string]?: ( - node: K extends t.Node["type"] ? Extract : t.Node, - // todo: - // node: K extends keyof typeof t - // ? Extract - // : t.Node, - parent: t.Node, - ) => void; -} = { +export const nodes: NodeHandlers = { /** * Test if AssignmentExpression needs whitespace. */ @@ -221,7 +214,7 @@ nodes.ObjectProperty = nodes.ObjectMethod = function ( node: t.ObjectProperty | t.ObjectTypeProperty | t.ObjectMethod, - parent: any, + parent: t.ObjectExpression, ): WhitespaceObject | undefined | null { if (parent.properties[0] === node) { return { @@ -232,7 +225,7 @@ nodes.ObjectProperty = nodes.ObjectTypeCallProperty = function ( node: t.ObjectTypeCallProperty, - parent: any, + parent: t.ObjectTypeAnnotation, ): WhitespaceObject | undefined | null { if (parent.callProperties[0] === node && !parent.properties?.length) { return { @@ -243,7 +236,7 @@ nodes.ObjectTypeCallProperty = function ( nodes.ObjectTypeIndexer = function ( node: t.ObjectTypeIndexer, - parent: any, + parent: t.ObjectTypeAnnotation, ): WhitespaceObject | undefined | null { if ( parent.indexers[0] === node && @@ -258,7 +251,7 @@ nodes.ObjectTypeIndexer = function ( nodes.ObjectTypeInternalSlot = function ( node: t.ObjectTypeInternalSlot, - parent: any, + parent: t.ObjectTypeAnnotation, ): WhitespaceObject | undefined | null { if ( parent.internalSlots[0] === node && @@ -276,7 +269,7 @@ nodes.ObjectTypeInternalSlot = function ( * Returns lists from node types that need whitespace. */ -export const list = { +export const list: NodeHandlers = { /** * Return VariableDeclaration declarations init properties. */ @@ -314,16 +307,13 @@ export const list = { ["LabeledStatement", true], ["SwitchStatement", true], ["TryStatement", true], - ] as Array<[string, any]> + ] as const ).forEach(function ([type, amounts]) { - if (typeof amounts === "boolean") { - amounts = { after: amounts, before: amounts }; - } [type as string] .concat(FLIPPED_ALIAS_KEYS[type] || []) .forEach(function (type) { nodes[type] = function () { - return amounts; + return { after: amounts, before: amounts }; }; }); }); diff --git a/packages/babel-generator/src/printer.ts b/packages/babel-generator/src/printer.ts index 1bde04f31c63..227fabe71aa6 100644 --- a/packages/babel-generator/src/printer.ts +++ b/packages/babel-generator/src/printer.ts @@ -1,7 +1,13 @@ import Buffer from "./buffer"; +import type { Loc } from "./buffer"; import * as n from "./node"; import { isProgram, isFile, isEmptyStatement } from "@babel/types"; import type * as t from "@babel/types"; +import type { + RecordAndTuplePluginOptions, + PipelineOperatorPluginOptions, +} from "@babel/parser"; +import type { Opts as jsescOptions } from "jsesc"; import * as generatorFunctions from "./generators"; import type SourceMap from "./source-map"; @@ -30,16 +36,35 @@ export type Format = { base: number; }; decoratorsBeforeExport: boolean; - recordAndTupleSyntaxType: "bar" | "hash"; - jsescOption; - jsonCompatibleStrings?; + recordAndTupleSyntaxType: RecordAndTuplePluginOptions["syntaxType"]; + jsescOption: jsescOptions; + jsonCompatibleStrings?: boolean; /** * For use with the Hack-style pipe operator. * Changes what token is used for pipe bodies’ topic references. */ - topicToken?: "#"; + topicToken?: PipelineOperatorPluginOptions["topicToken"]; }; +interface AddNewlinesOptions { + addNewlines(leading: boolean, node: t.Node): number; +} + +interface PrintSequenceOptions extends Partial { + statement?: boolean; + indent?: boolean; +} + +interface PrintListOptions { + separator?: (this: Printer) => void; + statement?: boolean; + indent?: boolean; +} + +type PrintJoinOptions = PrintListOptions & + PrintSequenceOptions & { + iterator?: (node: t.Node, index: number) => void; + }; class Printer { constructor(format: Format, map: SourceMap) { this.format = format; @@ -53,14 +78,14 @@ class Printer { _printStack: Array = []; _indent: number = 0; _insideAux: boolean = false; - _parenPushNewlineState: any = null; + _parenPushNewlineState: { printed: boolean } | null = null; _noLineTerminator: boolean = false; _printAuxAfterOnNextUserNode: boolean = false; - _printedComments: WeakSet = new WeakSet(); + _printedComments = new WeakSet(); _endsWithInteger = false; _endsWithWord = false; - generate(ast) { + generate(ast: t.Node) { this.print(ast); this._maybeAddAuxComment(); @@ -229,19 +254,23 @@ class Printer { this._buf.removeTrailingNewline(); } - exactSource(loc: any, cb: () => void) { + exactSource(loc: Loc | undefined, cb: () => void) { this._catchUp("start", loc); this._buf.exactSource(loc, cb); } - source(prop: string, loc: any): void { + source(prop: "start" | "end", loc: Loc | undefined): void { this._catchUp(prop, loc); this._buf.source(prop, loc); } - withSource(prop: string, loc: any, cb: () => void): void { + withSource( + prop: "start" | "end", + loc: Loc | undefined, + cb: () => void, + ): void { this._catchUp(prop, loc); this._buf.withSource(prop, loc, cb); @@ -337,7 +366,7 @@ class Printer { parenPushNewlineState.printed = true; } - _catchUp(prop: string, loc: any) { + _catchUp(prop: "start" | "end", loc?: Loc) { if (!this.format.retainLines) return; // catch up to this nodes newline if we're behind @@ -359,55 +388,64 @@ class Printer { return this.format.indent.style.repeat(this._indent); } - /** - * Set some state that will be modified if a newline has been inserted before any - * non-space characters. - * - * This is to prevent breaking semantics for terminatorless separator nodes. eg: - * - * return foo; - * - * returns `foo`. But if we do: - * - * return - * foo; - * - * `undefined` will be returned and not `foo` due to the terminator. - */ - - startTerminatorless(isLabel: boolean = false) { + printTerminatorless(node: t.Node, parent: t.Node, isLabel: boolean) { + /** + * Set some state that will be modified if a newline has been inserted before any + * non-space characters. + * + * This is to prevent breaking semantics for terminatorless separator nodes. eg: + * + * return foo; + * + * returns `foo`. But if we do: + * + * return + * foo; + * + * `undefined` will be returned and not `foo` due to the terminator. + */ if (isLabel) { this._noLineTerminator = true; - return null; + this.print(node, parent); + this._noLineTerminator = false; } else { - return (this._parenPushNewlineState = { + const terminatorState = { printed: false, - }); - } - } - - /** - * Print an ending parentheses if a starting one has been printed. - */ - - endTerminatorless(state?: any) { - this._noLineTerminator = false; - if (state?.printed) { - this.dedent(); - this.newline(); - this.token(")"); + }; + this._parenPushNewlineState = terminatorState; + this.print(node, parent); + /** + * Print an ending parentheses if a starting one has been printed. + */ + if (terminatorState.printed) { + this.dedent(); + this.newline(); + this.token(")"); + } } } - print(node, parent?) { + print(node: t.Node | null, parent?: t.Node) { if (!node) return; const oldConcise = this.format.concise; - if (node._compact) { + if ( + // @ts-ignore document _compact AST properties + node._compact + ) { this.format.concise = true; } - const printMethod = this[node.type]; + const printMethod = + this[ + node.type as Exclude< + t.Node["type"], + // removed + | "Noop" + // renamed + | t.DeprecatedAliases["type"] + > + ]; if (!printMethod) { throw new ReferenceError( `unknown node of type ${JSON.stringify( @@ -451,7 +489,7 @@ class Printer { this._insideAux = oldInAux; } - _maybeAddAuxComment(enteredPositionlessNode?) { + _maybeAddAuxComment(enteredPositionlessNode?: boolean) { if (enteredPositionlessNode) this._printAuxBeforeComment(); if (!this._insideAux) this._printAuxAfterComment(); } @@ -482,7 +520,15 @@ class Printer { } } - getPossibleRaw(node) { + getPossibleRaw( + node: + | t.StringLiteral + | t.NumericLiteral + | t.BigIntLiteral + | t.DecimalLiteral + | t.DirectiveLiteral + | t.JSXText, + ): string | undefined { const extra = node.extra; if ( extra && @@ -490,16 +536,21 @@ class Printer { extra.rawValue != null && node.value === extra.rawValue ) { + // @ts-expect-error: The extra.raw of these AST node types must be a string return extra.raw; } } - printJoin(nodes: Array | undefined | null, parent: any, opts: any = {}) { + printJoin( + nodes: Array | undefined | null, + parent: t.Node, + opts: PrintJoinOptions = {}, + ) { if (!nodes?.length) return; if (opts.indent) this.indent(); - const newlineOpts = { + const newlineOpts: AddNewlinesOptions = { addNewlines: opts.addNewlines, }; @@ -525,14 +576,14 @@ class Printer { if (opts.indent) this.dedent(); } - printAndIndentOnComments(node, parent) { + printAndIndentOnComments(node: t.Node, parent: t.Node) { const indent = node.leadingComments && node.leadingComments.length > 0; if (indent) this.indent(); this.print(node, parent); if (indent) this.dedent(); } - printBlock(parent) { + printBlock(parent: Extract) { const node = parent.body; if (!isEmptyStatement(node)) { @@ -542,11 +593,11 @@ class Printer { this.print(node, parent); } - _printTrailingComments(node) { + _printTrailingComments(node: t.Node) { this._printComments(this._getComments(false, node)); } - _printLeadingComments(node) { + _printLeadingComments(node: t.Node) { this._printComments( this._getComments(true, node), // Don't add leading/trailing new lines to #__PURE__ annotations @@ -554,7 +605,7 @@ class Printer { ); } - printInnerComments(node, indent = true) { + printInnerComments(node: t.Node, indent = true) { if (!node.innerComments?.length) return; if (indent) this.indent(); this._printComments(node.innerComments); @@ -562,23 +613,15 @@ class Printer { } printSequence( - nodes, - parent, - opts: { - statement?: boolean; - indent?: boolean; - addNewlines?: Function; - } = {}, + nodes: t.Node[], + parent: t.Node, + opts: PrintSequenceOptions = {}, ) { opts.statement = true; return this.printJoin(nodes, parent, opts); } - printList( - items, - parent, - opts: { separator?: Function; indent?: boolean; statement?: boolean } = {}, - ) { + printList(items: t.Node[], parent: t.Node, opts: PrintListOptions = {}) { if (opts.separator == null) { opts.separator = commaSeparator; } @@ -586,7 +629,12 @@ class Printer { return this.printJoin(items, parent, opts); } - _printNewline(leading, node, parent, opts) { + _printNewline( + leading: boolean, + node: t.Node, + parent: t.Node, + opts: AddNewlinesOptions, + ) { // Fast path since 'this.newline' does nothing when not tracking lines. if (this.format.retainLines || this.format.compact) return; @@ -610,7 +658,7 @@ class Printer { this.newline(Math.min(2, lines)); } - _getComments(leading, node) { + _getComments(leading: boolean, node: t.Node) { // Note, we use a boolean flag here instead of passing in the attribute name as it is faster // because this is called extremely frequently. return ( @@ -618,10 +666,10 @@ class Printer { ); } - _printComment(comment, skipNewLines?: boolean) { + _printComment(comment: t.Comment, skipNewLines?: boolean) { if (!this.format.shouldPrintComment(comment.value)) return; - // Some plugins use this to mark comments as removed using the AST-root 'comments' property, + // Some plugins (such as flow-strip-types) use this to mark comments as removed using the AST-root 'comments' property, // where they can't manually mutate the AST node comment lists. if (comment.ignore) return; @@ -674,7 +722,10 @@ class Printer { if (printNewLines) this.newline(1); } - _printComments(comments?: Array, inlinePureAnnotation?: boolean) { + _printComments( + comments?: readonly t.Comment[], + inlinePureAnnotation?: boolean, + ) { if (!comments?.length) return; if ( @@ -694,7 +745,7 @@ class Printer { } } // todo(flow->ts): was Node - printAssertions(node) { + printAssertions(node: Extract) { if (node.assertions?.length) { this.space(); this.word("assert"); diff --git a/packages/babel-helper-annotate-as-pure/src/index.ts b/packages/babel-helper-annotate-as-pure/src/index.ts index e2644da46e68..f6a92c4fb7ba 100644 --- a/packages/babel-helper-annotate-as-pure/src/index.ts +++ b/packages/babel-helper-annotate-as-pure/src/index.ts @@ -1,5 +1,5 @@ -import { addComment } from "@babel/types"; -import type { Node } from "@babel/types"; +import { addComment, type Node } from "@babel/types"; +import type { NodePath } from "@babel/traverse"; const PURE_ANNOTATION = "#__PURE__"; @@ -7,10 +7,10 @@ const isPureAnnotated = ({ leadingComments }: Node): boolean => !!leadingComments && leadingComments.some(comment => /[@#]__PURE__/.test(comment.value)); -export default function annotateAsPure( - pathOrNode: Node | { node: Node }, -): void { - const node = pathOrNode["node"] || pathOrNode; +export default function annotateAsPure(pathOrNode: Node | NodePath): void { + const node = + // @ts-expect-error Node will not have `node` property + (pathOrNode["node"] || pathOrNode) as Node; if (isPureAnnotated(node)) { return; } diff --git a/packages/babel-helper-builder-binary-assignment-operator-visitor/src/index.ts b/packages/babel-helper-builder-binary-assignment-operator-visitor/src/index.ts index c498d70369d7..a98fd672aea5 100644 --- a/packages/babel-helper-builder-binary-assignment-operator-visitor/src/index.ts +++ b/packages/babel-helper-builder-binary-assignment-operator-visitor/src/index.ts @@ -1,8 +1,15 @@ import explode from "@babel/helper-explode-assignable-expression"; import { assignmentExpression, sequenceExpression } from "@babel/types"; import type { Visitor } from "@babel/traverse"; +import type * as t from "@babel/types"; -export default function (opts: { build: Function; operator: string }) { +export default function (opts: { + build: ( + left: t.Expression | t.PrivateName, + right: t.Expression, + ) => t.Expression; + operator: t.BinaryExpression["operator"]; +}) { const { build, operator } = opts; const visitor: Visitor = { @@ -10,8 +17,8 @@ export default function (opts: { build: Function; operator: string }) { const { node, scope } = path; if (node.operator !== operator + "=") return; - const nodes = []; - // @ts-expect-error todo(flow->ts) + const nodes: t.AssignmentExpression[] = []; + // @ts-expect-error Fixme: node.left can be a TSAsExpression const exploded = explode(node.left, nodes, this, scope); nodes.push( assignmentExpression( diff --git a/packages/babel-helper-builder-react-jsx/src/index.ts b/packages/babel-helper-builder-react-jsx/src/index.ts index e9f436afb6fa..f51975543fbc 100644 --- a/packages/babel-helper-builder-react-jsx/src/index.ts +++ b/packages/babel-helper-builder-react-jsx/src/index.ts @@ -9,7 +9,6 @@ import { isJSXMemberExpression, isJSXNamespacedName, isJSXSpreadAttribute, - isLiteral, isObjectExpression, isReferenced, isStringLiteral, @@ -24,10 +23,12 @@ import { thisExpression, } from "@babel/types"; import annotateAsPure from "@babel/helper-annotate-as-pure"; -import type { Visitor } from "@babel/traverse"; +import type { NodePath, Visitor } from "@babel/traverse"; +import type { PluginPass, File } from "@babel/core"; +import type * as t from "@babel/types"; type ElementState = { - tagExpr: any; // tag node, + tagExpr: t.Expression; // tag node, tagName: string | undefined | null; // raw string tag name, args: Array; // array of call arguments, call?: any; // optional call property that can be set to override the call expression returned, @@ -35,8 +36,17 @@ type ElementState = { callee?: any; }; -export default function (opts) { - const visitor: Visitor = {}; +export interface Options { + filter?: (node: t.Node, file: File) => boolean; + pre?: (state: ElementState, file: File) => void; + post?: (state: ElementState, file: File) => void; + compat?: boolean; + pure?: string; + throwIfNamespace?: boolean; +} + +export default function (opts: Options) { + const visitor: Visitor = {}; visitor.JSXNamespacedName = function (path) { if (opts.throwIfNamespace) { @@ -54,8 +64,8 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, }; visitor.JSXElement = { - exit(path, file) { - const callExpr = buildElementCall(path, file); + exit(path, state) { + const callExpr = buildElementCall(path, state.file); if (callExpr) { path.replaceWith(inherits(callExpr, path.node)); } @@ -63,13 +73,13 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, }; visitor.JSXFragment = { - exit(path, file) { + exit(path, state) { if (opts.compat) { throw path.buildCodeFrameError( "Fragment tags are only supported in React 16 and up.", ); } - const callExpr = buildFragmentCall(path, file); + const callExpr = buildFragmentCall(path, state.file); if (callExpr) { path.replaceWith(inherits(callExpr, path.node)); } @@ -78,7 +88,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, return visitor; - function convertJSXIdentifier(node, parent) { + function convertJSXIdentifier( + node: t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName, + parent: t.JSXOpeningElement | t.JSXMemberExpression, + ): t.ThisExpression | t.StringLiteral | t.MemberExpression | t.Identifier { if (isJSXIdentifier(node)) { if (node.name === "this" && isReferenced(node, parent)) { return thisExpression(); @@ -101,10 +114,13 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, return stringLiteral(`${node.namespace.name}:${node.name.name}`); } + // @ts-expect-error return node; } - function convertAttributeValue(node) { + function convertAttributeValue( + node: t.JSXAttribute["value"] | t.BooleanLiteral, + ) { if (isJSXExpressionContainer(node)) { return node.expression; } else { @@ -112,12 +128,11 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, } } - function convertAttribute(node) { - const value = convertAttributeValue(node.value || booleanLiteral(true)); - + function convertAttribute(node: t.JSXAttribute | t.JSXSpreadAttribute) { if (isJSXSpreadAttribute(node)) { return spreadElement(node.argument); } + const value = convertAttributeValue(node.value || booleanLiteral(true)); if (isStringLiteral(value) && !isJSXExpressionContainer(node.value)) { value.value = value.value.replace(/\n\s+/g, " "); @@ -127,35 +142,45 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, } if (isJSXNamespacedName(node.name)) { + // @ts-expect-error Mutating AST nodes node.name = stringLiteral( node.name.namespace.name + ":" + node.name.name.name, ); } else if (isValidIdentifier(node.name.name, false)) { + // @ts-expect-error Mutating AST nodes node.name.type = "Identifier"; } else { + // @ts-expect-error Mutating AST nodes node.name = stringLiteral(node.name.name); } - return inherits(objectProperty(node.name, value), node); + return inherits( + objectProperty( + // @ts-expect-error Mutating AST nodes + node.name, + value, + ), + node, + ); } - function buildElementCall(path, file) { + function buildElementCall(path: NodePath, file: File) { if (opts.filter && !opts.filter(path.node, file)) return; const openingPath = path.get("openingElement"); - openingPath.parent.children = react.buildChildren(openingPath.parent); + // @ts-expect-error mutating AST nodes + path.node.children = react.buildChildren(path.node); const tagExpr = convertJSXIdentifier( openingPath.node.name, openingPath.node, ); - const args = []; + const args: (t.Expression | t.JSXElement | t.JSXFragment)[] = []; - let tagName; + let tagName: string; if (isIdentifier(tagExpr)) { tagName = tagExpr.name; - } else if (isLiteral(tagExpr)) { - // @ts-expect-error todo(flow->ts) NullLiteral + } else if (isStringLiteral(tagExpr)) { tagName = tagExpr.value; } @@ -170,18 +195,23 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, opts.pre(state, file); } - let attribs = openingPath.node.attributes; + const attribs = openingPath.node.attributes; + let convertedAttributes: t.Expression; if (attribs.length) { if (process.env.BABEL_8_BREAKING) { - attribs = objectExpression(attribs.map(convertAttribute)); + convertedAttributes = objectExpression(attribs.map(convertAttribute)); } else { - attribs = buildOpeningElementAttributes(attribs, file); + convertedAttributes = buildOpeningElementAttributes(attribs, file); } } else { - attribs = nullLiteral(); + convertedAttributes = nullLiteral(); } - args.push(attribs, ...path.node.children); + args.push( + convertedAttributes, + // @ts-expect-error JSXExpressionContainer has been transformed by convertAttributeValue + ...path.node.children, + ); if (opts.post) { opts.post(state, file); @@ -193,7 +223,10 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, return call; } - function pushProps(_props, objs) { + function pushProps( + _props: (t.ObjectProperty | t.SpreadElement)[], + objs: t.Expression[], + ) { if (!_props.length) return _props; objs.push(objectExpression(_props)); @@ -207,9 +240,12 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, * all prior attributes to an array for later processing. */ - function buildOpeningElementAttributes(attribs, file) { - let _props = []; - const objs = []; + function buildOpeningElementAttributes( + attribs: (t.JSXAttribute | t.JSXSpreadAttribute)[], + file: File, + ): t.Expression { + let _props: (t.ObjectProperty | t.SpreadElement)[] = []; + const objs: t.Expression[] = []; const { useSpread = false } = file.opts; if (typeof useSpread !== "boolean") { @@ -250,10 +286,11 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, } pushProps(_props, objs); + let convertedAttribs: t.Expression; if (objs.length === 1) { // only one object - attribs = objs[0]; + convertedAttribs = objs[0]; } else { // looks like we have multiple objects if (!isObjectExpression(objs[0])) { @@ -265,20 +302,20 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, : file.addHelper("extends"); // spread it - attribs = callExpression(helper, objs); + convertedAttribs = callExpression(helper, objs); } - return attribs; + return convertedAttribs; } - function buildFragmentCall(path, file) { + function buildFragmentCall(path: NodePath, file: File) { if (opts.filter && !opts.filter(path.node, file)) return; - const openingPath = path.get("openingElement"); - openingPath.parent.children = react.buildChildren(openingPath.parent); + // @ts-expect-error mutating AST nodes + path.node.children = react.buildChildren(path.node); - const args = []; - const tagName = null; + const args: t.Expression[] = []; + const tagName: null = null; const tagExpr = file.get("jsxFragIdentifier")(); const state: ElementState = { @@ -293,7 +330,11 @@ You can set \`throwIfNamespace: false\` to bypass this warning.`, } // no attributes are allowed with <> syntax - args.push(nullLiteral(), ...path.node.children); + args.push( + nullLiteral(), + // @ts-expect-error JSXExpressionContainer has been transformed by convertAttributeValue + ...path.node.children, + ); if (opts.post) { opts.post(state, file); diff --git a/packages/babel-helper-check-duplicate-nodes/src/index.ts b/packages/babel-helper-check-duplicate-nodes/src/index.ts index 949c5c2a7871..2f44f4215c79 100644 --- a/packages/babel-helper-check-duplicate-nodes/src/index.ts +++ b/packages/babel-helper-check-duplicate-nodes/src/index.ts @@ -1,20 +1,25 @@ import { VISITOR_KEYS } from "@babel/types"; +import type * as t from "@babel/types"; -export default function checkDuplicateNodes(ast) { +type StackItem = { + node: t.Node; + parent: t.Node | null; +}; +export default function checkDuplicateNodes(ast: t.Node) { if (arguments.length !== 1) { throw new Error("checkDuplicateNodes accepts only one argument: ast"); } // A Map from node to its parent const parentsMap = new Map(); - const hidePrivateProperties = (key, val) => { + const hidePrivateProperties = (key: string, val: unknown) => { // Hides properties like _shadowedFunctionLiteral, // which makes the AST circular if (key[0] === "_") return "[Private]"; return val; }; - const stack = [{ node: ast, parent: null }]; + const stack: StackItem[] = [{ node: ast, parent: null }]; let item; while ((item = stack.pop()) !== undefined) { @@ -36,7 +41,9 @@ export default function checkDuplicateNodes(ast) { parentsMap.set(node, parent); for (const key of keys) { - const subNode = node[key]; + const subNode = + // @ts-expect-error visitor keys must present in node + node[key]; if (Array.isArray(subNode)) { for (const child of subNode) { diff --git a/packages/babel-helper-compilation-targets/src/debug.ts b/packages/babel-helper-compilation-targets/src/debug.ts index aef9fc12a249..d1ee514cd980 100644 --- a/packages/babel-helper-compilation-targets/src/debug.ts +++ b/packages/babel-helper-compilation-targets/src/debug.ts @@ -34,5 +34,5 @@ export function getInclusionReasons( } return result; - }, {}); + }, {} as Partial>); } diff --git a/packages/babel-helper-compilation-targets/src/index.ts b/packages/babel-helper-compilation-targets/src/index.ts index 31b9c40d0e27..b1bfbd17fe2f 100644 --- a/packages/babel-helper-compilation-targets/src/index.ts +++ b/packages/babel-helper-compilation-targets/src/index.ts @@ -12,7 +12,14 @@ import { import { OptionValidator } from "@babel/helper-validator-option"; import { browserNameMap } from "./targets"; import { TargetNames } from "./options"; -import type { Targets, InputTargets, Browsers, TargetsTuple } from "./types"; +import type { + Target, + Targets, + InputTargets, + Browsers, + BrowserslistBrowserName, + TargetsTuple, +} from "./types"; export type { Targets, InputTargets }; @@ -58,50 +65,49 @@ function validateBrowsers(browsers: Browsers | undefined) { } function getLowestVersions(browsers: Array): Targets { - return browsers.reduce((all: any, browser: string): any => { - const [browserName, browserVersion] = browser.split(" "); - const normalizedBrowserName = browserNameMap[browserName]; - - if (!normalizedBrowserName) { + return browsers.reduce((all, browser) => { + const [browserName, browserVersion] = browser.split(" ") as [ + BrowserslistBrowserName, + string, + ]; + const target: Target = browserNameMap[browserName]; + + if (!target) { return all; } try { // Browser version can return as "10.0-10.2" const splitVersion = browserVersion.split("-")[0].toLowerCase(); - const isSplitUnreleased = isUnreleasedVersion(splitVersion, browserName); + const isSplitUnreleased = isUnreleasedVersion(splitVersion, target); - if (!all[normalizedBrowserName]) { - all[normalizedBrowserName] = isSplitUnreleased + if (!all[target]) { + all[target] = isSplitUnreleased ? splitVersion : semverify(splitVersion); return all; } - const version = all[normalizedBrowserName]; - const isUnreleased = isUnreleasedVersion(version, browserName); + const version = all[target]; + const isUnreleased = isUnreleasedVersion(version, target); if (isUnreleased && isSplitUnreleased) { - all[normalizedBrowserName] = getLowestUnreleased( - version, - splitVersion, - browserName, - ); + all[target] = getLowestUnreleased(version, splitVersion, target); } else if (isUnreleased) { - all[normalizedBrowserName] = semverify(splitVersion); + all[target] = semverify(splitVersion); } else if (!isUnreleased && !isSplitUnreleased) { const parsedBrowserVersion = semverify(splitVersion); - all[normalizedBrowserName] = semverMin(version, parsedBrowserVersion); + all[target] = semverMin(version, parsedBrowserVersion); } } catch (e) {} return all; - }, {}); + }, {} as Record); } function outputDecimalWarning( - decimalTargets: Array<{ target: string; value: string }>, + decimalTargets: Array<{ target: string; value: number }>, ): void { if (!decimalTargets.length) { return; @@ -117,7 +123,7 @@ getting parsed as 6.1, which can lead to unexpected behavior. `); } -function semverifyTarget(target, value) { +function semverifyTarget(target: keyof Targets, value: string) { try { return semverify(value); } catch (error) { @@ -129,23 +135,24 @@ function semverifyTarget(target, value) { } } -const targetParserMap = { - __default(target, value) { - const version = isUnreleasedVersion(value, target) - ? value.toLowerCase() - : semverifyTarget(target, value); - return [target, version]; - }, - - // Parse `node: true` and `node: "current"` to version - node(target, value) { - const parsed = - value === true || value === "current" - ? process.versions.node - : semverifyTarget(target, value); - return [target, parsed]; - }, -}; +// Parse `node: true` and `node: "current"` to version +function nodeTargetParser(value: true | string) { + const parsed = + value === true || value === "current" + ? process.versions.node + : semverifyTarget("node", value); + return ["node" as const, parsed] as const; +} + +function defaultTargetParser( + target: Exclude, + value: string, +): readonly [Exclude, string] { + const version = isUnreleasedVersion(value, target) + ? value.toLowerCase() + : semverifyTarget(target, value); + return [target, version] as const; +} function generateTargets(inputTargets: InputTargets): Targets { const input = { ...inputTargets }; @@ -214,7 +221,10 @@ export default function getTargets( // These values OVERRIDE the `browsers` field. if (esmodules && (esmodules !== "intersect" || !browsers?.length)) { browsers = Object.keys(ESM_SUPPORT) - .map(browser => `${browser} >= ${ESM_SUPPORT[browser]}`) + .map( + (browser: keyof typeof ESM_SUPPORT) => + `${browser} >= ${ESM_SUPPORT[browser]}`, + ) .join(", "); esmodules = false; } @@ -226,13 +236,16 @@ export default function getTargets( const queryBrowsers = resolveTargets(browsers, options.browserslistEnv); if (esmodules === "intersect") { - for (const browser of Object.keys(queryBrowsers)) { + for (const browser of Object.keys(queryBrowsers) as Target[]) { const version = queryBrowsers[browser]; + const esmSupportVersion = + // @ts-ignore ie is not in ESM_SUPPORT + ESM_SUPPORT[browser]; - if (ESM_SUPPORT[browser]) { + if (esmSupportVersion) { queryBrowsers[browser] = getHighestUnreleased( version, - semverify(ESM_SUPPORT[browser]), + semverify(esmSupportVersion), browser, ); } else { @@ -247,7 +260,7 @@ export default function getTargets( // Parse remaining targets const result: Targets = {} as Targets; const decimalWarnings = []; - for (const target of Object.keys(targets).sort()) { + for (const target of Object.keys(targets).sort() as Target[]) { const value = targets[target]; // Warn when specifying minor/patch as a decimal @@ -255,10 +268,10 @@ export default function getTargets( decimalWarnings.push({ target, value }); } - // Check if we have a target parser? - // $FlowIgnore - Flow doesn't like that some targetParserMap[target] might be missing - const parser = targetParserMap[target] ?? targetParserMap.__default; - const [parsedTarget, parsedValue] = parser(target, value); + const [parsedTarget, parsedValue] = + target === "node" + ? nodeTargetParser(value) + : defaultTargetParser(target, value as string); if (parsedValue) { // Merge (lowest wins) diff --git a/packages/babel-helper-compilation-targets/src/pretty.ts b/packages/babel-helper-compilation-targets/src/pretty.ts index 10afed440b75..34f5257b15ae 100644 --- a/packages/babel-helper-compilation-targets/src/pretty.ts +++ b/packages/babel-helper-compilation-targets/src/pretty.ts @@ -1,6 +1,6 @@ import semver from "semver"; import { unreleasedLabels } from "./targets"; -import type { Targets } from "./types"; +import type { Targets, Target } from "./types"; export function prettifyVersion(version: string) { if (typeof version !== "string") { @@ -23,10 +23,12 @@ export function prettifyVersion(version: string) { } export function prettifyTargets(targets: Targets): Targets { - return Object.keys(targets).reduce((results, target) => { + return Object.keys(targets).reduce((results, target: Target) => { let value = targets[target]; - const unreleasedLabel = unreleasedLabels[target]; + const unreleasedLabel = + // @ts-expect-error undefined is strictly compared with string later + unreleasedLabels[target]; if (typeof value === "string" && unreleasedLabel !== value) { value = prettifyVersion(value); } diff --git a/packages/babel-helper-compilation-targets/src/targets.ts b/packages/babel-helper-compilation-targets/src/targets.ts index 401d7904a995..ff08703a6492 100644 --- a/packages/babel-helper-compilation-targets/src/targets.ts +++ b/packages/babel-helper-compilation-targets/src/targets.ts @@ -1,8 +1,11 @@ export const unreleasedLabels = { safari: "tp", -}; +} as const; -export const browserNameMap = { +import type { Target } from "./types"; + +// Map from browserslist|@mdn/browser-compat-data browser names to @kangax/compat-table browser names +export const browserNameMap: Record = { and_chr: "chrome", and_ff: "firefox", android: "android", @@ -17,4 +20,6 @@ export const browserNameMap = { opera: "opera", safari: "safari", samsung: "samsung", -}; +} as const; + +export type BrowserslistBrowserName = keyof typeof browserNameMap; diff --git a/packages/babel-helper-compilation-targets/src/types.ts b/packages/babel-helper-compilation-targets/src/types.ts index 00035a1f0e14..d17f15299333 100644 --- a/packages/babel-helper-compilation-targets/src/types.ts +++ b/packages/babel-helper-compilation-targets/src/types.ts @@ -1,4 +1,4 @@ -// Targets +// Targets, engine names defined in compat-tables export type Target = | "node" | "chrome" @@ -17,7 +17,9 @@ export type Targets = { }; export type TargetsTuple = { - [target in Target]: string; + [target in Exclude]: string; +} & { + node: string | true; }; export type Browsers = string | ReadonlyArray; @@ -31,3 +33,5 @@ export type InputTargets = { // remove `intersect`. esmodules?: boolean | "intersect"; } & Targets; + +export type { BrowserslistBrowserName } from "./targets"; diff --git a/packages/babel-helper-compilation-targets/src/utils.ts b/packages/babel-helper-compilation-targets/src/utils.ts index d527247e9fc0..2beea6b0404e 100644 --- a/packages/babel-helper-compilation-targets/src/utils.ts +++ b/packages/babel-helper-compilation-targets/src/utils.ts @@ -38,20 +38,27 @@ export function semverify(version: number | string): string { export function isUnreleasedVersion( version: string | number, - env: string, + env: Target, ): boolean { - const unreleasedLabel = unreleasedLabels[env]; + const unreleasedLabel = + // @ts-expect-error unreleasedLabel will be guarded later + unreleasedLabels[env]; return ( !!unreleasedLabel && unreleasedLabel === version.toString().toLowerCase() ); } -export function getLowestUnreleased(a: string, b: string, env: string): string { - const unreleasedLabel = unreleasedLabels[env]; - const hasUnreleased = [a, b].some(item => item === unreleasedLabel); - if (hasUnreleased) { - // @ts-expect-error todo(flow->ts): probably a bug - types of a hasUnreleased to not overlap - return a === hasUnreleased ? b : a || b; +export function getLowestUnreleased(a: string, b: string, env: Target): string { + const unreleasedLabel: + | typeof unreleasedLabels[keyof typeof unreleasedLabels] + | undefined = + // @ts-ignore unreleasedLabel is undefined when env is not safari + unreleasedLabels[env]; + if (a === unreleasedLabel) { + return b; + } + if (b === unreleasedLabel) { + return a; } return semverMin(a, b); } @@ -59,7 +66,7 @@ export function getLowestUnreleased(a: string, b: string, env: string): string { export function getHighestUnreleased( a: string, b: string, - env: string, + env: Target, ): string { return getLowestUnreleased(a, b, env) === a ? b : a; } diff --git a/packages/babel-helper-create-class-features-plugin/src/decorators.ts b/packages/babel-helper-create-class-features-plugin/src/decorators.ts index bb80db63a7db..ef34aa56f399 100644 --- a/packages/babel-helper-create-class-features-plugin/src/decorators.ts +++ b/packages/babel-helper-create-class-features-plugin/src/decorators.ts @@ -108,9 +108,20 @@ function extractElementDescriptor( ].filter(Boolean); if (t.isClassMethod(node)) { - const id = node.computed ? null : node.key; - t.toExpression(node); - properties.push(prop("value", nameFunction({ node, id, scope }) || node)); + const id = node.computed + ? null + : (node.key as + | t.Identifier + | t.StringLiteral + | t.NumericLiteral + | t.BigIntLiteral); + const transformed = t.toExpression(node); + properties.push( + prop( + "value", + nameFunction({ node: transformed, id, scope }) || transformed, + ), + ); } else if (t.isClassProperty(node) && node.value) { properties.push( method("value", template.statements.ast`return ${node.value}`), diff --git a/packages/babel-helper-create-class-features-plugin/src/fields.ts b/packages/babel-helper-create-class-features-plugin/src/fields.ts index d938e0a9ce7a..24e0a46ed0c4 100644 --- a/packages/babel-helper-create-class-features-plugin/src/fields.ts +++ b/packages/babel-helper-create-class-features-plugin/src/fields.ts @@ -107,6 +107,7 @@ interface PrivateNameVisitorState { function privateNameVisitorFactory( visitor: Visitor, ) { + // @ts-ignore Fixme: TS complains _exploded: boolean does not satisfy visitor functions const privateNameVisitor: Visitor = { ...visitor, @@ -490,7 +491,15 @@ export function transformPrivateNamesUsage( ref: t.Identifier, path: NodePath, privateNamesMap: PrivateNamesMap, - { privateFieldsAsProperties, noDocumentAll, innerBinding }, + { + privateFieldsAsProperties, + noDocumentAll, + innerBinding, + }: { + privateFieldsAsProperties: boolean; + noDocumentAll: boolean; + innerBinding: t.Identifier; + }, state: File, ) { if (!privateNamesMap.size) return; @@ -539,7 +548,7 @@ function buildPrivateInstanceFieldInitSpec( ref: t.Expression, prop: NodePath, privateNamesMap: PrivateNamesMap, - state, + state: File, ) { const { id } = privateNamesMap.get(prop.node.key.id.name); const value = prop.node.value || prop.scope.buildUndefinedNode(); @@ -646,7 +655,7 @@ function buildPrivateInstanceMethodInitSpec( ref: t.Expression, prop: NodePath, privateNamesMap: PrivateNamesMap, - state, + state: File, ) { const privateName = privateNamesMap.get(prop.node.key.id.name); const { getId, setId, initAdded } = privateName; @@ -675,7 +684,7 @@ function buildPrivateAccessorInitialization( ref: t.Expression, prop: NodePath, privateNamesMap: PrivateNamesMap, - state, + state: File, ) { const privateName = privateNamesMap.get(prop.node.key.id.name); const { id, getId, setId } = privateName; @@ -711,7 +720,7 @@ function buildPrivateInstanceMethodInitalization( ref: t.Expression, prop: NodePath, privateNamesMap: PrivateNamesMap, - state, + state: File, ) { const privateName = privateNamesMap.get(prop.node.key.id.name); const { id } = privateName; @@ -748,7 +757,7 @@ function buildPublicFieldInitLoose( function buildPublicFieldInitSpec( ref: t.Expression, prop: NodePath, - state, + state: File, ) { const { key, computed } = prop.node; const value = prop.node.value || prop.scope.buildUndefinedNode(); @@ -767,7 +776,7 @@ function buildPublicFieldInitSpec( function buildPrivateStaticMethodInitLoose( ref: t.Expression, prop: NodePath, - state, + state: File, privateNamesMap: PrivateNamesMap, ) { const privateName = privateNamesMap.get(prop.node.key.id.name); diff --git a/packages/babel-helper-create-regexp-features-plugin/src/index.ts b/packages/babel-helper-create-regexp-features-plugin/src/index.ts index 34bd564024ed..060a3638f94d 100644 --- a/packages/babel-helper-create-regexp-features-plugin/src/index.ts +++ b/packages/babel-helper-create-regexp-features-plugin/src/index.ts @@ -1,6 +1,7 @@ import rewritePattern from "regexpu-core"; import { featuresKey, FEATURES, enableFeature, runtimeKey } from "./features"; import { generateRegexpuOptions, canSkipRegexpu, transformFlags } from "./util"; +import type { NodePath } from "@babel/traverse"; import { types as t } from "@babel/core"; import type { PluginObject } from "@babel/core"; @@ -17,12 +18,22 @@ const version = PACKAGE_JSON.version .reduce((v, x) => v * 1e5 + +x, 0); const versionKey = "@babel/plugin-regexp-features/version"; +export interface Options { + name: string; + feature: keyof typeof FEATURES; + options?: { + useUnicodeFlag?: boolean; + runtime?: boolean; + }; + manipulateOptions?: PluginObject["manipulateOptions"]; +} + export function createRegExpFeaturePlugin({ name, feature, - options = {} as any, - manipulateOptions = (() => {}) as PluginObject["manipulateOptions"], -}): PluginObject { + options = {}, + manipulateOptions = () => {}, +}: Options): PluginObject { return { name, @@ -60,7 +71,7 @@ export function createRegExpFeaturePlugin({ const regexpuOptions = generateRegexpuOptions(features); if (canSkipRegexpu(node, regexpuOptions)) return; - const namedCaptureGroups = {}; + const namedCaptureGroups: Record = {}; if (regexpuOptions.namedGroups === "transform") { regexpuOptions.onNamedGroup = (name, index) => { namedCaptureGroups[name] = index; @@ -90,7 +101,7 @@ export function createRegExpFeaturePlugin({ }; } -function isRegExpTest(path) { +function isRegExpTest(path: NodePath) { return ( path.parentPath.isMemberExpression({ object: path.node, diff --git a/packages/babel-helper-create-regexp-features-plugin/src/util.ts b/packages/babel-helper-create-regexp-features-plugin/src/util.ts index b8126ad009b3..b4c7e5251dfc 100644 --- a/packages/babel-helper-create-regexp-features-plugin/src/util.ts +++ b/packages/babel-helper-create-regexp-features-plugin/src/util.ts @@ -1,14 +1,7 @@ import type { types as t } from "@babel/core"; import { FEATURES, hasFeature } from "./features"; -type RegexpuOptions = { - unicodeFlag: "transform" | false; - unicodeSetsFlag: "transform" | "parse" | false; - dotAllFlag: "transform" | false; - unicodePropertyEscapes: "transform" | false; - namedGroups: "transform" | false; - onNamedGroup: (name: string, index: number) => void; -}; +import type { RegexpuOptions } from "regexpu-core"; export function generateRegexpuOptions(toTransform: number): RegexpuOptions { type Experimental = 1; diff --git a/packages/babel-helper-define-map/src/index.ts b/packages/babel-helper-define-map/src/index.ts index 0653c7991744..c303e022632f 100644 --- a/packages/babel-helper-define-map/src/index.ts +++ b/packages/babel-helper-define-map/src/index.ts @@ -17,8 +17,11 @@ import { toComputedKey, toKeyAlias, } from "@babel/types"; +import type { File } from "@babel/core"; +import type * as t from "@babel/types"; +import type { Scope } from "@babel/traverse"; -function toKind(node: any) { +function toKind(node: t.Property | t.Method) { if (isClassMethod(node) || isObjectMethod(node)) { if (node.kind === "get" || node.kind === "set") { return node.kind; @@ -30,12 +33,32 @@ function toKind(node: any) { const has = Function.prototype.call.bind(Object.prototype.hasOwnProperty); -export function push(mutatorMap: any, node: any, kind: string, file, scope?) { +type DefineMap = { + decorators: t.ArrayExpression; + _computed: boolean; + _inherits: t.Node[]; + _key: t.Expression | t.PrivateName; + value?: t.Expression; + initializer?: t.Expression; + get?: t.Expression; + set?: t.Expression; + kind: "get" | "set" | "value" | "initializer"; +}; + +export type MutatorMap = Record; + +export function push( + mutatorMap: MutatorMap, + node: t.Property | t.Method, + kind: DefineMap["kind"], + file: File, + scope?: Scope, +) { const alias = toKeyAlias(node); // - let map = {} as any; + let map = {} as DefineMap; if (has(mutatorMap, alias)) map = mutatorMap[alias]; mutatorMap[alias] = map; @@ -46,7 +69,10 @@ export function push(mutatorMap: any, node: any, kind: string, file, scope?) { map._key = node.key; - if (node.computed) { + if ( + // @ts-expect-error computed is not in private property + node.computed + ) { map._computed = true; } @@ -69,7 +95,7 @@ export function push(mutatorMap: any, node: any, kind: string, file, scope?) { } if (isProperty(node)) { - value = node.value; + value = node.value as t.Expression; } else if (isObjectMethod(node) || isClassMethod(node)) { value = functionExpression( null, diff --git a/packages/babel-helper-explode-assignable-expression/src/index.ts b/packages/babel-helper-explode-assignable-expression/src/index.ts index deaebcc99b60..01762222cdaa 100644 --- a/packages/babel-helper-explode-assignable-expression/src/index.ts +++ b/packages/babel-helper-explode-assignable-expression/src/index.ts @@ -78,7 +78,7 @@ export default function ( scope: Scope, allowedSingleIdent?: boolean, ): { - uid: t.Identifier | t.MemberExpression; + uid: t.Identifier | t.MemberExpression | t.Super; ref: t.Identifier | t.MemberExpression; } { let obj; diff --git a/packages/babel-helper-fixtures/src/index.ts b/packages/babel-helper-fixtures/src/index.ts index 8f3189881b0e..c1119cb2bbed 100644 --- a/packages/babel-helper-fixtures/src/index.ts +++ b/packages/babel-helper-fixtures/src/index.ts @@ -3,56 +3,81 @@ import path from "path"; import fs from "fs"; import { fileURLToPath } from "url"; import { createRequire } from "module"; +import type { InputOptions } from "@babel/core"; +import type { EncodedSourceMap, Mapping } from "@jridgewell/gen-mapping"; const require = createRequire(import.meta.url); const nodeVersion = semver.clean(process.version.slice(1)); -function humanize(val, noext?) { +function humanize(val: string, noext?: boolean) { if (noext) val = path.basename(val, path.extname(val)); return val.replace(/-/g, " "); } -type TestFile = { +interface TestIO { loc: string; code: string; +} + +export interface TestFile extends TestIO { filename: string; -}; +} -type Test = { +export interface Test { + taskDir: string; title: string; disabled: boolean; - options: any; + options: TaskOptions; + optionsDir: string; + doNotSetSourceType: boolean; + externalHelpers: boolean; + ignoreOutput: boolean; + stdout: TestIO; + stderr: TestIO; exec: TestFile; actual: TestFile; - expected: TestFile; - // todo(flow->ts): improve types here - sourceMappings; - sourceMap; + expect: TestFile; + inputSourceMap?: EncodedSourceMap; + sourceMappings?: Mapping[]; + sourceMap: string; sourceMapFile: TestFile; -}; + validateLogs: boolean; +} + +export interface TaskOptions extends InputOptions { + BABEL_8_BREAKING?: boolean; + DO_NOT_SET_SOURCE_TYPE?: boolean; + externalHelpers?: boolean; + ignoreOutput?: boolean; + minNodeVersion?: string; + sourceMap?: boolean; + os?: string | string[]; + validateLogs?: boolean; + throws?: boolean | string; +} type Suite = { - options: any; + options: TaskOptions; tests: Array; title: string; filename: string; }; -function tryResolve(module) { +function tryResolve(module: string) { try { return require.resolve(module); } catch (e) { return null; } } -function assertDirectory(loc) { +function assertDirectory(loc: string) { if (!fs.statSync(loc).isDirectory()) { throw new Error(`Expected ${loc} to be a directory.`); } } -function shouldIgnore(name, ignore?: Array) { +function shouldIgnore(name: string, ignore?: Array) { if (ignore && ignore.indexOf(name) >= 0) { return true; } @@ -87,7 +112,12 @@ function findFile(filepath: string, allowJSON?: boolean) { return matches[0]; } -function pushTask(taskName, taskDir, suite, suiteName) { +function pushTask( + taskName: string, + taskDir: string, + suite: Suite, + suiteName: string, +) { const taskDirStats = fs.statSync(taskDir); let actualLoc = findFile(taskDir + "/input"); let execLoc = findFile(taskDir + "/exec"); @@ -126,12 +156,12 @@ function pushTask(taskName, taskDir, suite, suiteName) { execLocAlias = suiteName + "/" + taskName; } - const taskOpts = JSON.parse(JSON.stringify(suite.options)); + const taskOpts: TaskOptions = JSON.parse(JSON.stringify(suite.options)); const taskOptsLoc = tryResolve(taskDir + "/options"); if (taskOptsLoc) Object.assign(taskOpts, require(taskOptsLoc)); - const test = { + const test: Test = { taskDir, optionsDir: taskOptsLoc ? path.dirname(taskOptsLoc) : null, title: humanize(taskName, true), @@ -264,7 +294,7 @@ function pushTask(taskName, taskDir, suite, suiteName) { (test.stdout.code ? stdoutLoc : stderrLoc), ); } - if (test.options.ignoreOutput) { + if (test.ignoreOutput) { if (test.expect.code) { throw new Error( "Test cannot ignore its output and also validate it: " + expectLoc, @@ -284,7 +314,11 @@ function pushTask(taskName, taskDir, suite, suiteName) { delete test.options.externalHelpers; } -function wrapPackagesArray(type, names, optionsDir) { +function wrapPackagesArray( + type: "plugin" | "preset", + names: (string | [string, object?, string?])[], + optionsDir: string, +) { return names.map(function (val) { if (typeof val === "string") val = [val]; @@ -362,10 +396,10 @@ export function resolveOptionPluginOrPreset( return options; } -export default function get(entryLoc): Array { +export default function get(entryLoc: string): Array { const suites = []; - let rootOpts = {}; + let rootOpts: TaskOptions = {}; const rootOptsLoc = tryResolve(entryLoc + "/options"); if (rootOptsLoc) rootOpts = require(rootOptsLoc); @@ -374,7 +408,7 @@ export default function get(entryLoc): Array { const suite = { options: { ...rootOpts }, - tests: [], + tests: [] as Test[], title: humanize(suiteName), filename: entryLoc + "/" + suiteName, }; @@ -398,8 +432,8 @@ export default function get(entryLoc): Array { return suites; } -export function multiple(entryLoc, ignore?: Array) { - const categories = {}; +export function multiple(entryLoc: string, ignore?: Array) { + const categories: Record = {}; for (const name of fs.readdirSync(entryLoc)) { if (shouldIgnore(name, ignore)) continue; @@ -413,7 +447,7 @@ export function multiple(entryLoc, ignore?: Array) { return categories; } -export function readFile(filename) { +export function readFile(filename: string) { if (fs.existsSync(filename)) { let file = fs.readFileSync(filename, "utf8").trimRight(); file = file.replace(/\r\n/g, "\n"); diff --git a/packages/babel-helper-function-name/src/index.ts b/packages/babel-helper-function-name/src/index.ts index fc157b28be35..1f854bb5895d 100644 --- a/packages/babel-helper-function-name/src/index.ts +++ b/packages/babel-helper-function-name/src/index.ts @@ -18,6 +18,7 @@ import { toBindingIdentifierName, } from "@babel/types"; import type * as t from "@babel/types"; +import type { NodePath, Scope, Visitor } from "@babel/traverse"; function getFunctionArity(node: t.Function): number { const count = node.params.findIndex( @@ -26,7 +27,7 @@ function getFunctionArity(node: t.Function): number { return count === -1 ? node.params.length : count; } -const buildPropertyMethodAssignmentWrapper = template(` +const buildPropertyMethodAssignmentWrapper = template.statement(` (function (FUNCTION_KEY) { function FUNCTION_ID() { return FUNCTION_KEY.apply(this, arguments); @@ -40,7 +41,7 @@ const buildPropertyMethodAssignmentWrapper = template(` })(FUNCTION) `); -const buildGeneratorPropertyMethodAssignmentWrapper = template(` +const buildGeneratorPropertyMethodAssignmentWrapper = template.statement(` (function (FUNCTION_KEY) { function* FUNCTION_ID() { return yield* FUNCTION_KEY.apply(this, arguments); @@ -54,8 +55,18 @@ const buildGeneratorPropertyMethodAssignmentWrapper = template(` })(FUNCTION) `); -const visitor = { - "ReferencedIdentifier|BindingIdentifier"(path, state) { +type State = { + name: string; + outerDeclar: t.Identifier; + selfAssignment: boolean; + selfReference: boolean; +}; + +const visitor: Visitor = { + "ReferencedIdentifier|BindingIdentifier"( + path: NodePath, + state, + ) { // check if this node matches our function id if (path.node.name !== state.name) return; @@ -69,7 +80,7 @@ const visitor = { }, }; -function getNameFromLiteralId(id) { +function getNameFromLiteralId(id: t.Literal) { if (isNullLiteral(id)) { return "null"; } @@ -89,7 +100,12 @@ function getNameFromLiteralId(id) { return ""; } -function wrap(state, method, id, scope) { +function wrap( + state: State, + method: t.FunctionExpression | t.Class, + id: t.Identifier, + scope: Scope, +) { if (state.selfReference) { if (scope.hasBinding(id.name) && !scope.hasGlobal(id.name)) { // we can just munge the local binding @@ -131,12 +147,15 @@ function wrap(state, method, id, scope) { scope.getProgramParent().references[id.name] = true; } -function visit(node, name, scope) { - const state = { +function visit( + node: t.FunctionExpression | t.Class, + name: string, + scope: Scope, +) { + const state: State = { selfAssignment: false, selfReference: false, outerDeclar: scope.getBindingIdentifier(name), - references: [], name: name, }; @@ -182,16 +201,21 @@ function visit(node, name, scope) { * @param {Boolean} localBinding whether a name could shadow a self-reference (e.g. converting arrow function) * @param {Boolean} supportUnicodeId whether a target support unicodeId or not */ -export default function ( +export default function ( { node, parent, scope, id, - }: { node: any; parent?: any; scope: any; id?: any }, + }: { + node: N; + parent?: t.Node; + scope: Scope; + id?: t.LVal | t.StringLiteral | t.NumericLiteral | t.BigIntLiteral; + }, localBinding = false, supportUnicodeId = false, -) { +): t.CallExpression | N { // has an `id` so we don't need to infer one if (node.id) return; @@ -200,7 +224,11 @@ export default function ( (!parent.computed || isLiteral(parent.key)) ) { // { foo() {} }; - id = parent.key; + id = parent.key as + | t.Identifier + | t.StringLiteral + | t.NumericLiteral + | t.BigIntLiteral; } else if (isVariableDeclarator(parent)) { // let foo = function () {}; id = parent.id; @@ -215,6 +243,7 @@ export default function ( ) { // always going to reference this method node.id = cloneNode(id); + // @ts-expect-error Fixme: avoid mutating AST nodes node.id[NOT_LOCAL_BINDING] = true; return; } @@ -242,13 +271,14 @@ export default function ( } name = toBindingIdentifierName(name); - id = identifier(name); + const newId = identifier(name); // The id shouldn't be considered a local binding to the function because // we are simply trying to set the function name and not actually create // a local binding. - id[NOT_LOCAL_BINDING] = true; + // @ts-ignore Fixme: avoid mutating AST nodes + newId[NOT_LOCAL_BINDING] = true; const state = visit(node, name, scope); - return wrap(state, node, id, scope) || node; + return wrap(state, node, newId, scope) || node; } diff --git a/packages/babel-helper-hoist-variables/src/index.ts b/packages/babel-helper-hoist-variables/src/index.ts index 1cb3b8a3c4c5..89653ac33af8 100644 --- a/packages/babel-helper-hoist-variables/src/index.ts +++ b/packages/babel-helper-hoist-variables/src/index.ts @@ -4,7 +4,7 @@ import { identifier, } from "@babel/types"; import type * as t from "@babel/types"; -import type { NodePath } from "@babel/traverse"; +import type { NodePath, Visitor } from "@babel/traverse"; export type EmitFunction = ( id: t.Identifier, @@ -19,16 +19,16 @@ type State = { type Unpacked = T extends (infer U)[] ? U : T; -const visitor = { - Scope(path: NodePath, state: State) { +const visitor: Visitor = { + Scope(path, state) { if (state.kind === "let") path.skip(); }, - FunctionParent(path: NodePath) { + FunctionParent(path) { path.skip(); }, - VariableDeclaration(path: NodePath, state: State) { + VariableDeclaration(path, state) { if (state.kind && path.node.kind !== state.kind) return; const nodes = []; diff --git a/packages/babel-helper-member-expression-to-functions/src/index.ts b/packages/babel-helper-member-expression-to-functions/src/index.ts index fd629d54de30..efaf7293366c 100644 --- a/packages/babel-helper-member-expression-to-functions/src/index.ts +++ b/packages/babel-helper-member-expression-to-functions/src/index.ts @@ -25,7 +25,7 @@ import type * as t from "@babel/types"; import { willPathCastToBoolean } from "./util"; class AssignmentMemoiser { - private _map: WeakMap; + private _map: WeakMap; constructor() { this._map = new WeakMap(); } @@ -49,7 +49,7 @@ class AssignmentMemoiser { return value; } - set(key: t.Expression, value: t.LVal, count: number) { + set(key: t.Expression, value: t.Identifier, count: number) { return this._map.set(key, { count, value }); } } @@ -66,11 +66,12 @@ function toNonOptional( if (path.isOptionalCallExpression()) { const callee = path.get("callee"); if (path.node.optional && callee.isOptionalMemberExpression()) { - const { object } = callee.node; - const context = path.scope.maybeGenerateMemoised(object) || object; + // object must be a conditional expression because the optional private access in object has been transformed + const object = callee.node.object as t.ConditionalExpression; + const context = path.scope.maybeGenerateMemoised(object); callee .get("object") - .replaceWith(assignmentExpression("=", context as t.LVal, object)); + .replaceWith(assignmentExpression("=", context, object)); return callExpression(memberExpression(base, identifier("call")), [ context, @@ -95,7 +96,13 @@ function isInDetachedTree(path: NodePath) { const { parentPath, container, listKey } = path; const parentNode = parentPath.node; if (listKey) { - if (container !== parentNode[listKey]) return true; + if ( + container !== + // @ts-expect-error listKey must be a valid parent node key + parentNode[listKey] + ) { + return true; + } } else { if (container !== parentNode) return true; } @@ -208,10 +215,9 @@ const handle = { ); } - const startingProp = startingOptional.isOptionalMemberExpression() - ? "object" - : "callee"; - const startingNode = startingOptional.node[startingProp]; + const startingNode = startingOptional.isOptionalMemberExpression() + ? startingOptional.node.object + : startingOptional.node.callee; const baseNeedsMemoised = scope.maybeGenerateMemoised(startingNode); const baseRef = baseNeedsMemoised ?? startingNode; @@ -281,7 +287,12 @@ const handle = { } const baseMemoised = baseNeedsMemoised - ? assignmentExpression("=", cloneNode(baseRef), cloneNode(startingNode)) + ? assignmentExpression( + "=", + // When base needs memoised, the baseRef must be an identifier + cloneNode(baseRef as t.Identifier), + cloneNode(startingNode), + ) : cloneNode(baseRef); if (willEndPathCastToBoolean) { diff --git a/packages/babel-helper-module-imports/src/import-builder.ts b/packages/babel-helper-module-imports/src/import-builder.ts index 55c7d09c8cac..da78c1fcf3ca 100644 --- a/packages/babel-helper-module-imports/src/import-builder.ts +++ b/packages/babel-helper-module-imports/src/import-builder.ts @@ -13,20 +13,23 @@ import { variableDeclaration, variableDeclarator, } from "@babel/types"; +import type * as t from "@babel/types"; +import type { Scope } from "@babel/traverse"; +import type { File } from "@babel/core"; /** * A class to track and accumulate mutations to the AST that will eventually * output a new require/import statement list. */ export default class ImportBuilder { - _statements = []; - _resultName = null; + private _statements: t.Statement[] = []; + private _resultName: t.Identifier | t.MemberExpression = null; - _scope = null; - _hub = null; - private _importedSource: any; + declare _scope: Scope; + declare _hub: File["hub"]; + private _importedSource: string; - constructor(importedSource, scope, hub) { + constructor(importedSource: string, scope: Scope, hub: File["hub"]) { this._scope = scope; this._hub = hub; this._importedSource = importedSource; @@ -67,29 +70,29 @@ export default class ImportBuilder { this._resultName = cloneNode(local); return this; } - default(name) { - name = this._scope.generateUidIdentifier(name); + default(name: string) { + const id = this._scope.generateUidIdentifier(name); const statement = this._statements[this._statements.length - 1]; assert(statement.type === "ImportDeclaration"); assert(statement.specifiers.length === 0); - statement.specifiers = [importDefaultSpecifier(name)]; - this._resultName = cloneNode(name); + statement.specifiers = [importDefaultSpecifier(id)]; + this._resultName = cloneNode(id); return this; } - named(name, importName) { + named(name: string, importName: string) { if (importName === "default") return this.default(name); - name = this._scope.generateUidIdentifier(name); + const id = this._scope.generateUidIdentifier(name); const statement = this._statements[this._statements.length - 1]; assert(statement.type === "ImportDeclaration"); assert(statement.specifiers.length === 0); - statement.specifiers = [importSpecifier(name, identifier(importName))]; - this._resultName = cloneNode(name); + statement.specifiers = [importSpecifier(id, identifier(importName))]; + this._resultName = cloneNode(id); return this; } - var(name) { - name = this._scope.generateUidIdentifier(name); + var(name: string) { + const id = this._scope.generateUidIdentifier(name); let statement = this._statements[this._statements.length - 1]; if (statement.type !== "ExpressionStatement") { assert(this._resultName); @@ -97,9 +100,9 @@ export default class ImportBuilder { this._statements.push(statement); } this._statements[this._statements.length - 1] = variableDeclaration("var", [ - variableDeclarator(name, statement.expression), + variableDeclarator(id, statement.expression), ]); - this._resultName = cloneNode(name); + this._resultName = cloneNode(id); return this; } @@ -110,7 +113,7 @@ export default class ImportBuilder { return this._interop(this._hub.addHelper("interopRequireWildcard")); } - _interop(callee) { + _interop(callee: t.Expression) { const statement = this._statements[this._statements.length - 1]; if (statement.type === "ExpressionStatement") { statement.expression = callExpression(callee, [statement.expression]); @@ -125,7 +128,7 @@ export default class ImportBuilder { return this; } - prop(name) { + prop(name: string) { const statement = this._statements[this._statements.length - 1]; if (statement.type === "ExpressionStatement") { statement.expression = memberExpression( @@ -144,7 +147,7 @@ export default class ImportBuilder { return this; } - read(name) { + read(name: string) { this._resultName = memberExpression(this._resultName, identifier(name)); } } diff --git a/packages/babel-helper-module-imports/src/import-injector.ts b/packages/babel-helper-module-imports/src/import-injector.ts index 2b1d78709795..7e439c44accd 100644 --- a/packages/babel-helper-module-imports/src/import-injector.ts +++ b/packages/babel-helper-module-imports/src/import-injector.ts @@ -1,7 +1,8 @@ import assert from "assert"; import { numericLiteral, sequenceExpression } from "@babel/types"; import type * as t from "@babel/types"; -import type { NodePath, Scope, HubInterface } from "@babel/traverse"; +import type { NodePath, Scope } from "@babel/traverse"; +import type { File } from "@babel/core"; import ImportBuilder from "./import-builder"; import isModule from "./is-module"; @@ -93,8 +94,8 @@ export type ImportOptions = { */ importPosition: "before" | "after"; - nameHint?; - blockHoist?; + nameHint?: string; + blockHoist?: number; }; /** @@ -114,7 +115,7 @@ export default class ImportInjector { /** * The file used to inject helpers and resolve paths. */ - declare _hub: HubInterface; + declare _hub: File["hub"]; /** * The default options to use with this instance when imports are added. @@ -129,21 +130,29 @@ export default class ImportInjector { importPosition: "before", }; - constructor(path: NodePath, importedSource?, opts?) { + constructor( + path: NodePath, + importedSource?: string, + opts?: Partial, + ) { const programPath = path.find(p => p.isProgram()) as NodePath; this._programPath = programPath; this._programScope = programPath.scope; - this._hub = programPath.hub; + this._hub = programPath.hub as File["hub"]; this._defaultOpts = this._applyDefaults(importedSource, opts, true); } - addDefault(importedSourceIn, opts) { + addDefault(importedSourceIn: string, opts: Partial) { return this.addNamed("default", importedSourceIn, opts); } - addNamed(importName, importedSourceIn, opts) { + addNamed( + importName: string, + importedSourceIn: string, + opts: Partial, + ) { assert(typeof importName === "string"); return this._generateImport( @@ -152,49 +161,44 @@ export default class ImportInjector { ); } - addNamespace(importedSourceIn, opts) { + addNamespace(importedSourceIn: string, opts: Partial) { return this._generateImport( this._applyDefaults(importedSourceIn, opts), null, ); } - addSideEffect(importedSourceIn, opts) { + addSideEffect(importedSourceIn: string, opts: Partial) { return this._generateImport( this._applyDefaults(importedSourceIn, opts), - false, + void 0, ); } - _applyDefaults(importedSource, opts, isInit = false) { - const optsList = []; + _applyDefaults( + importedSource: string | Partial, + opts: Partial | undefined, + isInit = false, + ) { + let newOpts: ImportOptions; if (typeof importedSource === "string") { - optsList.push({ importedSource }); - optsList.push(opts); + newOpts = { ...this._defaultOpts, importedSource, ...opts }; } else { assert(!opts, "Unexpected secondary arguments."); - - optsList.push(importedSource); + newOpts = { ...this._defaultOpts, ...importedSource }; } - const newOpts: ImportOptions = { - ...this._defaultOpts, - }; - for (const opts of optsList) { - if (!opts) continue; - Object.keys(newOpts).forEach(key => { - if (opts[key] !== undefined) newOpts[key] = opts[key]; - }); - - if (!isInit) { - if (opts.nameHint !== undefined) newOpts.nameHint = opts.nameHint; - if (opts.blockHoist !== undefined) newOpts.blockHoist = opts.blockHoist; - } + if (!isInit && opts) { + if (opts.nameHint !== undefined) newOpts.nameHint = opts.nameHint; + if (opts.blockHoist !== undefined) newOpts.blockHoist = opts.blockHoist; } return newOpts; } - _generateImport(opts, importName) { + _generateImport( + opts: Partial, + importName: string | null | undefined, + ) { const isDefault = importName === "default"; const isNamed = !!importName && !isDefault; const isNamespace = importName === null; @@ -422,7 +426,11 @@ export default class ImportInjector { return resultName; } - _insertStatements(statements, importPosition = "before", blockHoist = 3) { + _insertStatements( + statements: t.Statement[], + importPosition = "before", + blockHoist = 3, + ) { const body = this._programPath.get("body"); if (importPosition === "after") { @@ -434,6 +442,7 @@ export default class ImportInjector { } } else { statements.forEach(node => { + // @ts-expect-error handle _blockHoist node._blockHoist = blockHoist; }); diff --git a/packages/babel-helper-module-imports/src/index.ts b/packages/babel-helper-module-imports/src/index.ts index 44b98d5c2054..183aaabd6fe0 100644 --- a/packages/babel-helper-module-imports/src/index.ts +++ b/packages/babel-helper-module-imports/src/index.ts @@ -1,21 +1,77 @@ -import ImportInjector from "./import-injector"; +import ImportInjector, { type ImportOptions } from "./import-injector"; +import type { NodePath } from "@babel/traverse"; +import type * as t from "@babel/types"; export { ImportInjector }; export { default as isModule } from "./is-module"; -export function addDefault(path, importedSource, opts?) { +export function addDefault( + path: NodePath, + importedSource: string, + opts?: Partial, +) { return new ImportInjector(path).addDefault(importedSource, opts); } -export function addNamed(path, name, importedSource, opts?) { +function addNamed( + path: NodePath, + name: string, + importedSource: string, + opts?: Omit< + Partial, + "ensureLiveReference" | "ensureNoContext" + >, +): t.Identifier; +function addNamed( + path: NodePath, + name: string, + importedSource: string, + opts?: Omit, "ensureLiveReference"> & { + ensureLiveReference: true; + }, +): t.MemberExpression; +function addNamed( + path: NodePath, + name: string, + importedSource: string, + opts?: Omit, "ensureNoContext"> & { + ensureNoContext: true; + }, +): t.SequenceExpression; +/** + * add a named import to the program path of given path + * + * @export + * @param {NodePath} path The starting path to find a program path + * @param {string} name The name of the generated binding. Babel will prefix it with `_` + * @param {string} importedSource The source of the import + * @param {Partial} [opts] + * @returns {t.Identifier | t.MemberExpression | t.SequenceExpression} If opts.ensureNoContext is true, returns a SequenceExpression, + * else if opts.ensureLiveReference is true, returns a MemberExpression, else returns an Identifier + */ +function addNamed( + path: NodePath, + name: string, + importedSource: string, + opts?: Partial, +) { return new ImportInjector(path).addNamed(name, importedSource, opts); } +export { addNamed }; -export function addNamespace(path, importedSource, opts?) { +export function addNamespace( + path: NodePath, + importedSource: string, + opts?: Partial, +) { return new ImportInjector(path).addNamespace(importedSource, opts); } -export function addSideEffect(path, importedSource, opts?) { +export function addSideEffect( + path: NodePath, + importedSource: string, + opts?: Partial, +) { return new ImportInjector(path).addSideEffect(importedSource, opts); } diff --git a/packages/babel-helper-module-transforms/src/index.ts b/packages/babel-helper-module-transforms/src/index.ts index 5001699b619e..241dac12e110 100644 --- a/packages/babel-helper-module-transforms/src/index.ts +++ b/packages/babel-helper-module-transforms/src/index.ts @@ -27,7 +27,9 @@ import normalizeModuleAndLoadMetadata, { validateImportInteropOption, } from "./normalize-and-load-metadata"; import type { + ImportInterop, InteropType, + Lazy, ModuleMetadata, SourceModuleMetadata, } from "./normalize-and-load-metadata"; @@ -38,6 +40,22 @@ export type { PluginOptions } from "./get-module-name"; export { hasExports, isSideEffectImport, isModule, rewriteThis }; +export interface RewriteModuleStatementsAndPrepareHeaderOptions { + exportName?: string; + strict: boolean; + allowTopLevelThis?: boolean; + strictMode: boolean; + loose?: boolean; + importInterop?: ImportInterop; + noInterop?: boolean; + lazy?: Lazy; + esNamespaceOnly?: boolean; + filename: string | undefined; + constantReexports?: boolean | void; + enumerableModuleMeta?: boolean | void; + noIncompleteNsImportDetection?: boolean | void; +} + /** * Perform all of the generic ES6 module rewriting needed to handle initial * module processing. This function will rewrite the majority of the given @@ -63,21 +81,7 @@ export function rewriteModuleStatementsAndPrepareHeader( constantReexports = loose, enumerableModuleMeta = loose, noIncompleteNsImportDetection, - }: { - exportName?; - strict; - allowTopLevelThis?; - strictMode; - loose?; - importInterop?: "none" | "babel" | "node"; - noInterop?; - lazy?; - esNamespaceOnly?; - filename: string | undefined; - constantReexports?; - enumerableModuleMeta?; - noIncompleteNsImportDetection?: boolean; - }, + }: RewriteModuleStatementsAndPrepareHeaderOptions, ) { validateImportInteropOption(importInterop); assert(isModule(path), "Cannot process module statements in a script"); @@ -138,9 +142,10 @@ export function rewriteModuleStatementsAndPrepareHeader( * Flag a set of statements as hoisted above all else so that module init * statements all run before user code. */ -export function ensureStatementsHoisted(statements) { +export function ensureStatementsHoisted(statements: t.Statement[]) { // Force all of the header fields to be at the top of the file. statements.forEach(header => { + // @ts-ignore Fixme: handle _blockHoist property header._blockHoist = 3; }); } @@ -303,7 +308,7 @@ const buildReexportsFromMeta = ( */ function buildESModuleHeader( metadata: ModuleMetadata, - enumerableModuleMeta: boolean = false, + enumerableModuleMeta: boolean | void = false, ) { return ( enumerableModuleMeta @@ -321,7 +326,11 @@ function buildESModuleHeader( /** * Create a re-export initialization loop for a specific imported namespace. */ -function buildNamespaceReexport(metadata, namespace, constantReexports) { +function buildNamespaceReexport( + metadata: ModuleMetadata, + namespace: t.Identifier | t.CallExpression, + constantReexports: boolean | void, +) { return ( constantReexports ? template.statement` @@ -413,8 +422,8 @@ function buildExportNameListDeclaration( function buildExportInitializationStatements( programPath: NodePath, metadata: ModuleMetadata, - constantReexports: boolean = false, - noIncompleteNsImportDetection = false, + constantReexports: boolean | void = false, + noIncompleteNsImportDetection: boolean | void = false, ) { const initStatements: Array<[string, t.Statement | null]> = []; @@ -514,7 +523,11 @@ const InitTemplate = { default: template.expression`EXPORTS.NAME = VALUE`, }; -function buildInitStatement(metadata: ModuleMetadata, exportNames, initExpr) { +function buildInitStatement( + metadata: ModuleMetadata, + exportNames: string[], + initExpr: t.Expression, +) { const { stringSpecifiers, exportName: EXPORTS } = metadata; return expressionStatement( exportNames.reduce((acc, exportName) => { diff --git a/packages/babel-helper-module-transforms/src/normalize-and-load-metadata.ts b/packages/babel-helper-module-transforms/src/normalize-and-load-metadata.ts index bd2941fba21f..b0793cfcd0f7 100644 --- a/packages/babel-helper-module-transforms/src/normalize-and-load-metadata.ts +++ b/packages/babel-helper-module-transforms/src/normalize-and-load-metadata.ts @@ -28,6 +28,14 @@ export type InteropType = | "node-namespace" // Node.js interop for namespace or default+named imports | "none"; // No interop, or named-only imports +export type ImportInterop = + | "none" + | "babel" + | "node" + | ((source: string, filename?: string) => "none" | "babel" | "node"); + +export type Lazy = boolean | string[] | ((source: string) => boolean); + export interface SourceModuleMetadata { // A unique variable name to use for this namespace object. Centralized for simplicity. name: string; @@ -45,7 +53,7 @@ export interface SourceModuleMetadata { reexportAll: null | { loc: t.SourceLocation | undefined | null; }; - lazy?; + lazy?: Lazy; } export interface LocalExportMetadata { @@ -75,7 +83,7 @@ export function isSideEffectImport(source: SourceModuleMetadata) { export function validateImportInteropOption( importInterop: any, -): importInterop is "none" | "babel" | "node" | Function { +): importInterop is ImportInterop { if ( typeof importInterop !== "function" && importInterop !== "none" && @@ -90,8 +98,8 @@ export function validateImportInteropOption( } function resolveImportInterop( - importInterop, - source, + importInterop: ImportInterop, + source: string, filename: string | undefined, ) { if (typeof importInterop === "function") { @@ -113,6 +121,12 @@ export default function normalizeModuleAndLoadMetadata( lazy = false, esNamespaceOnly = false, filename, + }: { + importInterop: ImportInterop; + initializeReexports: boolean | void; + lazy: Lazy; + esNamespaceOnly: boolean; + filename: string; }, ): ModuleMetadata { if (!exportName) { @@ -219,8 +233,8 @@ function getModuleMetadata( initializeReexports, }: { // todo(flow-ts) changed from boolean, to match expected usage inside the function - lazy: boolean | string[] | Function; - initializeReexports: boolean; + lazy: boolean | string[] | ((source: string) => boolean); + initializeReexports: boolean | void; }, stringSpecifiers: Set, ) { @@ -231,7 +245,7 @@ function getModuleMetadata( ); const sourceData = new Map(); - const getData = sourceNode => { + const getData = (sourceNode: t.StringLiteral) => { const source = sourceNode.value; let data = sourceData.get(source); @@ -409,22 +423,26 @@ function getModuleMetadata( }; } +type ModuleBindingKind = "import" | "hoisted" | "block" | "var"; /** * Get metadata about local variables that are exported. */ function getLocalExportMetadata( programPath: NodePath, - initializeReexports: boolean, + initializeReexports: boolean | void, stringSpecifiers: Set, ): Map { const bindingKindLookup = new Map(); - programPath.get("body").forEach((child: any) => { - let kind; + programPath.get("body").forEach(child => { + let kind: ModuleBindingKind; if (child.isImportDeclaration()) { kind = "import"; } else { - if (child.isExportDefaultDeclaration()) child = child.get("declaration"); + if (child.isExportDefaultDeclaration()) { + // @ts-ignore + child = child.get("declaration"); + } if (child.isExportNamedDeclaration()) { if (child.node.declaration) { child = child.get("declaration"); @@ -460,7 +478,7 @@ function getLocalExportMetadata( }); const localMetadata = new Map(); - const getLocalMetadata = idPath => { + const getLocalMetadata = (idPath: NodePath) => { const localName = idPath.node.name; let metadata = localMetadata.get(localName); @@ -562,7 +580,9 @@ function removeModuleDeclarations(programPath: NodePath) { ) { // @ts-expect-error todo(flow->ts): avoid mutations declaration._blockHoist = child.node._blockHoist; - child.replaceWith(declaration); + child.replaceWith( + declaration as NodePath, + ); } else { // These should have been removed by the nameAnonymousExports() call. throw declaration.buildCodeFrameError( diff --git a/packages/babel-helper-module-transforms/src/rewrite-live-references.ts b/packages/babel-helper-module-transforms/src/rewrite-live-references.ts index 9ba5d3063182..33c7eb3af37b 100644 --- a/packages/babel-helper-module-transforms/src/rewrite-live-references.ts +++ b/packages/babel-helper-module-transforms/src/rewrite-live-references.ts @@ -27,12 +27,12 @@ import type { ModuleMetadata } from "./normalize-and-load-metadata"; interface RewriteReferencesVisitorState { exported: Map; metadata: ModuleMetadata; - requeueInParent: (path) => void; + requeueInParent: (path: NodePath) => void; scope: Scope; imported: Map; buildImportReference: ( - [source, importName, localName]: readonly [any, any, any], - identNode, + [source, importName, localName]: readonly [string, string, string], + identNode: t.Identifier | t.CallExpression | t.JSXIdentifier, ) => any; seen: WeakSet; } @@ -40,11 +40,11 @@ interface RewriteReferencesVisitorState { interface RewriteBindingInitVisitorState { exported: Map; metadata: ModuleMetadata; - requeueInParent: (path) => void; + requeueInParent: (path: NodePath) => void; scope: Scope; } -function isInType(path) { +function isInType(path: NodePath) { do { switch (path.parent.type) { case "TSTypeAnnotation": @@ -54,7 +54,13 @@ function isInType(path) { case "TypeAlias": return true; case "ExportSpecifier": - return path.parentPath.parent.exportKind === "type"; + return ( + ( + path.parentPath.parent as + | t.ExportDefaultDeclaration + | t.ExportNamedDeclaration + ).exportKind === "type" + ); default: if (path.parentPath.isStatement() || path.parentPath.isExpression()) { return false; @@ -69,7 +75,7 @@ export default function rewriteLiveReferences( ) { const imported = new Map(); const exported = new Map(); - const requeueInParent = path => { + const requeueInParent = (path: NodePath) => { // Manually re-queue `exports.default =` expressions so that the ES3 // transform has an opportunity to convert them. Ideally this would // happen automatically from the replaceWith above. See #4140 for @@ -127,7 +133,13 @@ export default function rewriteLiveReferences( const meta = metadata.source.get(source); if (localName) { - if (meta.lazy) identNode = callExpression(identNode, []); + if (meta.lazy) { + identNode = callExpression( + // @ts-expect-error Fixme: we should handle the case when identNode is a JSXIdentifier + identNode, + [], + ); + } return identNode; } @@ -203,9 +215,9 @@ const rewriteBindingInitVisitor: Visitor = { }; const buildBindingExportAssignmentExpression = ( - metadata, - exportNames, - localExpr, + metadata: ModuleMetadata, + exportNames: string[], + localExpr: t.Expression, ) => { return (exportNames || []).reduce((expr, exportName) => { // class Foo {} export { Foo, Foo as Bar }; @@ -225,7 +237,7 @@ const buildBindingExportAssignmentExpression = ( }, localExpr); }; -const buildImportThrow = localName => { +const buildImportThrow = (localName: string) => { return template.expression.ast` (function() { throw new Error('"' + '${localName}' + '" is read-only.'); @@ -403,7 +415,7 @@ const rewriteReferencesVisitor: Visitor = { const assignment = path.node; if (importData) { - assignment.left = buildImportReference(importData, assignment.left); + assignment.left = buildImportReference(importData, left.node); assignment.right = sequenceExpression([ assignment.right, @@ -437,7 +449,7 @@ const rewriteReferencesVisitor: Visitor = { // Complex ({a, b, c} = {}); export { a, c }; // => ({a, b, c} = {}), (exports.a = a, exports.c = c); - const items = []; + const items: t.Expression[] = []; programScopeIds.forEach(localName => { const exportedNames = exported.get(localName) || []; if (exportedNames.length > 0) { diff --git a/packages/babel-helper-module-transforms/src/rewrite-this.ts b/packages/babel-helper-module-transforms/src/rewrite-this.ts index 92d9dd233968..cfd6aa9c24a2 100644 --- a/packages/babel-helper-module-transforms/src/rewrite-this.ts +++ b/packages/babel-helper-module-transforms/src/rewrite-this.ts @@ -1,7 +1,6 @@ import environmentVisitor from "@babel/helper-environment-visitor"; import traverse from "@babel/traverse"; import { numericLiteral, unaryExpression } from "@babel/types"; -import type * as t from "@babel/types"; import type { NodePath, Visitor } from "@babel/traverse"; export default function rewriteThis(programPath: NodePath) { @@ -16,7 +15,7 @@ export default function rewriteThis(programPath: NodePath) { const rewriteThisVisitor: Visitor = traverse.visitors.merge([ environmentVisitor, { - ThisExpression(path: NodePath) { + ThisExpression(path) { path.replaceWith(unaryExpression("void", numericLiteral(0), true)); }, }, diff --git a/packages/babel-helper-optimise-call-expression/src/index.ts b/packages/babel-helper-optimise-call-expression/src/index.ts index 881aef8dd161..b0327d355b53 100644 --- a/packages/babel-helper-optimise-call-expression/src/index.ts +++ b/packages/babel-helper-optimise-call-expression/src/index.ts @@ -11,7 +11,6 @@ import type { CallExpression, Expression, OptionalCallExpression, - SpreadElement, } from "@babel/types"; /** @@ -21,14 +20,14 @@ import type { * @export * @param {Expression} callee The callee of call expression * @param {Expression} thisNode The desired this of call expression - * @param {Readonly>} args The arguments of call expression + * @param {Readonly} args The arguments of call expression * @param {boolean} optional Whether the call expression is optional * @returns {CallExpression | OptionalCallExpression} The generated new call expression */ export default function optimiseCallExpression( callee: Expression, thisNode: Expression, - args: Readonly>, + args: Readonly, optional: boolean, ): CallExpression | OptionalCallExpression { if ( diff --git a/packages/babel-helper-plugin-test-runner/src/index.ts b/packages/babel-helper-plugin-test-runner/src/index.ts index 44e5a7d56f32..f5a43239e7a2 100644 --- a/packages/babel-helper-plugin-test-runner/src/index.ts +++ b/packages/babel-helper-plugin-test-runner/src/index.ts @@ -2,7 +2,7 @@ import testRunner from "@babel/helper-transform-fixture-test-runner"; import path from "path"; import { URL } from "url"; -export default function (loc) { +export default function (loc: string) { if (!process.env.BABEL_8_BREAKING) { if (!loc.startsWith("file://")) { const name = path.basename(path.dirname(loc)); diff --git a/packages/babel-helper-plugin-utils/src/index.ts b/packages/babel-helper-plugin-utils/src/index.ts index 96c4296b4a24..d1327b6bf0ce 100644 --- a/packages/babel-helper-plugin-utils/src/index.ts +++ b/packages/babel-helper-plugin-utils/src/index.ts @@ -19,13 +19,16 @@ export function declare( ) => PluginObject { // @ts-ignore return (api, options: Option, dirname: string) => { - let clonedApi; + let clonedApi: PluginAPI; - for (const name of Object.keys(apiPolyfills)) { + for (const name of Object.keys( + apiPolyfills, + ) as (keyof typeof apiPolyfills)[]) { if (api[name]) continue; // TODO: Use ??= when flow lets us to do so clonedApi = clonedApi ?? copyApiObject(api); + // @ts-expect-error The shape of API polyfill is guaranteed by APIPolyfillFactory clonedApi[name] = apiPolyfills[name](clonedApi); } @@ -38,11 +41,21 @@ export const declarePreset = declare as