From 37eba48d6f2d3c99c5ecf2fc3967e428a6051dbb Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 7 May 2024 08:33:44 +0200 Subject: [PATCH] fix: don't crash when `fs.readFile` returns promise from another realm (#18416) * fix: don't crash when `fs.readFile` returns promise from another realm Fixes #18407 * update test case title --- lib/eslint/eslint.js | 3 +- package.json | 2 +- tests/lib/eslint/eslint.js | 76 +++++++++++++++++++++++++++++++------- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index d3875425e01..900353385cf 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -9,8 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -// Note: Node.js 12 does not support fs/promises. -const fs = require("fs").promises; +const fs = require("fs/promises"); const { existsSync } = require("fs"); const path = require("path"); const findUp = require("find-up"); diff --git a/package.json b/package.json index 5b5d814bdd2..54290f00d54 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@eslint/js": "9.2.0", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.2.3", + "@humanwhocodes/retry": "^0.2.4", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", "chalk": "^4.0.0", diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 0e7bcfc4a26..53a43fe19d9 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -13,7 +13,7 @@ const assert = require("assert"); const util = require("util"); const fs = require("fs"); -const fsp = fs.promises; +const fsp = require("fs/promises"); const os = require("os"); const path = require("path"); const timers = require("node:timers/promises"); @@ -1057,6 +1057,61 @@ describe("ESLint", () => { await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected object with parse\(\) or parseForESLint\(\) method/u); }); + // https://github.com/eslint/eslint/issues/18407 + it("should work in case when `fsp.readFile()` returns an object that is not an instance of Promise from this realm", async () => { + + /** + * Promise wrapper + */ + class PromiseLike { + constructor(promise) { + this.promise = promise; + } + then(...args) { + return new PromiseLike(this.promise.then(...args)); + } + catch(...args) { + return new PromiseLike(this.promise.catch(...args)); + } + finally(...args) { + return new PromiseLike(this.promise.finally(...args)); + } + } + + const spy = sinon.spy( + (...args) => new PromiseLike(fsp.readFile(...args)) + ); + + const { ESLint: LocalESLint } = proxyquire("../../../lib/eslint/eslint", { + "fs/promises": { + readFile: spy, + "@noCallThru": false // allows calling other methods of `fs/promises` + } + }); + + const testDir = "tests/fixtures/simple-valid-project"; + const expectedLintedFiles = [ + path.resolve(testDir, "foo.js"), + path.resolve(testDir, "src", "foobar.js") + ]; + + eslint = new LocalESLint({ + cwd: originalDir, + overrideConfigFile: path.resolve(testDir, "eslint.config.js") + }); + + const results = await eslint.lintFiles([`${testDir}/**/foo*.js`]); + + assert.strictEqual(results.length, expectedLintedFiles.length); + + expectedLintedFiles.forEach((file, index) => { + assert(spy.calledWith(file), `Spy was not called with ${file}`); + assert.strictEqual(results[index].filePath, file); + assert.strictEqual(results[index].messages.length, 0); + assert.strictEqual(results[index].suppressedMessages.length, 0); + }); + }); + describe("Invalid inputs", () => { [ @@ -5513,13 +5568,10 @@ describe("ESLint", () => { }); it("should call fs.writeFile() for each result with output", async () => { - const fakeFS = { - writeFile: sinon.spy(() => Promise.resolve()) - }; - const spy = fakeFS.writeFile; + const spy = sinon.spy(() => Promise.resolve()); const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { - fs: { - promises: fakeFS + "fs/promises": { + writeFile: spy } }); @@ -5542,15 +5594,13 @@ describe("ESLint", () => { }); it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => { - const fakeFS = { - writeFile: sinon.spy(() => Promise.resolve()) - }; - const spy = fakeFS.writeFile; + const spy = sinon.spy(() => Promise.resolve()); const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { - fs: { - promises: fakeFS + "fs/promises": { + writeFile: spy } }); + const results = [ { filePath: path.resolve("foo.js"),