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