<commit_before>// @flow

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 path from "path";
import traverse from "babel-traverse";
import clone from "lodash/clone";
import { makeWeakCache } from "./caching";
import { getEnv } from "./helpers/environment";

import {
  loadPlugin,
  loadPreset,
  loadParser,
  loadGenerator,
} from "./loading/files";

type MergeOptions = {
  +type: "arguments" | "options" | "preset",
  options: {},
  alias: string,
  loc: string,
  dirname: string,
};

const optionNames = new Set([
  "relative",
  "filename",
  "filenameRelative",
  "inputSourceMap",
  "env",
  "mode",
  "retainLines",
  "highlightCode",
  "suppressDeprecationMessages",
  "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",
]);

const ALLOWED_PLUGIN_KEYS = new Set([
  "name",
  "manipulateOptions",
  "pre",
  "post",
  "visitor",
  "inherits",
]);

export default function manageOptions(opts: {}): {
  options: Object,
  passes: Array<Array<Plugin>>,
} | null {
  return new OptionManager().init(opts);
}

class OptionManager {
  constructor() {
    this.options = createInitialOptions();
    this.passes = [[]];
  }

  options: Object;
  passes: Array<Array<Plugin>>;

  /**
   * This is called when we want to merge the input `opts` into the
   * base options.
   *
   *  - `alias` is used to output pretty traces back to the original source.
   *  - `loc` is used to point to the original config.
   *  - `dirname` is used to resolve plugins relative to it.
   */

  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];

    // resolve presets
    if (presets.length > 0) {
      let presetPasses = null;
      if (passPerPreset) {
        presetPasses = presets.map(() => []);
        // The passes are created in the same order as the preset list, but are inserted before any
        // existing additional passes.
        this.passes.splice(1, 0, ...presetPasses);
      }

      presets.forEach((presetConfig, i) => {
        this.mergeOptions(presetConfig, presetPasses ? presetPasses[i] : pass);
      });
    }

    // resolve plugins
    if (plugins.length > 0) {
      pass.unshift(...plugins);
    }

    merge(this.options, result.options);
  }

  init(opts: {}) {
    const configChain = buildConfigChain(opts);
    if (!configChain) return null;

    try {
      for (const config of configChain) {
        this.mergeOptions(config);
      }
    } catch (e) {
      // 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}`;
      }

      throw e;
    }

    opts = 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.
    opts.plugins = this.passes[0];
    opts.presets = this.passes
      .slice(1)
      .filter(plugins => plugins.length > 0)
      .map(plugins => ({ plugins }));

    if (opts.inputSourceMap) {
      opts.sourceMaps = true;
    }

    if (opts.moduleId) {
      opts.moduleIds = true;
    }

    defaults(opts, {
      moduleRoot: opts.sourceRoot,
    });

    defaults(opts, {
      sourceRoot: opts.moduleRoot,
    });

    defaults(opts, {
      filenameRelative: opts.filename,
    });

    const basenameRelative = path.basename(opts.filenameRelative);

    if (path.extname(opts.filenameRelative) === ".mjs") {
      opts.sourceType = "module";
    }

    defaults(opts, {
      sourceFileName: basenameRelative,
      sourceMapTarget: basenameRelative,
    });

    return {
      options: opts,
      passes: this.passes,
    };
  }
}

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.
 */
const loadConfig = makeWeakCache((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,
    );

    return {
      alias: filepath || `${config.loc}$${index}`,
      loc: filepath || config.loc,
      value,
      options,
      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,
    );

    return {
      alias: filepath || `${config.loc}$${index}`,
      loc: filepath || config.loc,
      value,
      options,
      dirname: config.dirname,
    };
  });

  return { options, plugins, presets };
});

/**
 * Load a generic plugin/preset from the given descriptor loaded from the config object.
 */
const loadDescriptor = makeWeakCache((descriptor, cache) => {
  if (typeof descriptor.value !== "function") {
    return { value: descriptor.value, descriptor };
  }
  const { value, options } = descriptor;

  const api = Object.assign(Object.create(context), {
    cache,
    env: () => cache.using(() => getEnv()),
  });

  let item;
  try {
    item = value(api, options, { dirname: descriptor.dirname });
  } catch (e) {
    if (descriptor.alias) {
      e.message += ` (While processing: ${JSON.stringify(descriptor.alias)})`;
    }
    throw e;
  }

  if (!item || typeof item !== "object") {
    throw new Error("Plugin/Preset did not return an object.");
  }

  return { value: item, descriptor };
});

/**
 * Instantiate a plugin for the given descriptor, returning the plugin/options pair.
 */
function loadPluginDescriptor(descriptor: BasicDescriptor) {
  if (descriptor.value instanceof Plugin) {
    if (descriptor.options) {
      throw new Error(
        "Passed options to an existing Plugin instance will not work.",
      );
    }

    return descriptor.value;
  }

  return instantiatePlugin(loadDescriptor(descriptor));
}

const instantiatePlugin = makeWeakCache(
  ({ value: pluginObj, descriptor }, cache) => {
    Object.keys(pluginObj).forEach(key => {
      if (!ALLOWED_PLUGIN_KEYS.has(key)) {
        throw new Error(
          `Plugin ${descriptor.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.",
      );
    }

    const plugin = Object.assign({}, pluginObj, {
      visitor: clone(pluginObj.visitor || {}),
    });

    traverse.explode(plugin.visitor);

    let inheritsDescriptor;
    let inherits;
    if (plugin.inherits) {
      inheritsDescriptor = {
        alias: `${descriptor.loc}$inherits`,
        loc: descriptor.loc,
        value: plugin.inherits,
        options: descriptor.options,
        dirname: descriptor.dirname,
      };

      // If the inherited plugin changes, reinstantiate this plugin.
      inherits = cache.invalidate(() =>
        loadPluginDescriptor(inheritsDescriptor),
      );

      plugin.pre = chain(inherits.pre, plugin.pre);
      plugin.post = chain(inherits.post, plugin.post);
      plugin.manipulateOptions = chain(
        inherits.manipulateOptions,
        plugin.manipulateOptions,
      );
      plugin.visitor = traverse.visitors.merge([
        inherits.visitor,
        plugin.visitor,
      ]);
    }

    return new Plugin(plugin, descriptor.options, descriptor.alias);
  },
);

/**
 * Generate a config object that will act as the root of a new nested config.
 */
const loadPresetDescriptor = (descriptor: BasicDescriptor): MergeOptions => {
  return instantiatePreset(loadDescriptor(descriptor));
};

const instantiatePreset = makeWeakCache(({ value, descriptor }) => {
  return {
    type: "preset",
    options: value,
    alias: descriptor.alias,
    loc: descriptor.loc,
    dirname: descriptor.dirname,
  };
});

/**
 * 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.parser,
      config.dirname,
    ).value;
  }

  if (
    options.generatorOpts &&
    typeof options.generatorOpts.generator === "string"
  ) {
    options.generatorOpts = Object.assign({}, options.generatorOpts);
    options.generatorOpts.generator = loadGenerator(
      options.generatorOpts.generator,
      config.dirname,
    ).value;
  }

  delete options.passPerPreset;
  delete options.plugins;
  delete options.presets;

  return options;
}

/**
 * Given a plugin/preset item, resolve it into a standard format.
 */
function normalizePair(
  pair: mixed,
  resolver,
  dirname,
): {
  filepath: string | null,
  value: {} | Function,
  options: ?{},
} {
  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;
  }

  let filepath = null;
  if (typeof value === "string") {
    ({ filepath, value } = 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;
    } else {
      throw new Error("Must export a default export when using ES6 modules.");
    }
  }

  if (typeof value !== "object" && typeof value !== "function") {
    throw new Error(
      `Unsupported format: ${typeof value}. 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 };
}

function chain(a, b) {
  const fns = [a, b].filter(Boolean);
  if (fns.length <= 1) return fns[0];

  return function(...args) {
    for (const fn of fns) {
      fn.apply(this, args);
    }
  };
}

function createInitialOptions() {
  return {
    sourceType: "module",
    babelrc: true,
    filename: "unknown",
    code: true,
    ast: true,
    comments: true,
    compact: "auto",
    highlightCode: true,
  };
}
<commit_msg>Add more types around descriptor processing.<commit_after>// @flow

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 path from "path";
import traverse from "babel-traverse";
import clone from "lodash/clone";
import { makeWeakCache } from "./caching";
import { getEnv } from "./helpers/environment";

import {
  loadPlugin,
  loadPreset,
  loadParser,
  loadGenerator,
} from "./loading/files";

type MergeOptions = {
  +type: "arguments" | "options" | "preset",
  options: {},
  alias: string,
  loc: string,
  dirname: string,
};

const optionNames = new Set([
  "relative",
  "filename",
  "filenameRelative",
  "inputSourceMap",
  "env",
  "mode",
  "retainLines",
  "highlightCode",
  "suppressDeprecationMessages",
  "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",
]);

const ALLOWED_PLUGIN_KEYS = new Set([
  "name",
  "manipulateOptions",
  "pre",
  "post",
  "visitor",
  "inherits",
]);

export default function manageOptions(opts: {}): {
  options: Object,
  passes: Array<Array<Plugin>>,
} | null {
  return new OptionManager().init(opts);
}

class OptionManager {
  constructor() {
    this.options = createInitialOptions();
    this.passes = [[]];
  }

  options: Object;
  passes: Array<Array<Plugin>>;

  /**
   * This is called when we want to merge the input `opts` into the
   * base options.
   *
   *  - `alias` is used to output pretty traces back to the original source.
   *  - `loc` is used to point to the original config.
   *  - `dirname` is used to resolve plugins relative to it.
   */

  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];

    // resolve presets
    if (presets.length > 0) {
      let presetPasses = null;
      if (passPerPreset) {
        presetPasses = presets.map(() => []);
        // The passes are created in the same order as the preset list, but are inserted before any
        // existing additional passes.
        this.passes.splice(1, 0, ...presetPasses);
      }

      presets.forEach((presetConfig, i) => {
        this.mergeOptions(presetConfig, presetPasses ? presetPasses[i] : pass);
      });
    }

    // resolve plugins
    if (plugins.length > 0) {
      pass.unshift(...plugins);
    }

    merge(this.options, result.options);
  }

  init(opts: {}) {
    const configChain = buildConfigChain(opts);
    if (!configChain) return null;

    try {
      for (const config of configChain) {
        this.mergeOptions(config);
      }
    } catch (e) {
      // 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}`;
      }

      throw e;
    }

    opts = 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.
    opts.plugins = this.passes[0];
    opts.presets = this.passes
      .slice(1)
      .filter(plugins => plugins.length > 0)
      .map(plugins => ({ plugins }));

    if (opts.inputSourceMap) {
      opts.sourceMaps = true;
    }

    if (opts.moduleId) {
      opts.moduleIds = true;
    }

    defaults(opts, {
      moduleRoot: opts.sourceRoot,
    });

    defaults(opts, {
      sourceRoot: opts.moduleRoot,
    });

    defaults(opts, {
      filenameRelative: opts.filename,
    });

    const basenameRelative = path.basename(opts.filenameRelative);

    if (path.extname(opts.filenameRelative) === ".mjs") {
      opts.sourceType = "module";
    }

    defaults(opts, {
      sourceFileName: basenameRelative,
      sourceMapTarget: basenameRelative,
    });

    return {
      options: opts,
      passes: this.passes,
    };
  }
}

type BasicDescriptor = {
  value: {} | Function,
  options: ?{},
  dirname: string,
  alias: string,
  loc: string,
};

type LoadedDescriptor = {
  value: {},
  descriptor: BasicDescriptor,
};

/**
 * Load and validate the given config into a set of options, plugins, and presets.
 */
const loadConfig = makeWeakCache((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,
    );

    return {
      alias: filepath || `${config.loc}$${index}`,
      loc: filepath || config.loc,
      value,
      options,
      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,
    );

    return {
      alias: filepath || `${config.loc}$${index}`,
      loc: filepath || config.loc,
      value,
      options,
      dirname: config.dirname,
    };
  });

  return { options, plugins, presets };
});

/**
 * Load a generic plugin/preset from the given descriptor loaded from the config object.
 */
const loadDescriptor = makeWeakCache(
  (descriptor: BasicDescriptor, cache): LoadedDescriptor => {
    if (typeof descriptor.value !== "function") {
      return { value: descriptor.value, descriptor };
    }
    const { value, options } = descriptor;

    const api = Object.assign(Object.create(context), {
      cache,
      env: () => cache.using(() => getEnv()),
    });

    let item;
    try {
      item = value(api, options, { dirname: descriptor.dirname });
    } catch (e) {
      if (descriptor.alias) {
        e.message += ` (While processing: ${JSON.stringify(descriptor.alias)})`;
      }
      throw e;
    }

    if (!item || typeof item !== "object") {
      throw new Error("Plugin/Preset did not return an object.");
    }

    return { value: item, descriptor };
  },
);

/**
 * Instantiate a plugin for the given descriptor, returning the plugin/options pair.
 */
function loadPluginDescriptor(descriptor: BasicDescriptor): Plugin {
  if (descriptor.value instanceof Plugin) {
    if (descriptor.options) {
      throw new Error(
        "Passed options to an existing Plugin instance will not work.",
      );
    }

    return descriptor.value;
  }

  return instantiatePlugin(loadDescriptor(descriptor));
}

const instantiatePlugin = makeWeakCache(
  ({ value: pluginObj, descriptor }: LoadedDescriptor, cache): Plugin => {
    Object.keys(pluginObj).forEach(key => {
      if (!ALLOWED_PLUGIN_KEYS.has(key)) {
        throw new Error(
          `Plugin ${descriptor.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.",
      );
    }

    const plugin = Object.assign({}, pluginObj, {
      visitor: clone(pluginObj.visitor || {}),
    });

    traverse.explode(plugin.visitor);

    let inheritsDescriptor;
    let inherits;
    if (plugin.inherits) {
      inheritsDescriptor = {
        alias: `${descriptor.loc}$inherits`,
        loc: descriptor.loc,
        value: plugin.inherits,
        options: descriptor.options,
        dirname: descriptor.dirname,
      };

      // If the inherited plugin changes, reinstantiate this plugin.
      inherits = cache.invalidate(() =>
        loadPluginDescriptor(inheritsDescriptor),
      );

      plugin.pre = chain(inherits.pre, plugin.pre);
      plugin.post = chain(inherits.post, plugin.post);
      plugin.manipulateOptions = chain(
        inherits.manipulateOptions,
        plugin.manipulateOptions,
      );
      plugin.visitor = traverse.visitors.merge([
        inherits.visitor,
        plugin.visitor,
      ]);
    }

    return new Plugin(plugin, descriptor.options, descriptor.alias);
  },
);

/**
 * Generate a config object that will act as the root of a new nested config.
 */
const loadPresetDescriptor = (descriptor: BasicDescriptor): MergeOptions => {
  return instantiatePreset(loadDescriptor(descriptor));
};

const instantiatePreset = makeWeakCache(
  ({ value, descriptor }: LoadedDescriptor): MergeOptions => {
    return {
      type: "preset",
      options: value,
      alias: descriptor.alias,
      loc: descriptor.loc,
      dirname: descriptor.dirname,
    };
  },
);

/**
 * 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.parser,
      config.dirname,
    ).value;
  }

  if (
    options.generatorOpts &&
    typeof options.generatorOpts.generator === "string"
  ) {
    options.generatorOpts = Object.assign({}, options.generatorOpts);
    options.generatorOpts.generator = loadGenerator(
      options.generatorOpts.generator,
      config.dirname,
    ).value;
  }

  delete options.passPerPreset;
  delete options.plugins;
  delete options.presets;

  return options;
}

/**
 * Given a plugin/preset item, resolve it into a standard format.
 */
function normalizePair(
  pair: mixed,
  resolver,
  dirname,
): {
  filepath: string | null,
  value: {} | Function,
  options: ?{},
} {
  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;
  }

  let filepath = null;
  if (typeof value === "string") {
    ({ filepath, value } = 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;
    } else {
      throw new Error("Must export a default export when using ES6 modules.");
    }
  }

  if (typeof value !== "object" && typeof value !== "function") {
    throw new Error(
      `Unsupported format: ${typeof value}. 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 };
}

function chain(a, b) {
  const fns = [a, b].filter(Boolean);
  if (fns.length <= 1) return fns[0];

  return function(...args) {
    for (const fn of fns) {
      fn.apply(this, args);
    }
  };
}

function createInitialOptions() {
  return {
    sourceType: "module",
    babelrc: true,
    filename: "unknown",
    code: true,
    ast: true,
    comments: true,
    compact: "auto",
    highlightCode: true,
  };
}
<|endoftext|>


<commit_before>$(document).ready(function() {

  // activate completion buttons
  $('.creation').each(function() {
    var url = $(this).attr('href');
    $(this).removeAttr('href');
    this.url = url;
    $(this).click(function() {
      console.log(this.url);
      // remove all button with same url
      $('.creation').each(function() {
        if (this.url == url) {
          $(this).remove();
        }
      });

      // complete SIP
      $.ajax({
        type: "POST",
        url: url,
        success: function(result) {
          console.log(result);
        }
      });
    });
  });

  // create new form instance, providing a single row of default data
  var search = new advancedSearch.AdvancedSearchView({
    el: $('#search_form_container'),
    rows: [{
      'op': '',
      'query': '',
      'field': '',
      'type': ''
    }]
  });

  // define op field
  var opAttributes = {
    title: 'boolean operator',
    class: 'search_op_selector'
  }
  search.addSelect('op', opAttributes, {
    'or': 'or',
    'and': 'and',
    'not': 'not'
  });

  // define query field
  search.addInput('query', {title: 'search query', 'class': 'aip-search-query-input'});

  // default field name field
  search.addSelect('field', {title: 'field name'}, {
    ''             : 'Any',
    'filename'     : 'File name',
    'fileExtension': 'File extension',
    'accessionid'  : 'Accession number',
    'ingestdate'   : 'Ingest date (YYYY-MM-DD)',
    'sipuuid'      : 'SIP UUID'
  });

  // default field name field
  search.addSelect('type', {title: 'query type'}, {
    'term': 'Keyword',
    'string': 'Phrase'
  });

  // don't show first op field
  search.fieldVisibilityCheck = function(rowIndex, fieldName) {
    return rowIndex > 0 || fieldName != 'op';
  };

  // override default search state if URL parameters set
  if (search.urlParamsToData()) {
    search.rows = search.urlParamsToData();
  }

  search.render();

  function backlogSearchSubmit() {
    var destination = '/ingest/backlog/' + '?' + search.toUrlParams();
    if($('#search_mode').is(':checked')) {
      destination += '&mode=file';
    }
    window.location = destination;
  }

  // submit logic
  $('#search_submit').click(function() {
    backlogSearchSubmit();
  });

  $('#search_form').submit(function() {
    backlogSearchSubmit();
    return false;
  });
});
<commit_msg>Added UI feedback when SIP created.<commit_after>$(document).ready(function() {

  // activate completion buttons
  $('.creation').each(function() {
    var url = $(this).attr('href');
    $(this).removeAttr('href');
    this.url = url;
    this.fired = false;
    $(this).click(function() {
      if (this.fired == false) {
        this.fired = true;
        // remove all button with same url
        $('.creation').each(function() {
          if (this.url == url) {
            $(this).attr('disabled', 'disabled');
          }
        });

        // complete SIP
        $.ajax({
          type: "POST",
          url: url,
          error: function(error) {
            $('.creation').each(function() {
              if (this.url == url) {
                alert(error.statusText);
                $(this).removeAttr('disabled');
                this.fired = false;
              }
            });
          },
          success: function(result) {
            $('.creation').each(function() {
              if (this.url == url) {
                $(this).parent().parent().fadeOut();
              }
            });
          }
        });
      }
    });
  });

  // create new form instance, providing a single row of default data
  var search = new advancedSearch.AdvancedSearchView({
    el: $('#search_form_container'),
    rows: [{
      'op': '',
      'query': '',
      'field': '',
      'type': ''
    }]
  });

  // define op field
  var opAttributes = {
    title: 'boolean operator',
    class: 'search_op_selector'
  }
  search.addSelect('op', opAttributes, {
    'or': 'or',
    'and': 'and',
    'not': 'not'
  });

  // define query field
  search.addInput('query', {title: 'search query', 'class': 'aip-search-query-input'});

  // default field name field
  search.addSelect('field', {title: 'field name'}, {
    ''             : 'Any',
    'filename'     : 'File name',
    'fileExtension': 'File extension',
    'accessionid'  : 'Accession number',
    'ingestdate'   : 'Ingest date (YYYY-MM-DD)',
    'sipuuid'      : 'SIP UUID'
  });

  // default field name field
  search.addSelect('type', {title: 'query type'}, {
    'term': 'Keyword',
    'string': 'Phrase'
  });

  // don't show first op field
  search.fieldVisibilityCheck = function(rowIndex, fieldName) {
    return rowIndex > 0 || fieldName != 'op';
  };

  // override default search state if URL parameters set
  if (search.urlParamsToData()) {
    search.rows = search.urlParamsToData();
  }

  search.render();

  function backlogSearchSubmit() {
    var destination = '/ingest/backlog/' + '?' + search.toUrlParams();
    if($('#search_mode').is(':checked')) {
      destination += '&mode=file';
    }
    window.location = destination;
  }

  // submit logic
  $('#search_submit').click(function() {
    backlogSearchSubmit();
  });

  $('#search_form').submit(function() {
    backlogSearchSubmit();
    return false;
  });
});
<|endoftext|>


<commit_before>var fs = require('fs');
var child_process = require('child_process')
var max_sleep = 300
//Added a comment that achieves no real goal.

var commit_messages = [
  "Fixing an important issue with the universe.",
  "Someone poisoned the waterhole!",
  "You feeling lucky punk?",
  "Five bullets or six?",
  "Fixed memory parsing error #1337",
  "Cleaned out the intertubes",
  "AAAAAAAAAAARGH!",
  "Fixed a typo",
  "Updated the readme",
  "Fixing a typo added in the last readme update",
  "GET A HAIRCUT!",
  "Updated the readme again",
  "Found an issue with the letter 'a', so I fixed it.",
  "Incompatible dimensions found, Cthuhlu Error 666",
  "Who you calling a fool?",
  "How many more of these can I make?",
  "Fixing an error introduced in the last commit",
  "Re-updating the readme.",
  "Work in Progress",
  "WIP",
  "Quick fix",
  "Grammar fix",
  "Fixing punctuation",
  "Correcting style",
  "Logic error"
]

if ( process.argv[ 2 ] && process.argv[ 3 ] ) {
  var inFile = process.argv[ 2 ]
  var outFile = process.argv[ 3 ]
  if (process.argv [ 4 ]) max_sleep = process.argv [ 4 ]
  console.info("Writing from %s to %s, with up to %s seconds between commits", inFile, outFile, max_sleep)
  var outFD = fs.openSync(outFile, 'w')
  fs.readFile(inFile, function(err,data) {
    var length = data.length
    console.info("Bytes: %s", length)
    try {
      child_process.execFileSync('/usr/bin/git', ['add',<commit_msg>AAAAAAAAAAARGH!<commit_after>var fs = require('fs');
var child_process = require('child_process')
var max_sleep = 300
//Added a comment that achieves no real goal.

var commit_messages = [
  "Fixing an important issue with the universe.",
  "Someone poisoned the waterhole!",
  "You feeling lucky punk?",
  "Five bullets or six?",
  "Fixed memory parsing error #1337",
  "Cleaned out the intertubes",
  "AAAAAAAAAAARGH!",
  "Fixed a typo",
  "Updated the readme",
  "Fixing a typo added in the last readme update",
  "GET A HAIRCUT!",
  "Updated the readme again",
  "Found an issue with the letter 'a', so I fixed it.",
  "Incompatible dimensions found, Cthuhlu Error 666",
  "Who you calling a fool?",
  "How many more of these can I make?",
  "Fixing an error introduced in the last commit",
  "Re-updating the readme.",
  "Work in Progress",
  "WIP",
  "Quick fix",
  "Grammar fix",
  "Fixing punctuation",
  "Correcting style",
  "Logic error"
]

if ( process.argv[ 2 ] && process.argv[ 3 ] ) {
  var inFile = process.argv[ 2 ]
  var outFile = process.argv[ 3 ]
  if (process.argv [ 4 ]) max_sleep = process.argv [ 4 ]
  console.info("Writing from %s to %s, with up to %s seconds between commits", inFile, outFile, max_sleep)
  var outFD = fs.openSync(outFile, 'w')
  fs.readFile(inFile, function(err,data) {
    var length = data.length
    console.info("Bytes: %s", length)
    try {
      child_process.execFileSync('/usr/bin/git', ['add', <|endoftext|>


<commit_before>const dispatch = require('../dispatch/central-dispatch');
const log = require('../util/log');
const maybeFormatMessage = require('../util/maybe-format-message');

const BlockType = require('./block-type');

// These extensions are currently built into the VM repository but should not be loaded at startup.
// TODO: move these out into a separate repository?
// TODO: change extension spec so that library info, including extension ID, can be collected through static methods
const Scratch3PenBlocks = require('../extensions/scratch3_pen');
const Scratch3WeDo2Blocks = require('../extensions/scratch3_wedo2');
const Scratch3MusicBlocks = require('../extensions/scratch3_music');
const Scratch3MicroBitBlocks = require('../extensions/scratch3_microbit');
const Scratch3TranslateBlocks = require('../extensions/scratch3_translate');
const Scratch3VideoSensingBlocks = require('../extensions/scratch3_video_sensing');
const Scratch3SpeechBlocks = require('../extensions/scratch3_speech');

const builtinExtensions = {
    pen: Scratch3PenBlocks,
    wedo2: Scratch3WeDo2Blocks,
    music: Scratch3MusicBlocks,
    microbit: Scratch3MicroBitBlocks,
    translate: Scratch3TranslateBlocks,
    videoSensing: Scratch3VideoSensingBlocks
    speech: Scratch3SpeechBlocks
};

/**
 * @typedef {object} ArgumentInfo - Information about an extension block argument
 * @property {ArgumentType} type - the type of value this argument can take
 * @property {*|undefined} default - the default value of this argument (default: blank)
 */

/**
 * @typedef {object} ConvertedBlockInfo - Raw extension block data paired with processed data ready for scratch-blocks
 * @property {ExtensionBlockMetadata} info - the raw block info
 * @property {object} json - the scratch-blocks JSON definition for this block
 * @property {string} xml - the scratch-blocks XML definition for this block
 */

/**
 * @typedef {object} CategoryInfo - Information about a block category
 * @property {string} id - the unique ID of this category
 * @property {string} name - the human-readable name of this category
 * @property {string|undefined} blockIconURI - optional URI for the block icon image
 * @property {string} color1 - the primary color for this category, in '#rrggbb' format
 * @property {string} color2 - the secondary color for this category, in '#rrggbb' format
 * @property {string} color3 - the tertiary color for this category, in '#rrggbb' format
 * @property {Array.<ConvertedBlockInfo>} blocks - the blocks, separators, etc. in this category
 * @property {Array.<object>} menus - the menus provided by this category
 */

/**
 * @typedef {object} PendingExtensionWorker - Information about an extension worker still initializing
 * @property {string} extensionURL - the URL of the extension to be loaded by this worker
 * @property {Function} resolve - function to call on successful worker startup
 * @property {Function} reject - function to call on failed worker startup
 */

class ExtensionManager {
    constructor (runtime) {
        /**
         * The ID number to provide to the next extension worker.
         * @type {int}
         */
        this.nextExtensionWorker = 0;

        /**
         * FIFO queue of extensions which have been requested but not yet loaded in a worker,
         * along with promise resolution functions to call once the worker is ready or failed.
         *
         * @type {Array.<PendingExtensionWorker>}
         */
        this.pendingExtensions = [];

        /**
         * Map of worker ID to workers which have been allocated but have not yet finished initialization.
         * @type {Array.<PendingExtensionWorker>}
         */
        this.pendingWorkers = [];

        /**
         * Set of loaded extension URLs/IDs (equivalent for built-in extensions).
         * @type {Set.<string>}
         * @private
         */
        this._loadedExtensions = new Map();

        /**
         * Keep a reference to the runtime so we can construct internal extension objects.
         * TODO: remove this in favor of extensions accessing the runtime as a service.
         * @type {Runtime}
         */
        this.runtime = runtime;

        dispatch.setService('extensions', this).catch(e => {
            log.error(`ExtensionManager was unable to register extension service: ${JSON.stringify(e)}`);
        });
    }

    /**
     * Check whether an extension is registered or is in the process of loading. This is intended to control loading or
     * adding extensions so it may return `true` before the extension is ready to be used. Use the promise returned by
     * `loadExtensionURL` if you need to wait until the extension is truly ready.
     * @param {string} extensionID - the ID of the extension.
     * @returns {boolean} - true if loaded, false otherwise.
     */
    isExtensionLoaded (extensionID) {
        return this._loadedExtensions.has(extensionID);
    }

    /**
     * Load an extension by URL or internal extension ID
     * @param {string} extensionURL - the URL for the extension to load OR the ID of an internal extension
     * @returns {Promise} resolved once the extension is loaded and initialized or rejected on failure
     */
    loadExtensionURL (extensionURL) {
        if (builtinExtensions.hasOwnProperty(extensionURL)) {
            /** @TODO dupe handling for non-builtin extensions. See commit 670e51d33580e8a2e852b3b038bb3afc282f81b9 */
            if (this.isExtensionLoaded(extensionURL)) {
                const message = `Rejecting attempt to load a second extension with ID ${extensionURL}`;
                log.warn(message);
                return Promise.reject(new Error(message));
            }

            const extension = builtinExtensions[extensionURL];
            const extensionInstance = new extension(this.runtime);
            return this._registerInternalExtension(extensionInstance).then(serviceName => {
                this._loadedExtensions.set(extensionURL, serviceName);
            });
        }

        return new Promise((resolve, reject) => {
            // If we `require` this at the global level it breaks non-webpack targets, including tests
            const ExtensionWorker = require('worker-loader?name=extension-worker.js!./extension-worker');

            this.pendingExtensions.push({extensionURL, resolve, reject});
            dispatch.addWorker(new ExtensionWorker());
        });
    }

    /**
    * regenerate blockinfo for any loaded extensions
    */
    refreshBlocks () {
        this._loadedExtensions.forEach(serviceName => {
            dispatch.call(serviceName, 'getInfo')
                .then(info => {
                    dispatch.call('runtime', '_refreshExtensionPrimitives', info);
                })
                .catch(e => {
                    log.error(`Failed to refresh buildtin extension primitives: ${JSON.stringify(e)}`);
                });
        });
    }

    allocateWorker () {
        const id = this.nextExtensionWorker++;
        const workerInfo = this.pendingExtensions.shift();
        this.pendingWorkers[id] = workerInfo;
        return [id, workerInfo.extensionURL];
    }

    /**
     * Collect extension metadata from the specified service and begin the extension registration process.
     * @param {string} serviceName - the name of the service hosting the extension.
     */
    registerExtensionService (serviceName) {
        dispatch.call(serviceName, 'getInfo').then(info => {
            this._registerExtensionInfo(serviceName, info);
        });
    }

    /**
     * Called by an extension worker to indicate that the worker has finished initialization.
     * @param {int} id - the worker ID.
     * @param {*?} e - the error encountered during initialization, if any.
     */
    onWorkerInit (id, e) {
        const workerInfo = this.pendingWorkers[id];
        delete this.pendingWorkers[id];
        if (e) {
            workerInfo.reject(e);
        } else {
            workerInfo.resolve(id);
        }
    }

    /**
     * Register an internal (non-Worker) extension object
     * @param {object} extensionObject - the extension object to register
     * @returns {Promise} resolved once the extension is fully registered or rejected on failure
     */
    _registerInternalExtension (extensionObject) {
        const extensionInfo = extensionObject.getInfo();
        const fakeWorkerId = this.nextExtensionWorker++;
        const serviceName = `extension_${fakeWorkerId}_${extensionInfo.id}`;
        return dispatch.setService(serviceName, extensionObject)
            .then(() => {
                dispatch.call('extensions', 'registerExtensionService', serviceName);
                return serviceName;
            });
    }

    /**
     * Sanitize extension info then register its primitives with the VM.
     * @param {string} serviceName - the name of the service hosting the extension
     * @param {ExtensionInfo} extensionInfo - the extension's metadata
     * @private
     */
    _registerExtensionInfo (serviceName, extensionInfo) {
        extensionInfo = this._prepareExtensionInfo(serviceName, extensionInfo);
        dispatch.call('runtime', '_registerExtensionPrimitives', extensionInfo).catch(e => {
            log.error(`Failed to register primitives for extension on service ${serviceName}:`, e);
        });
    }

    /**
     * Modify the provided text as necessary to ensure that it may be used as an attribute value in valid XML.
     * @param {string} text - the text to be sanitized
     * @returns {string} - the sanitized text
     * @private
     */
    _sanitizeID (text) {
        return text.toString().replace(/[<"&]/, '_');
    }

    /**
     * Apply minor cleanup and defaults for optional extension fields.
     * TODO: make the ID unique in cases where two copies of the same extension are loaded.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {ExtensionInfo} extensionInfo - the extension info to be sanitized
     * @returns {ExtensionInfo} - a new extension info object with cleaned-up values
     * @private
     */
    _prepareExtensionInfo (serviceName, extensionInfo) {
        extensionInfo = Object.assign({}, extensionInfo);
        if (!/^[a-z0-9]+$/i.test(extensionInfo.id)) {
            throw new Error('Invalid extension id');
        }
        extensionInfo.name = extensionInfo.name || extensionInfo.id;
        extensionInfo.blocks = extensionInfo.blocks || [];
        extensionInfo.targetTypes = extensionInfo.targetTypes || [];
        extensionInfo.blocks = extensionInfo.blocks.reduce((results, blockInfo) => {
            try {
                let result;
                switch (blockInfo) {
                case '---': // separator
                    result = '---';
                    break;
                default: // an ExtensionBlockMetadata object
                    result = this._prepareBlockInfo(serviceName, blockInfo);
                    break;
                }
                results.push(result);
            } catch (e) {
                // TODO: more meaningful error reporting
                log.error(`Error processing block: ${e.message}, Block:\n${JSON.stringify(blockInfo)}`);
            }
            return results;
        }, []);
        extensionInfo.menus = extensionInfo.menus || [];
        extensionInfo.menus = this._prepareMenuInfo(serviceName, extensionInfo.menus);
        return extensionInfo;
    }

    /**
     * Prepare extension menus. e.g. setup binding for dynamic menu functions.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {Array.<MenuInfo>} menus - the menu defined by the extension.
     * @returns {Array.<MenuInfo>} - a menuInfo object with all preprocessing done.
     * @private
     */
    _prepareMenuInfo (serviceName, menus) {
        const menuNames = Object.getOwnPropertyNames(menus);
        for (let i = 0; i < menuNames.length; i++) {
            const item = menuNames[i];
            // If the value is a string, it should be the name of a function in the
            // extension object to call to populate the menu whenever it is opened.
            // Set up the binding for the function object here so
            // we can use it later when converting the menu for Scratch Blocks.
            if (typeof menus[item] === 'string') {
                const serviceObject = dispatch.services[serviceName];
                const menuName = menus[item];
                menus[item] = this._getExtensionMenuItems.bind(this, serviceObject, menuName);
            }
        }
        return menus;
    }

    /**
     * Fetch the items for a particular extension menu, providing the target ID for context.
     * @param {object} extensionObject - the extension object providing the menu.
     * @param {string} menuName - the name of the menu function to call.
     * @returns {Array} menu items ready for scratch-blocks.
     * @private
     */
    _getExtensionMenuItems (extensionObject, menuName) {
        // Fetch the items appropriate for the target currently being edited. This assumes that menus only
        // collect items when opened by the user while editing a particular target.
        const editingTarget = this.runtime.getEditingTarget() || this.runtime.getTargetForStage();
        const editingTargetID = editingTarget ? editingTarget.id : null;
        const extensionMessageContext = this.runtime.makeMessageContextForTarget(editingTarget);

        // TODO: Fix this to use dispatch.call when extensions are running in workers.
        const menuFunc = extensionObject[menuName];
        const menuItems = menuFunc.call(extensionObject, editingTargetID).map(
            item => {
                item = maybeFormatMessage(item, extensionMessageContext);
                if (typeof item === 'object') {
                    return [
                        maybeFormatMessage(item.text, extensionMessageContext),
                        item.value
                    ];
                }
                return item;
            });

        if (!menuItems || menuItems.length < 1) {
            throw new Error(`Extension menu returned no items: ${menuName}`);
        }
        return menuItems;
    }

    /**
     * Apply defaults for optional block fields.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {ExtensionBlockMetadata} blockInfo - the block info from the extension
     * @returns {ExtensionBlockMetadata} - a new block info object which has values for all relevant optional fields.
     * @private
     */
    _prepareBlockInfo (serviceName, blockInfo) {
        blockInfo = Object.assign({}, {
            blockType: BlockType.COMMAND,
            terminal: false,
            blockAllThreads: false,
            arguments: {}
        }, blockInfo);
        blockInfo.opcode = this._sanitizeID(blockInfo.opcode);
        blockInfo.text = blockInfo.text || blockInfo.opcode;

        if (blockInfo.blockType !== BlockType.EVENT) {
            blockInfo.func = blockInfo.func ? this._sanitizeID(blockInfo.func) : blockInfo.opcode;

            /**
             * This is only here because the VM performs poorly when blocks return promises.
             * @TODO make it possible for the VM to resolve a promise and continue during the same Scratch "tick"
             */
            if (dispatch._isRemoteService(serviceName)) {
                blockInfo.func = dispatch.call.bind(dispatch, serviceName, blockInfo.func);
            } else {
                const serviceObject = dispatch.services[serviceName];
                const func = serviceObject[blockInfo.func];
                if (func) {
                    blockInfo.func = func.bind(serviceObject);
                } else if (blockInfo.blockType !== BlockType.EVENT) {
                    throw new Error(`Could not find extension block function called ${blockInfo.func}`);
                }
            }
        } else if (blockInfo.func) {
            log.warn(`Ignoring function "${blockInfo.func}" for event block ${blockInfo.opcode}`);
        }

        return blockInfo;
    }
}

module.exports = ExtensionManager;
<commit_msg>Fix missing comma from bad merge.<commit_after>const dispatch = require('../dispatch/central-dispatch');
const log = require('../util/log');
const maybeFormatMessage = require('../util/maybe-format-message');

const BlockType = require('./block-type');

// These extensions are currently built into the VM repository but should not be loaded at startup.
// TODO: move these out into a separate repository?
// TODO: change extension spec so that library info, including extension ID, can be collected through static methods
const Scratch3PenBlocks = require('../extensions/scratch3_pen');
const Scratch3WeDo2Blocks = require('../extensions/scratch3_wedo2');
const Scratch3MusicBlocks = require('../extensions/scratch3_music');
const Scratch3MicroBitBlocks = require('../extensions/scratch3_microbit');
const Scratch3TranslateBlocks = require('../extensions/scratch3_translate');
const Scratch3VideoSensingBlocks = require('../extensions/scratch3_video_sensing');
const Scratch3SpeechBlocks = require('../extensions/scratch3_speech');

const builtinExtensions = {
    pen: Scratch3PenBlocks,
    wedo2: Scratch3WeDo2Blocks,
    music: Scratch3MusicBlocks,
    microbit: Scratch3MicroBitBlocks,
    translate: Scratch3TranslateBlocks,
    videoSensing: Scratch3VideoSensingBlocks,
    speech: Scratch3SpeechBlocks
};

/**
 * @typedef {object} ArgumentInfo - Information about an extension block argument
 * @property {ArgumentType} type - the type of value this argument can take
 * @property {*|undefined} default - the default value of this argument (default: blank)
 */

/**
 * @typedef {object} ConvertedBlockInfo - Raw extension block data paired with processed data ready for scratch-blocks
 * @property {ExtensionBlockMetadata} info - the raw block info
 * @property {object} json - the scratch-blocks JSON definition for this block
 * @property {string} xml - the scratch-blocks XML definition for this block
 */

/**
 * @typedef {object} CategoryInfo - Information about a block category
 * @property {string} id - the unique ID of this category
 * @property {string} name - the human-readable name of this category
 * @property {string|undefined} blockIconURI - optional URI for the block icon image
 * @property {string} color1 - the primary color for this category, in '#rrggbb' format
 * @property {string} color2 - the secondary color for this category, in '#rrggbb' format
 * @property {string} color3 - the tertiary color for this category, in '#rrggbb' format
 * @property {Array.<ConvertedBlockInfo>} blocks - the blocks, separators, etc. in this category
 * @property {Array.<object>} menus - the menus provided by this category
 */

/**
 * @typedef {object} PendingExtensionWorker - Information about an extension worker still initializing
 * @property {string} extensionURL - the URL of the extension to be loaded by this worker
 * @property {Function} resolve - function to call on successful worker startup
 * @property {Function} reject - function to call on failed worker startup
 */

class ExtensionManager {
    constructor (runtime) {
        /**
         * The ID number to provide to the next extension worker.
         * @type {int}
         */
        this.nextExtensionWorker = 0;

        /**
         * FIFO queue of extensions which have been requested but not yet loaded in a worker,
         * along with promise resolution functions to call once the worker is ready or failed.
         *
         * @type {Array.<PendingExtensionWorker>}
         */
        this.pendingExtensions = [];

        /**
         * Map of worker ID to workers which have been allocated but have not yet finished initialization.
         * @type {Array.<PendingExtensionWorker>}
         */
        this.pendingWorkers = [];

        /**
         * Set of loaded extension URLs/IDs (equivalent for built-in extensions).
         * @type {Set.<string>}
         * @private
         */
        this._loadedExtensions = new Map();

        /**
         * Keep a reference to the runtime so we can construct internal extension objects.
         * TODO: remove this in favor of extensions accessing the runtime as a service.
         * @type {Runtime}
         */
        this.runtime = runtime;

        dispatch.setService('extensions', this).catch(e => {
            log.error(`ExtensionManager was unable to register extension service: ${JSON.stringify(e)}`);
        });
    }

    /**
     * Check whether an extension is registered or is in the process of loading. This is intended to control loading or
     * adding extensions so it may return `true` before the extension is ready to be used. Use the promise returned by
     * `loadExtensionURL` if you need to wait until the extension is truly ready.
     * @param {string} extensionID - the ID of the extension.
     * @returns {boolean} - true if loaded, false otherwise.
     */
    isExtensionLoaded (extensionID) {
        return this._loadedExtensions.has(extensionID);
    }

    /**
     * Load an extension by URL or internal extension ID
     * @param {string} extensionURL - the URL for the extension to load OR the ID of an internal extension
     * @returns {Promise} resolved once the extension is loaded and initialized or rejected on failure
     */
    loadExtensionURL (extensionURL) {
        if (builtinExtensions.hasOwnProperty(extensionURL)) {
            /** @TODO dupe handling for non-builtin extensions. See commit 670e51d33580e8a2e852b3b038bb3afc282f81b9 */
            if (this.isExtensionLoaded(extensionURL)) {
                const message = `Rejecting attempt to load a second extension with ID ${extensionURL}`;
                log.warn(message);
                return Promise.reject(new Error(message));
            }

            const extension = builtinExtensions[extensionURL];
            const extensionInstance = new extension(this.runtime);
            return this._registerInternalExtension(extensionInstance).then(serviceName => {
                this._loadedExtensions.set(extensionURL, serviceName);
            });
        }

        return new Promise((resolve, reject) => {
            // If we `require` this at the global level it breaks non-webpack targets, including tests
            const ExtensionWorker = require('worker-loader?name=extension-worker.js!./extension-worker');

            this.pendingExtensions.push({extensionURL, resolve, reject});
            dispatch.addWorker(new ExtensionWorker());
        });
    }

    /**
    * regenerate blockinfo for any loaded extensions
    */
    refreshBlocks () {
        this._loadedExtensions.forEach(serviceName => {
            dispatch.call(serviceName, 'getInfo')
                .then(info => {
                    dispatch.call('runtime', '_refreshExtensionPrimitives', info);
                })
                .catch(e => {
                    log.error(`Failed to refresh buildtin extension primitives: ${JSON.stringify(e)}`);
                });
        });
    }

    allocateWorker () {
        const id = this.nextExtensionWorker++;
        const workerInfo = this.pendingExtensions.shift();
        this.pendingWorkers[id] = workerInfo;
        return [id, workerInfo.extensionURL];
    }

    /**
     * Collect extension metadata from the specified service and begin the extension registration process.
     * @param {string} serviceName - the name of the service hosting the extension.
     */
    registerExtensionService (serviceName) {
        dispatch.call(serviceName, 'getInfo').then(info => {
            this._registerExtensionInfo(serviceName, info);
        });
    }

    /**
     * Called by an extension worker to indicate that the worker has finished initialization.
     * @param {int} id - the worker ID.
     * @param {*?} e - the error encountered during initialization, if any.
     */
    onWorkerInit (id, e) {
        const workerInfo = this.pendingWorkers[id];
        delete this.pendingWorkers[id];
        if (e) {
            workerInfo.reject(e);
        } else {
            workerInfo.resolve(id);
        }
    }

    /**
     * Register an internal (non-Worker) extension object
     * @param {object} extensionObject - the extension object to register
     * @returns {Promise} resolved once the extension is fully registered or rejected on failure
     */
    _registerInternalExtension (extensionObject) {
        const extensionInfo = extensionObject.getInfo();
        const fakeWorkerId = this.nextExtensionWorker++;
        const serviceName = `extension_${fakeWorkerId}_${extensionInfo.id}`;
        return dispatch.setService(serviceName, extensionObject)
            .then(() => {
                dispatch.call('extensions', 'registerExtensionService', serviceName);
                return serviceName;
            });
    }

    /**
     * Sanitize extension info then register its primitives with the VM.
     * @param {string} serviceName - the name of the service hosting the extension
     * @param {ExtensionInfo} extensionInfo - the extension's metadata
     * @private
     */
    _registerExtensionInfo (serviceName, extensionInfo) {
        extensionInfo = this._prepareExtensionInfo(serviceName, extensionInfo);
        dispatch.call('runtime', '_registerExtensionPrimitives', extensionInfo).catch(e => {
            log.error(`Failed to register primitives for extension on service ${serviceName}:`, e);
        });
    }

    /**
     * Modify the provided text as necessary to ensure that it may be used as an attribute value in valid XML.
     * @param {string} text - the text to be sanitized
     * @returns {string} - the sanitized text
     * @private
     */
    _sanitizeID (text) {
        return text.toString().replace(/[<"&]/, '_');
    }

    /**
     * Apply minor cleanup and defaults for optional extension fields.
     * TODO: make the ID unique in cases where two copies of the same extension are loaded.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {ExtensionInfo} extensionInfo - the extension info to be sanitized
     * @returns {ExtensionInfo} - a new extension info object with cleaned-up values
     * @private
     */
    _prepareExtensionInfo (serviceName, extensionInfo) {
        extensionInfo = Object.assign({}, extensionInfo);
        if (!/^[a-z0-9]+$/i.test(extensionInfo.id)) {
            throw new Error('Invalid extension id');
        }
        extensionInfo.name = extensionInfo.name || extensionInfo.id;
        extensionInfo.blocks = extensionInfo.blocks || [];
        extensionInfo.targetTypes = extensionInfo.targetTypes || [];
        extensionInfo.blocks = extensionInfo.blocks.reduce((results, blockInfo) => {
            try {
                let result;
                switch (blockInfo) {
                case '---': // separator
                    result = '---';
                    break;
                default: // an ExtensionBlockMetadata object
                    result = this._prepareBlockInfo(serviceName, blockInfo);
                    break;
                }
                results.push(result);
            } catch (e) {
                // TODO: more meaningful error reporting
                log.error(`Error processing block: ${e.message}, Block:\n${JSON.stringify(blockInfo)}`);
            }
            return results;
        }, []);
        extensionInfo.menus = extensionInfo.menus || [];
        extensionInfo.menus = this._prepareMenuInfo(serviceName, extensionInfo.menus);
        return extensionInfo;
    }

    /**
     * Prepare extension menus. e.g. setup binding for dynamic menu functions.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {Array.<MenuInfo>} menus - the menu defined by the extension.
     * @returns {Array.<MenuInfo>} - a menuInfo object with all preprocessing done.
     * @private
     */
    _prepareMenuInfo (serviceName, menus) {
        const menuNames = Object.getOwnPropertyNames(menus);
        for (let i = 0; i < menuNames.length; i++) {
            const item = menuNames[i];
            // If the value is a string, it should be the name of a function in the
            // extension object to call to populate the menu whenever it is opened.
            // Set up the binding for the function object here so
            // we can use it later when converting the menu for Scratch Blocks.
            if (typeof menus[item] === 'string') {
                const serviceObject = dispatch.services[serviceName];
                const menuName = menus[item];
                menus[item] = this._getExtensionMenuItems.bind(this, serviceObject, menuName);
            }
        }
        return menus;
    }

    /**
     * Fetch the items for a particular extension menu, providing the target ID for context.
     * @param {object} extensionObject - the extension object providing the menu.
     * @param {string} menuName - the name of the menu function to call.
     * @returns {Array} menu items ready for scratch-blocks.
     * @private
     */
    _getExtensionMenuItems (extensionObject, menuName) {
        // Fetch the items appropriate for the target currently being edited. This assumes that menus only
        // collect items when opened by the user while editing a particular target.
        const editingTarget = this.runtime.getEditingTarget() || this.runtime.getTargetForStage();
        const editingTargetID = editingTarget ? editingTarget.id : null;
        const extensionMessageContext = this.runtime.makeMessageContextForTarget(editingTarget);

        // TODO: Fix this to use dispatch.call when extensions are running in workers.
        const menuFunc = extensionObject[menuName];
        const menuItems = menuFunc.call(extensionObject, editingTargetID).map(
            item => {
                item = maybeFormatMessage(item, extensionMessageContext);
                if (typeof item === 'object') {
                    return [
                        maybeFormatMessage(item.text, extensionMessageContext),
                        item.value
                    ];
                }
                return item;
            });

        if (!menuItems || menuItems.length < 1) {
            throw new Error(`Extension menu returned no items: ${menuName}`);
        }
        return menuItems;
    }

    /**
     * Apply defaults for optional block fields.
     * @param {string} serviceName - the name of the service hosting this extension block
     * @param {ExtensionBlockMetadata} blockInfo - the block info from the extension
     * @returns {ExtensionBlockMetadata} - a new block info object which has values for all relevant optional fields.
     * @private
     */
    _prepareBlockInfo (serviceName, blockInfo) {
        blockInfo = Object.assign({}, {
            blockType: BlockType.COMMAND,
            terminal: false,
            blockAllThreads: false,
            arguments: {}
        }, blockInfo);
        blockInfo.opcode = this._sanitizeID(blockInfo.opcode);
        blockInfo.text = blockInfo.text || blockInfo.opcode;

        if (blockInfo.blockType !== BlockType.EVENT) {
            blockInfo.func = blockInfo.func ? this._sanitizeID(blockInfo.func) : blockInfo.opcode;

            /**
             * This is only here because the VM performs poorly when blocks return promises.
             * @TODO make it possible for the VM to resolve a promise and continue during the same Scratch "tick"
             */
            if (dispatch._isRemoteService(serviceName)) {
                blockInfo.func = dispatch.call.bind(dispatch, serviceName, blockInfo.func);
            } else {
                const serviceObject = dispatch.services[serviceName];
                const func = serviceObject[blockInfo.func];
                if (func) {
                    blockInfo.func = func.bind(serviceObject);
                } else if (blockInfo.blockType !== BlockType.EVENT) {
                    throw new Error(`Could not find extension block function called ${blockInfo.func}`);
                }
            }
        } else if (blockInfo.func) {
            log.warn(`Ignoring function "${blockInfo.func}" for event block ${blockInfo.opcode}`);
        }

        return blockInfo;
    }
}

module.exports = ExtensionManager;
<|endoftext|>


<commit_before>'use strict'
const path = require('path');

module.exports = function (config) {
  let sourcePath = './app'
  config.set({
    basePath: '.',
    files: [
      'karma.shim.js'
    ],
    reporters: ['progress', 'coverage'],
    preprocessors: {
      'karma.shim.js': ['webpack'],
    },
    webpack: {
      cache: true,
      devtool: 'inline-source-map',
      entry: {},
      output: {},
      isparta: {
        embedSource: true,
        noAutoWrap: true,
        // these babel options will be passed only to isparta and not to babel-loader
        babel: {
          presets: ['es2015']
        }
      },
      module: {
        loaders: [
          {
            test: /\.js$/,
            exclude: /(node_modules)/,
            loader: 'babel',
            query: {
              presets: ['es2015']
            }
          },
          // transpile and instrument only testing sources with isparta
          {
            test: /\.js$/,
            include: /^(?!.*\-test.js$)[\/\w\.-]+$/,
            exclude: /(node_modules|mocks.js)/,
            loader: 'isparta'
          }
        ]
      },
      resolve: {
        root: [sourcePath],
        modules: [
          'node_modules',
          sourcePath
        ]
      }
    },
    autoWatch: true,
    frameworks: ['jasmine'],
    browsers: ['Chrome'],
    plugins: [
      'karma-chrome-launcher',
      'karma-firefox-launcher',
      'karma-jasmine',
      'karma-webpack',
      'karma-coverage',
    ]
  });
  let configuration = {
    // other things
    customLaunchers: {
      Chrome_travis_ci: {
        base: 'Chrome',
        flags: ['--no-sandbox']
      }
    },
  };
  if (process.env.TRAVIS) {
    configuration.browsers = ['Chrome_travis_ci'];
  }
  config.set(configuration);
}
<commit_msg>karma travis<commit_after>'use strict'
const path = require('path');
const isTravis = !!process.env.TRAVIS
module.exports = function (config) {
  let sourcePath = './app'
  let cr = null
  let browsers = ['Chrome']
  if (isTravis) {
    cr = {
      type: 'text'
    }
    browsers = ['Chrome_travis_ci'];
  }
  config.set({
    basePath: '.',
    files: [
      'karma.shim.js'
    ],
    reporters: ['progress', 'coverage'],
    preprocessors: {
      'karma.shim.js': ['webpack'],
    },
    webpack: {
      cache: true,
      devtool: 'inline-source-map',
      entry: {},
      output: {},
      isparta: {
        embedSource: true,
        noAutoWrap: true,
        // these babel options will be passed only to isparta and not to babel-loader
        babel: {
          presets: ['es2015']
        }
      },
      module: {
        loaders: [
          {
            test: /\.js$/,
            exclude: /(node_modules)/,
            loader: 'babel',
            query: {
              presets: ['es2015']
            }
          },
          // transpile and instrument only testing sources with isparta
          {
            test: /\.js$/,
            include: /^(?!.*\-test.js$)[\/\w\.-]+$/,
            exclude: /(node_modules|mocks.js)/,
            loader: 'isparta'
          }
        ]
      },
      resolve: {
        root: [sourcePath],
        modules: [
          'node_modules',
          sourcePath
        ]
      }
    },
    coverageReporter: cr,
    autoWatch: true,
    frameworks: ['jasmine'],
    browsers: browsers,
    plugins: [
      'karma-chrome-launcher',
      'karma-firefox-launcher',
      'karma-jasmine',
      'karma-webpack',
      'karma-coverage',
    ],
    customLaunchers: {
      Chrome_travis_ci: {
        base: 'Chrome',
        flags: ['--no-sandbox']
      }
    }
  });
}
<|endoftext|>


<commit_before>import { merge } from 'lodash';

import { LOAD } from './constants';

export default function(reducer) {
    return (state, action) => reducer(
        action.type === LOAD
            ? merge(state, action.payload)
            : state,
        action
    );
}
<commit_msg>Return new state object on reducer LOAD<commit_after>import { merge } from 'lodash';

import { LOAD } from './constants';

export default function(reducer) {
    return (state, action) => reducer(
        action.type === LOAD
            ? merge({}, state, action.payload)
            : state,
        action
    );
}
<|endoftext|>


<commit_before>import * as context from "../index";
import Plugin from "./plugin";
import * as messages from "babel-messages";
import defaults from "lodash/defaults";
import merge from "./helpers/merge";
import removed from "./removed";
import buildConfigChain from "./build-config-chain";
import path from "path";
import traverse from "babel-traverse";
import clone from "lodash/clone";

import { loadPlugin, loadPreset, loadParser, loadGenerator } from "./loading/files";

type MergeOptions = {
  type: "arguments"|"options"|"preset",
  options?: Object,
  alias: string,
  loc?: string,
  dirname?: string
};

const optionNames = new Set([
  "filename",
  "filenameRelative",
  "inputSourceMap",
  "env",
  "mode",
  "retainLines",
  "highlightCode",
  "suppressDeprecationMessages",
  "presets",
  "plugins",
  "ignore",
  "only",
  "code",
  "metadata",
  "ast",
  "extends",
  "comments",
  "shouldPrintComment",
  "wrapPluginVisitorMethod",
  "compact",
  "minified",
  "sourceMaps",
  "sourceMapTarget",
  "sourceFileName",
  "sourceRoot",
  "babelrc",
  "sourceType",
  "auxiliaryCommentBefore",
  "auxiliaryCommentAfter",
  "resolveModuleSource",
  "getModuleId",
  "moduleRoot",
  "moduleIds",
  "moduleId",
  "passPerPreset",
  // Deprecate top level parserOpts
  "parserOpts",
  // Deprecate top level generatorOpts
  "generatorOpts",
]);

const ALLOWED_PLUGIN_KEYS = new Set([
  "name",
  "manipulateOptions",
  "pre",
  "post",
  "visitor",
  "inherits",
]);

export default function manageOptions(opts?: Object) {
  return new OptionManager().init(opts);
}

class OptionManager {
  constructor() {
    this.options = createBareOptions();
    this.passes = [[]];
  }

  options: Object;
  passes: Array<Array<Plugin>>;

  /**
   * This is called when we want to merge the input `opts` into the
   * base options.
   *
   *  - `alias` is used to output pretty traces back to the original source.
   *  - `loc` is used to point to the original config.
   *  - `dirname` is used to resolve plugins relative to it.
   */

  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));

    const passPerPreset = config.options.passPerPreset;
    pass = pass || this.passes[0];

    // resolve presets
    if (presets.length > 0) {
      let presetPasses = null;
      if (passPerPreset) {
        presetPasses = presets.map(() => []);
        // The passes are created in the same order as the preset list, but are inserted before any
        // existing additional passes.
        this.passes.splice(1, 0, ...presetPasses);
      }

      presets.forEach((presetConfig, i) => {
        this.mergeOptions(presetConfig, presetPasses ? presetPasses[i] : pass);
      });
    }

    // resolve plugins
    if (plugins.length > 0) {
      pass.unshift(...plugins);
    }

    merge(this.options, result.options);
  }

  init(opts: Object = {}): Object {
    const configChain = buildConfigChain(opts);
    if (!configChain) return null;

    try {
      for (const config of configChain) {
        this.mergeOptions(config);
      }
    } catch (e) {
      // 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}`;
      }

      throw e;
    }

    opts = 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.
    opts.plugins = this.passes[0];
    opts.presets = this.passes.slice(1)
      .filter((plugins) => plugins.length > 0)
      .map((plugins) => ({ plugins }));

    if (opts.inputSourceMap) {
      opts.sourceMaps = true;
    }

    if (opts.moduleId) {
      opts.moduleIds = true;
    }

    defaults(opts, {
      moduleRoot: opts.sourceRoot,
    });

    defaults(opts, {
      sourceRoot: opts.moduleRoot,
    });

    defaults(opts, {
      filenameRelative: opts.filename,
    });

    const basenameRelative = path.basename(opts.filenameRelative);

    defaults(opts, {
      sourceFileName: basenameRelative,
      sourceMapTarget: basenameRelative,
    });

    return {
      options: opts,
      passes: this.passes,
    };
  }
}

/**
 * Load and validate the given config into a set of options, plugins, and presets.
 */
function loadConfig(config) {
  const options = normalizeOptions(config);

  const plugins = (config.options.plugins || []).map((plugin, index) => {
    const { filepath, value, options } = normalizePair(plugin, loadPlugin, config.dirname);

    return {
      alias: filepath || `${config.loc}$${index}`,
      loc: filepath || config.loc,
      value,
      options,
      dirname: config.dirname,
    };
  });
  const presets = (config.options.presets || []).map((preset, index) => {
    const { filepath, value, options } = normalizePair(preset, loadPreset, config.dirname);

    return {
      alias: filepath || `${config.loc}$${index}`,
      loc: filepath || config.loc,
      value,
      options,
      dirname: config.dirname,
    };
  });

  return { options, plugins, presets };
}

/**
 * Load a generic plugin/preset from the given descriptor loaded from the config object.
 */
function loadDescriptor(descriptor, skipOptions) {
  if (typeof descriptor.value !== "function") return { value: descriptor.value, descriptor };

  const { value, options } = descriptor;
  let item;
  try {
    if (skipOptions) {
      item = value(context);
    } else {
      item = value(context, options, { dirname: descriptor.dirname });
    }
  } catch (e) {
    if (descriptor.alias) e.message += ` (While processing: ${JSON.stringify(descriptor.alias)})`;
    throw e;
  }

  if (!item || typeof item !== "object") {
    throw new Error("Plugin/Preset did not return an object.");
  }

  return { value: item, descriptor };
}

/**
 * Instantiate a plugin for the given descriptor, returning the plugin/options pair.
 */
const PLUGIN_CACHE = new WeakMap();
function loadPluginDescriptor(descriptor) {
  if (descriptor.value instanceof Plugin) return [ descriptor.value, descriptor.options ];

  let result = PLUGIN_CACHE.get(descriptor.value);
  if (!result) {
    result = instantiatePlugin(loadDescriptor(descriptor, true /* skipOptions */));
    PLUGIN_CACHE.set(descriptor.value, result);
  }

  return [ result, descriptor.options];
}

function instantiatePlugin({ value: pluginObject, descriptor }) {
  Object.keys(pluginObject).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)) {
    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 || {}),
  });

  traverse.explode(plugin.visitor);

  let inheritsDescriptor;
  let inherits;
  if (plugin.inherits) {
    inheritsDescriptor = {
      alias: `${descriptor.loc}$inherits`,
      loc: descriptor.loc,
      value: plugin.inherits,
      options: descriptor.options,
    };

    inherits = loadPluginDescriptor(inheritsDescriptor)[0];

    plugin.pre = chain(inherits.pre, plugin.pre);
    plugin.post = chain(inherits.post, plugin.post);
    plugin.manipulateOptions = chain(inherits.manipulateOptions, plugin.manipulateOptions);
    plugin.visitor = traverse.visitors.merge([inherits.visitor, plugin.visitor]);
  }

  return new Plugin(plugin, descriptor.alias);
}

/**
 * Generate a config object that will act as the root of a new nested config.
 */
function loadPresetDescriptor(descriptor) {
  return {
    type: "preset",
    options: loadDescriptor(descriptor).value,
    alias: descriptor.alias,
    loc: descriptor.loc,
    dirname: descriptor.dirname,
  };
}

/**
 * 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]) {
        throw new ReferenceError(`Using removed Babel 5 option: ${alias}.${key} - ${removed[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.parser, config.dirname).value;
  }

  if (options.generatorOpts && typeof options.generatorOpts.generator === "string") {
    options.generatorOpts = Object.assign({}, options.generatorOpts);
    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;

  return options;
}

/**
 * Given a plugin/preset item, resolve it into a standard format.
 */
function normalizePair(pair, resolver, dirname) {
  let options;
  let value = pair;
  if (Array.isArray(value)) {
    if (value.length > 2) {
      throw new Error(`Unexpected extra options ${JSON.stringify(value.slice(2))}.`);
    }

    [value, options] = value;
  }

  let filepath = null;
  if (typeof value === "string") {
    ({
      filepath,
      value,
    } = resolver(value, dirname));
  }

  if (typeof value === "object" && value.__esModule) {
    if (value.default) {
      value = value.default;
    } else {
      throw new Error("Must export a default export when using ES6 modules.");
    }
  }

  if (!value) {
    throw new Error(`Unexpected falsy value: ${value}`);
  }

  const type = typeof value;
  if (type !== "object" && type !== "function") {
    throw new Error(`Unsupported format: ${type}. Expected an object or a function.`);
  }

  return { filepath, value, options };
}

function chain(a, b) {
  const fns = [a, b].filter(Boolean);
  if (fns.length <= 1) return fns[0];

  return function(...args) {
    for (const fn of fns) {
      fn.apply(this, args);
    }
  };
}

function createBareOptions() {
  return {
    sourceType: "module",
    babelrc: true,
    filename: "unknown",
    code: true,
    metadata: true,
    ast: true,
    comments: true,
    compact: "auto",
    highlightCode: true,
  };
}
<commit_msg>Use clearer function name.<commit_after>import * as context from "../index";
import Plugin from "./plugin";
import * as messages from "babel-messages";
import defaults from "lodash/defaults";
import merge from "./helpers/merge";
import removed from "./removed";
import buildConfigChain from "./build-config-chain";
import path from "path";
import traverse from "babel-traverse";
import clone from "lodash/clone";

import { loadPlugin, loadPreset, loadParser, loadGenerator } from "./loading/files";

type MergeOptions = {
  type: "arguments"|"options"|"preset",
  options?: Object,
  alias: string,
  loc?: string,
  dirname?: string
};

const optionNames = new Set([
  "filename",
  "filenameRelative",
  "inputSourceMap",
  "env",
  "mode",
  "retainLines",
  "highlightCode",
  "suppressDeprecationMessages",
  "presets",
  "plugins",
  "ignore",
  "only",
  "code",
  "metadata",
  "ast",
  "extends",
  "comments",
  "shouldPrintComment",
  "wrapPluginVisitorMethod",
  "compact",
  "minified",
  "sourceMaps",
  "sourceMapTarget",
  "sourceFileName",
  "sourceRoot",
  "babelrc",
  "sourceType",
  "auxiliaryCommentBefore",
  "auxiliaryCommentAfter",
  "resolveModuleSource",
  "getModuleId",
  "moduleRoot",
  "moduleIds",
  "moduleId",
  "passPerPreset",
  // Deprecate top level parserOpts
  "parserOpts",
  // Deprecate top level generatorOpts
  "generatorOpts",
]);

const ALLOWED_PLUGIN_KEYS = new Set([
  "name",
  "manipulateOptions",
  "pre",
  "post",
  "visitor",
  "inherits",
]);

export default function manageOptions(opts?: Object) {
  return new OptionManager().init(opts);
}

class OptionManager {
  constructor() {
    this.options = createInitialOptions();
    this.passes = [[]];
  }

  options: Object;
  passes: Array<Array<Plugin>>;

  /**
   * This is called when we want to merge the input `opts` into the
   * base options.
   *
   *  - `alias` is used to output pretty traces back to the original source.
   *  - `loc` is used to point to the original config.
   *  - `dirname` is used to resolve plugins relative to it.
   */

  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));

    const passPerPreset = config.options.passPerPreset;
    pass = pass || this.passes[0];

    // resolve presets
    if (presets.length > 0) {
      let presetPasses = null;
      if (passPerPreset) {
        presetPasses = presets.map(() => []);
        // The passes are created in the same order as the preset list, but are inserted before any
        // existing additional passes.
        this.passes.splice(1, 0, ...presetPasses);
      }

      presets.forEach((presetConfig, i) => {
        this.mergeOptions(presetConfig, presetPasses ? presetPasses[i] : pass);
      });
    }

    // resolve plugins
    if (plugins.length > 0) {
      pass.unshift(...plugins);
    }

    merge(this.options, result.options);
  }

  init(opts: Object = {}): Object {
    const configChain = buildConfigChain(opts);
    if (!configChain) return null;

    try {
      for (const config of configChain) {
        this.mergeOptions(config);
      }
    } catch (e) {
      // 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}`;
      }

      throw e;
    }

    opts = 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.
    opts.plugins = this.passes[0];
    opts.presets = this.passes.slice(1)
      .filter((plugins) => plugins.length > 0)
      .map((plugins) => ({ plugins }));

    if (opts.inputSourceMap) {
      opts.sourceMaps = true;
    }

    if (opts.moduleId) {
      opts.moduleIds = true;
    }

    defaults(opts, {
      moduleRoot: opts.sourceRoot,
    });

    defaults(opts, {
      sourceRoot: opts.moduleRoot,
    });

    defaults(opts, {
      filenameRelative: opts.filename,
    });

    const basenameRelative = path.basename(opts.filenameRelative);

    defaults(opts, {
      sourceFileName: basenameRelative,
      sourceMapTarget: basenameRelative,
    });

    return {
      options: opts,
      passes: this.passes,
    };
  }
}

/**
 * Load and validate the given config into a set of options, plugins, and presets.
 */
function loadConfig(config) {
  const options = normalizeOptions(config);

  const plugins = (config.options.plugins || []).map((plugin, index) => {
    const { filepath, value, options } = normalizePair(plugin, loadPlugin, config.dirname);

    return {
      alias: filepath || `${config.loc}$${index}`,
      loc: filepath || config.loc,
      value,
      options,
      dirname: config.dirname,
    };
  });
  const presets = (config.options.presets || []).map((preset, index) => {
    const { filepath, value, options } = normalizePair(preset, loadPreset, config.dirname);

    return {
      alias: filepath || `${config.loc}$${index}`,
      loc: filepath || config.loc,
      value,
      options,
      dirname: config.dirname,
    };
  });

  return { options, plugins, presets };
}

/**
 * Load a generic plugin/preset from the given descriptor loaded from the config object.
 */
function loadDescriptor(descriptor, skipOptions) {
  if (typeof descriptor.value !== "function") return { value: descriptor.value, descriptor };

  const { value, options } = descriptor;
  let item;
  try {
    if (skipOptions) {
      item = value(context);
    } else {
      item = value(context, options, { dirname: descriptor.dirname });
    }
  } catch (e) {
    if (descriptor.alias) e.message += ` (While processing: ${JSON.stringify(descriptor.alias)})`;
    throw e;
  }

  if (!item || typeof item !== "object") {
    throw new Error("Plugin/Preset did not return an object.");
  }

  return { value: item, descriptor };
}

/**
 * Instantiate a plugin for the given descriptor, returning the plugin/options pair.
 */
const PLUGIN_CACHE = new WeakMap();
function loadPluginDescriptor(descriptor) {
  if (descriptor.value instanceof Plugin) return [ descriptor.value, descriptor.options ];

  let result = PLUGIN_CACHE.get(descriptor.value);
  if (!result) {
    result = instantiatePlugin(loadDescriptor(descriptor, true /* skipOptions */));
    PLUGIN_CACHE.set(descriptor.value, result);
  }

  return [ result, descriptor.options];
}

function instantiatePlugin({ value: pluginObject, descriptor }) {
  Object.keys(pluginObject).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)) {
    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 || {}),
  });

  traverse.explode(plugin.visitor);

  let inheritsDescriptor;
  let inherits;
  if (plugin.inherits) {
    inheritsDescriptor = {
      alias: `${descriptor.loc}$inherits`,
      loc: descriptor.loc,
      value: plugin.inherits,
      options: descriptor.options,
    };

    inherits = loadPluginDescriptor(inheritsDescriptor)[0];

    plugin.pre = chain(inherits.pre, plugin.pre);
    plugin.post = chain(inherits.post, plugin.post);
    plugin.manipulateOptions = chain(inherits.manipulateOptions, plugin.manipulateOptions);
    plugin.visitor = traverse.visitors.merge([inherits.visitor, plugin.visitor]);
  }

  return new Plugin(plugin, descriptor.alias);
}

/**
 * Generate a config object that will act as the root of a new nested config.
 */
function loadPresetDescriptor(descriptor) {
  return {
    type: "preset",
    options: loadDescriptor(descriptor).value,
    alias: descriptor.alias,
    loc: descriptor.loc,
    dirname: descriptor.dirname,
  };
}

/**
 * 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]) {
        throw new ReferenceError(`Using removed Babel 5 option: ${alias}.${key} - ${removed[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.parser, config.dirname).value;
  }

  if (options.generatorOpts && typeof options.generatorOpts.generator === "string") {
    options.generatorOpts = Object.assign({}, options.generatorOpts);
    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;

  return options;
}

/**
 * Given a plugin/preset item, resolve it into a standard format.
 */
function normalizePair(pair, resolver, dirname) {
  let options;
  let value = pair;
  if (Array.isArray(value)) {
    if (value.length > 2) {
      throw new Error(`Unexpected extra options ${JSON.stringify(value.slice(2))}.`);
    }

    [value, options] = value;
  }

  let filepath = null;
  if (typeof value === "string") {
    ({
      filepath,
      value,
    } = resolver(value, dirname));
  }

  if (typeof value === "object" && value.__esModule) {
    if (value.default) {
      value = value.default;
    } else {
      throw new Error("Must export a default export when using ES6 modules.");
    }
  }

  if (!value) {
    throw new Error(`Unexpected falsy value: ${value}`);
  }

  const type = typeof value;
  if (type !== "object" && type !== "function") {
    throw new Error(`Unsupported format: ${type}. Expected an object or a function.`);
  }

  return { filepath, value, options };
}

function chain(a, b) {
  const fns = [a, b].filter(Boolean);
  if (fns.length <= 1) return fns[0];

  return function(...args) {
    for (const fn of fns) {
      fn.apply(this, args);
    }
  };
}

function createInitialOptions() {
  return {
    sourceType: "module",
    babelrc: true,
    filename: "unknown",
    code: true,
    metadata: true,
    ast: true,
    comments: true,
    compact: "auto",
    highlightCode: true,
  };
}
<|endoftext|>