/** * @fileoverview Plugins manager * @author Nicholas C. Zakas */ "use strict"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const Environments = require("./environments"), Rules = require("../rules"); const debug = require("debug")("eslint:plugins"); //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ let plugins = Object.create(null); const PLUGIN_NAME_PREFIX = "eslint-plugin-", NAMESPACE_REGEX = /^@.*\//i; /** * Removes the prefix `eslint-plugin-` from a plugin name. * @param {string} pluginName The name of the plugin which may have the prefix. * @returns {string} The name of the plugin without prefix. */ function removePrefix(pluginName) { return pluginName.indexOf(PLUGIN_NAME_PREFIX) === 0 ? pluginName.substring(PLUGIN_NAME_PREFIX.length) : pluginName; } /** * Gets the scope (namespace) of a plugin. * @param {string} pluginName The name of the plugin which may have the prefix. * @returns {string} The name of the plugins namepace if it has one. */ function getNamespace(pluginName) { return pluginName.match(NAMESPACE_REGEX) ? pluginName.match(NAMESPACE_REGEX)[0] : ""; } /** * Removes the namespace from a plugin name. * @param {string} pluginName The name of the plugin which may have the prefix. * @returns {string} The name of the plugin without the namespace. */ function removeNamespace(pluginName) { return pluginName.replace(NAMESPACE_REGEX, ""); } //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ module.exports = { removePrefix, getNamespace, removeNamespace, /** * Defines a plugin with a given name rather than loading from disk. * @param {string} pluginName The name of the plugin to load. * @param {Object} plugin The plugin object. * @returns {void} */ define(pluginName, plugin) { const pluginNamespace = getNamespace(pluginName), pluginNameWithoutNamespace = removeNamespace(pluginName), pluginNameWithoutPrefix = removePrefix(pluginNameWithoutNamespace), shortName = pluginNamespace + pluginNameWithoutPrefix; // load up environments and rules plugins[shortName] = plugin; Environments.importPlugin(plugin, shortName); Rules.importPlugin(plugin, shortName); // load up environments and rules for the name that '@scope/' was omitted // 3 lines below will be removed by 4.0.0 plugins[pluginNameWithoutPrefix] = plugin; Environments.importPlugin(plugin, pluginNameWithoutPrefix); Rules.importPlugin(plugin, pluginNameWithoutPrefix); }, /** * Gets a plugin with the given name. * @param {string} pluginName The name of the plugin to retrieve. * @returns {Object} The plugin or null if not loaded. */ get(pluginName) { return plugins[pluginName] || null; }, /** * Returns all plugins that are loaded. * @returns {Object} The plugins cache. */ getAll() { return plugins; }, /** * Loads a plugin with the given name. * @param {string} pluginName The name of the plugin to load. * @returns {void} * @throws {Error} If the plugin cannot be loaded. */ load(pluginName) { const pluginNamespace = getNamespace(pluginName), pluginNameWithoutNamespace = removeNamespace(pluginName), pluginNameWithoutPrefix = removePrefix(pluginNameWithoutNamespace), shortName = pluginNamespace + pluginNameWithoutPrefix, longName = pluginNamespace + PLUGIN_NAME_PREFIX + pluginNameWithoutPrefix; let plugin = null; if (pluginName.match(/\s+/)) { const whitespaceError = new Error("Whitespace found in plugin name '" + pluginName + "'"); whitespaceError.messageTemplate = "whitespace-found"; whitespaceError.messageData = { pluginName: longName }; throw whitespaceError; } if (!plugins[shortName]) { try { plugin = require(longName); } catch (err) { debug("Failed to load plugin " + longName + "."); err.message = "Failed to load plugin " + pluginName + ": " + err.message; err.messageTemplate = "plugin-missing"; err.messageData = { pluginName: longName }; throw err; } this.define(pluginName, plugin); } }, /** * Loads all plugins from an array. * @param {string[]} pluginNames An array of plugins names. * @returns {void} * @throws {Error} If a plugin cannot be loaded. */ loadAll(pluginNames) { pluginNames.forEach(this.load, this); }, /** * Resets plugin information. Use for tests only. * @returns {void} */ testReset() { plugins = Object.create(null); } };