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