Typecheck much more of the config loading process (#5642)
* Add type declarations for micromatch. * Enable Flowtype on all config loading. * Remove unneeded comments.
This commit is contained in:
parent
c46ef658b5
commit
6af8e64711
@ -9,6 +9,7 @@ packages/*/src
|
||||
lib/file.js
|
||||
lib/parser.js
|
||||
lib/types.js
|
||||
lib/third-party-libs.js.flow
|
||||
|
||||
[options]
|
||||
strip_root=true
|
||||
|
||||
9
lib/third-party-libs.js.flow
Normal file
9
lib/third-party-libs.js.flow
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Basic declarations for the npm modules we use.
|
||||
*/
|
||||
|
||||
declare module "micromatch" {
|
||||
declare function exports(Array<string>, Array<string>, ?{
|
||||
nocase: boolean,
|
||||
}): Array<string>;
|
||||
}
|
||||
@ -1,10 +1,24 @@
|
||||
import * as babel from "../index";
|
||||
// @flow
|
||||
|
||||
import { getEnv } from "./helpers/environment";
|
||||
import path from "path";
|
||||
import micromatch from "micromatch";
|
||||
|
||||
import { findConfigs, loadConfig } from "./loading/files";
|
||||
|
||||
export default function buildConfigChain(opts: Object = {}) {
|
||||
type ConfigItem = {
|
||||
type: "options"|"arguments",
|
||||
options: {},
|
||||
dirname: string,
|
||||
alias: string,
|
||||
loc: string,
|
||||
};
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
const filename = opts.filename ? path.resolve(opts.filename) : null;
|
||||
const builder = new ConfigChainBuilder(filename);
|
||||
|
||||
@ -17,7 +31,7 @@ export default function buildConfigChain(opts: Object = {}) {
|
||||
});
|
||||
|
||||
// resolve all .babelrc files
|
||||
if (opts.babelrc !== false) {
|
||||
if (opts.babelrc !== false && filename) {
|
||||
builder.findConfigs(filename);
|
||||
}
|
||||
} catch (e) {
|
||||
@ -30,6 +44,10 @@ export default function buildConfigChain(opts: Object = {}) {
|
||||
}
|
||||
|
||||
class ConfigChainBuilder {
|
||||
filename: string|null;
|
||||
configs: Array<ConfigItem>;
|
||||
possibleDirs: null|Array<string>;
|
||||
|
||||
constructor(filename) {
|
||||
this.configs = [];
|
||||
this.filename = filename;
|
||||
@ -40,59 +58,68 @@ class ConfigChainBuilder {
|
||||
* Tests if a filename should be ignored based on "ignore" and "only" options.
|
||||
*/
|
||||
shouldIgnore(
|
||||
ignore: Array<string | RegExp | Function>,
|
||||
only?: Array<string | RegExp | Function>,
|
||||
ignore: mixed,
|
||||
only: mixed,
|
||||
dirname: string,
|
||||
): boolean {
|
||||
if (!this.filename) return false;
|
||||
|
||||
if (ignore && !Array.isArray(ignore)) {
|
||||
throw new Error(`.ignore should be an array, ${JSON.stringify(ignore)} given`);
|
||||
if (ignore) {
|
||||
if (!Array.isArray(ignore)) {
|
||||
throw new Error(`.ignore should be an array, ${JSON.stringify(ignore)} given`);
|
||||
}
|
||||
|
||||
if (this.matchesPatterns(ignore, dirname)) return true;
|
||||
}
|
||||
|
||||
if (only && !Array.isArray(only)) {
|
||||
throw new Error(`.only should be an array, ${JSON.stringify(only)} given`);
|
||||
if (only) {
|
||||
if (!Array.isArray(only)) {
|
||||
throw new Error(`.only should be an array, ${JSON.stringify(only)} given`);
|
||||
}
|
||||
|
||||
if (!this.matchesPatterns(only, dirname)) return true;
|
||||
}
|
||||
|
||||
return (ignore && this.matchesPatterns(ignore, dirname)) ||
|
||||
(only && !this.matchesPatterns(only, dirname));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns result of calling function with filename if pattern is a function.
|
||||
* Otherwise returns result of matching pattern Regex with filename.
|
||||
*/
|
||||
matchesPatterns(patterns: Array<string | Function | RegExp>, dirname: string) {
|
||||
matchesPatterns(patterns: Array<mixed>, dirname: string) {
|
||||
const filename = this.filename;
|
||||
if (!filename) throw new Error("Assertion failure: .filename should always exist here");
|
||||
|
||||
const res = [];
|
||||
const strings = [];
|
||||
const fns = [];
|
||||
|
||||
patterns.forEach((pattern) => {
|
||||
const type = typeof pattern;
|
||||
if (type === "string") strings.push(pattern);
|
||||
else if (type === "function") fns.push(pattern);
|
||||
else res.push(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");
|
||||
});
|
||||
|
||||
if (res.some((re) => re.test(this.filename))) return true;
|
||||
if (fns.some((fn) => fn(this.filename))) return true;
|
||||
if (res.some((re) => re.test(filename))) return true;
|
||||
if (fns.some((fn) => fn(filename))) return true;
|
||||
|
||||
if (strings.length > 0) {
|
||||
let possibleDirs = this.possibleDirs;
|
||||
// Lazy-init so we don't initialize this for files that have no glob patterns.
|
||||
if (!this.possibleDirs) {
|
||||
this.possibleDirs = [];
|
||||
if (!possibleDirs) {
|
||||
possibleDirs = this.possibleDirs = [];
|
||||
|
||||
if (this.filename) {
|
||||
this.possibleDirs.push(this.filename);
|
||||
possibleDirs.push(filename);
|
||||
|
||||
let current = this.filename;
|
||||
while (true) {
|
||||
const previous = current;
|
||||
current = path.dirname(current);
|
||||
if (previous === current) break;
|
||||
let current = filename;
|
||||
while (true) {
|
||||
const previous = current;
|
||||
current = path.dirname(current);
|
||||
if (previous === current) break;
|
||||
|
||||
this.possibleDirs.push(current);
|
||||
}
|
||||
possibleDirs.push(current);
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,7 +131,7 @@ class ConfigChainBuilder {
|
||||
return (negate ? "!" : "") + path.resolve(dirname, pattern);
|
||||
});
|
||||
|
||||
if (micromatch(this.possibleDirs, absolutePatterns, { nocase: true }).length > 0) {
|
||||
if (micromatch(possibleDirs, absolutePatterns, { nocase: true }).length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -113,12 +140,6 @@ class ConfigChainBuilder {
|
||||
}
|
||||
|
||||
findConfigs(loc: string) {
|
||||
if (!loc) return;
|
||||
|
||||
if (!path.isAbsolute(loc)) {
|
||||
loc = path.join(process.cwd(), loc);
|
||||
}
|
||||
|
||||
findConfigs(path.dirname(loc)).forEach(({ filepath, dirname, options }) => {
|
||||
this.mergeConfig({
|
||||
type: "options",
|
||||
@ -131,31 +152,33 @@ class ConfigChainBuilder {
|
||||
|
||||
mergeConfig({
|
||||
type,
|
||||
options,
|
||||
options: rawOpts,
|
||||
alias,
|
||||
loc,
|
||||
dirname,
|
||||
}) {
|
||||
if (!options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bail out ASAP if this file is ignored so that we run as little logic as possible on ignored files.
|
||||
if (this.filename && this.shouldIgnore(options.ignore, options.only, dirname)) {
|
||||
if (this.filename && this.shouldIgnore(rawOpts.ignore || null, rawOpts.only || null, dirname)) {
|
||||
// TODO(logan): This is a really cross way to bail out. Avoid this in rewrite.
|
||||
throw Object.assign(new Error("This file has been ignored."), { code: "BABEL_IGNORED_FILE" });
|
||||
throw Object.assign((new Error("This file has been ignored."): any), { code: "BABEL_IGNORED_FILE" });
|
||||
}
|
||||
|
||||
options = Object.assign({}, options);
|
||||
const options = Object.assign({}, rawOpts);
|
||||
delete options.env;
|
||||
delete options.extends;
|
||||
|
||||
loc = loc || alias;
|
||||
const envKey = getEnv();
|
||||
|
||||
// env
|
||||
const envKey = babel.getEnv();
|
||||
if (options.env) {
|
||||
const envOpts = options.env[envKey];
|
||||
delete options.env;
|
||||
if (rawOpts.env != null && (typeof rawOpts.env !== "object" || Array.isArray(rawOpts.env))) {
|
||||
throw new Error(".env block must be an object, null, or undefined");
|
||||
}
|
||||
|
||||
const envOpts = rawOpts.env && rawOpts.env[envKey];
|
||||
|
||||
if (envOpts != null && (typeof envOpts !== "object" || Array.isArray(envOpts))) {
|
||||
throw new Error(".env[...] block must be an object, null, or undefined");
|
||||
}
|
||||
|
||||
if (envOpts) {
|
||||
this.mergeConfig({
|
||||
type,
|
||||
options: envOpts,
|
||||
@ -168,13 +191,14 @@ class ConfigChainBuilder {
|
||||
type,
|
||||
options,
|
||||
alias,
|
||||
loc,
|
||||
loc: alias,
|
||||
dirname,
|
||||
});
|
||||
|
||||
// add extends clause
|
||||
if (options.extends) {
|
||||
const extendsConfig = loadConfig(options.extends, dirname);
|
||||
if (rawOpts.extends) {
|
||||
if (typeof rawOpts.extends !== "string") throw new Error(".extends must be a string");
|
||||
|
||||
const extendsConfig = loadConfig(rawOpts.extends, dirname);
|
||||
|
||||
const existingConfig = this.configs.some((config) => {
|
||||
return config.alias === extendsConfig.filepath;
|
||||
@ -187,7 +211,6 @@ class ConfigChainBuilder {
|
||||
dirname: extendsConfig.dirname,
|
||||
});
|
||||
}
|
||||
delete options.extends;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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: Object, ResultT>(
|
||||
export function makeWeakCache<ArgT: {}, ResultT>(
|
||||
handler: (ArgT, CacheConfigurator) => ResultT,
|
||||
autoPermacache?: boolean,
|
||||
): (ArgT) => ResultT {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export function getEnv(defaultValue = "development") {
|
||||
// @flow
|
||||
|
||||
export function getEnv(defaultValue: string = "development"): string {
|
||||
return process.env.BABEL_ENV
|
||||
|| process.env.NODE_ENV
|
||||
|| defaultValue;
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import mergeWith from "lodash/mergeWith";
|
||||
|
||||
export default function (dest?: Object, src?: Object): ?Object {
|
||||
export default function<T: {}>(dest?: T, src?: T) {
|
||||
if (!dest || !src) return;
|
||||
|
||||
return mergeWith(dest, src, function (a, b) {
|
||||
mergeWith(dest, src, function (a, b) {
|
||||
if (b && Array.isArray(a)) {
|
||||
const newArray = b.slice(0);
|
||||
|
||||
|
||||
@ -1,14 +1,20 @@
|
||||
// @flow
|
||||
|
||||
import type Plugin from "./plugin";
|
||||
import manageOptions from "./option-manager";
|
||||
|
||||
export type ResolvedConfig = {
|
||||
options: Object,
|
||||
passes: Array<Array<Plugin>>,
|
||||
passes: Array<Array<[ Plugin, ?{} ]>>,
|
||||
};
|
||||
|
||||
/**
|
||||
* Standard API for loading Babel configuration data. Not for public consumption.
|
||||
*/
|
||||
export default function loadConfig(opts: Object): ResolvedConfig|null {
|
||||
return manageOptions(opts);
|
||||
export default function loadConfig(opts: mixed): ResolvedConfig|null {
|
||||
if (opts != null && typeof opts !== "object") {
|
||||
throw new Error("Babel options must be an object, null, or undefined");
|
||||
}
|
||||
|
||||
return manageOptions(opts || {});
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import { makeStrongCache } from "../../caching";
|
||||
type ConfigFile = {
|
||||
filepath: string,
|
||||
dirname: string,
|
||||
options: Object,
|
||||
options: {},
|
||||
};
|
||||
|
||||
const BABELRC_FILENAME = ".babelrc";
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
type ConfigFile = {
|
||||
filepath: string,
|
||||
dirname: string,
|
||||
options: Object,
|
||||
options: {},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import * as context from "../index";
|
||||
import Plugin from "./plugin";
|
||||
import * as messages from "babel-messages";
|
||||
@ -12,11 +14,11 @@ import clone from "lodash/clone";
|
||||
import { loadPlugin, loadPreset, loadParser, loadGenerator } from "./loading/files";
|
||||
|
||||
type MergeOptions = {
|
||||
type: "arguments"|"options"|"preset",
|
||||
options?: Object,
|
||||
+type: "arguments"|"options"|"preset",
|
||||
options: {},
|
||||
alias: string,
|
||||
loc?: string,
|
||||
dirname?: string
|
||||
loc: string,
|
||||
dirname: string
|
||||
};
|
||||
|
||||
const optionNames = new Set([
|
||||
@ -70,7 +72,10 @@ const ALLOWED_PLUGIN_KEYS = new Set([
|
||||
"inherits",
|
||||
]);
|
||||
|
||||
export default function manageOptions(opts?: Object) {
|
||||
export default function manageOptions(opts: {}): {
|
||||
options: Object,
|
||||
passes: Array<Array<[ Plugin, ?{} ]>>,
|
||||
}|null {
|
||||
return new OptionManager().init(opts);
|
||||
}
|
||||
|
||||
@ -81,7 +86,7 @@ class OptionManager {
|
||||
}
|
||||
|
||||
options: Object;
|
||||
passes: Array<Array<Plugin>>;
|
||||
passes: Array<Array<[Plugin, ?{}]>>;
|
||||
|
||||
/**
|
||||
* This is called when we want to merge the input `opts` into the
|
||||
@ -92,12 +97,19 @@ class OptionManager {
|
||||
* - `dirname` is used to resolve plugins relative to it.
|
||||
*/
|
||||
|
||||
mergeOptions(config: MergeOptions, pass?: Array<Plugin>) {
|
||||
mergeOptions(config: MergeOptions, pass?: Array<[Plugin, ?{}]>) {
|
||||
const result = loadConfig(config);
|
||||
|
||||
const plugins = result.plugins.map((descriptor) => loadPluginDescriptor(descriptor));
|
||||
const presets = result.presets.map((descriptor) => 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];
|
||||
|
||||
@ -124,7 +136,7 @@ class OptionManager {
|
||||
merge(this.options, result.options);
|
||||
}
|
||||
|
||||
init(opts: Object = {}): Object {
|
||||
init(opts: {}) {
|
||||
const configChain = buildConfigChain(opts);
|
||||
if (!configChain) return null;
|
||||
|
||||
@ -136,7 +148,8 @@ 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)) {
|
||||
e.message = `[BABEL] ${opts.filename || "unknown"}: ${e.message}`;
|
||||
const filename = typeof opts.filename === "string" ? opts.filename : null;
|
||||
e.message = `[BABEL] ${filename || "unknown"}: ${e.message}`;
|
||||
}
|
||||
|
||||
throw e;
|
||||
@ -185,12 +198,28 @@ class OptionManager {
|
||||
}
|
||||
}
|
||||
|
||||
type BasicDescriptor = {
|
||||
value: {}|Function,
|
||||
options: ?{},
|
||||
dirname: string,
|
||||
alias: string,
|
||||
loc: string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Load and validate the given config into a set of options, plugins, and presets.
|
||||
*/
|
||||
function loadConfig(config) {
|
||||
function loadConfig(config): {
|
||||
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, loadPlugin, config.dirname);
|
||||
|
||||
@ -202,6 +231,11 @@ function loadConfig(config) {
|
||||
dirname: config.dirname,
|
||||
};
|
||||
});
|
||||
|
||||
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, loadPreset, config.dirname);
|
||||
|
||||
@ -259,19 +293,19 @@ function loadPluginDescriptor(descriptor) {
|
||||
return [ result, descriptor.options];
|
||||
}
|
||||
|
||||
function instantiatePlugin({ value: pluginObject, descriptor }) {
|
||||
Object.keys(pluginObject).forEach((key) => {
|
||||
function instantiatePlugin({ value: pluginObj, descriptor }) {
|
||||
Object.keys(pluginObj).forEach((key) => {
|
||||
if (!ALLOWED_PLUGIN_KEYS.has(key)) {
|
||||
throw new Error(messages.get("pluginInvalidProperty", descriptor.alias, key));
|
||||
}
|
||||
});
|
||||
if (pluginObject.visitor && (pluginObject.visitor.enter || pluginObject.visitor.exit)) {
|
||||
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.");
|
||||
}
|
||||
|
||||
const plugin = Object.assign({}, pluginObject, {
|
||||
visitor: clone(pluginObject.visitor || {}),
|
||||
const plugin = Object.assign({}, pluginObj, {
|
||||
visitor: clone(pluginObj.visitor || {}),
|
||||
});
|
||||
|
||||
traverse.explode(plugin.visitor);
|
||||
@ -284,6 +318,7 @@ function instantiatePlugin({ value: pluginObject, descriptor }) {
|
||||
loc: descriptor.loc,
|
||||
value: plugin.inherits,
|
||||
options: descriptor.options,
|
||||
dirname: descriptor.dirname,
|
||||
};
|
||||
|
||||
inherits = loadPluginDescriptor(inheritsDescriptor)[0];
|
||||
@ -300,7 +335,7 @@ function instantiatePlugin({ value: pluginObject, descriptor }) {
|
||||
/**
|
||||
* Generate a config object that will act as the root of a new nested config.
|
||||
*/
|
||||
function loadPresetDescriptor(descriptor) {
|
||||
function loadPresetDescriptor(descriptor): MergeOptions {
|
||||
return {
|
||||
type: "preset",
|
||||
options: loadDescriptor(descriptor).value,
|
||||
@ -375,13 +410,6 @@ function normalizeOptions(config) {
|
||||
options.generatorOpts.generator = loadGenerator(options.generatorOpts.generator, config.dirname).value;
|
||||
}
|
||||
|
||||
if (config.options.presets && !Array.isArray(config.options.presets)) {
|
||||
throw new Error(`${alias}.presets should be an array`);
|
||||
}
|
||||
if (config.options.plugins && !Array.isArray(config.options.plugins)) {
|
||||
throw new Error(`${alias}.plugins should be an array`);
|
||||
}
|
||||
|
||||
delete options.passPerPreset;
|
||||
delete options.plugins;
|
||||
delete options.presets;
|
||||
@ -392,15 +420,19 @@ function normalizeOptions(config) {
|
||||
/**
|
||||
* Given a plugin/preset item, resolve it into a standard format.
|
||||
*/
|
||||
function normalizePair(pair, resolver, dirname) {
|
||||
function normalizePair(pair: mixed, resolver, dirname): {
|
||||
filepath: string|null,
|
||||
value: {}|Function,
|
||||
options: ?{},
|
||||
} {
|
||||
let options;
|
||||
let value = pair;
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length > 2) {
|
||||
throw new Error(`Unexpected extra options ${JSON.stringify(value.slice(2))}.`);
|
||||
if (Array.isArray(pair)) {
|
||||
if (pair.length > 2) {
|
||||
throw new Error(`Unexpected extra options ${JSON.stringify(pair.slice(2))}.`);
|
||||
}
|
||||
|
||||
[value, options] = value;
|
||||
[value, options] = pair;
|
||||
}
|
||||
|
||||
let filepath = null;
|
||||
@ -411,6 +443,10 @@ function normalizePair(pair, resolver, dirname) {
|
||||
} = resolver(value, dirname));
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
throw new Error(`Unexpected falsy value: ${String(value)}`);
|
||||
}
|
||||
|
||||
if (typeof value === "object" && value.__esModule) {
|
||||
if (value.default) {
|
||||
value = value.default;
|
||||
@ -419,13 +455,12 @@ function normalizePair(pair, resolver, dirname) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
throw new Error(`Unexpected falsy value: ${value}`);
|
||||
if (typeof value !== "object" && typeof value !== "function") {
|
||||
throw new Error(`Unsupported format: ${typeof value}. Expected an object or a function.`);
|
||||
}
|
||||
|
||||
const type = typeof value;
|
||||
if (type !== "object" && type !== "function") {
|
||||
throw new Error(`Unsupported format: ${type}. Expected an object or a function.`);
|
||||
if (options != null && typeof options !== "object") {
|
||||
throw new Error("Plugin/Preset options must be an object, null, or undefined");
|
||||
}
|
||||
|
||||
return { filepath, value, options };
|
||||
|
||||
@ -1,5 +1,23 @@
|
||||
// @flow
|
||||
|
||||
export default class Plugin {
|
||||
constructor(plugin: Object, key?: string) {
|
||||
constructor(plugin: {}, 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");
|
||||
}
|
||||
|
||||
this.key = plugin.name || key;
|
||||
|
||||
this.manipulateOptions = plugin.manipulateOptions;
|
||||
@ -12,5 +30,5 @@ export default class Plugin {
|
||||
manipulateOptions: ?Function;
|
||||
post: ?Function;
|
||||
pre: ?Function;
|
||||
visitor: Object;
|
||||
visitor: ?{};
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// @flow
|
||||
/* eslint max-len: "off" */
|
||||
|
||||
export default {
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
{
|
||||
"plugins": [["transform-react-jsx", "foo.bar"]]
|
||||
"plugins": [
|
||||
["transform-react-jsx", {"pragma": "foo.bar"}]
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user