Refine babel core types (#11544)

Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
This commit is contained in:
Huáng Jùnliàng 2020-06-21 16:04:12 -04:00 committed by GitHub
parent 30835f14db
commit 601c824873
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 97 additions and 50 deletions

View File

@ -28,3 +28,4 @@ esproposal.export_star_as=enable
esproposal.optional_chaining=enable esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable esproposal.nullish_coalescing=enable
module.name_mapper='^@babel\/\([a-zA-Z0-9_\-]+\)$' -> '<PROJECT_ROOT>/packages/babel-\1/src/index' module.name_mapper='^@babel\/\([a-zA-Z0-9_\-]+\)$' -> '<PROJECT_ROOT>/packages/babel-\1/src/index'
module.ignore_non_literal_requires=true

View File

@ -2,6 +2,10 @@
* Basic declarations for the npm modules we use. * 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 module "resolve" {
declare export default { declare export default {
(string, {| basedir: string |}, (err: ?Error, res: string) => void): void; (string, {| basedir: string |}, (err: ?Error, res: string) => void): void;
@ -27,6 +31,10 @@ declare module "lodash/merge" {
declare export default <T: Object>(T, Object) => T; declare export default <T: Object>(T, Object) => T;
} }
declare module "lodash/escapeRegExp" {
declare export default (toEscape?: string) => string;
}
declare module "semver" { declare module "semver" {
declare class SemVer { declare class SemVer {
build: Array<string>; build: Array<string>;

View File

@ -108,7 +108,7 @@ function makeCachedFunction<ArgT, ResultT, SideChannel, Cache: *>(
const asyncContext = yield* isAsync(); const asyncContext = yield* isAsync();
const callCache = asyncContext ? callCacheAsync : callCacheSync; const callCache = asyncContext ? callCacheAsync : callCacheSync;
const cached = yield* getCachedValueOrWait( const cached = yield* getCachedValueOrWait<ArgT, ResultT, SideChannel>(
asyncContext, asyncContext,
callCache, callCache,
futureCache, futureCache,
@ -119,7 +119,7 @@ function makeCachedFunction<ArgT, ResultT, SideChannel, Cache: *>(
const cache = new CacheConfigurator(data); const cache = new CacheConfigurator(data);
const handlerResult = handler(arg, cache); const handlerResult: Handler<ResultT> | ResultT = handler(arg, cache);
let finishLock: ?Lock<ResultT>; let finishLock: ?Lock<ResultT>;
let value: ResultT; let value: ResultT;
@ -313,7 +313,7 @@ class CacheConfigurator<SideChannel = void> {
); );
if (isThenable(key)) { if (isThenable(key)) {
return key.then(key => { return key.then((key: mixed) => {
this._pairs.push([key, fn]); this._pairs.push([key, fn]);
return key; return key;
}); });
@ -369,7 +369,13 @@ function makeSimpleConfigurator(
// Types are limited here so that in the future these values can be used // Types are limited here so that in the future these values can be used
// as part of Babel's caching logic. // as part of Babel's caching logic.
type SimpleType = string | boolean | number | null | void | Promise<SimpleType>; export type SimpleType =
| string
| boolean
| number
| null
| void
| Promise<SimpleType>;
export function assertSimpleType(value: mixed): SimpleType { export function assertSimpleType(value: mixed): SimpleType {
if (isThenable(value)) { if (isThenable(value)) {
throw new Error( throw new Error(

View File

@ -76,7 +76,6 @@ export const buildPresetChainWalker: (
arg: PresetInstance, arg: PresetInstance,
context: *, context: *,
) => * = makeChainWalker({ ) => * = makeChainWalker({
init: arg => arg,
root: preset => loadPresetDescriptors(preset), root: preset => loadPresetDescriptors(preset),
env: (preset, envName) => loadPresetEnvDescriptors(preset)(envName), env: (preset, envName) => loadPresetEnvDescriptors(preset)(envName),
overrides: (preset, index) => loadPresetOverridesDescriptors(preset)(index), overrides: (preset, index) => loadPresetOverridesDescriptors(preset)(index),
@ -419,12 +418,12 @@ function makeChainWalker<ArgT: { options: ValidatedOptions, dirname: string }>({
env, env,
overrides, overrides,
overridesEnv, overridesEnv,
}: { }: {|
root: ArgT => OptionsAndDescriptors, root: ArgT => OptionsAndDescriptors,
env: (ArgT, string) => OptionsAndDescriptors | null, env: (ArgT, string) => OptionsAndDescriptors | null,
overrides: (ArgT, number) => OptionsAndDescriptors, overrides: (ArgT, number) => OptionsAndDescriptors,
overridesEnv: (ArgT, number, string) => OptionsAndDescriptors | null, overridesEnv: (ArgT, number, string) => OptionsAndDescriptors | null,
}): ( |}): (
ArgT, ArgT,
ConfigContext, ConfigContext,
Set<ConfigFile> | void, Set<ConfigFile> | void,

View File

@ -151,7 +151,7 @@ export function* loadConfig(
* Read the given config file, returning the result. Returns null if no config was found, but will * 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. * throw if there are parsing errors while loading a config.
*/ */
function readConfig(filepath, envName, caller) { function readConfig(filepath, envName, caller): Handler<ConfigFile | null> {
const ext = path.extname(filepath); const ext = path.extname(filepath);
return ext === ".js" || ext === ".cjs" || ext === ".mjs" return ext === ".js" || ext === ".cjs" || ext === ".mjs"
? readConfigJS(filepath, { envName, caller }) ? readConfigJS(filepath, { envName, caller })
@ -236,7 +236,7 @@ const readConfigJS = makeStrongCache(function* readConfigJS(
const packageToBabelConfig = makeWeakCacheSync( const packageToBabelConfig = makeWeakCacheSync(
(file: ConfigFile): ConfigFile | null => { (file: ConfigFile): ConfigFile | null => {
const babel = file.options[("babel": string)]; const babel: mixed = file.options[("babel": string)];
if (typeof babel === "undefined") return null; 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; let options;
try { try {
options = json5.parse(content); options = json5.parse(content);
@ -281,7 +281,7 @@ const readIgnoreConfig = makeStaticFileCache((filepath, content) => {
const ignoreDir = path.dirname(filepath); const ignoreDir = path.dirname(filepath);
const ignorePatterns = content const ignorePatterns = content
.split("\n") .split("\n")
.map(line => line.replace(/#(.*?)$/, "").trim()) .map<string>(line => line.replace(/#(.*?)$/, "").trim())
.filter(line => !!line); .filter(line => !!line);
for (const pattern of ignorePatterns) { for (const pattern of ignorePatterns) {
@ -299,7 +299,7 @@ const readIgnoreConfig = makeStaticFileCache((filepath, content) => {
}; };
}); });
function throwConfigError() { function throwConfigError(): empty {
throw new Error(`\ throw new Error(`\
Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured 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: for various types of caching, using the first param of their handler functions:

View File

@ -1,3 +1,4 @@
// @flow
// We keep this in a seprate file so that in older node versions, where // 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 // import() isn't supported, we can try/catch around the require() call
// when loading this file. // when loading this file.

View File

@ -39,12 +39,14 @@ const readConfigPackage = makeStaticFileCache(
(filepath, content): ConfigFile => { (filepath, content): ConfigFile => {
let options; let options;
try { try {
options = JSON.parse(content); options = (JSON.parse(content): mixed);
} catch (err) { } catch (err) {
err.message = `${filepath}: Error while parsing JSON - ${err.message}`; err.message = `${filepath}: Error while parsing JSON - ${err.message}`;
throw err; throw err;
} }
if (!options) throw new Error(`${filepath}: No config detected`);
if (typeof options !== "object") { if (typeof options !== "object") {
throw new Error(`${filepath}: Config returned typeof ${typeof options}`); throw new Error(`${filepath}: Config returned typeof ${typeof options}`);
} }

View File

@ -105,7 +105,7 @@ function resolveStandardizedName(
try { try {
resolve.sync(name, { basedir: dirname }); resolve.sync(name, { basedir: dirname });
resolvedOriginal = true; resolvedOriginal = true;
} catch (e2) {} } catch {}
if (resolvedOriginal) { if (resolvedOriginal) {
e.message += `\n- If you want to resolve "${name}", use "module:${name}"`; e.message += `\n- If you want to resolve "${name}", use "module:${name}"`;
@ -118,7 +118,7 @@ function resolveStandardizedName(
basedir: dirname, basedir: dirname,
}); });
resolvedBabel = true; resolvedBabel = true;
} catch (e2) {} } catch {}
if (resolvedBabel) { if (resolvedBabel) {
e.message += `\n- Did you mean "@babel/${name}"?`; e.message += `\n- Did you mean "@babel/${name}"?`;
@ -129,7 +129,7 @@ function resolveStandardizedName(
try { try {
resolve.sync(standardizeName(oppositeType, name), { basedir: dirname }); resolve.sync(standardizeName(oppositeType, name), { basedir: dirname });
resolvedOppositeType = true; resolvedOppositeType = true;
} catch (e2) {} } catch {}
if (resolvedOppositeType) { if (resolvedOppositeType) {
e.message += `\n- Did you accidentally pass a ${oppositeType} as a ${type}?`; e.message += `\n- Did you accidentally pass a ${oppositeType} as a ${type}?`;
@ -151,7 +151,6 @@ function requireModule(type: string, name: string): mixed {
try { try {
LOADING_MODULES.add(name); LOADING_MODULES.add(name);
// $FlowIssue
return require(name); return require(name);
} finally { } finally {
LOADING_MODULES.delete(name); LOADING_MODULES.delete(name);

View File

@ -66,7 +66,7 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
const { options, context } = result; const { options, context } = result;
const optionDefaults = {}; const optionDefaults = {};
const passes = [[]]; const passes: Array<Array<Plugin>> = [[]];
try { try {
const { plugins, presets } = options; 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"); throw new Error("Assertion failure - plugins and presets exist");
} }
const ignored = yield* (function* recurseDescriptors( const ignored = yield* (function* recurseDescriptors(config, pass) {
config: { const plugins: Array<Plugin> = [];
plugins: Array<UnloadedDescriptor>,
presets: Array<UnloadedDescriptor>,
},
pass: Array<Plugin>,
) {
const plugins = [];
for (let i = 0; i < config.plugins.length; i++) { for (let i = 0; i < config.plugins.length; i++) {
const descriptor = config.plugins[i]; const descriptor = config.plugins[i];
if (descriptor.options !== false) { 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<Plugin>,
|}> = [];
for (let i = 0; i < config.presets.length; i++) { for (let i = 0; i < config.presets.length; i++) {
const descriptor = config.presets[i]; const descriptor = config.presets[i];
if (descriptor.options !== false) { if (descriptor.options !== false) {

View File

@ -6,6 +6,7 @@ import {
assertSimpleType, assertSimpleType,
type CacheConfigurator, type CacheConfigurator,
type SimpleCacheConfigurator, type SimpleCacheConfigurator,
type SimpleType,
} from "../caching"; } from "../caching";
import type { CallerMetadata } from "../validation/options"; import type { CallerMetadata } from "../validation/options";
@ -17,13 +18,15 @@ type EnvFunction = {
(Array<string>): boolean, (Array<string>): boolean,
}; };
type CallerFactory = ((CallerMetadata | void) => mixed) => SimpleType;
export type PluginAPI = {| export type PluginAPI = {|
version: string, version: string,
cache: SimpleCacheConfigurator, cache: SimpleCacheConfigurator,
env: EnvFunction, env: EnvFunction,
async: () => boolean, async: () => boolean,
assertVersion: typeof assertVersion, assertVersion: typeof assertVersion,
caller?: any, caller?: CallerFactory,
|}; |};
export default function makeAPI( export default function makeAPI(
@ -37,7 +40,7 @@ export default function makeAPI(
} }
if (!Array.isArray(value)) value = [value]; if (!Array.isArray(value)) value = [value];
return value.some(entry => { return value.some((entry: mixed) => {
if (typeof entry !== "string") { if (typeof entry !== "string") {
throw new Error("Unexpected non-string value"); throw new Error("Unexpected non-string value");
} }
@ -45,8 +48,7 @@ export default function makeAPI(
}); });
}); });
const caller: any = cb => const caller = cb => cache.using(data => assertSimpleType(cb(data.caller)));
cache.using(data => assertSimpleType(cb(data.caller)));
return { return {
version: coreVersion, version: coreVersion,

View File

@ -103,7 +103,7 @@ class ConfigItem {
// programmatically, and also make sure that if people happen to // programmatically, and also make sure that if people happen to
// pass the item through JSON.stringify, it doesn't show up. // pass the item through JSON.stringify, it doesn't show up.
this._descriptor = descriptor; this._descriptor = descriptor;
Object.defineProperty(this, "_descriptor", ({ enumerable: false }: any)); Object.defineProperty(this, "_descriptor", { enumerable: false });
this.value = this._descriptor.value; this.value = this._descriptor.value;
this.options = this._descriptor.options; this.options = this._descriptor.options;

View File

@ -4,7 +4,7 @@ import type { PluginObject } from "./validation/plugins";
export default class Plugin { export default class Plugin {
key: ?string; key: ?string;
manipulateOptions: Function | void; manipulateOptions: ((options: mixed, parserOpts: mixed) => void) | void;
post: Function | void; post: Function | void;
pre: Function | void; pre: Function | void;
visitor: {}; visitor: {};

View File

@ -18,6 +18,8 @@ import type {
RootMode, RootMode,
} from "./options"; } from "./options";
export type { RootPath } from "./options";
export type ValidatorSet = { export type ValidatorSet = {
[string]: Validator<any>, [string]: Validator<any>,
}; };
@ -192,7 +194,10 @@ export function assertBoolean(loc: GeneralPath, value: mixed): boolean | void {
return value; return value;
} }
export function assertObject(loc: GeneralPath, value: mixed): {} | void { export function assertObject(
loc: GeneralPath,
value: mixed,
): { +[string]: mixed } | void {
if ( if (
value !== undefined && value !== undefined &&
(typeof value !== "object" || Array.isArray(value) || !value) (typeof value !== "object" || Array.isArray(value) || !value)

View File

@ -276,7 +276,7 @@ export type OptionsSource =
| "preset" | "preset"
| "plugin"; | "plugin";
type RootPath = $ReadOnly<{ export type RootPath = $ReadOnly<{
type: "root", type: "root",
source: OptionsSource, source: OptionsSource,
}>; }>;
@ -311,7 +311,7 @@ function validateNested(loc: NestingPath, opts: {}) {
assertNoDuplicateSourcemap(opts); assertNoDuplicateSourcemap(opts);
Object.keys(opts).forEach(key => { Object.keys(opts).forEach((key: string) => {
const optLoc = { const optLoc = {
type: "option", type: "option",
name: key, name: key,
@ -364,7 +364,10 @@ function throwUnknownError(loc: OptionPath) {
const key = loc.name; const key = loc.name;
if (removed[key]) { if (removed[key]) {
const { message, version = 5 } = removed[key]; const {
message,
version = 5,
}: { message: string, version?: number } = removed[key];
throw new Error( throw new Error(
`Using removed Babel ${version} option: ${msg(loc)} - ${message}`, `Using removed Babel ${version} option: ${msg(loc)} - ${message}`,

View File

@ -1,9 +1,13 @@
// @flow
import { import {
assertString, assertString,
assertFunction, assertFunction,
assertObject, assertObject,
msg,
type ValidatorSet, type ValidatorSet,
type Validator, type Validator,
type OptionPath,
type RootPath,
} from "./option-assertions"; } from "./option-assertions";
// Note: The casts here are just meant to be static assertions to make sure // 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 { function assertVisitorMap(loc: OptionPath, value: mixed): VisitorMap {
const obj = assertObject(key, value); const obj = assertObject(loc, value);
if (obj) { if (obj) {
Object.keys(obj).forEach(prop => assertVisitorHandler(prop, obj[prop])); Object.keys(obj).forEach(prop => assertVisitorHandler(prop, obj[prop]));
if (obj.enter || obj.exit) { if (obj.enter || obj.exit) {
throw new Error( 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, value: mixed,
): VisitorHandler | void { ): VisitorHandler | void {
if (value && typeof value === "object") { if (value && typeof value === "object") {
Object.keys(value).forEach(handler => { Object.keys(value).forEach((handler: string) => {
if (handler !== "enter" && handler !== "exit") { if (handler !== "enter" && handler !== "exit") {
throw new Error( throw new Error(
`.visitor["${key}"] may only have .enter and/or .exit handlers.`, `.visitor["${key}"] may only have .enter and/or .exit handlers.`,
@ -71,7 +77,7 @@ export type VisitorMap = {
export type PluginObject = { export type PluginObject = {
name?: string, name?: string,
manipulateOptions?: Function, manipulateOptions?: (options: mixed, parserOpts: mixed) => void,
pre?: Function, pre?: Function,
post?: Function, post?: Function,
@ -88,16 +94,17 @@ export function validatePluginObject(obj: {}): PluginObject {
type: "root", type: "root",
source: "plugin", source: "plugin",
}; };
Object.keys(obj).forEach(key => { Object.keys(obj).forEach((key: string) => {
const validator = VALIDATORS[key]; const validator = VALIDATORS[key];
const optLoc = {
if (validator) {
const optLoc: OptionPath = {
type: "option", type: "option",
name: key, name: key,
parent: rootPath, parent: rootPath,
}; };
validator(optLoc, obj[key]);
if (validator) validator(optLoc, obj[key]); } else {
else {
const invalidPluginPropertyError = new Error( const invalidPluginPropertyError = new Error(
`.${key} is not a valid Plugin property`, `.${key} is not a valid Plugin property`,
); );

View File

@ -339,6 +339,23 @@ describe("@babel/core config loading", () => {
/\.inherits must be a function, or undefined/, /\.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", () => { describe("caller metadata", () => {