From ed2627a735395a9efbe451df24b04ae0d1243d29 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Tue, 18 Oct 2022 02:06:14 +0200 Subject: [PATCH 01/23] feat(fs): `readable`, `readableDir` and `readableFile` --- fs/mod.ts | 3 ++ fs/readable.ts | 81 +++++++++++++++++++++++++++++++++++++++ fs/readable_dir.ts | 79 ++++++++++++++++++++++++++++++++++++++ fs/readable_dir_test.ts | 61 +++++++++++++++++++++++++++++ fs/readable_file.ts | 79 ++++++++++++++++++++++++++++++++++++++ fs/readable_file_test.ts | 61 +++++++++++++++++++++++++++++ fs/readable_test.ts | 61 +++++++++++++++++++++++++++++ fs/testdata/testdata-link | 1 + 8 files changed, 426 insertions(+) create mode 100644 fs/readable.ts create mode 100644 fs/readable_dir.ts create mode 100644 fs/readable_dir_test.ts create mode 100644 fs/readable_file.ts create mode 100644 fs/readable_file_test.ts create mode 100644 fs/readable_test.ts create mode 120000 fs/testdata/testdata-link diff --git a/fs/mod.ts b/fs/mod.ts index b44c1db22a8a..288bb2c396a4 100644 --- a/fs/mod.ts +++ b/fs/mod.ts @@ -10,6 +10,9 @@ export * from "./ensure_dir.ts"; export * from "./ensure_file.ts"; export * from "./ensure_link.ts"; export * from "./ensure_symlink.ts"; +export * from "./readable.ts"; +export * from "./readable_dir.ts"; +export * from "./readable_file.ts"; export * from "./exists.ts"; export * from "./expand_glob.ts"; export * from "./move.ts"; diff --git a/fs/readable.ts b/fs/readable.ts new file mode 100644 index 000000000000..96fad8922e56 --- /dev/null +++ b/fs/readable.ts @@ -0,0 +1,81 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +/** + * Test whether or not the given path is readable by checking with the file system. To check simultaneously if the path is either a file or a directory please use `readableFile` or `readableDir` instead. + * + * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. + * + * Bad: + * ```ts + * import { readable } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * + * if (await readable("./foo")) { + * await Deno.remove("./foo"); + * } + * ``` + * + * Good: + * ```ts + * // Notice no use of readable + * try { + * await Deno.remove("./foo", { recursive: true }); + * } catch (error) { + * if (!(error instanceof Deno.errors.NotFound)) { + * throw error; + * } + * // Do nothing... + * } + * ``` + * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use + */ +export async function readable(path: string | URL): Promise { + try { + await Deno.stat(path); + return true; + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + return false; + } + throw error; + } +} + +/** + * Test whether or not the given path is readable by checking with the file system. To check simultaneously if the path is either a file or a directory please use `readableFile` or `readableDir` instead. + * + * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. + * + * Bad: + * ```ts + * import { readable } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * + * if (await readable("./foo")) { + * await Deno.remove("./foo"); + * } + * ``` + * + * Good: + * ```ts + * // Notice no use of readable + * try { + * await Deno.remove("./foo", { recursive: true }); + * } catch (error) { + * if (!(error instanceof Deno.errors.NotFound)) { + * throw error; + * } + * // Do nothing... + * } + * ``` + * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use + */ +export function readableSync(path: string | URL): boolean { + try { + Deno.statSync(path); + return true; + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + return false; + } + throw error; + } +} diff --git a/fs/readable_dir.ts b/fs/readable_dir.ts new file mode 100644 index 000000000000..7449bd454426 --- /dev/null +++ b/fs/readable_dir.ts @@ -0,0 +1,79 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +/** + * Test whether or not the given path is readable directory by checking with the file system. + * + * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. + * + * Bad: + * ```ts + * import { readableDir } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * + * if (await readableDir("./foo")) { + * await Deno.remove("./foo"); + * } + * ``` + * + * Good: + * ```ts + * // Notice no use of readableDir + * try { + * await Deno.remove("./foo", { recursive: true }); + * } catch (error) { + * if (!(error instanceof Deno.errors.NotFound)) { + * throw error; + * } + * // Do nothing... + * } + * ``` + * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use + */ +export async function readableDir(dirPath: string | URL): Promise { + try { + return (await Deno.stat(dirPath)).isDirectory; + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + return false; + } + throw error; + } +} + +/** + * Test whether or not the given path is readable directory by checking with the file system. + * + * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. + * + * Bad: + * ```ts + * import { readableDir } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * + * if (await readableDir("./foo")) { + * await Deno.remove("./foo"); + * } + * ``` + * + * Good: + * ```ts + * // Notice no use of readableDir + * try { + * await Deno.remove("./foo", { recursive: true }); + * } catch (error) { + * if (!(error instanceof Deno.errors.NotFound)) { + * throw error; + * } + * // Do nothing... + * } + * ``` + * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use + */ +export function readableDirSync(dirPath: string | URL): boolean { + try { + return Deno.statSync(dirPath).isDirectory; + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + return false; + } + throw error; + } +} diff --git a/fs/readable_dir_test.ts b/fs/readable_dir_test.ts new file mode 100644 index 000000000000..1d1d730bfc73 --- /dev/null +++ b/fs/readable_dir_test.ts @@ -0,0 +1,61 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "../testing/asserts.ts"; +import * as path from "../path/mod.ts"; +import { readableDir, readableDirSync } from "./readable_dir.ts"; + +const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); +const testdataDir = path.resolve(moduleDir, "testdata"); + +Deno.test("[fs] readableFile", async function () { + assertEquals( + await readableDir(path.join(testdataDir, "not_exist_file.ts")), + false, + ); + assertEquals(await readableDir(path.join(testdataDir, "0.ts")), false); +}); + +Deno.test("[fs] readableFileSync", function () { + assertEquals(readableDirSync(path.join(testdataDir, "not_exist_file.ts")), false); + assertEquals(readableDirSync(path.join(testdataDir, "0.ts")), false); +}); + +Deno.test("[fs] readableDir", async function () { + assertEquals( + await readableDir(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(await readableDir(testdataDir), true); +}); + +Deno.test("[fs] readableDirSync", function () { + assertEquals( + readableDirSync(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(readableDirSync(testdataDir), true); +}); + +Deno.test("[fs] readableFileLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(await readableDir(path.join(testdataDir, "0-link")), false); +}); + +Deno.test("[fs] readableFileLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(readableDirSync(path.join(testdataDir, "0-link")), false); +}); + +Deno.test("[fs] readableDirLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(await readableDir(path.join(testdataDir, "testdata-link")), true); +}); + +Deno.test("[fs] readableDirLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(readableDirSync(path.join(testdataDir, "testdata-link")), true); +}); + diff --git a/fs/readable_file.ts b/fs/readable_file.ts new file mode 100644 index 000000000000..e3687e98dae2 --- /dev/null +++ b/fs/readable_file.ts @@ -0,0 +1,79 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +/** + * Test whether or not the given path is a readable file by checking with the file system. + * + * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. + * + * Bad: + * ```ts + * import { readableFile } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * + * if (await readableFile("./foo.txt")) { + * await Deno.remove("./foo.txt"); + * } + * ``` + * + * Good: + * ```ts + * // Notice no use of readableFile + * try { + * await Deno.remove("./foo.txt"); + * } catch (error) { + * if (!(error instanceof Deno.errors.NotFound)) { + * throw error; + * } + * // Do nothing... + * } + * ``` + * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use + */ +export async function readableFile(filePath: string | URL): Promise { + try { + return (await Deno.stat(filePath)).isFile; + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + return false; + } + throw error; + } +} + +/** + * Test whether or not the given path is a readable file by checking with the file system. + * + * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. + * + * Bad: + * ```ts + * import { readableFile } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * + * if (await readableFile("./foo.txt")) { + * await Deno.remove("./foo.txt"); + * } + * ``` + * + * Good: + * ```ts + * // Notice no use of readableFile + * try { + * await Deno.remove("./foo.txt"); + * } catch (error) { + * if (!(error instanceof Deno.errors.NotFound)) { + * throw error; + * } + * // Do nothing... + * } + * ``` + * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use + */ +export function readableFileSync(filePath: string | URL): boolean { + try { + return Deno.statSync(filePath).isFile; + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + return false; + } + throw error; + } +} diff --git a/fs/readable_file_test.ts b/fs/readable_file_test.ts new file mode 100644 index 000000000000..fad8b12d8ce7 --- /dev/null +++ b/fs/readable_file_test.ts @@ -0,0 +1,61 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "../testing/asserts.ts"; +import * as path from "../path/mod.ts"; +import { readableFile, readableFileSync } from "./readable_file.ts"; + +const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); +const testdataDir = path.resolve(moduleDir, "testdata"); + +Deno.test("[fs] readableFile", async function () { + assertEquals( + await readableFile(path.join(testdataDir, "not_exist_file.ts")), + false, + ); + assertEquals(await readableFile(path.join(testdataDir, "0.ts")), true); +}); + +Deno.test("[fs] readableFileSync", function () { + assertEquals(readableFileSync(path.join(testdataDir, "not_exist_file.ts")), false); + assertEquals(readableFileSync(path.join(testdataDir, "0.ts")), true); +}); + +Deno.test("[fs] readableDir", async function () { + assertEquals( + await readableFile(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(await readableFile(testdataDir), false); +}); + +Deno.test("[fs] readableDirSync", function () { + assertEquals( + readableFileSync(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(readableFileSync(testdataDir), false); +}); + +Deno.test("[fs] readableFileLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(await readableFile(path.join(testdataDir, "0-link")), true); +}); + +Deno.test("[fs] readableFileLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(readableFileSync(path.join(testdataDir, "0-link")), true); +}); + +Deno.test("[fs] readableDirLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(await readableFile(path.join(testdataDir, "testdata-link")), false); +}); + +Deno.test("[fs] readableDirLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(readableFileSync(path.join(testdataDir, "testdata-link")), false); +}); + diff --git a/fs/readable_test.ts b/fs/readable_test.ts new file mode 100644 index 000000000000..b2efe89a5ab4 --- /dev/null +++ b/fs/readable_test.ts @@ -0,0 +1,61 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "../testing/asserts.ts"; +import * as path from "../path/mod.ts"; +import { readable, readableSync } from "./readable.ts"; + +const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); +const testdataDir = path.resolve(moduleDir, "testdata"); + +Deno.test("[fs] readableFile", async function () { + assertEquals( + await readable(path.join(testdataDir, "not_exist_file.ts")), + false, + ); + assertEquals(await readable(path.join(testdataDir, "0.ts")), true); +}); + +Deno.test("[fs] readableFileSync", function () { + assertEquals(readableSync(path.join(testdataDir, "not_exist_file.ts")), false); + assertEquals(readableSync(path.join(testdataDir, "0.ts")), true); +}); + +Deno.test("[fs] readableDir", async function () { + assertEquals( + await readable(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(await readable(testdataDir), true); +}); + +Deno.test("[fs] readableDirSync", function () { + assertEquals( + readableSync(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(readableSync(testdataDir), true); +}); + +Deno.test("[fs] readableFileLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(await readable(path.join(testdataDir, "0-link")), true); +}); + +Deno.test("[fs] readableFileLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(readableSync(path.join(testdataDir, "0-link")), true); +}); + +Deno.test("[fs] readableDirLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(await readable(path.join(testdataDir, "testdata-link")), true); +}); + +Deno.test("[fs] readableDirLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(readableSync(path.join(testdataDir, "testdata-link")), true); +}); + diff --git a/fs/testdata/testdata-link b/fs/testdata/testdata-link new file mode 120000 index 000000000000..945c9b46d684 --- /dev/null +++ b/fs/testdata/testdata-link @@ -0,0 +1 @@ +. \ No newline at end of file From 4c25f35509c0807f3db767b57cd108c38b4d758a Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Tue, 18 Oct 2022 03:07:14 +0200 Subject: [PATCH 02/23] docs(fs): correct `readableSync`, `readableDirSync` and `readableFileSync` examples --- fs/readable.ts | 10 +++++----- fs/readable_dir.ts | 10 +++++----- fs/readable_file.ts | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/fs/readable.ts b/fs/readable.ts index 96fad8922e56..e43949875951 100644 --- a/fs/readable.ts +++ b/fs/readable.ts @@ -47,18 +47,18 @@ export async function readable(path: string | URL): Promise { * * Bad: * ```ts - * import { readable } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * import { readableSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * - * if (await readable("./foo")) { - * await Deno.remove("./foo"); + * if (readableSync("./foo")) { + * Deno.removeSync("./foo"); * } * ``` * * Good: * ```ts - * // Notice no use of readable + * // Notice no use of readableSync * try { - * await Deno.remove("./foo", { recursive: true }); + * Deno.removeSync("./foo", { recursive: true }); * } catch (error) { * if (!(error instanceof Deno.errors.NotFound)) { * throw error; diff --git a/fs/readable_dir.ts b/fs/readable_dir.ts index 7449bd454426..66fd39658378 100644 --- a/fs/readable_dir.ts +++ b/fs/readable_dir.ts @@ -46,18 +46,18 @@ export async function readableDir(dirPath: string | URL): Promise { * * Bad: * ```ts - * import { readableDir } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * import { readableDirSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * - * if (await readableDir("./foo")) { - * await Deno.remove("./foo"); + * if (readableDirSync("./foo")) { + * Deno.removeSync("./foo"); * } * ``` * * Good: * ```ts - * // Notice no use of readableDir + * // Notice no use of readableDirSync * try { - * await Deno.remove("./foo", { recursive: true }); + * Deno.removeSync("./foo", { recursive: true }); * } catch (error) { * if (!(error instanceof Deno.errors.NotFound)) { * throw error; diff --git a/fs/readable_file.ts b/fs/readable_file.ts index e3687e98dae2..19398645fe89 100644 --- a/fs/readable_file.ts +++ b/fs/readable_file.ts @@ -46,18 +46,18 @@ export async function readableFile(filePath: string | URL): Promise { * * Bad: * ```ts - * import { readableFile } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * import { readableFileSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * - * if (await readableFile("./foo.txt")) { - * await Deno.remove("./foo.txt"); + * if (readableFileSync("./foo.txt")) { + * Deno.removeSync("./foo.txt"); * } * ``` * * Good: * ```ts - * // Notice no use of readableFile + * // Notice no use of readableFileSync * try { - * await Deno.remove("./foo.txt"); + * Deno.removeSync("./foo.txt"); * } catch (error) { * if (!(error instanceof Deno.errors.NotFound)) { * throw error; From 591dc1321b4b4a3e7e891aaeda98daa3ee2e6cc1 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Tue, 18 Oct 2022 15:21:28 +0200 Subject: [PATCH 03/23] refactor(fs): `readable*` to `isReadable*` --- fs/{readable.ts => is_readable.ts} | 20 +++--- fs/{readable_dir.ts => is_readable_dir.ts} | 16 ++--- fs/is_readable_dir_test.ts | 69 ++++++++++++++++++++ fs/{readable_file.ts => is_readable_file.ts} | 16 ++--- fs/is_readable_file_test.ts | 69 ++++++++++++++++++++ fs/is_readable_test.ts | 63 ++++++++++++++++++ fs/mod.ts | 6 +- fs/readable_dir_test.ts | 61 ----------------- fs/readable_file_test.ts | 61 ----------------- fs/readable_test.ts | 61 ----------------- 10 files changed, 230 insertions(+), 212 deletions(-) rename fs/{readable.ts => is_readable.ts} (74%) rename fs/{readable_dir.ts => is_readable_dir.ts} (78%) create mode 100644 fs/is_readable_dir_test.ts rename fs/{readable_file.ts => is_readable_file.ts} (77%) create mode 100644 fs/is_readable_file_test.ts create mode 100644 fs/is_readable_test.ts delete mode 100644 fs/readable_dir_test.ts delete mode 100644 fs/readable_file_test.ts delete mode 100644 fs/readable_test.ts diff --git a/fs/readable.ts b/fs/is_readable.ts similarity index 74% rename from fs/readable.ts rename to fs/is_readable.ts index e43949875951..6acbb64ae992 100644 --- a/fs/readable.ts +++ b/fs/is_readable.ts @@ -1,22 +1,22 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. /** - * Test whether or not the given path is readable by checking with the file system. To check simultaneously if the path is either a file or a directory please use `readableFile` or `readableDir` instead. + * Test whether or not the given path is readable by checking with the file system. To check simultaneously if the path is either a file or a directory please use `isReadableFile` or `isReadableDir` instead. * * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. * * Bad: * ```ts - * import { readable } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * import { isReadable } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * - * if (await readable("./foo")) { + * if (await isReadable("./foo")) { * await Deno.remove("./foo"); * } * ``` * * Good: * ```ts - * // Notice no use of readable + * // Notice no use of isReadable * try { * await Deno.remove("./foo", { recursive: true }); * } catch (error) { @@ -28,7 +28,7 @@ * ``` * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use */ -export async function readable(path: string | URL): Promise { +export async function isReadable(path: string | URL): Promise { try { await Deno.stat(path); return true; @@ -41,22 +41,22 @@ export async function readable(path: string | URL): Promise { } /** - * Test whether or not the given path is readable by checking with the file system. To check simultaneously if the path is either a file or a directory please use `readableFile` or `readableDir` instead. + * Test whether or not the given path is readable by checking with the file system. To check simultaneously if the path is either a file or a directory please use `isReadableFile` or `isReadableDir` instead. * * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. * * Bad: * ```ts - * import { readableSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * import { isReadableSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * - * if (readableSync("./foo")) { + * if (isReadableSync("./foo")) { * Deno.removeSync("./foo"); * } * ``` * * Good: * ```ts - * // Notice no use of readableSync + * // Notice no use of isReadableSync * try { * Deno.removeSync("./foo", { recursive: true }); * } catch (error) { @@ -68,7 +68,7 @@ export async function readable(path: string | URL): Promise { * ``` * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use */ -export function readableSync(path: string | URL): boolean { +export function isReadableSync(path: string | URL): boolean { try { Deno.statSync(path); return true; diff --git a/fs/readable_dir.ts b/fs/is_readable_dir.ts similarity index 78% rename from fs/readable_dir.ts rename to fs/is_readable_dir.ts index 66fd39658378..6390ffcfc6f5 100644 --- a/fs/readable_dir.ts +++ b/fs/is_readable_dir.ts @@ -7,16 +7,16 @@ * * Bad: * ```ts - * import { readableDir } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * import { isReadableDir } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * - * if (await readableDir("./foo")) { + * if (await isReadableDir("./foo")) { * await Deno.remove("./foo"); * } * ``` * * Good: * ```ts - * // Notice no use of readableDir + * // Notice no use of isReadableDir * try { * await Deno.remove("./foo", { recursive: true }); * } catch (error) { @@ -28,7 +28,7 @@ * ``` * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use */ -export async function readableDir(dirPath: string | URL): Promise { +export async function isReadableDir(dirPath: string | URL): Promise { try { return (await Deno.stat(dirPath)).isDirectory; } catch (error) { @@ -46,16 +46,16 @@ export async function readableDir(dirPath: string | URL): Promise { * * Bad: * ```ts - * import { readableDirSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * import { isReadableDirSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * - * if (readableDirSync("./foo")) { + * if (isReadableDirSync("./foo")) { * Deno.removeSync("./foo"); * } * ``` * * Good: * ```ts - * // Notice no use of readableDirSync + * // Notice no use of isReadableDirSync * try { * Deno.removeSync("./foo", { recursive: true }); * } catch (error) { @@ -67,7 +67,7 @@ export async function readableDir(dirPath: string | URL): Promise { * ``` * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use */ -export function readableDirSync(dirPath: string | URL): boolean { +export function isReadableDirSync(dirPath: string | URL): boolean { try { return Deno.statSync(dirPath).isDirectory; } catch (error) { diff --git a/fs/is_readable_dir_test.ts b/fs/is_readable_dir_test.ts new file mode 100644 index 000000000000..c91bdc060d35 --- /dev/null +++ b/fs/is_readable_dir_test.ts @@ -0,0 +1,69 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "../testing/asserts.ts"; +import * as path from "../path/mod.ts"; +import { isReadableDir, isReadableDirSync } from "./is_readable_dir.ts"; + +const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); +const testdataDir = path.resolve(moduleDir, "testdata"); + +Deno.test("[fs] isReadableFile", async function () { + assertEquals( + await isReadableDir(path.join(testdataDir, "not_exist_file.ts")), + false, + ); + assertEquals(await isReadableDir(path.join(testdataDir, "0.ts")), false); +}); + +Deno.test("[fs] isReadableFileSync", function () { + assertEquals( + isReadableDirSync(path.join(testdataDir, "not_exist_file.ts")), + false, + ); + assertEquals(isReadableDirSync(path.join(testdataDir, "0.ts")), false); +}); + +Deno.test("[fs] isReadableDir", async function () { + assertEquals( + await isReadableDir(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(await isReadableDir(testdataDir), true); +}); + +Deno.test("[fs] isReadableDirSync", function () { + assertEquals( + isReadableDirSync(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(isReadableDirSync(testdataDir), true); +}); + +Deno.test("[fs] isReadableFileLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(await isReadableDir(path.join(testdataDir, "0-link")), false); +}); + +Deno.test("[fs] isReadableFileLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(isReadableDirSync(path.join(testdataDir, "0-link")), false); +}); + +Deno.test("[fs] isReadableDirLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals( + await isReadableDir(path.join(testdataDir, "testdata-link")), + true, + ); +}); + +Deno.test("[fs] isReadableDirLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals( + isReadableDirSync(path.join(testdataDir, "testdata-link")), + true, + ); +}); diff --git a/fs/readable_file.ts b/fs/is_readable_file.ts similarity index 77% rename from fs/readable_file.ts rename to fs/is_readable_file.ts index 19398645fe89..c52f94ea8f7c 100644 --- a/fs/readable_file.ts +++ b/fs/is_readable_file.ts @@ -7,16 +7,16 @@ * * Bad: * ```ts - * import { readableFile } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * import { isReadableFile } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * - * if (await readableFile("./foo.txt")) { + * if (await isReadableFile("./foo.txt")) { * await Deno.remove("./foo.txt"); * } * ``` * * Good: * ```ts - * // Notice no use of readableFile + * // Notice no use of isReadableFile * try { * await Deno.remove("./foo.txt"); * } catch (error) { @@ -28,7 +28,7 @@ * ``` * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use */ -export async function readableFile(filePath: string | URL): Promise { +export async function isReadableFile(filePath: string | URL): Promise { try { return (await Deno.stat(filePath)).isFile; } catch (error) { @@ -46,16 +46,16 @@ export async function readableFile(filePath: string | URL): Promise { * * Bad: * ```ts - * import { readableFileSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * import { isReadableFileSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * - * if (readableFileSync("./foo.txt")) { + * if (isReadableFileSync("./foo.txt")) { * Deno.removeSync("./foo.txt"); * } * ``` * * Good: * ```ts - * // Notice no use of readableFileSync + * // Notice no use of isReadableFileSync * try { * Deno.removeSync("./foo.txt"); * } catch (error) { @@ -67,7 +67,7 @@ export async function readableFile(filePath: string | URL): Promise { * ``` * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use */ -export function readableFileSync(filePath: string | URL): boolean { +export function isReadableFileSync(filePath: string | URL): boolean { try { return Deno.statSync(filePath).isFile; } catch (error) { diff --git a/fs/is_readable_file_test.ts b/fs/is_readable_file_test.ts new file mode 100644 index 000000000000..287db781b9cf --- /dev/null +++ b/fs/is_readable_file_test.ts @@ -0,0 +1,69 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "../testing/asserts.ts"; +import * as path from "../path/mod.ts"; +import { isReadableFile, isReadableFileSync } from "./is_readable_file.ts"; + +const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); +const testdataDir = path.resolve(moduleDir, "testdata"); + +Deno.test("[fs] isReadableFile", async function () { + assertEquals( + await isReadableFile(path.join(testdataDir, "not_exist_file.ts")), + false, + ); + assertEquals(await isReadableFile(path.join(testdataDir, "0.ts")), true); +}); + +Deno.test("[fs] isReadableFileSync", function () { + assertEquals( + isReadableFileSync(path.join(testdataDir, "not_exist_file.ts")), + false, + ); + assertEquals(isReadableFileSync(path.join(testdataDir, "0.ts")), true); +}); + +Deno.test("[fs] isReadableDir", async function () { + assertEquals( + await isReadableFile(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(await isReadableFile(testdataDir), false); +}); + +Deno.test("[fs] isReadableDirSync", function () { + assertEquals( + isReadableFileSync(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(isReadableFileSync(testdataDir), false); +}); + +Deno.test("[fs] isReadableFileLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(await isReadableFile(path.join(testdataDir, "0-link")), true); +}); + +Deno.test("[fs] isReadableFileLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(isReadableFileSync(path.join(testdataDir, "0-link")), true); +}); + +Deno.test("[fs] isReadableDirLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals( + await isReadableFile(path.join(testdataDir, "testdata-link")), + false, + ); +}); + +Deno.test("[fs] isReadableDirLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals( + isReadableFileSync(path.join(testdataDir, "testdata-link")), + false, + ); +}); diff --git a/fs/is_readable_test.ts b/fs/is_readable_test.ts new file mode 100644 index 000000000000..583198c2bba5 --- /dev/null +++ b/fs/is_readable_test.ts @@ -0,0 +1,63 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +import { assertEquals } from "../testing/asserts.ts"; +import * as path from "../path/mod.ts"; +import { isReadable, isReadableSync } from "./is_readable.ts"; + +const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); +const testdataDir = path.resolve(moduleDir, "testdata"); + +Deno.test("[fs] isReadableFile", async function () { + assertEquals( + await isReadable(path.join(testdataDir, "not_exist_file.ts")), + false, + ); + assertEquals(await isReadable(path.join(testdataDir, "0.ts")), true); +}); + +Deno.test("[fs] isReadableFileSync", function () { + assertEquals( + isReadableSync(path.join(testdataDir, "not_exist_file.ts")), + false, + ); + assertEquals(isReadableSync(path.join(testdataDir, "0.ts")), true); +}); + +Deno.test("[fs] isReadableDir", async function () { + assertEquals( + await isReadable(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(await isReadable(testdataDir), true); +}); + +Deno.test("[fs] isReadableDirSync", function () { + assertEquals( + isReadableSync(path.join(testdataDir, "not_exist_directory")), + false, + ); + assertEquals(isReadableSync(testdataDir), true); +}); + +Deno.test("[fs] isReadableFileLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(await isReadable(path.join(testdataDir, "0-link")), true); +}); + +Deno.test("[fs] isReadableFileLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(isReadableSync(path.join(testdataDir, "0-link")), true); +}); + +Deno.test("[fs] isReadableDirLink", async function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(await isReadable(path.join(testdataDir, "testdata-link")), true); +}); + +Deno.test("[fs] isReadableDirLinkSync", function () { + // TODO(axetroy): generate link file use Deno api instead of set a link file + // in repository + assertEquals(isReadableSync(path.join(testdataDir, "testdata-link")), true); +}); diff --git a/fs/mod.ts b/fs/mod.ts index 288bb2c396a4..da76a2e6ffb6 100644 --- a/fs/mod.ts +++ b/fs/mod.ts @@ -10,9 +10,9 @@ export * from "./ensure_dir.ts"; export * from "./ensure_file.ts"; export * from "./ensure_link.ts"; export * from "./ensure_symlink.ts"; -export * from "./readable.ts"; -export * from "./readable_dir.ts"; -export * from "./readable_file.ts"; +export * from "./is_readable.ts"; +export * from "./is_readable_dir.ts"; +export * from "./is_readable_file.ts"; export * from "./exists.ts"; export * from "./expand_glob.ts"; export * from "./move.ts"; diff --git a/fs/readable_dir_test.ts b/fs/readable_dir_test.ts deleted file mode 100644 index 1d1d730bfc73..000000000000 --- a/fs/readable_dir_test.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "../testing/asserts.ts"; -import * as path from "../path/mod.ts"; -import { readableDir, readableDirSync } from "./readable_dir.ts"; - -const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); -const testdataDir = path.resolve(moduleDir, "testdata"); - -Deno.test("[fs] readableFile", async function () { - assertEquals( - await readableDir(path.join(testdataDir, "not_exist_file.ts")), - false, - ); - assertEquals(await readableDir(path.join(testdataDir, "0.ts")), false); -}); - -Deno.test("[fs] readableFileSync", function () { - assertEquals(readableDirSync(path.join(testdataDir, "not_exist_file.ts")), false); - assertEquals(readableDirSync(path.join(testdataDir, "0.ts")), false); -}); - -Deno.test("[fs] readableDir", async function () { - assertEquals( - await readableDir(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(await readableDir(testdataDir), true); -}); - -Deno.test("[fs] readableDirSync", function () { - assertEquals( - readableDirSync(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(readableDirSync(testdataDir), true); -}); - -Deno.test("[fs] readableFileLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(await readableDir(path.join(testdataDir, "0-link")), false); -}); - -Deno.test("[fs] readableFileLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(readableDirSync(path.join(testdataDir, "0-link")), false); -}); - -Deno.test("[fs] readableDirLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(await readableDir(path.join(testdataDir, "testdata-link")), true); -}); - -Deno.test("[fs] readableDirLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(readableDirSync(path.join(testdataDir, "testdata-link")), true); -}); - diff --git a/fs/readable_file_test.ts b/fs/readable_file_test.ts deleted file mode 100644 index fad8b12d8ce7..000000000000 --- a/fs/readable_file_test.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "../testing/asserts.ts"; -import * as path from "../path/mod.ts"; -import { readableFile, readableFileSync } from "./readable_file.ts"; - -const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); -const testdataDir = path.resolve(moduleDir, "testdata"); - -Deno.test("[fs] readableFile", async function () { - assertEquals( - await readableFile(path.join(testdataDir, "not_exist_file.ts")), - false, - ); - assertEquals(await readableFile(path.join(testdataDir, "0.ts")), true); -}); - -Deno.test("[fs] readableFileSync", function () { - assertEquals(readableFileSync(path.join(testdataDir, "not_exist_file.ts")), false); - assertEquals(readableFileSync(path.join(testdataDir, "0.ts")), true); -}); - -Deno.test("[fs] readableDir", async function () { - assertEquals( - await readableFile(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(await readableFile(testdataDir), false); -}); - -Deno.test("[fs] readableDirSync", function () { - assertEquals( - readableFileSync(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(readableFileSync(testdataDir), false); -}); - -Deno.test("[fs] readableFileLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(await readableFile(path.join(testdataDir, "0-link")), true); -}); - -Deno.test("[fs] readableFileLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(readableFileSync(path.join(testdataDir, "0-link")), true); -}); - -Deno.test("[fs] readableDirLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(await readableFile(path.join(testdataDir, "testdata-link")), false); -}); - -Deno.test("[fs] readableDirLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(readableFileSync(path.join(testdataDir, "testdata-link")), false); -}); - diff --git a/fs/readable_test.ts b/fs/readable_test.ts deleted file mode 100644 index b2efe89a5ab4..000000000000 --- a/fs/readable_test.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "../testing/asserts.ts"; -import * as path from "../path/mod.ts"; -import { readable, readableSync } from "./readable.ts"; - -const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); -const testdataDir = path.resolve(moduleDir, "testdata"); - -Deno.test("[fs] readableFile", async function () { - assertEquals( - await readable(path.join(testdataDir, "not_exist_file.ts")), - false, - ); - assertEquals(await readable(path.join(testdataDir, "0.ts")), true); -}); - -Deno.test("[fs] readableFileSync", function () { - assertEquals(readableSync(path.join(testdataDir, "not_exist_file.ts")), false); - assertEquals(readableSync(path.join(testdataDir, "0.ts")), true); -}); - -Deno.test("[fs] readableDir", async function () { - assertEquals( - await readable(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(await readable(testdataDir), true); -}); - -Deno.test("[fs] readableDirSync", function () { - assertEquals( - readableSync(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(readableSync(testdataDir), true); -}); - -Deno.test("[fs] readableFileLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(await readable(path.join(testdataDir, "0-link")), true); -}); - -Deno.test("[fs] readableFileLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(readableSync(path.join(testdataDir, "0-link")), true); -}); - -Deno.test("[fs] readableDirLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(await readable(path.join(testdataDir, "testdata-link")), true); -}); - -Deno.test("[fs] readableDirLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(readableSync(path.join(testdataDir, "testdata-link")), true); -}); - From 99dc87a8db21bcb1b5d24f683f5841772dfc7b6d Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Wed, 19 Oct 2022 03:58:32 +0200 Subject: [PATCH 04/23] fix(fs): `isReadable*` handles permissions --- fs/is_readable.ts | 30 +++++- fs/is_readable_dir.ts | 34 ++++++- fs/is_readable_dir_test.ts | 177 +++++++++++++++++++++++++-------- fs/is_readable_file.ts | 34 ++++++- fs/is_readable_file_test.ts | 177 +++++++++++++++++++++++++-------- fs/is_readable_test.ts | 193 +++++++++++++++++++++++++++++------- fs/testdata/testdata-link | 1 - 7 files changed, 523 insertions(+), 123 deletions(-) delete mode 120000 fs/testdata/testdata-link diff --git a/fs/is_readable.ts b/fs/is_readable.ts index 6acbb64ae992..ae242646dbc1 100644 --- a/fs/is_readable.ts +++ b/fs/is_readable.ts @@ -30,12 +30,23 @@ */ export async function isReadable(path: string | URL): Promise { try { - await Deno.stat(path); - return true; + const stat: Deno.FileInfo = await Deno.stat(path); + if (stat.mode == null) { + return true; // Non POSIX exclusive + } + if (Deno.getUid() == stat.uid) { + return (stat.mode & 0o400) == 0o400; + } else if (Deno.getGid() == stat.gid) { + return (stat.mode & 0o040) == 0o040; + } + return (stat.mode & 0o004) == 0o004; } catch (error) { if (error instanceof Deno.errors.NotFound) { return false; } + if (error instanceof Deno.errors.PermissionDenied) { + return false; // Windows exclusive + } throw error; } } @@ -70,12 +81,23 @@ export async function isReadable(path: string | URL): Promise { */ export function isReadableSync(path: string | URL): boolean { try { - Deno.statSync(path); - return true; + const stat: Deno.FileInfo = Deno.statSync(path); + if (stat.mode == null) { + return true; // Non POSIX exclusive + } + if (Deno.getUid() == stat.uid) { + return (stat.mode & 0o400) == 0o400; + } else if (Deno.getGid() == stat.gid) { + return (stat.mode & 0o040) == 0o040; + } + return (stat.mode & 0o004) == 0o004; } catch (error) { if (error instanceof Deno.errors.NotFound) { return false; } + if (error instanceof Deno.errors.PermissionDenied) { + return false; // Windows exclusive + } throw error; } } diff --git a/fs/is_readable_dir.ts b/fs/is_readable_dir.ts index 6390ffcfc6f5..d80f57cdf008 100644 --- a/fs/is_readable_dir.ts +++ b/fs/is_readable_dir.ts @@ -30,11 +30,26 @@ */ export async function isReadableDir(dirPath: string | URL): Promise { try { - return (await Deno.stat(dirPath)).isDirectory; + const stat: Deno.FileInfo = await Deno.stat(dirPath); + if (!stat.isDirectory) { + return false; + } + if (stat.mode == null) { + return true; // Non POSIX exclusive + } + if (Deno.getUid() == stat.uid) { + return (stat.mode & 0o400) == 0o400; + } else if (Deno.getGid() == stat.gid) { + return (stat.mode & 0o040) == 0o040; + } + return (stat.mode & 0o004) == 0o004; } catch (error) { if (error instanceof Deno.errors.NotFound) { return false; } + if (error instanceof Deno.errors.PermissionDenied) { + return false; // Windows exclusive + } throw error; } } @@ -69,11 +84,26 @@ export async function isReadableDir(dirPath: string | URL): Promise { */ export function isReadableDirSync(dirPath: string | URL): boolean { try { - return Deno.statSync(dirPath).isDirectory; + const stat: Deno.FileInfo = Deno.statSync(dirPath); + if (!stat.isDirectory) { + return false; + } + if (stat.mode == null) { + return true; // Non POSIX exclusive + } + if (Deno.getUid() == stat.uid) { + return (stat.mode & 0o400) == 0o400; + } else if (Deno.getGid() == stat.gid) { + return (stat.mode & 0o040) == 0o040; + } + return (stat.mode & 0o004) == 0o004; } catch (error) { if (error instanceof Deno.errors.NotFound) { return false; } + if (error instanceof Deno.errors.PermissionDenied) { + return false; // Windows exclusive + } throw error; } } diff --git a/fs/is_readable_dir_test.ts b/fs/is_readable_dir_test.ts index c91bdc060d35..d89b1e7a2202 100644 --- a/fs/is_readable_dir_test.ts +++ b/fs/is_readable_dir_test.ts @@ -3,67 +3,164 @@ import { assertEquals } from "../testing/asserts.ts"; import * as path from "../path/mod.ts"; import { isReadableDir, isReadableDirSync } from "./is_readable_dir.ts"; -const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); -const testdataDir = path.resolve(moduleDir, "testdata"); +Deno.test("[fs] isReadableNotExist", async function () { + const tempDirPath: string = await Deno.makeTempDir(); + try { + assertEquals( + await isReadableDir(path.join(tempDirPath, "not_exists")), + false, + ); + } catch (error) { + throw error; + } finally { + await Deno.remove(tempDirPath, { recursive: true }); + } +}); + +Deno.test("[fs] isReadableNotExistSync", function () { + const tempDirPath: string = Deno.makeTempDirSync(); + try { + assertEquals( + isReadableDirSync(path.join(tempDirPath, "not_exists")), + false, + ); + } catch (error) { + throw error; + } finally { + Deno.removeSync(tempDirPath, { recursive: true }); + } +}); Deno.test("[fs] isReadableFile", async function () { - assertEquals( - await isReadableDir(path.join(testdataDir, "not_exist_file.ts")), - false, - ); - assertEquals(await isReadableDir(path.join(testdataDir, "0.ts")), false); + const tempDirPath: string = await Deno.makeTempDir(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempFile: Deno.FsFile = await Deno.create(tempFilePath); + try { + assertEquals(await isReadableDir(tempFilePath), false); + } catch (error) { + throw error; + } finally { + tempFile.close(); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableFileSync", function () { - assertEquals( - isReadableDirSync(path.join(testdataDir, "not_exist_file.ts")), - false, - ); - assertEquals(isReadableDirSync(path.join(testdataDir, "0.ts")), false); + const tempDirPath: string = Deno.makeTempDirSync(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); + try { + assertEquals(isReadableDirSync(tempFilePath), false); + } catch (error) { + throw error; + } finally { + tempFile.close(); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDir", async function () { - assertEquals( - await isReadableDir(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(await isReadableDir(testdataDir), true); + const tempDirPath: string = await Deno.makeTempDir(); + try { + assertEquals(isReadableDirSync(tempDirPath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempDirPath, 0o000); + assertEquals(await isReadableDir(tempDirPath), false); + } + } catch (error) { + throw error; + } finally { + await Deno.chmod(tempDirPath, 0o755); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDirSync", function () { - assertEquals( - isReadableDirSync(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(isReadableDirSync(testdataDir), true); + const tempDirPath: string = Deno.makeTempDirSync(); + try { + assertEquals(isReadableDirSync(tempDirPath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempDirPath, 0o000); + assertEquals(isReadableDirSync(tempDirPath), false); + } + } catch (error) { + throw error; + } finally { + Deno.chmodSync(tempDirPath, 0o755); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableFileLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(await isReadableDir(path.join(testdataDir, "0-link")), false); + const tempDirPath: string = await Deno.makeTempDir(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempLinkFilePath: string = path.join(tempDirPath, "0-link.ts"); + const tempFile: Deno.FsFile = await Deno.create(tempFilePath); + try { + await Deno.symlink(tempFilePath, tempLinkFilePath); + assertEquals(await isReadableDir(tempLinkFilePath), false); + } catch (error) { + throw error; + } finally { + tempFile.close(); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableFileLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(isReadableDirSync(path.join(testdataDir, "0-link")), false); + const tempDirPath: string = Deno.makeTempDirSync(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempLinkFilePath: string = path.join(tempDirPath, "0-link.ts"); + const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); + try { + Deno.symlinkSync(tempFilePath, tempLinkFilePath); + assertEquals(isReadableDirSync(tempLinkFilePath), false); + } catch (error) { + throw error; + } finally { + tempFile.close(); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDirLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals( - await isReadableDir(path.join(testdataDir, "testdata-link")), - true, - ); + const tempDirPath: string = await Deno.makeTempDir(); + const tempLinkDirPath: string = path.join(tempDirPath, "temp-link"); + try { + await Deno.symlink(tempDirPath, tempLinkDirPath); + assertEquals(await isReadableDir(tempLinkDirPath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempDirPath, 0o000); + assertEquals(await isReadableDir(tempLinkDirPath), false); + // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it + } + } catch (error) { + throw error; + } finally { + await Deno.chmod(tempDirPath, 0o755); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDirLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals( - isReadableDirSync(path.join(testdataDir, "testdata-link")), - true, - ); + const tempDirPath: string = Deno.makeTempDirSync(); + const tempLinkDirPath: string = path.join(tempDirPath, "temp-link"); + try { + Deno.symlinkSync(tempDirPath, tempLinkDirPath); + assertEquals(isReadableDirSync(tempLinkDirPath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempDirPath, 0o000); + assertEquals(isReadableDirSync(tempLinkDirPath), false); + // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it + } + } catch (error) { + throw error; + } finally { + Deno.chmodSync(tempDirPath, 0o755); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); diff --git a/fs/is_readable_file.ts b/fs/is_readable_file.ts index c52f94ea8f7c..761044cfb5f1 100644 --- a/fs/is_readable_file.ts +++ b/fs/is_readable_file.ts @@ -30,11 +30,26 @@ */ export async function isReadableFile(filePath: string | URL): Promise { try { - return (await Deno.stat(filePath)).isFile; + const stat: Deno.FileInfo = await Deno.stat(filePath); + if (!stat.isFile) { + return false; + } + if (stat.mode == null) { + return true; // Non POSIX exclusive + } + if (Deno.getUid() == stat.uid) { + return (stat.mode & 0o400) == 0o400; + } else if (Deno.getGid() == stat.gid) { + return (stat.mode & 0o040) == 0o040; + } + return (stat.mode & 0o004) == 0o004; } catch (error) { if (error instanceof Deno.errors.NotFound) { return false; } + if (error instanceof Deno.errors.PermissionDenied) { + return false; // Windows exclusive + } throw error; } } @@ -69,11 +84,26 @@ export async function isReadableFile(filePath: string | URL): Promise { */ export function isReadableFileSync(filePath: string | URL): boolean { try { - return Deno.statSync(filePath).isFile; + const stat: Deno.FileInfo = Deno.statSync(filePath); + if (!stat.isFile) { + return false; + } + if (stat.mode == null) { + return true; // Non POSIX exclusive + } + if (Deno.getUid() == stat.uid) { + return (stat.mode & 0o400) == 0o400; + } else if (Deno.getGid() == stat.gid) { + return (stat.mode & 0o040) == 0o040; + } + return (stat.mode & 0o004) == 0o004; } catch (error) { if (error instanceof Deno.errors.NotFound) { return false; } + if (error instanceof Deno.errors.PermissionDenied) { + return false; // Windows exclusive + } throw error; } } diff --git a/fs/is_readable_file_test.ts b/fs/is_readable_file_test.ts index 287db781b9cf..866511620bce 100644 --- a/fs/is_readable_file_test.ts +++ b/fs/is_readable_file_test.ts @@ -3,67 +3,164 @@ import { assertEquals } from "../testing/asserts.ts"; import * as path from "../path/mod.ts"; import { isReadableFile, isReadableFileSync } from "./is_readable_file.ts"; -const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); -const testdataDir = path.resolve(moduleDir, "testdata"); +Deno.test("[fs] isReadableNotExist", async function () { + const tempDirPath: string = await Deno.makeTempDir(); + try { + assertEquals( + await isReadableFile(path.join(tempDirPath, "not_exists")), + false, + ); + } catch (error) { + throw error; + } finally { + await Deno.remove(tempDirPath, { recursive: true }); + } +}); + +Deno.test("[fs] isReadableNotExistSync", function () { + const tempDirPath: string = Deno.makeTempDirSync(); + try { + assertEquals( + isReadableFileSync(path.join(tempDirPath, "not_exists")), + false, + ); + } catch (error) { + throw error; + } finally { + Deno.removeSync(tempDirPath, { recursive: true }); + } +}); Deno.test("[fs] isReadableFile", async function () { - assertEquals( - await isReadableFile(path.join(testdataDir, "not_exist_file.ts")), - false, - ); - assertEquals(await isReadableFile(path.join(testdataDir, "0.ts")), true); + const tempDirPath: string = await Deno.makeTempDir(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempFile: Deno.FsFile = await Deno.create(tempFilePath); + try { + assertEquals(await isReadableFile(tempFilePath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempFilePath, 0o000); + assertEquals(await isReadableFile(tempFilePath), false); + } + } catch (error) { + throw error; + } finally { + tempFile.close(); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableFileSync", function () { - assertEquals( - isReadableFileSync(path.join(testdataDir, "not_exist_file.ts")), - false, - ); - assertEquals(isReadableFileSync(path.join(testdataDir, "0.ts")), true); + const tempDirPath: string = Deno.makeTempDirSync(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); + try { + assertEquals(isReadableFileSync(tempFilePath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempFilePath, 0o000); + assertEquals(isReadableFileSync(tempFilePath), false); + } + } catch (error) { + throw error; + } finally { + tempFile.close(); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDir", async function () { - assertEquals( - await isReadableFile(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(await isReadableFile(testdataDir), false); + const tempDirPath: string = await Deno.makeTempDir(); + try { + assertEquals(isReadableFileSync(tempDirPath), false); + } catch (error) { + throw error; + } finally { + await Deno.chmod(tempDirPath, 0o755); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDirSync", function () { - assertEquals( - isReadableFileSync(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(isReadableFileSync(testdataDir), false); + const tempDirPath = Deno.makeTempDirSync(); + try { + assertEquals(isReadableFileSync(tempDirPath), false); + } catch (error) { + throw error; + } finally { + Deno.chmodSync(tempDirPath, 0o755); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableFileLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(await isReadableFile(path.join(testdataDir, "0-link")), true); + const tempDirPath = await Deno.makeTempDir(); + const tempFilePath = path.join(tempDirPath, "0.ts"); + const tempLinkFilePath = path.join(tempDirPath, "0-link.ts"); + const tempFile: Deno.FsFile = await Deno.create(tempFilePath); + try { + await Deno.symlink(tempFilePath, tempLinkFilePath); + assertEquals(await isReadableFile(tempLinkFilePath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempFilePath, 0o000); + assertEquals(await isReadableFile(tempLinkFilePath), false); + // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it + } + } catch (error) { + throw error; + } finally { + tempFile.close(); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableFileLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(isReadableFileSync(path.join(testdataDir, "0-link")), true); + const tempDirPath: string = Deno.makeTempDirSync(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempLinkFilePath: string = path.join(tempDirPath, "0-link.ts"); + const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); + try { + Deno.symlinkSync(tempFilePath, tempLinkFilePath); + assertEquals(isReadableFileSync(tempLinkFilePath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempFilePath, 0o000); + assertEquals(isReadableFileSync(tempLinkFilePath), false); + // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it + } + } catch (error) { + throw error; + } finally { + tempFile.close(); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDirLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals( - await isReadableFile(path.join(testdataDir, "testdata-link")), - false, - ); + const tempDirPath = await Deno.makeTempDir(); + const tempLinkDirPath = path.join(tempDirPath, "temp-link"); + try { + await Deno.symlink(tempDirPath, tempLinkDirPath); + assertEquals(await isReadableFile(tempLinkDirPath), false); + } catch (error) { + throw error; + } finally { + await Deno.chmod(tempDirPath, 0o755); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDirLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals( - isReadableFileSync(path.join(testdataDir, "testdata-link")), - false, - ); + const tempDirPath: string = Deno.makeTempDirSync(); + const tempLinkDirPath: string = path.join(tempDirPath, "temp-link"); + try { + Deno.symlinkSync(tempDirPath, tempLinkDirPath); + assertEquals(isReadableFileSync(tempLinkDirPath), false); + } catch (error) { + throw error; + } finally { + Deno.chmodSync(tempDirPath, 0o755); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); diff --git a/fs/is_readable_test.ts b/fs/is_readable_test.ts index 583198c2bba5..a1f388eecc55 100644 --- a/fs/is_readable_test.ts +++ b/fs/is_readable_test.ts @@ -3,61 +3,186 @@ import { assertEquals } from "../testing/asserts.ts"; import * as path from "../path/mod.ts"; import { isReadable, isReadableSync } from "./is_readable.ts"; -const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); -const testdataDir = path.resolve(moduleDir, "testdata"); +Deno.test("[fs] isReadableNotExist", async function () { + const tempDirPath: string = await Deno.makeTempDir(); + try { + assertEquals( + await isReadable(path.join(tempDirPath, "not_exists")), + false, + ); + } catch (error) { + throw error; + } finally { + await Deno.remove(tempDirPath, { recursive: true }); + } +}); + +Deno.test("[fs] isReadableNotExistSync", function () { + const tempDirPath: string = Deno.makeTempDirSync(); + try { + assertEquals( + isReadableSync(path.join(tempDirPath, "not_exists")), + false, + ); + } catch (error) { + throw error; + } finally { + Deno.removeSync(tempDirPath, { recursive: true }); + } +}); Deno.test("[fs] isReadableFile", async function () { - assertEquals( - await isReadable(path.join(testdataDir, "not_exist_file.ts")), - false, - ); - assertEquals(await isReadable(path.join(testdataDir, "0.ts")), true); + const tempDirPath: string = await Deno.makeTempDir(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempFile: Deno.FsFile = await Deno.create(tempFilePath); + try { + assertEquals(await isReadable(tempFilePath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempFilePath, 0o000); + assertEquals(await isReadable(tempFilePath), false); + } + } catch (error) { + throw error; + } finally { + tempFile.close(); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableFileSync", function () { - assertEquals( - isReadableSync(path.join(testdataDir, "not_exist_file.ts")), - false, - ); - assertEquals(isReadableSync(path.join(testdataDir, "0.ts")), true); + const tempDirPath: string = Deno.makeTempDirSync(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); + try { + assertEquals(isReadableSync(tempFilePath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempFilePath, 0o000); + assertEquals(isReadableSync(tempFilePath), false); + } + } catch (error) { + throw error; + } finally { + tempFile.close(); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDir", async function () { - assertEquals( - await isReadable(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(await isReadable(testdataDir), true); + const tempDirPath: string = await Deno.makeTempDir(); + try { + assertEquals(isReadableSync(tempDirPath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempDirPath, 0o000); + assertEquals(await isReadable(tempDirPath), false); + } + } catch (error) { + throw error; + } finally { + await Deno.chmod(tempDirPath, 0o755); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDirSync", function () { - assertEquals( - isReadableSync(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(isReadableSync(testdataDir), true); + const tempDirPath = Deno.makeTempDirSync(); + try { + assertEquals(isReadableSync(tempDirPath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempDirPath, 0o000); + assertEquals(isReadableSync(tempDirPath), false); + } + } catch (error) { + throw error; + } finally { + Deno.chmodSync(tempDirPath, 0o755); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableFileLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(await isReadable(path.join(testdataDir, "0-link")), true); + const tempDirPath = await Deno.makeTempDir(); + const tempFilePath = path.join(tempDirPath, "0.ts"); + const tempLinkFilePath = path.join(tempDirPath, "0-link.ts"); + const tempFile: Deno.FsFile = await Deno.create(tempFilePath); + try { + await Deno.symlink(tempFilePath, tempLinkFilePath); + assertEquals(await isReadable(tempLinkFilePath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempFilePath, 0o000); + assertEquals(await isReadable(tempLinkFilePath), false); + // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it + } + } catch (error) { + throw error; + } finally { + tempFile.close(); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableFileLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(isReadableSync(path.join(testdataDir, "0-link")), true); + const tempDirPath: string = Deno.makeTempDirSync(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempLinkFilePath: string = path.join(tempDirPath, "0-link.ts"); + const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); + try { + Deno.symlinkSync(tempFilePath, tempLinkFilePath); + assertEquals(isReadableSync(tempLinkFilePath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempFilePath, 0o000); + assertEquals(isReadableSync(tempLinkFilePath), false); + // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it + } + } catch (error) { + throw error; + } finally { + tempFile.close(); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDirLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(await isReadable(path.join(testdataDir, "testdata-link")), true); + const tempDirPath = await Deno.makeTempDir(); + const tempLinkDirPath = path.join(tempDirPath, "temp-link"); + try { + await Deno.symlink(tempDirPath, tempLinkDirPath); + assertEquals(await isReadable(tempLinkDirPath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempDirPath, 0o000); + assertEquals(await isReadable(tempLinkDirPath), false); + // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it + } + } catch (error) { + throw error; + } finally { + await Deno.chmod(tempDirPath, 0o755); + await Deno.remove(tempDirPath, { recursive: true }); + } }); Deno.test("[fs] isReadableDirLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(isReadableSync(path.join(testdataDir, "testdata-link")), true); + const tempDirPath: string = Deno.makeTempDirSync(); + const tempLinkDirPath: string = path.join(tempDirPath, "temp-link"); + try { + Deno.symlinkSync(tempDirPath, tempLinkDirPath); + assertEquals(isReadableSync(tempLinkDirPath), true); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempDirPath, 0o000); + assertEquals(isReadableSync(tempLinkDirPath), false); + // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it + } + } catch (error) { + throw error; + } finally { + Deno.chmodSync(tempDirPath, 0o755); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); diff --git a/fs/testdata/testdata-link b/fs/testdata/testdata-link deleted file mode 120000 index 945c9b46d684..000000000000 --- a/fs/testdata/testdata-link +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file From 47e2c30f5cf565b7a3baee0473a950b89cae748a Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Wed, 19 Oct 2022 04:49:06 +0200 Subject: [PATCH 05/23] chore(fs): Remove irrelevant TODOs --- fs/is_readable_dir_test.ts | 2 -- fs/is_readable_file_test.ts | 2 -- fs/is_readable_test.ts | 4 ---- 3 files changed, 8 deletions(-) diff --git a/fs/is_readable_dir_test.ts b/fs/is_readable_dir_test.ts index d89b1e7a2202..d4fb8e10b042 100644 --- a/fs/is_readable_dir_test.ts +++ b/fs/is_readable_dir_test.ts @@ -135,7 +135,6 @@ Deno.test("[fs] isReadableDirLink", async function () { // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT await Deno.chmod(tempDirPath, 0o000); assertEquals(await isReadableDir(tempLinkDirPath), false); - // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it } } catch (error) { throw error; @@ -155,7 +154,6 @@ Deno.test("[fs] isReadableDirLinkSync", function () { // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT Deno.chmodSync(tempDirPath, 0o000); assertEquals(isReadableDirSync(tempLinkDirPath), false); - // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it } } catch (error) { throw error; diff --git a/fs/is_readable_file_test.ts b/fs/is_readable_file_test.ts index 866511620bce..f46e1466f854 100644 --- a/fs/is_readable_file_test.ts +++ b/fs/is_readable_file_test.ts @@ -105,7 +105,6 @@ Deno.test("[fs] isReadableFileLink", async function () { // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT await Deno.chmod(tempFilePath, 0o000); assertEquals(await isReadableFile(tempLinkFilePath), false); - // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it } } catch (error) { throw error; @@ -127,7 +126,6 @@ Deno.test("[fs] isReadableFileLinkSync", function () { // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT Deno.chmodSync(tempFilePath, 0o000); assertEquals(isReadableFileSync(tempLinkFilePath), false); - // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it } } catch (error) { throw error; diff --git a/fs/is_readable_test.ts b/fs/is_readable_test.ts index a1f388eecc55..71f166799a43 100644 --- a/fs/is_readable_test.ts +++ b/fs/is_readable_test.ts @@ -115,7 +115,6 @@ Deno.test("[fs] isReadableFileLink", async function () { // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT await Deno.chmod(tempFilePath, 0o000); assertEquals(await isReadable(tempLinkFilePath), false); - // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it } } catch (error) { throw error; @@ -137,7 +136,6 @@ Deno.test("[fs] isReadableFileLinkSync", function () { // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT Deno.chmodSync(tempFilePath, 0o000); assertEquals(isReadableSync(tempLinkFilePath), false); - // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it } } catch (error) { throw error; @@ -154,10 +152,8 @@ Deno.test("[fs] isReadableDirLink", async function () { await Deno.symlink(tempDirPath, tempLinkDirPath); assertEquals(await isReadable(tempLinkDirPath), true); if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT await Deno.chmod(tempDirPath, 0o000); assertEquals(await isReadable(tempLinkDirPath), false); - // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it } } catch (error) { throw error; From bf1ff4f5e11cd1fdfd17c45cf232a160a5e37d4c Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Wed, 16 Nov 2022 00:10:59 +0100 Subject: [PATCH 06/23] refactor(fs): combine isReadable* to isReadable --- fs/is_readable.ts | 85 +++++++++++--- fs/is_readable_dir.ts | 109 ----------------- fs/is_readable_dir_test.ts | 164 -------------------------- fs/is_readable_file.ts | 109 ----------------- fs/is_readable_file_test.ts | 164 -------------------------- fs/is_readable_test.ts | 226 +++++++++++++++++++++++++++--------- fs/mod.ts | 2 - 7 files changed, 235 insertions(+), 624 deletions(-) delete mode 100644 fs/is_readable_dir.ts delete mode 100644 fs/is_readable_dir_test.ts delete mode 100644 fs/is_readable_file.ts delete mode 100644 fs/is_readable_file_test.ts diff --git a/fs/is_readable.ts b/fs/is_readable.ts index ae242646dbc1..ef27b5fd39ed 100644 --- a/fs/is_readable.ts +++ b/fs/is_readable.ts @@ -1,9 +1,22 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +export interface IsReadableOptions { + /** + * When `true`, will check if the path is a directory as well. + * Directory symlinks are included. + */ + isDirectory?: boolean; + /** + * When `true`, will check if the path is a file as well. + * File symlinks are included. + */ + isFile?: boolean; +} + /** - * Test whether or not the given path is readable by checking with the file system. To check simultaneously if the path is either a file or a directory please use `isReadableFile` or `isReadableDir` instead. + * Test whether or not the given path exists and is readable by checking with the file system. To check simultaneously if the path is either a file or a directory please provide additional `options`. * - * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. + * Note: Do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. * * Bad: * ```ts @@ -28,24 +41,42 @@ * ``` * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use */ -export async function isReadable(path: string | URL): Promise { +export async function isReadable( + path: string | URL, + options?: IsReadableOptions +): Promise { try { const stat: Deno.FileInfo = await Deno.stat(path); + if (options && (options.isDirectory || options.isFile)) { + if (options.isDirectory && options.isFile) { + throw new TypeError( + "IsReadableOptions.options.isDirectory and IsReadableOptions.options.isFile must not be true together." + ); + } + if ( + (options.isDirectory && !stat.isDirectory) || + (options.isFile && !stat.isFile) + ) { + return false; + } + } if (stat.mode == null) { - return true; // Non POSIX exclusive + return true; // Exclusive on Non-POSIX systems } - if (Deno.getUid() == stat.uid) { - return (stat.mode & 0o400) == 0o400; - } else if (Deno.getGid() == stat.gid) { - return (stat.mode & 0o040) == 0o040; + if (Deno.uid() == stat.uid) { + // User is owner + return (stat.mode & 0o400) == 0o400; // ... and user can read? + } else if (Deno.gid() == stat.gid) { + // User is in owner group + return (stat.mode & 0o040) == 0o040; // ... and group can read? } - return (stat.mode & 0o004) == 0o004; + return (stat.mode & 0o004) == 0o004; // Others can read? } catch (error) { if (error instanceof Deno.errors.NotFound) { return false; } if (error instanceof Deno.errors.PermissionDenied) { - return false; // Windows exclusive + return false; // Exclusive on Windows systems } throw error; } @@ -79,24 +110,42 @@ export async function isReadable(path: string | URL): Promise { * ``` * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use */ -export function isReadableSync(path: string | URL): boolean { +export function isReadableSync( + path: string | URL, + options?: IsReadableOptions +): boolean { try { const stat: Deno.FileInfo = Deno.statSync(path); + if (options && (options.isDirectory || options.isFile)) { + if (options.isDirectory && options.isFile) { + throw new TypeError( + "IsReadableOptions.options.isDirectory and IsReadableOptions.options.isFile must not be true together." + ); + } + if ( + (options.isDirectory && !stat.isDirectory) || + (options.isFile && !stat.isFile) + ) { + return false; + } + } if (stat.mode == null) { - return true; // Non POSIX exclusive + return true; // Exclusive on Non-POSIX systems } - if (Deno.getUid() == stat.uid) { - return (stat.mode & 0o400) == 0o400; - } else if (Deno.getGid() == stat.gid) { - return (stat.mode & 0o040) == 0o040; + if (Deno.uid() == stat.uid) { + // User is owner + return (stat.mode & 0o400) == 0o400; // ... and user can read? + } else if (Deno.gid() == stat.gid) { + // User is in owner group + return (stat.mode & 0o040) == 0o040; // ... and group can read? } - return (stat.mode & 0o004) == 0o004; + return (stat.mode & 0o004) == 0o004; // Others can read? } catch (error) { if (error instanceof Deno.errors.NotFound) { return false; } if (error instanceof Deno.errors.PermissionDenied) { - return false; // Windows exclusive + return false; // Exclusive on Windows systems } throw error; } diff --git a/fs/is_readable_dir.ts b/fs/is_readable_dir.ts deleted file mode 100644 index d80f57cdf008..000000000000 --- a/fs/is_readable_dir.ts +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -/** - * Test whether or not the given path is readable directory by checking with the file system. - * - * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. - * - * Bad: - * ```ts - * import { isReadableDir } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - * - * if (await isReadableDir("./foo")) { - * await Deno.remove("./foo"); - * } - * ``` - * - * Good: - * ```ts - * // Notice no use of isReadableDir - * try { - * await Deno.remove("./foo", { recursive: true }); - * } catch (error) { - * if (!(error instanceof Deno.errors.NotFound)) { - * throw error; - * } - * // Do nothing... - * } - * ``` - * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use - */ -export async function isReadableDir(dirPath: string | URL): Promise { - try { - const stat: Deno.FileInfo = await Deno.stat(dirPath); - if (!stat.isDirectory) { - return false; - } - if (stat.mode == null) { - return true; // Non POSIX exclusive - } - if (Deno.getUid() == stat.uid) { - return (stat.mode & 0o400) == 0o400; - } else if (Deno.getGid() == stat.gid) { - return (stat.mode & 0o040) == 0o040; - } - return (stat.mode & 0o004) == 0o004; - } catch (error) { - if (error instanceof Deno.errors.NotFound) { - return false; - } - if (error instanceof Deno.errors.PermissionDenied) { - return false; // Windows exclusive - } - throw error; - } -} - -/** - * Test whether or not the given path is readable directory by checking with the file system. - * - * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. - * - * Bad: - * ```ts - * import { isReadableDirSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - * - * if (isReadableDirSync("./foo")) { - * Deno.removeSync("./foo"); - * } - * ``` - * - * Good: - * ```ts - * // Notice no use of isReadableDirSync - * try { - * Deno.removeSync("./foo", { recursive: true }); - * } catch (error) { - * if (!(error instanceof Deno.errors.NotFound)) { - * throw error; - * } - * // Do nothing... - * } - * ``` - * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use - */ -export function isReadableDirSync(dirPath: string | URL): boolean { - try { - const stat: Deno.FileInfo = Deno.statSync(dirPath); - if (!stat.isDirectory) { - return false; - } - if (stat.mode == null) { - return true; // Non POSIX exclusive - } - if (Deno.getUid() == stat.uid) { - return (stat.mode & 0o400) == 0o400; - } else if (Deno.getGid() == stat.gid) { - return (stat.mode & 0o040) == 0o040; - } - return (stat.mode & 0o004) == 0o004; - } catch (error) { - if (error instanceof Deno.errors.NotFound) { - return false; - } - if (error instanceof Deno.errors.PermissionDenied) { - return false; // Windows exclusive - } - throw error; - } -} diff --git a/fs/is_readable_dir_test.ts b/fs/is_readable_dir_test.ts deleted file mode 100644 index d4fb8e10b042..000000000000 --- a/fs/is_readable_dir_test.ts +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "../testing/asserts.ts"; -import * as path from "../path/mod.ts"; -import { isReadableDir, isReadableDirSync } from "./is_readable_dir.ts"; - -Deno.test("[fs] isReadableNotExist", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - try { - assertEquals( - await isReadableDir(path.join(tempDirPath, "not_exists")), - false, - ); - } catch (error) { - throw error; - } finally { - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableNotExistSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - try { - assertEquals( - isReadableDirSync(path.join(tempDirPath, "not_exists")), - false, - ); - } catch (error) { - throw error; - } finally { - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFile", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempFile: Deno.FsFile = await Deno.create(tempFilePath); - try { - assertEquals(await isReadableDir(tempFilePath), false); - } catch (error) { - throw error; - } finally { - tempFile.close(); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFileSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); - try { - assertEquals(isReadableDirSync(tempFilePath), false); - } catch (error) { - throw error; - } finally { - tempFile.close(); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDir", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - try { - assertEquals(isReadableDirSync(tempDirPath), true); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - await Deno.chmod(tempDirPath, 0o000); - assertEquals(await isReadableDir(tempDirPath), false); - } - } catch (error) { - throw error; - } finally { - await Deno.chmod(tempDirPath, 0o755); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDirSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - try { - assertEquals(isReadableDirSync(tempDirPath), true); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - Deno.chmodSync(tempDirPath, 0o000); - assertEquals(isReadableDirSync(tempDirPath), false); - } - } catch (error) { - throw error; - } finally { - Deno.chmodSync(tempDirPath, 0o755); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFileLink", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempLinkFilePath: string = path.join(tempDirPath, "0-link.ts"); - const tempFile: Deno.FsFile = await Deno.create(tempFilePath); - try { - await Deno.symlink(tempFilePath, tempLinkFilePath); - assertEquals(await isReadableDir(tempLinkFilePath), false); - } catch (error) { - throw error; - } finally { - tempFile.close(); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFileLinkSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempLinkFilePath: string = path.join(tempDirPath, "0-link.ts"); - const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); - try { - Deno.symlinkSync(tempFilePath, tempLinkFilePath); - assertEquals(isReadableDirSync(tempLinkFilePath), false); - } catch (error) { - throw error; - } finally { - tempFile.close(); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDirLink", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - const tempLinkDirPath: string = path.join(tempDirPath, "temp-link"); - try { - await Deno.symlink(tempDirPath, tempLinkDirPath); - assertEquals(await isReadableDir(tempLinkDirPath), true); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - await Deno.chmod(tempDirPath, 0o000); - assertEquals(await isReadableDir(tempLinkDirPath), false); - } - } catch (error) { - throw error; - } finally { - await Deno.chmod(tempDirPath, 0o755); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDirLinkSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempLinkDirPath: string = path.join(tempDirPath, "temp-link"); - try { - Deno.symlinkSync(tempDirPath, tempLinkDirPath); - assertEquals(isReadableDirSync(tempLinkDirPath), true); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - Deno.chmodSync(tempDirPath, 0o000); - assertEquals(isReadableDirSync(tempLinkDirPath), false); - } - } catch (error) { - throw error; - } finally { - Deno.chmodSync(tempDirPath, 0o755); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); diff --git a/fs/is_readable_file.ts b/fs/is_readable_file.ts deleted file mode 100644 index 761044cfb5f1..000000000000 --- a/fs/is_readable_file.ts +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -/** - * Test whether or not the given path is a readable file by checking with the file system. - * - * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. - * - * Bad: - * ```ts - * import { isReadableFile } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - * - * if (await isReadableFile("./foo.txt")) { - * await Deno.remove("./foo.txt"); - * } - * ``` - * - * Good: - * ```ts - * // Notice no use of isReadableFile - * try { - * await Deno.remove("./foo.txt"); - * } catch (error) { - * if (!(error instanceof Deno.errors.NotFound)) { - * throw error; - * } - * // Do nothing... - * } - * ``` - * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use - */ -export async function isReadableFile(filePath: string | URL): Promise { - try { - const stat: Deno.FileInfo = await Deno.stat(filePath); - if (!stat.isFile) { - return false; - } - if (stat.mode == null) { - return true; // Non POSIX exclusive - } - if (Deno.getUid() == stat.uid) { - return (stat.mode & 0o400) == 0o400; - } else if (Deno.getGid() == stat.gid) { - return (stat.mode & 0o040) == 0o040; - } - return (stat.mode & 0o004) == 0o004; - } catch (error) { - if (error instanceof Deno.errors.NotFound) { - return false; - } - if (error instanceof Deno.errors.PermissionDenied) { - return false; // Windows exclusive - } - throw error; - } -} - -/** - * Test whether or not the given path is a readable file by checking with the file system. - * - * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. - * - * Bad: - * ```ts - * import { isReadableFileSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - * - * if (isReadableFileSync("./foo.txt")) { - * Deno.removeSync("./foo.txt"); - * } - * ``` - * - * Good: - * ```ts - * // Notice no use of isReadableFileSync - * try { - * Deno.removeSync("./foo.txt"); - * } catch (error) { - * if (!(error instanceof Deno.errors.NotFound)) { - * throw error; - * } - * // Do nothing... - * } - * ``` - * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use - */ -export function isReadableFileSync(filePath: string | URL): boolean { - try { - const stat: Deno.FileInfo = Deno.statSync(filePath); - if (!stat.isFile) { - return false; - } - if (stat.mode == null) { - return true; // Non POSIX exclusive - } - if (Deno.getUid() == stat.uid) { - return (stat.mode & 0o400) == 0o400; - } else if (Deno.getGid() == stat.gid) { - return (stat.mode & 0o040) == 0o040; - } - return (stat.mode & 0o004) == 0o004; - } catch (error) { - if (error instanceof Deno.errors.NotFound) { - return false; - } - if (error instanceof Deno.errors.PermissionDenied) { - return false; // Windows exclusive - } - throw error; - } -} diff --git a/fs/is_readable_file_test.ts b/fs/is_readable_file_test.ts deleted file mode 100644 index f46e1466f854..000000000000 --- a/fs/is_readable_file_test.ts +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "../testing/asserts.ts"; -import * as path from "../path/mod.ts"; -import { isReadableFile, isReadableFileSync } from "./is_readable_file.ts"; - -Deno.test("[fs] isReadableNotExist", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - try { - assertEquals( - await isReadableFile(path.join(tempDirPath, "not_exists")), - false, - ); - } catch (error) { - throw error; - } finally { - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableNotExistSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - try { - assertEquals( - isReadableFileSync(path.join(tempDirPath, "not_exists")), - false, - ); - } catch (error) { - throw error; - } finally { - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFile", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempFile: Deno.FsFile = await Deno.create(tempFilePath); - try { - assertEquals(await isReadableFile(tempFilePath), true); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - await Deno.chmod(tempFilePath, 0o000); - assertEquals(await isReadableFile(tempFilePath), false); - } - } catch (error) { - throw error; - } finally { - tempFile.close(); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFileSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); - try { - assertEquals(isReadableFileSync(tempFilePath), true); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - Deno.chmodSync(tempFilePath, 0o000); - assertEquals(isReadableFileSync(tempFilePath), false); - } - } catch (error) { - throw error; - } finally { - tempFile.close(); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDir", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - try { - assertEquals(isReadableFileSync(tempDirPath), false); - } catch (error) { - throw error; - } finally { - await Deno.chmod(tempDirPath, 0o755); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDirSync", function () { - const tempDirPath = Deno.makeTempDirSync(); - try { - assertEquals(isReadableFileSync(tempDirPath), false); - } catch (error) { - throw error; - } finally { - Deno.chmodSync(tempDirPath, 0o755); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFileLink", async function () { - const tempDirPath = await Deno.makeTempDir(); - const tempFilePath = path.join(tempDirPath, "0.ts"); - const tempLinkFilePath = path.join(tempDirPath, "0-link.ts"); - const tempFile: Deno.FsFile = await Deno.create(tempFilePath); - try { - await Deno.symlink(tempFilePath, tempLinkFilePath); - assertEquals(await isReadableFile(tempLinkFilePath), true); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - await Deno.chmod(tempFilePath, 0o000); - assertEquals(await isReadableFile(tempLinkFilePath), false); - } - } catch (error) { - throw error; - } finally { - tempFile.close(); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFileLinkSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempLinkFilePath: string = path.join(tempDirPath, "0-link.ts"); - const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); - try { - Deno.symlinkSync(tempFilePath, tempLinkFilePath); - assertEquals(isReadableFileSync(tempLinkFilePath), true); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - Deno.chmodSync(tempFilePath, 0o000); - assertEquals(isReadableFileSync(tempLinkFilePath), false); - } - } catch (error) { - throw error; - } finally { - tempFile.close(); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDirLink", async function () { - const tempDirPath = await Deno.makeTempDir(); - const tempLinkDirPath = path.join(tempDirPath, "temp-link"); - try { - await Deno.symlink(tempDirPath, tempLinkDirPath); - assertEquals(await isReadableFile(tempLinkDirPath), false); - } catch (error) { - throw error; - } finally { - await Deno.chmod(tempDirPath, 0o755); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDirLinkSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempLinkDirPath: string = path.join(tempDirPath, "temp-link"); - try { - Deno.symlinkSync(tempDirPath, tempLinkDirPath); - assertEquals(isReadableFileSync(tempLinkDirPath), false); - } catch (error) { - throw error; - } finally { - Deno.chmodSync(tempDirPath, 0o755); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); diff --git a/fs/is_readable_test.ts b/fs/is_readable_test.ts index 71f166799a43..84c5a95a1e52 100644 --- a/fs/is_readable_test.ts +++ b/fs/is_readable_test.ts @@ -6,10 +6,7 @@ import { isReadable, isReadableSync } from "./is_readable.ts"; Deno.test("[fs] isReadableNotExist", async function () { const tempDirPath: string = await Deno.makeTempDir(); try { - assertEquals( - await isReadable(path.join(tempDirPath, "not_exists")), - false, - ); + assertEquals(await isReadable(path.join(tempDirPath, "not_exists")), false); } catch (error) { throw error; } finally { @@ -20,10 +17,7 @@ Deno.test("[fs] isReadableNotExist", async function () { Deno.test("[fs] isReadableNotExistSync", function () { const tempDirPath: string = Deno.makeTempDirSync(); try { - assertEquals( - isReadableSync(path.join(tempDirPath, "not_exists")), - false, - ); + assertEquals(isReadableSync(path.join(tempDirPath, "not_exists")), false); } catch (error) { throw error; } finally { @@ -37,6 +31,19 @@ Deno.test("[fs] isReadableFile", async function () { const tempFile: Deno.FsFile = await Deno.create(tempFilePath); try { assertEquals(await isReadable(tempFilePath), true); + assertEquals(await isReadable(tempFilePath, {}), true); + assertEquals( + await isReadable(tempFilePath, { + isDirectory: true, + }), + false + ); + assertEquals( + await isReadable(tempFilePath, { + isFile: true, + }), + true + ); if (Deno.build.os != "windows") { // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT await Deno.chmod(tempFilePath, 0o000); @@ -45,82 +52,82 @@ Deno.test("[fs] isReadableFile", async function () { } catch (error) { throw error; } finally { - tempFile.close(); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFileSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); - try { - assertEquals(isReadableSync(tempFilePath), true); if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - Deno.chmodSync(tempFilePath, 0o000); - assertEquals(isReadableSync(tempFilePath), false); + await Deno.chmod(tempFilePath, 0o644); } - } catch (error) { - throw error; - } finally { tempFile.close(); - Deno.removeSync(tempDirPath, { recursive: true }); + await Deno.remove(tempDirPath, { recursive: true }); } }); -Deno.test("[fs] isReadableDir", async function () { - const tempDirPath: string = await Deno.makeTempDir(); +Deno.test("[fs] isReadableFileLink", async function () { + const tempDirPath = await Deno.makeTempDir(); + const tempFilePath = path.join(tempDirPath, "0.ts"); + const tempLinkFilePath = path.join(tempDirPath, "0-link.ts"); + const tempFile: Deno.FsFile = await Deno.create(tempFilePath); try { - assertEquals(isReadableSync(tempDirPath), true); + await Deno.symlink(tempFilePath, tempLinkFilePath); + assertEquals(await isReadable(tempLinkFilePath), true); + assertEquals(await isReadable(tempLinkFilePath, {}), true); + assertEquals( + await isReadable(tempLinkFilePath, { + isDirectory: true, + }), + false + ); + assertEquals( + await isReadable(tempLinkFilePath, { + isFile: true, + }), + true + ); if (Deno.build.os != "windows") { // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - await Deno.chmod(tempDirPath, 0o000); - assertEquals(await isReadable(tempDirPath), false); + await Deno.chmod(tempFilePath, 0o000); + assertEquals(await isReadable(tempLinkFilePath), false); + // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } } catch (error) { throw error; } finally { - await Deno.chmod(tempDirPath, 0o755); + await Deno.chmod(tempFilePath, 0o644); + tempFile.close(); await Deno.remove(tempDirPath, { recursive: true }); } }); -Deno.test("[fs] isReadableDirSync", function () { - const tempDirPath = Deno.makeTempDirSync(); +Deno.test("[fs] isReadableFileSync", function () { + const tempDirPath: string = Deno.makeTempDirSync(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); try { - assertEquals(isReadableSync(tempDirPath), true); + assertEquals(isReadableSync(tempFilePath), true); + assertEquals(isReadableSync(tempFilePath, {}), true); + assertEquals( + isReadableSync(tempFilePath, { + isDirectory: true, + }), + false + ); + assertEquals( + isReadableSync(tempFilePath, { + isFile: true, + }), + true + ); if (Deno.build.os != "windows") { // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - Deno.chmodSync(tempDirPath, 0o000); - assertEquals(isReadableSync(tempDirPath), false); + Deno.chmodSync(tempFilePath, 0o000); + assertEquals(isReadableSync(tempFilePath), false); } } catch (error) { throw error; } finally { - Deno.chmodSync(tempDirPath, 0o755); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFileLink", async function () { - const tempDirPath = await Deno.makeTempDir(); - const tempFilePath = path.join(tempDirPath, "0.ts"); - const tempLinkFilePath = path.join(tempDirPath, "0-link.ts"); - const tempFile: Deno.FsFile = await Deno.create(tempFilePath); - try { - await Deno.symlink(tempFilePath, tempLinkFilePath); - assertEquals(await isReadable(tempLinkFilePath), true); if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - await Deno.chmod(tempFilePath, 0o000); - assertEquals(await isReadable(tempLinkFilePath), false); + Deno.chmodSync(tempFilePath, 0o644); } - } catch (error) { - throw error; - } finally { tempFile.close(); - await Deno.remove(tempDirPath, { recursive: true }); + Deno.removeSync(tempDirPath, { recursive: true }); } }); @@ -132,28 +139,88 @@ Deno.test("[fs] isReadableFileLinkSync", function () { try { Deno.symlinkSync(tempFilePath, tempLinkFilePath); assertEquals(isReadableSync(tempLinkFilePath), true); + assertEquals(isReadableSync(tempLinkFilePath, {}), true); + assertEquals( + isReadableSync(tempLinkFilePath, { + isDirectory: true, + }), + false + ); + assertEquals( + isReadableSync(tempLinkFilePath, { + isFile: true, + }), + true + ); if (Deno.build.os != "windows") { // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT Deno.chmodSync(tempFilePath, 0o000); assertEquals(isReadableSync(tempLinkFilePath), false); + // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } } catch (error) { throw error; } finally { + Deno.chmodSync(tempFilePath, 0o644); tempFile.close(); Deno.removeSync(tempDirPath, { recursive: true }); } }); +Deno.test("[fs] isReadableDir", async function () { + const tempDirPath: string = await Deno.makeTempDir(); + try { + assertEquals(await isReadable(tempDirPath), true); + assertEquals(await isReadable(tempDirPath, {}), true); + assertEquals( + await isReadable(tempDirPath, { + isDirectory: true, + }), + true + ); + assertEquals( + await isReadable(tempDirPath, { + isFile: true, + }), + false + ); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempDirPath, 0o000); + assertEquals(await isReadable(tempDirPath), false); + } + } catch (error) { + throw error; + } finally { + await Deno.chmod(tempDirPath, 0o755); + await Deno.remove(tempDirPath, { recursive: true }); + } +}); + Deno.test("[fs] isReadableDirLink", async function () { const tempDirPath = await Deno.makeTempDir(); const tempLinkDirPath = path.join(tempDirPath, "temp-link"); try { await Deno.symlink(tempDirPath, tempLinkDirPath); assertEquals(await isReadable(tempLinkDirPath), true); + assertEquals(await isReadable(tempLinkDirPath, {}), true); + assertEquals( + await isReadable(tempLinkDirPath, { + isDirectory: true, + }), + true + ); + assertEquals( + await isReadable(tempLinkDirPath, { + isFile: true, + }), + false + ); if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT await Deno.chmod(tempDirPath, 0o000); assertEquals(await isReadable(tempLinkDirPath), false); + // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } } catch (error) { throw error; @@ -163,17 +230,60 @@ Deno.test("[fs] isReadableDirLink", async function () { } }); +Deno.test("[fs] isReadableDirSync", function () { + const tempDirPath = Deno.makeTempDirSync(); + try { + assertEquals(isReadableSync(tempDirPath), true); + assertEquals(isReadableSync(tempDirPath, {}), true); + assertEquals( + isReadableSync(tempDirPath, { + isDirectory: true, + }), + true + ); + assertEquals( + isReadableSync(tempDirPath, { + isFile: true, + }), + false + ); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempDirPath, 0o000); + assertEquals(isReadableSync(tempDirPath), false); + } + } catch (error) { + throw error; + } finally { + Deno.chmodSync(tempDirPath, 0o755); + Deno.removeSync(tempDirPath, { recursive: true }); + } +}); + Deno.test("[fs] isReadableDirLinkSync", function () { const tempDirPath: string = Deno.makeTempDirSync(); const tempLinkDirPath: string = path.join(tempDirPath, "temp-link"); try { Deno.symlinkSync(tempDirPath, tempLinkDirPath); assertEquals(isReadableSync(tempLinkDirPath), true); + assertEquals(isReadableSync(tempLinkDirPath, {}), true); + assertEquals( + isReadableSync(tempLinkDirPath, { + isDirectory: true, + }), + true + ); + assertEquals( + isReadableSync(tempLinkDirPath, { + isFile: true, + }), + false + ); if (Deno.build.os != "windows") { // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT Deno.chmodSync(tempDirPath, 0o000); assertEquals(isReadableSync(tempLinkDirPath), false); - // TODO(martin-braun): test with missing read permission on link when Rust/Deno supports it + // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } } catch (error) { throw error; diff --git a/fs/mod.ts b/fs/mod.ts index da76a2e6ffb6..a650fb679381 100644 --- a/fs/mod.ts +++ b/fs/mod.ts @@ -11,8 +11,6 @@ export * from "./ensure_file.ts"; export * from "./ensure_link.ts"; export * from "./ensure_symlink.ts"; export * from "./is_readable.ts"; -export * from "./is_readable_dir.ts"; -export * from "./is_readable_file.ts"; export * from "./exists.ts"; export * from "./expand_glob.ts"; export * from "./move.ts"; From c5c3b54504ba0ef630890187058d52be36ba202a Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Wed, 16 Nov 2022 12:35:49 +0100 Subject: [PATCH 07/23] refactor(fs): `isReadable` to `exists` with optional permission check --- fs/exists.ts | 129 +++++++++++-- fs/exists_test.ts | 428 ++++++++++++++++++++++++++++++----------- fs/is_readable.ts | 152 --------------- fs/is_readable_test.ts | 294 ---------------------------- fs/mod.ts | 1 - 5 files changed, 428 insertions(+), 576 deletions(-) delete mode 100644 fs/is_readable.ts delete mode 100644 fs/is_readable_test.ts diff --git a/fs/exists.ts b/fs/exists.ts index f01897b4af8b..bc458ff0f176 100644 --- a/fs/exists.ts +++ b/fs/exists.ts @@ -1,16 +1,45 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +export interface ExistsOptions { + /** + * When `true`, will check if the path is readable by the user as well. + */ + isReadable?: boolean; + /** + * When `true`, will check if the path is a directory as well. + * Directory symlinks are included. + */ + isDirectory?: boolean; + /** + * When `true`, will check if the path is a file as well. + * File symlinks are included. + */ + isFile?: boolean; +} + /** - * Test whether or not the given path exists by checking with the file system. + * Test whether or not the given path exists by checking with the file system. Please consider to check if the path is readable and either a file or a directory by providing additional `options`: * - * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. + * ```ts + * import { isReadable } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * const isReadableDir = await exists("./foo", { + * isReadable: true, + * isDirectory: true + * }); + * const isReadableFile = await exists("./bar", { + * isReadable: true, + * isFile: true + * }); + * ``` + * + * Note: Do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. * * Bad: * ```ts * import { exists } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * - * if (await exists("./foo.txt")) { - * await Deno.remove("./foo.txt"); + * if (await exists("./foo")) { + * await Deno.remove("./foo"); * } * ``` * @@ -18,7 +47,7 @@ * ```ts * // Notice no use of exists * try { - * await Deno.remove("./foo.txt"); + * await Deno.remove("./foo", { recursive: true }); * } catch (error) { * if (!(error instanceof Deno.errors.NotFound)) { * throw error; @@ -27,22 +56,63 @@ * } * ``` * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use - * @deprecated (will be removed after 0.157.0) Checking the state of a file before using it causes a race condition. Perform the actual operation directly instead. */ -export async function exists(filePath: string | URL): Promise { +export async function exists( + path: string | URL, + options?: ExistsOptions +): Promise { try { - await Deno.lstat(filePath); + const stat: Deno.FileInfo = await Deno.stat(path); + if (options && (options.isReadable || options.isDirectory || options.isFile)) { + if (options.isDirectory && options.isFile) { + throw new TypeError( + "ExistsOptions.options.isDirectory and ExistsOptions.options.isFile must not be true together." + ); + } + if ( + (options.isDirectory && !stat.isDirectory) || + (options.isFile && !stat.isFile) + ) { + return false; + } + if (options.isReadable) { + if (stat.mode == null) { + return true; // Exclusive on Non-POSIX systems + } + if (Deno.uid() == stat.uid) { + return (stat.mode & 0o400) == 0o400; // User is owner and can read? + } else if (Deno.gid() == stat.gid) { + return (stat.mode & 0o040) == 0o040; // User group is owner can read? + } + return (stat.mode & 0o004) == 0o004; // Others can read? + } + } return true; } catch (error) { if (error instanceof Deno.errors.NotFound) { return false; } + if (error instanceof Deno.errors.PermissionDenied) { + return !options?.isReadable; // Exclusive on Windows systems + } throw error; } } /** - * Test whether or not the given path exists by checking with the file system. + * Test whether or not the given path exists by checking with the file system. Please consider to check if the path is readable and either a file or a directory by providing additional `options`: + * + * ```ts + * import { existsSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * const isReadableDir = existsSync("./foo", { + * isReadable: true, + * isDirectory: true + * }); + * const isReadableFile = existsSync("./bar", { + * isReadable: true, + * isFile: true + * }); + * ``` * * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. * @@ -50,8 +120,8 @@ export async function exists(filePath: string | URL): Promise { * ```ts * import { existsSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * - * if (existsSync("./foo.txt")) { - * Deno.removeSync("./foo.txt"); + * if (existsSync("./foo")) { + * Deno.removeSync("./foo"); * } * ``` * @@ -59,7 +129,7 @@ export async function exists(filePath: string | URL): Promise { * ```ts * // Notice no use of existsSync * try { - * Deno.removeSync("./foo.txt"); + * Deno.removeSync("./foo", { recursive: true }); * } catch (error) { * if (!(error instanceof Deno.errors.NotFound)) { * throw error; @@ -68,16 +138,45 @@ export async function exists(filePath: string | URL): Promise { * } * ``` * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use - * @deprecated (will be removed after 0.157.0) Checking the state of a file before using it causes a race condition. Perform the actual operation directly instead. */ -export function existsSync(filePath: string | URL): boolean { +export function existsSync( + path: string | URL, + options?: ExistsOptions +): boolean { try { - Deno.lstatSync(filePath); + const stat: Deno.FileInfo = Deno.statSync(path); + if (options && (options.isReadable || options.isDirectory || options.isFile)) { + if (options.isDirectory && options.isFile) { + throw new TypeError( + "ExistsOptions.options.isDirectory and ExistsOptions.options.isFile must not be true together." + ); + } + if ( + (options.isDirectory && !stat.isDirectory) || + (options.isFile && !stat.isFile) + ) { + return false; + } + if (options.isReadable) { + if (stat.mode == null) { + return true; // Exclusive on Non-POSIX systems + } + if (Deno.uid() == stat.uid) { + return (stat.mode & 0o400) == 0o400; // User is owner and can read? + } else if (Deno.gid() == stat.gid) { + return (stat.mode & 0o040) == 0o040; // User group is owner can read? + } + return (stat.mode & 0o004) == 0o004; // Others can read? + } + } return true; } catch (error) { if (error instanceof Deno.errors.NotFound) { return false; } + if (error instanceof Deno.errors.PermissionDenied) { + return !options?.isReadable; // Exclusive on Windows systems + } throw error; } } diff --git a/fs/exists_test.ts b/fs/exists_test.ts index 94fc6f6b147c..de6f4eab4a36 100644 --- a/fs/exists_test.ts +++ b/fs/exists_test.ts @@ -1,134 +1,334 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertStringIncludes } from "../testing/asserts.ts"; +import { assertEquals } from "../testing/asserts.ts"; import * as path from "../path/mod.ts"; import { exists, existsSync } from "./exists.ts"; -const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); -const testdataDir = path.resolve(moduleDir, "testdata"); - -Deno.test("[fs] existsFile", async function () { - assertEquals( - await exists(path.join(testdataDir, "not_exist_file.ts")), - false, - ); - assertEquals(await existsSync(path.join(testdataDir, "0.ts")), true); +Deno.test("[fs] existsNotExist", async function () { + const tempDirPath: string = await Deno.makeTempDir(); + try { + assertEquals(await exists(path.join(tempDirPath, "not_exists")), false); + } catch (error) { + throw error; + } finally { + await Deno.remove(tempDirPath, { recursive: true }); + } }); -Deno.test("[fs] existsFileSync", function () { - assertEquals(existsSync(path.join(testdataDir, "not_exist_file.ts")), false); - assertEquals(existsSync(path.join(testdataDir, "0.ts")), true); +Deno.test("[fs] existsNotExistSync", function () { + const tempDirPath: string = Deno.makeTempDirSync(); + try { + assertEquals(existsSync(path.join(tempDirPath, "not_exists")), false); + } catch (error) { + throw error; + } finally { + Deno.removeSync(tempDirPath, { recursive: true }); + } }); -Deno.test("[fs] existsDirectory", async function () { - assertEquals( - await exists(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(existsSync(testdataDir), true); +Deno.test("[fs] existsFile", async function () { + const tempDirPath: string = await Deno.makeTempDir(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempFile: Deno.FsFile = await Deno.create(tempFilePath); + try { + assertEquals(await exists(tempFilePath), true); + assertEquals(await exists(tempFilePath, {}), true); + assertEquals( + await exists(tempFilePath, { + isDirectory: true, + }), + false + ); + assertEquals( + await exists(tempFilePath, { + isFile: true, + }), + true + ); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempFilePath, 0o000); + assertEquals( + await exists(tempFilePath, { + isReadable: true, + }), + false + ); + } + } catch (error) { + throw error; + } finally { + if (Deno.build.os != "windows") { + await Deno.chmod(tempFilePath, 0o644); + } + tempFile.close(); + await Deno.remove(tempDirPath, { recursive: true }); + } }); -Deno.test("[fs] existsDirectorySync", function () { - assertEquals( - existsSync(path.join(testdataDir, "not_exist_directory")), - false, - ); - assertEquals(existsSync(testdataDir), true); +Deno.test("[fs] existsFileLink", async function () { + const tempDirPath = await Deno.makeTempDir(); + const tempFilePath = path.join(tempDirPath, "0.ts"); + const tempLinkFilePath = path.join(tempDirPath, "0-link.ts"); + const tempFile: Deno.FsFile = await Deno.create(tempFilePath); + try { + await Deno.symlink(tempFilePath, tempLinkFilePath); + assertEquals(await exists(tempLinkFilePath), true); + assertEquals(await exists(tempLinkFilePath, {}), true); + assertEquals( + await exists(tempLinkFilePath, { + isDirectory: true, + }), + false + ); + assertEquals( + await exists(tempLinkFilePath, { + isFile: true, + }), + true + ); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempFilePath, 0o000); + assertEquals( + await exists(tempLinkFilePath, { + isReadable: true, + }), + false + ); + // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented + } + } catch (error) { + throw error; + } finally { + await Deno.chmod(tempFilePath, 0o644); + tempFile.close(); + await Deno.remove(tempDirPath, { recursive: true }); + } }); -Deno.test("[fs] existsLinkSync", function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(existsSync(path.join(testdataDir, "0-link")), true); +Deno.test("[fs] existsFileSync", function () { + const tempDirPath: string = Deno.makeTempDirSync(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); + try { + assertEquals(existsSync(tempFilePath), true); + assertEquals(existsSync(tempFilePath, {}), true); + assertEquals( + existsSync(tempFilePath, { + isDirectory: true, + }), + false + ); + assertEquals( + existsSync(tempFilePath, { + isFile: true, + }), + true + ); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempFilePath, 0o000); + assertEquals( + existsSync(tempFilePath, { + isReadable: true, + }), + false + ); + } + } catch (error) { + throw error; + } finally { + if (Deno.build.os != "windows") { + Deno.chmodSync(tempFilePath, 0o644); + } + tempFile.close(); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); -Deno.test("[fs] existsLink", async function () { - // TODO(axetroy): generate link file use Deno api instead of set a link file - // in repository - assertEquals(await exists(path.join(testdataDir, "0-link")), true); +Deno.test("[fs] existsFileLinkSync", function () { + const tempDirPath: string = Deno.makeTempDirSync(); + const tempFilePath: string = path.join(tempDirPath, "0.ts"); + const tempLinkFilePath: string = path.join(tempDirPath, "0-link.ts"); + const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); + try { + Deno.symlinkSync(tempFilePath, tempLinkFilePath); + assertEquals(existsSync(tempLinkFilePath), true); + assertEquals(existsSync(tempLinkFilePath, {}), true); + assertEquals( + existsSync(tempLinkFilePath, { + isDirectory: true, + }), + false + ); + assertEquals( + existsSync(tempLinkFilePath, { + isFile: true, + }), + true + ); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempFilePath, 0o000); + assertEquals( + existsSync(tempLinkFilePath, { + isReadable: true, + }), + false + ); + // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented + } + } catch (error) { + throw error; + } finally { + Deno.chmodSync(tempFilePath, 0o644); + tempFile.close(); + Deno.removeSync(tempDirPath, { recursive: true }); + } }); -interface Scenes { - read: boolean; // --allow-read - async: boolean; - output: string; - file: string; // target file to run -} - -const scenes: Scenes[] = [ - // 1 - { - read: false, - async: true, - output: "run again with the --allow-read flag", - file: "0.ts", - }, - { - read: false, - async: false, - output: "run again with the --allow-read flag", - file: "0.ts", - }, - // 2 - { - read: true, - async: true, - output: "exist", - file: "0.ts", - }, - { - read: true, - async: false, - output: "exist", - file: "0.ts", - }, - // 3 - { - read: false, - async: true, - output: "run again with the --allow-read flag", - file: "no_exist_file_for_test.ts", - }, - { - read: false, - async: false, - output: "run again with the --allow-read flag", - file: "no_exist_file_for_test.ts", - }, - // 4 - { - read: true, - async: true, - output: "not exist", - file: "no_exist_file_for_test.ts", - }, - { - read: true, - async: false, - output: "not exist", - file: "no_exist_file_for_test.ts", - }, -]; - -for (const s of scenes) { - let title = `test ${s.async ? "exists" : "existsSync"}("testdata/${s.file}")`; - title += ` ${s.read ? "with" : "without"} --allow-read`; - Deno.test(`[fs] existsPermission ${title}`, async function () { - const args = ["run", "--quiet", "--no-prompt"]; - - if (s.read) { - args.push("--allow-read"); +Deno.test("[fs] existsDir", async function () { + const tempDirPath: string = await Deno.makeTempDir(); + try { + assertEquals(await exists(tempDirPath), true); + assertEquals(await exists(tempDirPath, {}), true); + assertEquals( + await exists(tempDirPath, { + isDirectory: true, + }), + true + ); + assertEquals( + await exists(tempDirPath, { + isFile: true, + }), + false + ); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempDirPath, 0o000); + assertEquals( + await exists(tempDirPath, { + isReadable: true, + }), + false + ); } + } catch (error) { + throw error; + } finally { + await Deno.chmod(tempDirPath, 0o755); + await Deno.remove(tempDirPath, { recursive: true }); + } +}); - args.push(path.join(testdataDir, s.async ? "exists.ts" : "exists_sync.ts")); - args.push(s.file); +Deno.test("[fs] existsDirLink", async function () { + const tempDirPath = await Deno.makeTempDir(); + const tempLinkDirPath = path.join(tempDirPath, "temp-link"); + try { + await Deno.symlink(tempDirPath, tempLinkDirPath); + assertEquals(await exists(tempLinkDirPath), true); + assertEquals(await exists(tempLinkDirPath, {}), true); + assertEquals( + await exists(tempLinkDirPath, { + isDirectory: true, + }), + true + ); + assertEquals( + await exists(tempLinkDirPath, { + isFile: true, + }), + false + ); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + await Deno.chmod(tempDirPath, 0o000); + assertEquals( + await exists(tempLinkDirPath, { + isReadable: true, + }), + false + ); + // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented + } + } catch (error) { + throw error; + } finally { + await Deno.chmod(tempDirPath, 0o755); + await Deno.remove(tempDirPath, { recursive: true }); + } +}); - const command = new Deno.Command(Deno.execPath(), { - cwd: testdataDir, - args, - }); - const { stdout } = await command.output(); +Deno.test("[fs] existsDirSync", function () { + const tempDirPath = Deno.makeTempDirSync(); + try { + assertEquals(existsSync(tempDirPath), true); + assertEquals(existsSync(tempDirPath, {}), true); + assertEquals( + existsSync(tempDirPath, { + isDirectory: true, + }), + true + ); + assertEquals( + existsSync(tempDirPath, { + isFile: true, + }), + false + ); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempDirPath, 0o000); + assertEquals( + existsSync(tempDirPath, { + isReadable: true, + }), + false + ); + } + } catch (error) { + throw error; + } finally { + Deno.chmodSync(tempDirPath, 0o755); + Deno.removeSync(tempDirPath, { recursive: true }); + } +}); - assertStringIncludes(new TextDecoder().decode(stdout), s.output); - }); - // done -} +Deno.test("[fs] existsDirLinkSync", function () { + const tempDirPath: string = Deno.makeTempDirSync(); + const tempLinkDirPath: string = path.join(tempDirPath, "temp-link"); + try { + Deno.symlinkSync(tempDirPath, tempLinkDirPath); + assertEquals(existsSync(tempLinkDirPath), true); + assertEquals(existsSync(tempLinkDirPath, {}), true); + assertEquals( + existsSync(tempLinkDirPath, { + isDirectory: true, + }), + true + ); + assertEquals( + existsSync(tempLinkDirPath, { + isFile: true, + }), + false + ); + if (Deno.build.os != "windows") { + // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + Deno.chmodSync(tempDirPath, 0o000); + assertEquals( + existsSync(tempLinkDirPath, { + isReadable: true, + }), + false + ); + // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented + } + } catch (error) { + throw error; + } finally { + Deno.chmodSync(tempDirPath, 0o755); + Deno.removeSync(tempDirPath, { recursive: true }); + } +}); diff --git a/fs/is_readable.ts b/fs/is_readable.ts deleted file mode 100644 index ef27b5fd39ed..000000000000 --- a/fs/is_readable.ts +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -export interface IsReadableOptions { - /** - * When `true`, will check if the path is a directory as well. - * Directory symlinks are included. - */ - isDirectory?: boolean; - /** - * When `true`, will check if the path is a file as well. - * File symlinks are included. - */ - isFile?: boolean; -} - -/** - * Test whether or not the given path exists and is readable by checking with the file system. To check simultaneously if the path is either a file or a directory please provide additional `options`. - * - * Note: Do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. - * - * Bad: - * ```ts - * import { isReadable } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - * - * if (await isReadable("./foo")) { - * await Deno.remove("./foo"); - * } - * ``` - * - * Good: - * ```ts - * // Notice no use of isReadable - * try { - * await Deno.remove("./foo", { recursive: true }); - * } catch (error) { - * if (!(error instanceof Deno.errors.NotFound)) { - * throw error; - * } - * // Do nothing... - * } - * ``` - * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use - */ -export async function isReadable( - path: string | URL, - options?: IsReadableOptions -): Promise { - try { - const stat: Deno.FileInfo = await Deno.stat(path); - if (options && (options.isDirectory || options.isFile)) { - if (options.isDirectory && options.isFile) { - throw new TypeError( - "IsReadableOptions.options.isDirectory and IsReadableOptions.options.isFile must not be true together." - ); - } - if ( - (options.isDirectory && !stat.isDirectory) || - (options.isFile && !stat.isFile) - ) { - return false; - } - } - if (stat.mode == null) { - return true; // Exclusive on Non-POSIX systems - } - if (Deno.uid() == stat.uid) { - // User is owner - return (stat.mode & 0o400) == 0o400; // ... and user can read? - } else if (Deno.gid() == stat.gid) { - // User is in owner group - return (stat.mode & 0o040) == 0o040; // ... and group can read? - } - return (stat.mode & 0o004) == 0o004; // Others can read? - } catch (error) { - if (error instanceof Deno.errors.NotFound) { - return false; - } - if (error instanceof Deno.errors.PermissionDenied) { - return false; // Exclusive on Windows systems - } - throw error; - } -} - -/** - * Test whether or not the given path is readable by checking with the file system. To check simultaneously if the path is either a file or a directory please use `isReadableFile` or `isReadableDir` instead. - * - * Note: do not use this function if performing a check before another operation on that file. Doing so creates a race condition. Instead, perform the actual file operation directly. - * - * Bad: - * ```ts - * import { isReadableSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - * - * if (isReadableSync("./foo")) { - * Deno.removeSync("./foo"); - * } - * ``` - * - * Good: - * ```ts - * // Notice no use of isReadableSync - * try { - * Deno.removeSync("./foo", { recursive: true }); - * } catch (error) { - * if (!(error instanceof Deno.errors.NotFound)) { - * throw error; - * } - * // Do nothing... - * } - * ``` - * @see https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use - */ -export function isReadableSync( - path: string | URL, - options?: IsReadableOptions -): boolean { - try { - const stat: Deno.FileInfo = Deno.statSync(path); - if (options && (options.isDirectory || options.isFile)) { - if (options.isDirectory && options.isFile) { - throw new TypeError( - "IsReadableOptions.options.isDirectory and IsReadableOptions.options.isFile must not be true together." - ); - } - if ( - (options.isDirectory && !stat.isDirectory) || - (options.isFile && !stat.isFile) - ) { - return false; - } - } - if (stat.mode == null) { - return true; // Exclusive on Non-POSIX systems - } - if (Deno.uid() == stat.uid) { - // User is owner - return (stat.mode & 0o400) == 0o400; // ... and user can read? - } else if (Deno.gid() == stat.gid) { - // User is in owner group - return (stat.mode & 0o040) == 0o040; // ... and group can read? - } - return (stat.mode & 0o004) == 0o004; // Others can read? - } catch (error) { - if (error instanceof Deno.errors.NotFound) { - return false; - } - if (error instanceof Deno.errors.PermissionDenied) { - return false; // Exclusive on Windows systems - } - throw error; - } -} diff --git a/fs/is_readable_test.ts b/fs/is_readable_test.ts deleted file mode 100644 index 84c5a95a1e52..000000000000 --- a/fs/is_readable_test.ts +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "../testing/asserts.ts"; -import * as path from "../path/mod.ts"; -import { isReadable, isReadableSync } from "./is_readable.ts"; - -Deno.test("[fs] isReadableNotExist", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - try { - assertEquals(await isReadable(path.join(tempDirPath, "not_exists")), false); - } catch (error) { - throw error; - } finally { - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableNotExistSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - try { - assertEquals(isReadableSync(path.join(tempDirPath, "not_exists")), false); - } catch (error) { - throw error; - } finally { - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFile", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempFile: Deno.FsFile = await Deno.create(tempFilePath); - try { - assertEquals(await isReadable(tempFilePath), true); - assertEquals(await isReadable(tempFilePath, {}), true); - assertEquals( - await isReadable(tempFilePath, { - isDirectory: true, - }), - false - ); - assertEquals( - await isReadable(tempFilePath, { - isFile: true, - }), - true - ); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - await Deno.chmod(tempFilePath, 0o000); - assertEquals(await isReadable(tempFilePath), false); - } - } catch (error) { - throw error; - } finally { - if (Deno.build.os != "windows") { - await Deno.chmod(tempFilePath, 0o644); - } - tempFile.close(); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFileLink", async function () { - const tempDirPath = await Deno.makeTempDir(); - const tempFilePath = path.join(tempDirPath, "0.ts"); - const tempLinkFilePath = path.join(tempDirPath, "0-link.ts"); - const tempFile: Deno.FsFile = await Deno.create(tempFilePath); - try { - await Deno.symlink(tempFilePath, tempLinkFilePath); - assertEquals(await isReadable(tempLinkFilePath), true); - assertEquals(await isReadable(tempLinkFilePath, {}), true); - assertEquals( - await isReadable(tempLinkFilePath, { - isDirectory: true, - }), - false - ); - assertEquals( - await isReadable(tempLinkFilePath, { - isFile: true, - }), - true - ); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - await Deno.chmod(tempFilePath, 0o000); - assertEquals(await isReadable(tempLinkFilePath), false); - // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented - } - } catch (error) { - throw error; - } finally { - await Deno.chmod(tempFilePath, 0o644); - tempFile.close(); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFileSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); - try { - assertEquals(isReadableSync(tempFilePath), true); - assertEquals(isReadableSync(tempFilePath, {}), true); - assertEquals( - isReadableSync(tempFilePath, { - isDirectory: true, - }), - false - ); - assertEquals( - isReadableSync(tempFilePath, { - isFile: true, - }), - true - ); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - Deno.chmodSync(tempFilePath, 0o000); - assertEquals(isReadableSync(tempFilePath), false); - } - } catch (error) { - throw error; - } finally { - if (Deno.build.os != "windows") { - Deno.chmodSync(tempFilePath, 0o644); - } - tempFile.close(); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableFileLinkSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempLinkFilePath: string = path.join(tempDirPath, "0-link.ts"); - const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); - try { - Deno.symlinkSync(tempFilePath, tempLinkFilePath); - assertEquals(isReadableSync(tempLinkFilePath), true); - assertEquals(isReadableSync(tempLinkFilePath, {}), true); - assertEquals( - isReadableSync(tempLinkFilePath, { - isDirectory: true, - }), - false - ); - assertEquals( - isReadableSync(tempLinkFilePath, { - isFile: true, - }), - true - ); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - Deno.chmodSync(tempFilePath, 0o000); - assertEquals(isReadableSync(tempLinkFilePath), false); - // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented - } - } catch (error) { - throw error; - } finally { - Deno.chmodSync(tempFilePath, 0o644); - tempFile.close(); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDir", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - try { - assertEquals(await isReadable(tempDirPath), true); - assertEquals(await isReadable(tempDirPath, {}), true); - assertEquals( - await isReadable(tempDirPath, { - isDirectory: true, - }), - true - ); - assertEquals( - await isReadable(tempDirPath, { - isFile: true, - }), - false - ); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - await Deno.chmod(tempDirPath, 0o000); - assertEquals(await isReadable(tempDirPath), false); - } - } catch (error) { - throw error; - } finally { - await Deno.chmod(tempDirPath, 0o755); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDirLink", async function () { - const tempDirPath = await Deno.makeTempDir(); - const tempLinkDirPath = path.join(tempDirPath, "temp-link"); - try { - await Deno.symlink(tempDirPath, tempLinkDirPath); - assertEquals(await isReadable(tempLinkDirPath), true); - assertEquals(await isReadable(tempLinkDirPath, {}), true); - assertEquals( - await isReadable(tempLinkDirPath, { - isDirectory: true, - }), - true - ); - assertEquals( - await isReadable(tempLinkDirPath, { - isFile: true, - }), - false - ); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - await Deno.chmod(tempDirPath, 0o000); - assertEquals(await isReadable(tempLinkDirPath), false); - // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented - } - } catch (error) { - throw error; - } finally { - await Deno.chmod(tempDirPath, 0o755); - await Deno.remove(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDirSync", function () { - const tempDirPath = Deno.makeTempDirSync(); - try { - assertEquals(isReadableSync(tempDirPath), true); - assertEquals(isReadableSync(tempDirPath, {}), true); - assertEquals( - isReadableSync(tempDirPath, { - isDirectory: true, - }), - true - ); - assertEquals( - isReadableSync(tempDirPath, { - isFile: true, - }), - false - ); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - Deno.chmodSync(tempDirPath, 0o000); - assertEquals(isReadableSync(tempDirPath), false); - } - } catch (error) { - throw error; - } finally { - Deno.chmodSync(tempDirPath, 0o755); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); - -Deno.test("[fs] isReadableDirLinkSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempLinkDirPath: string = path.join(tempDirPath, "temp-link"); - try { - Deno.symlinkSync(tempDirPath, tempLinkDirPath); - assertEquals(isReadableSync(tempLinkDirPath), true); - assertEquals(isReadableSync(tempLinkDirPath, {}), true); - assertEquals( - isReadableSync(tempLinkDirPath, { - isDirectory: true, - }), - true - ); - assertEquals( - isReadableSync(tempLinkDirPath, { - isFile: true, - }), - false - ); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT - Deno.chmodSync(tempDirPath, 0o000); - assertEquals(isReadableSync(tempLinkDirPath), false); - // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented - } - } catch (error) { - throw error; - } finally { - Deno.chmodSync(tempDirPath, 0o755); - Deno.removeSync(tempDirPath, { recursive: true }); - } -}); diff --git a/fs/mod.ts b/fs/mod.ts index a650fb679381..b44c1db22a8a 100644 --- a/fs/mod.ts +++ b/fs/mod.ts @@ -10,7 +10,6 @@ export * from "./ensure_dir.ts"; export * from "./ensure_file.ts"; export * from "./ensure_link.ts"; export * from "./ensure_symlink.ts"; -export * from "./is_readable.ts"; export * from "./exists.ts"; export * from "./expand_glob.ts"; export * from "./move.ts"; From 029f86ce7e5d29fb56df8fbc26549019a7633116 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Wed, 16 Nov 2022 12:44:22 +0100 Subject: [PATCH 08/23] chore(fs): update formatting on exists --- fs/exists.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/exists.ts b/fs/exists.ts index bc458ff0f176..bf301cc655dd 100644 --- a/fs/exists.ts +++ b/fs/exists.ts @@ -63,7 +63,10 @@ export async function exists( ): Promise { try { const stat: Deno.FileInfo = await Deno.stat(path); - if (options && (options.isReadable || options.isDirectory || options.isFile)) { + if ( + options && + (options.isReadable || options.isDirectory || options.isFile) + ) { if (options.isDirectory && options.isFile) { throw new TypeError( "ExistsOptions.options.isDirectory and ExistsOptions.options.isFile must not be true together." @@ -145,7 +148,10 @@ export function existsSync( ): boolean { try { const stat: Deno.FileInfo = Deno.statSync(path); - if (options && (options.isReadable || options.isDirectory || options.isFile)) { + if ( + options && + (options.isReadable || options.isDirectory || options.isFile) + ) { if (options.isDirectory && options.isFile) { throw new TypeError( "ExistsOptions.options.isDirectory and ExistsOptions.options.isFile must not be true together." From 19a20b5b54691146d074ea8df667a0b7f7d437d0 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Wed, 16 Nov 2022 13:36:17 +0100 Subject: [PATCH 09/23] docs(fs): u`exists` options usage --- fs/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/README.md b/fs/README.md index a7d21a92c794..2a8c97e0d4bc 100644 --- a/fs/README.md +++ b/fs/README.md @@ -95,7 +95,7 @@ format(CRLFinput, EOL.LF); // output "deno\nis not\nnode" ### exists -Test whether or not the given path exists by checking with the file system. +Test whether or not the given path exists by checking with the file system. Please consider to check if the path is readable and either a file or a directory by providing additional `options`. ```ts import { @@ -105,6 +105,8 @@ import { exists("./foo.txt"); // resolves a boolean existsSync("./foo.txt"); // returns a boolean +existsSync("./foo.txt", { isReadable: true, isFile: true }); // also checks permissions and type +existsSync("./bar", { isDirectory: true }); ``` **Note: do not use this function if performing a check before another operation From 61b3cbe523b6e1ee69e4f53d57ce6698e2382130 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Thu, 17 Nov 2022 02:25:04 +0100 Subject: [PATCH 10/23] feat(fs): `exists` permission test scenes --- fs/exists.ts | 15 +++++- fs/exists_test.ts | 125 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 128 insertions(+), 12 deletions(-) diff --git a/fs/exists.ts b/fs/exists.ts index bf301cc655dd..cb2c548846e5 100644 --- a/fs/exists.ts +++ b/fs/exists.ts @@ -96,7 +96,13 @@ export async function exists( return false; } if (error instanceof Deno.errors.PermissionDenied) { - return !options?.isReadable; // Exclusive on Windows systems + if ( + (await Deno.permissions.query({ name: "read", path })).state === + "granted" + ) { + // --allow-read not missing + return !options?.isReadable; // PermissionDenied was raised by file system + } } throw error; } @@ -181,7 +187,12 @@ export function existsSync( return false; } if (error instanceof Deno.errors.PermissionDenied) { - return !options?.isReadable; // Exclusive on Windows systems + if ( + Deno.permissions.querySync({ name: "read", path }).state === "granted" + ) { + // --allow-read not missing + return !options?.isReadable; // PermissionDenied was raised by file system + } } throw error; } diff --git a/fs/exists_test.ts b/fs/exists_test.ts index de6f4eab4a36..c1c3da85850f 100644 --- a/fs/exists_test.ts +++ b/fs/exists_test.ts @@ -1,8 +1,11 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "../testing/asserts.ts"; +import { assertEquals, assertStringIncludes } from "../testing/asserts.ts"; import * as path from "../path/mod.ts"; import { exists, existsSync } from "./exists.ts"; +const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); +const testdataDir = path.resolve(moduleDir, "testdata"); + Deno.test("[fs] existsNotExist", async function () { const tempDirPath: string = await Deno.makeTempDir(); try { @@ -45,7 +48,7 @@ Deno.test("[fs] existsFile", async function () { true ); if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT await Deno.chmod(tempFilePath, 0o000); assertEquals( await exists(tempFilePath, { @@ -87,7 +90,7 @@ Deno.test("[fs] existsFileLink", async function () { true ); if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT await Deno.chmod(tempFilePath, 0o000); assertEquals( await exists(tempLinkFilePath, { @@ -126,7 +129,7 @@ Deno.test("[fs] existsFileSync", function () { true ); if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT Deno.chmodSync(tempFilePath, 0o000); assertEquals( existsSync(tempFilePath, { @@ -168,7 +171,7 @@ Deno.test("[fs] existsFileLinkSync", function () { true ); if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT Deno.chmodSync(tempFilePath, 0o000); assertEquals( existsSync(tempLinkFilePath, { @@ -205,7 +208,7 @@ Deno.test("[fs] existsDir", async function () { false ); if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT await Deno.chmod(tempDirPath, 0o000); assertEquals( await exists(tempDirPath, { @@ -242,7 +245,7 @@ Deno.test("[fs] existsDirLink", async function () { false ); if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT await Deno.chmod(tempDirPath, 0o000); assertEquals( await exists(tempLinkDirPath, { @@ -278,7 +281,7 @@ Deno.test("[fs] existsDirSync", function () { false ); if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT Deno.chmodSync(tempDirPath, 0o000); assertEquals( existsSync(tempDirPath, { @@ -314,8 +317,8 @@ Deno.test("[fs] existsDirLinkSync", function () { }), false ); - if (Deno.build.os != "windows") { - // TODO(martin-braun): include permission check for Windows tests when chmod is ported to NT + if (Deno.build.os !== "windows") { + // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT Deno.chmodSync(tempDirPath, 0o000); assertEquals( existsSync(tempLinkDirPath, { @@ -332,3 +335,105 @@ Deno.test("[fs] existsDirLinkSync", function () { Deno.removeSync(tempDirPath, { recursive: true }); } }); + +/** + * Scenes control additional permission tests by spawning new Deno processes with and without --allow-read flag. + */ +interface Scene { + read: boolean; // true to test with --allow-read + sync: boolean; // true to test sync + fictional: boolean; // true to test on non existing file + output: string; // required string include of stdout to succeed +} + +const scenes: Scene[] = [ + // 1 + { + read: false, + sync: false, + fictional: false, + output: "run again with the --allow-read flag", + }, + { + read: false, + sync: true, + fictional: false, + output: "run again with the --allow-read flag", + }, + // 2 + { + read: true, + sync: false, + fictional: false, + output: "exist", + }, + { + read: true, + sync: true, + fictional: false, + output: "exist", + }, + // 3 + { + read: false, + sync: false, + fictional: true, + output: "run again with the --allow-read flag", + }, + { + read: false, + sync: true, + fictional: true, + output: "run again with the --allow-read flag", + }, + // 4 + { + read: true, + sync: false, + fictional: true, + output: "not exist", + }, + { + read: true, + sync: true, + fictional: true, + output: "not exist", + }, +]; + +for (const s of scenes) { + let title = `test ${!s.sync ? "exists" : "existsSync"} on`; + title += ` ${s.fictional ? "fictional" : "real"} file`; + title += ` ${s.read ? "with" : "without"} --allow-read`; + Deno.test(`[fs] existsPermission ${title}`, async function () { + const args = ["run", "--quiet", "--no-prompt"]; + + if (s.read) { + args.push("--allow-read"); + } + args.push(path.join(testdataDir, !s.sync ? "exists.ts" : "exists_sync.ts")); + + let tempFilePath = "does_not_exist.ts"; + let tempDirPath: string | null = null; + let tempFile: Deno.FsFile | null = null; + if (!s.fictional) { + tempDirPath = await Deno.makeTempDir(); + tempFilePath = path.join(tempDirPath, "0.ts"); + tempFile = await Deno.create(tempFilePath); + } + args.push(tempFilePath); + + const command = new Deno.Command(Deno.execPath(), { + args, + }); + const { stdout } = await command.output(); + + if (tempFile != null) { + tempFile.close(); + await Deno.remove(tempDirPath!, { recursive: true }); + } + + assertStringIncludes(new TextDecoder().decode(stdout), s.output); + }); + // done +} From 7032f6856193b84f2046b6762698939754469c02 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Thu, 17 Nov 2022 11:57:09 +0100 Subject: [PATCH 11/23] chore(fs): strong OS comparison at `exists` tests --- fs/exists_test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/exists_test.ts b/fs/exists_test.ts index c1c3da85850f..89c2f2a76059 100644 --- a/fs/exists_test.ts +++ b/fs/exists_test.ts @@ -47,7 +47,7 @@ Deno.test("[fs] existsFile", async function () { }), true ); - if (Deno.build.os != "windows") { + if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT await Deno.chmod(tempFilePath, 0o000); assertEquals( @@ -60,7 +60,7 @@ Deno.test("[fs] existsFile", async function () { } catch (error) { throw error; } finally { - if (Deno.build.os != "windows") { + if (Deno.build.os !== "windows") { await Deno.chmod(tempFilePath, 0o644); } tempFile.close(); @@ -89,7 +89,7 @@ Deno.test("[fs] existsFileLink", async function () { }), true ); - if (Deno.build.os != "windows") { + if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT await Deno.chmod(tempFilePath, 0o000); assertEquals( @@ -128,7 +128,7 @@ Deno.test("[fs] existsFileSync", function () { }), true ); - if (Deno.build.os != "windows") { + if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT Deno.chmodSync(tempFilePath, 0o000); assertEquals( @@ -141,7 +141,7 @@ Deno.test("[fs] existsFileSync", function () { } catch (error) { throw error; } finally { - if (Deno.build.os != "windows") { + if (Deno.build.os !== "windows") { Deno.chmodSync(tempFilePath, 0o644); } tempFile.close(); @@ -170,7 +170,7 @@ Deno.test("[fs] existsFileLinkSync", function () { }), true ); - if (Deno.build.os != "windows") { + if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT Deno.chmodSync(tempFilePath, 0o000); assertEquals( @@ -207,7 +207,7 @@ Deno.test("[fs] existsDir", async function () { }), false ); - if (Deno.build.os != "windows") { + if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT await Deno.chmod(tempDirPath, 0o000); assertEquals( @@ -244,7 +244,7 @@ Deno.test("[fs] existsDirLink", async function () { }), false ); - if (Deno.build.os != "windows") { + if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT await Deno.chmod(tempDirPath, 0o000); assertEquals( @@ -280,7 +280,7 @@ Deno.test("[fs] existsDirSync", function () { }), false ); - if (Deno.build.os != "windows") { + if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT Deno.chmodSync(tempDirPath, 0o000); assertEquals( From f75ce82717ad2d073ed1cf3688214814b74806b8 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Mon, 20 Mar 2023 02:08:01 +0100 Subject: [PATCH 12/23] chore(fs): lint exists --- fs/README.md | 4 +++- fs/exists.ts | 10 +++++----- fs/exists_test.ts | 48 +++++++++++++++++++++++------------------------ 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/fs/README.md b/fs/README.md index 2a8c97e0d4bc..6752e2b4660f 100644 --- a/fs/README.md +++ b/fs/README.md @@ -95,7 +95,9 @@ format(CRLFinput, EOL.LF); // output "deno\nis not\nnode" ### exists -Test whether or not the given path exists by checking with the file system. Please consider to check if the path is readable and either a file or a directory by providing additional `options`. +Test whether or not the given path exists by checking with the file system. +Please consider to check if the path is readable and either a file or a +directory by providing additional `options`. ```ts import { diff --git a/fs/exists.ts b/fs/exists.ts index 51aa369fdb24..73aa29bd4eb7 100644 --- a/fs/exists.ts +++ b/fs/exists.ts @@ -59,7 +59,7 @@ export interface ExistsOptions { */ export async function exists( path: string | URL, - options?: ExistsOptions + options?: ExistsOptions, ): Promise { try { const stat: Deno.FileInfo = await Deno.stat(path); @@ -69,7 +69,7 @@ export async function exists( ) { if (options.isDirectory && options.isFile) { throw new TypeError( - "ExistsOptions.options.isDirectory and ExistsOptions.options.isFile must not be true together." + "ExistsOptions.options.isDirectory and ExistsOptions.options.isFile must not be true together.", ); } if ( @@ -98,7 +98,7 @@ export async function exists( if (error instanceof Deno.errors.PermissionDenied) { if ( (await Deno.permissions.query({ name: "read", path })).state === - "granted" + "granted" ) { // --allow-read not missing return !options?.isReadable; // PermissionDenied was raised by file system @@ -150,7 +150,7 @@ export async function exists( */ export function existsSync( path: string | URL, - options?: ExistsOptions + options?: ExistsOptions, ): boolean { try { const stat: Deno.FileInfo = Deno.statSync(path); @@ -160,7 +160,7 @@ export function existsSync( ) { if (options.isDirectory && options.isFile) { throw new TypeError( - "ExistsOptions.options.isDirectory and ExistsOptions.options.isFile must not be true together." + "ExistsOptions.options.isDirectory and ExistsOptions.options.isFile must not be true together.", ); } if ( diff --git a/fs/exists_test.ts b/fs/exists_test.ts index f51d09ab2146..301cf6a1c5ff 100644 --- a/fs/exists_test.ts +++ b/fs/exists_test.ts @@ -39,13 +39,13 @@ Deno.test("[fs] existsFile", async function () { await exists(tempFilePath, { isDirectory: true, }), - false + false, ); assertEquals( await exists(tempFilePath, { isFile: true, }), - true + true, ); if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT @@ -54,7 +54,7 @@ Deno.test("[fs] existsFile", async function () { await exists(tempFilePath, { isReadable: true, }), - false + false, ); } } catch (error) { @@ -81,13 +81,13 @@ Deno.test("[fs] existsFileLink", async function () { await exists(tempLinkFilePath, { isDirectory: true, }), - false + false, ); assertEquals( await exists(tempLinkFilePath, { isFile: true, }), - true + true, ); if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT @@ -96,7 +96,7 @@ Deno.test("[fs] existsFileLink", async function () { await exists(tempLinkFilePath, { isReadable: true, }), - false + false, ); // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } @@ -120,13 +120,13 @@ Deno.test("[fs] existsFileSync", function () { existsSync(tempFilePath, { isDirectory: true, }), - false + false, ); assertEquals( existsSync(tempFilePath, { isFile: true, }), - true + true, ); if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT @@ -135,7 +135,7 @@ Deno.test("[fs] existsFileSync", function () { existsSync(tempFilePath, { isReadable: true, }), - false + false, ); } } catch (error) { @@ -162,13 +162,13 @@ Deno.test("[fs] existsFileLinkSync", function () { existsSync(tempLinkFilePath, { isDirectory: true, }), - false + false, ); assertEquals( existsSync(tempLinkFilePath, { isFile: true, }), - true + true, ); if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT @@ -177,7 +177,7 @@ Deno.test("[fs] existsFileLinkSync", function () { existsSync(tempLinkFilePath, { isReadable: true, }), - false + false, ); // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } @@ -199,13 +199,13 @@ Deno.test("[fs] existsDir", async function () { await exists(tempDirPath, { isDirectory: true, }), - true + true, ); assertEquals( await exists(tempDirPath, { isFile: true, }), - false + false, ); if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT @@ -214,7 +214,7 @@ Deno.test("[fs] existsDir", async function () { await exists(tempDirPath, { isReadable: true, }), - false + false, ); } } catch (error) { @@ -236,13 +236,13 @@ Deno.test("[fs] existsDirLink", async function () { await exists(tempLinkDirPath, { isDirectory: true, }), - true + true, ); assertEquals( await exists(tempLinkDirPath, { isFile: true, }), - false + false, ); if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT @@ -251,7 +251,7 @@ Deno.test("[fs] existsDirLink", async function () { await exists(tempLinkDirPath, { isReadable: true, }), - false + false, ); // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } @@ -272,13 +272,13 @@ Deno.test("[fs] existsDirSync", function () { existsSync(tempDirPath, { isDirectory: true, }), - true + true, ); assertEquals( existsSync(tempDirPath, { isFile: true, }), - false + false, ); if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT @@ -287,7 +287,7 @@ Deno.test("[fs] existsDirSync", function () { existsSync(tempDirPath, { isReadable: true, }), - false + false, ); } } catch (error) { @@ -309,13 +309,13 @@ Deno.test("[fs] existsDirLinkSync", function () { existsSync(tempLinkDirPath, { isDirectory: true, }), - true + true, ); assertEquals( existsSync(tempLinkDirPath, { isFile: true, }), - false + false, ); if (Deno.build.os !== "windows") { // TODO(martin-braun): include mode check for Windows tests when chmod is ported to NT @@ -324,7 +324,7 @@ Deno.test("[fs] existsDirLinkSync", function () { existsSync(tempLinkDirPath, { isReadable: true, }), - false + false, ); // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } From addf0a9695073537a8b2a54e4916cd975a376c76 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Mon, 20 Mar 2023 02:44:30 +0100 Subject: [PATCH 13/23] docs(fs): adjust exists comment for actual implementation --- fs/exists.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/exists.ts b/fs/exists.ts index 73aa29bd4eb7..48caca457a62 100644 --- a/fs/exists.ts +++ b/fs/exists.ts @@ -21,7 +21,7 @@ export interface ExistsOptions { * Test whether or not the given path exists by checking with the file system. Please consider to check if the path is readable and either a file or a directory by providing additional `options`: * * ```ts - * import { isReadable } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; + * import { exists } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; * const isReadableDir = await exists("./foo", { * isReadable: true, * isDirectory: true From 39892034183d6ffdaf6ca9cf4de97b197227fb4f Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Mon, 20 Mar 2023 02:45:42 +0100 Subject: [PATCH 14/23] docs(fs): undo adding removed README.md --- fs/README.md | 232 --------------------------------------------------- 1 file changed, 232 deletions(-) delete mode 100644 fs/README.md diff --git a/fs/README.md b/fs/README.md deleted file mode 100644 index 6752e2b4660f..000000000000 --- a/fs/README.md +++ /dev/null @@ -1,232 +0,0 @@ -# fs - -fs module is made to provide helpers to manipulate the filesystem. - -## Usage - -Most of the following modules are exposed in `mod.ts`. This feature is currently -unstable. To enable it use `deno run --unstable`. - -### emptyDir - -Ensures that a directory is empty. Deletes directory contents if the directory -is not empty. If the directory does not exist, it is created. The directory -itself is not deleted. - -```ts -import { - emptyDir, - emptyDirSync, -} from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - -emptyDir("./foo"); // returns a promise -emptyDirSync("./foo"); // void -``` - -### ensureDir - -Ensures that the directory exists. If the directory structure does not exist, it -is created. Like `mkdir -p`. - -```ts -import { - ensureDir, - ensureDirSync, -} from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - -ensureDir("./bar"); // returns a promise -ensureDirSync("./ensureDirSync"); // void -``` - -### ensureFile - -Ensures that the file exists. If the file that is requested to be created is in -directories that do not exist, these directories are created. If the file -already exists, it is **NOT MODIFIED**. - -```ts -import { - ensureFile, - ensureFileSync, -} from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - -ensureFile("./folder/targetFile.dat"); // returns promise -ensureFileSync("./folder/targetFile.dat"); // void -``` - -### ensureSymlink - -Ensures that the link exists. If the directory structure does not exist, it is -created. - -```ts -import { - ensureSymlink, - ensureSymlinkSync, -} from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - -ensureSymlink("./folder/targetFile.dat", "./folder/targetFile.link.dat"); // returns promise -ensureSymlinkSync("./folder/targetFile.dat", "./folder/targetFile.link.dat"); // void -``` - -### EOL - -Detects and format the passed string for the targeted End Of Line character. - -```ts -import { - detect, - EOL, - format, -} from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - -const CRLFinput = "deno\r\nis not\r\nnode"; -const Mixedinput = "deno\nis not\r\nnode"; -const LFinput = "deno\nis not\nnode"; -const NoNLinput = "deno is not node"; - -detect(LFinput); // output EOL.LF -detect(CRLFinput); // output EOL.CRLF -detect(Mixedinput); // output EOL.CRLF -detect(NoNLinput); // output null - -format(CRLFinput, EOL.LF); // output "deno\nis not\nnode" -``` - -### exists - -Test whether or not the given path exists by checking with the file system. -Please consider to check if the path is readable and either a file or a -directory by providing additional `options`. - -```ts -import { - exists, - existsSync, -} from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - -exists("./foo.txt"); // resolves a boolean -existsSync("./foo.txt"); // returns a boolean -existsSync("./foo.txt", { isReadable: true, isFile: true }); // also checks permissions and type -existsSync("./bar", { isDirectory: true }); -``` - -**Note: do not use this function if performing a check before another operation -on that file. Doing so causes a race condition. Instead, perform the actual file -operation directly.** - -Bad: - -```ts -import { - exists, - existsSync, -} from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - -if (await exists("./foo.txt")) { - await Deno.remove("./foo.txt"); -} - -// OR - -if (existsSync("./foo.txt")) { - Deno.removeSync("./foo.txt"); -} -``` - -Good: - -```ts -// Notice no use of exists or existsSync -try { - await Deno.remove("./foo.txt"); -} catch (error) { - if (!(error instanceof Deno.errors.NotFound)) { - throw error; - } - // Do nothing... -} - -// OR - -try { - Deno.removeSync("./foo.txt"); -} catch (error) { - if (!(error instanceof Deno.errors.NotFound)) { - throw error; - } - // Do nothing... -} -``` - -### move - -Moves a file or directory. Overwrites it if option provided. - -```ts -import { move, moveSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - -move("./foo", "./bar"); // returns a promise -moveSync("./foo", "./bar"); // void -moveSync("./foo", "./existingFolder", { overwrite: true }); -// Will overwrite existingFolder -``` - -### copy - -copy a file or directory. Overwrites it if option provided. - -```ts -import { copy, copySync } from "https://deno.land/std@$STD_VERSION/fs/copy.ts"; - -copy("./foo", "./bar"); // returns a promise -copySync("./foo", "./bar"); // void -copySync("./foo", "./existingFolder", { overwrite: true }); -// Will overwrite existingFolder -``` - -### walk - -Iterate all files in a directory recursively. - -```ts -import { walk, walkSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - -for (const entry of walkSync(".")) { - console.log(entry.path); -} - -// Async -async function printFilesNames() { - for await (const entry of walk(".")) { - console.log(entry.path); - } -} - -printFilesNames().then(() => console.log("Done!")); -``` - -### expandGlob - -Expand the glob string from the specified `root` directory and yield each result -as a `WalkEntry` object. - -```ts -import { expandGlob } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - -for await (const file of expandGlob("**/*.ts")) { - console.log(file); -} -``` - -### expandGlobSync - -Synchronous version of `expandGlob()`. - -```ts -import { expandGlobSync } from "https://deno.land/std@$STD_VERSION/fs/mod.ts"; - -for (const file of expandGlobSync("**/*.ts")) { - console.log(file); -} -``` From 464347929d36dc2a0e45c22683705bde883cae89 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Mon, 20 Mar 2023 02:47:44 +0100 Subject: [PATCH 15/23] docs(fs): add default values for ExistsOptions --- fs/exists.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/exists.ts b/fs/exists.ts index 48caca457a62..e76547a5cd1e 100644 --- a/fs/exists.ts +++ b/fs/exists.ts @@ -3,16 +3,19 @@ export interface ExistsOptions { /** * When `true`, will check if the path is readable by the user as well. + * @default {false} */ isReadable?: boolean; /** * When `true`, will check if the path is a directory as well. * Directory symlinks are included. + * @default {false} */ isDirectory?: boolean; /** * When `true`, will check if the path is a file as well. * File symlinks are included. + * @default {false} */ isFile?: boolean; } From ccee9d4c62ec93412036dfeffe2aa8edec56cb9b Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Mon, 20 Mar 2023 03:04:42 +0100 Subject: [PATCH 16/23] docs(fs): improved grammar and details on exists --- fs/exists.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/exists.ts b/fs/exists.ts index e76547a5cd1e..7f91fe7e4817 100644 --- a/fs/exists.ts +++ b/fs/exists.ts @@ -88,7 +88,7 @@ export async function exists( if (Deno.uid() == stat.uid) { return (stat.mode & 0o400) == 0o400; // User is owner and can read? } else if (Deno.gid() == stat.gid) { - return (stat.mode & 0o040) == 0o040; // User group is owner can read? + return (stat.mode & 0o040) == 0o040; // User group is owner and can read? } return (stat.mode & 0o004) == 0o004; // Others can read? } @@ -104,7 +104,7 @@ export async function exists( "granted" ) { // --allow-read not missing - return !options?.isReadable; // PermissionDenied was raised by file system + return !options?.isReadable; // PermissionDenied was raised by file system, so the item exists, but can't be read } } throw error; @@ -179,7 +179,7 @@ export function existsSync( if (Deno.uid() == stat.uid) { return (stat.mode & 0o400) == 0o400; // User is owner and can read? } else if (Deno.gid() == stat.gid) { - return (stat.mode & 0o040) == 0o040; // User group is owner can read? + return (stat.mode & 0o040) == 0o040; // User group is owner and can read? } return (stat.mode & 0o004) == 0o004; // Others can read? } @@ -194,7 +194,7 @@ export function existsSync( Deno.permissions.querySync({ name: "read", path }).state === "granted" ) { // --allow-read not missing - return !options?.isReadable; // PermissionDenied was raised by file system + return !options?.isReadable; // PermissionDenied was raised by file system, so the item exists, but can't be read } } throw error; From 1dca1ba7eb14e779ae06e6624453fbcbced07e7a Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Tue, 21 Mar 2023 13:33:06 +0100 Subject: [PATCH 17/23] chore(fs): remove former ignore-deprecation rules for exists --- _tools/check_deprecation.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/_tools/check_deprecation.ts b/_tools/check_deprecation.ts index bc0c13a98d01..33b46c7af98b 100644 --- a/_tools/check_deprecation.ts +++ b/_tools/check_deprecation.ts @@ -22,14 +22,6 @@ const EXCLUDED_PATHS = [ "_util", ]; -console.warn( - colors.yellow("Warning"), - `ignore ${ - colors.green(`"fs/exists.ts"`) - } until issue is resolved: https://github.com/denoland/deno_std/issues/2594`, -); -EXCLUDED_PATHS.push("fs/exists.ts"); - const ROOT = new URL("../", import.meta.url); const FAIL_FAST = Deno.args.includes("--fail-fast"); From 9743c8b62a418e596a62da8e04dab39c3616c0e3 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Tue, 21 Mar 2023 13:34:15 +0100 Subject: [PATCH 18/23] chore(fs): add missing type definition in a test --- fs/exists_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/exists_test.ts b/fs/exists_test.ts index 301cf6a1c5ff..d8720d71f245 100644 --- a/fs/exists_test.ts +++ b/fs/exists_test.ts @@ -264,7 +264,7 @@ Deno.test("[fs] existsDirLink", async function () { }); Deno.test("[fs] existsDirSync", function () { - const tempDirPath = Deno.makeTempDirSync(); + const tempDirPath: string = Deno.makeTempDirSync(); try { assertEquals(existsSync(tempDirPath), true); assertEquals(existsSync(tempDirPath, {}), true); From 87f3a6afb4214ae0589555ad36d4e60952964baf Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Wed, 22 Mar 2023 14:54:24 +0100 Subject: [PATCH 19/23] fix(fs): chmod calls on Windows exists tests --- fs/exists_test.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/fs/exists_test.ts b/fs/exists_test.ts index d8720d71f245..7246d6664662 100644 --- a/fs/exists_test.ts +++ b/fs/exists_test.ts @@ -103,7 +103,9 @@ Deno.test("[fs] existsFileLink", async function () { } catch (error) { throw error; } finally { - await Deno.chmod(tempFilePath, 0o644); + if (Deno.build.os !== "windows") { + await Deno.chmod(tempFilePath, 0o644); + } tempFile.close(); await Deno.remove(tempDirPath, { recursive: true }); } @@ -184,7 +186,9 @@ Deno.test("[fs] existsFileLinkSync", function () { } catch (error) { throw error; } finally { - Deno.chmodSync(tempFilePath, 0o644); + if (Deno.build.os !== "windows") { + Deno.chmodSync(tempFilePath, 0o644); + } tempFile.close(); Deno.removeSync(tempDirPath, { recursive: true }); } @@ -220,7 +224,9 @@ Deno.test("[fs] existsDir", async function () { } catch (error) { throw error; } finally { - await Deno.chmod(tempDirPath, 0o755); + if (Deno.build.os !== "windows") { + await Deno.chmod(tempDirPath, 0o755); + } await Deno.remove(tempDirPath, { recursive: true }); } }); @@ -258,7 +264,9 @@ Deno.test("[fs] existsDirLink", async function () { } catch (error) { throw error; } finally { - await Deno.chmod(tempDirPath, 0o755); + if (Deno.build.os !== "windows") { + await Deno.chmod(tempDirPath, 0o755); + } await Deno.remove(tempDirPath, { recursive: true }); } }); @@ -293,7 +301,9 @@ Deno.test("[fs] existsDirSync", function () { } catch (error) { throw error; } finally { - Deno.chmodSync(tempDirPath, 0o755); + if (Deno.build.os !== "windows") { + Deno.chmodSync(tempDirPath, 0o755); + } Deno.removeSync(tempDirPath, { recursive: true }); } }); @@ -331,7 +341,9 @@ Deno.test("[fs] existsDirLinkSync", function () { } catch (error) { throw error; } finally { - Deno.chmodSync(tempDirPath, 0o755); + if (Deno.build.os !== "windows") { + Deno.chmodSync(tempDirPath, 0o755); + } Deno.removeSync(tempDirPath, { recursive: true }); } }); From f4ae4e5385ad77fa67fa2cce25b862da8eca3d4a Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Fri, 24 Mar 2023 03:56:57 +0100 Subject: [PATCH 20/23] chore(fs): remove unnecessary type annotations --- fs/exists.ts | 4 ++-- fs/exists_test.ts | 34 +++++++++++++++++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/fs/exists.ts b/fs/exists.ts index 7f91fe7e4817..65a9ed82f975 100644 --- a/fs/exists.ts +++ b/fs/exists.ts @@ -65,7 +65,7 @@ export async function exists( options?: ExistsOptions, ): Promise { try { - const stat: Deno.FileInfo = await Deno.stat(path); + const stat = await Deno.stat(path); if ( options && (options.isReadable || options.isDirectory || options.isFile) @@ -156,7 +156,7 @@ export function existsSync( options?: ExistsOptions, ): boolean { try { - const stat: Deno.FileInfo = Deno.statSync(path); + const stat = Deno.statSync(path); if ( options && (options.isReadable || options.isDirectory || options.isFile) diff --git a/fs/exists_test.ts b/fs/exists_test.ts index 7246d6664662..912b9d077bc0 100644 --- a/fs/exists_test.ts +++ b/fs/exists_test.ts @@ -7,7 +7,7 @@ const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); const testdataDir = path.resolve(moduleDir, "testdata"); Deno.test("[fs] existsNotExist", async function () { - const tempDirPath: string = await Deno.makeTempDir(); + const tempDirPath = await Deno.makeTempDir(); try { assertEquals(await exists(path.join(tempDirPath, "not_exists")), false); } catch (error) { @@ -18,7 +18,7 @@ Deno.test("[fs] existsNotExist", async function () { }); Deno.test("[fs] existsNotExistSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); + const tempDirPath = Deno.makeTempDirSync(); try { assertEquals(existsSync(path.join(tempDirPath, "not_exists")), false); } catch (error) { @@ -29,9 +29,9 @@ Deno.test("[fs] existsNotExistSync", function () { }); Deno.test("[fs] existsFile", async function () { - const tempDirPath: string = await Deno.makeTempDir(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempFile: Deno.FsFile = await Deno.create(tempFilePath); + const tempDirPath = await Deno.makeTempDir(); + const tempFilePath = path.join(tempDirPath, "0.ts"); + const tempFile = await Deno.create(tempFilePath); try { assertEquals(await exists(tempFilePath), true); assertEquals(await exists(tempFilePath, {}), true); @@ -72,7 +72,7 @@ Deno.test("[fs] existsFileLink", async function () { const tempDirPath = await Deno.makeTempDir(); const tempFilePath = path.join(tempDirPath, "0.ts"); const tempLinkFilePath = path.join(tempDirPath, "0-link.ts"); - const tempFile: Deno.FsFile = await Deno.create(tempFilePath); + const tempFile = await Deno.create(tempFilePath); try { await Deno.symlink(tempFilePath, tempLinkFilePath); assertEquals(await exists(tempLinkFilePath), true); @@ -112,9 +112,9 @@ Deno.test("[fs] existsFileLink", async function () { }); Deno.test("[fs] existsFileSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); + const tempDirPath = Deno.makeTempDirSync(); + const tempFilePath = path.join(tempDirPath, "0.ts"); + const tempFile = Deno.createSync(tempFilePath); try { assertEquals(existsSync(tempFilePath), true); assertEquals(existsSync(tempFilePath, {}), true); @@ -152,10 +152,10 @@ Deno.test("[fs] existsFileSync", function () { }); Deno.test("[fs] existsFileLinkSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempFilePath: string = path.join(tempDirPath, "0.ts"); - const tempLinkFilePath: string = path.join(tempDirPath, "0-link.ts"); - const tempFile: Deno.FsFile = Deno.createSync(tempFilePath); + const tempDirPath = Deno.makeTempDirSync(); + const tempFilePath = path.join(tempDirPath, "0.ts"); + const tempLinkFilePath = path.join(tempDirPath, "0-link.ts"); + const tempFile = Deno.createSync(tempFilePath); try { Deno.symlinkSync(tempFilePath, tempLinkFilePath); assertEquals(existsSync(tempLinkFilePath), true); @@ -195,7 +195,7 @@ Deno.test("[fs] existsFileLinkSync", function () { }); Deno.test("[fs] existsDir", async function () { - const tempDirPath: string = await Deno.makeTempDir(); + const tempDirPath = await Deno.makeTempDir(); try { assertEquals(await exists(tempDirPath), true); assertEquals(await exists(tempDirPath, {}), true); @@ -272,7 +272,7 @@ Deno.test("[fs] existsDirLink", async function () { }); Deno.test("[fs] existsDirSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); + const tempDirPath = Deno.makeTempDirSync(); try { assertEquals(existsSync(tempDirPath), true); assertEquals(existsSync(tempDirPath, {}), true); @@ -309,8 +309,8 @@ Deno.test("[fs] existsDirSync", function () { }); Deno.test("[fs] existsDirLinkSync", function () { - const tempDirPath: string = Deno.makeTempDirSync(); - const tempLinkDirPath: string = path.join(tempDirPath, "temp-link"); + const tempDirPath = Deno.makeTempDirSync(); + const tempLinkDirPath = path.join(tempDirPath, "temp-link"); try { Deno.symlinkSync(tempDirPath, tempLinkDirPath); assertEquals(existsSync(tempLinkDirPath), true); From 5c291663f4dc5f87f99cff229e0e2ef1aacf6330 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Fri, 24 Mar 2023 03:59:40 +0100 Subject: [PATCH 21/23] refactor(fs): fictional to reversed exists on test scenes --- fs/exists_test.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/fs/exists_test.ts b/fs/exists_test.ts index 912b9d077bc0..027f2e409a46 100644 --- a/fs/exists_test.ts +++ b/fs/exists_test.ts @@ -354,7 +354,7 @@ Deno.test("[fs] existsDirLinkSync", function () { interface Scene { read: boolean; // true to test with --allow-read sync: boolean; // true to test sync - fictional: boolean; // true to test on non existing file + exists: boolean; // true to test on existing file output: string; // required string include of stdout to succeed } @@ -363,59 +363,59 @@ const scenes: Scene[] = [ { read: false, sync: false, - fictional: false, + exists: false, output: "run again with the --allow-read flag", }, { read: false, sync: true, - fictional: false, + exists: false, output: "run again with the --allow-read flag", }, // 2 { read: true, sync: false, - fictional: false, - output: "exist", + exists: false, + output: "not exist", }, { read: true, sync: true, - fictional: false, - output: "exist", + exists: false, + output: "non exist", }, // 3 { read: false, sync: false, - fictional: true, + exists: true, output: "run again with the --allow-read flag", }, { read: false, sync: true, - fictional: true, + exists: true, output: "run again with the --allow-read flag", }, // 4 { read: true, sync: false, - fictional: true, - output: "not exist", + exists: true, + output: "exist", }, { read: true, sync: true, - fictional: true, - output: "not exist", + exists: true, + output: "exist", }, ]; for (const s of scenes) { let title = `test ${!s.sync ? "exists" : "existsSync"} on`; - title += ` ${s.fictional ? "fictional" : "real"} file`; + title += ` ${s.exists ? "existing" : "non-existing"} file`; title += ` ${s.read ? "with" : "without"} --allow-read`; Deno.test(`[fs] existsPermission ${title}`, async function () { const args = ["run", "--quiet", "--no-prompt"]; @@ -428,7 +428,7 @@ for (const s of scenes) { let tempFilePath = "does_not_exist.ts"; let tempDirPath: string | null = null; let tempFile: Deno.FsFile | null = null; - if (!s.fictional) { + if (s.exists) { tempDirPath = await Deno.makeTempDir(); tempFilePath = path.join(tempDirPath, "0.ts"); tempFile = await Deno.create(tempFilePath); From d026283ba33390bf4a1d6dd164d5a86685694f86 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Fri, 24 Mar 2023 04:01:41 +0100 Subject: [PATCH 22/23] chore(fs): remove unnecessary catch blocks --- fs/exists_test.ts | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/fs/exists_test.ts b/fs/exists_test.ts index 027f2e409a46..56c5ca6c3353 100644 --- a/fs/exists_test.ts +++ b/fs/exists_test.ts @@ -10,8 +10,6 @@ Deno.test("[fs] existsNotExist", async function () { const tempDirPath = await Deno.makeTempDir(); try { assertEquals(await exists(path.join(tempDirPath, "not_exists")), false); - } catch (error) { - throw error; } finally { await Deno.remove(tempDirPath, { recursive: true }); } @@ -21,8 +19,6 @@ Deno.test("[fs] existsNotExistSync", function () { const tempDirPath = Deno.makeTempDirSync(); try { assertEquals(existsSync(path.join(tempDirPath, "not_exists")), false); - } catch (error) { - throw error; } finally { Deno.removeSync(tempDirPath, { recursive: true }); } @@ -57,8 +53,6 @@ Deno.test("[fs] existsFile", async function () { false, ); } - } catch (error) { - throw error; } finally { if (Deno.build.os !== "windows") { await Deno.chmod(tempFilePath, 0o644); @@ -100,8 +94,6 @@ Deno.test("[fs] existsFileLink", async function () { ); // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } - } catch (error) { - throw error; } finally { if (Deno.build.os !== "windows") { await Deno.chmod(tempFilePath, 0o644); @@ -140,8 +132,6 @@ Deno.test("[fs] existsFileSync", function () { false, ); } - } catch (error) { - throw error; } finally { if (Deno.build.os !== "windows") { Deno.chmodSync(tempFilePath, 0o644); @@ -183,8 +173,6 @@ Deno.test("[fs] existsFileLinkSync", function () { ); // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } - } catch (error) { - throw error; } finally { if (Deno.build.os !== "windows") { Deno.chmodSync(tempFilePath, 0o644); @@ -221,8 +209,6 @@ Deno.test("[fs] existsDir", async function () { false, ); } - } catch (error) { - throw error; } finally { if (Deno.build.os !== "windows") { await Deno.chmod(tempDirPath, 0o755); @@ -261,8 +247,6 @@ Deno.test("[fs] existsDirLink", async function () { ); // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } - } catch (error) { - throw error; } finally { if (Deno.build.os !== "windows") { await Deno.chmod(tempDirPath, 0o755); @@ -298,8 +282,6 @@ Deno.test("[fs] existsDirSync", function () { false, ); } - } catch (error) { - throw error; } finally { if (Deno.build.os !== "windows") { Deno.chmodSync(tempDirPath, 0o755); @@ -338,8 +320,6 @@ Deno.test("[fs] existsDirLinkSync", function () { ); // TODO(martin-braun): test unreadable link when Rust's nix::sys::stat::fchmodat has been implemented } - } catch (error) { - throw error; } finally { if (Deno.build.os !== "windows") { Deno.chmodSync(tempDirPath, 0o755); From d0afa08a756fdf134ee220df174f3f10911f09f2 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Fri, 24 Mar 2023 14:19:57 +0100 Subject: [PATCH 23/23] fix(fs): typo in output check of 4th exists test scene --- fs/exists_test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/fs/exists_test.ts b/fs/exists_test.ts index 56c5ca6c3353..b009cbe60d01 100644 --- a/fs/exists_test.ts +++ b/fs/exists_test.ts @@ -339,7 +339,6 @@ interface Scene { } const scenes: Scene[] = [ - // 1 { read: false, sync: false, @@ -352,7 +351,6 @@ const scenes: Scene[] = [ exists: false, output: "run again with the --allow-read flag", }, - // 2 { read: true, sync: false, @@ -363,9 +361,8 @@ const scenes: Scene[] = [ read: true, sync: true, exists: false, - output: "non exist", + output: "not exist", }, - // 3 { read: false, sync: false, @@ -378,7 +375,6 @@ const scenes: Scene[] = [ exists: true, output: "run again with the --allow-read flag", }, - // 4 { read: true, sync: false,