Compare commits

...

6 Commits

Author SHA1 Message Date
autofix-ci[bot]
0fab19276c [autofix.ci] apply automated fixes 2025-08-21 08:10:49 +00:00
Claude Bot
8d5d5268b1 Use shared UV_DIRENT constants from fs.constants
Instead of redefining constants locally, use the centrally defined
UV_DIRENT constants from fs.constants. These constants are already
exposed in the fs module and properly maintained in the C++ bindings.

This eliminates code duplication and ensures consistency across the
codebase. The constants are defined in:
- C++: ProcessBindingConstants.cpp (processBindingConstantsGetFs)
- Accessible as: fs.constants.UV_DIRENT_*
2025-08-21 08:08:15 +00:00
Claude Bot
777c833b24 Use proper Dirent constructor and const assertions
- Use actual fs.Dirent constructor so instanceof works correctly
- Make UV_DIRENT constants actual const assertions with 'as const'
- Add instanceof Dirent test to verify proper object type
- Significantly cleaner and more correct implementation

Now fs.glob with withFileTypes returns proper Dirent objects that work
with instanceof checks, just like fs.readdir does.
2025-08-21 08:05:02 +00:00
Claude Bot
87d935c4d2 Use named constants instead of magic numbers for UV_DIRENT types
Replace magic numbers with well-documented UV_DIRENT constants that match
the libuv uv_dirent_type_t enum values. This makes the code more readable
and maintainable.

The constants are defined locally since they're not exported in Node.js
os.constants, but they match the official libuv values.
2025-08-21 07:55:35 +00:00
autofix-ci[bot]
c4f5701334 [autofix.ci] apply automated fixes 2025-08-21 07:43:26 +00:00
Claude Bot
2e6635154c Fix fs.glob withFileTypes support (#22018)
This commit implements support for the `withFileTypes` option in `fs.glob()` and `fs.globSync()`.

Previously, using `withFileTypes: true` would throw a TypeError with the message "fs.glob does not support options.withFileTypes yet. Please open an issue on GitHub."

Changes:
- Remove the error that was blocking withFileTypes usage
- Implement createDirent() function that creates Dirent-like objects with proper file type detection
- Add support for both sync and async glob functions
- Use lstatSync to get accurate file type information
- Handle relative paths correctly when cwd is specified

The implementation creates Dirent-compatible objects with:
- name: filename
- parentPath: absolute path to parent directory
- isFile(), isDirectory(), isSymbolicLink(), etc. methods that work correctly

Fixes #22018

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 07:41:12 +00:00
2 changed files with 193 additions and 7 deletions

View File

@@ -14,10 +14,11 @@ interface GlobOptions {
withFileTypes?: boolean;
}
async function* glob(pattern: string | string[], options?: GlobOptions): AsyncGenerator<string> {
async function* glob(pattern: string | string[], options?: GlobOptions): AsyncGenerator<string | any> {
const patterns = validatePattern(pattern);
const globOptions = mapOptions(options || {});
const exclude = globOptions.exclude;
const withFileTypes = options?.withFileTypes || false;
const excludeGlobs = Array.isArray(exclude)
? exclude.flatMap(pattern => [new Bun.Glob(pattern), new Bun.Glob(pattern.replace(/\/+$/, "") + "/**")])
: null;
@@ -32,15 +33,20 @@ async function* glob(pattern: string | string[], options?: GlobOptions): AsyncGe
}
}
yield ent;
if (withFileTypes) {
yield createDirent(ent, globOptions.cwd);
} else {
yield ent;
}
}
}
}
function* globSync(pattern: string | string[], options?: GlobOptions): Generator<string> {
function* globSync(pattern: string | string[], options?: GlobOptions): Generator<string | any> {
const patterns = validatePattern(pattern);
const globOptions = mapOptions(options || {});
const exclude = globOptions.exclude;
const withFileTypes = options?.withFileTypes || false;
const excludeGlobs = Array.isArray(exclude)
? exclude.flatMap(pattern => [new Bun.Glob(pattern), new Bun.Glob(pattern.replace(/\/+$/, "") + "/**")])
: null;
@@ -55,11 +61,71 @@ function* globSync(pattern: string | string[], options?: GlobOptions): Generator
}
}
yield ent;
if (withFileTypes) {
yield createDirent(ent, globOptions.cwd);
} else {
yield ent;
}
}
}
}
function createDirent(path: string, cwd?: string): any {
const { basename, dirname, resolve, join } = require("node:path");
const { lstatSync, Dirent, constants } = require("node:fs");
// Use the shared UV_DIRENT constants from fs.constants
const {
UV_DIRENT_UNKNOWN,
UV_DIRENT_FILE,
UV_DIRENT_DIR,
UV_DIRENT_LINK,
UV_DIRENT_FIFO,
UV_DIRENT_SOCKET,
UV_DIRENT_CHAR,
UV_DIRENT_BLOCK,
} = constants;
try {
// Construct the full path if cwd is provided
const fullPath = cwd ? join(cwd, path) : path;
// Use lstatSync to get file info without following symlinks
const stats = lstatSync(fullPath);
const name = basename(path);
// The parent path should be the directory containing the matched file
const parentPath = cwd ? resolve(cwd, dirname(path)) : resolve(dirname(path));
// Get the file type number that matches DirEntType enum from the C++ code
let type: number;
if (stats.isFile()) {
type = UV_DIRENT_FILE;
} else if (stats.isDirectory()) {
type = UV_DIRENT_DIR;
} else if (stats.isSymbolicLink()) {
type = UV_DIRENT_LINK;
} else if (stats.isFIFO()) {
type = UV_DIRENT_FIFO;
} else if (stats.isSocket()) {
type = UV_DIRENT_SOCKET;
} else if (stats.isCharacterDevice()) {
type = UV_DIRENT_CHAR;
} else if (stats.isBlockDevice()) {
type = UV_DIRENT_BLOCK;
} else {
type = UV_DIRENT_UNKNOWN;
}
// Create a proper Dirent object using the constructor
return new Dirent(name, type, parentPath);
} catch (err) {
// If stat fails (e.g., broken symlink), create a Dirent with unknown type
const name = basename(path);
const parentPath = cwd ? resolve(cwd, dirname(path)) : resolve(dirname(path));
return new Dirent(name, UV_DIRENT_UNKNOWN, parentPath);
}
}
function validatePattern(pattern: string | string[]): string[] {
if (Array.isArray(pattern)) {
validateArray(pattern, "pattern");
@@ -86,9 +152,7 @@ function mapOptions(options: GlobOptions): GlobScanOptions & { exclude: GlobOpti
validateFunction(exclude, "options.exclude");
}
if (options.withFileTypes) {
throw new TypeError("fs.glob does not support options.withFileTypes yet. Please open an issue on GitHub.");
}
// withFileTypes is now supported
return {
// NOTE: this is subtly different from Glob's default behavior.

View File

@@ -0,0 +1,122 @@
import { expect, test } from "bun:test";
import { Dirent, globSync } from "fs";
import { tempDirWithFiles } from "harness";
test("fs.globSync with withFileTypes should return Dirent objects", async () => {
const dir = tempDirWithFiles("glob-withFileTypes", {
"file1.txt": "content1",
"file2.js": "console.log('hello')",
"subdir/file3.txt": "content3",
"subdir/file4.md": "# Title",
});
// Test globSync with withFileTypes: true
const results = Array.from(
globSync("*", {
cwd: dir,
withFileTypes: true,
}),
);
expect(results.length).toBeGreaterThan(0);
for (const dirent of results) {
// Check that we got proper Dirent objects with instanceof
expect(dirent instanceof Dirent).toBe(true);
// Check that we got Dirent objects
expect(dirent).toHaveProperty("name");
expect(dirent).toHaveProperty("isFile");
expect(dirent).toHaveProperty("isDirectory");
expect(dirent).toHaveProperty("isSymbolicLink");
expect(dirent).toHaveProperty("isBlockDevice");
expect(dirent).toHaveProperty("isCharacterDevice");
expect(dirent).toHaveProperty("isFIFO");
expect(dirent).toHaveProperty("isSocket");
// Verify methods work
expect(typeof dirent.isFile()).toBe("boolean");
expect(typeof dirent.isDirectory()).toBe("boolean");
expect(typeof dirent.isSymbolicLink()).toBe("boolean");
// Check name property
expect(typeof dirent.name).toBe("string");
expect(dirent.name.length).toBeGreaterThan(0);
// Check parentPath property (should be the cwd)
expect(dirent.parentPath).toBe(dir);
}
// Verify that we have both files and directories
const files = results.filter(d => d.isFile());
const dirs = results.filter(d => d.isDirectory());
expect(files.length).toBeGreaterThan(0);
expect(dirs.length).toBeGreaterThan(0);
});
test("fs.globSync with withFileTypes: false should return strings", async () => {
const dir = tempDirWithFiles("glob-strings", {
"file1.txt": "content1",
"file2.js": "console.log('hello')",
});
const results = Array.from(
globSync("*", {
cwd: dir,
withFileTypes: false,
}),
);
expect(results.length).toBeGreaterThan(0);
for (const result of results) {
// Check that we got strings, not Dirent objects
expect(typeof result).toBe("string");
expect(result).not.toHaveProperty("isFile");
}
});
test("fs.globSync default behavior should return strings", async () => {
const dir = tempDirWithFiles("glob-default", {
"file1.txt": "content1",
"file2.js": "console.log('hello')",
});
const results = Array.from(
globSync("*", {
cwd: dir,
}),
);
expect(results.length).toBeGreaterThan(0);
for (const result of results) {
// Check that we got strings by default
expect(typeof result).toBe("string");
expect(result).not.toHaveProperty("isFile");
}
});
test("fs.globSync withFileTypes with nested patterns", async () => {
const dir = tempDirWithFiles("glob-nested", {
"file1.txt": "content1",
"subdir/file2.txt": "content2",
"subdir/nested/file3.txt": "content3",
});
const results = Array.from(
globSync("**/*.txt", {
cwd: dir,
withFileTypes: true,
}),
);
expect(results.length).toBe(3);
for (const dirent of results) {
expect(dirent.isFile()).toBe(true);
expect(dirent.isDirectory()).toBe(false);
expect(dirent.name.endsWith(".txt")).toBe(true);
}
});