Perform option validation up front to avoid repeating assertions.

This commit is contained in:
Logan Smyth 2017-10-16 18:33:32 -07:00
parent 3673fbbd52
commit 64abf75d1f
9 changed files with 571 additions and 307 deletions

View File

@ -4,6 +4,12 @@ import { getEnv } from "./helpers/environment";
import path from "path";
import micromatch from "micromatch";
import buildDebug from "debug";
import {
validate,
type ValidatedOptions,
type PluginList,
type IgnoreList,
} from "./options";
const debug = buildDebug("babel:config:config-chain");
@ -11,19 +17,19 @@ import { findConfigs, loadConfig, type ConfigFile } from "./loading/files";
import { makeWeakCache, makeStrongCache } from "./caching";
type ConfigItem = {
type: "options" | "arguments",
options: {},
dirname: string,
export type ConfigItem = {
type: "arguments" | "env" | "file",
options: ValidatedOptions,
alias: string,
dirname: string,
};
type ConfigPart =
| {
part: "config",
config: ConfigItem,
ignore: ?Array<mixed>,
only: ?Array<mixed>,
ignore: ?IgnoreList,
only: ?IgnoreList,
activeEnv: string | null,
}
| {
@ -33,11 +39,9 @@ type ConfigPart =
activeEnv: string | null,
};
export default function buildConfigChain(opts: {}): Array<ConfigItem> | null {
if (typeof opts.filename !== "string" && opts.filename != null) {
throw new Error(".filename must be a string, null, or undefined");
}
export default function buildConfigChain(
opts: ValidatedOptions,
): Array<ConfigItem> | null {
const filename = opts.filename ? path.resolve(opts.filename) : null;
const builder = new ConfigChainBuilder(
filename ? new LoadedFile(filename) : null,
@ -70,7 +74,11 @@ class ConfigChainBuilder {
this.file = file;
}
mergeConfigArguments(opts: {}, dirname: string, envKey: string) {
mergeConfigArguments(
opts: ValidatedOptions,
dirname: string,
envKey: string,
) {
flattenArgumentsOptionsParts(opts, dirname, envKey).forEach(part =>
this._processConfigPart(part, envKey),
);
@ -117,43 +125,26 @@ class ConfigChainBuilder {
* object identity preserved between calls so that they can be used for caching.
*/
function flattenArgumentsOptionsParts(
opts: {},
opts: ValidatedOptions,
dirname: string,
envKey: string,
): Array<ConfigPart> {
const {
env,
plugins,
presets,
passPerPreset,
extends: extendsPath,
...options
} = opts;
const raw = [];
const env = typeof opts.env === "object" ? opts.env : null;
const plugins = Array.isArray(opts.plugins) ? opts.plugins : null;
const presets = Array.isArray(opts.presets) ? opts.presets : null;
const passPerPreset =
typeof opts.passPerPreset === "boolean" ? opts.passPerPreset : false;
if (env) {
raw.push(...flattenArgumentsEnvOptionsParts(env)(dirname)(envKey));
}
const innerOpts = Object.assign({}, opts);
// If the env, plugins, and presets values on the object aren't arrays or
// objects, leave them in the base opts so that normal options validation
// will throw errors on them later.
if (env) delete innerOpts.env;
if (plugins) delete innerOpts.plugins;
if (presets) {
delete innerOpts.presets;
delete innerOpts.passPerPreset;
}
delete innerOpts.extends;
if (Object.keys(innerOpts).length > 0) {
raw.push(
...flattenOptionsParts({
type: "arguments",
options: innerOpts,
alias: "base",
dirname,
}),
);
if (Object.keys(options).length > 0) {
raw.push(...flattenOptionsParts(buildArgumentsItem(options, dirname)));
}
if (plugins) {
@ -161,14 +152,14 @@ function flattenArgumentsOptionsParts(
}
if (presets) {
raw.push(
...flattenArgumentsPresetsOptionsParts(presets)(passPerPreset)(dirname),
...flattenArgumentsPresetsOptionsParts(presets)(!!passPerPreset)(dirname),
);
}
if (opts.extends != null) {
if (extendsPath != null) {
raw.push(
...flattenOptionsParts(
buildArgumentsItem({ extends: opts.extends }, dirname),
buildArgumentsItem({ extends: extendsPath }, dirname),
),
);
}
@ -181,7 +172,7 @@ function flattenArgumentsOptionsParts(
* the object identity of the 'env' object.
*/
const flattenArgumentsEnvOptionsParts = makeWeakCache((env: {}) => {
const options = { env };
const options: ValidatedOptions = { env };
return makeStrongCache((dirname: string) =>
flattenOptionsPartsLookup(buildArgumentsItem(options, dirname)),
@ -193,8 +184,8 @@ const flattenArgumentsEnvOptionsParts = makeWeakCache((env: {}) => {
* the object identity of the 'plugins' object.
*/
const flattenArgumentsPluginsOptionsParts = makeWeakCache(
(plugins: Array<mixed>) => {
const options = { plugins };
(plugins: PluginList) => {
const options: ValidatedOptions = { plugins };
return makeStrongCache((dirname: string) =>
flattenOptionsParts(buildArgumentsItem(options, dirname)),
@ -207,8 +198,8 @@ const flattenArgumentsPluginsOptionsParts = makeWeakCache(
* the object identity of the 'presets' object.
*/
const flattenArgumentsPresetsOptionsParts = makeWeakCache(
(presets: Array<mixed>) =>
makeStrongCache((passPerPreset: ?boolean) => {
(presets: PluginList) =>
makeStrongCache((passPerPreset: boolean) => {
// The concept of passPerPreset is integrally tied to the preset list
// so unfortunately we need to copy both values here, adding an extra
// layer of caching functions.
@ -220,7 +211,10 @@ const flattenArgumentsPresetsOptionsParts = makeWeakCache(
}),
);
function buildArgumentsItem(options: {}, dirname: string): ConfigItem {
function buildArgumentsItem(
options: ValidatedOptions,
dirname: string,
): ConfigItem {
return {
type: "arguments",
options,
@ -236,8 +230,8 @@ function buildArgumentsItem(options: {}, dirname: string): ConfigItem {
*/
const flattenFileOptionsParts = makeWeakCache((file: ConfigFile) => {
return flattenOptionsPartsLookup({
type: "options",
options: file.options,
type: "file",
options: validate("file", file.options),
alias: file.filepath,
dirname: file.dirname,
});
@ -278,74 +272,37 @@ function flattenOptionsParts(
config: ConfigItem,
activeEnv: string | null = null,
): Array<ConfigPart> {
const { type, options: rawOpts, alias, dirname } = config;
if (rawOpts.ignore != null && !Array.isArray(rawOpts.ignore)) {
throw new Error(
`.ignore should be an array, ${JSON.stringify(rawOpts.ignore)} given`,
);
}
if (rawOpts.only != null && !Array.isArray(rawOpts.only)) {
throw new Error(
`.only should be an array, ${JSON.stringify(rawOpts.only)} given`,
);
}
const ignore = rawOpts.ignore || null;
const only = rawOpts.only || null;
const { options: rawOpts, alias, dirname } = config;
const parts = [];
if (
rawOpts.env != null &&
(typeof rawOpts.env !== "object" || Array.isArray(rawOpts.env))
) {
throw new Error(".env block must be an object, null, or undefined");
if (rawOpts.env) {
for (const envKey of Object.keys(rawOpts.env)) {
if (rawOpts.env[envKey]) {
parts.push(
...flattenOptionsParts(
{
type: "env",
options: rawOpts.env[envKey],
alias: alias + `.env.${envKey}`,
dirname,
},
envKey,
),
);
}
}
}
const rawEnv = rawOpts.env || {};
Object.keys(rawEnv).forEach(envKey => {
const envOpts = rawEnv[envKey];
if (envOpts !== undefined && activeEnv !== null && activeEnv !== envKey) {
throw new Error(`Unreachable .env[${envKey}] block detected`);
}
if (
envOpts != null &&
(typeof envOpts !== "object" || Array.isArray(envOpts))
) {
throw new Error(".env[...] block must be an object, null, or undefined");
}
if (envOpts) {
parts.push(
...flattenOptionsParts(
{
type,
options: envOpts,
alias: alias + `.env.${envKey}`,
dirname,
},
envKey,
),
);
}
});
parts.push({
part: "config",
config,
ignore,
only,
ignore: rawOpts.ignore,
only: rawOpts.only,
activeEnv,
});
if (rawOpts.extends != null) {
if (typeof rawOpts.extends !== "string") {
throw new Error(".extends must be a string");
}
parts.push({
part: "extends",
path: rawOpts.extends,
@ -372,8 +329,8 @@ class LoadedFile {
* Tests if a filename should be ignored based on "ignore" and "only" options.
*/
shouldIgnore(
ignore: ?Array<mixed>,
only: ?Array<mixed>,
ignore: ?IgnoreList,
only: ?IgnoreList,
dirname: string,
): boolean {
if (ignore) {
@ -407,7 +364,7 @@ class LoadedFile {
* Returns result of calling function with filename if pattern is a function.
* Otherwise returns result of matching pattern Regex with filename.
*/
_matchesPatterns(patterns: Array<mixed>, dirname: string): boolean {
_matchesPatterns(patterns: IgnoreList, dirname: string): boolean {
const res = [];
const strings = [];
const fns = [];
@ -415,12 +372,7 @@ class LoadedFile {
patterns.forEach(pattern => {
if (typeof pattern === "string") strings.push(pattern);
else if (typeof pattern === "function") fns.push(pattern);
else if (pattern instanceof RegExp) res.push(pattern);
else {
throw new Error(
"Patterns must be a string, function, or regular expression",
);
}
else res.push(pattern);
});
const filename = this.filename;

View File

@ -31,7 +31,7 @@ export function makeStrongCache<ArgT, ResultT>(
* configures its caching behavior. Cached values are stored weakly and the function argument must be
* an object type.
*/
export function makeWeakCache<ArgT: {} | Array<*>, ResultT>(
export function makeWeakCache<ArgT: {} | Array<*> | $ReadOnlyArray<*>, ResultT>(
handler: (ArgT, CacheConfigurator) => ResultT,
autoPermacache?: boolean,
): ArgT => ResultT {

View File

@ -16,7 +16,7 @@ export type PluginPasses = Array<PluginPassList>;
* Standard API for loading Babel configuration data. Not for public consumption.
*/
export default function loadConfig(opts: mixed): ResolvedConfig | null {
if (opts != null && typeof opts !== "object") {
if (opts != null && (typeof opts !== "object" || Array.isArray(opts))) {
throw new Error("Babel options must be an object, null, or undefined");
}

View File

@ -0,0 +1,174 @@
// @flow
import type {
IgnoreList,
IgnoreItem,
PluginList,
PluginItem,
PluginTarget,
SourceMapsOption,
SourceTypeOption,
CompactOption,
RootInputSourceMapOption,
} from "./options";
export function assertSourceMaps(key: string, value: mixed): ?SourceMapsOption {
if (
value != null &&
typeof value !== "boolean" &&
value !== "inline" &&
value !== "both"
) {
throw new Error(
`.${key} must be a boolean, "inline", "both", null, or undefined`,
);
}
return value;
}
export function assertCompact(key: string, value: mixed): ?CompactOption {
if (value != null && typeof value !== "boolean" && value !== "auto") {
throw new Error(`.${key} must be a boolean, "auto", null, or undefined`);
}
return value;
}
export function assertSourceType(key: string, value: mixed): ?SourceTypeOption {
if (value != null && value !== "module" && value !== "script") {
throw new Error(`.${key} must be "module", "script", null, or undefined`);
}
return value;
}
export function assertInputSourceMap(
key: string,
value: mixed,
): ?RootInputSourceMapOption {
if (
value != null &&
typeof value !== "boolean" &&
typeof value !== "object"
) {
throw new Error(
".inputSourceMap must be a boolean, object, null, or undefined",
);
}
return value;
}
export function assertString(key: string, value: mixed): ?string {
if (value != null && typeof value !== "string") {
throw new Error(`.${key} must be a string, null, or undefined`);
}
return value;
}
export function assertFunction(key: string, value: mixed): ?Function {
if (value != null && typeof value !== "function") {
throw new Error(`.${key} must be a function, null, or undefined`);
}
return value;
}
export function assertBoolean(key: string, value: mixed): ?boolean {
if (value != null && typeof value !== "boolean") {
throw new Error(`.${key} must be a boolean, null, or undefined`);
}
return value;
}
export function assertObject(key: string, value: mixed): ?{} {
if (value != null && (typeof value !== "object" || Array.isArray(value))) {
throw new Error(`.${key} must be an object, null, or undefined`);
}
return value;
}
export function assertIgnoreList(key: string, value: mixed): ?IgnoreList {
const arr = assertArray(key, value);
if (arr) {
arr.forEach((item, i) => assertIgnoreItem(key, i, item));
}
return (arr: any);
}
function assertIgnoreItem(
key: string,
index: number,
value: mixed,
): IgnoreItem {
if (
typeof value !== "string" &&
typeof value !== "function" &&
!(value instanceof RegExp)
) {
throw new Error(
`.${key}[${index}] must be an array of string/Funtion/RegExp values, or null, or undefined`,
);
}
return value;
}
export function assertPluginList(key: string, value: mixed): ?PluginList {
const arr = assertArray(key, value);
if (arr) {
// Loop instead of using `.map` in order to preserve object identity
// for plugin array for use during config chain processing.
arr.forEach((item, i) => assertPluginItem(key, i, item));
}
return (arr: any);
}
function assertPluginItem(
key: string,
index: number,
value: mixed,
): PluginItem {
if (Array.isArray(value)) {
if (value.length === 0) {
throw new Error(`.${key}[${index}] must include an object`);
}
if (value.length > 2) {
throw new Error(`.${key}[${index}] may only be a two-tuple`);
}
assertPluginTarget(key, index, true, value[0]);
if (value.length === 2) {
const opts = value[1];
if (opts != null && (typeof opts !== "object" || Array.isArray(opts))) {
throw new Error(
`.${key}[${index}][1] must be an object, null, or undefined`,
);
}
}
} else {
assertPluginTarget(key, index, false, value);
}
return (value: any);
}
function assertPluginTarget(
key: string,
index: number,
inArray: boolean,
value: mixed,
): PluginTarget {
if (
(typeof value !== "object" || !value) &&
typeof value !== "string" &&
typeof value !== "function"
) {
throw new Error(
`.${key}[${index}]${inArray
? `[0]`
: ""} must be a string, object, function`,
);
}
return value;
}
function assertArray(key: string, value: mixed): ?$ReadOnlyArray<mixed> {
if (value != null && !Array.isArray(value)) {
throw new Error(`.${key} must be an array, null, or undefined`);
}
return value;
}

View File

@ -4,13 +4,13 @@ import * as context from "../index";
import Plugin from "./plugin";
import defaults from "lodash/defaults";
import merge from "lodash/merge";
import removed from "./removed";
import buildConfigChain from "./build-config-chain";
import buildConfigChain, { type ConfigItem } from "./build-config-chain";
import path from "path";
import traverse from "@babel/traverse";
import clone from "lodash/clone";
import { makeWeakCache } from "./caching";
import { getEnv } from "./helpers/environment";
import { validate, type ValidatedOptions, type PluginItem } from "./options";
import {
loadPlugin,
@ -19,50 +19,14 @@ import {
loadGenerator,
} from "./loading/files";
type MergeOptions = {
+type: "arguments" | "options" | "preset",
options: {},
alias: string,
dirname: string,
};
const optionNames = new Set([
"filename",
"filenameRelative",
"inputSourceMap",
"env",
"retainLines",
"highlightCode",
"presets",
"plugins",
"ignore",
"only",
"code",
"ast",
"extends",
"comments",
"shouldPrintComment",
"wrapPluginVisitorMethod",
"compact",
"minified",
"sourceMaps",
"sourceMapTarget",
"sourceFileName",
"sourceRoot",
"babelrc",
"sourceType",
"auxiliaryCommentBefore",
"auxiliaryCommentAfter",
"getModuleId",
"moduleRoot",
"moduleIds",
"moduleId",
"passPerPreset",
// Deprecate top level parserOpts
"parserOpts",
// Deprecate top level generatorOpts
"generatorOpts",
]);
type MergeOptions =
| ConfigItem
| {
type: "preset",
options: ValidatedOptions,
alias: string,
dirname: string,
};
const ALLOWED_PLUGIN_KEYS = new Set([
"name",
@ -82,11 +46,11 @@ export default function manageOptions(opts: {}): {
class OptionManager {
constructor() {
this.options = createInitialOptions();
this.options = {};
this.passes = [[]];
}
options: Object;
options: ValidatedOptions;
passes: Array<Array<Plugin>>;
/**
@ -108,12 +72,6 @@ class OptionManager {
loadPresetDescriptor(descriptor),
);
if (
config.options.passPerPreset != null &&
typeof config.options.passPerPreset !== "boolean"
) {
throw new Error(".passPerPreset must be a boolean or undefined");
}
const passPerPreset = config.options.passPerPreset;
pass = pass || this.passes[0];
@ -142,12 +100,22 @@ class OptionManager {
delete options.env;
delete options.plugins;
delete options.presets;
delete options.passPerPreset;
// "sourceMap" is just aliased to sourceMap, so copy it over as
// we merge the options together.
if (options.sourceMap) {
options.sourceMaps = options.sourceMap;
delete options.sourceMap;
}
merge(this.options, options);
}
init(opts: {}) {
const configChain = buildConfigChain(opts);
init(inputOpts: {}) {
const args = validate("arguments", inputOpts);
const configChain = buildConfigChain(args);
if (!configChain) return null;
try {
@ -158,15 +126,13 @@ class OptionManager {
// There are a few case where thrown errors will try to annotate themselves multiple times, so
// to keep things simple we just bail out if re-wrapping the message.
if (!/^\[BABEL\]/.test(e.message)) {
const filename =
typeof opts.filename === "string" ? opts.filename : null;
e.message = `[BABEL] ${filename || "unknown"}: ${e.message}`;
e.message = `[BABEL] ${args.filename || "unknown"}: ${e.message}`;
}
throw e;
}
opts = this.options;
const opts: Object = merge(createInitialOptions(), this.options);
// Tack the passes onto the object itself so that, if this object is passed back to Babel a second time,
// it will be in the right structure to not change behavior.
@ -175,6 +141,7 @@ class OptionManager {
.slice(1)
.filter(plugins => plugins.length > 0)
.map(plugins => ({ plugins }));
opts.passPerPreset = opts.presets.length > 0;
if (opts.inputSourceMap) {
opts.sourceMaps = true;
@ -231,20 +198,13 @@ type LoadedDescriptor = {
/**
* Load and validate the given config into a set of options, plugins, and presets.
*/
const loadConfig = makeWeakCache((config): {
const loadConfig = makeWeakCache((config: MergeOptions): {
options: {},
plugins: Array<BasicDescriptor>,
presets: Array<BasicDescriptor>,
} => {
const options = normalizeOptions(config);
if (
config.options.plugins != null &&
!Array.isArray(config.options.plugins)
) {
throw new Error(".plugins should be an array, null, or undefined");
}
const plugins = (config.options.plugins || []).map((plugin, index) => {
const { filepath, value, options } = normalizePair(
plugin,
@ -260,13 +220,6 @@ const loadConfig = makeWeakCache((config): {
};
});
if (
config.options.presets != null &&
!Array.isArray(config.options.presets)
) {
throw new Error(".presets should be an array, null, or undefined");
}
const presets = (config.options.presets || []).map((preset, index) => {
const { filepath, value, options } = normalizePair(
preset,
@ -405,7 +358,7 @@ const instantiatePreset = makeWeakCache(
({ value, dirname, alias }: LoadedDescriptor): MergeOptions => {
return {
type: "preset",
options: value,
options: validate("preset", value),
alias,
dirname,
};
@ -416,72 +369,12 @@ const instantiatePreset = makeWeakCache(
* Validate and return the options object for the config.
*/
function normalizeOptions(config) {
const alias = config.alias || "foreign";
const type = config.type;
//
if (typeof config.options !== "object" || Array.isArray(config.options)) {
throw new TypeError(`Invalid options type for ${alias}`);
}
//
const options = Object.assign({}, config.options);
if (type !== "arguments") {
if (options.filename !== undefined) {
throw new Error(`${alias}.filename is only allowed as a root argument`);
}
if (options.babelrc !== undefined) {
throw new Error(`${alias}.babelrc is only allowed as a root argument`);
}
}
if (type === "preset") {
if (options.only !== undefined) {
throw new Error(`${alias}.only is not supported in a preset`);
}
if (options.ignore !== undefined) {
throw new Error(`${alias}.ignore is not supported in a preset`);
}
if (options.extends !== undefined) {
throw new Error(`${alias}.extends is not supported in a preset`);
}
if (options.env !== undefined) {
throw new Error(`${alias}.env is not supported in a preset`);
}
}
if (options.sourceMap !== undefined) {
if (options.sourceMaps !== undefined) {
throw new Error(`Both ${alias}.sourceMap and .sourceMaps have been set`);
}
options.sourceMaps = options.sourceMap;
delete options.sourceMap;
}
for (const key in options) {
// check for an unknown option
if (!optionNames.has(key)) {
if (removed[key]) {
const { message, version = 5 } = removed[key];
throw new ReferenceError(
`Using removed Babel ${version} option: ${alias}.${key} - ${message}`,
);
} else {
// eslint-disable-next-line max-len
const unknownOptErr = `Unknown option: ${alias}.${key}. Check out http://babeljs.io/docs/usage/options/ for more information about options.`;
throw new ReferenceError(unknownOptErr);
}
}
}
if (options.parserOpts && typeof options.parserOpts.parser === "string") {
options.parserOpts = Object.assign({}, options.parserOpts);
options.parserOpts.parser = loadParser(
(options.parserOpts: any).parser = loadParser(
options.parserOpts.parser,
config.dirname,
).value;
@ -492,16 +385,12 @@ function normalizeOptions(config) {
typeof options.generatorOpts.generator === "string"
) {
options.generatorOpts = Object.assign({}, options.generatorOpts);
options.generatorOpts.generator = loadGenerator(
(options.generatorOpts: any).generator = loadGenerator(
options.generatorOpts.generator,
config.dirname,
).value;
}
delete options.passPerPreset;
delete options.plugins;
delete options.presets;
return options;
}
@ -509,7 +398,7 @@ function normalizeOptions(config) {
* Given a plugin/preset item, resolve it into a standard format.
*/
function normalizePair(
pair: mixed,
pair: PluginItem,
resolver,
dirname,
): {
@ -519,14 +408,8 @@ function normalizePair(
} {
let options;
let value = pair;
if (Array.isArray(pair)) {
if (pair.length > 2) {
throw new Error(
`Unexpected extra options ${JSON.stringify(pair.slice(2))}.`,
);
}
[value, options] = pair;
if (Array.isArray(value)) {
[value, options] = value;
}
let filepath = null;

View File

@ -0,0 +1,255 @@
// @flow
import removed from "./removed";
import {
assertString,
assertBoolean,
assertObject,
assertInputSourceMap,
assertIgnoreList,
assertPluginList,
assertFunction,
assertSourceMaps,
assertCompact,
assertSourceType,
} from "./option-assertions";
type ValidatorSet = {
[string]: Validator<any>,
};
type Validator<T> = (string, mixed) => T;
const ROOT_VALIDATORS: ValidatorSet = {
filename: (assertString: Validator<
$PropertyType<ValidatedOptions, "filename">,
>),
filenameRelative: (assertString: Validator<
$PropertyType<ValidatedOptions, "filenameRelative">,
>),
babelrc: (assertBoolean: Validator<
$PropertyType<ValidatedOptions, "babelrc">,
>),
code: (assertBoolean: Validator<$PropertyType<ValidatedOptions, "code">>),
ast: (assertBoolean: Validator<$PropertyType<ValidatedOptions, "ast">>),
};
const NONPRESET_VALIDATORS: ValidatorSet = {
extends: (assertString: Validator<
$PropertyType<ValidatedOptions, "extends">,
>),
env: (assertEnvSet: Validator<$PropertyType<ValidatedOptions, "env">>),
ignore: (assertIgnoreList: Validator<
$PropertyType<ValidatedOptions, "ignore">,
>),
only: (assertIgnoreList: Validator<$PropertyType<ValidatedOptions, "only">>),
};
const COMMON_VALIDATORS: ValidatorSet = {
// TODO: Should 'inputSourceMap' be moved to be a root-only option?
// We may want a boolean-only version to be a common option, with the
// object only allowed as a root config argument.
inputSourceMap: (assertInputSourceMap: Validator<
$PropertyType<ValidatedOptions, "inputSourceMap">,
>),
presets: (assertPluginList: Validator<
$PropertyType<ValidatedOptions, "presets">,
>),
plugins: (assertPluginList: Validator<
$PropertyType<ValidatedOptions, "plugins">,
>),
passPerPreset: (assertBoolean: Validator<
$PropertyType<ValidatedOptions, "passPerPreset">,
>),
retainLines: (assertBoolean: Validator<
$PropertyType<ValidatedOptions, "retainLines">,
>),
comments: (assertBoolean: Validator<
$PropertyType<ValidatedOptions, "comments">,
>),
shouldPrintComment: (assertFunction: Validator<
$PropertyType<ValidatedOptions, "shouldPrintComment">,
>),
compact: (assertCompact: Validator<
$PropertyType<ValidatedOptions, "compact">,
>),
minified: (assertBoolean: Validator<
$PropertyType<ValidatedOptions, "minified">,
>),
auxiliaryCommentBefore: (assertString: Validator<
$PropertyType<ValidatedOptions, "auxiliaryCommentBefore">,
>),
auxiliaryCommentAfter: (assertString: Validator<
$PropertyType<ValidatedOptions, "auxiliaryCommentAfter">,
>),
sourceType: (assertSourceType: Validator<
$PropertyType<ValidatedOptions, "sourceType">,
>),
wrapPluginVisitorMethod: (assertFunction: Validator<
$PropertyType<ValidatedOptions, "wrapPluginVisitorMethod">,
>),
highlightCode: (assertBoolean: Validator<
$PropertyType<ValidatedOptions, "highlightCode">,
>),
sourceMaps: (assertSourceMaps: Validator<
$PropertyType<ValidatedOptions, "sourceMaps">,
>),
sourceMap: (assertSourceMaps: Validator<
$PropertyType<ValidatedOptions, "sourceMap">,
>),
sourceMapTarget: (assertString: Validator<
$PropertyType<ValidatedOptions, "sourceMapTarget">,
>),
sourceFileName: (assertString: Validator<
$PropertyType<ValidatedOptions, "sourceFileName">,
>),
sourceRoot: (assertString: Validator<
$PropertyType<ValidatedOptions, "sourceRoot">,
>),
getModuleId: (assertFunction: Validator<
$PropertyType<ValidatedOptions, "getModuleId">,
>),
moduleRoot: (assertString: Validator<
$PropertyType<ValidatedOptions, "moduleRoot">,
>),
moduleIds: (assertBoolean: Validator<
$PropertyType<ValidatedOptions, "moduleIds">,
>),
moduleId: (assertString: Validator<
$PropertyType<ValidatedOptions, "moduleId">,
>),
parserOpts: (assertObject: Validator<
$PropertyType<ValidatedOptions, "parserOpts">,
>),
generatorOpts: (assertObject: Validator<
$PropertyType<ValidatedOptions, "generatorOpts">,
>),
};
export type ValidatedOptions = {
filename?: ?string,
filenameRelative?: ?string,
babelrc?: ?boolean,
code?: ?boolean,
ast?: ?boolean,
inputSourceMap?: ?RootInputSourceMapOption,
extends?: ?string,
env?: ?EnvSet<ValidatedOptions>,
ignore?: ?IgnoreList,
only?: ?IgnoreList,
presets?: ?PluginList,
plugins?: ?PluginList,
passPerPreset?: ?boolean,
// Options for @babel/generator
retainLines?: ?boolean,
comments?: ?boolean,
shouldPrintComment?: ?Function,
compact?: ?CompactOption,
minified?: ?boolean,
auxiliaryCommentBefore?: ?string,
auxiliaryCommentAfter?: ?string,
// Parser
sourceType?: ?SourceTypeOption,
wrapPluginVisitorMethod?: ?Function,
highlightCode?: ?boolean,
// Sourcemap generation options.
sourceMaps?: ?SourceMapsOption,
sourceMap?: ?SourceMapsOption,
sourceMapTarget?: ?string,
sourceFileName?: ?string,
sourceRoot?: ?string,
// AMD/UMD/SystemJS module naming options.
getModuleId?: ?Function,
moduleRoot?: ?string,
moduleIds?: ?boolean,
moduleId?: ?string,
// Deprecate top level parserOpts
parserOpts?: ?{},
// Deprecate top level generatorOpts
generatorOpts?: ?{},
};
export type EnvSet<T> = {
[string]: ?T,
};
export type IgnoreItem = string | Function | RegExp;
export type IgnoreList = $ReadOnlyArray<IgnoreItem>;
export type PluginTarget = string | {} | Function;
export type PluginItem = PluginTarget | [PluginTarget, {} | void];
export type PluginList = $ReadOnlyArray<PluginItem>;
export type SourceMapsOption = boolean | "inline" | "both";
export type SourceTypeOption = "module" | "script";
export type CompactOption = boolean | "auto";
export type RootInputSourceMapOption = {} | boolean;
export type OptionsType = "arguments" | "file" | "env" | "preset";
export function validate(type: OptionsType, opts: {}): ValidatedOptions {
assertNoDuplicateSourcemap(opts);
Object.keys(opts).forEach(key => {
if (type === "preset" && NONPRESET_VALIDATORS[key]) {
throw new Error(`.${key} is not allowed in preset options`);
}
if (type !== "arguments" && ROOT_VALIDATORS[key]) {
throw new Error(`.${key} is only allowed in root programmatic options`);
}
const validator =
COMMON_VALIDATORS[key] ||
NONPRESET_VALIDATORS[key] ||
ROOT_VALIDATORS[key];
if (validator) validator(key, opts[key]);
else throw buildUnknownError(key);
});
return (opts: any);
}
function buildUnknownError(key: string) {
if (removed[key]) {
const { message, version = 5 } = removed[key];
throw new ReferenceError(
`Using removed Babel ${version} option: .${key} - ${message}`,
);
} else {
// eslint-disable-next-line max-len
const unknownOptErr = `Unknown option: .${key}. Check out http://babeljs.io/docs/usage/options/ for more information about options.`;
throw new ReferenceError(unknownOptErr);
}
}
function has(obj: {}, key: string) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
function assertNoDuplicateSourcemap(opts: {}): void {
if (has(opts, "sourceMap") && has(opts, "sourceMaps")) {
throw new Error(".sourceMap is an alias for .sourceMaps, cannot use both");
}
}
function assertEnvSet(key: string, value: mixed): ?EnvSet<ValidatedOptions> {
const obj = assertObject(key, value);
if (obj) {
// Validate but don't copy the .env object in order to preserve
// object identity for use during config chain processing.
for (const key of Object.keys(obj)) {
const env = assertObject(key, obj[key]);
if (env) validate("env", env);
}
}
return (obj: any);
}

View File

@ -145,7 +145,7 @@ describe("api", function() {
babel.transform("", {
plugins: [__dirname + "/../../babel-plugin-syntax-jsx", false],
});
}, /Error: \[BABEL\] unknown: Unexpected falsy value: false/);
}, /.plugins\[1\] must be a string, object, function/);
});
it("options merge backwards", function() {

View File

@ -372,7 +372,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
plugins: ["extended"],
},
@ -380,7 +380,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
extends: "./extended.babelrc.json",
plugins: ["root"],
@ -389,7 +389,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -416,7 +416,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -424,7 +424,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
plugins: ["dir2"],
},
@ -451,7 +451,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
plugins: ["extended"],
},
@ -459,7 +459,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
extends: "./extended.babelrc.json",
plugins: ["root"],
@ -468,7 +468,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -495,7 +495,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -503,7 +503,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
env: {
bar: {
@ -540,7 +540,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -548,7 +548,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
env: {
bar: {
@ -564,7 +564,7 @@ describe("buildConfigChain", function() {
dirname: fixture("env"),
},
{
type: "options",
type: "env",
options: {
plugins: ["env-foo"],
},
@ -594,7 +594,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -602,7 +602,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
env: {
bar: {
@ -618,7 +618,7 @@ describe("buildConfigChain", function() {
dirname: fixture("env"),
},
{
type: "options",
type: "env",
options: {
plugins: ["env-bar"],
},
@ -647,7 +647,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
plugins: ["pkg-plugin"],
},
@ -655,7 +655,7 @@ describe("buildConfigChain", function() {
dirname: fixture("pkg"),
},
{
type: "options",
type: "file",
options: {
ignore: ["pkg-ignore"],
},
@ -682,7 +682,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -690,7 +690,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
plugins: ["foo", "bar"],
},
@ -717,7 +717,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -725,7 +725,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
compact: true,
},
@ -752,7 +752,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -760,7 +760,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
plugins: ["foo", "bar"],
},
@ -786,7 +786,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -794,7 +794,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
plugins: ["extended"],
},
@ -802,7 +802,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
extends: "../extended.babelrc.json",
plugins: ["foo", "bar"],
@ -833,7 +833,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -841,7 +841,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
plugins: ["json"],
},
@ -869,7 +869,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -877,7 +877,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
ignore: ["*", "!src.js"],
},
@ -910,7 +910,7 @@ describe("buildConfigChain", function() {
const expected = [
{
type: "options",
type: "file",
options: {
ignore: ["root-ignore"],
},
@ -918,7 +918,7 @@ describe("buildConfigChain", function() {
dirname: fixture(),
},
{
type: "options",
type: "file",
options: {
ignore: ["*", "!folder"],
},

View File

@ -17,7 +17,7 @@ describe("option-manager", () => {
manageOptions({
randomOption: true,
});
}, /Unknown option: base.randomOption/);
}, /Unknown option: .randomOption/);
});
it("throws for removed babel 5 options", () => {
@ -29,7 +29,7 @@ describe("option-manager", () => {
});
},
// eslint-disable-next-line max-len
/Using removed Babel 5 option: base.auxiliaryComment - Use `auxiliaryCommentBefore` or `auxiliaryCommentAfter`/,
/Using removed Babel 5 option: .auxiliaryComment - Use `auxiliaryCommentBefore` or `auxiliaryCommentAfter`/,
);
});
@ -47,7 +47,7 @@ describe("option-manager", () => {
describe("source type", function() {
it("should set module for .mjs extension", () => {
const config = manageOptions({
sourceType: "program",
sourceType: "script",
filename: "foo.mjs",
});