Move plugin object validation into plugin file.

This commit is contained in:
Logan Smyth 2017-11-08 14:41:05 -08:00
parent 98969b8a73
commit 52d337e4d9
4 changed files with 108 additions and 75 deletions

View File

@ -12,6 +12,12 @@ import type {
RootInputSourceMapOption,
} from "./options";
export type ValidatorSet = {
[string]: Validator<any>,
};
export type Validator<T> = (string, mixed) => T;
export function assertSourceMaps(
key: string,
value: mixed,

View File

@ -1,7 +1,7 @@
// @flow
import * as context from "../index";
import Plugin from "./plugin";
import Plugin, { validatePluginObject } from "./plugin";
import defaults from "lodash/defaults";
import merge from "lodash/merge";
import buildConfigChain, { type ConfigItem } from "./build-config-chain";
@ -28,15 +28,6 @@ type MergeOptions =
dirname: string,
};
const ALLOWED_PLUGIN_KEYS = new Set([
"name",
"manipulateOptions",
"pre",
"post",
"visitor",
"inherits",
]);
export default function manageOptions(opts: {}): {
options: Object,
passes: Array<Array<Plugin>>,
@ -275,37 +266,16 @@ function loadPluginDescriptor(descriptor: BasicDescriptor): Plugin {
}
const instantiatePlugin = makeWeakCache(
(
{ value: pluginObj, options, dirname, alias }: LoadedDescriptor,
cache,
): Plugin => {
Object.keys(pluginObj).forEach(key => {
if (!ALLOWED_PLUGIN_KEYS.has(key)) {
throw new Error(
`Plugin ${alias} provided an invalid property of ${key}`,
);
}
});
if (
pluginObj.visitor &&
(pluginObj.visitor.enter || pluginObj.visitor.exit)
) {
throw new Error(
"Plugins aren't allowed to specify catch-all enter/exit handlers. " +
"Please target individual nodes.",
);
({ value, options, dirname, alias }: LoadedDescriptor, cache): Plugin => {
const pluginObj = validatePluginObject(value);
const plugin = Object.assign({}, pluginObj);
if (plugin.visitor) {
plugin.visitor = traverse.explode(clone(plugin.visitor));
}
const plugin = Object.assign({}, pluginObj, {
visitor: clone(pluginObj.visitor || {}),
});
traverse.explode(plugin.visitor);
let inheritsDescriptor;
let inherits;
if (plugin.inherits) {
inheritsDescriptor = {
const inheritsDescriptor = {
alias: `${alias}$inherits`,
value: plugin.inherits,
options,
@ -313,7 +283,7 @@ const instantiatePlugin = makeWeakCache(
};
// If the inherited plugin changes, reinstantiate this plugin.
inherits = cache.invalidate(() =>
const inherits = cache.invalidate(() =>
loadPluginDescriptor(inheritsDescriptor),
);
@ -324,8 +294,8 @@ const instantiatePlugin = makeWeakCache(
plugin.manipulateOptions,
);
plugin.visitor = traverse.visitors.merge([
inherits.visitor,
plugin.visitor,
inherits.visitor || {},
plugin.visitor || {},
]);
}

View File

@ -12,14 +12,10 @@ import {
assertSourceMaps,
assertCompact,
assertSourceType,
type ValidatorSet,
type Validator,
} from "./option-assertions";
type ValidatorSet = {
[string]: Validator<any>,
};
type Validator<T> = (string, mixed) => T;
const ROOT_VALIDATORS: ValidatorSet = {
filename: (assertString: Validator<
$PropertyType<ValidatedOptions, "filename">,

View File

@ -1,43 +1,104 @@
// @flow
// $FlowIssue recursion?
import {
assertString,
assertFunction,
assertObject,
type ValidatorSet,
type Validator,
} from "./option-assertions";
const VALIDATORS: ValidatorSet = {
name: (assertString: Validator<$PropertyType<PluginObject, "name">>),
manipulateOptions: (assertFunction: Validator<
$PropertyType<PluginObject, "manipulateOptions">,
>),
pre: (assertFunction: Validator<$PropertyType<PluginObject, "pre">>),
post: (assertFunction: Validator<$PropertyType<PluginObject, "post">>),
inherits: (assertFunction: Validator<
$PropertyType<PluginObject, "inherits">,
>),
visitor: (assertVisitorMap: Validator<
$PropertyType<PluginObject, "visitor">,
>),
};
function assertVisitorMap(key: string, value: mixed): VisitorMap {
const obj = assertObject(key, 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.`,
);
}
}
return (obj: any);
}
function assertVisitorHandler(
key: string,
value: mixed,
): VisitorHandler | void {
if (value && typeof value === "object") {
Object.keys(value).forEach(handler => {
if (handler !== "enter" && handler !== "exit") {
throw new Error(
`.visitor["${key}"] may only have .enter and/or .exit handlers.`,
);
}
});
} else if (typeof value !== "function") {
throw new Error(`.visitor["${key}"] must be a function`);
}
return (value: any);
}
type VisitorHandler = Function | { enter?: Function, exit?: Function };
export type VisitorMap = {
[string]: VisitorHandler,
};
export type PluginObject = {
name?: string,
manipulateOptions?: Function,
pre?: Function,
post?: Function,
inherits?: Function,
visitor?: VisitorMap,
};
export function validatePluginObject(obj: {}): PluginObject {
Object.keys(obj).forEach(key => {
const validator = VALIDATORS[key];
if (validator) validator(key, obj[key]);
else throw new Error(`.${key} is not a valid Plugin property`);
});
return (obj: any);
}
export default class Plugin {
key: ?string;
manipulateOptions: ?Function;
post: ?Function;
pre: ?Function;
visitor: ?{};
manipulateOptions: Function | void;
post: Function | void;
pre: Function | void;
visitor: {};
options: {};
constructor(plugin: {}, options: {}, key?: string) {
if (plugin.name != null && typeof plugin.name !== "string") {
throw new Error("Plugin .name must be a string, null, or undefined");
}
if (
plugin.manipulateOptions != null &&
typeof plugin.manipulateOptions !== "function"
) {
throw new Error(
"Plugin .manipulateOptions must be a function, null, or undefined",
);
}
if (plugin.post != null && typeof plugin.post !== "function") {
throw new Error("Plugin .post must be a function, null, or undefined");
}
if (plugin.pre != null && typeof plugin.pre !== "function") {
throw new Error("Plugin .pre must be a function, null, or undefined");
}
if (plugin.visitor != null && typeof plugin.visitor !== "object") {
throw new Error("Plugin .visitor must be an object, null, or undefined");
}
constructor(plugin: PluginObject, options: {}, key?: string) {
this.key = plugin.name || key;
this.manipulateOptions = plugin.manipulateOptions;
this.post = plugin.post;
this.pre = plugin.pre;
this.visitor = plugin.visitor;
this.visitor = plugin.visitor || {};
this.options = options;
}
}