1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | "use strict";
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | const
|
25 | util = require("util"),
|
26 | configSchema = require("../../conf/config-schema"),
|
27 | BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"),
|
28 | BuiltInRules = require("../rules"),
|
29 | ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
|
30 | { emitDeprecationWarning } = require("./deprecation-warnings");
|
31 |
|
32 | const ajv = require("./ajv")();
|
33 | const ruleValidators = new WeakMap();
|
34 | const noop = Function.prototype;
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | let validateSchema;
|
40 | const severityMap = {
|
41 | error: 2,
|
42 | warn: 1,
|
43 | off: 0
|
44 | };
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | function getRuleOptionsSchema(rule) {
|
52 | if (!rule) {
|
53 | return null;
|
54 | }
|
55 |
|
56 | const schema = rule.schema || rule.meta && rule.meta.schema;
|
57 |
|
58 |
|
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 |
|
77 | return schema || null;
|
78 | }
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | function 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 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 | function validateRuleSchema(rule, localOptions) {
|
104 | if (!ruleValidators.has(rule)) {
|
105 | const schema = 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 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 | function validateRuleOptions(rule, ruleId, options, source = null) {
|
134 | try {
|
135 | const severity = validateRuleSeverity(options);
|
136 |
|
137 | if (severity !== 0) {
|
138 | 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 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 | function validateEnvironment(
|
159 | environment,
|
160 | source,
|
161 | getAdditionalEnv = noop
|
162 | ) {
|
163 |
|
164 |
|
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 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 | function 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) || BuiltInRules.get(id) || null;
|
198 |
|
199 | validateRuleOptions(rule, id, rulesConfig[id], source);
|
200 | });
|
201 | }
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 | function 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 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 | function 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 |
|
239 |
|
240 |
|
241 |
|
242 | function 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 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 | function 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${formatErrors(validateSchema.errors)}`);
|
274 | }
|
275 |
|
276 | if (Object.hasOwnProperty.call(config, "ecmaFeatures")) {
|
277 | emitDeprecationWarning(source, "ESLINT_LEGACY_ECMAFEATURES");
|
278 | }
|
279 | }
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 | function validate(config, source, getAdditionalRule, getAdditionalEnv) {
|
290 | validateConfigSchema(config, source);
|
291 | validateRules(config.rules, source, getAdditionalRule);
|
292 | validateEnvironment(config.env, source, getAdditionalEnv);
|
293 | validateGlobals(config.globals, source);
|
294 |
|
295 | for (const override of config.overrides || []) {
|
296 | validateRules(override.rules, source, getAdditionalRule);
|
297 | validateEnvironment(override.env, source, getAdditionalEnv);
|
298 | validateGlobals(config.globals, source);
|
299 | }
|
300 | }
|
301 |
|
302 | const validated = new WeakSet();
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 | function validateConfigArray(configArray) {
|
310 | const getPluginEnv = Map.prototype.get.bind(configArray.pluginEnvironments);
|
311 | const getPluginProcessor = Map.prototype.get.bind(configArray.pluginProcessors);
|
312 | const getPluginRule = Map.prototype.get.bind(configArray.pluginRules);
|
313 |
|
314 |
|
315 | for (const element of configArray) {
|
316 | if (validated.has(element)) {
|
317 | continue;
|
318 | }
|
319 | validated.add(element);
|
320 |
|
321 | validateEnvironment(element.env, element.name, getPluginEnv);
|
322 | validateGlobals(element.globals, element.name);
|
323 | validateProcessor(element.processor, element.name, getPluginProcessor);
|
324 | validateRules(element.rules, element.name, getPluginRule);
|
325 | }
|
326 | }
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 | module.exports = {
|
333 | getRuleOptionsSchema,
|
334 | validate,
|
335 | validateConfigArray,
|
336 | validateConfigSchema,
|
337 | validateRuleOptions
|
338 | };
|