Skip to content

Commit

Permalink
feat: add bigint option support
Browse files Browse the repository at this point in the history
  • Loading branch information
pizzafroide committed Nov 12, 2018
1 parent 7c29b93 commit 00a017e
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 52 deletions.
3 changes: 3 additions & 0 deletions docs/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ available.

It uses `Promise` when available and throws when `promises` property is
accessed in an environment that does not support this ES2015 feature.

It uses `BigInt` when available and throws when `bigint` option is used
in an environment that does not support this ESNext feature.
58 changes: 34 additions & 24 deletions src/Stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,72 @@ import { constants } from './constants';

const { S_IFMT, S_IFDIR, S_IFREG, S_IFBLK, S_IFCHR, S_IFLNK, S_IFIFO, S_IFSOCK } = constants;

export type TStatNumber = number | BigInt;

/**
* Statistics about a file/directory, like `fs.Stats`.
*/
export class Stats {
static build(node: Node) {
static build(node: Node, bigint: boolean = false) {
const stats = new Stats();
const { uid, gid, atime, mtime, ctime } = node;

const getStatNumber = !bigint
? number => number
: typeof BigInt === 'function'
? BigInt
: () => {
throw new Error('BigInt is not supported in this environment.');
};

// Copy all values on Stats from Node, so that if Node values
// change, values on Stats would still be the old ones,
// just like in Node fs.

stats.uid = uid;
stats.gid = gid;
stats.uid = getStatNumber(uid);
stats.gid = getStatNumber(gid);

stats.atime = atime;
stats.mtime = mtime;
stats.ctime = ctime;
stats.birthtime = ctime;

stats.atimeMs = atime.getTime();
stats.mtimeMs = mtime.getTime();
const ctimeMs = ctime.getTime();
stats.atimeMs = getStatNumber(atime.getTime());
stats.mtimeMs = getStatNumber(mtime.getTime());
const ctimeMs = getStatNumber(ctime.getTime());
stats.ctimeMs = ctimeMs;
stats.birthtimeMs = ctimeMs;

stats.size = node.getSize();
stats.mode = node.mode;
stats.ino = node.ino;
stats.nlink = node.nlink;
stats.size = getStatNumber(node.getSize());
stats.mode = getStatNumber(node.mode);
stats.ino = getStatNumber(node.ino);
stats.nlink = getStatNumber(node.nlink);

return stats;
}

uid: number = 0;
gid: number = 0;
uid: TStatNumber = 0;
gid: TStatNumber = 0;

rdev: number = 0;
blksize: number = 4096;
ino: number = 0;
size: number = 0;
blocks: number = 1;
rdev: TStatNumber = 0;
blksize: TStatNumber = 4096;
ino: TStatNumber = 0;
size: TStatNumber = 0;
blocks: TStatNumber = 1;

atime: Date = null;
mtime: Date = null;
ctime: Date = null;
birthtime: Date = null;

atimeMs: number = 0.0;
mtimeMs: number = 0.0;
ctimeMs: number = 0.0;
birthtimeMs: number = 0.0;
atimeMs: TStatNumber = 0.0;
mtimeMs: TStatNumber = 0.0;
ctimeMs: TStatNumber = 0.0;
birthtimeMs: TStatNumber = 0.0;

dev: number = 0;
mode: number = 0;
nlink: number = 0;
dev: TStatNumber = 0;
mode: TStatNumber = 0;
nlink: TStatNumber = 0;

private _checkModeProperty(property: number): boolean {
return (this.mode & S_IFMT) === property;
Expand Down
29 changes: 29 additions & 0 deletions src/__tests__/volume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import Stats from '../Stats';
import Dirent from '../Dirent';
import { Volume, filenameToSteps, StatWatcher } from '../volume';

// I did not find how to include '../bigint.d.ts' here!
type BigInt = number;
declare const BigInt: typeof Number;

describe('volume', () => {
describe('filenameToSteps(filename): string[]', () => {
it('/ -> []', () => {
Expand Down Expand Up @@ -665,6 +669,14 @@ describe('volume', () => {
expect(stats.isFile()).toBe(true);
expect(stats.isDirectory()).toBe(false);
});
it('Returns file stats using BigInt', () => {
if (typeof BigInt === 'function') {
const stats = vol.lstatSync('/dojo.js', { bigint: true });
expect(typeof stats.ino).toBe('bigint');
} else {
expect(() => vol.lstatSync('/dojo.js', { bigint: true })).toThrowError();
}
});
it('Stats on symlink returns results about the symlink', () => {
vol.symlinkSync('/dojo.js', '/link.js');
const stats = vol.lstatSync('/link.js');
Expand All @@ -688,6 +700,14 @@ describe('volume', () => {
expect(stats.isFile()).toBe(true);
expect(stats.isDirectory()).toBe(false);
});
it('Returns file stats using BigInt', () => {
if (typeof BigInt === 'function') {
const stats = vol.statSync('/dojo.js', { bigint: true });
expect(typeof stats.ino).toBe('bigint');
} else {
expect(() => vol.statSync('/dojo.js', { bigint: true })).toThrowError();
}
});
it('Stats on symlink returns results about the resolved file', () => {
vol.symlinkSync('/dojo.js', '/link.js');
const stats = vol.statSync('/link.js');
Expand Down Expand Up @@ -723,6 +743,15 @@ describe('volume', () => {
expect(stats.isFile()).toBe(true);
expect(stats.isDirectory()).toBe(false);
});
it('Returns file stats using BigInt', () => {
const fd = vol.openSync('/dojo.js', 'r');
if (typeof BigInt === 'function') {
const stats = vol.fstatSync(fd, { bigint: true });
expect(typeof stats.ino).toBe('bigint');
} else {
expect(() => vol.fstatSync(fd, { bigint: true })).toThrowError();
}
});
});
describe('.fstat(fd, callback)', () => {
xit('...', () => {});
Expand Down
6 changes: 6 additions & 0 deletions src/bigint.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This definition file is here as a workaround and should be replaced
// by "esnext.bigint" library when TypeScript will support `BigInt` type.
// Track this at Microsoft/TypeScript#15096.

type BigInt = number;
declare const BigInt: typeof Number;
19 changes: 10 additions & 9 deletions src/promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
IReadFileOptions,
IRealpathOptions,
IWriteFileOptions,
IStatOptions,
} from './volume';
import Stats from './Stats';
import Dirent from './Dirent';
Expand Down Expand Up @@ -52,7 +53,7 @@ export interface IFileHandle {
datasync(): Promise<void>;
read(buffer: Buffer | Uint8Array, offset: number, length: number, position: number): Promise<TFileHandleReadResult>;
readFile(options?: IReadFileOptions | string): Promise<TDataOut>;
stat(): Promise<Stats>;
stat(options?: IStatOptions): Promise<Stats>;
truncate(len?: number): Promise<void>;
utimes(atime: TTime, mtime: TTime): Promise<void>;
write(
Expand All @@ -76,7 +77,7 @@ export interface IPromisesAPI {
lchmod(path: TFilePath, mode: TMode): Promise<void>;
lchown(path: TFilePath, uid: number, gid: number): Promise<void>;
link(existingPath: TFilePath, newPath: TFilePath): Promise<void>;
lstat(path: TFilePath): Promise<Stats>;
lstat(path: TFilePath, options?: IStatOptions): Promise<Stats>;
mkdir(path: TFilePath, options?: TMode | IMkdirOptions): Promise<void>;
mkdtemp(prefix: string, options?: IOptions): Promise<TDataOut>;
open(path: TFilePath, flags: TFlags, mode?: TMode): Promise<FileHandle>;
Expand All @@ -86,7 +87,7 @@ export interface IPromisesAPI {
realpath(path: TFilePath, options?: IRealpathOptions | string): Promise<TDataOut>;
rename(oldPath: TFilePath, newPath: TFilePath): Promise<void>;
rmdir(path: TFilePath): Promise<void>;
stat(path: TFilePath): Promise<Stats>;
stat(path: TFilePath, options?: IStatOptions): Promise<Stats>;
symlink(target: TFilePath, path: TFilePath, type?: TSymlinkType): Promise<void>;
truncate(path: TFilePath, len?: number): Promise<void>;
unlink(path: TFilePath): Promise<void>;
Expand Down Expand Up @@ -132,8 +133,8 @@ export class FileHandle implements IFileHandle {
return promisify(this.vol, 'readFile')(this.fd, options);
}

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

sync(): Promise<void> {
Expand Down Expand Up @@ -205,8 +206,8 @@ export default function createPromisesApi(vol: Volume): null | IPromisesAPI {
return promisify(vol, 'link')(existingPath, newPath);
},

lstat(path: TFilePath): Promise<Stats> {
return promisify(vol, 'lstat')(path);
lstat(path: TFilePath, options?: IStatOptions): Promise<Stats> {
return promisify(vol, 'lstat')(path, options);
},

mkdir(path: TFilePath, options?: TMode | IMkdirOptions): Promise<void> {
Expand Down Expand Up @@ -245,8 +246,8 @@ export default function createPromisesApi(vol: Volume): null | IPromisesAPI {
return promisify(vol, 'rmdir')(path);
},

stat(path: TFilePath): Promise<Stats> {
return promisify(vol, 'stat')(path);
stat(path: TFilePath, options?: IStatOptions): Promise<Stats> {
return promisify(vol, 'stat')(path, options);
},

symlink(target: TFilePath, path: TFilePath, type?: TSymlinkType): Promise<void> {
Expand Down
61 changes: 42 additions & 19 deletions src/volume.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as pathModule from 'path';
import { Node, Link, File } from './node';
import Stats from './Stats';
import Stats, { TStatNumber } from './Stats';
import Dirent from './Dirent';
import { Buffer } from 'buffer';
import setImmediate from './setImmediate';
Expand Down Expand Up @@ -357,6 +357,20 @@ 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`
export interface IStatOptions {
bigint?: boolean;
}
const statDefaults: IStatOptions = {
bigint: false,
};
const getStatOptions: (options?: any) => IStatOptions = (options = {}) => extend({}, statDefaults, options);
const getStatOptsAndCb: (options: any, callback?: TCallback<Stats>) => [IStatOptions, TCallback<Stats>] = (
options,
callback?,
) =>
typeof options === 'function' ? [getStatOptions(), options] : [getStatOptions(options), validateCallback(callback)];

// ---------------------------------------- Utility functions

function getPathFromURLPosix(url): string {
Expand Down Expand Up @@ -1416,51 +1430,60 @@ export class Volume {
this.wrapAsync(this.realpathBase, [pathFilename, opts.encoding], callback);
}

private lstatBase(filename: string): Stats {
private lstatBase(filename: string, bigint: boolean = false): Stats {
const link: Link = this.getLink(filenameToSteps(filename));
if (!link) throwError(ENOENT, 'lstat', filename);
return Stats.build(link.getNode());
return Stats.build(link.getNode(), bigint);
}

lstatSync(path: TFilePath): Stats {
return this.lstatBase(pathToFilename(path));
lstatSync(path: TFilePath, options?: IStatOptions): Stats {
return this.lstatBase(pathToFilename(path), getStatOptions(options).bigint);
}

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

private statBase(filename: string): Stats {
private statBase(filename: string, bigint: boolean = false): Stats {
let link: Link = this.getLink(filenameToSteps(filename));
if (!link) throwError(ENOENT, 'stat', filename);

// Resolve symlinks.
link = this.resolveSymlinks(link);
if (!link) throwError(ENOENT, 'stat', filename);

return Stats.build(link.getNode());
return Stats.build(link.getNode(), bigint);
}

statSync(path: TFilePath): Stats {
return this.statBase(pathToFilename(path));
statSync(path: TFilePath, options?: IStatOptions): Stats {
return this.statBase(pathToFilename(path), getStatOptions(options).bigint);
}

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

private fstatBase(fd: number): Stats {
private fstatBase(fd: number, bigint: boolean = false): Stats {
const file = this.getFileByFd(fd);
if (!file) throwError(EBADF, 'fstat');
return Stats.build(file.node);
return Stats.build(file.node, bigint);
}

fstatSync(fd: number): Stats {
return this.fstatBase(fd);
fstatSync(fd: number, options?: IStatOptions): Stats {
return this.fstatBase(fd, getStatOptions(options).bigint);
}

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

private renameBase(oldPathFilename: string, newPathFilename: string) {
Expand Down

0 comments on commit 00a017e

Please sign in to comment.