Repository: denoland/deno
Pull request: fix(ext/node): add parentPath to Dirent from fs.opendir and support Buffer paths in lstat

Closes https://github.com/denoland/deno/issues/32175

## Summary

- **Fix `fs.opendir` Dirent `parentPath`**: `Dir.read()` and `Dir.readSync()` now pass the directory path to `direntFromDeno()`, so Dirents correctly have `parentPath` set (was `undefined` before).
- **Fix `lstat` with Buffer paths**: Added Buffer-to-string conversion in `lstat`/`lstatSync`, fixing `getDirents`/`getDirent` when filenames are Buffers (previously failed with ENOENT).
- **Enable `test-fs-utils-get-dirents.js`** in node_compat test suite.

## Test plan

- [x] `cargo test --test node_compat parallel -- --filter "test-fs-utils-get-dirents"` passes
- [x] Verified `fs.opendir` Dirents now have correct `parentPath`
- [x] Verified `fs.readdir` Dirents are unaffected (backwards compatible)
- [x] Pre-existing failures in `test-fs-opendir.js`, `test-fs-readdir-types.js`, `test-fs-readdir-buffer.js` confirmed to exist on `main` as well

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Changed files:
filename: ext/node/polyfills/_fs/_fs_dir.ts
patch: @@ -50,12 +50,16 @@ export default class Dir {
         AsyncGeneratorPrototypeNext(this.#asyncIterator),
         (iteratorResult) => {
           resolve(
-            iteratorResult.done ? null : direntFromDeno(iteratorResult.value),
+            iteratorResult.done
+              ? null
+              : direntFromDeno(iteratorResult.value, this.#dirPath),
           );
           if (callback) {
             callback(
               null,
-              iteratorResult.done ? null : direntFromDeno(iteratorResult.value),
+              iteratorResult.done
+                ? null
+                : direntFromDeno(iteratorResult.value, this.#dirPath),
             );
           }
         },
@@ -78,7 +82,7 @@ export default class Dir {
     if (iteratorResult.done) {
       return null;
     } else {
-      return direntFromDeno(iteratorResult.value);
+      return direntFromDeno(iteratorResult.value, this.#dirPath);
     }
   }
 

filename: ext/node/polyfills/_fs/_fs_lstat.ts
patch: @@ -9,6 +9,7 @@ import {
   statOptions,
   Stats,
 } from "ext:deno_node/_fs/_fs_stat.ts";
+import { getValidatedPathToString } from "ext:deno_node/internal/fs/utils.mjs";
 import { promisify } from "ext:deno_node/internal/util.mjs";
 import { primordials } from "ext:core/mod.js";
 
@@ -47,7 +48,7 @@ export function lstat(
   if (!callback) throw new Error("No callback function supplied");
 
   PromisePrototypeThen(
-    Deno.lstat(path),
+    Deno.lstat(getValidatedPathToString(path)),
     (stat) => callback(null, CFISBIS(stat, options.bigint)),
     (err) => callback(denoErrorToNodeError(err, { syscall: "lstat" })),
   );
@@ -73,7 +74,7 @@ export function lstatSync(
   options?: statOptions,
 ): Stats | BigIntStats {
   try {
-    const origin = Deno.lstatSync(path);
+    const origin = Deno.lstatSync(getValidatedPathToString(path));
     return CFISBIS(origin, options?.bigint || false);
   } catch (err) {
     if (

filename: ext/node/polyfills/internal/fs/utils.mjs
patch: @@ -195,7 +195,7 @@ export class Dirent {
   }
 }
 
-export function direntFromDeno(entry) {
+export function direntFromDeno(entry, path) {
   let type;
 
   if (entry.isDirectory) {
@@ -206,7 +206,7 @@ export function direntFromDeno(entry) {
     type = UV_DIRENT_LINK;
   }
 
-  return new Dirent(entry.name, type, entry.parentPath);
+  return new Dirent(entry.name, type, path ?? entry.parentPath);
 }
 
 export class DirentFromStats extends Dirent {

filename: tests/node_compat/config.jsonc
patch: @@ -593,6 +593,7 @@
     "parallel/test-fs-symlink-dir.js": {},
     "parallel/test-fs-symlink.js": {},
     "parallel/test-fs-unlink-type-check.js": {},
+    "parallel/test-fs-utils-get-dirents.js": {},
     "parallel/test-fs-util-validateoffsetlength.js": {},
     "parallel/test-fs-write-file-typedarrays.js": {},
     "parallel/test-fs-write-file.js": {