minor adjustment to readme
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / shared / config-validator.js
1 /**
2  * @fileoverview Validates configs.
3  * @author Brandon Mills
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const
13     path = require("path"),
14     util = require("util"),
15     lodash = require("lodash"),
16     configSchema = require("../../conf/config-schema"),
17     BuiltInEnvironments = require("../../conf/environments"),
18     BuiltInRules = require("../rules"),
19     ConfigOps = require("./config-ops");
20
21 const ajv = require("./ajv")();
22 const ruleValidators = new WeakMap();
23 const noop = Function.prototype;
24
25 //------------------------------------------------------------------------------
26 // Private
27 //------------------------------------------------------------------------------
28 let validateSchema;
29
30 // Defitions for deprecation warnings.
31 const deprecationWarningMessages = {
32     ESLINT_LEGACY_ECMAFEATURES: "The 'ecmaFeatures' config file property is deprecated, and has no effect."
33 };
34 const severityMap = {
35     error: 2,
36     warn: 1,
37     off: 0
38 };
39
40 /**
41  * Gets a complete options schema for a rule.
42  * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
43  * @returns {Object} JSON Schema for the rule's options.
44  */
45 function getRuleOptionsSchema(rule) {
46     if (!rule) {
47         return null;
48     }
49
50     const schema = rule.schema || rule.meta && rule.meta.schema;
51
52     // Given a tuple of schemas, insert warning level at the beginning
53     if (Array.isArray(schema)) {
54         if (schema.length) {
55             return {
56                 type: "array",
57                 items: schema,
58                 minItems: 0,
59                 maxItems: schema.length
60             };
61         }
62         return {
63             type: "array",
64             minItems: 0,
65             maxItems: 0
66         };
67
68     }
69
70     // Given a full schema, leave it alone
71     return schema || null;
72 }
73
74 /**
75  * Validates a rule's severity and returns the severity value. Throws an error if the severity is invalid.
76  * @param {options} options The given options for the rule.
77  * @returns {number|string} The rule's severity value
78  */
79 function validateRuleSeverity(options) {
80     const severity = Array.isArray(options) ? options[0] : options;
81     const normSeverity = typeof severity === "string" ? severityMap[severity.toLowerCase()] : severity;
82
83     if (normSeverity === 0 || normSeverity === 1 || normSeverity === 2) {
84         return normSeverity;
85     }
86
87     throw new Error(`\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '${util.inspect(severity).replace(/'/gu, "\"").replace(/\n/gu, "")}').\n`);
88
89 }
90
91 /**
92  * Validates the non-severity options passed to a rule, based on its schema.
93  * @param {{create: Function}} rule The rule to validate
94  * @param {Array} localOptions The options for the rule, excluding severity
95  * @returns {void}
96  */
97 function validateRuleSchema(rule, localOptions) {
98     if (!ruleValidators.has(rule)) {
99         const schema = getRuleOptionsSchema(rule);
100
101         if (schema) {
102             ruleValidators.set(rule, ajv.compile(schema));
103         }
104     }
105
106     const validateRule = ruleValidators.get(rule);
107
108     if (validateRule) {
109         validateRule(localOptions);
110         if (validateRule.errors) {
111             throw new Error(validateRule.errors.map(
112                 error => `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`
113             ).join(""));
114         }
115     }
116 }
117
118 /**
119  * Validates a rule's options against its schema.
120  * @param {{create: Function}|null} rule The rule that the config is being validated for
121  * @param {string} ruleId The rule's unique name.
122  * @param {Array|number} options The given options for the rule.
123  * @param {string|null} source The name of the configuration source to report in any errors. If null or undefined,
124  * no source is prepended to the message.
125  * @returns {void}
126  */
127 function validateRuleOptions(rule, ruleId, options, source = null) {
128     try {
129         const severity = validateRuleSeverity(options);
130
131         if (severity !== 0) {
132             validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []);
133         }
134     } catch (err) {
135         const enhancedMessage = `Configuration for rule "${ruleId}" is invalid:\n${err.message}`;
136
137         if (typeof source === "string") {
138             throw new Error(`${source}:\n\t${enhancedMessage}`);
139         } else {
140             throw new Error(enhancedMessage);
141         }
142     }
143 }
144
145 /**
146  * Validates an environment object
147  * @param {Object} environment The environment config object to validate.
148  * @param {string} source The name of the configuration source to report in any errors.
149  * @param {function(envId:string): Object} [getAdditionalEnv] A map from strings to loaded environments.
150  * @returns {void}
151  */
152 function validateEnvironment(
153     environment,
154     source,
155     getAdditionalEnv = noop
156 ) {
157
158     // not having an environment is ok
159     if (!environment) {
160         return;
161     }
162
163     Object.keys(environment).forEach(id => {
164         const env = getAdditionalEnv(id) || BuiltInEnvironments.get(id) || null;
165
166         if (!env) {
167             const message = `${source}:\n\tEnvironment key "${id}" is unknown\n`;
168
169             throw new Error(message);
170         }
171     });
172 }
173
174 /**
175  * Validates a rules config object
176  * @param {Object} rulesConfig The rules config object to validate.
177  * @param {string} source The name of the configuration source to report in any errors.
178  * @param {function(ruleId:string): Object} getAdditionalRule A map from strings to loaded rules
179  * @returns {void}
180  */
181 function validateRules(
182     rulesConfig,
183     source,
184     getAdditionalRule = noop
185 ) {
186     if (!rulesConfig) {
187         return;
188     }
189
190     Object.keys(rulesConfig).forEach(id => {
191         const rule = getAdditionalRule(id) || BuiltInRules.get(id) || null;
192
193         validateRuleOptions(rule, id, rulesConfig[id], source);
194     });
195 }
196
197 /**
198  * Validates a `globals` section of a config file
199  * @param {Object} globalsConfig The `glboals` section
200  * @param {string|null} source The name of the configuration source to report in the event of an error.
201  * @returns {void}
202  */
203 function validateGlobals(globalsConfig, source = null) {
204     if (!globalsConfig) {
205         return;
206     }
207
208     Object.entries(globalsConfig)
209         .forEach(([configuredGlobal, configuredValue]) => {
210             try {
211                 ConfigOps.normalizeConfigGlobal(configuredValue);
212             } catch (err) {
213                 throw new Error(`ESLint configuration of global '${configuredGlobal}' in ${source} is invalid:\n${err.message}`);
214             }
215         });
216 }
217
218 /**
219  * Validate `processor` configuration.
220  * @param {string|undefined} processorName The processor name.
221  * @param {string} source The name of config file.
222  * @param {function(id:string): Processor} getProcessor The getter of defined processors.
223  * @returns {void}
224  */
225 function validateProcessor(processorName, source, getProcessor) {
226     if (processorName && !getProcessor(processorName)) {
227         throw new Error(`ESLint configuration of processor in '${source}' is invalid: '${processorName}' was not found.`);
228     }
229 }
230
231 /**
232  * Formats an array of schema validation errors.
233  * @param {Array} errors An array of error messages to format.
234  * @returns {string} Formatted error message
235  */
236 function formatErrors(errors) {
237     return errors.map(error => {
238         if (error.keyword === "additionalProperties") {
239             const formattedPropertyPath = error.dataPath.length ? `${error.dataPath.slice(1)}.${error.params.additionalProperty}` : error.params.additionalProperty;
240
241             return `Unexpected top-level property "${formattedPropertyPath}"`;
242         }
243         if (error.keyword === "type") {
244             const formattedField = error.dataPath.slice(1);
245             const formattedExpectedType = Array.isArray(error.schema) ? error.schema.join("/") : error.schema;
246             const formattedValue = JSON.stringify(error.data);
247
248             return `Property "${formattedField}" is the wrong type (expected ${formattedExpectedType} but got \`${formattedValue}\`)`;
249         }
250
251         const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath;
252
253         return `"${field}" ${error.message}. Value: ${JSON.stringify(error.data)}`;
254     }).map(message => `\t- ${message}.\n`).join("");
255 }
256
257 /**
258  * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted
259  * for each unique file path, but repeated invocations with the same file path have no effect.
260  * No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active.
261  * @param {string} source The name of the configuration source to report the warning for.
262  * @param {string} errorCode The warning message to show.
263  * @returns {void}
264  */
265 const emitDeprecationWarning = lodash.memoize((source, errorCode) => {
266     const rel = path.relative(process.cwd(), source);
267     const message = deprecationWarningMessages[errorCode];
268
269     process.emitWarning(
270         `${message} (found in "${rel}")`,
271         "DeprecationWarning",
272         errorCode
273     );
274 });
275
276 /**
277  * Validates the top level properties of the config object.
278  * @param {Object} config The config object to validate.
279  * @param {string} source The name of the configuration source to report in any errors.
280  * @returns {void}
281  */
282 function validateConfigSchema(config, source = null) {
283     validateSchema = validateSchema || ajv.compile(configSchema);
284
285     if (!validateSchema(config)) {
286         throw new Error(`ESLint configuration in ${source} is invalid:\n${formatErrors(validateSchema.errors)}`);
287     }
288
289     if (Object.hasOwnProperty.call(config, "ecmaFeatures")) {
290         emitDeprecationWarning(source, "ESLINT_LEGACY_ECMAFEATURES");
291     }
292 }
293
294 /**
295  * Validates an entire config object.
296  * @param {Object} config The config object to validate.
297  * @param {string} source The name of the configuration source to report in any errors.
298  * @param {function(ruleId:string): Object} [getAdditionalRule] A map from strings to loaded rules.
299  * @param {function(envId:string): Object} [getAdditionalEnv] A map from strings to loaded envs.
300  * @returns {void}
301  */
302 function validate(config, source, getAdditionalRule, getAdditionalEnv) {
303     validateConfigSchema(config, source);
304     validateRules(config.rules, source, getAdditionalRule);
305     validateEnvironment(config.env, source, getAdditionalEnv);
306     validateGlobals(config.globals, source);
307
308     for (const override of config.overrides || []) {
309         validateRules(override.rules, source, getAdditionalRule);
310         validateEnvironment(override.env, source, getAdditionalEnv);
311         validateGlobals(config.globals, source);
312     }
313 }
314
315 const validated = new WeakSet();
316
317 /**
318  * Validate config array object.
319  * @param {ConfigArray} configArray The config array to validate.
320  * @returns {void}
321  */
322 function validateConfigArray(configArray) {
323     const getPluginEnv = Map.prototype.get.bind(configArray.pluginEnvironments);
324     const getPluginProcessor = Map.prototype.get.bind(configArray.pluginProcessors);
325     const getPluginRule = Map.prototype.get.bind(configArray.pluginRules);
326
327     // Validate.
328     for (const element of configArray) {
329         if (validated.has(element)) {
330             continue;
331         }
332         validated.add(element);
333
334         validateEnvironment(element.env, element.name, getPluginEnv);
335         validateGlobals(element.globals, element.name);
336         validateProcessor(element.processor, element.name, getPluginProcessor);
337         validateRules(element.rules, element.name, getPluginRule);
338     }
339 }
340
341 //------------------------------------------------------------------------------
342 // Public Interface
343 //------------------------------------------------------------------------------
344
345 module.exports = {
346     getRuleOptionsSchema,
347     validate,
348     validateConfigArray,
349     validateConfigSchema,
350     validateRuleOptions
351 };