diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js index 479d5f14f2c..5866e445ec0 100644 --- a/lib/eslint/flat-eslint.js +++ b/lib/eslint/flat-eslint.js @@ -262,24 +262,16 @@ function findFlatConfigFile(cwd) { /** * Load the config array from the given filename. * @param {string} filePath The filename to load from. - * @param {Object} options Options to help load the config file. - * @param {string} options.basePath The base path for the config array. - * @param {boolean} options.shouldIgnore Whether to honor ignore patterns. - * @returns {Promise} The config array loaded from the config file. + * @returns {Promise} The config loaded from the config file. */ -async function loadFlatConfigFile(filePath, { basePath, shouldIgnore }) { +async function loadFlatConfigFile(filePath) { debug(`Loading config from ${filePath}`); const fileURL = pathToFileURL(filePath); debug(`Config file URL is ${fileURL}`); - const module = await import(fileURL); - - return new FlatConfigArray(module.default, { - basePath, - shouldIgnore - }); + return (await import(fileURL)).default; } /** @@ -290,6 +282,7 @@ async function loadFlatConfigFile(filePath, { basePath, shouldIgnore }) { */ async function calculateConfigArray(eslint, { cwd, + baseConfig, overrideConfig, configFile, ignore: shouldIgnore, @@ -321,16 +314,18 @@ async function calculateConfigArray(eslint, { basePath = path.resolve(path.dirname(configFilePath)); } - // load config array - let configs; + const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore }); + + // load config file if (configFilePath) { - configs = await loadFlatConfigFile(configFilePath, { - basePath, - shouldIgnore - }); - } else { - configs = new FlatConfigArray([], { basePath, shouldIgnore }); + const fileConfig = await loadFlatConfigFile(configFilePath); + + if (Array.isArray(fileConfig)) { + configs.push(...fileConfig); + } else { + configs.push(fileConfig); + } } // add in any configured defaults diff --git a/tests/fixtures/eslint.config_with_rules.js b/tests/fixtures/eslint.config_with_rules.js new file mode 100644 index 00000000000..6817217d965 --- /dev/null +++ b/tests/fixtures/eslint.config_with_rules.js @@ -0,0 +1,5 @@ +module.exports = [{ + rules: { + quotes: ["error", "single"] + } +}]; diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index f4d28039503..60c0975a61c 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -4799,4 +4799,192 @@ describe("FlatESLint", () => { }); }); + describe("baseConfig", () => { + it("can be an object", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: { + rules: { + semi: 2 + } + } + }); + + const [{ messages }] = await eslint.lintText("foo"); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + }); + + it("can be an array", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: [ + { + rules: { + "no-var": 2 + } + }, + { + rules: { + semi: 2 + } + } + ] + }); + + const [{ messages }] = await eslint.lintText("var foo"); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-var"); + assert.strictEqual(messages[1].ruleId, "semi"); + }); + + it("should be inserted after default configs", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: { + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + } + } + }); + + const [{ messages }] = await eslint.lintText("let x"); + + /* + * if baseConfig was inserted before default configs, + * `ecmaVersion: "latest"` from default configs would overwrite + * `ecmaVersion: 5` from baseConfig, so this wouldn't be a parsing error. + */ + + assert.strictEqual(messages.length, 1); + assert(messages[0].fatal, "Fatal error expected."); + }); + + it("should be inserted before configs from the config file", async () => { + const eslint = new FlatESLint({ + cwd: getFixturePath(), + baseConfig: { + rules: { + strict: ["error", "global"] + }, + languageOptions: { + sourceType: "script" + } + } + }); + + const [{ messages }] = await eslint.lintText("foo"); + + /* + * if baseConfig was inserted after configs from the config file, + * `strict: 0` from eslint.config.js wouldn't overwrite `strict: ["error", "global"]` + * from baseConfig, so there would be an error message from the `strict` rule. + */ + + assert.strictEqual(messages.length, 0); + }); + + it("should be inserted before overrideConfig", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: true, + baseConfig: { + rules: { + semi: 2 + } + }, + overrideConfig: { + rules: { + semi: 1 + } + } + }); + + const [{ messages }] = await eslint.lintText("foo"); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].severity, 1); + }); + + it("should be inserted before configs from the config file and overrideConfig", async () => { + const eslint = new FlatESLint({ + overrideConfigFile: getFixturePath("eslint.config_with_rules.js"), + baseConfig: { + rules: { + quotes: ["error", "double"], + semi: "error" + } + }, + overrideConfig: { + rules: { + quotes: "warn" + } + } + }); + + const [{ messages }] = await eslint.lintText('const foo = "bar"'); + + /* + * baseConfig: { quotes: ["error", "double"], semi: "error" } + * eslint.config_with_rules.js: { quotes: ["error", "single"] } + * overrideConfig: { quotes: "warn" } + * + * Merged config: { quotes: ["warn", "single"], semi: "error" } + */ + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "quotes"); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[1].ruleId, "semi"); + assert.strictEqual(messages[1].severity, 2); + }); + + it("when it has 'files' they should be intepreted as relative to the config file", async () => { + + /* + * `fixtures/plugins` directory does not have a config file. + * It's parent directory `fixtures` does have a config file, so + * the base path will be `fixtures`, cwd will be `fixtures/plugins` + */ + const eslint = new FlatESLint({ + cwd: getFixturePath("plugins"), + baseConfig: { + files: ["plugins/a.js"], + rules: { + semi: 2 + } + } + }); + + const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js") }); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + }); + + it("when it has 'ignores' they should be intepreted as relative to the config file", async () => { + + /* + * `fixtures/plugins` directory does not have a config file. + * It's parent directory `fixtures` does have a config file, so + * the base path will be `fixtures`, cwd will be `fixtures/plugins` + */ + const eslint = new FlatESLint({ + cwd: getFixturePath("plugins"), + baseConfig: { + ignores: ["plugins/a.js"] + } + }); + + const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js"), warnIgnored: true }); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.match(messages[0].message, /ignored/u); + }); + }); + });