Repository: denoland/deno
Pull request: fix(ext/node): `fs.path`' `makelong` and `resolve` compatibility

Changes are based on Node.js' implementation https://github.com/nodejs/node/tree/v24.2.0/lib/path.js.

It allows this tests to pass:
- [parallel/test-path-makelong.js](https://github.com/nodejs/node/blob/v24.2.0/test/parallel/test-path-makelong.js)
- [parallel/test-path-resolve.js](https://github.com/nodejs/node/blob/v24.2.0/test/parallel/test-path-resolve.js)

Changed files:
filename: ext/node/polyfills/path/_posix.ts
patch: @@ -16,36 +16,58 @@ import {
   normalizeString,
 } from "ext:deno_node/path/_util.ts";
 import { primordials } from "ext:core/mod.js";
-
-const { StringPrototypeSlice, StringPrototypeCharCodeAt, TypeError } =
-  primordials;
+import { validateString } from "ext:deno_node/internal/validators.mjs";
+import { isWindows } from "ext:deno_node/_util/os.ts";
+import process from "node:process";
+
+const {
+  StringPrototypeReplace,
+  StringPrototypeCharCodeAt,
+  StringPrototypeIndexOf,
+  StringPrototypeSlice,
+  SafeRegExp,
+} = primordials;
 
 export const sep = "/";
 export const delimiter = ":";
 
+const posixCwd = (() => {
+  if (isWindows) {
+    // Converts Windows' backslash path separators to POSIX forward slashes
+    // and truncates any drive indicator
+    const regexp = new SafeRegExp(/\\/g);
+    return () => {
+      const cwd = StringPrototypeReplace(process.cwd(), regexp, "/");
+      return StringPrototypeSlice(cwd, StringPrototypeIndexOf(cwd, "/"));
+    };
+  }
+
+  // We're already on POSIX, no need for any transformations
+  return () => process.cwd();
+})();
+
 // path.resolve([from ...], to)
 /**
  * Resolves `pathSegments` into an absolute path.
  * @param pathSegments an array of path segments
  */
 export function resolve(...pathSegments: string[]): string {
+  if (
+    pathSegments.length === 0 ||
+    (pathSegments.length === 1 &&
+      (pathSegments[0] === "" || pathSegments[0] === "."))
+  ) {
+    const cwd = posixCwd();
+    if (StringPrototypeCharCodeAt(cwd, 0) === CHAR_FORWARD_SLASH) {
+      return cwd;
+    }
+  }
   let resolvedPath = "";
   let resolvedAbsolute = false;
 
-  for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
-    let path: string;
-
-    if (i >= 0) path = pathSegments[i];
-    else {
-      // deno-lint-ignore no-explicit-any
-      const { Deno } = globalThis as any;
-      if (typeof Deno?.cwd !== "function") {
-        throw new TypeError("Resolved a relative path without a CWD.");
-      }
-      path = Deno.cwd();
-    }
-
-    assertPath(path);
+  for (let i = pathSegments.length - 1; i >= 0 && !resolvedAbsolute; i--) {
+    const path = pathSegments[i];
+    validateString(path, `paths[${i}]`);
 
     // Skip empty entries
     if (path.length === 0) {
@@ -57,6 +79,12 @@ export function resolve(...pathSegments: string[]): string {
       StringPrototypeCharCodeAt(path, 0) === CHAR_FORWARD_SLASH;
   }
 
+  if (!resolvedAbsolute) {
+    const cwd = posixCwd();
+    resolvedPath = `${cwd}/${resolvedPath}`;
+    resolvedAbsolute = StringPrototypeCharCodeAt(cwd, 0) === CHAR_FORWARD_SLASH;
+  }
+
   // At this point the path should be resolved to a full absolute path, but
   // handle relative paths to be safe (might happen when process.cwd() fails)
 
@@ -69,10 +97,9 @@ export function resolve(...pathSegments: string[]): string {
   );
 
   if (resolvedAbsolute) {
-    if (resolvedPath.length > 0) return `/${resolvedPath}`;
-    else return "/";
-  } else if (resolvedPath.length > 0) return resolvedPath;
-  else return ".";
+    return `/${resolvedPath}`;
+  }
+  return resolvedPath.length > 0 ? resolvedPath : ".";
 }
 
 /**

filename: ext/node/polyfills/path/_win32.ts
patch: @@ -23,6 +23,7 @@ import {
 } from "ext:deno_node/path/_util.ts";
 import { assert } from "ext:deno_node/_util/asserts.ts";
 import { primordials } from "ext:core/mod.js";
+import process from "node:process";
 
 const {
   StringPrototypeCharCodeAt,
@@ -54,20 +55,34 @@ export function resolve(...pathSegments: string[]): string {
         throw new TypeError("Resolved a drive-letter-less path without a CWD.");
       }
       path = Deno.cwd();
+      if (
+        pathSegments.length === 0 ||
+        ((pathSegments.length === 1 &&
+          (pathSegments[0] === "" || pathSegments[0] === ".")) &&
+          isPathSeparator(StringPrototypeCharCodeAt(path, 0)))
+      ) {
+        return path;
+      }
     } else {
       if (
         typeof Deno?.env?.get !== "function" || typeof Deno?.cwd !== "function"
       ) {
         throw new TypeError("Resolved a relative path without a CWD.");
       }
-      path = Deno.cwd();
+      // Windows has the concept of drive-specific current working
+      // directories. If we've resolved a drive letter but not yet an
+      // absolute path, get cwd for that drive, or the process cwd if
+      // the drive cwd is not available. We're sure the device is not
+      // a UNC path at this points, because UNC paths are always absolute.
+      path = process.env[`=${resolvedDevice}`] || Deno.cwd();
 
       // Verify that a cwd was found and that it actually points
       // to our drive. If not, default to the drive's root.
       if (
         path === undefined ||
-        StringPrototypeToLowerCase(StringPrototypeSlice(path, 0, 3)) !==
-          `${StringPrototypeToLowerCase(resolvedDevice)}\\`
+        (StringPrototypeToLowerCase(StringPrototypeSlice(path, 0, 2)) !==
+            StringPrototypeToLowerCase(resolvedDevice) &&
+          StringPrototypeCharCodeAt(path, 2) === CHAR_BACKWARD_SLASH)
       ) {
         path = `${resolvedDevice}\\`;
       }
@@ -76,107 +91,115 @@ export function resolve(...pathSegments: string[]): string {
     assertPath(path);
 
     const len = path.length;
-
-    // Skip empty entries
-    if (len === 0) continue;
-
     let rootEnd = 0;
     let device = "";
     let isAbsolute = false;
     const code = StringPrototypeCharCodeAt(path, 0);
 
     // Try to match a root
-    if (len > 1) {
+    if (len === 1) {
       if (isPathSeparator(code)) {
-        // Possible UNC root
-
-        // If we started with a separator, we know we at least have an
-        // absolute path of some kind (UNC or otherwise)
+        // `path` contains just a path separator
+        rootEnd = 1;
         isAbsolute = true;
+      }
+    } else if (isPathSeparator(code)) {
+      // Possible UNC root
 
-        if (isPathSeparator(StringPrototypeCharCodeAt(path, 1))) {
-          // Matched double path separator at beginning
-          let j = 2;
-          let last = j;
-          // Match 1 or more non-path separators
-          for (; j < len; ++j) {
-            if (isPathSeparator(StringPrototypeCharCodeAt(path, j))) break;
+      // If we started with a separator, we know we at least have an
+      // absolute path of some kind (UNC or otherwise)
+      isAbsolute = true;
+
+      if (isPathSeparator(StringPrototypeCharCodeAt(path, 1))) {
+        // Matched double path separator at beginning
+        let j = 2;
+        let last = j;
+        // Match 1 or more non-path separators
+        while (
+          j < len &&
+          !isPathSeparator(StringPrototypeCharCodeAt(path, j))
+        ) {
+          j++;
+        }
+        if (j < len && j !== last) {
+          const firstPart = StringPrototypeSlice(path, last, j);
+          // Matched!
+          last = j;
+          // Match 1 or more path separators
+          while (
+            j < len &&
+            isPathSeparator(StringPrototypeCharCodeAt(path, j))
+          ) {
+            j++;
           }
           if (j < len && j !== last) {
-            const firstPart = StringPrototypeSlice(path, last, j);
             // Matched!
             last = j;
-            // Match 1 or more path separators
-            for (; j < len; ++j) {
-              if (!isPathSeparator(StringPrototypeCharCodeAt(path, j))) break;
+            // Match 1 or more non-path separators
+            while (
+              j < len &&
+              !isPathSeparator(StringPrototypeCharCodeAt(path, j))
+            ) {
+              j++;
             }
-            if (j < len && j !== last) {
-              // Matched!
-              last = j;
-              // Match 1 or more non-path separators
-              for (; j < len; ++j) {
-                if (isPathSeparator(StringPrototypeCharCodeAt(path, j))) break;
-              }
-              if (j === len) {
-                // We matched a UNC root only
-                device = `\\\\${firstPart}\\${
-                  StringPrototypeSlice(path, last)
-                }`;
-                rootEnd = j;
-              } else if (j !== last) {
-                // We matched a UNC root with leftovers
-
+            if (j === len || j !== last) {
+              if (firstPart !== "." && firstPart !== "?") {
+                // We matched a UNC root
                 device = `\\\\${firstPart}\\${
                   StringPrototypeSlice(path, last, j)
                 }`;
                 rootEnd = j;
+              } else {
+                // We matched a device root (e.g. \\\\.\\PHYSICALDRIVE0)
+                device = `\\\\${firstPart}`;
+                rootEnd = 4;
               }
             }
           }
-        } else {
-          rootEnd = 1;
-        }
-      } else if (isWindowsDeviceRoot(code)) {
-        // Possible device root
-
-        if (StringPrototypeCharCodeAt(path, 1) === CHAR_COLON) {
-          device = StringPrototypeSlice(path, 0, 2);
-          rootEnd = 2;
-          if (len > 2) {
-            if (isPathSeparator(StringPrototypeCharCodeAt(path, 2))) {
-              // Treat separator following drive name as an absolute path
-              // indicator
-              isAbsolute = true;
-              rootEnd = 3;
-            }
-          }
         }
+      } else {
+        rootEnd = 1;
       }
-    } else if (isPathSeparator(code)) {
-      // `path` contains just a path separator
-      rootEnd = 1;
-      isAbsolute = true;
-    }
-
-    if (
-      device.length > 0 &&
-      resolvedDevice.length > 0 &&
-      StringPrototypeToLowerCase(device) !==
-        StringPrototypeToLowerCase(resolvedDevice)
+    } else if (
+      isWindowsDeviceRoot(code) &&
+      StringPrototypeCharCodeAt(path, 1) === CHAR_COLON
     ) {
-      // This path points to another device so it is not applicable
-      continue;
+      // Possible device root
+      device = StringPrototypeSlice(path, 0, 2);
+      rootEnd = 2;
+      if (len > 2 && isPathSeparator(StringPrototypeCharCodeAt(path, 2))) {
+        // Treat separator following drive name as an absolute path
+        // indicator
+        isAbsolute = true;
+        rootEnd = 3;
+      }
     }
 
-    if (resolvedDevice.length === 0 && device.length > 0) {
-      resolvedDevice = device;
+    if (device.length > 0) {
+      if (resolvedDevice.length > 0) {
+        if (
+          StringPrototypeToLowerCase(device) !==
+            StringPrototypeToLowerCase(resolvedDevice)
+        ) {
+          // This path points to another device so it is not applicable
+          continue;
+        }
+      } else {
+        resolvedDevice = device;
+      }
     }
-    if (!resolvedAbsolute) {
+
+    if (resolvedAbsolute) {
+      if (resolvedDevice.length > 0) {
+        break;
+      }
+    } else {
       resolvedTail = `${StringPrototypeSlice(path, rootEnd)}\\${resolvedTail}`;
       resolvedAbsolute = isAbsolute;
+      if (isAbsolute && resolvedDevice.length > 0) {
+        break;
+      }
     }
-
-    if (resolvedAbsolute && resolvedDevice.length > 0) break;
   }
 
   // At this point the path should be resolved to a full absolute path,
@@ -191,7 +214,9 @@ export function resolve(...pathSegments: string[]): string {
     isPathSeparator,
   );
 
-  return resolvedDevice + (resolvedAbsolute ? "\\" : "") + resolvedTail || ".";
+  return resolvedAbsolute
+    ? `${resolvedDevice}\\${resolvedTail}`
+    : `${resolvedDevice}${resolvedTail}` || ".";
 }
 
 /**
@@ -545,33 +570,30 @@ export function toNamespacedPath(path: string): string {
 
   const resolvedPath = resolve(path);
 
-  if (resolvedPath.length >= 3) {
-    if (StringPrototypeCharCodeAt(resolvedPath, 0) === CHAR_BACKWARD_SLASH) {
-      // Possible UNC root
+  if (resolvedPath.length <= 2) {
+    return path;
+  }
 
-      if (StringPrototypeCharCodeAt(resolvedPath, 1) === CHAR_BACKWARD_SLASH) {
-        const code = StringPrototypeCharCodeAt(resolvedPath, 2);
-        if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
-          // Matched non-long UNC root, convert the path to a long UNC path
-          return `\\\\?\\UNC\\${StringPrototypeSlice(resolvedPath, 2)}`;
-        }
-      }
-    } else if (
-      isWindowsDeviceRoot(StringPrototypeCharCodeAt(resolvedPath, 0))
-    ) {
-      // Possible device root
+  if (StringPrototypeCharCodeAt(resolvedPath, 0) === CHAR_BACKWARD_SLASH) {
+    // Possible UNC root
 
-      if (
-        StringPrototypeCharCodeAt(resolvedPath, 1) === CHAR_COLON &&
-        StringPrototypeCharCodeAt(resolvedPath, 2) === CHAR_BACKWARD_SLASH
-      ) {
-        // Matched device root, convert the path to a long UNC path
-        return `\\\\?\\${resolvedPath}`;
+    if (StringPrototypeCharCodeAt(resolvedPath, 1) === CHAR_BACKWARD_SLASH) {
+      const code = StringPrototypeCharCodeAt(resolvedPath, 2);
+      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
+        // Matched non-long UNC root, convert the path to a long UNC path
+        return `\\\\?\\UNC\\${StringPrototypeSlice(resolvedPath, 2)}`;
       }
     }
+  } else if (
+    isWindowsDeviceRoot(StringPrototypeCharCodeAt(resolvedPath, 0)) &&
+    StringPrototypeCharCodeAt(resolvedPath, 1) === CHAR_COLON &&
+    StringPrototypeCharCodeAt(resolvedPath, 2) === CHAR_BACKWARD_SLASH
+  ) {
+    // Matched device root, convert the path to a long UNC path
+    return `\\\\?\\${resolvedPath}`;
   }
 
-  return path;
+  return resolvedPath;
 }
 
 /**

filename: tests/node_compat/config.toml
patch: @@ -823,8 +823,10 @@
 "parallel/test-path-dirname.js" = {}
 "parallel/test-path-extname.js" = {}
 "parallel/test-path-isabsolute.js" = {}
+"parallel/test-path-makelong.js" = {}
 "parallel/test-path-parse-format.js" = {}
 "parallel/test-path-posix-exists.js" = {}
+"parallel/test-path-resolve.js" = {}
 "parallel/test-path-win32-exists.js" = {}
 "parallel/test-path-zero-length-strings.js" = {}
 "parallel/test-path.js" = {}