]> git.proxmox.com Git - pve-eslint.git/blame - eslint/tests/lib/shared/config-validator.js
import 8.41.0 source
[pve-eslint.git] / eslint / tests / lib / shared / config-validator.js
CommitLineData
6f036462
TL
1/*
2 * STOP!!! DO NOT MODIFY.
3 *
4 * This file is part of the ongoing work to move the eslintrc-style config
5 * system into the @eslint/eslintrc package. This file needs to remain
6 * unchanged in order for this work to proceed.
7 *
8 * If you think you need to change this file, please contact @nzakas first.
9 *
10 * Thanks in advance for your cooperation.
11 */
12
eb39fafa
DC
13/**
14 * @fileoverview Tests for config validator.
15 * @author Brandon Mills
16 */
17
18"use strict";
19
20//------------------------------------------------------------------------------
21// Requirements
22//------------------------------------------------------------------------------
23
24const assert = require("chai").assert,
25 { Linter } = require("../../../lib/linter"),
26 validator = require("../../../lib/shared/config-validator"),
27 Rules = require("../../../lib/linter/rules");
eb39fafa
DC
28
29//------------------------------------------------------------------------------
609c276f 30// Helpers
eb39fafa
DC
31//------------------------------------------------------------------------------
32
609c276f
TL
33const linter = new Linter();
34
f2a92ac6
DC
35const mockRule = {
36 meta: {
37 schema: [{
38 enum: ["first", "second"]
39 }]
40 },
41 create(context) {
42 return {
43 Program(node) {
44 context.report(node, "Expected a validation error.");
45 }
46 };
eb39fafa 47 }
eb39fafa
DC
48};
49
f2a92ac6
DC
50const mockObjectRule = {
51 meta: {
52 schema: {
53 enum: ["first", "second"]
eb39fafa 54 }
f2a92ac6
DC
55 },
56 create(context) {
57 return {
58 Program(node) {
59 context.report(node, "Expected a validation error.");
60 }
61 };
62 }
63};
eb39fafa 64
f2a92ac6
DC
65const mockNoOptionsRule = {
66 meta: {
67 schema: []
68 },
69 create(context) {
70 return {
71 Program(node) {
72 context.report(node, "Expected a validation error.");
73 }
74 };
75 }
76};
eb39fafa
DC
77
78const mockRequiredOptionsRule = {
79 meta: {
80 schema: {
81 type: "array",
82 minItems: 1
83 }
84 },
85 create(context) {
86 return {
87 Program(node) {
88 context.report(node, "Expected a validation error.");
89 }
90 };
91 }
92};
93
609c276f
TL
94//------------------------------------------------------------------------------
95// Tests
96//------------------------------------------------------------------------------
97
eb39fafa
DC
98describe("Validator", () => {
99
100 /**
101 * Gets a loaded rule given a rule ID
102 * @param {string} ruleId The ID of the rule
103 * @returns {{create: Function}} The loaded rule
104 */
105 function ruleMapper(ruleId) {
106 return linter.getRules().get(ruleId) || new Rules().get(ruleId);
107 }
108
109 beforeEach(() => {
110 linter.defineRule("mock-rule", mockRule);
111 linter.defineRule("mock-required-options-rule", mockRequiredOptionsRule);
112 });
113
114 describe("validate", () => {
115
116 it("should do nothing with an empty config", () => {
117 validator.validate({}, "tests", ruleMapper);
118 });
119
120 it("should do nothing with a valid eslint config", () => {
121 validator.validate(
122 {
123 $schema: "http://json.schemastore.org/eslintrc",
124 root: true,
125 globals: { globalFoo: "readonly" },
126 parser: "parserFoo",
127 env: { browser: true },
128 plugins: ["pluginFoo", "pluginBar"],
129 settings: { foo: "bar" },
130 extends: ["configFoo", "configBar"],
131 parserOptions: { foo: "bar" },
132 rules: {}
133 },
134 "tests",
135 ruleMapper
136 );
137 });
138
139 it("should throw with an unknown property", () => {
140 const fn = validator.validate.bind(
141 null,
142 {
143 foo: true
144 },
145 "tests",
146 ruleMapper
147 );
148
149 assert.throws(fn, "Unexpected top-level property \"foo\".");
150 });
151
152 describe("root", () => {
153 it("should throw with a string value", () => {
154 const fn = validator.validate.bind(null, { root: "true" }, null, ruleMapper);
155
156 assert.throws(fn, "Property \"root\" is the wrong type (expected boolean but got `\"true\"`).");
157 });
158
159 it("should throw with a numeric value", () => {
160 const fn = validator.validate.bind(null, { root: 0 }, null, ruleMapper);
161
162 assert.throws(fn, "Property \"root\" is the wrong type (expected boolean but got `0`).");
163 });
164 });
165
166 describe("globals", () => {
167 it("should throw with a string value", () => {
168 const fn = validator.validate.bind(null, { globals: "jQuery" }, null, ruleMapper);
169
170 assert.throws(fn, "Property \"globals\" is the wrong type (expected object but got `\"jQuery\"`).");
171 });
172
173 it("should throw with an array value", () => {
174 const fn = validator.validate.bind(null, { globals: ["jQuery"] }, null, ruleMapper);
175
176 assert.throws(fn, "Property \"globals\" is the wrong type (expected object but got `[\"jQuery\"]`).");
177 });
178 });
179
180 describe("parser", () => {
181 it("should not throw with a null value", () => {
182 validator.validate({ parser: null }, null, ruleMapper);
183 });
184 });
185
186 describe("env", () => {
187
188 it("should throw with an array environment", () => {
189 const fn = validator.validate.bind(null, { env: [] }, null, ruleMapper);
190
191 assert.throws(fn, "Property \"env\" is the wrong type (expected object but got `[]`).");
192 });
193
194 it("should throw with a primitive environment", () => {
195 const fn = validator.validate.bind(null, { env: 1 }, null, ruleMapper);
196
197 assert.throws(fn, "Property \"env\" is the wrong type (expected object but got `1`).");
198 });
199
200 it("should catch invalid environments", () => {
201 const fn = validator.validate.bind(null, { env: { browser: true, invalid: true } }, null, ruleMapper);
202
203 assert.throws(fn, "Environment key \"invalid\" is unknown\n");
204 });
205
206 it("should catch disabled invalid environments", () => {
207 const fn = validator.validate.bind(null, { env: { browser: true, invalid: false } }, null, ruleMapper);
208
209 assert.throws(fn, "Environment key \"invalid\" is unknown\n");
210 });
211
212 it("should do nothing with an undefined environment", () => {
213 validator.validate({}, null, ruleMapper);
214 });
215
216 });
217
218 describe("plugins", () => {
219 it("should not throw with an empty array", () => {
220 validator.validate({ plugins: [] }, null, ruleMapper);
221 });
222
223 it("should throw with a string", () => {
224 const fn = validator.validate.bind(null, { plugins: "react" }, null, ruleMapper);
225
226 assert.throws(fn, "Property \"plugins\" is the wrong type (expected array but got `\"react\"`).");
227 });
228 });
229
230 describe("settings", () => {
231 it("should not throw with an empty object", () => {
232 validator.validate({ settings: {} }, null, ruleMapper);
233 });
234
235 it("should throw with an array", () => {
236 const fn = validator.validate.bind(null, { settings: ["foo"] }, null, ruleMapper);
237
238 assert.throws(fn, "Property \"settings\" is the wrong type (expected object but got `[\"foo\"]`).");
239 });
240 });
241
242 describe("extends", () => {
243 it("should not throw with an empty array", () => {
244 validator.validate({ extends: [] }, null, ruleMapper);
245 });
246
247 it("should not throw with a string", () => {
248 validator.validate({ extends: "react" }, null, ruleMapper);
249 });
250
251 it("should throw with an object", () => {
252 const fn = validator.validate.bind(null, { extends: {} }, null, ruleMapper);
253
254 assert.throws(fn, "ESLint configuration in null is invalid:\n\t- Property \"extends\" is the wrong type (expected string but got `{}`).\n\t- Property \"extends\" is the wrong type (expected array but got `{}`).\n\t- \"extends\" should match exactly one schema in oneOf. Value: {}.");
255 });
256 });
257
258 describe("parserOptions", () => {
259 it("should not throw with an empty object", () => {
260 validator.validate({ parserOptions: {} }, null, ruleMapper);
261 });
262
263 it("should throw with an array", () => {
264 const fn = validator.validate.bind(null, { parserOptions: ["foo"] }, null, ruleMapper);
265
266 assert.throws(fn, "Property \"parserOptions\" is the wrong type (expected object but got `[\"foo\"]`).");
267 });
268 });
269
270 describe("rules", () => {
271
272 it("should do nothing with an empty rules object", () => {
273 validator.validate({ rules: {} }, "tests", ruleMapper);
274 });
275
276 it("should do nothing with a valid config with rules", () => {
277 validator.validate({ rules: { "mock-rule": [2, "second"] } }, "tests", ruleMapper);
278 });
279
280 it("should do nothing with a valid config when severity is off", () => {
281 validator.validate({ rules: { "mock-rule": ["off", "second"] } }, "tests", ruleMapper);
282 });
283
284 it("should do nothing with an invalid config when severity is off", () => {
285 validator.validate({ rules: { "mock-required-options-rule": "off" } }, "tests", ruleMapper);
286 });
287
288 it("should do nothing with an invalid config when severity is an array with 'off'", () => {
289 validator.validate({ rules: { "mock-required-options-rule": ["off"] } }, "tests", ruleMapper);
290 });
291
292 it("should do nothing with a valid config when severity is warn", () => {
293 validator.validate({ rules: { "mock-rule": ["warn", "second"] } }, "tests", ruleMapper);
294 });
295
296 it("should do nothing with a valid config when severity is error", () => {
297 validator.validate({ rules: { "mock-rule": ["error", "second"] } }, "tests", ruleMapper);
298 });
299
300 it("should do nothing with a valid config when severity is Off", () => {
301 validator.validate({ rules: { "mock-rule": ["Off", "second"] } }, "tests", ruleMapper);
302 });
303
304 it("should do nothing with a valid config when severity is Warn", () => {
305 validator.validate({ rules: { "mock-rule": ["Warn", "second"] } }, "tests", ruleMapper);
306 });
307
308 it("should do nothing with a valid config when severity is Error", () => {
309 validator.validate({ rules: { "mock-rule": ["Error", "second"] } }, "tests", ruleMapper);
310 });
311
312 it("should catch invalid rule options", () => {
313 const fn = validator.validate.bind(null, { rules: { "mock-rule": [3, "third"] } }, "tests", ruleMapper);
314
315 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
316 });
317
318 it("should allow for rules with no options", () => {
319 linter.defineRule("mock-no-options-rule", mockNoOptionsRule);
320
321 validator.validate({ rules: { "mock-no-options-rule": 2 } }, "tests", ruleMapper);
322 });
323
324 it("should not allow options for rules with no options", () => {
325 linter.defineRule("mock-no-options-rule", mockNoOptionsRule);
326
327 const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": [2, "extra"] } }, "tests", ruleMapper);
328
329 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-no-options-rule\" is invalid:\n\tValue [\"extra\"] should NOT have more than 0 items.\n");
330 });
331 });
332
333 describe("globals", () => {
334 it("should disallow globals set to invalid values", () => {
335 assert.throws(
336 () => validator.validate({ globals: { foo: "AAAAA" } }, "tests", ruleMapper),
337 "ESLint configuration of global 'foo' in tests is invalid:\n'AAAAA' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')"
338 );
339 });
340 });
341
342 describe("overrides", () => {
343 it("should not throw with an empty overrides array", () => {
344 validator.validate({ overrides: [] }, "tests", ruleMapper);
345 });
346
347 it("should not throw with a valid overrides array", () => {
348 validator.validate({ overrides: [{ files: "*", rules: {} }] }, "tests", ruleMapper);
349 });
350
351 it("should throw if override does not specify files", () => {
352 const fn = validator.validate.bind(null, { overrides: [{ rules: {} }] }, "tests", ruleMapper);
353
354 assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- \"overrides[0]\" should have required property 'files'. Value: {\"rules\":{}}.\n");
355 });
356
357 it("should throw if override has an empty files array", () => {
358 const fn = validator.validate.bind(null, { overrides: [{ files: [] }] }, "tests", ruleMapper);
359
360 assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Property \"overrides[0].files\" is the wrong type (expected string but got `[]`).\n\t- \"overrides[0].files\" should NOT have fewer than 1 items. Value: [].\n\t- \"overrides[0].files\" should match exactly one schema in oneOf. Value: [].\n");
361 });
362
363 it("should not throw if override has nested overrides", () => {
364 validator.validate({ overrides: [{ files: "*", overrides: [{ files: "*", rules: {} }] }] }, "tests", ruleMapper);
365 });
366
367 it("should not throw if override extends", () => {
368 validator.validate({ overrides: [{ files: "*", extends: "eslint-recommended" }] }, "tests", ruleMapper);
369 });
370
371 it("should throw if override tries to set root", () => {
372 const fn = validator.validate.bind(null, { overrides: [{ files: "*", root: "true" }] }, "tests", ruleMapper);
373
374 assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Unexpected top-level property \"overrides[0].root\".\n");
375 });
376
377 describe("env", () => {
378
379 it("should catch invalid environments", () => {
380 const fn = validator.validate.bind(null, { overrides: [{ files: "*", env: { browser: true, invalid: true } }] }, null, ruleMapper);
381
382 assert.throws(fn, "Environment key \"invalid\" is unknown\n");
383 });
384
385 it("should catch disabled invalid environments", () => {
386 const fn = validator.validate.bind(null, { overrides: [{ files: "*", env: { browser: true, invalid: false } }] }, null, ruleMapper);
387
388 assert.throws(fn, "Environment key \"invalid\" is unknown\n");
389 });
390
391 });
392
393 describe("rules", () => {
394
395 it("should catch invalid rule options", () => {
396 const fn = validator.validate.bind(null, { overrides: [{ files: "*", rules: { "mock-rule": [3, "third"] } }] }, "tests", ruleMapper);
397
398 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
399 });
400
401 it("should not allow options for rules with no options", () => {
402 linter.defineRule("mock-no-options-rule", mockNoOptionsRule);
403
404 const fn = validator.validate.bind(null, { overrides: [{ files: "*", rules: { "mock-no-options-rule": [2, "extra"] } }] }, "tests", ruleMapper);
405
406 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-no-options-rule\" is invalid:\n\tValue [\"extra\"] should NOT have more than 0 items.\n");
407 });
408 });
409
410 });
411
412 });
413
414 describe("getRuleOptionsSchema", () => {
415
416 it("should return null for a missing rule", () => {
417 assert.strictEqual(validator.getRuleOptionsSchema(ruleMapper("non-existent-rule")), null);
418 });
419
420 it("should not modify object schema", () => {
421 linter.defineRule("mock-object-rule", mockObjectRule);
422 assert.deepStrictEqual(validator.getRuleOptionsSchema(ruleMapper("mock-object-rule")), {
423 enum: ["first", "second"]
424 });
425 });
426
427 });
428
429 describe("validateRuleOptions", () => {
430
431 it("should throw for incorrect warning level number", () => {
432 const fn = validator.validateRuleOptions.bind(null, ruleMapper("mock-rule"), "mock-rule", 3, "tests");
433
434 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
435 });
436
437 it("should throw for incorrect warning level string", () => {
438 const fn = validator.validateRuleOptions.bind(null, ruleMapper("mock-rule"), "mock-rule", "booya", "tests");
439
440 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '\"booya\"').\n");
441 });
442
443 it("should throw for invalid-type warning level", () => {
444 const fn = validator.validateRuleOptions.bind(null, ruleMapper("mock-rule"), "mock-rule", [["error"]], "tests");
445
446 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '[ \"error\" ]').\n");
447 });
448
449 it("should only check warning level for nonexistent rules", () => {
450 const fn = validator.validateRuleOptions.bind(null, ruleMapper("non-existent-rule"), "non-existent-rule", [3, "foobar"], "tests");
451
452 assert.throws(fn, "tests:\n\tConfiguration for rule \"non-existent-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
453 });
454
455 it("should only check warning level for plugin rules", () => {
456 const fn = validator.validateRuleOptions.bind(null, ruleMapper("plugin/rule"), "plugin/rule", 3, "tests");
457
458 assert.throws(fn, "tests:\n\tConfiguration for rule \"plugin/rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
459 });
460
461 it("should throw for incorrect configuration values", () => {
462 const fn = validator.validateRuleOptions.bind(null, ruleMapper("mock-rule"), "mock-rule", [2, "frist"], "tests");
463
464 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"frist\" should be equal to one of the allowed values.\n");
465 });
466
467 it("should throw for too many configuration values", () => {
468 const fn = validator.validateRuleOptions.bind(null, ruleMapper("mock-rule"), "mock-rule", [2, "first", "second"], "tests");
469
470 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue [\"first\",\"second\"] should NOT have more than 1 items.\n");
471 });
472
473 });
474
475});