/** * @fileoverview Plugins manager * @author Nicholas C. Zakas */ "use strict"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const debug = require("debug")("eslint:plugins"); const naming = require("../util/naming"); //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ const PLUGIN_NAME_PREFIX = "eslint-plugin-"; //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ /** * Plugin class */ class Plugins { /** * Creates the plugins context * @param {Environments} envContext - env context * @param {Rules} rulesContext - rules context */ constructor(envContext, rulesContext) { this._plugins = Object.create(null); this._environments = envContext; this._rules = rulesContext; } /** * 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 = naming.getNamespaceFromTerm(pluginName), pluginNameWithoutNamespace = naming.removeNamespaceFromTerm(pluginName), pluginNameWithoutPrefix = naming.removePrefixFromTerm(PLUGIN_NAME_PREFIX, pluginNameWithoutNamespace), shortName = pluginNamespace + pluginNameWithoutPrefix; // load up environments and rules this._plugins[shortName] = plugin; this._environments.importPlugin(plugin, shortName); this._rules.importPlugin(plugin, shortName); } /** * 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 this._plugins[pluginName] || null; } /** * Returns all plugins that are loaded. * @returns {Object} The plugins cache. */ getAll() { return this._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 = naming.getNamespaceFromTerm(pluginName), pluginNameWithoutNamespace = naming.removeNamespaceFromTerm(pluginName), pluginNameWithoutPrefix = naming.removePrefixFromTerm(PLUGIN_NAME_PREFIX, 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 (!this._plugins[shortName]) { try { plugin = require(longName); } catch (pluginLoadErr) { try { // Check whether the plugin exists require.resolve(longName); } catch (missingPluginErr) { // If the plugin can't be resolved, display the missing plugin error (usually a config or install error) debug(`Failed to load plugin ${longName}.`); missingPluginErr.message = `Failed to load plugin ${pluginName}: ${missingPluginErr.message}`; missingPluginErr.messageTemplate = "plugin-missing"; missingPluginErr.messageData = { pluginName: longName }; throw missingPluginErr; } // Otherwise, the plugin exists and is throwing on module load for some reason, so print the stack trace. throw pluginLoadErr; } // This step is costly, so skip if debug is disabled if (debug.enabled) { const resolvedPath = require.resolve(longName); let version = null; try { version = require(`${longName}/package.json`).version; } catch (e) { // Do nothing } const loadedPluginAndVersion = version ? `${longName}@${version}` : `${longName}, version unknown`; debug(`Loaded plugin ${pluginName} (${loadedPluginAndVersion}) (from ${resolvedPath})`); } 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. * @throws {Error} If "plugins" in config is not an array */ loadAll(pluginNames) { // if "plugins" in config is not an array, throw an error so user can fix their config. if (!Array.isArray(pluginNames)) { const pluginNotArrayMessage = "ESLint configuration error: \"plugins\" value must be an array"; debug(`${pluginNotArrayMessage}: ${JSON.stringify(pluginNames)}`); throw new Error(pluginNotArrayMessage); } // load each plugin by name pluginNames.forEach(this.load, this); } } module.exports = Plugins;