Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support throwIfNoEntry in StatOptions #782

Merged
merged 1 commit into from Nov 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/__tests__/promises.test.ts
Expand Up @@ -149,6 +149,40 @@ describe('Promises API', () => {
return expect(fileHandle.stat()).rejects.toBeInstanceOf(Error);
});
});
describe('.stat(path, options)', () => {
const { promises: vol } = new Volume();

it('Does not reject when entry does not exist if throwIfNoEntry is false', async () => {
const stat = await vol.stat('/no', { throwIfNoEntry: false });
expect(stat).toBeUndefined();
});
it('Rejects when entry does not exist if throwIfNoEntry is true', async () => {
await expect(vol.stat('/foo', { throwIfNoEntry: true })).rejects.toBeInstanceOf(Error);
});
it('Rejects when entry does not exist if throwIfNoEntry is not specified', async () => {
await expect(vol.stat('/foo')).rejects.toBeInstanceOf(Error);
});
it('Rejects when entry does not exist if throwIfNoEntry is explicitly undefined', async () => {
await expect(vol.stat('/foo', { throwIfNoEntry: undefined })).rejects.toBeInstanceOf(Error);
});
});
describe('.lstat(path, options)', () => {
const { promises: vol } = new Volume();

it('Does not throw when entry does not exist if throwIfNoEntry is false', async () => {
const stat = await vol.lstat('/foo', { throwIfNoEntry: false });
expect(stat).toBeUndefined();
});
it('Rejects when entry does not exist if throwIfNoEntry is true', async () => {
await expect(vol.lstat('/foo', { throwIfNoEntry: true })).rejects.toBeInstanceOf(Error);
});
it('Rejects when entry does not exist if throwIfNoEntry is not specified', async () => {
await expect(vol.lstat('/foo')).rejects.toBeInstanceOf(Error);
});
it('Rejects when entry does not exist if throwIfNoEntry is explicitly undefined', async () => {
await expect(vol.lstat('/foo', { throwIfNoEntry: undefined })).rejects.toBeInstanceOf(Error);
});
});
describe('truncate([len])', () => {
const vol = new Volume();
const { promises } = vol;
Expand Down
34 changes: 34 additions & 0 deletions src/__tests__/volume.test.ts
Expand Up @@ -695,6 +695,40 @@ describe('volume', () => {
});
});
});
describe('.statSync(path, options)', () => {
const vol = new Volume();

it('Does not throw when entry does not exist if throwIfNoEntry is false', () => {
const stat = vol.statSync('/foo', { throwIfNoEntry: false });
expect(stat).toBeUndefined();
});
it('Throws when entry does not exist if throwIfNoEntry is true', () => {
expect(() => vol.statSync('/foo', { throwIfNoEntry: true })).toThrow();
});
it('Throws when entry does not exist if throwIfNoEntry is not specified', () => {
expect(() => vol.statSync('/foo')).toThrow();
});
it('Throws when entry does not exist if throwIfNoEntry is explicitly undefined', () => {
expect(() => vol.statSync('/foo', { throwIfNoEntry: undefined })).toThrow();
});
});
describe('.lstatSync(path, options)', () => {
const vol = new Volume();

it('Does not throw when entry does not exist if throwIfNoEntry is false', () => {
const stat = vol.lstatSync('/foo', { throwIfNoEntry: false });
expect(stat).toBeUndefined();
});
it('Throws when entry does not exist if throwIfNoEntry is true', () => {
expect(() => vol.lstatSync('/foo', { throwIfNoEntry: true })).toThrow();
});
it('Throws when entry does not exist if throwIfNoEntry is not specified', () => {
expect(() => vol.lstatSync('/foo')).toThrow();
});
it('Throws when entry does not exist if throwIfNoEntry is explicitly undefined', () => {
expect(() => vol.lstatSync('/foo', { throwIfNoEntry: undefined })).toThrow();
});
});
describe('.lstatSync(path)', () => {
const vol = new Volume();
const dojo = vol.root.createChild('dojo.js');
Expand Down
3 changes: 2 additions & 1 deletion src/promises.ts
Expand Up @@ -14,6 +14,7 @@ import {
IWriteFileOptions,
IStatOptions,
IRmOptions,
IFStatOptions,
} from './volume';
import Stats from './Stats';
import Dirent from './Dirent';
Expand Down Expand Up @@ -134,7 +135,7 @@ export class FileHandle implements IFileHandle {
return promisify(this.vol, 'readFile')(this.fd, options);
}

stat(options?: IStatOptions): Promise<Stats> {
stat(options?: IFStatOptions): Promise<Stats> {
return promisify(this.vol, 'fstat')(this.fd, options);
}

Expand Down
101 changes: 68 additions & 33 deletions src/volume.ts
Expand Up @@ -365,10 +365,17 @@ const readdirDefaults: IReaddirOptions = {
const getReaddirOptions = optsGenerator<IReaddirOptions>(readdirDefaults);
const getReaddirOptsAndCb = optsAndCbGenerator<IReaddirOptions, TDataOut[] | Dirent[]>(getReaddirOptions);

// Options for `fs.fstat`, `fs.fstatSync`, `fs.lstat`, `fs.lstatSync`, `fs.stat`, and `fs.statSync`
// Options for `fs.lstat`, `fs.lstatSync`, `fs.stat`, and `fs.statSync`
export interface IStatOptions {
bigint?: boolean;
throwIfNoEntry?: boolean;
}

// Options for `fs.fstat`, fs.fstatSync
export interface IFStatOptions {
bigint?: boolean;
mausworks marked this conversation as resolved.
Show resolved Hide resolved
}

const statDefaults: IStatOptions = {
bigint: false,
};
Expand Down Expand Up @@ -1499,50 +1506,78 @@ export class Volume {
this.wrapAsync(this.realpathBase, [pathFilename, opts.encoding], callback);
}

private lstatBase(filename: string, bigint: false): Stats<number>;
private lstatBase(filename: string, bigint: true): Stats<bigint>;
private lstatBase(filename: string, bigint: boolean = false): Stats {
private lstatBase(filename: string, bigint: false, throwIfNoEntry: true): Stats<number>;
private lstatBase(filename: string, bigint: true, throwIfNoEntry: true): Stats<bigint>;
private lstatBase(filename: string, bigint: true, throwIfNoEntry: false): Stats<bigint> | undefined;
private lstatBase(filename: string, bigint: false, throwIfNoEntry: false): Stats<number> | undefined;
private lstatBase(filename: string, bigint = false, throwIfNoEntry = false): Stats | undefined {
const link = this.getLink(filenameToSteps(filename));
if (!link) throw createError(ENOENT, 'lstat', filename);
return Stats.build(link.getNode(), bigint);

if (link) {
return Stats.build(link.getNode(), bigint);
} else if (!throwIfNoEntry) {
return undefined;
} else {
throw createError(ENOENT, 'lstat', filename);
}
}

lstatSync(path: PathLike): Stats<number>;
lstatSync(path: PathLike, options: { bigint: false }): Stats<number>;
lstatSync(path: PathLike, options: { bigint: true }): Stats<bigint>;
lstatSync(path: PathLike, options?: IStatOptions): Stats {
return this.lstatBase(pathToFilename(path), getStatOptions(options).bigint as any);
lstatSync(path: PathLike, options: { throwIfNoEntry?: true | undefined }): Stats<number>;
lstatSync(path: PathLike, options: { bigint: false; throwIfNoEntry?: true | undefined }): Stats<number>;
lstatSync(path: PathLike, options: { bigint: true; throwIfNoEntry?: true | undefined }): Stats<bigint>;
lstatSync(path: PathLike, options: { throwIfNoEntry: false }): Stats<number> | undefined;
lstatSync(path: PathLike, options: { bigint: false; throwIfNoEntry: false }): Stats<number> | undefined;
lstatSync(path: PathLike, options: { bigint: true; throwIfNoEntry: false }): Stats<bigint> | undefined;
lstatSync(path: PathLike, options?: IStatOptions): Stats | undefined {
Comment on lines 1525 to +1532
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like these type overloads are slowly getting out of control.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed! 😅

Should probably be revised if a future version of NodeJS ships with new options.

const { throwIfNoEntry = true, bigint = false } = getStatOptions(options);

return this.lstatBase(pathToFilename(path), bigint as any, throwIfNoEntry as any);
}

lstat(path: PathLike, callback: TCallback<Stats>);
lstat(path: PathLike, options: IStatOptions, callback: TCallback<Stats>);
lstat(path: PathLike, a: TCallback<Stats> | IStatOptions, b?: TCallback<Stats>) {
const [opts, callback] = getStatOptsAndCb(a, b);
this.wrapAsync(this.lstatBase, [pathToFilename(path), opts.bigint], callback);
lstat(path: PathLike, callback: TCallback<Stats>): void;
lstat(path: PathLike, options: IStatOptions, callback: TCallback<Stats>): void;
lstat(path: PathLike, a: TCallback<Stats> | IStatOptions, b?: TCallback<Stats>): void {
const [{ throwIfNoEntry = true, bigint = false }, callback] = getStatOptsAndCb(a, b);
this.wrapAsync(this.lstatBase, [pathToFilename(path), bigint, throwIfNoEntry], callback);
}

private statBase(filename: string): Stats<number>;
private statBase(filename: string, bigint: false): Stats<number>;
private statBase(filename: string, bigint: true): Stats<bigint>;
private statBase(filename: string, bigint: boolean = false): Stats {
private statBase(filename: string, bigint: false, throwIfNoEntry: true): Stats<number>;
private statBase(filename: string, bigint: true, throwIfNoEntry: true): Stats<bigint>;
private statBase(filename: string, bigint: true, throwIfNoEntry: false): Stats<bigint> | undefined;
private statBase(filename: string, bigint: false, throwIfNoEntry: false): Stats<number> | undefined;
private statBase(filename: string, bigint = false, throwIfNoEntry = true): Stats | undefined {
const link = this.getResolvedLink(filenameToSteps(filename));
if (!link) throw createError(ENOENT, 'stat', filename);

return Stats.build(link.getNode(), bigint);
if (link) {
return Stats.build(link.getNode(), bigint);
} else if (!throwIfNoEntry) {
return undefined;
} else {
throw createError(ENOENT, 'stat', filename);
}
}

statSync(path: PathLike): Stats<number>;
statSync(path: PathLike, options: { bigint: false }): Stats<number>;
statSync(path: PathLike, options: { bigint: true }): Stats<bigint>;
statSync(path: PathLike, options?: IStatOptions): Stats {
return this.statBase(pathToFilename(path), getStatOptions(options).bigint as any);
statSync(path: PathLike, options: { throwIfNoEntry?: true }): Stats<number>;
statSync(path: PathLike, options: { throwIfNoEntry: false }): Stats<number> | undefined;
statSync(path: PathLike, options: { bigint: false; throwIfNoEntry?: true }): Stats<number>;
statSync(path: PathLike, options: { bigint: true; throwIfNoEntry?: true }): Stats<bigint>;
statSync(path: PathLike, options: { bigint: false; throwIfNoEntry: false }): Stats<number> | undefined;
statSync(path: PathLike, options: { bigint: true; throwIfNoEntry: false }): Stats<bigint> | undefined;
statSync(path: PathLike, options?: IStatOptions): Stats | undefined {
const { bigint = true, throwIfNoEntry = true } = getStatOptions(options);

return this.statBase(pathToFilename(path), bigint as any, throwIfNoEntry as any);
}

stat(path: PathLike, callback: TCallback<Stats>);
stat(path: PathLike, options: IStatOptions, callback: TCallback<Stats>);
stat(path: PathLike, a: TCallback<Stats> | IStatOptions, b?: TCallback<Stats>) {
const [opts, callback] = getStatOptsAndCb(a, b);
this.wrapAsync(this.statBase, [pathToFilename(path), opts.bigint], callback);
stat(path: PathLike, callback: TCallback<Stats>): void;
stat(path: PathLike, options: IStatOptions, callback: TCallback<Stats>): void;
stat(path: PathLike, a: TCallback<Stats> | IStatOptions, b?: TCallback<Stats>): void {
const [{ bigint = false, throwIfNoEntry = true }, callback] = getStatOptsAndCb(a, b);

this.wrapAsync(this.statBase, [pathToFilename(path), bigint, throwIfNoEntry], callback);
}

private fstatBase(fd: number): Stats<number>;
Expand All @@ -1557,13 +1592,13 @@ export class Volume {
fstatSync(fd: number): Stats<number>;
fstatSync(fd: number, options: { bigint: false }): Stats<number>;
fstatSync(fd: number, options: { bigint: true }): Stats<bigint>;
fstatSync(fd: number, options?: IStatOptions): Stats {
fstatSync(fd: number, options?: IFStatOptions): Stats {
return this.fstatBase(fd, getStatOptions(options).bigint as any);
}

fstat(fd: number, callback: TCallback<Stats>);
fstat(fd: number, options: IStatOptions, callback: TCallback<Stats>);
fstat(fd: number, a: TCallback<Stats> | IStatOptions, b?: TCallback<Stats>) {
fstat(fd: number, callback: TCallback<Stats>): void;
fstat(fd: number, options: IFStatOptions, callback: TCallback<Stats>): void;
fstat(fd: number, a: TCallback<Stats> | IFStatOptions, b?: TCallback<Stats>): void {
const [opts, callback] = getStatOptsAndCb(a, b);
this.wrapAsync(this.fstatBase, [fd, opts.bigint], callback);
}
Expand Down