Skip to content

Commit 1a05a8f

Browse files
committed
vfs: fix lint errors across implementation and tests
- Use primordials correctly (MathMin, Promise, Error) - Alphabetize primordials imports - Fix JSDoc types (function -> Function) - Fix function name matching for fs overrides - Fix use-before-define issues by restructuring code - Add null-prototype to async function returns - Remove unused imports and variables - Use proper error codes from internal/errors - Add URL import from internal/url - Fix test files: remove unused variables, use node: prefix
1 parent 65499d8 commit 1a05a8f

19 files changed

+160
-171
lines changed

doc/api/fs.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8294,7 +8294,7 @@ added: REPLACEME
82948294
> Stability: 1 - Experimental
82958295
82968296
The virtual file system (VFS) allows creating in-memory file system overlays
8297-
that integrate seamlessly with Node.js's `fs` module and module loader. Virtual
8297+
that integrate seamlessly with the Node.js `fs` module and module loader. Virtual
82988298
files and directories can be accessed using standard `fs` operations and can be
82998299
`require()`d or `import`ed like regular files.
83008300
@@ -8384,6 +8384,7 @@ added: REPLACEME
83848384
returns the content.
83858385
83868386
Adds a virtual file. The `content` can be:
8387+
83878388
* A `string` or `Buffer` for static content
83888389
* A synchronous function `() => string|Buffer` for dynamic content
83898390
* An async function `async () => string|Buffer` for async dynamic content
@@ -8628,6 +8629,7 @@ stream.on('end', () => console.log('Done'));
86288629
```
86298630
86308631
The readable stream supports the following options:
8632+
86318633
* `encoding` {string} Character encoding for string output.
86328634
* `start` {integer} Byte position to start reading from.
86338635
* `end` {integer} Byte position to stop reading at (inclusive).
@@ -8637,7 +8639,7 @@ The readable stream supports the following options:
86378639
### Module loading from VFS
86388640
86398641
Virtual files can be loaded as modules using `require()` or `import`. The VFS
8640-
integrates with Node.js's module loaders automatically when mounted or in
8642+
integrates with the Node.js module loaders automatically when mounted or in
86418643
overlay mode.
86428644
86438645
```cjs
@@ -8711,10 +8713,12 @@ fs.glob('/virtual/src/*.js', (err, matches) => {
87118713
});
87128714

87138715
// Async glob with promises (returns async iterator)
8714-
const { glob } = require('fs/promises');
8715-
for await (const file of glob('/virtual/src/**/*.js')) {
8716-
console.log(file);
8717-
}
8716+
const { glob } = require('node:fs/promises');
8717+
(async () => {
8718+
for await (const file of glob('/virtual/src/**/*.js')) {
8719+
console.log(file);
8720+
}
8721+
})();
87188722
```
87198723
87208724
### SEA (Single Executable Application) integration

lib/fs.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3212,8 +3212,8 @@ const lazySeaVfs = getLazy(() => require('internal/vfs/sea'));
32123212
/**
32133213
* Creates a new virtual file system instance.
32143214
* @param {object} [options] Configuration options
3215-
* @param {boolean} [options.fallthrough=true] Whether to fall through to real fs on miss
3216-
* @param {boolean} [options.moduleHooks=true] Whether to enable require/import hooks
3215+
* @param {boolean} [options.fallthrough] Whether to fall through to real fs on miss
3216+
* @param {boolean} [options.moduleHooks] Whether to enable require/import hooks
32173217
* @returns {VirtualFileSystem}
32183218
*/
32193219
function createVirtual(options) {
@@ -3225,8 +3225,8 @@ function createVirtual(options) {
32253225
* Gets the VFS containing SEA (Single Executable Application) assets.
32263226
* Returns null if not running as a SEA or if there are no assets.
32273227
* @param {object} [options] Configuration options
3228-
* @param {string} [options.prefix='/sea'] Mount point prefix for SEA assets
3229-
* @param {boolean} [options.moduleHooks=true] Whether to enable require/import hooks
3228+
* @param {string} [options.prefix] Mount point prefix for SEA assets
3229+
* @param {boolean} [options.moduleHooks] Whether to enable require/import hooks
32303230
* @returns {VirtualFileSystem|null}
32313231
*/
32323232
function getSeaVfs(options) {

lib/internal/test_runner/mock/mock.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ class MockTracker {
795795
/**
796796
* Creates a mock file system using VFS.
797797
* @param {object} [options] - Options for the mock file system.
798-
* @param {string} [options.prefix='/mock'] - The mount prefix for the VFS.
798+
* @param {string} [options.prefix] - The mount prefix for the VFS.
799799
* @param {object} [options.files] - Initial files to add (path: content pairs).
800800
* @returns {MockFSContext} The mock file system context.
801801
*/
@@ -807,7 +807,7 @@ class MockTracker {
807807
}
808808

809809
const { VirtualFileSystem } = require('internal/vfs/virtual_fs');
810-
const vfs = new VirtualFileSystem({ moduleHooks: true });
810+
const vfs = new VirtualFileSystem({ __proto__: null, moduleHooks: true });
811811

812812
// Add initial files if provided
813813
if (files) {

lib/internal/vfs/entries.js

Lines changed: 33 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
'use strict';
22

33
const {
4-
DateNow,
4+
Promise,
55
SafeMap,
66
Symbol,
77
} = primordials;
88

99
const { Buffer } = require('buffer');
10+
const {
11+
codes: {
12+
ERR_INVALID_STATE,
13+
},
14+
} = require('internal/errors');
1015
const { createFileStats, createDirectoryStats } = require('internal/vfs/stats');
1116

1217
// Symbols for private properties
@@ -69,7 +74,7 @@ class VirtualEntry {
6974
class VirtualFile extends VirtualEntry {
7075
/**
7176
* @param {string} path The absolute path of this file
72-
* @param {Buffer|string|function} content The file content or content provider
77+
* @param {Buffer|string|Function} content The file content or content provider
7378
* @param {object} [options] Optional configuration
7479
* @param {number} [options.mode] File mode (default: 0o644)
7580
*/
@@ -113,7 +118,7 @@ class VirtualFile extends VirtualEntry {
113118
if (this[kContentProvider] !== null) {
114119
const result = this[kContentProvider]();
115120
if (result instanceof Promise) {
116-
throw new Error('Cannot use sync API with async content provider');
121+
throw new ERR_INVALID_STATE('cannot use sync API with async content provider');
117122
}
118123
const buffer = typeof result === 'string' ? Buffer.from(result) : result;
119124
// Update stats with actual size
@@ -147,52 +152,13 @@ class VirtualFile extends VirtualEntry {
147152
}
148153
}
149154

150-
/**
151-
* Scoped VFS interface passed to dynamic directory populate callbacks.
152-
* Allows adding files and directories relative to the parent directory.
153-
*/
154-
class ScopedVFS {
155-
/**
156-
* @param {VirtualDirectory} directory The parent directory
157-
* @param {function} addEntry Callback to add an entry
158-
*/
159-
constructor(directory, addEntry) {
160-
this._directory = directory;
161-
this._addEntry = addEntry;
162-
}
163-
164-
/**
165-
* Adds a file to this directory.
166-
* @param {string} name The file name (not a path)
167-
* @param {Buffer|string|function} content The file content or content provider
168-
* @param {object} [options] Optional configuration
169-
*/
170-
addFile(name, content, options) {
171-
const path = this._directory.path + '/' + name;
172-
const file = new VirtualFile(path, content, options);
173-
this._addEntry(name, file);
174-
}
175-
176-
/**
177-
* Adds a subdirectory to this directory.
178-
* @param {string} name The directory name (not a path)
179-
* @param {function} [populate] Optional callback to populate the directory
180-
* @param {object} [options] Optional configuration
181-
*/
182-
addDirectory(name, populate, options) {
183-
const path = this._directory.path + '/' + name;
184-
const dir = new VirtualDirectory(path, populate, options);
185-
this._addEntry(name, dir);
186-
}
187-
}
188-
189155
/**
190156
* Represents a virtual directory with static or dynamic entries.
191157
*/
192158
class VirtualDirectory extends VirtualEntry {
193159
/**
194160
* @param {string} path The absolute path of this directory
195-
* @param {function} [populate] Optional callback to populate directory contents
161+
* @param {Function} [populate] Optional callback to populate directory contents
196162
* @param {object} [options] Optional configuration
197163
* @param {number} [options.mode] Directory mode (default: 0o755)
198164
*/
@@ -233,7 +199,7 @@ class VirtualDirectory extends VirtualEntry {
233199
*/
234200
ensurePopulated() {
235201
if (!this[kPopulated] && this[kPopulate] !== null) {
236-
const scopedVfs = new ScopedVFS(this, (name, entry) => {
202+
const scopedVfs = createScopedVFS(this, (name, entry) => {
237203
this[kEntries].set(name, entry);
238204
});
239205
this[kPopulate](scopedVfs);
@@ -298,11 +264,33 @@ class VirtualDirectory extends VirtualEntry {
298264
}
299265
}
300266

267+
/**
268+
* Creates a scoped VFS interface for dynamic directory populate callbacks.
269+
* @param {VirtualDirectory} directory The parent directory
270+
* @param {Function} addEntry Callback to add an entry
271+
* @returns {object} Scoped VFS interface
272+
*/
273+
function createScopedVFS(directory, addEntry) {
274+
return {
275+
__proto__: null,
276+
addFile(name, content, options) {
277+
const path = directory.path + '/' + name;
278+
const file = new VirtualFile(path, content, options);
279+
addEntry(name, file);
280+
},
281+
addDirectory(name, populate, options) {
282+
const path = directory.path + '/' + name;
283+
const dir = new VirtualDirectory(path, populate, options);
284+
addEntry(name, dir);
285+
},
286+
};
287+
}
288+
301289
module.exports = {
302290
VirtualEntry,
303291
VirtualFile,
304292
VirtualDirectory,
305-
ScopedVFS,
293+
createScopedVFS,
306294
kContent,
307295
kContentProvider,
308296
kPopulate,

lib/internal/vfs/module_hooks.js

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
'use strict';
22

33
const {
4-
ArrayPrototypePush,
54
ArrayPrototypeIndexOf,
5+
ArrayPrototypePush,
66
ArrayPrototypeSplice,
77
StringPrototypeEndsWith,
88
StringPrototypeStartsWith,
99
} = primordials;
1010

1111
const { normalizePath } = require('internal/vfs/router');
12-
const { pathToFileURL, fileURLToPath } = require('internal/url');
12+
const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
1313
const { createENOENT } = require('internal/vfs/errors');
1414

1515
// Registry of active VFS instances
@@ -35,8 +35,6 @@ let originalPromisesReaddir = null;
3535
let originalPromisesLstat = null;
3636
// Track if hooks are installed
3737
let hooksInstalled = false;
38-
// ESM hooks instance (for potential cleanup)
39-
let esmHooksInstance = null;
4038

4139
/**
4240
* Registers a VFS instance to be checked for CJS module loading.
@@ -242,7 +240,7 @@ async function findVFSForReaddirAsync(dirname, options) {
242240
if (vfs.existsSync(normalized)) {
243241
try {
244242
const entries = await vfs.promises.readdir(normalized, options);
245-
return { vfs, entries };
243+
return { __proto__: null, vfs, entries };
246244
} catch (e) {
247245
if (vfs.isMounted) {
248246
throw e;
@@ -269,7 +267,7 @@ async function findVFSForLstatAsync(filename) {
269267
if (vfs.existsSync(normalized)) {
270268
try {
271269
const stats = await vfs.promises.lstat(normalized);
272-
return { vfs, stats };
270+
return { __proto__: null, vfs, stats };
273271
} catch (e) {
274272
if (vfs.isMounted) {
275273
throw e;
@@ -319,7 +317,7 @@ function urlToPath(urlOrPath) {
319317
* ESM resolve hook for VFS.
320318
* @param {string} specifier The module specifier
321319
* @param {object} context The resolve context
322-
* @param {function} nextResolve The next resolve function in the chain
320+
* @param {Function} nextResolve The next resolve function in the chain
323321
* @returns {object} The resolve result
324322
*/
325323
function vfsResolveHook(specifier, context, nextResolve) {
@@ -380,7 +378,7 @@ function vfsResolveHook(specifier, context, nextResolve) {
380378
* ESM load hook for VFS.
381379
* @param {string} url The module URL
382380
* @param {object} context The load context
383-
* @param {function} nextLoad The next load function in the chain
381+
* @param {Function} nextLoad The next load function in the chain
384382
* @returns {object} The load result
385383
*/
386384
function vfsLoadHook(url, context, nextLoad) {
@@ -450,7 +448,7 @@ function installHooks() {
450448
// Override Module._stat
451449
// This uses the setter which emits an experimental warning, but that's acceptable
452450
// for now since VFS integration IS experimental.
453-
Module._stat = function vfsStat(filename) {
451+
Module._stat = function _stat(filename) {
454452
const vfsResult = findVFSForStat(filename);
455453
if (vfsResult !== null) {
456454
return vfsResult.result;
@@ -460,7 +458,7 @@ function installHooks() {
460458

461459
// Override fs.readFileSync
462460
// We need to be careful to only intercept when VFS should handle the path
463-
fs.readFileSync = function vfsReadFileSync(path, options) {
461+
fs.readFileSync = function readFileSync(path, options) {
464462
// Only intercept string paths (not file descriptors)
465463
if (typeof path === 'string' || path instanceof URL) {
466464
const pathStr = typeof path === 'string' ? path : path.pathname;
@@ -473,7 +471,7 @@ function installHooks() {
473471
};
474472

475473
// Override fs.realpathSync
476-
fs.realpathSync = function vfsRealpathSync(path, options) {
474+
fs.realpathSync = function realpathSync(path, options) {
477475
if (typeof path === 'string' || path instanceof URL) {
478476
const pathStr = typeof path === 'string' ? path : path.pathname;
479477
const vfsResult = findVFSForRealpath(pathStr);
@@ -487,7 +485,7 @@ function installHooks() {
487485
fs.realpathSync.native = originalRealpathSync.native;
488486

489487
// Override fs.lstatSync
490-
fs.lstatSync = function vfsLstatSync(path, options) {
488+
fs.lstatSync = function lstatSync(path, options) {
491489
if (typeof path === 'string' || path instanceof URL) {
492490
const pathStr = typeof path === 'string' ? path : path.pathname;
493491
const vfsResult = findVFSForFsStat(pathStr);
@@ -499,7 +497,7 @@ function installHooks() {
499497
};
500498

501499
// Override fs.statSync
502-
fs.statSync = function vfsStatSync(path, options) {
500+
fs.statSync = function statSync(path, options) {
503501
if (typeof path === 'string' || path instanceof URL) {
504502
const pathStr = typeof path === 'string' ? path : path.pathname;
505503
const vfsResult = findVFSForFsStat(pathStr);
@@ -512,7 +510,7 @@ function installHooks() {
512510

513511
// Override fs.readdirSync (needed for glob support)
514512
originalReaddirSync = fs.readdirSync;
515-
fs.readdirSync = function vfsReaddirSync(path, options) {
513+
fs.readdirSync = function readdirSync(path, options) {
516514
if (typeof path === 'string' || path instanceof URL) {
517515
const pathStr = typeof path === 'string' ? path : path.pathname;
518516
const vfsResult = findVFSForReaddir(pathStr, options);
@@ -525,7 +523,7 @@ function installHooks() {
525523

526524
// Override fs.existsSync
527525
originalExistsSync = fs.existsSync;
528-
fs.existsSync = function vfsExistsSync(path) {
526+
fs.existsSync = function existsSync(path) {
529527
if (typeof path === 'string' || path instanceof URL) {
530528
const pathStr = typeof path === 'string' ? path : path.pathname;
531529
const vfsResult = findVFSForExists(pathStr);
@@ -541,7 +539,7 @@ function installHooks() {
541539

542540
// Override fs/promises.readdir (needed for async glob support)
543541
originalPromisesReaddir = fsPromises.readdir;
544-
fsPromises.readdir = async function vfsPromisesReaddir(path, options) {
542+
fsPromises.readdir = async function readdir(path, options) {
545543
if (typeof path === 'string' || path instanceof URL) {
546544
const pathStr = typeof path === 'string' ? path : path.pathname;
547545
const vfsResult = await findVFSForReaddirAsync(pathStr, options);
@@ -554,7 +552,7 @@ function installHooks() {
554552

555553
// Override fs/promises.lstat (needed for async glob support)
556554
originalPromisesLstat = fsPromises.lstat;
557-
fsPromises.lstat = async function vfsPromisesLstat(path, options) {
555+
fsPromises.lstat = async function lstat(path, options) {
558556
if (typeof path === 'string' || path instanceof URL) {
559557
const pathStr = typeof path === 'string' ? path : path.pathname;
560558
const vfsResult = await findVFSForLstatAsync(pathStr);
@@ -566,7 +564,7 @@ function installHooks() {
566564
};
567565

568566
// Register ESM hooks using Module.registerHooks
569-
esmHooksInstance = Module.registerHooks({
567+
Module.registerHooks({
570568
resolve: vfsResolveHook,
571569
load: vfsLoadHook,
572570
});

lib/internal/vfs/router.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use strict';
22

33
const {
4-
StringPrototypeStartsWith,
5-
StringPrototypeSlice,
64
StringPrototypeEndsWith,
5+
StringPrototypeSlice,
6+
StringPrototypeStartsWith,
77
} = primordials;
88

99
const path = require('path');

0 commit comments

Comments
 (0)