diff --git a/lib/CachedInputFileSystem.js b/lib/CachedInputFileSystem.js index 022e72c0..cdd1130f 100644 --- a/lib/CachedInputFileSystem.js +++ b/lib/CachedInputFileSystem.js @@ -577,6 +577,19 @@ module.exports = class CachedInputFileSystem { this.readlinkSync = /** @type {SyncFileSystem["readlinkSync"]} */ ( readlinkSync ); + + this._realpathBackend = createBackend( + duration, + this.fileSystem.realpath, + this.fileSystem.realpathSync, + this.fileSystem + ); + const realpath = this._realpathBackend.provide; + this.realpath = /** @type {FileSystem["realpath"]} */ (realpath); + const realpathSync = this._realpathBackend.provideSync; + this.realpathSync = /** @type {SyncFileSystem["realpathSync"]} */ ( + realpathSync + ); } /** @@ -589,5 +602,6 @@ module.exports = class CachedInputFileSystem { this._readFileBackend.purge(what); this._readlinkBackend.purge(what); this._readJsonBackend.purge(what); + this._realpathBackend.purge(what); } }; diff --git a/lib/Resolver.js b/lib/Resolver.js index 8806bb1c..97d21a85 100644 --- a/lib/Resolver.js +++ b/lib/Resolver.js @@ -17,9 +17,9 @@ const { /** @typedef {import("./ResolverFactory").ResolveOptions} ResolveOptions */ -/** @typedef {Error & {details?: string}} ErrorWithDetail */ +/** @typedef {Error & { details?: string }} ErrorWithDetail */ -/** @typedef {(err: ErrorWithDetail|null, res?: string|false, req?: ResolveRequest) => void} ResolveCallback */ +/** @typedef {(err: ErrorWithDetail | null, res?: string | false, req?: ResolveRequest) => void} ResolveCallback */ /** * @typedef {Object} FileSystemStats @@ -65,6 +65,7 @@ const { * @property {(function(string, FileSystemCallback): void) & function(string, object, FileSystemCallback): void} readlink * @property {(function(string, FileSystemCallback): void) & function(string, object, FileSystemCallback): void=} lstat * @property {(function(string, FileSystemCallback): void) & function(string, object, FileSystemCallback): void} stat + * @property {(function(string, FileSystemCallback): void) & function(string, object, FileSystemCallback): void=} realpath */ /** @@ -75,6 +76,7 @@ const { * @property {function(string, object=): Buffer | string} readlinkSync * @property {function(string, object=): FileSystemStats=} lstatSync * @property {function(string, object=): FileSystemStats} statSync + * @property {function(string, object=): string | Buffer=} realpathSync */ /** diff --git a/test/CachedInputFileSystem.test.js b/test/CachedInputFileSystem.test.js index 9e57fd04..913e9ba5 100644 --- a/test/CachedInputFileSystem.test.js +++ b/test/CachedInputFileSystem.test.js @@ -165,6 +165,90 @@ describe("CachedInputFileSystem OperationMergerBackend ('lstat' and 'lstatSync') }); }); +describe("CachedInputFileSystem OperationMergerBackend ('realpath' and 'realpathSync')", () => { + let fs; + + beforeEach(() => { + fs = new CachedInputFileSystem( + { + realpath: function (path, options, callback) { + if (!callback) { + callback = options; + options = undefined; + } + setTimeout( + () => + callback(null, { + path, + options + }), + 100 + ); + }, + realpathSync: function (path, options) { + return { + path, + options + }; + } + }, + 0 + ); + }); + afterEach(() => { + fs.purge(); + }); + + it("should join accesses", function (done) { + fs.realpath("a", function (err, result) { + expect(result).toBeDefined(); + result.a = true; + }); + fs.realpath("a", function (err, result) { + expect(result).toBeDefined(); + expect(result.a).toBeDefined(); + done(); + }); + }); + + it("should not join accesses with options", function (done) { + fs.realpath("a", function (err, result) { + expect(result).toBeDefined(); + + result.a = true; + + expect(result).toBeDefined(); + expect(result.path).toEqual("a"); + expect(result.options).toBeUndefined(); + }); + fs.realpath("a", { options: true }, function (err, result) { + expect(result).toBeDefined(); + expect(result.a).toBeUndefined(); + expect(result.path).toEqual("a"); + expect(result.options).toMatchObject({ options: true }); + done(); + }); + }); + + it("should not cache accesses", function (done) { + fs.realpath("a", function (err, result) { + result.a = true; + fs.realpath("a", function (err, result) { + expect(result.a).toBeUndefined(); + done(); + }); + }); + }); + + it("should not cache sync accesses", () => { + const result = fs.realpathSync("a"); + result.a = true; + const result2 = fs.realpathSync("a"); + + expect(result2.a).toBeUndefined(); + }); +}); + describe("CachedInputFileSystem CacheBackend", () => { let fs; diff --git a/types.d.ts b/types.d.ts index 431b42ec..1522fc6a 100644 --- a/types.d.ts +++ b/types.d.ts @@ -104,6 +104,15 @@ declare class CachedInputFileSystem { ): void; }; readlinkSync: (arg0: string, arg1?: object) => string | Buffer; + realpath?: { + (arg0: string, arg1: FileSystemCallback): void; + ( + arg0: string, + arg1: object, + arg2: FileSystemCallback + ): void; + }; + realpathSync?: (arg0: string, arg1?: object) => string | Buffer; purge(what?: string | Set | string[]): void; } declare class CloneBasenamePlugin { @@ -206,6 +215,14 @@ declare interface FileSystem { arg2: FileSystemCallback ): void; }; + realpath?: { + (arg0: string, arg1: FileSystemCallback): void; + ( + arg0: string, + arg1: object, + arg2: FileSystemCallback + ): void; + }; } declare interface FileSystemCallback { (err?: null | (PossibleFileSystemError & Error), result?: T): any;