]> git.proxmox.com Git - pve-eslint.git/blob - eslint/tests/lib/cli-engine/config-array/config-array.js
fafb688d768deabce59cfc8328abe1c302c7a549
[pve-eslint.git] / eslint / tests / lib / cli-engine / config-array / config-array.js
1 /**
2 * @fileoverview Tests for ConfigArray class.
3 * @author Toru Nagashima <https://github.com/mysticatea>
4 */
5 "use strict";
6
7 const path = require("path");
8 const { assert } = require("chai");
9 const { ConfigArray, OverrideTester, getUsedExtractedConfigs } = require("../../../../lib/cli-engine/config-array");
10
11 describe("ConfigArray", () => {
12 it("should be a sub class of Array.", () => {
13 assert(new ConfigArray() instanceof Array);
14 });
15
16 describe("'constructor(...elements)' should adopt the elements as array elements.", () => {
17 const patterns = [
18 { elements: [] },
19 { elements: [{ value: 1 }] },
20 { elements: [{ value: 2 }, { value: 3 }] },
21 { elements: [{ value: 4 }, { value: 5 }, { value: 6 }] }
22 ];
23
24 for (const { elements } of patterns) {
25 describe(`if it gave ${JSON.stringify(elements)} then`, () => {
26 let configArray;
27
28 beforeEach(() => {
29 configArray = new ConfigArray(...elements);
30 });
31
32 it(`should have ${elements.length} as the length.`, () => {
33 assert.strictEqual(configArray.length, elements.length);
34 });
35
36 for (let i = 0; i < elements.length; ++i) {
37 it(`should have ${JSON.stringify(elements[i])} at configArray[${i}].`, () => { // eslint-disable-line no-loop-func
38 assert.strictEqual(configArray[i], elements[i]);
39 });
40 }
41 });
42 }
43 });
44
45 describe("'isRoot()' method should be the value of the last element which has 'root' property.", () => {
46 const patterns = [
47 { elements: [], expected: false },
48 { elements: [{}], expected: false },
49 { elements: [{}, {}], expected: false },
50 { elements: [{ root: false }], expected: false },
51 { elements: [{ root: true }], expected: true },
52 { elements: [{ root: true }, { root: false }], expected: false },
53 { elements: [{ root: false }, { root: true }], expected: true },
54 { elements: [{ root: false }, { root: true }, { rules: {} }], expected: true }, // ignore undefined.
55 { elements: [{ root: true }, { root: 1 }], expected: true } // ignore non-boolean value
56 ];
57
58 for (const { elements, expected } of patterns) {
59 it(`should be ${expected} if the elements are ${JSON.stringify(elements)}.`, () => {
60 assert.strictEqual(new ConfigArray(...elements).isRoot(), expected);
61 });
62 }
63 });
64
65 describe("'pluginEnvironments' property should be the environments of all plugins.", () => {
66 const env = {
67 "aaa/xxx": {},
68 "bbb/xxx": {}
69 };
70 let configArray;
71
72 beforeEach(() => {
73 configArray = new ConfigArray(
74 {
75 plugins: {
76 aaa: {
77 definition: {
78 environments: {
79 xxx: env["aaa/xxx"]
80 }
81 }
82 }
83 }
84 },
85 {
86 criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
87 plugins: {
88 bbb: {
89 definition: {
90 environments: {
91 xxx: env["bbb/xxx"]
92 }
93 }
94 }
95 }
96 }
97 );
98 });
99
100 it("should return null for built-in env", () => {
101 assert.strictEqual(configArray.pluginEnvironments.get("node"), void 0);
102 });
103
104 it("should return 'aaa/xxx' if it exists.", () => {
105 assert.strictEqual(configArray.pluginEnvironments.get("aaa/xxx"), env["aaa/xxx"]);
106 });
107
108 it("should return 'bbb/xxx' if it exists.", () => {
109 assert.strictEqual(configArray.pluginEnvironments.get("bbb/xxx"), env["bbb/xxx"]);
110 });
111
112 it("should throw an error if it tried to mutate.", () => {
113 assert.throws(() => {
114 configArray.pluginEnvironments.set("ccc/xxx", {});
115 });
116 });
117 });
118
119 describe("'pluginProcessors' property should be the processors of all plugins.", () => {
120 const processors = {
121 "aaa/.xxx": {},
122 "bbb/.xxx": {}
123 };
124 let configArray;
125
126 beforeEach(() => {
127 configArray = new ConfigArray(
128 {
129 plugins: {
130 aaa: {
131 definition: {
132 processors: {
133 ".xxx": processors["aaa/.xxx"]
134 }
135 }
136 }
137 }
138 },
139 {
140 criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
141 plugins: {
142 bbb: {
143 definition: {
144 processors: {
145 ".xxx": processors["bbb/.xxx"]
146 }
147 }
148 }
149 }
150 }
151 );
152 });
153
154 it("should return 'aaa/.xxx' if it exists.", () => {
155 assert.strictEqual(configArray.pluginProcessors.get("aaa/.xxx"), processors["aaa/.xxx"]);
156 });
157
158 it("should return 'bbb/.xxx' if it exists.", () => {
159 assert.strictEqual(configArray.pluginProcessors.get("bbb/.xxx"), processors["bbb/.xxx"]);
160 });
161
162 it("should throw an error if it tried to mutate.", () => {
163 assert.throws(() => {
164 configArray.pluginProcessors.set("ccc/.xxx", {});
165 });
166 });
167 });
168
169 describe("'pluginRules' property should be the rules of all plugins.", () => {
170 const rules = {
171 "aaa/xxx": {},
172 "bbb/xxx": {}
173 };
174 let configArray;
175
176 beforeEach(() => {
177 configArray = new ConfigArray(
178 {
179 plugins: {
180 aaa: {
181 definition: {
182 rules: {
183 xxx: rules["aaa/xxx"]
184 }
185 }
186 }
187 }
188 },
189 {
190 criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
191 plugins: {
192 bbb: {
193 definition: {
194 rules: {
195 xxx: rules["bbb/xxx"]
196 }
197 }
198 }
199 }
200 }
201 );
202 });
203
204 it("should return null for built-in rules", () => {
205 assert.strictEqual(configArray.pluginRules.get("eqeqeq"), void 0);
206 });
207
208 it("should return 'aaa/xxx' if it exists.", () => {
209 assert.strictEqual(configArray.pluginRules.get("aaa/xxx"), rules["aaa/xxx"]);
210 });
211
212 it("should return 'bbb/xxx' if it exists.", () => {
213 assert.strictEqual(configArray.pluginRules.get("bbb/xxx"), rules["bbb/xxx"]);
214 });
215
216 it("should throw an error if it tried to mutate.", () => {
217 assert.throws(() => {
218 configArray.pluginRules.set("ccc/xxx", {});
219 });
220 });
221 });
222
223 describe("'extractConfig(filePath)' method should retrieve the merged config for a given file.", () => {
224 it("should throw an error if a 'parser' has the loading error.", () => {
225 assert.throws(() => {
226 new ConfigArray(
227 {
228 parser: { error: new Error("Failed to load a parser.") }
229 }
230 ).extractConfig(__filename);
231 }, "Failed to load a parser.");
232 });
233
234 it("should not throw if the errored 'parser' was not used; overwriten", () => {
235 const parser = { id: "a parser" };
236 const config = new ConfigArray(
237 {
238 parser: { error: new Error("Failed to load a parser.") }
239 },
240 {
241 parser
242 }
243 ).extractConfig(__filename);
244
245 assert.strictEqual(config.parser, parser);
246 });
247
248 it("should not throw if the errored 'parser' was not used; not matched", () => {
249 const config = new ConfigArray(
250 {
251 criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
252 parser: { error: new Error("Failed to load a parser.") }
253 }
254 ).extractConfig(__filename);
255
256 assert.strictEqual(config.parser, null);
257 });
258
259 it("should throw an error if a 'plugins' value has the loading error.", () => {
260 assert.throws(() => {
261 new ConfigArray(
262 {
263 plugins: {
264 foo: { error: new Error("Failed to load a plugin.") }
265 }
266 }
267 ).extractConfig(__filename);
268 }, "Failed to load a plugin.");
269 });
270
271 it("should not throw if the errored 'plugins' value was not used; not matched", () => {
272 const config = new ConfigArray(
273 {
274 criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
275 plugins: {
276 foo: { error: new Error("Failed to load a plugin.") }
277 }
278 }
279 ).extractConfig(__filename);
280
281 assert.deepStrictEqual(config.plugins, {});
282 });
283
284 it("should not merge the elements which were not matched.", () => {
285 const config = new ConfigArray(
286 {
287 rules: {
288 "no-redeclare": "error"
289 }
290 },
291 {
292 criteria: OverrideTester.create(["*.js"], [], process.cwd()),
293 rules: {
294 "no-undef": "error"
295 }
296 },
297 {
298 criteria: OverrideTester.create(["*.js"], [path.basename(__filename)], process.cwd()),
299 rules: {
300 "no-use-before-define": "error"
301 }
302 },
303 {
304 criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
305 rules: {
306 "no-unused-vars": "error"
307 }
308 }
309 ).extractConfig(__filename);
310
311 assert.deepStrictEqual(config.rules, {
312 "no-redeclare": ["error"],
313 "no-undef": ["error"]
314 });
315 });
316
317 it("should return the same instance for every the same matching.", () => {
318 const configArray = new ConfigArray(
319 {
320 rules: {
321 "no-redeclare": "error"
322 }
323 },
324 {
325 criteria: OverrideTester.create(["*.js"], [], process.cwd()),
326 rules: {
327 "no-undef": "error"
328 }
329 },
330 {
331 criteria: OverrideTester.create(["*.js"], [path.basename(__filename)], process.cwd()),
332 rules: {
333 "no-use-before-define": "error"
334 }
335 },
336 {
337 criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
338 rules: {
339 "no-unused-vars": "error"
340 }
341 }
342 );
343
344 assert.strictEqual(
345 configArray.extractConfig(path.join(__dirname, "a.js")),
346 configArray.extractConfig(path.join(__dirname, "b.js"))
347 );
348 });
349
350 /**
351 * Merge two config data.
352 *
353 * The test cases which depend on this function were moved from
354 * 'tests/lib/config/config-ops.js' when refactoring to keep the
355 * cumulated test cases.
356 *
357 * Previously, the merging logic of multiple config data had been
358 * implemented in `ConfigOps.merge()` function. But currently, it's
359 * implemented in `ConfigArray#extractConfig()` method.
360 * @param {Object} target A config data.
361 * @param {Object} source Another config data.
362 * @returns {Object} The merged config data.
363 */
364 function merge(target, source) {
365 return new ConfigArray(target, source).extractConfig(__filename);
366 }
367
368 it("should combine two objects when passed two objects with different top-level properties", () => {
369 const config = [
370 { env: { browser: true } },
371 { globals: { foo: "bar" } }
372 ];
373
374 const result = merge(config[0], config[1]);
375
376 assert.strictEqual(result.globals.foo, "bar");
377 assert.isTrue(result.env.browser);
378 });
379
380 it("should combine without blowing up on null values", () => {
381 const config = [
382 { env: { browser: true } },
383 { env: { node: null } }
384 ];
385
386 const result = merge(config[0], config[1]);
387
388 assert.strictEqual(result.env.node, null);
389 assert.isTrue(result.env.browser);
390 });
391
392 it("should combine two objects with parser when passed two objects with different top-level properties", () => {
393 const config = [
394 { env: { browser: true }, parser: "espree" },
395 { globals: { foo: "bar" } }
396 ];
397
398 const result = merge(config[0], config[1]);
399
400 assert.strictEqual(result.parser, "espree");
401 });
402
403 it("should combine configs and override rules when passed configs with the same rules", () => {
404 const config = [
405 { rules: { "no-mixed-requires": [0, false] } },
406 { rules: { "no-mixed-requires": [1, true] } }
407 ];
408
409 const result = merge(config[0], config[1]);
410
411 assert.isArray(result.rules["no-mixed-requires"]);
412 assert.strictEqual(result.rules["no-mixed-requires"][0], 1);
413 assert.strictEqual(result.rules["no-mixed-requires"][1], true);
414 });
415
416 it("should combine configs when passed configs with parserOptions", () => {
417 const config = [
418 { parserOptions: { ecmaFeatures: { jsx: true } } },
419 { parserOptions: { ecmaFeatures: { globalReturn: true } } }
420 ];
421
422 const result = merge(config[0], config[1]);
423
424 assert.deepStrictEqual(result, {
425 configNameOfNoInlineConfig: "",
426 env: {},
427 globals: {},
428 ignores: void 0,
429 noInlineConfig: void 0,
430 parser: null,
431 parserOptions: {
432 ecmaFeatures: {
433 jsx: true,
434 globalReturn: true
435 }
436 },
437 plugins: {},
438 processor: null,
439 reportUnusedDisableDirectives: void 0,
440 rules: {},
441 settings: {}
442 });
443
444 // double-check that originals were not changed
445 assert.deepStrictEqual(config[0], { parserOptions: { ecmaFeatures: { jsx: true } } });
446 assert.deepStrictEqual(config[1], { parserOptions: { ecmaFeatures: { globalReturn: true } } });
447 });
448
449 it("should override configs when passed configs with the same ecmaFeatures", () => {
450 const config = [
451 { parserOptions: { ecmaFeatures: { globalReturn: false } } },
452 { parserOptions: { ecmaFeatures: { globalReturn: true } } }
453 ];
454
455 const result = merge(config[0], config[1]);
456
457 assert.deepStrictEqual(result, {
458 configNameOfNoInlineConfig: "",
459 env: {},
460 globals: {},
461 ignores: void 0,
462 noInlineConfig: void 0,
463 parser: null,
464 parserOptions: {
465 ecmaFeatures: {
466 globalReturn: true
467 }
468 },
469 plugins: {},
470 processor: null,
471 reportUnusedDisableDirectives: void 0,
472 rules: {},
473 settings: {}
474 });
475 });
476
477 it("should combine configs and override rules when merging two configs with arrays and int", () => {
478
479 const config = [
480 { rules: { "no-mixed-requires": [0, false] } },
481 { rules: { "no-mixed-requires": 1 } }
482 ];
483
484 const result = merge(config[0], config[1]);
485
486 assert.isArray(result.rules["no-mixed-requires"]);
487 assert.strictEqual(result.rules["no-mixed-requires"][0], 1);
488 assert.strictEqual(result.rules["no-mixed-requires"][1], false);
489 assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires": [0, false] } });
490 assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires": 1 } });
491 });
492
493 it("should combine configs and override rules options completely", () => {
494
495 const config = [
496 { rules: { "no-mixed-requires1": [1, { event: ["evt", "e"] }] } },
497 { rules: { "no-mixed-requires1": [1, { err: ["error", "e"] }] } }
498 ];
499
500 const result = merge(config[0], config[1]);
501
502 assert.isArray(result.rules["no-mixed-requires1"]);
503 assert.deepStrictEqual(result.rules["no-mixed-requires1"][1], { err: ["error", "e"] });
504 assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires1": [1, { event: ["evt", "e"] }] } });
505 assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires1": [1, { err: ["error", "e"] }] } });
506 });
507
508 it("should combine configs and override rules options without array or object", () => {
509
510 const config = [
511 { rules: { "no-mixed-requires1": ["warn", "nconf", "underscore"] } },
512 { rules: { "no-mixed-requires1": [2, "requirejs"] } }
513 ];
514
515 const result = merge(config[0], config[1]);
516
517 assert.strictEqual(result.rules["no-mixed-requires1"][0], 2);
518 assert.strictEqual(result.rules["no-mixed-requires1"][1], "requirejs");
519 assert.isUndefined(result.rules["no-mixed-requires1"][2]);
520 assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires1": ["warn", "nconf", "underscore"] } });
521 assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires1": [2, "requirejs"] } });
522 });
523
524 it("should combine configs and override rules options without array or object but special case", () => {
525
526 const config = [
527 { rules: { "no-mixed-requires1": [1, "nconf", "underscore"] } },
528 { rules: { "no-mixed-requires1": "error" } }
529 ];
530
531 const result = merge(config[0], config[1]);
532
533 assert.strictEqual(result.rules["no-mixed-requires1"][0], "error");
534 assert.strictEqual(result.rules["no-mixed-requires1"][1], "nconf");
535 assert.strictEqual(result.rules["no-mixed-requires1"][2], "underscore");
536 assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires1": [1, "nconf", "underscore"] } });
537 assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires1": "error" } });
538 });
539
540 it("should combine configs correctly", () => {
541
542 const config = [
543 {
544 rules: {
545 "no-mixed-requires1": [1, { event: ["evt", "e"] }],
546 "valid-jsdoc": 1,
547 semi: 1,
548 quotes1: [2, { exception: ["hi"] }],
549 smile: [1, ["hi", "bye"]]
550 },
551 parserOptions: {
552 ecmaFeatures: { jsx: true }
553 },
554 env: { browser: true },
555 globals: { foo: false }
556 },
557 {
558 rules: {
559 "no-mixed-requires1": [1, { err: ["error", "e"] }],
560 "valid-jsdoc": 2,
561 test: 1,
562 smile: [1, ["xxx", "yyy"]]
563 },
564 parserOptions: {
565 ecmaFeatures: { globalReturn: true }
566 },
567 env: { browser: false },
568 globals: { foo: true }
569 }
570 ];
571
572 const result = merge(config[0], config[1]);
573
574 assert.deepStrictEqual(result, {
575 configNameOfNoInlineConfig: "",
576 parser: null,
577 parserOptions: {
578 ecmaFeatures: {
579 jsx: true,
580 globalReturn: true
581 }
582 },
583 plugins: {},
584 env: {
585 browser: false
586 },
587 globals: {
588 foo: true
589 },
590 rules: {
591 "no-mixed-requires1": [1,
592 {
593 err: [
594 "error",
595 "e"
596 ]
597 }
598 ],
599 quotes1: [2,
600 {
601 exception: [
602 "hi"
603 ]
604 }
605 ],
606 semi: [1],
607 smile: [1, ["xxx", "yyy"]],
608 test: [1],
609 "valid-jsdoc": [2]
610 },
611 settings: {},
612 processor: null,
613 noInlineConfig: void 0,
614 reportUnusedDisableDirectives: void 0,
615 ignores: void 0
616 });
617 assert.deepStrictEqual(config[0], {
618 rules: {
619 "no-mixed-requires1": [1, { event: ["evt", "e"] }],
620 "valid-jsdoc": 1,
621 semi: 1,
622 quotes1: [2, { exception: ["hi"] }],
623 smile: [1, ["hi", "bye"]]
624 },
625 parserOptions: {
626 ecmaFeatures: { jsx: true }
627 },
628 env: { browser: true },
629 globals: { foo: false }
630 });
631 assert.deepStrictEqual(config[1], {
632 rules: {
633 "no-mixed-requires1": [1, { err: ["error", "e"] }],
634 "valid-jsdoc": 2,
635 test: 1,
636 smile: [1, ["xxx", "yyy"]]
637 },
638 parserOptions: {
639 ecmaFeatures: { globalReturn: true }
640 },
641 env: { browser: false },
642 globals: { foo: true }
643 });
644 });
645
646 it("should copy deeply if there is not the destination's property", () => {
647 const a = {};
648 const b = { settings: { bar: 1 } };
649
650 const result = merge(a, b);
651
652 assert(a.settings === void 0);
653 assert(b.settings.bar === 1);
654 assert(result.settings.bar === 1);
655
656 result.settings.bar = 2;
657 assert(b.settings.bar === 1);
658 assert(result.settings.bar === 2);
659 });
660 });
661
662 describe("'getUsedExtractedConfigs(instance)' function should retrieve used extracted configs from the instance's internal cache.", () => {
663 let configArray;
664
665 beforeEach(() => {
666 configArray = new ConfigArray(
667 {
668 rules: {
669 "no-redeclare": "error"
670 }
671 },
672 {
673 criteria: OverrideTester.create(["*.js"], [], process.cwd()),
674 rules: {
675 "no-undef": "error"
676 }
677 },
678 {
679 criteria: OverrideTester.create(["*.js"], [path.basename(__filename)], process.cwd()),
680 rules: {
681 "no-use-before-define": "error"
682 }
683 },
684 {
685 criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
686 rules: {
687 "no-unused-vars": "error"
688 }
689 }
690 );
691 });
692
693 it("should return empty array before it called 'extractConfig(filePath)'.", () => {
694 assert.deepStrictEqual(getUsedExtractedConfigs(configArray), []);
695 });
696
697 for (const { filePaths } of [
698 { filePaths: [__filename] },
699 { filePaths: [__filename, `${__filename}.ts`] },
700 { filePaths: [__filename, `${__filename}.ts`, path.join(__dirname, "foo.js")] }
701 ]) {
702 describe(`after it called 'extractConfig(filePath)' ${filePaths.length} time(s) with ${JSON.stringify(filePaths, null, 4)}, the returned array`, () => { // eslint-disable-line no-loop-func
703 let configs;
704 let usedConfigs;
705
706 beforeEach(() => {
707 configs = filePaths.map(filePath => configArray.extractConfig(filePath));
708 usedConfigs = getUsedExtractedConfigs(configArray);
709 });
710
711 it(`should have ${filePaths.length} as the length.`, () => {
712 assert.strictEqual(usedConfigs.length, configs.length);
713 });
714
715 for (let i = 0; i < filePaths.length; ++i) {
716 it(`should contain 'configs[${i}]'.`, () => { // eslint-disable-line no-loop-func
717 assert(usedConfigs.includes(configs[i]));
718 });
719 }
720 });
721 }
722
723 it("should not contain duplicate values.", () => {
724
725 // Call some times, including with the same arguments.
726 configArray.extractConfig(__filename);
727 configArray.extractConfig(`${__filename}.ts`);
728 configArray.extractConfig(path.join(__dirname, "foo.js"));
729 configArray.extractConfig(__filename);
730 configArray.extractConfig(path.join(__dirname, "foo.js"));
731 configArray.extractConfig(path.join(__dirname, "bar.js"));
732 configArray.extractConfig(path.join(__dirname, "baz.js"));
733
734 const usedConfigs = getUsedExtractedConfigs(configArray);
735
736 assert.strictEqual(new Set(usedConfigs).size, usedConfigs.length);
737 });
738 });
739 });