8000 fix: new instance of FlatESLint should load latest config file versio… · eslint/eslint@87b2470 · GitHub
[go: up one dir, main page]

Skip to content

Commit 87b2470

Browse files
authored
fix: new instance of FlatESLint should load latest config file version (#16608)
* fix: new instance of FlatESLint should load latest config file version * clear require.cache * add delays
1 parent 8d93081 commit 87b2470

File tree

2 files changed

+117
-1
lines changed

2 files changed

+117
-1
lines changed

lib/eslint/flat-eslint.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ const FLAT_CONFIG_FILENAME = "eslint.config.js";
9393
const debug = require("debug")("eslint:flat-eslint");
9494
const removedFormatters = new Set(["table", "codeframe"]);
9595
const privateMembers = new WeakMap();
96+
const importedConfigFileModificationTime = new Map();
9697

9798
/**
9899
* It will calculate the error and warning count for collection of messages per file
@@ -281,7 +282,42 @@ async function loadFlatConfigFile(filePath) {
281282

282283
debug(`Config file URL is ${fileURL}`);
283284

284-
return (await import(fileURL)).default;
285+
const mtime = (await fs.stat(filePath)).mtime.getTime();
286+
287+
/*
288+
* Append a query with the config file's modification time (`mtime`) in order
289+
* to import the current version of the config file. Without the query, `import()` would
290+
* cache the config file module by the pathname only, and then always return
291+
* the same version (the one that was actual when the module was imported for the first time).
292+
*
293+
* This ensures that the config file module is loaded and executed again
294+
* if it has been changed since the last time it was imported.
295+
* If it hasn't been changed, `import()` will just return the cached version.
296+
*
297+
* Note that we should not overuse queries (e.g., by appending the current time
298+
* to always reload the config file module) as that could cause memory leaks
299+
* because entries are never removed from the import cache.
300+
*/
301+
fileURL.searchParams.append("mtime", mtime);
302+
303+
/*
304+
* With queries, we can bypass the import cache. However, when import-ing a CJS module,
305+
* Node.js uses the require infrastructure under the hood. That includes the require cache,
306+
* which caches the config file module by its file path (queries have no effect).
307+
* Therefore, we also need to clear the require cache before importing the config file module.
308+
* In order to get the same behavior with ESM and CJS config files, in particular - to reload
309+
* the config file only if it has been changed, we track file modification times and clear
310+
* the require cache only if the file has been changed.
311+
*/
312+
if (importedConfigFileModificationTime.get(filePath) !== mtime) {
313+
delete require.cache[filePath];
314+
}
315+
316+
const config = (await import(fileURL)).default;
317+
318+
importedConfigFileModificationTime.set(filePath, mtime);
319+
320+
return config;
285321
}
286322

287323
/**

tests/lib/eslint/flat-eslint.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//------------------------------------------------------------------------------
1212

1313
const assert = require("assert");
14+
const util = require("util");
1415
const fs = require("fs");
1516
const fsp = fs.promises;
1617
const os = require("os");
@@ -41,6 +42,15 @@ function ensureDirectoryExists(dirPath) {
4142
}
4243
}
4344

45+
/**
46+
* Does nothing for a given time.
47+
* @param {number} time Time in ms.
48+
* @returns {void}
49+
*/
50+
async function sleep(time) {
51+
await util.promisify(setTimeout)(time);
52+
}
53+
4454
//------------------------------------------------------------------------------
4555
// Tests
4656
//------------------------------------------------------------------------------
@@ -5305,4 +5315,74 @@ describe("FlatESLint", () => {
53055315
});
53065316
});
53075317

5318+
describe("config file", () => {
5319+
5320+
it("new instance of FlatESLint should use the latest version of the config file (ESM)", async () => {
5321+
const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`);
5322+
const configFileContent = "export default [{ rules: { semi: ['error', 'always'] } }];";
5323+
const teardown = createCustomTeardown({
5324+
cwd,
5325+
files: {
5326+
"package.json": '{ "type": "module" }',
5327+
"eslint.config.js": configFileContent,
5328+
"a.js": "foo\nbar;"
5329+
}
5330+
});
5331+
5332+
await teardown.prepare();
5333+
5334+
let eslint = new FlatESLint({ cwd });
5335+
let [{ messages }] = await eslint.lintFiles(["a.js"]);
5336+
5337+
assert.strictEqual(messages.length, 1);
5338+
assert.strictEqual(messages[0].ruleId, "semi");
5339+
assert.strictEqual(messages[0].messageId, "missingSemi");
5340+
assert.strictEqual(messages[0].line, 1);
5341+
5342+
await sleep(100);
5343+
await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never"));
5344+
5345+
eslint = new FlatESLint({ cwd });
5346+
[{ messages }] = await eslint.lintFiles(["a.js"]);
5347+
5348+
assert.strictEqual(messages.length, 1);
5349+
assert.strictEqual(messages[0].ruleId, "semi");
5350+
assert.strictEqual(messages[0].messageId, "extraSemi");
5351+
assert.strictEqual(messages[0].line, 2);
5352+
});
5353+
5354+
it("new instance of FlatESLint should use the latest version of the config file (CJS)", async () => {
5355+
const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`);
5356+
const configFileContent = "module.exports = [{ rules: { semi: ['error', 'always'] } }];";
5357+
const teardown = createCustomTeardown({
5358+
cwd,
5359+
files: {
5360+
"eslint.config.js": configFileContent,
5361+
"a.js": "foo\nbar;"
5362+
}
5363+
});
5364+
5365+
await teardown.prepare();
5366+
5367+
let eslint = new FlatESLint({ cwd });
5368+
let [{ messages }] = await eslint.lintFiles(["a.js"]);
5369+
5370+
assert.strictEqual(messages.length, 1);
5371+
assert.strictEqual(messages[0].ruleId, "semi");
5372+
assert.strictEqual(messages[0].messageId, "missingSemi");
5373+
assert.strictEqual(messages[0].line, 1);
5374+
5375+
await sleep(100);
5376+
await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never"));
5377+
5378+
eslint = new FlatESLint({ cwd });
5379+
[{ messages }] = await eslint.lintFiles(["a.js"]);
5380+
5381+
assert.strictEqual(messages.length, 1);
5382+
assert.strictEqual(messages[0].ruleId, "semi");
5383+
assert.strictEqual(messages[0].messageId, "extraSemi");
5384+
assert.strictEqual(messages[0].line, 2);
5385+
});
5386+
});
5387+
53085388
});

0 commit comments

Comments
 (0)
0