Skip to content

Commit

Permalink
fix: ignore . and .. from readdir
Browse files Browse the repository at this point in the history
refactor: add the appropriate modifiers to properties & getters & setters.

Co-authored-by: Gareth Jones <Jones258@Gmail.com>
  • Loading branch information
vangie and G-Rath committed Apr 17, 2023
1 parent f0049e3 commit 48a6cad
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 53 deletions.
32 changes: 20 additions & 12 deletions src/__tests__/node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,31 +69,39 @@ describe('node.ts', () => {
expect(node.perm).toBe(0o600);
expect(node.isFile()).toBe(true);
});
describe.each([['uid'], ['gid'], ['atime'], ['mtime'], ['perm'], ['nlink']])('ctime changes', field => {
it(`set ${field}`, () => {
describe.each(['uid', 'gid', 'atime', 'mtime', 'perm', 'nlink'])('when %s changes', field => {
it('updates the property and the ctime', () => {
const node = new Node(1);
const oldCtime = node.ctime;
node[field] = 1;
const newCtime = node.ctime;
expect(newCtime !== oldCtime).toBeTruthy();
expect(newCtime).not.toBe(oldCtime);
expect(node[field]).toBe(1);
});
});

describe.each([
['getString', []],
['getBuffer', []],
['read', [Buffer.alloc(0)]],
])('atime changes', (method, args) => {
it(`${method}()`, () => {
describe.each(['getString', 'getBuffer'])('when node.%s is called', method => {
it(`updates the atime and ctime`, () => {
const node = new Node(1);
const oldAtime = node.atime;
const oldCtime = node.ctime;
node[method](...args);
node[method]();
const newAtime = node.atime;
const newCtime = node.ctime;
expect(newAtime !== oldAtime).toBeTruthy();
expect(newCtime !== oldCtime).toBeTruthy();
expect(newAtime).not.toBe(oldAtime);
expect(newCtime).not.toBe(oldCtime);
});
});

it('when node.read is called, updates the atime and ctime', () => {
const node = new Node(1);
const oldAtime = node.atime;
const oldCtime = node.ctime;
node.read(Buffer.alloc(0));
const newAtime = node.atime;
const newCtime = node.ctime;
expect(newAtime).not.toBe(oldAtime);
expect(newCtime).not.toBe(oldCtime);
});
});
});
2 changes: 1 addition & 1 deletion src/__tests__/promises.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ describe('Promises API', () => {
'/foo/baz': 'baz',
});
it('Read an existing directory', async () => {
expect(await promises.readdir('/foo')).toEqual(['.', '..', 'bar', 'baz']);
expect(await promises.readdir('/foo')).toEqual(['bar', 'baz']);
});
it('Reject when directory does not exist', () => {
return expect(promises.readdir('/bar')).rejects.toBeInstanceOf(Error);
Expand Down
20 changes: 10 additions & 10 deletions src/__tests__/volume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ describe('volume', () => {
const stat = vol.statSync('/dir');

expect(stat.isDirectory()).toBe(true);
expect(vol.readdirSync('/dir')).toEqual(['.', '..']);
expect(vol.readdirSync('/dir')).toEqual([]);
});
});

Expand Down Expand Up @@ -338,7 +338,7 @@ describe('volume', () => {
expect(vol.root.getChild('test.txt')).toBeInstanceOf(Link);
expect(typeof fd).toBe('number');
expect(fd).toBeGreaterThan(0);
expect(oldMtime !== newMtime).toBeTruthy();
expect(oldMtime).not.toBe(newMtime);
});
it('Error on file not found', () => {
try {
Expand Down Expand Up @@ -873,20 +873,20 @@ describe('volume', () => {
vol.writeFileSync('/1.js', '123');
vol.writeFileSync('/2.js', '123');
const list = vol.readdirSync('/');
expect(list.length).toBe(4);
expect(list).toEqual(['.', '..', '1.js', '2.js']);
expect(list.length).toBe(2);
expect(list).toEqual(['1.js', '2.js']);
});
it('Returns a Dirent list', () => {
const vol = new Volume();
vol.writeFileSync('/1', '123');
vol.mkdirSync('/2');
const list = vol.readdirSync('/', { withFileTypes: true });
expect(list.length).toBe(4);
expect(list[2]).toBeInstanceOf(Dirent);
const dirent0 = list[2] as Dirent;
expect(list.length).toBe(2);
expect(list[0]).toBeInstanceOf(Dirent);
const dirent0 = list[0] as Dirent;
expect(dirent0.name).toBe('1');
expect(dirent0.isFile()).toBe(true);
const dirent1 = list[3] as Dirent;
const dirent1 = list[1] as Dirent;
expect(dirent1.name).toBe('2');
expect(dirent1.isDirectory()).toBe(true);
});
Expand Down Expand Up @@ -1000,8 +1000,8 @@ describe('volume', () => {
const child = tryGetChild(vol.root, 'test');
expect(child).toBeInstanceOf(Link);
expect(child.getNode().isDirectory()).toBe(true);
expect(oldMtime !== newMtime).toBeTruthy();
expect(newNlink === oldNlink + 1).toBeTruthy;
expect(oldMtime).not.toBe(newMtime);
expect(newNlink).toBe(oldNlink + 1);
});
it('Create 2 levels deep folders', () => {
const vol = new Volume();
Expand Down
10 changes: 5 additions & 5 deletions src/__tests__/volume/readdirSync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe('readdirSync()', () => {
});
const dirs = vol.readdirSync('/');

expect(dirs).toEqual(['.', '..', 'foo']);
expect(dirs).toEqual(['foo']);
});

it('returns multiple directories', () => {
Expand All @@ -20,14 +20,14 @@ describe('readdirSync()', () => {

(dirs as any).sort();

expect(dirs).toEqual(['.', '..', 'ab', 'foo', 'tro']);
expect(dirs).toEqual(['ab', 'foo', 'tro']);
});

it('returns empty array when dir empty', () => {
const vol = create({});
const dirs = vol.readdirSync('/');

expect(dirs).toEqual(['.', '..']);
expect(dirs).toEqual([]);
});

it('respects symlinks', () => {
Expand All @@ -43,7 +43,7 @@ describe('readdirSync()', () => {

(dirs as any).sort();

expect(dirs).toEqual(['.', '..', 'a', 'aa']);
expect(dirs).toEqual(['a', 'aa']);
});

it('respects recursive symlinks', () => {
Expand All @@ -53,6 +53,6 @@ describe('readdirSync()', () => {

const dirs = vol.readdirSync('/foo');

expect(dirs).toEqual(['.', '..', 'foo']);
expect(dirs).toEqual(['foo']);
});
});
91 changes: 71 additions & 20 deletions src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,42 +19,93 @@ export class Node extends EventEmitter {
ino: number;

// User ID and group ID.
uid: number = getuid();
gid: number = getgid();
private _uid: number = getuid();
private _gid: number = getgid();

atime = new Date();
mtime = new Date();
ctime = new Date();
private _atime = new Date();
private _mtime = new Date();
private _ctime = new Date();

// data: string = '';
buf: Buffer;

perm = 0o666; // Permissions `chmod`, `fchmod`
private _perm = 0o666; // Permissions `chmod`, `fchmod`

mode = S_IFREG; // S_IFDIR, S_IFREG, etc.. (file by default?)

// Number of hard links pointing at this Node.
nlink = 1;
private _nlink = 1;

// Steps to another node, if this node is a symlink.
symlink: string[];

constructor(ino: number, perm: number = 0o666) {
super();
this.perm = perm;
this._perm = perm;
this.mode |= perm;
this.ino = ino;
}

return new Proxy(this, {
set(target, name, value): boolean {
const metadataProps = ['uid', 'gid', 'atime', 'mtime', 'perm', 'nlink'];
if (metadataProps.includes(String(name))) {
target.ctime = new Date();
}
target[name] = value;
return true;
},
});
public set ctime(ctime: Date) {
this._ctime = ctime;
}

public get ctime(): Date {
return this._ctime;
}

public set uid(uid: number) {
this._uid = uid;
this.ctime = new Date();
}

public get uid(): number {
return this._uid;
}

public set gid(gid: number) {
this._gid = gid;
this.ctime = new Date();
}

public get gid(): number {
return this._gid;
}

public set atime(atime: Date) {
this._atime = atime;
this.ctime = new Date();
}

public get atime(): Date {
return this._atime;
}

public set mtime(mtime: Date) {
this._mtime = mtime;
this.ctime = new Date();
}

public get mtime(): Date {
return this._mtime;
}

public set perm(perm: number) {
this._perm = perm;
this.ctime = new Date();
}

public get perm(): number {
return this._perm;
}

public set nlink(nlink: number) {
this._nlink = nlink;
this.ctime = new Date();
}

public get nlink(): number {
return this._nlink;
}

getString(encoding = 'utf8'): string {
Expand Down Expand Up @@ -324,9 +375,9 @@ export class Link extends EventEmitter {
if (node.isDirectory()) {
link.children['..'] = this;
this.getNode().nlink++;
this.getNode().mtime = new Date();
}

this.getNode().mtime = new Date();
this.emit('child:add', link, this);

return link;
Expand All @@ -337,11 +388,11 @@ export class Link extends EventEmitter {
if (node.isDirectory()) {
delete link.children['..'];
this.getNode().nlink--;
this.getNode().mtime = new Date();
}
delete this.children[link.getName()];
this.length--;

this.getNode().mtime = new Date();
this.emit('child:delete', link, this);
}

Expand Down
13 changes: 8 additions & 5 deletions src/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1744,7 +1744,7 @@ export class Volume {
for (const name in link.children) {
const child = link.getChild(name);

if (!child) {
if (!child || name === '.' || name === '..') {
continue;
}

Expand All @@ -1761,6 +1761,9 @@ export class Volume {

const list: TDataOut[] = [];
for (const name in link.children) {
if (name === '.' || name === '..') {
continue;
}
list.push(strToEncoding(name, options.encoding));
}

Expand Down Expand Up @@ -2801,8 +2804,8 @@ export class FSWatcher extends EventEmitter {
};

// children nodes changed
Object.values(link.children).forEach(childLink => {
if (childLink) {
Object.entries(link.children).forEach(([name, childLink]) => {
if (childLink && name !== '.' && name !== '..') {
watchLinkNodeChanged(childLink);
}
});
Expand All @@ -2817,8 +2820,8 @@ export class FSWatcher extends EventEmitter {
});

if (recursive) {
Object.values(link.children).forEach(childLink => {
if (childLink) {
Object.entries(link.children).forEach(([name, childLink]) => {
if (childLink && name !== '.' && name !== '..') {
watchLinkChildrenChanged(childLink);
}
});
Expand Down

0 comments on commit 48a6cad

Please sign in to comment.