Skip to content

Commit

Permalink
BREAKING(fs): throw Deno.errors.NotFound instead of WalkError in …
Browse files Browse the repository at this point in the history
…`walk[Sync]()` (#5477)
  • Loading branch information
iuioiua authored Jul 24, 2024
1 parent e689f43 commit 0dc7ce1
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 104 deletions.
128 changes: 32 additions & 96 deletions fs/walk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,64 +3,13 @@
// https://golang.org/pkg/path/filepath/#Walk
// Copyright 2009 The Go Authors. All rights reserved. BSD license.
import { join } from "@std/path/join";
import { normalize } from "@std/path/normalize";
import { toPathString } from "./_to_path_string.ts";
import {
createWalkEntry,
createWalkEntrySync,
type WalkEntry,
} from "./_create_walk_entry.ts";

/**
* Error thrown in {@linkcode walk} or {@linkcode walkSync} during iteration.
*
* @example Usage
* ```ts no-eval
* import { walk, WalkError } from "@std/fs/walk";
*
* try {
* for await (const entry of walk("./non_existent_root")) {
* console.log(entry.path);
* }
* } catch (error) {
* if (error instanceof WalkError) {
* console.error(error.message);
* }
* }
* ```
*/
export class WalkError extends Error {
/**
* File path of the root that's being walked.
*
* @example Usage
* ```ts
* import { WalkError } from "@std/fs/walk";
* import { assertEquals } from "@std/assert";
*
* const error = new WalkError("error message", "./foo");
*
* assertEquals(error.root, "./foo");
* ```
*/
root: string;

/**
* Constructs a new instance.
*
* @param cause The cause of the error.
* @param root The root directory that's being walked.
*/
constructor(cause: unknown, root: string) {
super(
`${cause instanceof Error ? cause.message : cause} for path "${root}"`,
);
this.cause = cause;
this.name = this.constructor.name;
this.root = root;
}
}

function include(
path: string,
exts?: string[],
Expand All @@ -79,11 +28,6 @@ function include(
return true;
}

function wrapErrorWithPath(err: unknown, root: string) {
if (err instanceof WalkError) return err;
return new WalkError(err, root);
}

/** Options for {@linkcode walk} and {@linkcode walkSync}. */
export interface WalkOptions {
/**
Expand Down Expand Up @@ -163,6 +107,7 @@ export type { WalkEntry };
*
* @param root The root directory to start the walk from, as a string or URL.
* @param options The options for the walk.
* @throws {Deno.errors.NotFound} If the root directory does not exist.
*
* @returns An async iterable iterator that yields the walk entry objects.
*
Expand Down Expand Up @@ -532,46 +477,42 @@ export async function* walk(
if (maxDepth < 1 || !include(root, undefined, undefined, skip)) {
return;
}
try {
for await (const entry of Deno.readDir(root)) {
let path = join(root, entry.name);
for await (const entry of Deno.readDir(root)) {
let path = join(root, entry.name);

let { isSymlink, isDirectory } = entry;
let { isSymlink, isDirectory } = entry;

if (isSymlink) {
if (!followSymlinks) {
if (includeSymlinks && include(path, exts, match, skip)) {
yield { path, ...entry };
}
continue;
}
const realPath = await Deno.realPath(path);
if (canonicalize) {
path = realPath;
if (isSymlink) {
if (!followSymlinks) {
if (includeSymlinks && include(path, exts, match, skip)) {
yield { path, ...entry };
}
// Caveat emptor: don't assume |path| is not a symlink. realpath()
// resolves symlinks but another process can replace the file system
// entity with a different type of entity before we call lstat().
({ isSymlink, isDirectory } = await Deno.lstat(realPath));
continue;
}

if (isSymlink || isDirectory) {
yield* walk(path, {
maxDepth: maxDepth - 1,
includeFiles,
includeDirs,
includeSymlinks,
followSymlinks,
exts,
match,
skip,
});
} else if (includeFiles && include(path, exts, match, skip)) {
yield { path, ...entry };
const realPath = await Deno.realPath(path);
if (canonicalize) {
path = realPath;
}
// Caveat emptor: don't assume |path| is not a symlink. realpath()
// resolves symlinks but another process can replace the file system
// entity with a different type of entity before we call lstat().
({ isSymlink, isDirectory } = await Deno.lstat(realPath));
}

if (isSymlink || isDirectory) {
yield* walk(path, {
maxDepth: maxDepth - 1,
includeFiles,
includeDirs,
includeSymlinks,
followSymlinks,
exts,
match,
skip,
});
} else if (includeFiles && include(path, exts, match, skip)) {
yield { path, ...entry };
}
} catch (err) {
throw wrapErrorWithPath(err, normalize(root));
}
}

Expand Down Expand Up @@ -958,12 +899,7 @@ export function* walkSync(
if (maxDepth < 1 || !include(root, undefined, undefined, skip)) {
return;
}
let entries;
try {
entries = Deno.readDirSync(root);
} catch (err) {
throw wrapErrorWithPath(err, normalize(root));
}
const entries = Deno.readDirSync(root);
for (const entry of entries) {
let path = join(root, entry.name);

Expand Down
19 changes: 11 additions & 8 deletions fs/walk_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { walk, WalkError, type WalkOptions, walkSync } from "./walk.ts";
import { walk, type WalkOptions, walkSync } from "./walk.ts";
import {
assertArrayIncludes,
assertEquals,
Expand Down Expand Up @@ -234,16 +234,19 @@ Deno.test({
},
});

Deno.test("walk() rejects with WalkError when root is removed during execution", async () => {
Deno.test("walk() rejects with `Deno.errors.NotFound` when root is removed during execution", async () => {
const root = resolve(testdataDir, "error");
await Deno.mkdir(root);
try {
await assertRejects(async () => {
await Array.fromAsync(
walk(root),
async () => await Deno.remove(root, { recursive: true }),
);
}, WalkError);
await assertRejects(
async () => {
await Array.fromAsync(
walk(root),
async () => await Deno.remove(root, { recursive: true }),
);
},
Deno.errors.NotFound,
);
} catch (err) {
await Deno.remove(root, { recursive: true });
throw err;
Expand Down

0 comments on commit 0dc7ce1

Please sign in to comment.