2 * @fileoverview Tests for FlatConfigArray
3 * @author Nicholas C. Zakas
8 //-----------------------------------------------------------------------------
10 //-----------------------------------------------------------------------------
12 const { FlatConfigArray
} = require("../../../lib/config/flat-config-array");
13 const assert
= require("chai").assert
;
16 recommended
: recommendedConfig
17 } = require("@eslint/js").configs
;
18 const stringify
= require("json-stable-stringify-without-jsonify");
19 const espree
= require("espree");
21 //-----------------------------------------------------------------------------
23 //-----------------------------------------------------------------------------
36 enum: ["always", "never"]
83 * Creates a config array with the correct default options.
84 * @param {*[]} configs An array of configs to use in the config array.
85 * @returns {FlatConfigArray} The config array;
87 function createFlatConfigArray(configs
) {
88 return new FlatConfigArray(configs
, {
89 baseConfig
: [baseConfig
]
94 * Asserts that a given set of configs will be merged into the given
96 * @param {*[]} values An array of configs to use in the config array.
97 * @param {Object} result The expected merged result of the configs.
99 * @throws {AssertionError} If the actual result doesn't match the
102 async
function assertMergedResult(values
, result
) {
103 const configs
= createFlatConfigArray(values
);
105 await configs
.normalize();
107 const config
= configs
.getConfig("foo.js");
109 assert
.deepStrictEqual(config
, result
);
113 * Asserts that a given set of configs results in an invalid config.
114 * @param {*[]} values An array of configs to use in the config array.
115 * @param {string|RegExp} message The expected error message.
117 * @throws {AssertionError} If the config is valid or if the error
118 * has an unexpected message.
120 async
function assertInvalidConfig(values
, message
) {
121 const configs
= createFlatConfigArray(values
);
123 await configs
.normalize();
125 assert
.throws(() => {
126 configs
.getConfig("foo.js");
131 * Normalizes the rule configs to an array with severity to match
132 * how Flat Config merges rule options.
133 * @param {Object} rulesConfig The rules config portion of a config.
134 * @returns {Array} The rules config object.
136 function normalizeRuleConfig(rulesConfig
) {
137 const rulesConfigCopy
= {
141 for (const ruleId
of Object
.keys(rulesConfigCopy
)) {
142 rulesConfigCopy
[ruleId
] = [2];
145 return rulesConfigCopy
;
148 //-----------------------------------------------------------------------------
150 //-----------------------------------------------------------------------------
152 describe("FlatConfigArray", () => {
154 it("should allow noniterable baseConfig objects", () => {
163 const configs
= new FlatConfigArray([], {
167 // should not throw error
168 configs
.normalizeSync();
171 it("should not reuse languageOptions.parserOptions across configs", () => {
181 const configs
= new FlatConfigArray([], {
185 configs
.normalizeSync();
187 const config
= configs
.getConfig("foo.js");
189 assert
.notStrictEqual(base
[0].languageOptions
, config
.languageOptions
);
190 assert
.notStrictEqual(base
[0].languageOptions
.parserOptions
, config
.languageOptions
.parserOptions
, "parserOptions should be new object");
193 describe("Serialization of configs", () => {
195 it("should convert config into normalized JSON object", () => {
197 const configs
= new FlatConfigArray([{
204 configs
.normalizeSync();
206 const config
= configs
.getConfig("foo.js");
208 plugins
: ["@", "a", "b"],
210 ecmaVersion
: "latest",
211 sourceType
: "module",
212 parser
: `espree@${espree.version}`,
217 const actual
= config
.toJSON();
219 assert
.deepStrictEqual(actual
, expected
);
221 assert
.strictEqual(stringify(actual
), stringify(expected
));
224 it("should convert config with plugin name/version into normalized JSON object", () => {
226 const configs
= new FlatConfigArray([{
236 configs
.normalizeSync();
238 const config
= configs
.getConfig("foo.js");
240 plugins
: ["@", "a", "b:b-plugin@2.3.1"],
242 ecmaVersion
: "latest",
243 sourceType
: "module",
244 parser
: `espree@${espree.version}`,
249 const actual
= config
.toJSON();
251 assert
.deepStrictEqual(actual
, expected
);
253 assert
.strictEqual(stringify(actual
), stringify(expected
));
256 it("should convert config with plugin meta into normalized JSON object", () => {
258 const configs
= new FlatConfigArray([{
270 configs
.normalizeSync();
272 const config
= configs
.getConfig("foo.js");
274 plugins
: ["@", "a", "b:b-plugin@2.3.1"],
276 ecmaVersion
: "latest",
277 sourceType
: "module",
278 parser
: `espree@${espree.version}`,
283 const actual
= config
.toJSON();
285 assert
.deepStrictEqual(actual
, expected
);
287 assert
.strictEqual(stringify(actual
), stringify(expected
));
290 it("should throw an error when config with unnamed parser object is normalized", () => {
292 const configs
= new FlatConfigArray([{
295 parse() { /* empty */ }
300 configs
.normalizeSync();
302 const config
= configs
.getConfig("foo.js");
304 assert
.throws(() => {
306 }, /Could not serialize parser/u);
310 it("should throw an error when config with unnamed parser object with empty meta object is normalized", () => {
312 const configs
= new FlatConfigArray([{
316 parse() { /* empty */ }
321 configs
.normalizeSync();
323 const config
= configs
.getConfig("foo.js");
325 assert
.throws(() => {
327 }, /Could not serialize parser/u);
331 it("should throw an error when config with unnamed parser object with only meta version is normalized", () => {
333 const configs
= new FlatConfigArray([{
339 parse() { /* empty */ }
344 configs
.normalizeSync();
346 const config
= configs
.getConfig("foo.js");
348 assert
.throws(() => {
350 }, /Could not serialize parser/u);
354 it("should not throw an error when config with named parser object is normalized", () => {
356 const configs
= new FlatConfigArray([{
360 name
: "custom-parser"
362 parse() { /* empty */ }
367 configs
.normalizeSync();
369 const config
= configs
.getConfig("foo.js");
371 assert
.deepStrictEqual(config
.toJSON(), {
373 ecmaVersion
: "latest",
374 parser
: "custom-parser",
384 it("should not throw an error when config with named and versioned parser object is normalized", () => {
386 const configs
= new FlatConfigArray([{
390 name
: "custom-parser",
393 parse() { /* empty */ }
398 configs
.normalizeSync();
400 const config
= configs
.getConfig("foo.js");
402 assert
.deepStrictEqual(config
.toJSON(), {
404 ecmaVersion
: "latest",
405 parser
: "custom-parser@0.1.0",
415 it("should not throw an error when config with meta-named and versioned parser object is normalized", () => {
417 const configs
= new FlatConfigArray([{
421 name
: "custom-parser"
424 parse() { /* empty */ }
429 configs
.normalizeSync();
431 const config
= configs
.getConfig("foo.js");
433 assert
.deepStrictEqual(config
.toJSON(), {
435 ecmaVersion
: "latest",
436 parser
: "custom-parser@0.1.0",
446 it("should not throw an error when config with named and versioned parser object outside of meta object is normalized", () => {
448 const configs
= new FlatConfigArray([{
451 name
: "custom-parser",
453 parse() { /* empty */ }
458 configs
.normalizeSync();
460 const config
= configs
.getConfig("foo.js");
462 assert
.deepStrictEqual(config
.toJSON(), {
464 ecmaVersion
: "latest",
465 parser
: "custom-parser@0.1.0",
475 it("should throw an error when config with unnamed processor object is normalized", () => {
477 const configs
= new FlatConfigArray([{
479 preprocess() { /* empty */ },
480 postprocess() { /* empty */ }
484 configs
.normalizeSync();
486 const config
= configs
.getConfig("foo.js");
488 assert
.throws(() => {
490 }, /Could not serialize processor/u);
494 it("should throw an error when config with processor object with empty meta object is normalized", () => {
496 const configs
= new FlatConfigArray([{
499 preprocess() { /* empty */ },
500 postprocess() { /* empty */ }
504 configs
.normalizeSync();
506 const config
= configs
.getConfig("foo.js");
508 assert
.throws(() => {
510 }, /Could not serialize processor/u);
515 it("should not throw an error when config with named processor object is normalized", () => {
517 const configs
= new FlatConfigArray([{
520 name
: "custom-processor"
522 preprocess() { /* empty */ },
523 postprocess() { /* empty */ }
527 configs
.normalizeSync();
529 const config
= configs
.getConfig("foo.js");
531 assert
.deepStrictEqual(config
.toJSON(), {
533 ecmaVersion
: "latest",
534 parser
: `espree@${espree.version}`,
539 processor
: "custom-processor"
544 it("should not throw an error when config with named processor object without meta is normalized", () => {
546 const configs
= new FlatConfigArray([{
548 name
: "custom-processor",
549 preprocess() { /* empty */ },
550 postprocess() { /* empty */ }
554 configs
.normalizeSync();
556 const config
= configs
.getConfig("foo.js");
558 assert
.deepStrictEqual(config
.toJSON(), {
560 ecmaVersion
: "latest",
561 parser
: `espree@${espree.version}`,
566 processor
: "custom-processor"
571 it("should not throw an error when config with named and versioned processor object is normalized", () => {
573 const configs
= new FlatConfigArray([{
576 name
: "custom-processor",
579 preprocess() { /* empty */ },
580 postprocess() { /* empty */ }
585 configs
.normalizeSync();
587 const config
= configs
.getConfig("foo.js");
589 assert
.deepStrictEqual(config
.toJSON(), {
591 ecmaVersion
: "latest",
592 parser
: `espree@${espree.version}`,
597 processor
: "custom-processor@1.2.3"
602 it("should not throw an error when config with named and versioned processor object without meta is normalized", () => {
604 const configs
= new FlatConfigArray([{
606 name
: "custom-processor",
608 preprocess() { /* empty */ },
609 postprocess() { /* empty */ }
614 configs
.normalizeSync();
616 const config
= configs
.getConfig("foo.js");
618 assert
.deepStrictEqual(config
.toJSON(), {
620 ecmaVersion
: "latest",
621 parser
: `espree@${espree.version}`,
626 processor
: "custom-processor@1.2.3"
633 describe("Special configs", () => {
634 it("eslint:recommended is replaced with an actual config", async () => {
635 const configs
= new FlatConfigArray(["eslint:recommended"]);
637 await configs
.normalize();
638 const config
= configs
.getConfig("foo.js");
640 assert
.deepStrictEqual(config
.rules
, normalizeRuleConfig(recommendedConfig
.rules
));
643 it("eslint:all is replaced with an actual config", async () => {
644 const configs
= new FlatConfigArray(["eslint:all"]);
646 await configs
.normalize();
647 const config
= configs
.getConfig("foo.js");
649 assert
.deepStrictEqual(config
.rules
, normalizeRuleConfig(allConfig
.rules
));
653 describe("Config Properties", () => {
655 describe("settings", () => {
657 it("should merge two objects", () => assertMergedResult([
671 plugins
: baseConfig
.plugins
,
681 it("should merge two objects when second object has overrides", () => assertMergedResult([
698 plugins
: baseConfig
.plugins
,
709 it("should deeply merge two objects when second object has overrides", () => assertMergedResult([
727 plugins
: baseConfig
.plugins
,
738 it("should merge an object and undefined into one object", () => assertMergedResult([
748 plugins
: baseConfig
.plugins
,
756 it("should merge undefined and an object into one object", () => assertMergedResult([
766 plugins
: baseConfig
.plugins
,
776 describe("plugins", () => {
782 it("should merge two objects", () => assertMergedResult([
799 ...baseConfig
.plugins
803 it("should merge an object and undefined into one object", () => assertMergedResult([
816 ...baseConfig
.plugins
820 it("should error when attempting to redefine a plugin", async () => {
822 await
assertInvalidConfig([
834 ], "Cannot redefine plugin \"a\".");
837 it("should error when plugin is not an object", async () => {
839 await
assertInvalidConfig([
845 ], "Key \"a\": Expected an object.");
851 describe("processor", () => {
853 it("should merge two values when second is a string", () => {
855 const stubProcessor
= {
860 return assertMergedResult([
871 markdown
: stubProcessor
875 processor
: "markdown/markdown"
881 markdown
: stubProcessor
884 ...baseConfig
.plugins
886 processor
: stubProcessor
890 it("should merge two values when second is an object", () => {
897 return assertMergedResult([
899 processor
: "markdown/markdown"
905 plugins
: baseConfig
.plugins
,
911 it("should error when an invalid string is used", async () => {
913 await
assertInvalidConfig([
917 ], "pluginName/objectName");
920 it("should error when an empty string is used", async () => {
922 await
assertInvalidConfig([
926 ], "pluginName/objectName");
929 it("should error when an invalid processor is used", async () => {
930 await
assertInvalidConfig([
934 ], "Object must have a preprocess() and a postprocess() method.");
938 it("should error when a processor cannot be found in a plugin", async () => {
939 await
assertInvalidConfig([
946 ], /Could not find "bar" in plugin "foo"/u);
952 describe("linterOptions", () => {
954 it("should error when an unexpected key is found", async () => {
956 await
assertInvalidConfig([
962 ], "Unexpected key \"foo\" found.");
966 describe("noInlineConfig", () => {
968 it("should error when an unexpected value is found", async () => {
970 await
assertInvalidConfig([
973 noInlineConfig
: "true"
976 ], "Expected a Boolean.");
979 it("should merge two objects when second object has overrides", () => assertMergedResult([
987 noInlineConfig
: false
991 plugins
: baseConfig
.plugins
,
994 noInlineConfig
: false
998 it("should merge an object and undefined into one object", () => assertMergedResult([
1001 noInlineConfig
: false
1007 plugins
: baseConfig
.plugins
,
1010 noInlineConfig
: false
1014 it("should merge undefined and an object into one object", () => assertMergedResult([
1019 noInlineConfig
: false
1023 plugins
: baseConfig
.plugins
,
1026 noInlineConfig
: false
1032 describe("reportUnusedDisableDirectives", () => {
1034 it("should error when an unexpected value is found", async () => {
1036 await
assertInvalidConfig([
1039 reportUnusedDisableDirectives
: "true"
1042 ], /Expected a Boolean/u);
1045 it("should merge two objects when second object has overrides", () => assertMergedResult([
1048 reportUnusedDisableDirectives
: false
1053 reportUnusedDisableDirectives
: true
1057 plugins
: baseConfig
.plugins
,
1060 reportUnusedDisableDirectives
: true
1064 it("should merge an object and undefined into one object", () => assertMergedResult([
1068 reportUnusedDisableDirectives
: true
1072 plugins
: baseConfig
.plugins
,
1075 reportUnusedDisableDirectives
: true
1084 describe("languageOptions", () => {
1086 it("should error when an unexpected key is found", async () => {
1088 await
assertInvalidConfig([
1094 ], "Unexpected key \"foo\" found.");
1098 it("should merge two languageOptions objects with different properties", () => assertMergedResult([
1106 sourceType
: "commonjs"
1110 plugins
: baseConfig
.plugins
,
1114 sourceType
: "commonjs"
1118 describe("ecmaVersion", () => {
1120 it("should error when an unexpected value is found", async () => {
1122 await
assertInvalidConfig([
1128 ], /Key "languageOptions": Key "ecmaVersion": Expected a number or "latest"\./u);
1131 it("should merge two objects when second object has overrides", () => assertMergedResult([
1143 plugins
: baseConfig
.plugins
,
1150 it("should merge an object and undefined into one object", () => assertMergedResult([
1159 plugins
: baseConfig
.plugins
,
1167 it("should merge undefined and an object into one object", () => assertMergedResult([
1176 plugins
: baseConfig
.plugins
,
1186 describe("sourceType", () => {
1188 it("should error when an unexpected value is found", async () => {
1190 await
assertInvalidConfig([
1196 ], "Expected \"script\", \"module\", or \"commonjs\".");
1199 it("should merge two objects when second object has overrides", () => assertMergedResult([
1202 sourceType
: "module"
1207 sourceType
: "script"
1211 plugins
: baseConfig
.plugins
,
1214 sourceType
: "script"
1218 it("should merge an object and undefined into one object", () => assertMergedResult([
1221 sourceType
: "script"
1227 plugins
: baseConfig
.plugins
,
1230 sourceType
: "script"
1235 it("should merge undefined and an object into one object", () => assertMergedResult([
1240 sourceType
: "module"
1244 plugins
: baseConfig
.plugins
,
1247 sourceType
: "module"
1254 describe("globals", () => {
1256 it("should error when an unexpected value is found", async () => {
1258 await
assertInvalidConfig([
1264 ], "Expected an object.");
1267 it("should error when an unexpected key value is found", async () => {
1269 await
assertInvalidConfig([
1277 ], "Key \"foo\": Expected \"readonly\", \"writable\", or \"off\".");
1280 it("should error when a global has leading whitespace", async () => {
1282 await
assertInvalidConfig([
1290 ], /Global " foo" has leading or trailing whitespace/u);
1293 it("should error when a global has trailing whitespace", async () => {
1295 await
assertInvalidConfig([
1303 ], /Global "foo " has leading or trailing whitespace/u);
1306 it("should merge two objects when second object has different keys", () => assertMergedResult([
1322 plugins
: baseConfig
.plugins
,
1332 it("should merge two objects when second object has overrides", () => assertMergedResult([
1348 plugins
: baseConfig
.plugins
,
1357 it("should merge an object and undefined into one object", () => assertMergedResult([
1368 plugins
: baseConfig
.plugins
,
1378 it("should merge undefined and an object into one object", () => assertMergedResult([
1389 plugins
: baseConfig
.plugins
,
1401 describe("parser", () => {
1403 it("should error when an unexpected value is found", async () => {
1405 await
assertInvalidConfig([
1411 ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method.");
1414 it("should error when a null is found", async () => {
1416 await
assertInvalidConfig([
1422 ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method.");
1425 it("should error when a parser is a string", async () => {
1427 await
assertInvalidConfig([
1433 ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method.");
1436 it("should error when a value doesn't have a parse() method", async () => {
1438 await
assertInvalidConfig([
1444 ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method.");
1447 it("should merge two objects when second object has overrides", () => {
1449 const parser
= { parse() {} };
1450 const stubParser
= { parse() { } };
1452 return assertMergedResult([
1465 ...baseConfig
.plugins
1473 it("should merge an object and undefined into one object", () => {
1475 const stubParser
= { parse() { } };
1477 return assertMergedResult([
1487 ...baseConfig
.plugins
1498 it("should merge undefined and an object into one object", () => {
1500 const stubParser
= { parse() {} };
1502 return assertMergedResult([
1512 ...baseConfig
.plugins
1525 describe("parserOptions", () => {
1527 it("should error when an unexpected value is found", async () => {
1529 await
assertInvalidConfig([
1532 parserOptions
: "true"
1535 ], "Expected an object.");
1538 it("should merge two objects when second object has different keys", () => assertMergedResult([
1554 plugins
: baseConfig
.plugins
,
1564 it("should deeply merge two objects when second object has different keys", () => assertMergedResult([
1584 plugins
: baseConfig
.plugins
,
1596 it("should deeply merge two objects when second object has missing key", () => assertMergedResult([
1612 plugins
: baseConfig
.plugins
,
1624 it("should merge two objects when second object has overrides", () => assertMergedResult([
1640 plugins
: baseConfig
.plugins
,
1649 it("should merge an object and undefined into one object", () => assertMergedResult([
1660 plugins
: baseConfig
.plugins
,
1670 it("should merge undefined and an object into one object", () => assertMergedResult([
1681 plugins
: baseConfig
.plugins
,
1696 describe("rules", () => {
1698 it("should error when an unexpected value is found", async () => {
1700 await
assertInvalidConfig([
1704 ], "Expected an object.");
1707 it("should error when an invalid rule severity is set", async () => {
1709 await
assertInvalidConfig([
1715 ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2.");
1718 it("should error when an invalid rule severity of the right type is set", async () => {
1720 await
assertInvalidConfig([
1726 ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2.");
1729 it("should error when an invalid rule severity is set in an array", async () => {
1731 await
assertInvalidConfig([
1737 ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2.");
1740 it("should error when rule doesn't exist", async () => {
1742 await
assertInvalidConfig([
1748 ], /Key "rules": Key "foox": Could not find "foox" in plugin "@"./u);
1751 it("should error and suggest alternative when rule doesn't exist", async () => {
1753 await
assertInvalidConfig([
1756 "test2/match": "error"
1759 ], /Key "rules": Key "test2\/match": Could not find "match" in plugin "test2"\. Did you mean "test1\/match"\?/u);
1762 it("should error when plugin for rule doesn't exist", async () => {
1764 await
assertInvalidConfig([
1767 "doesnt-exist/match": "error"
1770 ], /Key "rules": Key "doesnt-exist\/match": Could not find plugin "doesnt-exist"\./u);
1773 it("should error when rule options don't match schema", async () => {
1775 await
assertInvalidConfig([
1781 ], /Value "bar" should be equal to one of the allowed values/u);
1784 it("should error when rule options don't match schema requiring at least one item", async () => {
1786 await
assertInvalidConfig([
1792 ], /Value \[\] should NOT have fewer than 1 items/u);
1795 it("should merge two objects", () => assertMergedResult([
1809 plugins
: baseConfig
.plugins
,
1819 it("should merge two objects when second object has simple overrides", () => assertMergedResult([
1833 plugins
: baseConfig
.plugins
,
1841 it("should merge two objects when second object has array overrides", () => assertMergedResult([
1850 foo
: ["error", "never"],
1851 foo2
: ["warn", "foo"]
1855 plugins
: baseConfig
.plugins
,
1862 it("should merge two objects and options when second object overrides without options", () => assertMergedResult([
1880 "@foo/baz/boom/bang": "error"
1885 ...baseConfig
.plugins
,
1895 "@foo/baz/boom/bang": [2]
1899 it("should merge an object and undefined into one object", () => assertMergedResult([
1909 plugins
: baseConfig
.plugins
,
1916 it("should merge a rule that doesn't exist without error when the rule is off", () => assertMergedResult([
1926 nonExistentRule2
: ["off", "bar"]
1930 plugins
: baseConfig
.plugins
,
1934 nonExistentRule
: [0],
1935 nonExistentRule2
: [0, "bar"]