Skip to content

Commit 00a017e

Browse files
committed
feat: add bigint option support
1 parent 7c29b93 commit 00a017e

File tree

6 files changed

+124
-52
lines changed

6 files changed

+124
-52
lines changed

docs/dependencies.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ available.
88

99
It uses `Promise` when available and throws when `promises` property is
1010
accessed in an environment that does not support this ES2015 feature.
11+
12+
It uses `BigInt` when available and throws when `bigint` option is used
13+
in an environment that does not support this ESNext feature.

src/Stats.ts

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,62 +3,72 @@ import { constants } from './constants';
33

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

6+
export type TStatNumber = number | BigInt;
7+
68
/**
79
* Statistics about a file/directory, like `fs.Stats`.
810
*/
911
export class Stats {
10-
static build(node: Node) {
12+
static build(node: Node, bigint: boolean = false) {
1113
const stats = new Stats();
1214
const { uid, gid, atime, mtime, ctime } = node;
1315

16+
const getStatNumber = !bigint
17+
? number => number
18+
: typeof BigInt === 'function'
19+
? BigInt
20+
: () => {
21+
throw new Error('BigInt is not supported in this environment.');
22+
};
23+
1424
// Copy all values on Stats from Node, so that if Node values
1525
// change, values on Stats would still be the old ones,
1626
// just like in Node fs.
1727

18-
stats.uid = uid;
19-
stats.gid = gid;
28+
stats.uid = getStatNumber(uid);
29+
stats.gid = getStatNumber(gid);
2030

2131
stats.atime = atime;
2232
stats.mtime = mtime;
2333
stats.ctime = ctime;
2434
stats.birthtime = ctime;
2535

26-
stats.atimeMs = atime.getTime();
27-
stats.mtimeMs = mtime.getTime();
28-
const ctimeMs = ctime.getTime();
36+
stats.atimeMs = getStatNumber(atime.getTime());
37+
stats.mtimeMs = getStatNumber(mtime.getTime());
38+
const ctimeMs = getStatNumber(ctime.getTime());
2939
stats.ctimeMs = ctimeMs;
3040
stats.birthtimeMs = ctimeMs;
3141

32-
stats.size = node.getSize();
33-
stats.mode = node.mode;
34-
stats.ino = node.ino;
35-
stats.nlink = node.nlink;
42+
stats.size = getStatNumber(node.getSize());
43+
stats.mode = getStatNumber(node.mode);
44+
stats.ino = getStatNumber(node.ino);
45+
stats.nlink = getStatNumber(node.nlink);
3646

3747
return stats;
3848
}
3949

40-
uid: number = 0;
41-
gid: number = 0;
50+
uid: TStatNumber = 0;
51+
gid: TStatNumber = 0;
4252

43-
rdev: number = 0;
44-
blksize: number = 4096;
45-
ino: number = 0;
46-
size: number = 0;
47-
blocks: number = 1;
53+
rdev: TStatNumber = 0;
54+
blksize: TStatNumber = 4096;
55+
ino: TStatNumber = 0;
56+
size: TStatNumber = 0;
57+
blocks: TStatNumber = 1;
4858

4959
atime: Date = null;
5060
mtime: Date = null;
5161
ctime: Date = null;
5262
birthtime: Date = null;
5363

54-
atimeMs: number = 0.0;
55-
mtimeMs: number = 0.0;
56-
ctimeMs: number = 0.0;
57-
birthtimeMs: number = 0.0;
64+
atimeMs: TStatNumber = 0.0;
65+
mtimeMs: TStatNumber = 0.0;
66+
ctimeMs: TStatNumber = 0.0;
67+
birthtimeMs: TStatNumber = 0.0;
5868

59-
dev: number = 0;
60-
mode: number = 0;
61-
nlink: number = 0;
69+
dev: TStatNumber = 0;
70+
mode: TStatNumber = 0;
71+
nlink: TStatNumber = 0;
6272

6373
private _checkModeProperty(property: number): boolean {
6474
return (this.mode & S_IFMT) === property;

src/__tests__/volume.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import Stats from '../Stats';
44
import Dirent from '../Dirent';
55
import { Volume, filenameToSteps, StatWatcher } from '../volume';
66

7+
// I did not find how to include '../bigint.d.ts' here!
8+
type BigInt = number;
9+
declare const BigInt: typeof Number;
10+
711
describe('volume', () => {
812
describe('filenameToSteps(filename): string[]', () => {
913
it('/ -> []', () => {
@@ -665,6 +669,14 @@ describe('volume', () => {
665669
expect(stats.isFile()).toBe(true);
666670
expect(stats.isDirectory()).toBe(false);
667671
});
672+
it('Returns file stats using BigInt', () => {
673+
if (typeof BigInt === 'function') {
674+
const stats = vol.lstatSync('/dojo.js', { bigint: true });
675+
expect(typeof stats.ino).toBe('bigint');
676+
} else {
677+
expect(() => vol.lstatSync('/dojo.js', { bigint: true })).toThrowError();
678+
}
679+
});
668680
it('Stats on symlink returns results about the symlink', () => {
669681
vol.symlinkSync('/dojo.js', '/link.js');
670682
const stats = vol.lstatSync('/link.js');
@@ -688,6 +700,14 @@ describe('volume', () => {
688700
expect(stats.isFile()).toBe(true);
689701
expect(stats.isDirectory()).toBe(false);
690702
});
703+
it('Returns file stats using BigInt', () => {
704+
if (typeof BigInt === 'function') {
705+
const stats = vol.statSync('/dojo.js', { bigint: true });
706+
expect(typeof stats.ino).toBe('bigint');
707+
} else {
708+
expect(() => vol.statSync('/dojo.js', { bigint: true })).toThrowError();
709+
}
710+
});
691711
it('Stats on symlink returns results about the resolved file', () => {
692712
vol.symlinkSync('/dojo.js', '/link.js');
693713
const stats = vol.statSync('/link.js');
@@ -723,6 +743,15 @@ describe('volume', () => {
723743
expect(stats.isFile()).toBe(true);
724744
expect(stats.isDirectory()).toBe(false);
725745
});
746+
it('Returns file stats using BigInt', () => {
747+
const fd = vol.openSync('/dojo.js', 'r');
748+
if (typeof BigInt === 'function') {
749+
const stats = vol.fstatSync(fd, { bigint: true });
750+
expect(typeof stats.ino).toBe('bigint');
751+
} else {
752+
expect(() => vol.fstatSync(fd, { bigint: true })).toThrowError();
753+
}
754+
});
726755
});
727756
describe('.fstat(fd, callback)', () => {
728757
xit('...', () => {});

src/bigint.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// This definition file is here as a workaround and should be replaced
2+
// by "esnext.bigint" library when TypeScript will support `BigInt` type.
3+
// Track this at Microsoft/TypeScript#15096.
4+
5+
type BigInt = number;
6+
declare const BigInt: typeof Number;

src/promises.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
IReadFileOptions,
1515
IRealpathOptions,
1616
IWriteFileOptions,
17+
IStatOptions,
1718
} from './volume';
1819
import Stats from './Stats';
1920
import Dirent from './Dirent';
@@ -52,7 +53,7 @@ export interface IFileHandle {
5253
datasync(): Promise<void>;
5354
read(buffer: Buffer | Uint8Array, offset: number, length: number, position: number): Promise<TFileHandleReadResult>;
5455
readFile(options?: IReadFileOptions | string): Promise<TDataOut>;
55-
stat(): Promise<Stats>;
56+
stat(options?: IStatOptions): Promise<Stats>;
5657
truncate(len?: number): Promise<void>;
5758
utimes(atime: TTime, mtime: TTime): Promise<void>;
5859
write(
@@ -76,7 +77,7 @@ export interface IPromisesAPI {
7677
lchmod(path: TFilePath, mode: TMode): Promise<void>;
7778
lchown(path: TFilePath, uid: number, gid: number): Promise<void>;
7879
link(existingPath: TFilePath, newPath: TFilePath): Promise<void>;
79-
lstat(path: TFilePath): Promise<Stats>;
80+
lstat(path: TFilePath, options?: IStatOptions): Promise<Stats>;
8081
mkdir(path: TFilePath, options?: TMode | IMkdirOptions): Promise<void>;
8182
mkdtemp(prefix: string, options?: IOptions): Promise<TDataOut>;
8283
open(path: TFilePath, flags: TFlags, mode?: TMode): Promise<FileHandle>;
@@ -86,7 +87,7 @@ export interface IPromisesAPI {
8687
realpath(path: TFilePath, options?: IRealpathOptions | string): Promise<TDataOut>;
8788
rename(oldPath: TFilePath, newPath: TFilePath): Promise<void>;
8889
rmdir(path: TFilePath): Promise<void>;
89-
stat(path: TFilePath): Promise<Stats>;
90+
stat(path: TFilePath, options?: IStatOptions): Promise<Stats>;
9091
symlink(target: TFilePath, path: TFilePath, type?: TSymlinkType): Promise<void>;
9192
truncate(path: TFilePath, len?: number): Promise<void>;
9293
unlink(path: TFilePath): Promise<void>;
@@ -132,8 +133,8 @@ export class FileHandle implements IFileHandle {
132133
return promisify(this.vol, 'readFile')(this.fd, options);
133134
}
134135

135-
stat(): Promise<Stats> {
136-
return promisify(this.vol, 'fstat')(this.fd);
136+
stat(options?: IStatOptions): Promise<Stats> {
137+
return promisify(this.vol, 'fstat')(this.fd, options);
137138
}
138139

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

208-
lstat(path: TFilePath): Promise<Stats> {
209-
return promisify(vol, 'lstat')(path);
209+
lstat(path: TFilePath, options?: IStatOptions): Promise<Stats> {
210+
return promisify(vol, 'lstat')(path, options);
210211
},
211212

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

248-
stat(path: TFilePath): Promise<Stats> {
249-
return promisify(vol, 'stat')(path);
249+
stat(path: TFilePath, options?: IStatOptions): Promise<Stats> {
250+
return promisify(vol, 'stat')(path, options);
250251
},
251252

252253
symlink(target: TFilePath, path: TFilePath, type?: TSymlinkType): Promise<void> {

src/volume.ts

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as pathModule from 'path';
22
import { Node, Link, File } from './node';
3-
import Stats from './Stats';
3+
import Stats, { TStatNumber } from './Stats';
44
import Dirent from './Dirent';
55
import { Buffer } from 'buffer';
66
import setImmediate from './setImmediate';
@@ -357,6 +357,20 @@ const readdirDefaults: IReaddirOptions = {
357357
const getReaddirOptions = optsGenerator<IReaddirOptions>(readdirDefaults);
358358
const getReaddirOptsAndCb = optsAndCbGenerator<IReaddirOptions, TDataOut[] | Dirent[]>(getReaddirOptions);
359359

360+
// Options for `fs.fstat`, `fs.fstatSync`, `fs.lstat`, `fs.lstatSync`, `fs.stat`, and `fs.statSync`
361+
export interface IStatOptions {
362+
bigint?: boolean;
363+
}
364+
const statDefaults: IStatOptions = {
365+
bigint: false,
366+
};
367+
const getStatOptions: (options?: any) => IStatOptions = (options = {}) => extend({}, statDefaults, options);
368+
const getStatOptsAndCb: (options: any, callback?: TCallback<Stats>) => [IStatOptions, TCallback<Stats>] = (
369+
options,
370+
callback?,
371+
) =>
372+
typeof options === 'function' ? [getStatOptions(), options] : [getStatOptions(options), validateCallback(callback)];
373+
360374
// ---------------------------------------- Utility functions
361375

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

1419-
private lstatBase(filename: string): Stats {
1433+
private lstatBase(filename: string, bigint: boolean = false): Stats {
14201434
const link: Link = this.getLink(filenameToSteps(filename));
14211435
if (!link) throwError(ENOENT, 'lstat', filename);
1422-
return Stats.build(link.getNode());
1436+
return Stats.build(link.getNode(), bigint);
14231437
}
14241438

1425-
lstatSync(path: TFilePath): Stats {
1426-
return this.lstatBase(pathToFilename(path));
1439+
lstatSync(path: TFilePath, options?: IStatOptions): Stats {
1440+
return this.lstatBase(pathToFilename(path), getStatOptions(options).bigint);
14271441
}
14281442

1429-
lstat(path: TFilePath, callback: TCallback<Stats>) {
1430-
this.wrapAsync(this.lstatBase, [pathToFilename(path)], callback);
1443+
lstat(path: TFilePath, callback: TCallback<Stats>);
1444+
lstat(path: TFilePath, options: IStatOptions, callback: TCallback<Stats>);
1445+
lstat(path: TFilePath, a: TCallback<Stats> | IStatOptions, b?: TCallback<Stats>) {
1446+
const [opts, callback] = getStatOptsAndCb(a, b);
1447+
this.wrapAsync(this.lstatBase, [pathToFilename(path), opts.bigint], callback);
14311448
}
14321449

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

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

1441-
return Stats.build(link.getNode());
1458+
return Stats.build(link.getNode(), bigint);
14421459
}
14431460

1444-
statSync(path: TFilePath): Stats {
1445-
return this.statBase(pathToFilename(path));
1461+
statSync(path: TFilePath, options?: IStatOptions): Stats {
1462+
return this.statBase(pathToFilename(path), getStatOptions(options).bigint);
14461463
}
14471464

1448-
stat(path: TFilePath, callback: TCallback<Stats>) {
1449-
this.wrapAsync(this.statBase, [pathToFilename(path)], callback);
1465+
stat(path: TFilePath, callback: TCallback<Stats>);
1466+
stat(path: TFilePath, options: IStatOptions, callback: TCallback<Stats>);
1467+
stat(path: TFilePath, a: TCallback<Stats> | IStatOptions, b?: TCallback<Stats>) {
1468+
const [opts, callback] = getStatOptsAndCb(a, b);
1469+
this.wrapAsync(this.statBase, [pathToFilename(path), opts.bigint], callback);
14501470
}
14511471

1452-
private fstatBase(fd: number): Stats {
1472+
private fstatBase(fd: number, bigint: boolean = false): Stats {
14531473
const file = this.getFileByFd(fd);
14541474
if (!file) throwError(EBADF, 'fstat');
1455-
return Stats.build(file.node);
1475+
return Stats.build(file.node, bigint);
14561476
}
14571477

1458-
fstatSync(fd: number): Stats {
1459-
return this.fstatBase(fd);
1478+
fstatSync(fd: number, options?: IStatOptions): Stats {
1479+
return this.fstatBase(fd, getStatOptions(options).bigint);
14601480
}
14611481

1462-
fstat(fd: number, callback: TCallback<Stats>) {
1463-
this.wrapAsync(this.fstatBase, [fd], callback);
1482+
fstat(fd: number, callback: TCallback<Stats>);
1483+
fstat(fd: number, options: IStatOptions, callback: TCallback<Stats>);
1484+
fstat(fd: number, a: TCallback<Stats> | IStatOptions, b?: TCallback<Stats>) {
1485+
const [opts, callback] = getStatOptsAndCb(a, b);
1486+
this.wrapAsync(this.fstatBase, [fd, opts.bigint], callback);
14641487
}
14651488

14661489
private renameBase(oldPathFilename: string, newPathFilename: string) {

0 commit comments

Comments
 (0)