Repository: streamich/memfs
Pull request #486 (2019-12-24 16:10:41)

fix(rmdir): support recursive option
Hey there,

So another PR for today :)
This one adds the `recursive` capabilities of `rmdir` and `rmdirSync` https://nodejs.org/api/fs.html#fs_fs_rmdir_path_options_callback

This one does include tests, but only for rmdirSync (as I could not find base tests for `rmdir`). As in the previous PR, I'd be happy to add them in a separate PR if desired.

Thanks again!


Patch:
diff --git a/src/volume.ts b/src/volume.ts
index 2fd63950..c734866d 100644
--- a/src/volume.ts
+++ b/src/volume.ts
@@ -342,6 +342,17 @@ const getMkdirOptions = (options): IMkdirOptions => {
   return extend({}, mkdirDefaults, options);
 };
 
+// Options for `fs.rmdir` and `fs.rmdirSync`
+export interface IRmdirOptions {
+  recursive?: boolean;
+}
+const rmdirDefaults: IRmdirOptions = {
+  recursive: false,
+};
+const getRmdirOptions = (options): IRmdirOptions => {
+  return extend({}, rmdirDefaults, options);
+};
+
 // Options for `fs.readdir` and `fs.readdirSync`
 export interface IReaddirOptions extends IOptions {
   withFileTypes?: boolean;
@@ -1901,21 +1912,26 @@ export class Volume {
     this.wrapAsync(this.mkdtempBase, [prefix, encoding], callback);
   }
 
-  private rmdirBase(filename: string) {
+  private rmdirBase(filename: string, options?: IRmdirOptions) {
+    const opts = getRmdirOptions(options);
     const link = this.getLinkAsDirOrThrow(filename, 'rmdir');
 
     // Check directory is empty.
-    if (link.length) throw createError(ENOTEMPTY, 'rmdir', filename);
+    if (link.length && !opts.recursive) throw createError(ENOTEMPTY, 'rmdir', filename);
 
     this.deleteLink(link);
   }
 
-  rmdirSync(path: TFilePath) {
-    this.rmdirBase(pathToFilename(path));
+  rmdirSync(path: TFilePath, options?: IRmdirOptions) {
+    this.rmdirBase(pathToFilename(path), options);
   }
 
-  rmdir(path: TFilePath, callback: TCallback<void>) {
-    this.wrapAsync(this.rmdirBase, [pathToFilename(path)], callback);
+  rmdir(path: TFilePath, callback: TCallback<void>);
+  rmdir(path: TFilePath, options: IRmdirOptions, callback: TCallback<void>);
+  rmdir(path: TFilePath, a: TCallback<void> | IRmdirOptions, b?: TCallback<void>) {
+    const opts: IRmdirOptions = getRmdirOptions(a);
+    const callback: TCallback<void> = validateCallback(typeof a === 'function' ? a : b);
+    this.wrapAsync(this.rmdirBase, [pathToFilename(path), opts], callback);
   }
 
   private fchmodBase(fd: number, modeNum: number) {


Test patch:
diff --git a/src/__tests__/volume.test.ts b/src/__tests__/volume.test.ts
index cc7fd074..3ff568c4 100644
--- a/src/__tests__/volume.test.ts
+++ b/src/__tests__/volume.test.ts
@@ -954,9 +954,23 @@ describe('volume', () => {
         vol.rmdirSync('/dir');
         expect(!!vol.root.getChild('dir')).toBe(false);
       });
+      it('Remove dir /dir1/dir2/dir3 recursively', () => {
+        const vol = new Volume();
+        vol.mkdirSync('/dir1/dir2/dir3', { recursive: true });
+        vol.rmdirSync('/dir1', { recursive: true });
+        expect(!!vol.root.getChild('dir1')).toBe(false);
+      });
     });
     describe('.rmdir(path, callback)', () => {
       xit('Remove single dir', () => {});
+      it('Async remove dir /dir1/dir2/dir3 recursively', done => {
+        const vol = new Volume();
+        vol.mkdirSync('/dir1/dir2/dir3', { recursive: true });
+        vol.rmdir('/dir1', { recursive: true }, () => {
+          expect(!!vol.root.getChild('dir1')).toBe(false);
+          done();
+        });
+      });
     });
     describe('.watchFile(path[, options], listener)', () => {
       it('Calls listener on .writeFile', done => {