feat(fs): introduce canonicalize option to WalkOptions (#3679)

This commit is contained in:
Heejoon Kang 2023-10-16 22:32:15 +09:00 committed by GitHub
parent 65125db61f
commit a62c90853a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 62 additions and 6 deletions

View File

@ -19,6 +19,12 @@ export interface ExpandGlobOptions extends Omit<GlobOptions, "os"> {
exclude?: string[];
includeDirs?: boolean;
followSymlinks?: boolean;
/**
* Indicates whether the followed symlink's path should be canonicalized.
* This option works only if `followSymlinks` is not `false`.
* @default {true}
*/
canonicalize?: boolean;
}
interface SplitPath {
@ -80,6 +86,7 @@ export async function* expandGlob(
globstar = true,
caseInsensitive,
followSymlinks,
canonicalize,
}: ExpandGlobOptions = {},
): AsyncIterableIterator<WalkEntry> {
const globOptions: GlobOptions = { extended, globstar, caseInsensitive };
@ -134,6 +141,7 @@ export async function* expandGlob(
skip: excludePatterns,
maxDepth: globstar ? Infinity : 1,
followSymlinks,
canonicalize,
});
}
const globPattern = globToRegExp(globSegment, globOptions);
@ -202,6 +210,7 @@ export function* expandGlobSync(
globstar = true,
caseInsensitive,
followSymlinks,
canonicalize,
}: ExpandGlobOptions = {},
): IterableIterator<WalkEntry> {
const globOptions: GlobOptions = { extended, globstar, caseInsensitive };
@ -256,6 +265,7 @@ export function* expandGlobSync(
skip: excludePatterns,
maxDepth: globstar ? Infinity : 1,
followSymlinks,
canonicalize,
});
}
const globPattern = globToRegExp(globSegment, globOptions);

View File

@ -156,3 +156,28 @@ Deno.test("expandGlobFollowSymlink", async function () {
};
assertEquals(await expandGlobArray("*", options), ["abc"]);
});
Deno.test("expandGlobFollowSymlink with canonicalize", async function () {
const options = {
...EG_OPTIONS,
root: join(EG_OPTIONS.root!, "."),
followSymlinks: true,
};
assertEquals(
await expandGlobArray("**/abc", options),
["abc", join("subdir", "abc")],
);
});
Deno.test("expandGlobFollowSymlink without canonicalize", async function () {
const options = {
...EG_OPTIONS,
root: join(EG_OPTIONS.root!, "."),
followSymlinks: true,
canonicalize: false,
};
assertEquals(
await expandGlobArray("**/abc", options),
["abc", join("link", "abc"), join("subdir", "abc")],
);
});

0
fs/testdata/walk/symlink/a/z vendored Normal file
View File

1
fs/testdata/walk/symlink/b vendored Symbolic link
View File

@ -0,0 +1 @@
a

View File

@ -75,6 +75,12 @@ export interface WalkOptions {
* @default {false}
*/
followSymlinks?: boolean;
/**
* Indicates whether the followed symlink's path should be canonicalized.
* This option works only if `followSymlinks` is not `false`.
* @default {true}
*/
canonicalize?: boolean;
/**
* List of file extensions used to filter entries.
* If specified, entries without the file extension specified by this option are excluded.
@ -119,6 +125,7 @@ export async function* walk(
includeDirs = true,
includeSymlinks = true,
followSymlinks = false,
canonicalize = true,
exts = undefined,
match = undefined,
skip = undefined,
@ -147,11 +154,14 @@ export async function* walk(
}
continue;
}
path = await Deno.realPath(path);
const realPath = await Deno.realPath(path);
if (canonicalize) {
path = realPath;
}
// Caveat emptor: don't assume |path| is not a symlink. realpath()
// resolves symlinks but another process can replace the file system
// entity with a different type of entity before we call lstat().
({ isSymlink, isDirectory } = await Deno.lstat(path));
({ isSymlink, isDirectory } = await Deno.lstat(realPath));
}
if (isSymlink || isDirectory) {
@ -183,6 +193,7 @@ export function* walkSync(
includeDirs = true,
includeSymlinks = true,
followSymlinks = false,
canonicalize = true,
exts = undefined,
match = undefined,
skip = undefined,
@ -216,11 +227,14 @@ export function* walkSync(
}
continue;
}
path = Deno.realPathSync(path);
const realPath = Deno.realPathSync(path);
if (canonicalize) {
path = realPath;
}
// Caveat emptor: don't assume |path| is not a symlink. realpath()
// resolves symlinks but another process can replace the file system
// entity with a different type of entity before we call lstat().
({ isSymlink, isDirectory } = Deno.lstatSync(path));
({ isSymlink, isDirectory } = Deno.lstatSync(realPath));
}
if (isSymlink || isDirectory) {

View File

@ -74,12 +74,18 @@ Deno.test("[fs/walk] skip", async () =>
// https://github.com/denoland/deno_std/issues/1358
Deno.test("[fs/walk] symlink", async () =>
await assertWalkPaths("symlink", [".", "x", "x"], {
await assertWalkPaths("symlink", [".", "a", "a/z", "a", "a/z", "x", "x"], {
followSymlinks: true,
}));
Deno.test("[fs/walk] symlink without canonicalize", async () =>
await assertWalkPaths("symlink", [".", "a", "a/z", "b", "b/z", "x", "y"], {
followSymlinks: true,
canonicalize: false,
}));
Deno.test("[fs/walk] symlink without followSymlink", async () => {
await assertWalkPaths("symlink", [".", "x", "y"], {
await assertWalkPaths("symlink", [".", "a", "a/z", "b", "x", "y"], {
followSymlinks: false,
});
});