+ 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 interpreted 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 interpreted 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);
+ });
+ });
+
+ describe("config file", () => {
+
+ it("new instance of FlatESLint should use the latest version of the config file (ESM)", async () => {
+ const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`);
+ const configFileContent = "export default [{ rules: { semi: ['error', 'always'] } }];";
+ const teardown = createCustomTeardown({
+ cwd,
+ files: {
+ "package.json": '{ "type": "module" }',
+ "eslint.config.js": configFileContent,
+ "a.js": "foo\nbar;"
+ }
+ });
+
+ await teardown.prepare();
+
+ let eslint = new FlatESLint({ cwd });
+ let [{ messages }] = await eslint.lintFiles(["a.js"]);
+
+ assert.strictEqual(messages.length, 1);
+ assert.strictEqual(messages[0].ruleId, "semi");
+ assert.strictEqual(messages[0].messageId, "missingSemi");
+ assert.strictEqual(messages[0].line, 1);
+
+ await sleep(100);
+ await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never"));
+
+ eslint = new FlatESLint({ cwd });
+ [{ messages }] = await eslint.lintFiles(["a.js"]);
+
+ assert.strictEqual(messages.length, 1);
+ assert.strictEqual(messages[0].ruleId, "semi");
+ assert.strictEqual(messages[0].messageId, "extraSemi");
+ assert.strictEqual(messages[0].line, 2);
+ });
+
+ it("new instance of FlatESLint should use the latest version of the config file (CJS)", async () => {
+ const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`);
+ const configFileContent = "module.exports = [{ rules: { semi: ['error', 'always'] } }];";
+ const teardown = createCustomTeardown({
+ cwd,
+ files: {
+ "eslint.config.js": configFileContent,
+ "a.js": "foo\nbar;"
+ }
+ });
+
+ await teardown.prepare();
+
+ let eslint = new FlatESLint({ cwd });
+ let [{ messages }] = await eslint.lintFiles(["a.js"]);
+
+ assert.strictEqual(messages.length, 1);
+ assert.strictEqual(messages[0].ruleId, "semi");
+ assert.strictEqual(messages[0].messageId, "missingSemi");
+ assert.strictEqual(messages[0].line, 1);
+
+ await sleep(100);
+ await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never"));
+
+ eslint = new FlatESLint({ cwd });
+ [{ messages }] = await eslint.lintFiles(["a.js"]);
+
+ assert.strictEqual(messages.length, 1);
+ assert.strictEqual(messages[0].ruleId, "semi");
+ assert.strictEqual(messages[0].messageId, "extraSemi");
+ assert.strictEqual(messages[0].line, 2);
+ });
+ });
+
+});
+
+describe("shouldUseFlatConfig", () => {
+
+ /**
+ * Check that `shouldUseFlatConfig` returns the expected value from a CWD
+ * with a flat config and one without a flat config.
+ * @param {boolean} expectedValueWithConfig the expected return value of
+ * `shouldUseFlatConfig` when in a directory with a flat config present
+ * @param {boolean} expectedValueWithoutConfig the expected return value of
+ * `shouldUseFlatConfig` when in a directory without any flat config present
+ * @returns {void}
+ */
+ function testShouldUseFlatConfig(expectedValueWithConfig, expectedValueWithoutConfig) {
+ describe("when there is a flat config file present", () => {
+ const originalDir = process.cwd();
+
+ beforeEach(() => {
+ process.chdir(__dirname);
+ });
+
+ afterEach(() => {
+ process.chdir(originalDir);
+ });
+
+ it(`is \`${expectedValueWithConfig}\``, async () => {
+ assert.strictEqual(await shouldUseFlatConfig(), expectedValueWithConfig);
+ });
+ });
+
+ describe("when there is no flat config file present", () => {
+ const originalDir = process.cwd();
+
+ beforeEach(() => {
+ process.chdir(os.tmpdir());
+ });
+
+ afterEach(() => {
+ process.chdir(originalDir);
+ });
+
+ it(`is \`${expectedValueWithoutConfig}\``, async () => {
+ assert.strictEqual(await shouldUseFlatConfig(), expectedValueWithoutConfig);
+ });
+ });
+ }
+
+ describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'true'`", () => {
+ beforeEach(() => {
+ process.env.ESLINT_USE_FLAT_CONFIG = true;
+ });
+
+ afterEach(() => {
+ delete process.env.ESLINT_USE_FLAT_CONFIG;
+ });
+
+ testShouldUseFlatConfig(true, true);
+ });
+
+ describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'false'`", () => {
+ beforeEach(() => {
+ process.env.ESLINT_USE_FLAT_CONFIG = false;
+ });
+
+ afterEach(() => {
+ delete process.env.ESLINT_USE_FLAT_CONFIG;
+ });
+
+ testShouldUseFlatConfig(false, false);
+ });
+
+ describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is unset", () => {
+ testShouldUseFlatConfig(true, false);
+ });