From 6ba0b7952d1ca867d0e166de4d36dcd6fe489e89 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 6 Mar 2024 18:29:10 +0530 Subject: [PATCH] fix(node): stat/statSync returns instance of fs.Stats (#22294) Fixes https://github.com/denoland/deno/issues/22291 --------- Signed-off-by: Divy Srivastava --- ext/node/polyfills/_fs/_fs_stat.ts | 101 +++++++++++++++++++++++++--- ext/node/polyfills/_fs/_fs_watch.ts | 3 +- ext/node/polyfills/fs.ts | 8 ++- tests/unit_node/fs_test.ts | 11 +++ 4 files changed, 111 insertions(+), 12 deletions(-) diff --git a/ext/node/polyfills/_fs/_fs_stat.ts b/ext/node/polyfills/_fs/_fs_stat.ts index 119faeceed..de9b69ba36 100644 --- a/ext/node/polyfills/_fs/_fs_stat.ts +++ b/ext/node/polyfills/_fs/_fs_stat.ts @@ -5,13 +5,16 @@ import { denoErrorToNodeError } from "ext:deno_node/internal/errors.ts"; import { promisify } from "ext:deno_node/internal/util.mjs"; +import { primordials } from "ext:core/mod.js"; + +const { ObjectCreate, ObjectAssign } = primordials; export type statOptions = { bigint: boolean; throwIfNoEntry?: boolean; }; -export type Stats = { +interface IStats { /** ID of the device containing the file. * * _Linux/Mac OS only._ */ @@ -80,9 +83,84 @@ export type Stats = { isFile: () => boolean; isSocket: () => boolean; isSymbolicLink: () => boolean; -}; +} -export type BigIntStats = { +class StatsBase { + constructor( + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + ) { + this.dev = dev; + this.mode = mode; + this.nlink = nlink; + this.uid = uid; + this.gid = gid; + this.rdev = rdev; + this.blksize = blksize; + this.ino = ino; + this.size = size; + this.blocks = blocks; + } +} + +// The Date constructor performs Math.floor() to the timestamp. +// https://www.ecma-international.org/ecma-262/#sec-timeclip +// Since there may be a precision loss when the timestamp is +// converted to a floating point number, we manually round +// the timestamp here before passing it to Date(). +function dateFromMs(ms) { + return new Date(Number(ms) + 0.5); +} + +export class Stats extends StatsBase { + constructor( + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + atimeMs, + mtimeMs, + ctimeMs, + birthtimeMs, + ) { + super( + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + ); + this.atimeMs = atimeMs; + this.mtimeMs = mtimeMs; + this.ctimeMs = ctimeMs; + this.birthtimeMs = birthtimeMs; + this.atime = dateFromMs(atimeMs); + this.mtime = dateFromMs(mtimeMs); + this.ctime = dateFromMs(ctimeMs); + this.birthtime = dateFromMs(birthtimeMs); + } +} + +export interface IBigIntStats { /** ID of the device containing the file. * * _Linux/Mac OS only._ */ @@ -159,10 +237,13 @@ export type BigIntStats = { isFile: () => boolean; isSocket: () => boolean; isSymbolicLink: () => boolean; -}; +} + +export class BigIntStats {} export function convertFileInfoToStats(origin: Deno.FileInfo): Stats { - return { + const stats = ObjectCreate(Stats.prototype); + ObjectAssign(stats, { dev: origin.dev, ino: origin.ino, mode: origin.mode, @@ -189,7 +270,9 @@ export function convertFileInfoToStats(origin: Deno.FileInfo): Stats { isSocket: () => false, ctime: origin.mtime, ctimeMs: origin.mtime?.getTime() || null, - }; + }); + + return stats; } function toBigInt(number?: number | null) { @@ -200,7 +283,8 @@ function toBigInt(number?: number | null) { export function convertFileInfoToBigIntStats( origin: Deno.FileInfo, ): BigIntStats { - return { + const stats = ObjectCreate(BigIntStats.prototype); + ObjectAssign(stats, { dev: toBigInt(origin.dev), ino: toBigInt(origin.ino), mode: toBigInt(origin.mode), @@ -233,7 +317,8 @@ export function convertFileInfoToBigIntStats( ctime: origin.mtime, ctimeMs: origin.mtime ? BigInt(origin.mtime.getTime()) : null, ctimeNs: origin.mtime ? BigInt(origin.mtime.getTime()) * 1000000n : null, - }; + }); + return stats; } // shortcut for Convert File Info to Stats or BigIntStats diff --git a/ext/node/polyfills/_fs/_fs_watch.ts b/ext/node/polyfills/_fs/_fs_watch.ts index 2ba372e348..78903cd553 100644 --- a/ext/node/polyfills/_fs/_fs_watch.ts +++ b/ext/node/polyfills/_fs/_fs_watch.ts @@ -10,7 +10,6 @@ import { promisify } from "node:util"; import { getValidatedPath } from "ext:deno_node/internal/fs/utils.mjs"; import { validateFunction } from "ext:deno_node/internal/validators.mjs"; import { stat, Stats } from "ext:deno_node/_fs/_fs_stat.ts"; -import { Stats as StatsClass } from "ext:deno_node/internal/fs/utils.mjs"; import { Buffer } from "node:buffer"; import { delay } from "ext:deno_node/_util/async.ts"; @@ -22,7 +21,7 @@ const statAsync = async (filename: string): Promise => { return emptyStats; } }; -const emptyStats = new StatsClass( +const emptyStats = new Stats( 0, 0, 0, diff --git a/ext/node/polyfills/fs.ts b/ext/node/polyfills/fs.ts index 38a7a2bfb6..bf43dd92e9 100644 --- a/ext/node/polyfills/fs.ts +++ b/ext/node/polyfills/fs.ts @@ -69,7 +69,12 @@ import { } from "ext:deno_node/_fs/_fs_rename.ts"; import { rmdir, rmdirPromise, rmdirSync } from "ext:deno_node/_fs/_fs_rmdir.ts"; import { rm, rmPromise, rmSync } from "ext:deno_node/_fs/_fs_rm.ts"; -import { stat, statPromise, statSync } from "ext:deno_node/_fs/_fs_stat.ts"; +import { + stat, + statPromise, + Stats, + statSync, +} from "ext:deno_node/_fs/_fs_stat.ts"; import { symlink, symlinkPromise, @@ -105,7 +110,6 @@ import { writeFilePromise, writeFileSync, } from "ext:deno_node/_fs/_fs_writeFile.ts"; -import { Stats } from "ext:deno_node/internal/fs/utils.mjs"; // @deno-types="./internal/fs/streams.d.ts" import { createReadStream, diff --git a/tests/unit_node/fs_test.ts b/tests/unit_node/fs_test.ts index 4a8ef89f3d..caa266ef2c 100644 --- a/tests/unit_node/fs_test.ts +++ b/tests/unit_node/fs_test.ts @@ -9,6 +9,8 @@ import { mkdtempSync, promises, readFileSync, + Stats, + statSync, writeFileSync, } from "node:fs"; import { constants as fsPromiseConstants, cp } from "node:fs/promises"; @@ -97,6 +99,15 @@ Deno.test( }, ); +Deno.test( + "[node/fs statSync] instanceof fs.Stats", + () => { + const stat = statSync("tests/testdata/assets/fixture.json"); + assert(stat); + assert(stat instanceof Stats); + }, +); + Deno.test( "[node/fs/promises cp] copy file", async () => {