diff --git a/.flowconfig b/.flowconfig index 77b3550cda2b..c003d67d151d 100644 --- a/.flowconfig +++ b/.flowconfig @@ -28,3 +28,4 @@ esproposal.export_star_as=enable esproposal.optional_chaining=enable esproposal.nullish_coalescing=enable module.name_mapper='^@babel\/\([a-zA-Z0-9_\-]+\)$' -> '/packages/babel-\1/src/index' +module.ignore_non_literal_requires=true diff --git a/lib/third-party-libs.js.flow b/lib/third-party-libs.js.flow index b41fbe725b4e..f9723ea6fe4e 100644 --- a/lib/third-party-libs.js.flow +++ b/lib/third-party-libs.js.flow @@ -2,6 +2,10 @@ * Basic declarations for the npm modules we use. */ +declare module "debug" { + declare export default (namespace: string) => (formatter: string, ...args: any[]) => void; +} + declare module "resolve" { declare export default { (string, {| basedir: string |}, (err: ?Error, res: string) => void): void; @@ -27,6 +31,10 @@ declare module "lodash/merge" { declare export default (T, Object) => T; } +declare module "lodash/escapeRegExp" { + declare export default (toEscape?: string) => string; +} + declare module "semver" { declare class SemVer { build: Array; diff --git a/packages/babel-core/src/config/caching.js b/packages/babel-core/src/config/caching.js index 6b6e7967a293..050c59d856dc 100644 --- a/packages/babel-core/src/config/caching.js +++ b/packages/babel-core/src/config/caching.js @@ -108,7 +108,7 @@ function makeCachedFunction( const asyncContext = yield* isAsync(); const callCache = asyncContext ? callCacheAsync : callCacheSync; - const cached = yield* getCachedValueOrWait( + const cached = yield* getCachedValueOrWait( asyncContext, callCache, futureCache, @@ -119,7 +119,7 @@ function makeCachedFunction( const cache = new CacheConfigurator(data); - const handlerResult = handler(arg, cache); + const handlerResult: Handler | ResultT = handler(arg, cache); let finishLock: ?Lock; let value: ResultT; @@ -313,7 +313,7 @@ class CacheConfigurator { ); if (isThenable(key)) { - return key.then(key => { + return key.then((key: mixed) => { this._pairs.push([key, fn]); return key; }); @@ -369,7 +369,13 @@ function makeSimpleConfigurator( // Types are limited here so that in the future these values can be used // as part of Babel's caching logic. -type SimpleType = string | boolean | number | null | void | Promise; +export type SimpleType = + | string + | boolean + | number + | null + | void + | Promise; export function assertSimpleType(value: mixed): SimpleType { if (isThenable(value)) { throw new Error( diff --git a/packages/babel-core/src/config/config-chain.js b/packages/babel-core/src/config/config-chain.js index 4fc2f8d0efcf..f54dc4dcd6bd 100644 --- a/packages/babel-core/src/config/config-chain.js +++ b/packages/babel-core/src/config/config-chain.js @@ -76,7 +76,6 @@ export const buildPresetChainWalker: ( arg: PresetInstance, context: *, ) => * = makeChainWalker({ - init: arg => arg, root: preset => loadPresetDescriptors(preset), env: (preset, envName) => loadPresetEnvDescriptors(preset)(envName), overrides: (preset, index) => loadPresetOverridesDescriptors(preset)(index), @@ -419,12 +418,12 @@ function makeChainWalker({ env, overrides, overridesEnv, -}: { +}: {| root: ArgT => OptionsAndDescriptors, env: (ArgT, string) => OptionsAndDescriptors | null, overrides: (ArgT, number) => OptionsAndDescriptors, overridesEnv: (ArgT, number, string) => OptionsAndDescriptors | null, -}): ( +|}): ( ArgT, ConfigContext, Set | void, diff --git a/packages/babel-core/src/config/files/configuration.js b/packages/babel-core/src/config/files/configuration.js index f119bc1f8101..887c0334fdf2 100644 --- a/packages/babel-core/src/config/files/configuration.js +++ b/packages/babel-core/src/config/files/configuration.js @@ -151,7 +151,7 @@ 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) { +function readConfig(filepath, envName, caller): Handler { const ext = path.extname(filepath); return ext === ".js" || ext === ".cjs" || ext === ".mjs" ? readConfigJS(filepath, { envName, caller }) @@ -236,7 +236,7 @@ const readConfigJS = makeStrongCache(function* readConfigJS( const packageToBabelConfig = makeWeakCacheSync( (file: ConfigFile): ConfigFile | null => { - const babel = file.options[("babel": string)]; + const babel: mixed = file.options[("babel": string)]; if (typeof babel === "undefined") return null; @@ -252,7 +252,7 @@ const packageToBabelConfig = makeWeakCacheSync( }, ); -const readConfigJSON5 = makeStaticFileCache((filepath, content) => { +const readConfigJSON5 = makeStaticFileCache((filepath, content): ConfigFile => { let options; try { options = json5.parse(content); @@ -281,7 +281,7 @@ const readIgnoreConfig = makeStaticFileCache((filepath, content) => { const ignoreDir = path.dirname(filepath); const ignorePatterns = content .split("\n") - .map(line => line.replace(/#(.*?)$/, "").trim()) + .map(line => line.replace(/#(.*?)$/, "").trim()) .filter(line => !!line); for (const pattern of ignorePatterns) { @@ -299,7 +299,7 @@ const readIgnoreConfig = makeStaticFileCache((filepath, content) => { }; }); -function throwConfigError() { +function throwConfigError(): empty { throw new Error(`\ Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured for various types of caching, using the first param of their handler functions: diff --git a/packages/babel-core/src/config/files/import.js b/packages/babel-core/src/config/files/import.js index e16bc29752a9..d81b18f7539f 100644 --- a/packages/babel-core/src/config/files/import.js +++ b/packages/babel-core/src/config/files/import.js @@ -1,3 +1,4 @@ +// @flow // We keep this in a seprate file so that in older node versions, where // import() isn't supported, we can try/catch around the require() call // when loading this file. diff --git a/packages/babel-core/src/config/files/package.js b/packages/babel-core/src/config/files/package.js index 278ddfb852f5..99ac13364e77 100644 --- a/packages/babel-core/src/config/files/package.js +++ b/packages/babel-core/src/config/files/package.js @@ -39,12 +39,14 @@ const readConfigPackage = makeStaticFileCache( (filepath, content): ConfigFile => { let options; try { - options = JSON.parse(content); + options = (JSON.parse(content): mixed); } catch (err) { err.message = `${filepath}: Error while parsing JSON - ${err.message}`; throw err; } + if (!options) throw new Error(`${filepath}: No config detected`); + if (typeof options !== "object") { throw new Error(`${filepath}: Config returned typeof ${typeof options}`); } diff --git a/packages/babel-core/src/config/files/plugins.js b/packages/babel-core/src/config/files/plugins.js index be20468d1615..47cfa8645003 100644 --- a/packages/babel-core/src/config/files/plugins.js +++ b/packages/babel-core/src/config/files/plugins.js @@ -105,7 +105,7 @@ function resolveStandardizedName( try { resolve.sync(name, { basedir: dirname }); resolvedOriginal = true; - } catch (e2) {} + } catch {} if (resolvedOriginal) { e.message += `\n- If you want to resolve "${name}", use "module:${name}"`; @@ -118,7 +118,7 @@ function resolveStandardizedName( basedir: dirname, }); resolvedBabel = true; - } catch (e2) {} + } catch {} if (resolvedBabel) { e.message += `\n- Did you mean "@babel/${name}"?`; @@ -129,7 +129,7 @@ function resolveStandardizedName( try { resolve.sync(standardizeName(oppositeType, name), { basedir: dirname }); resolvedOppositeType = true; - } catch (e2) {} + } catch {} if (resolvedOppositeType) { e.message += `\n- Did you accidentally pass a ${oppositeType} as a ${type}?`; @@ -151,7 +151,6 @@ function requireModule(type: string, name: string): mixed { try { LOADING_MODULES.add(name); - // $FlowIssue return require(name); } finally { LOADING_MODULES.delete(name); diff --git a/packages/babel-core/src/config/full.js b/packages/babel-core/src/config/full.js index c9fb038a3074..04fa5b599083 100644 --- a/packages/babel-core/src/config/full.js +++ b/packages/babel-core/src/config/full.js @@ -66,7 +66,7 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig( const { options, context } = result; const optionDefaults = {}; - const passes = [[]]; + const passes: Array> = [[]]; try { const { plugins, presets } = options; @@ -74,14 +74,8 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig( throw new Error("Assertion failure - plugins and presets exist"); } - const ignored = yield* (function* recurseDescriptors( - config: { - plugins: Array, - presets: Array, - }, - pass: Array, - ) { - const plugins = []; + const ignored = yield* (function* recurseDescriptors(config, pass) { + const plugins: Array = []; for (let i = 0; i < config.plugins.length; i++) { const descriptor = config.plugins[i]; if (descriptor.options !== false) { @@ -103,7 +97,10 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig( } } - const presets = []; + const presets: Array<{| + preset: ConfigChain | null, + pass: Array, + |}> = []; for (let i = 0; i < config.presets.length; i++) { const descriptor = config.presets[i]; if (descriptor.options !== false) { diff --git a/packages/babel-core/src/config/helpers/config-api.js b/packages/babel-core/src/config/helpers/config-api.js index f49e038f764e..fea73139af68 100644 --- a/packages/babel-core/src/config/helpers/config-api.js +++ b/packages/babel-core/src/config/helpers/config-api.js @@ -6,6 +6,7 @@ import { assertSimpleType, type CacheConfigurator, type SimpleCacheConfigurator, + type SimpleType, } from "../caching"; import type { CallerMetadata } from "../validation/options"; @@ -17,13 +18,15 @@ type EnvFunction = { (Array): boolean, }; +type CallerFactory = ((CallerMetadata | void) => mixed) => SimpleType; + export type PluginAPI = {| version: string, cache: SimpleCacheConfigurator, env: EnvFunction, async: () => boolean, assertVersion: typeof assertVersion, - caller?: any, + caller?: CallerFactory, |}; export default function makeAPI( @@ -37,7 +40,7 @@ export default function makeAPI( } if (!Array.isArray(value)) value = [value]; - return value.some(entry => { + return value.some((entry: mixed) => { if (typeof entry !== "string") { throw new Error("Unexpected non-string value"); } @@ -45,8 +48,7 @@ export default function makeAPI( }); }); - const caller: any = cb => - cache.using(data => assertSimpleType(cb(data.caller))); + const caller = cb => cache.using(data => assertSimpleType(cb(data.caller))); return { version: coreVersion, diff --git a/packages/babel-core/src/config/item.js b/packages/babel-core/src/config/item.js index c847df029e52..d1f548ffe0fa 100644 --- a/packages/babel-core/src/config/item.js +++ b/packages/babel-core/src/config/item.js @@ -103,7 +103,7 @@ class ConfigItem { // programmatically, and also make sure that if people happen to // pass the item through JSON.stringify, it doesn't show up. this._descriptor = descriptor; - Object.defineProperty(this, "_descriptor", ({ enumerable: false }: any)); + Object.defineProperty(this, "_descriptor", { enumerable: false }); this.value = this._descriptor.value; this.options = this._descriptor.options; diff --git a/packages/babel-core/src/config/plugin.js b/packages/babel-core/src/config/plugin.js index 6da0fe636014..c034a0976c31 100644 --- a/packages/babel-core/src/config/plugin.js +++ b/packages/babel-core/src/config/plugin.js @@ -4,7 +4,7 @@ import type { PluginObject } from "./validation/plugins"; export default class Plugin { key: ?string; - manipulateOptions: Function | void; + manipulateOptions: ((options: mixed, parserOpts: mixed) => void) | void; post: Function | void; pre: Function | void; visitor: {}; diff --git a/packages/babel-core/src/config/validation/option-assertions.js b/packages/babel-core/src/config/validation/option-assertions.js index a279a72781a0..f3962f18945f 100644 --- a/packages/babel-core/src/config/validation/option-assertions.js +++ b/packages/babel-core/src/config/validation/option-assertions.js @@ -18,6 +18,8 @@ import type { RootMode, } from "./options"; +export type { RootPath } from "./options"; + export type ValidatorSet = { [string]: Validator, }; @@ -192,7 +194,10 @@ export function assertBoolean(loc: GeneralPath, value: mixed): boolean | void { return value; } -export function assertObject(loc: GeneralPath, value: mixed): {} | void { +export function assertObject( + loc: GeneralPath, + value: mixed, +): { +[string]: mixed } | void { if ( value !== undefined && (typeof value !== "object" || Array.isArray(value) || !value) diff --git a/packages/babel-core/src/config/validation/options.js b/packages/babel-core/src/config/validation/options.js index c3e7f359cd90..d4500a747c26 100644 --- a/packages/babel-core/src/config/validation/options.js +++ b/packages/babel-core/src/config/validation/options.js @@ -276,7 +276,7 @@ export type OptionsSource = | "preset" | "plugin"; -type RootPath = $ReadOnly<{ +export type RootPath = $ReadOnly<{ type: "root", source: OptionsSource, }>; @@ -311,7 +311,7 @@ function validateNested(loc: NestingPath, opts: {}) { assertNoDuplicateSourcemap(opts); - Object.keys(opts).forEach(key => { + Object.keys(opts).forEach((key: string) => { const optLoc = { type: "option", name: key, @@ -364,7 +364,10 @@ function throwUnknownError(loc: OptionPath) { const key = loc.name; if (removed[key]) { - const { message, version = 5 } = removed[key]; + const { + message, + version = 5, + }: { message: string, version?: number } = removed[key]; throw new Error( `Using removed Babel ${version} option: ${msg(loc)} - ${message}`, diff --git a/packages/babel-core/src/config/validation/plugins.js b/packages/babel-core/src/config/validation/plugins.js index 06778caff269..63510dd13c25 100644 --- a/packages/babel-core/src/config/validation/plugins.js +++ b/packages/babel-core/src/config/validation/plugins.js @@ -1,9 +1,13 @@ +// @flow import { assertString, assertFunction, assertObject, + msg, type ValidatorSet, type Validator, + type OptionPath, + type RootPath, } from "./option-assertions"; // Note: The casts here are just meant to be static assertions to make sure @@ -31,14 +35,16 @@ const VALIDATORS: ValidatorSet = { >), }; -function assertVisitorMap(key: string, value: mixed): VisitorMap { - const obj = assertObject(key, value); +function assertVisitorMap(loc: OptionPath, value: mixed): VisitorMap { + const obj = assertObject(loc, value); if (obj) { Object.keys(obj).forEach(prop => assertVisitorHandler(prop, obj[prop])); if (obj.enter || obj.exit) { throw new Error( - `.${key} cannot contain catch-all "enter" or "exit" handlers. Please target individual nodes.`, + `${msg( + loc, + )} cannot contain catch-all "enter" or "exit" handlers. Please target individual nodes.`, ); } } @@ -50,7 +56,7 @@ function assertVisitorHandler( value: mixed, ): VisitorHandler | void { if (value && typeof value === "object") { - Object.keys(value).forEach(handler => { + Object.keys(value).forEach((handler: string) => { if (handler !== "enter" && handler !== "exit") { throw new Error( `.visitor["${key}"] may only have .enter and/or .exit handlers.`, @@ -71,7 +77,7 @@ export type VisitorMap = { export type PluginObject = { name?: string, - manipulateOptions?: Function, + manipulateOptions?: (options: mixed, parserOpts: mixed) => void, pre?: Function, post?: Function, @@ -88,16 +94,17 @@ export function validatePluginObject(obj: {}): PluginObject { type: "root", source: "plugin", }; - Object.keys(obj).forEach(key => { + Object.keys(obj).forEach((key: string) => { const validator = VALIDATORS[key]; - const optLoc = { - type: "option", - name: key, - parent: rootPath, - }; - if (validator) validator(optLoc, obj[key]); - else { + if (validator) { + const optLoc: OptionPath = { + type: "option", + name: key, + parent: rootPath, + }; + validator(optLoc, obj[key]); + } else { const invalidPluginPropertyError = new Error( `.${key} is not a valid Plugin property`, ); diff --git a/packages/babel-core/test/config-loading.js b/packages/babel-core/test/config-loading.js index d62a948254cf..736534ecd8ae 100644 --- a/packages/babel-core/test/config-loading.js +++ b/packages/babel-core/test/config-loading.js @@ -339,6 +339,23 @@ describe("@babel/core config loading", () => { /\.inherits must be a function, or undefined/, ); }); + + it("should throw when plugin contains `enter` handler", () => { + const fooPlugin = { + visitor: { + enter() {}, + }, + }; + const opts = { + cwd: path.dirname(FILEPATH), + filename: FILEPATH, + plugins: [fooPlugin], + }; + + expect(() => loadConfig(opts)).toThrow( + /\.visitor cannot contain catch-all "enter" or "exit" handlers\. Please target individual nodes\./, + ); + }); }); describe("caller metadata", () => {