Skip to content

Commit

Permalink
feat(fs/unstable): add statSync and lstatSync (#6300)
Browse files Browse the repository at this point in the history
  • Loading branch information
kt3k authored Jan 10, 2025
1 parent 9b86f0f commit 148107e
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 45 deletions.
3 changes: 3 additions & 0 deletions _tools/check_docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ const ENTRY_POINTS = [
"../front_matter/mod.ts",
"../front_matter/unstable_yaml.ts",
"../fs/mod.ts",
"../fs/unstable_lstat.ts",
"../fs/unstable_stat.ts",
"../fs/unstable_types.ts",
"../html/mod.ts",
"../html/unstable_is_valid_custom_element_name.ts",
"../http/mod.ts",
Expand Down
7 changes: 5 additions & 2 deletions fs/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ function checkWindows(): boolean {
return false;
}

export function getNodeFsPromises() {
return (globalThis as any).process.getBuiltinModule("node:fs/promises");
/**
* @returns The Node.js `fs` module.
*/
export function getNodeFs() {
return (globalThis as any).process.getBuiltinModule("node:fs");
}
3 changes: 2 additions & 1 deletion fs/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
"./exists": "./exists.ts",
"./expand-glob": "./expand_glob.ts",
"./move": "./move.ts",
"./unstable-stat": "./unstable_stat.ts",
"./unstable-lstat": "./unstable_lstat.ts",
"./unstable-stat": "./unstable_stat.ts",
"./unstable-types": "./unstable_types.ts",
"./walk": "./walk.ts"
}
}
52 changes: 44 additions & 8 deletions fs/unstable_lstat.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,68 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { getNodeFsPromises, isDeno } from "./_utils.ts";
import { getNodeFs, isDeno } from "./_utils.ts";
import { mapError } from "./_map_error.ts";
import { toFileInfo } from "./_to_file_info.ts";
import type { FileInfo } from "./unstable_types.ts";

/** Resolves to a {@linkcode FileInfo} for the specified `path`. If `path` is a symlink, information for the symlink will be returned instead of what it points to.
/**
* Resolves to a {@linkcode FileInfo} for the specified `path`. If `path` is a symlink, information for the symlink will be returned instead of what it points to.
*
* Requires `allow-read` permission in Deno.
*
* @example Usage
* ```ts
* import { assert } from "@std/assert";
* import { lstat } from "@std/fs/unstable-lstat";
* const fileInfo = await lstat("README.md");
* assert(fileInfo.isFile);
* ```
*
* Requires `allow-read` permission.
*
* @tags allow-read
* @category File System
*
* @param path The path to the file or directory.
* @returns A promise that resolves to a {@linkcode FileInfo} for the specified `path`.
*/
export async function lstat(path: string | URL): Promise<FileInfo> {
if (isDeno) {
return Deno.lstat(path);
} else {
const fsPromises = getNodeFsPromises();
try {
const stat = await fsPromises.lstat(path);
return toFileInfo(stat);
return toFileInfo(await getNodeFs().promises.lstat(path));
} catch (error) {
throw mapError(error);
}
}
}

/**
* Synchronously returns a {@linkcode FileInfo} for the specified
* `path`. If `path` is a symlink, information for the symlink will be
* returned instead of what it points to.
*
* Requires `allow-read` permission in Deno.
*
* @example Usage
*
* ```ts
* import { assert } from "@std/assert";
* import { lstatSync } from "@std/fs/unstable-lstat";
*
* const fileInfo = lstatSync("README.md");
* assert(fileInfo.isFile);
* ```
*
* @tags allow-read
*
* @param path The path to the file or directory.
* @returns A {@linkcode FileInfo} for the specified `path`.
*/
export function lstatSync(path: string | URL): FileInfo {
if (isDeno) {
return Deno.lstatSync(path);
} else {
try {
return toFileInfo(getNodeFs().lstatSync(path));
} catch (error) {
throw mapError(error);
}
Expand Down
50 changes: 34 additions & 16 deletions fs/unstable_lstat_test.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,48 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { assert, assertRejects } from "@std/assert";
import { lstat } from "./unstable_lstat.ts";
import { assert, assertRejects, assertThrows } from "@std/assert";
import { lstat, lstatSync } from "./unstable_lstat.ts";
import { NotFound } from "./unstable_errors.js";

Deno.test("lstat() returns FileInfo for a file", async () => {
const fileInfo = await lstat("README.md");

assert(fileInfo.isFile);
Deno.test("lstat() and lstatSync() return FileInfo for a file", async () => {
{
const fileInfo = await lstat("README.md");
assert(fileInfo.isFile);
}
{
const fileInfo = lstatSync("README.md");
assert(fileInfo.isFile);
}
});

Deno.test("lstat() does not follow symlinks", async () => {
const linkFile = `${import.meta.dirname}/testdata/0-link`;
const fileInfo = await lstat(linkFile);

assert(fileInfo.isSymlink);
Deno.test("lstat() and lstatSync() do not follow symlinks", async () => {
const linkFile = new URL("testdata/0-link", import.meta.url);
{
const fileInfo = await lstat(linkFile);
assert(fileInfo.isSymlink);
}
{
const fileInfo = lstatSync(linkFile);
assert(fileInfo.isSymlink);
}
});

Deno.test("lstat() returns FileInfo for a directory", async () => {
const fileInfo = await lstat("fs");

assert(fileInfo.isDirectory);
Deno.test("lstat() and lstatSync() return FileInfo for a directory", async () => {
{
const fileInfo = await lstat("fs");
assert(fileInfo.isDirectory);
}
{
const fileInfo = lstatSync("fs");
assert(fileInfo.isDirectory);
}
});

Deno.test("lstat() rejects with NotFound for a non-existent file", async () => {
Deno.test("lstat() and lstatSync() throw with NotFound for a non-existent file", async () => {
await assertRejects(async () => {
await lstat("non_existent_file");
}, NotFound);
assertThrows(() => {
lstatSync("non_existent_file");
}, NotFound);
});
50 changes: 42 additions & 8 deletions fs/unstable_stat.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,67 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { getNodeFsPromises, isDeno } from "./_utils.ts";
import { getNodeFs, isDeno } from "./_utils.ts";
import { mapError } from "./_map_error.ts";
import { toFileInfo } from "./_to_file_info.ts";
import type { FileInfo } from "./unstable_types.ts";

/** Resolves to a {@linkcode FileInfo} for the specified `path`. Will
/**
* Resolves to a {@linkcode FileInfo} for the specified `path`. Will
* always follow symlinks.
*
* Requires `allow-read` permission in Deno.
*
* @example Usage
* ```ts
* import { assert } from "@std/assert";
* import { stat } from "@std/fs/unstable-stat";
* const fileInfo = await stat("README.md");
* assert(fileInfo.isFile);
* ```
*
* Requires `allow-read` permission.
*
* @tags allow-read
* @category File System
*
* @param path The path to the file or directory.
* @returns A promise that resolves to a {@linkcode FileInfo} for the specified `path`.
*/
export async function stat(path: string | URL): Promise<FileInfo> {
if (isDeno) {
return Deno.stat(path);
} else {
const fsPromises = getNodeFsPromises();
try {
const stat = await fsPromises.stat(path);
return toFileInfo(stat);
return toFileInfo(await getNodeFs().promises.stat(path));
} catch (error) {
throw mapError(error);
}
}
}

/**
* Synchronously returns a {@linkcode FileInfo} for the specified
* `path`. Will always follow symlinks.
*
* Requires `allow-read` permission in Deno.
*
* @example Usage
* ```ts
* import { assert } from "@std/assert";
* import { statSync } from "@std/fs/unstable-stat";
*
* const fileInfo = statSync("README.md");
* assert(fileInfo.isFile);
* ```
*
* @tags allow-read
*
* @param path The path to the file or directory.
* @returns A {@linkcode FileInfo} for the specified `path`.
*/
export function statSync(path: string | URL): FileInfo {
if (isDeno) {
return Deno.statSync(path);
} else {
try {
return toFileInfo(getNodeFs().statSync(path));
} catch (error) {
throw mapError(error);
}
Expand Down
34 changes: 24 additions & 10 deletions fs/unstable_stat_test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { assert, assertRejects } from "@std/assert";
import { stat } from "./unstable_stat.ts";
import { assert, assertRejects, assertThrows } from "@std/assert";
import { stat, statSync } from "./unstable_stat.ts";
import { NotFound } from "./unstable_errors.js";

Deno.test("stat() returns FileInfo for a file", async () => {
const fileInfo = await stat("README.md");
Deno.test("stat() and statSync() return FileInfo for a file", async () => {
{
const fileInfo = await stat("README.md");
assert(fileInfo.isFile);
}

assert(fileInfo.isFile);
{
const fileInfo = statSync("README.md");
assert(fileInfo.isFile);
}
});

Deno.test("stat() returns FileInfo for a directory", async () => {
const fileInfo = await stat("fs");

assert(fileInfo.isDirectory);
Deno.test("stat() and statSync() return FileInfo for a directory", async () => {
{
const fileInfo = await stat("fs");
assert(fileInfo.isDirectory);
}
{
const fileInfo = statSync("fs");
assert(fileInfo.isDirectory);
}
});

Deno.test("stat() rejects with NotFound for a non-existent file", async () => {
Deno.test("stat() and statSync() throw with NotFound for a non-existent file", async () => {
await assertRejects(async () => {
await stat("non_existent_file");
}, NotFound);
assertThrows(() => {
statSync("non_existent_file");
}, NotFound);
});
8 changes: 8 additions & 0 deletions fs/unstable_types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// Copyright 2018-2025 the Deno authors. MIT license.

/**
* Provides information about a file and is returned by
* {@linkcode stat}, {@linkcode lstat}, {@linkcode statSync},
* and {@linkcode lstatSync} or from calling `stat()` and `statSync()`
* on an {@linkcode FsFile} instance.
*
* @category File System
*/
export interface FileInfo {
/** True if this is info for a regular file. Mutually exclusive to
* `FileInfo.isDirectory` and `FileInfo.isSymlink`. */
Expand Down

0 comments on commit 148107e

Please sign in to comment.