UNPKG

5.68 kBJavaScriptView Raw
1/**
2 * @fileoverview Plugins manager
3 * @author Nicholas C. Zakas
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11const debug = require("debug")("eslint:plugins");
12const naming = require("../util/naming");
13const path = require("path");
14
15//------------------------------------------------------------------------------
16// Public Interface
17//------------------------------------------------------------------------------
18
19/**
20 * Plugin class
21 */
22class Plugins {
23
24 /**
25 * Creates the plugins context
26 * @param {Environments} envContext - env context
27 * @param {function(string, Rule): void} defineRule - Callback for when a plugin is defined which introduces rules
28 */
29 constructor(envContext, defineRule) {
30 this._plugins = Object.create(null);
31 this._environments = envContext;
32 this._defineRule = defineRule;
33 }
34
35 /**
36 * Defines a plugin with a given name rather than loading from disk.
37 * @param {string} pluginName The name of the plugin to load.
38 * @param {Object} plugin The plugin object.
39 * @returns {void}
40 */
41 define(pluginName, plugin) {
42 const longName = naming.normalizePackageName(pluginName, "eslint-plugin");
43 const shortName = naming.getShorthandName(longName, "eslint-plugin");
44
45 // load up environments and rules
46 this._plugins[shortName] = plugin;
47 this._environments.importPlugin(plugin, shortName);
48
49 if (plugin.rules) {
50 Object.keys(plugin.rules).forEach(ruleId => {
51 const qualifiedRuleId = `${shortName}/${ruleId}`,
52 rule = plugin.rules[ruleId];
53
54 this._defineRule(qualifiedRuleId, rule);
55 });
56 }
57 }
58
59 /**
60 * Gets a plugin with the given name.
61 * @param {string} pluginName The name of the plugin to retrieve.
62 * @returns {Object} The plugin or null if not loaded.
63 */
64 get(pluginName) {
65 return this._plugins[pluginName] || null;
66 }
67
68 /**
69 * Returns all plugins that are loaded.
70 * @returns {Object} The plugins cache.
71 */
72 getAll() {
73 return this._plugins;
74 }
75
76 /**
77 * Loads a plugin with the given name.
78 * @param {string} pluginName The name of the plugin to load.
79 * @returns {void}
80 * @throws {Error} If the plugin cannot be loaded.
81 */
82 load(pluginName) {
83 const longName = naming.normalizePackageName(pluginName, "eslint-plugin");
84 const shortName = naming.getShorthandName(longName, "eslint-plugin");
85 let plugin = null;
86
87 if (pluginName.match(/\s+/u)) {
88 const whitespaceError = new Error(`Whitespace found in plugin name '${pluginName}'`);
89
90 whitespaceError.messageTemplate = "whitespace-found";
91 whitespaceError.messageData = {
92 pluginName: longName
93 };
94 throw whitespaceError;
95 }
96
97 if (!this._plugins[shortName]) {
98 try {
99 plugin = require(longName);
100 } catch (pluginLoadErr) {
101 try {
102
103 // Check whether the plugin exists
104 require.resolve(longName);
105 } catch (missingPluginErr) {
106
107 // If the plugin can't be resolved, display the missing plugin error (usually a config or install error)
108 debug(`Failed to load plugin ${longName}.`);
109 missingPluginErr.message = `Failed to load plugin ${pluginName}: ${missingPluginErr.message}`;
110 missingPluginErr.messageTemplate = "plugin-missing";
111 missingPluginErr.messageData = {
112 pluginName: longName,
113 eslintPath: path.resolve(__dirname, "../..")
114 };
115 throw missingPluginErr;
116 }
117
118 // Otherwise, the plugin exists and is throwing on module load for some reason, so print the stack trace.
119 throw pluginLoadErr;
120 }
121
122 // This step is costly, so skip if debug is disabled
123 if (debug.enabled) {
124 const resolvedPath = require.resolve(longName);
125
126 let version = null;
127
128 try {
129 version = require(`${longName}/package.json`).version;
130 } catch (e) {
131
132 // Do nothing
133 }
134
135 const loadedPluginAndVersion = version
136 ? `${longName}@${version}`
137 : `${longName}, version unknown`;
138
139 debug(`Loaded plugin ${pluginName} (${loadedPluginAndVersion}) (from ${resolvedPath})`);
140 }
141
142 this.define(pluginName, plugin);
143 }
144 }
145
146 /**
147 * Loads all plugins from an array.
148 * @param {string[]} pluginNames An array of plugins names.
149 * @returns {void}
150 * @throws {Error} If a plugin cannot be loaded.
151 * @throws {Error} If "plugins" in config is not an array
152 */
153 loadAll(pluginNames) {
154
155 // if "plugins" in config is not an array, throw an error so user can fix their config.
156 if (!Array.isArray(pluginNames)) {
157 const pluginNotArrayMessage = "ESLint configuration error: \"plugins\" value must be an array";
158
159 debug(`${pluginNotArrayMessage}: ${JSON.stringify(pluginNames)}`);
160
161 throw new Error(pluginNotArrayMessage);
162 }
163
164 // load each plugin by name
165 pluginNames.forEach(this.load, this);
166 }
167}
168
169module.exports = Plugins;