char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport path extension to typescript38d5bbf

main
50.2 KiB1688 linesraw
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22// Lexical POSIX and Windows path operations adapted from Node.js
23// `lib/path.js` at v22.16.0 (commit 9774069718d9f80578079d252dddf37ae6fd550d).
24// See vendor/AVRIL.md for provenance and the full list of local differences:
25// no `process` or `process.env` access (avril's permission-checked cwd op
26// with drive-root fallback instead of per-drive environment entries), no
27// `glob`/`matchesGlob`, no legacy `_makeLong`, and hardened primordial use.
28
29import {
30  ArrayIsArray,
31  ArrayPrototypeJoin,
32  ArrayPrototypePop,
33  ArrayPrototypePush,
34  ArrayPrototypeSlice,
35  ObjectFreeze,
36  StringPrototypeCharCodeAt,
37  StringPrototypeIncludes,
38  StringPrototypeIndexOf,
39  StringPrototypeLastIndexOf,
40  StringPrototypeRepeat,
41  StringPrototypeSlice,
42  StringPrototypeToLowerCase,
43  TypeError,
44} from "ext:core/primordials";
45import { op_path_platform, op_process_cwd } from "ext:core/ops";
46import { result } from "ext:file/errors.js";
47
48const CHAR_UPPERCASE_A = 65; // A
49const CHAR_LOWERCASE_A = 97; // a
50const CHAR_UPPERCASE_Z = 90; // Z
51const CHAR_LOWERCASE_Z = 122; // z
52const CHAR_DOT = 46; // .
53const CHAR_FORWARD_SLASH = 47; // /
54const CHAR_BACKWARD_SLASH = 92; // \
55const CHAR_COLON = 58; // :
56const CHAR_QUESTION_MARK = 63; // ?
57
58const hostIsWindows = op_path_platform() === "win32";
59
60function describeReceived(value: unknown): string {
61  if (value === null) return "null";
62  if (typeof value !== "object") return typeof value;
63  return ArrayIsArray(value) ? "an instance of Array" : "an instance of Object";
64}
65
66function validateString(value: unknown, name: string): asserts value is string {
67  if (typeof value !== "string") {
68    throw new TypeError(
69      `The "${name}" argument must be of type string. Received ${describeReceived(value)}`,
70    );
71  }
72}
73
74function validateObject(value: unknown, name: string): asserts value is Record<string, unknown> {
75  if (value === null || ArrayIsArray(value) || typeof value !== "object") {
76    throw new TypeError(
77      `The "${name}" argument must be of type Object. Received ${describeReceived(value)}`,
78    );
79  }
80}
81
82// Single-character string split without consulting a separator object's
83// Symbol.split hook: only primitive one-code-unit separators are supported.
84function splitOnChar(input: string, code: number): string[] {
85  const parts: string[] = [];
86  let start = 0;
87  for (let i = 0; i < input.length; i++) {
88    if (StringPrototypeCharCodeAt(input, i) === code) {
89      ArrayPrototypePush(parts, StringPrototypeSlice(input, start, i));
90      start = i + 1;
91    }
92  }
93  ArrayPrototypePush(parts, StringPrototypeSlice(input, start, input.length));
94  return parts;
95}
96
97// Permission-checked working directory. Called only when a relative,
98// root-relative, or drive-relative input actually needs it; fully absolute
99// inputs never reach here, so they need no read grant. A missing grant
100// throws the usual PermissionDenied failure.
101function nativeCwd() {
102  return result(op_process_cwd());
103}
104
105function isPathSeparator(code: number): boolean {
106  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
107}
108
109function isPosixPathSeparator(code: number): boolean {
110  return code === CHAR_FORWARD_SLASH;
111}
112
113function isWindowsDeviceRoot(code: number): boolean {
114  return (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
115    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z);
116}
117
118// Resolves . and .. elements in a path with directory names
119function normalizeString(path: string, allowAboveRoot: boolean, separator: string, isPathSeparator: (code: number) => boolean): string {
120  let res = "";
121  let lastSegmentLength = 0;
122  let lastSlash = -1;
123  let dots = 0;
124  let code = 0;
125  for (let i = 0; i <= path.length; ++i) {
126    if (i < path.length) {
127      code = StringPrototypeCharCodeAt(path, i);
128    } else if (isPathSeparator(code)) {
129      break;
130    } else {
131      code = CHAR_FORWARD_SLASH;
132    }
133
134    if (isPathSeparator(code)) {
135      if (lastSlash === i - 1 || dots === 1) {
136        // NOOP
137      } else if (dots === 2) {
138        if (
139          res.length < 2 || lastSegmentLength !== 2 ||
140          StringPrototypeCharCodeAt(res, res.length - 1) !== CHAR_DOT ||
141          StringPrototypeCharCodeAt(res, res.length - 2) !== CHAR_DOT
142        ) {
143          if (res.length > 2) {
144            const lastSlashIndex = StringPrototypeLastIndexOf(res, separator);
145            if (lastSlashIndex === -1) {
146              res = "";
147              lastSegmentLength = 0;
148            } else {
149              res = StringPrototypeSlice(res, 0, lastSlashIndex);
150              lastSegmentLength =
151                res.length - 1 - StringPrototypeLastIndexOf(res, separator);
152            }
153            lastSlash = i;
154            dots = 0;
155            continue;
156          } else if (res.length !== 0) {
157            res = "";
158            lastSegmentLength = 0;
159            lastSlash = i;
160            dots = 0;
161            continue;
162          }
163        }
164        if (allowAboveRoot) {
165          res += res.length > 0 ? `${separator}..` : "..";
166          lastSegmentLength = 2;
167        }
168      } else {
169        if (res.length > 0) {
170          res += `${separator}${StringPrototypeSlice(path, lastSlash + 1, i)}`;
171        } else {
172          res = StringPrototypeSlice(path, lastSlash + 1, i);
173        }
174        lastSegmentLength = i - lastSlash - 1;
175      }
176      lastSlash = i;
177      dots = 0;
178    } else if (code === CHAR_DOT && dots !== -1) {
179      ++dots;
180    } else {
181      dots = -1;
182    }
183  }
184  return res;
185}
186
187function formatExt(ext: string | undefined): string {
188  return ext ? `${ext[0] === "." ? "" : "."}${ext}` : "";
189}
190
191function _format(sep: string, pathObject: PathFormatObject): string {
192  validateObject(pathObject, "pathObject");
193  const dir = pathObject.dir || pathObject.root;
194  const base = pathObject.base ||
195    `${pathObject.name || ""}${formatExt(pathObject.ext)}`;
196  if (!dir) {
197    return base;
198  }
199  return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`;
200}
201
202// The working directory for a Windows drive. Node reads the drive's hidden
203// per-drive directory from the process environment here; avril grants no
204// environment access for paths, so a cwd on another drive (or with no drive
205// letter at all, as on POSIX hosts) falls back to the drive's root.
206function cwdForDrive(device: string): string {
207  const cwd = nativeCwd();
208  if (
209    cwd.length >= 2 &&
210    StringPrototypeToLowerCase(StringPrototypeSlice(cwd, 0, 2)) ===
211      StringPrototypeToLowerCase(device) &&
212    (cwd.length === 2 ||
213      StringPrototypeCharCodeAt(cwd, 2) === CHAR_BACKWARD_SLASH ||
214      StringPrototypeCharCodeAt(cwd, 2) === CHAR_FORWARD_SLASH)
215  ) {
216    return cwd;
217  }
218  return `${device}\\`;
219}
220
221type PathFormatObject = { dir?: string; root?: string; base?: string; name?: string; ext?: string };
222type ParsedPath = { root: string; dir: string; base: string; ext: string; name: string };
223type PathApi = {
224  resolve(...paths: string[]): string;
225  normalize(path: string): string;
226  isAbsolute(path: string): boolean;
227  join(...paths: string[]): string;
228  relative(from: string, to: string): string;
229  toNamespacedPath(path: string): string;
230  dirname(path: string): string;
231  basename(path: string, suffix?: string): string;
232  extname(path: string): string;
233  format(pathObject: PathFormatObject): string;
234  parse(path: string): ParsedPath;
235  sep: string;
236  delimiter: string;
237};
238
239const win32: PathApi = {
240  resolve(...args) {
241    let resolvedDevice = "";
242    let resolvedTail = "";
243    let resolvedAbsolute = false;
244
245    for (let i = args.length - 1; i >= -1; i--) {
246      let path;
247      if (i >= 0) {
248        path = args[i]!;
249        validateString(path, `paths[${i}]`);
250
251        // Skip empty entries
252        if (path.length === 0) {
253          continue;
254        }
255      } else if (resolvedDevice.length === 0) {
256        path = nativeCwd();
257      } else {
258        // We're sure the device is not a UNC path at this point, because UNC
259        // paths are always absolute.
260        path = cwdForDrive(resolvedDevice);
261      }
262
263      const len = path.length;
264      let rootEnd = 0;
265      let device = "";
266      let isAbsolute = false;
267      const code = StringPrototypeCharCodeAt(path, 0);
268
269      // Try to match a root
270      if (len === 1) {
271        if (isPathSeparator(code)) {
272          // `path` contains just a path separator
273          rootEnd = 1;
274          isAbsolute = true;
275        }
276      } else if (isPathSeparator(code)) {
277        // Possible UNC root
278
279        // If we started with a separator, we know we at least have an
280        // absolute path of some kind (UNC or otherwise)
281        isAbsolute = true;
282
283        if (isPathSeparator(StringPrototypeCharCodeAt(path, 1))) {
284          // Matched double path separator at beginning
285          let j = 2;
286          let last = j;
287          // Match 1 or more non-path separators
288          while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
289            j++;
290          }
291          if (j < len && j !== last) {
292            const firstPart = StringPrototypeSlice(path, last, j);
293            // Matched!
294            last = j;
295            // Match 1 or more path separators
296            while (j < len && isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
297              j++;
298            }
299            if (j < len && j !== last) {
300              // Matched!
301              last = j;
302              // Match 1 or more non-path separators
303              while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
304                j++;
305              }
306              if (j === len || j !== last) {
307                // We matched a UNC root
308                device = `\\\\${firstPart}\\${StringPrototypeSlice(path, last, j)}`;
309                rootEnd = j;
310              }
311            }
312          }
313        } else {
314          rootEnd = 1;
315        }
316      } else if (
317        isWindowsDeviceRoot(code) &&
318        StringPrototypeCharCodeAt(path, 1) === CHAR_COLON
319      ) {
320        // Possible device root
321        device = StringPrototypeSlice(path, 0, 2);
322        rootEnd = 2;
323        if (len > 2 && isPathSeparator(StringPrototypeCharCodeAt(path, 2))) {
324          // Treat separator following drive name as an absolute path
325          // indicator
326          isAbsolute = true;
327          rootEnd = 3;
328        }
329      }
330
331      if (device.length > 0) {
332        if (resolvedDevice.length > 0) {
333          if (
334            StringPrototypeToLowerCase(device) !==
335            StringPrototypeToLowerCase(resolvedDevice)
336          ) {
337            // This path points to another device so it is not applicable
338            continue;
339          }
340        } else {
341          resolvedDevice = device;
342        }
343      }
344
345      if (resolvedAbsolute) {
346        if (resolvedDevice.length > 0) {
347          break;
348        }
349      } else {
350        resolvedTail = `${StringPrototypeSlice(path, rootEnd)}\\${resolvedTail}`;
351        resolvedAbsolute = isAbsolute;
352        if (isAbsolute && resolvedDevice.length > 0) {
353          break;
354        }
355      }
356    }
357
358    // At this point the path should be resolved to a full absolute path,
359    // but handle relative paths to be safe (might happen when the working
360    // directory lookup fails, which surfaces as PermissionDenied instead).
361
362    // Normalize the tail path
363    resolvedTail = normalizeString(
364      resolvedTail,
365      !resolvedAbsolute,
366      "\\",
367      isPathSeparator,
368    );
369
370    return resolvedAbsolute
371      ? `${resolvedDevice}\\${resolvedTail}`
372      : `${resolvedDevice}${resolvedTail}` || ".";
373  },
374
375  normalize(path) {
376    validateString(path, "path");
377    const len = path.length;
378    if (len === 0) {
379      return ".";
380    }
381    let rootEnd = 0;
382    let device;
383    let isAbsolute = false;
384    const code = StringPrototypeCharCodeAt(path, 0);
385
386    // Try to match a root
387    if (len === 1) {
388      // `path` contains just a single char, exit early to avoid
389      // unnecessary work
390      return isPosixPathSeparator(code) ? "\\" : path;
391    }
392    if (isPathSeparator(code)) {
393      // Possible UNC root
394
395      // If we started with a separator, we know we at least have an absolute
396      // path of some kind (UNC or otherwise)
397      isAbsolute = true;
398
399      if (isPathSeparator(StringPrototypeCharCodeAt(path, 1))) {
400        // Matched double path separator at beginning
401        let j = 2;
402        let last = j;
403        // Match 1 or more non-path separators
404        while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
405          j++;
406        }
407        if (j < len && j !== last) {
408          const firstPart = StringPrototypeSlice(path, last, j);
409          // Matched!
410          last = j;
411          // Match 1 or more path separators
412          while (j < len && isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
413            j++;
414          }
415          if (j < len && j !== last) {
416            // Matched!
417            last = j;
418            // Match 1 or more non-path separators
419            while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
420              j++;
421            }
422            if (j === len) {
423              // We matched a UNC root only
424              // Return the normalized version of the UNC root since there
425              // is nothing left to process
426              return `\\\\${firstPart}\\${StringPrototypeSlice(path, last)}\\`;
427            }
428            if (j !== last) {
429              // We matched a UNC root with leftovers
430              device = `\\\\${firstPart}\\${StringPrototypeSlice(path, last, j)}`;
431              rootEnd = j;
432            }
433          }
434        }
435      } else {
436        rootEnd = 1;
437      }
438    } else if (
439      isWindowsDeviceRoot(code) &&
440      StringPrototypeCharCodeAt(path, 1) === CHAR_COLON
441    ) {
442      // Possible device root
443      device = StringPrototypeSlice(path, 0, 2);
444      rootEnd = 2;
445      if (len > 2 && isPathSeparator(StringPrototypeCharCodeAt(path, 2))) {
446        // Treat separator following drive name as an absolute path
447        // indicator
448        isAbsolute = true;
449        rootEnd = 3;
450      }
451    }
452
453    let tail = rootEnd < len
454      ? normalizeString(
455        StringPrototypeSlice(path, rootEnd),
456        !isAbsolute,
457        "\\",
458        isPathSeparator,
459      )
460      : "";
461    if (tail.length === 0 && !isAbsolute) {
462      tail = ".";
463    }
464    if (
465      tail.length > 0 &&
466      isPathSeparator(StringPrototypeCharCodeAt(path, len - 1))
467    ) {
468      tail += "\\";
469    }
470    if (!isAbsolute && device === undefined && StringPrototypeIncludes(path, ":")) {
471      // If the original path was not absolute and if we have not been able to
472      // resolve it relative to a particular device, we need to ensure that the
473      // `tail` has not become something that Windows might interpret as an
474      // absolute path. See CVE-2024-36139.
475      if (
476        tail.length >= 2 &&
477        isWindowsDeviceRoot(StringPrototypeCharCodeAt(tail, 0)) &&
478        StringPrototypeCharCodeAt(tail, 1) === CHAR_COLON
479      ) {
480        return `.\\${tail}`;
481      }
482      let index = StringPrototypeIndexOf(path, ":");
483      do {
484        if (
485          index === len - 1 ||
486          isPathSeparator(StringPrototypeCharCodeAt(path, index + 1))
487        ) {
488          return `.\\${tail}`;
489        }
490      } while ((index = StringPrototypeIndexOf(path, ":", index + 1)) !== -1);
491    }
492    if (device === undefined) {
493      return isAbsolute ? `\\${tail}` : tail;
494    }
495    return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`;
496  },
497
498  isAbsolute(path) {
499    validateString(path, "path");
500    const len = path.length;
501    if (len === 0) {
502      return false;
503    }
504
505    const code = StringPrototypeCharCodeAt(path, 0);
506    return isPathSeparator(code) ||
507      // Possible device root
508      (len > 2 &&
509        isWindowsDeviceRoot(code) &&
510        StringPrototypeCharCodeAt(path, 1) === CHAR_COLON &&
511        isPathSeparator(StringPrototypeCharCodeAt(path, 2)));
512  },
513
514  join(...args) {
515    if (args.length === 0) {
516      return ".";
517    }
518
519    let joined;
520    let firstPart: string;
521    for (let i = 0; i < args.length; ++i) {
522      const arg = args[i];
523      validateString(arg, "path");
524      if (arg.length > 0) {
525        if (joined === undefined) {
526          joined = firstPart = arg;
527        } else {
528          joined += `\\${arg}`;
529        }
530      }
531    }
532
533    if (joined === undefined) {
534      return ".";
535    }
536
537    // Make sure that the joined path doesn't start with two slashes, because
538    // normalize() will mistake it for a UNC path then.
539    //
540    // This step is skipped when it is very clear that the user actually
541    // intended to point at a UNC path. This is assumed when the first
542    // non-empty string arguments starts with exactly two slashes followed by
543    // at least one more non-slash character.
544    //
545    // Note that for normalize() to treat a path as a UNC path it needs to
546    // have at least 2 components, so we don't filter for that here.
547    // This means that the user can use join to construct UNC paths from
548    // a server name and a share name; for example:
549    //   path.join('//server', 'share') -> '\\\\server\\share\\')
550    let needsReplace = true;
551    let slashCount = 0;
552    if (isPathSeparator(StringPrototypeCharCodeAt(firstPart!, 0))) {
553      ++slashCount;
554      const firstLen = firstPart!.length;
555      if (
556        firstLen > 1 &&
557        isPathSeparator(StringPrototypeCharCodeAt(firstPart!, 1))
558      ) {
559        ++slashCount;
560        if (firstLen > 2) {
561          if (isPathSeparator(StringPrototypeCharCodeAt(firstPart!, 2))) {
562            ++slashCount;
563          } else {
564            // We matched a UNC path in the first part
565            needsReplace = false;
566          }
567        }
568      }
569    }
570    if (needsReplace) {
571      // Find any more consecutive slashes we need to replace
572      while (
573        slashCount < joined.length &&
574        isPathSeparator(StringPrototypeCharCodeAt(joined, slashCount))
575      ) {
576        slashCount++;
577      }
578
579      // Replace the slashes if needed
580      if (slashCount >= 2) {
581        joined = `\\${StringPrototypeSlice(joined, slashCount)}`;
582      }
583    }
584
585    return win32.normalize(joined);
586  },
587
588  relative(from, to) {
589    validateString(from, "from");
590    validateString(to, "to");
591
592    if (from === to) {
593      return "";
594    }
595
596    const fromOrig = win32.resolve(from);
597    const toOrig = win32.resolve(to);
598
599    if (fromOrig === toOrig) {
600      return "";
601    }
602
603    from = StringPrototypeToLowerCase(fromOrig);
604    to = StringPrototypeToLowerCase(toOrig);
605
606    if (from === to) {
607      return "";
608    }
609
610    if (
611      fromOrig.length !== from.length || toOrig.length !== to.length
612    ) {
613      const fromSplit = splitOnChar(fromOrig, CHAR_BACKWARD_SLASH);
614      const toSplit = splitOnChar(toOrig, CHAR_BACKWARD_SLASH);
615      if (fromSplit[fromSplit.length - 1] === "") {
616        ArrayPrototypePop(fromSplit);
617      }
618      if (toSplit[toSplit.length - 1] === "") {
619        ArrayPrototypePop(toSplit);
620      }
621
622      const fromLen = fromSplit.length;
623      const toLen = toSplit.length;
624      const length = fromLen < toLen ? fromLen : toLen;
625
626      let i;
627      for (i = 0; i < length; i++) {
628        if (
629          StringPrototypeToLowerCase(fromSplit[i]) !==
630          StringPrototypeToLowerCase(toSplit[i])
631        ) {
632          break;
633        }
634      }
635
636      if (i === 0) {
637        return toOrig;
638      } else if (i === length) {
639        if (toLen > length) {
640          return ArrayPrototypeJoin(ArrayPrototypeSlice(toSplit, i), "\\");
641        }
642        if (fromLen > length) {
643          return StringPrototypeRepeat("..\\", fromLen - 1 - i) + "..";
644        }
645        return "";
646      }
647
648      return StringPrototypeRepeat("..\\", fromLen - i) +
649        ArrayPrototypeJoin(ArrayPrototypeSlice(toSplit, i), "\\");
650    }
651
652    // Trim any leading backslashes
653    let fromStart = 0;
654    while (
655      fromStart < from.length &&
656      StringPrototypeCharCodeAt(from, fromStart) === CHAR_BACKWARD_SLASH
657    ) {
658      fromStart++;
659    }
660    // Trim trailing backslashes (applicable to UNC paths only)
661    let fromEnd = from.length;
662    while (
663      fromEnd - 1 > fromStart &&
664      StringPrototypeCharCodeAt(from, fromEnd - 1) === CHAR_BACKWARD_SLASH
665    ) {
666      fromEnd--;
667    }
668    const fromLen = fromEnd - fromStart;
669
670    // Trim any leading backslashes
671    let toStart = 0;
672    while (
673      toStart < to.length &&
674      StringPrototypeCharCodeAt(to, toStart) === CHAR_BACKWARD_SLASH
675    ) {
676      toStart++;
677    }
678    // Trim trailing backslashes (applicable to UNC paths only)
679    let toEnd = to.length;
680    while (
681      toEnd - 1 > toStart &&
682      StringPrototypeCharCodeAt(to, toEnd - 1) === CHAR_BACKWARD_SLASH
683    ) {
684      toEnd--;
685    }
686    const toLen = toEnd - toStart;
687
688    // Compare paths to find the longest common path from root
689    const length = fromLen < toLen ? fromLen : toLen;
690    let lastCommonSep = -1;
691    let i = 0;
692    for (; i < length; i++) {
693      const fromCode = StringPrototypeCharCodeAt(from, fromStart + i);
694      if (fromCode !== StringPrototypeCharCodeAt(to, toStart + i)) {
695        break;
696      } else if (fromCode === CHAR_BACKWARD_SLASH) {
697        lastCommonSep = i;
698      }
699    }
700
701    // We found a mismatch before the first common path separator was seen, so
702    // return the original `to`.
703    if (i !== length) {
704      if (lastCommonSep === -1) {
705        return toOrig;
706      }
707    } else {
708      if (toLen > length) {
709        if (
710          StringPrototypeCharCodeAt(to, toStart + i) === CHAR_BACKWARD_SLASH
711        ) {
712          // We get here if `from` is the exact base path for `to`.
713          // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz'
714          return StringPrototypeSlice(toOrig, toStart + i + 1);
715        }
716        if (i === 2) {
717          // We get here if `from` is the device root.
718          // For example: from='C:\\'; to='C:\\foo'
719          return StringPrototypeSlice(toOrig, toStart + i);
720        }
721      }
722      if (fromLen > length) {
723        if (
724          StringPrototypeCharCodeAt(from, fromStart + i) ===
725          CHAR_BACKWARD_SLASH
726        ) {
727          // We get here if `to` is the exact base path for `from`.
728          // For example: from='C:\\foo\\bar'; to='C:\\foo'
729          lastCommonSep = i;
730        } else if (i === 2) {
731          // We get here if `to` is the device root.
732          // For example: from='C:\\foo\\bar'; to='C:\\'
733          lastCommonSep = 3;
734        }
735      }
736      if (lastCommonSep === -1) {
737        lastCommonSep = 0;
738      }
739    }
740
741    let out = "";
742    // Generate the relative path based on the path difference between `to`
743    // and `from`
744    for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
745      if (
746        i === fromEnd ||
747        StringPrototypeCharCodeAt(from, i) === CHAR_BACKWARD_SLASH
748      ) {
749        out += out.length === 0 ? ".." : "\\..";
750      }
751    }
752
753    toStart += lastCommonSep;
754
755    // Lastly, append the rest of the destination (`to`) path that comes after
756    // the common path parts
757    if (out.length > 0) {
758      return `${out}${StringPrototypeSlice(toOrig, toStart, toEnd)}`;
759    }
760
761    if (StringPrototypeCharCodeAt(toOrig, toStart) === CHAR_BACKWARD_SLASH) {
762      ++toStart;
763    }
764    return StringPrototypeSlice(toOrig, toStart, toEnd);
765  },
766
767  toNamespacedPath(path) {
768    // Note: this will *probably* throw somewhere.
769    if (typeof path !== "string" || path.length === 0) {
770      return path;
771    }
772
773    const resolvedPath = win32.resolve(path);
774
775    if (resolvedPath.length <= 2) {
776      return path;
777    }
778
779    if (StringPrototypeCharCodeAt(resolvedPath, 0) === CHAR_BACKWARD_SLASH) {
780      // Possible UNC root
781      if (StringPrototypeCharCodeAt(resolvedPath, 1) === CHAR_BACKWARD_SLASH) {
782        const code = StringPrototypeCharCodeAt(resolvedPath, 2);
783        if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
784          // Matched non-long UNC root, convert the path to a long UNC path
785          return `\\\\?\\UNC\\${StringPrototypeSlice(resolvedPath, 2)}`;
786        }
787      }
788    } else if (
789      isWindowsDeviceRoot(StringPrototypeCharCodeAt(resolvedPath, 0)) &&
790      StringPrototypeCharCodeAt(resolvedPath, 1) === CHAR_COLON &&
791      StringPrototypeCharCodeAt(resolvedPath, 2) === CHAR_BACKWARD_SLASH
792    ) {
793      // Matched device root, convert the path to a long UNC path
794      return `\\\\?\\${resolvedPath}`;
795    }
796
797    return resolvedPath;
798  },
799
800  dirname(path) {
801    validateString(path, "path");
802    const len = path.length;
803    if (len === 0) {
804      return ".";
805    }
806    let rootEnd = -1;
807    let offset = 0;
808    const code = StringPrototypeCharCodeAt(path, 0);
809
810    if (len === 1) {
811      // `path` contains just a path separator, exit early to avoid
812      // unnecessary work or a dot.
813      return isPathSeparator(code) ? path : ".";
814    }
815
816    // Try to match a root
817    if (isPathSeparator(code)) {
818      // Possible UNC root
819
820      rootEnd = offset = 1;
821
822      if (isPathSeparator(StringPrototypeCharCodeAt(path, 1))) {
823        // Matched double path separator at beginning
824        let j = 2;
825        let last = j;
826        // Match 1 or more non-path separators
827        while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
828          j++;
829        }
830        if (j < len && j !== last) {
831          // Matched!
832          last = j;
833          // Match 1 or more path separators
834          while (j < len && isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
835            j++;
836          }
837          if (j < len && j !== last) {
838            // Matched!
839            last = j;
840            // Match 1 or more non-path separators
841            while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
842              j++;
843            }
844            if (j === len) {
845              // We matched a UNC root only
846              return path;
847            }
848            if (j !== last) {
849              // We matched a UNC root with leftovers
850
851              // Offset by 1 to include the separator after the UNC root to
852              // treat it as a "normal root" on top of a (UNC) root
853              rootEnd = offset = j + 1;
854            }
855          }
856        }
857      }
858      // Possible device root
859    } else if (
860      isWindowsDeviceRoot(code) &&
861      StringPrototypeCharCodeAt(path, 1) === CHAR_COLON
862    ) {
863      rootEnd = len > 2 && isPathSeparator(StringPrototypeCharCodeAt(path, 2)) ? 3 : 2;
864      offset = rootEnd;
865    }
866
867    let end = -1;
868    let matchedSlash = true;
869    for (let i = len - 1; i >= offset; --i) {
870      if (isPathSeparator(StringPrototypeCharCodeAt(path, i))) {
871        if (!matchedSlash) {
872          end = i;
873          break;
874        }
875      } else {
876        // We saw the first non-path separator
877        matchedSlash = false;
878      }
879    }
880
881    if (end === -1) {
882      if (rootEnd === -1) {
883        return ".";
884      }
885
886      end = rootEnd;
887    }
888    return StringPrototypeSlice(path, 0, end);
889  },
890
891  basename(path, suffix) {
892    if (suffix !== undefined) {
893      validateString(suffix, "suffix");
894    }
895    validateString(path, "path");
896    let start = 0;
897    let end = -1;
898    let matchedSlash = true;
899
900    // Check for a drive letter prefix so as not to mistake the following
901    // path separator as an extra separator at the end of the path that can be
902    // disregarded
903    if (
904      path.length >= 2 &&
905      isWindowsDeviceRoot(StringPrototypeCharCodeAt(path, 0)) &&
906      StringPrototypeCharCodeAt(path, 1) === CHAR_COLON
907    ) {
908      start = 2;
909    }
910
911    if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
912      if (suffix === path) {
913        return "";
914      }
915      let extIdx = suffix.length - 1;
916      let firstNonSlashEnd = -1;
917      for (let i = path.length - 1; i >= start; --i) {
918        const code = StringPrototypeCharCodeAt(path, i);
919        if (isPathSeparator(code)) {
920          // If we reached a path separator that was not part of a set of path
921          // separators at the end of the string, stop now
922          if (!matchedSlash) {
923            start = i + 1;
924            break;
925          }
926        } else {
927          if (firstNonSlashEnd === -1) {
928            // We saw the first non-path separator, remember this index in case
929            // we need it if the extension ends up not matching
930            matchedSlash = false;
931            firstNonSlashEnd = i + 1;
932          }
933          if (extIdx >= 0) {
934            // Try to match the explicit extension
935            if (code === StringPrototypeCharCodeAt(suffix, extIdx)) {
936              if (--extIdx === -1) {
937                // We matched the extension, so mark this as the end of our path
938                // component
939                end = i;
940              }
941            } else {
942              // Extension does not match, so our result is the entire path
943              // component
944              extIdx = -1;
945              end = firstNonSlashEnd;
946            }
947          }
948        }
949      }
950
951      if (start === end) {
952        end = firstNonSlashEnd;
953      } else if (end === -1) {
954        end = path.length;
955      }
956      return StringPrototypeSlice(path, start, end);
957    }
958    for (let i = path.length - 1; i >= start; --i) {
959      if (isPathSeparator(StringPrototypeCharCodeAt(path, i))) {
960        // If we reached a path separator that was not part of a set of path
961        // separators at the end of the string, stop now
962        if (!matchedSlash) {
963          start = i + 1;
964          break;
965        }
966      } else if (end === -1) {
967        // We saw the first non-path separator, mark this as the end of our
968        // path component
969        matchedSlash = false;
970        end = i + 1;
971      }
972    }
973
974    if (end === -1) {
975      return "";
976    }
977    return StringPrototypeSlice(path, start, end);
978  },
979
980  extname(path) {
981    validateString(path, "path");
982    let start = 0;
983    let startDot = -1;
984    let startPart = 0;
985    let end = -1;
986    let matchedSlash = true;
987    // Track the state of characters (if any) we see before our first dot and
988    // after any path separator we find
989    let preDotState = 0;
990
991    // Check for a drive letter prefix so as not to mistake the following
992    // path separator as an extra separator at the end of the path that can be
993    // disregarded
994
995    if (
996      path.length >= 2 &&
997      StringPrototypeCharCodeAt(path, 1) === CHAR_COLON &&
998      isWindowsDeviceRoot(StringPrototypeCharCodeAt(path, 0))
999    ) {
1000      start = startPart = 2;
1001    }
1002
1003    for (let i = path.length - 1; i >= start; --i) {
1004      const code = StringPrototypeCharCodeAt(path, i);
1005      if (isPathSeparator(code)) {
1006        // If we reached a path separator that was not part of a set of path
1007        // separators at the end of the string, stop now
1008        if (!matchedSlash) {
1009          startPart = i + 1;
1010          break;
1011        }
1012        continue;
1013      }
1014      if (end === -1) {
1015        // We saw the first non-path separator, mark this as the end of our
1016        // extension
1017        matchedSlash = false;
1018        end = i + 1;
1019      }
1020      if (code === CHAR_DOT) {
1021        // If this is our first dot, mark it as the start of our extension
1022        if (startDot === -1) {
1023          startDot = i;
1024        } else if (preDotState !== 1) {
1025          preDotState = 1;
1026        }
1027      } else if (startDot !== -1) {
1028        // We saw a non-dot and non-path separator before our dot, so we should
1029        // have a good chance at having a non-empty extension
1030        preDotState = -1;
1031      }
1032    }
1033
1034    if (
1035      startDot === -1 || end === -1 ||
1036      // We saw a non-dot character immediately before the dot
1037      preDotState === 0 ||
1038      // The (right-most) trimmed path component is exactly '..'
1039      (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
1040    ) {
1041      return "";
1042    }
1043    return StringPrototypeSlice(path, startDot, end);
1044  },
1045
1046  format(pathObject) {
1047    return _format("\\", pathObject);
1048  },
1049
1050  parse(path) {
1051    validateString(path, "path");
1052
1053    const ret = { root: "", dir: "", base: "", ext: "", name: "" };
1054    if (path.length === 0) {
1055      return ret;
1056    }
1057
1058    const len = path.length;
1059    let rootEnd = 0;
1060    let code = StringPrototypeCharCodeAt(path, 0);
1061
1062    if (len === 1) {
1063      if (isPathSeparator(code)) {
1064        // `path` contains just a path separator, exit early to avoid
1065        // unnecessary work
1066        ret.root = ret.dir = path;
1067        return ret;
1068      }
1069      ret.base = ret.name = path;
1070      return ret;
1071    }
1072    // Try to match a root
1073    if (isPathSeparator(code)) {
1074      // Possible UNC root
1075
1076      rootEnd = 1;
1077      if (isPathSeparator(StringPrototypeCharCodeAt(path, 1))) {
1078        // Matched double path separator at beginning
1079        let j = 2;
1080        let last = j;
1081        // Match 1 or more non-path separators
1082        while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
1083          j++;
1084        }
1085        if (j < len && j !== last) {
1086          // Matched!
1087          last = j;
1088          // Match 1 or more path separators
1089          while (j < len && isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
1090            j++;
1091          }
1092          if (j < len && j !== last) {
1093            // Matched!
1094            last = j;
1095            // Match 1 or more non-path separators
1096            while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
1097              j++;
1098            }
1099            if (j === len) {
1100              // We matched a UNC root only
1101              rootEnd = j;
1102            } else if (j !== last) {
1103              // We matched a UNC root with leftovers
1104              rootEnd = j + 1;
1105            }
1106          }
1107        }
1108      }
1109    } else if (
1110      isWindowsDeviceRoot(code) &&
1111      StringPrototypeCharCodeAt(path, 1) === CHAR_COLON
1112    ) {
1113      // Possible device root
1114      if (len <= 2) {
1115        // `path` contains just a drive root, exit early to avoid
1116        // unnecessary work
1117        ret.root = ret.dir = path;
1118        return ret;
1119      }
1120      rootEnd = 2;
1121      if (isPathSeparator(StringPrototypeCharCodeAt(path, 2))) {
1122        if (len === 3) {
1123          // `path` contains just a drive root, exit early to avoid
1124          // unnecessary work
1125          ret.root = ret.dir = path;
1126          return ret;
1127        }
1128        rootEnd = 3;
1129      }
1130    }
1131    if (rootEnd > 0) {
1132      ret.root = StringPrototypeSlice(path, 0, rootEnd);
1133    }
1134
1135    let startDot = -1;
1136    let startPart = rootEnd;
1137    let end = -1;
1138    let matchedSlash = true;
1139    let i = path.length - 1;
1140
1141    // Track the state of characters (if any) we see before our first dot and
1142    // after any path separator we find
1143    let preDotState = 0;
1144
1145    // Get non-dir info
1146    for (; i >= rootEnd; --i) {
1147      code = StringPrototypeCharCodeAt(path, i);
1148      if (isPathSeparator(code)) {
1149        // If we reached a path separator that was not part of a set of path
1150        // separators at the end of the string, stop now
1151        if (!matchedSlash) {
1152          startPart = i + 1;
1153          break;
1154        }
1155        continue;
1156      }
1157      if (end === -1) {
1158        // We saw the first non-path separator, mark this as the end of our
1159        // extension
1160        matchedSlash = false;
1161        end = i + 1;
1162      }
1163      if (code === CHAR_DOT) {
1164        // If this is our first dot, mark it as the start of our extension
1165        if (startDot === -1) {
1166          startDot = i;
1167        } else if (preDotState !== 1) {
1168          preDotState = 1;
1169        }
1170      } else if (startDot !== -1) {
1171        // We saw a non-dot and non-path separator before our dot, so we should
1172        // have a good chance at having a non-empty extension
1173        preDotState = -1;
1174      }
1175    }
1176
1177    if (end !== -1) {
1178      if (
1179        startDot === -1 ||
1180        // We saw a non-dot character immediately before the dot
1181        preDotState === 0 ||
1182        // The (right-most) trimmed path component is exactly '..'
1183        (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
1184      ) {
1185        ret.base = ret.name = StringPrototypeSlice(path, startPart, end);
1186      } else {
1187        ret.name = StringPrototypeSlice(path, startPart, startDot);
1188        ret.base = StringPrototypeSlice(path, startPart, end);
1189        ret.ext = StringPrototypeSlice(path, startDot, end);
1190      }
1191    }
1192
1193    // If the directory is the root, use the entire root as the `dir` including
1194    // the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the
1195    // trailing slash (`C:\abc\def` -> `C:\abc`).
1196    if (startPart > 0 && startPart !== rootEnd) {
1197      ret.dir = StringPrototypeSlice(path, 0, startPart - 1);
1198    } else {
1199      ret.dir = ret.root;
1200    }
1201
1202    return ret;
1203  },
1204
1205  sep: "\\",
1206  delimiter: ";",
1207};
1208
1209// Node views a Windows working directory through POSIX eyes by converting
1210// separators and truncating any drive indicator.
1211function posixCwd() {
1212  if (!hostIsWindows) {
1213    return nativeCwd();
1214  }
1215  const cwd = nativeCwd();
1216  const forward = ArrayPrototypeJoin(splitOnChar(cwd, CHAR_BACKWARD_SLASH), "/");
1217  return StringPrototypeSlice(forward, StringPrototypeIndexOf(forward, "/"));
1218}
1219
1220const posix: PathApi = {
1221  resolve(...args) {
1222    let resolvedPath = "";
1223    let resolvedAbsolute = false;
1224
1225    for (let i = args.length - 1; i >= 0 && !resolvedAbsolute; i--) {
1226      const path = args[i]!;
1227      validateString(path, `paths[${i}]`);
1228
1229      // Skip empty entries
1230      if (path.length === 0) {
1231        continue;
1232      }
1233
1234      resolvedPath = `${path}/${resolvedPath}`;
1235      resolvedAbsolute =
1236        StringPrototypeCharCodeAt(path, 0) === CHAR_FORWARD_SLASH;
1237    }
1238
1239    if (!resolvedAbsolute) {
1240      const cwd = posixCwd();
1241      resolvedPath = `${cwd}/${resolvedPath}`;
1242      resolvedAbsolute =
1243        StringPrototypeCharCodeAt(cwd, 0) === CHAR_FORWARD_SLASH;
1244    }
1245
1246    // At this point the path should be resolved to a full absolute path, but
1247    // handle relative paths to be safe (might happen when the working
1248    // directory lookup fails, which surfaces as PermissionDenied instead).
1249
1250    // Normalize the path
1251    resolvedPath = normalizeString(
1252      resolvedPath,
1253      !resolvedAbsolute,
1254      "/",
1255      isPosixPathSeparator,
1256    );
1257
1258    if (resolvedAbsolute) {
1259      return `/${resolvedPath}`;
1260    }
1261    return resolvedPath.length > 0 ? resolvedPath : ".";
1262  },
1263
1264  normalize(path) {
1265    validateString(path, "path");
1266
1267    if (path.length === 0) {
1268      return ".";
1269    }
1270
1271    const isAbsolute =
1272      StringPrototypeCharCodeAt(path, 0) === CHAR_FORWARD_SLASH;
1273    const trailingSeparator =
1274      StringPrototypeCharCodeAt(path, path.length - 1) === CHAR_FORWARD_SLASH;
1275
1276    // Normalize the path
1277    path = normalizeString(path, !isAbsolute, "/", isPosixPathSeparator);
1278
1279    if (path.length === 0) {
1280      if (isAbsolute) {
1281        return "/";
1282      }
1283      return trailingSeparator ? "./" : ".";
1284    }
1285    if (trailingSeparator) {
1286      path += "/";
1287    }
1288
1289    return isAbsolute ? `/${path}` : path;
1290  },
1291
1292  isAbsolute(path) {
1293    validateString(path, "path");
1294    return path.length > 0 &&
1295      StringPrototypeCharCodeAt(path, 0) === CHAR_FORWARD_SLASH;
1296  },
1297
1298  join(...args) {
1299    if (args.length === 0) {
1300      return ".";
1301    }
1302
1303    const path: string[] = [];
1304    for (let i = 0; i < args.length; ++i) {
1305      const arg = args[i];
1306      validateString(arg, "path");
1307      if (arg.length > 0) {
1308        ArrayPrototypePush(path, arg);
1309      }
1310    }
1311
1312    if (path.length === 0) {
1313      return ".";
1314    }
1315
1316    return posix.normalize(ArrayPrototypeJoin(path, "/"));
1317  },
1318
1319  relative(from, to) {
1320    validateString(from, "from");
1321    validateString(to, "to");
1322
1323    if (from === to) {
1324      return "";
1325    }
1326
1327    // Trim leading forward slashes.
1328    from = posix.resolve(from);
1329    to = posix.resolve(to);
1330
1331    if (from === to) {
1332      return "";
1333    }
1334
1335    const fromStart = 1;
1336    const fromEnd = from.length;
1337    const fromLen = fromEnd - fromStart;
1338    const toStart = 1;
1339    const toLen = to.length - toStart;
1340
1341    // Compare paths to find the longest common path from root
1342    const length = (fromLen < toLen ? fromLen : toLen);
1343    let lastCommonSep = -1;
1344    let i = 0;
1345    for (; i < length; i++) {
1346      const fromCode = StringPrototypeCharCodeAt(from, fromStart + i);
1347      if (fromCode !== StringPrototypeCharCodeAt(to, toStart + i)) {
1348        break;
1349      } else if (fromCode === CHAR_FORWARD_SLASH) {
1350        lastCommonSep = i;
1351      }
1352    }
1353    if (i === length) {
1354      if (toLen > length) {
1355        if (StringPrototypeCharCodeAt(to, toStart + i) === CHAR_FORWARD_SLASH) {
1356          // We get here if `from` is the exact base path for `to`.
1357          // For example: from='/foo/bar'; to='/foo/bar/baz'
1358          return StringPrototypeSlice(to, toStart + i + 1);
1359        }
1360        if (i === 0) {
1361          // We get here if `from` is the root
1362          // For example: from='/'; to='/foo'
1363          return StringPrototypeSlice(to, toStart + i);
1364        }
1365      } else if (fromLen > length) {
1366        if (
1367          StringPrototypeCharCodeAt(from, fromStart + i) ===
1368          CHAR_FORWARD_SLASH
1369        ) {
1370          // We get here if `to` is the exact base path for `from`.
1371          // For example: from='/foo/bar/baz'; to='/foo/bar'
1372          lastCommonSep = i;
1373        } else if (i === 0) {
1374          // We get here if `to` is the root.
1375          // For example: from='/foo/bar'; to='/'
1376          lastCommonSep = 0;
1377        }
1378      }
1379    }
1380
1381    let out = "";
1382    // Generate the relative path based on the path difference between `to`
1383    // and `from`.
1384    for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
1385      if (
1386        i === fromEnd ||
1387        StringPrototypeCharCodeAt(from, i) === CHAR_FORWARD_SLASH
1388      ) {
1389        out += out.length === 0 ? ".." : "/..";
1390      }
1391    }
1392
1393    // Lastly, append the rest of the destination (`to`) path that comes after
1394    // the common path parts.
1395    return `${out}${StringPrototypeSlice(to, toStart + lastCommonSep)}`;
1396  },
1397
1398  toNamespacedPath(path) {
1399    // Non-op on posix systems
1400    return path;
1401  },
1402
1403  dirname(path) {
1404    validateString(path, "path");
1405    if (path.length === 0) {
1406      return ".";
1407    }
1408    const hasRoot = StringPrototypeCharCodeAt(path, 0) === CHAR_FORWARD_SLASH;
1409    let end = -1;
1410    let matchedSlash = true;
1411    for (let i = path.length - 1; i >= 1; --i) {
1412      if (StringPrototypeCharCodeAt(path, i) === CHAR_FORWARD_SLASH) {
1413        if (!matchedSlash) {
1414          end = i;
1415          break;
1416        }
1417      } else {
1418        // We saw the first non-path separator
1419        matchedSlash = false;
1420      }
1421    }
1422
1423    if (end === -1) {
1424      return hasRoot ? "/" : ".";
1425    }
1426    if (hasRoot && end === 1) {
1427      return "//";
1428    }
1429    return StringPrototypeSlice(path, 0, end);
1430  },
1431
1432  basename(path, suffix) {
1433    if (suffix !== undefined) {
1434      validateString(suffix, "suffix");
1435    }
1436    validateString(path, "path");
1437
1438    let start = 0;
1439    let end = -1;
1440    let matchedSlash = true;
1441
1442    if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
1443      if (suffix === path) {
1444        return "";
1445      }
1446      let extIdx = suffix.length - 1;
1447      let firstNonSlashEnd = -1;
1448      for (let i = path.length - 1; i >= 0; --i) {
1449        const code = StringPrototypeCharCodeAt(path, i);
1450        if (code === CHAR_FORWARD_SLASH) {
1451          // If we reached a path separator that was not part of a set of path
1452          // separators at the end of the string, stop now
1453          if (!matchedSlash) {
1454            start = i + 1;
1455            break;
1456          }
1457        } else {
1458          if (firstNonSlashEnd === -1) {
1459            // We saw the first non-path separator, remember this index in case
1460            // we need it if the extension ends up not matching
1461            matchedSlash = false;
1462            firstNonSlashEnd = i + 1;
1463          }
1464          if (extIdx >= 0) {
1465            // Try to match the explicit extension
1466            if (code === StringPrototypeCharCodeAt(suffix, extIdx)) {
1467              if (--extIdx === -1) {
1468                // We matched the extension, so mark this as the end of our path
1469                // component
1470                end = i;
1471              }
1472            } else {
1473              // Extension does not match, so our result is the entire path
1474              // component
1475              extIdx = -1;
1476              end = firstNonSlashEnd;
1477            }
1478          }
1479        }
1480      }
1481
1482      if (start === end) {
1483        end = firstNonSlashEnd;
1484      } else if (end === -1) {
1485        end = path.length;
1486      }
1487      return StringPrototypeSlice(path, start, end);
1488    }
1489    for (let i = path.length - 1; i >= 0; --i) {
1490      if (StringPrototypeCharCodeAt(path, i) === CHAR_FORWARD_SLASH) {
1491        // If we reached a path separator that was not part of a set of path
1492        // separators at the end of the string, stop now
1493        if (!matchedSlash) {
1494          start = i + 1;
1495          break;
1496        }
1497      } else if (end === -1) {
1498        // We saw the first non-path separator, mark this as the end of our
1499        // path component
1500        matchedSlash = false;
1501        end = i + 1;
1502      }
1503    }
1504
1505    if (end === -1) {
1506      return "";
1507    }
1508    return StringPrototypeSlice(path, start, end);
1509  },
1510
1511  extname(path) {
1512    validateString(path, "path");
1513    let startDot = -1;
1514    let startPart = 0;
1515    let end = -1;
1516    let matchedSlash = true;
1517    // Track the state of characters (if any) we see before our first dot and
1518    // after any path separator we find
1519    let preDotState = 0;
1520    for (let i = path.length - 1; i >= 0; --i) {
1521      const code = StringPrototypeCharCodeAt(path, i);
1522      if (code === CHAR_FORWARD_SLASH) {
1523        // If we reached a path separator that was not part of a set of path
1524        // separators at the end of the string, stop now
1525        if (!matchedSlash) {
1526          startPart = i + 1;
1527          break;
1528        }
1529        continue;
1530      }
1531      if (end === -1) {
1532        // We saw the first non-path separator, mark this as the end of our
1533        // extension
1534        matchedSlash = false;
1535        end = i + 1;
1536      }
1537      if (code === CHAR_DOT) {
1538        // If this is our first dot, mark it as the start of our extension
1539        if (startDot === -1) {
1540          startDot = i;
1541        } else if (preDotState !== 1) {
1542          preDotState = 1;
1543        }
1544      } else if (startDot !== -1) {
1545        // We saw a non-dot and non-path separator before our dot, so we should
1546        // have a good chance at having a non-empty extension
1547        preDotState = -1;
1548      }
1549    }
1550
1551    if (
1552      startDot === -1 || end === -1 ||
1553      // We saw a non-dot character immediately before the dot
1554      preDotState === 0 ||
1555      // The (right-most) trimmed path component is exactly '..'
1556      (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
1557    ) {
1558      return "";
1559    }
1560    return StringPrototypeSlice(path, startDot, end);
1561  },
1562
1563  format(pathObject) {
1564    return _format("/", pathObject);
1565  },
1566
1567  parse(path) {
1568    validateString(path, "path");
1569
1570    const ret = { root: "", dir: "", base: "", ext: "", name: "" };
1571    if (path.length === 0) {
1572      return ret;
1573    }
1574    const isAbsolute =
1575      StringPrototypeCharCodeAt(path, 0) === CHAR_FORWARD_SLASH;
1576    let start;
1577    if (isAbsolute) {
1578      ret.root = "/";
1579      start = 1;
1580    } else {
1581      start = 0;
1582    }
1583    let startDot = -1;
1584    let startPart = 0;
1585    let end = -1;
1586    let matchedSlash = true;
1587    let i = path.length - 1;
1588
1589    // Track the state of characters (if any) we see before our first dot and
1590    // after any path separator we find
1591    let preDotState = 0;
1592
1593    // Get non-dir info
1594    for (; i >= start; --i) {
1595      const code = StringPrototypeCharCodeAt(path, i);
1596      if (code === CHAR_FORWARD_SLASH) {
1597        // If we reached a path separator that was not part of a set of path
1598        // separators at the end of the string, stop now
1599        if (!matchedSlash) {
1600          startPart = i + 1;
1601          break;
1602        }
1603        continue;
1604      }
1605      if (end === -1) {
1606        // We saw the first non-path separator, mark this as the end of our
1607        // extension
1608        matchedSlash = false;
1609        end = i + 1;
1610      }
1611      if (code === CHAR_DOT) {
1612        // If this is our first dot, mark it as the start of our extension
1613        if (startDot === -1) {
1614          startDot = i;
1615        } else if (preDotState !== 1) {
1616          preDotState = 1;
1617        }
1618      } else if (startDot !== -1) {
1619        // We saw a non-dot and non-path separator before our dot, so we should
1620        // have a good chance at having a non-empty extension
1621        preDotState = -1;
1622      }
1623    }
1624
1625    if (end !== -1) {
1626      const baseStart = startPart === 0 && isAbsolute ? 1 : startPart;
1627      if (
1628        startDot === -1 ||
1629        // We saw a non-dot character immediately before the dot
1630        preDotState === 0 ||
1631        // The (right-most) trimmed path component is exactly '..'
1632        (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
1633      ) {
1634        ret.base = ret.name = StringPrototypeSlice(path, baseStart, end);
1635      } else {
1636        ret.name = StringPrototypeSlice(path, baseStart, startDot);
1637        ret.base = StringPrototypeSlice(path, baseStart, end);
1638        ret.ext = StringPrototypeSlice(path, startDot, end);
1639      }
1640    }
1641
1642    if (startPart > 0) {
1643      ret.dir = StringPrototypeSlice(path, 0, startPart - 1);
1644    } else if (isAbsolute) {
1645      ret.dir = "/";
1646    }
1647
1648    return ret;
1649  },
1650
1651  sep: "/",
1652  delimiter: ":",
1653};
1654
1655const posixBase = ObjectFreeze({
1656  join: posix.join,
1657  resolve: posix.resolve,
1658  normalize: posix.normalize,
1659  relative: posix.relative,
1660  dirname: posix.dirname,
1661  basename: posix.basename,
1662  extname: posix.extname,
1663  isAbsolute: posix.isAbsolute,
1664  parse: posix.parse,
1665  format: posix.format,
1666  toNamespacedPath: posix.toNamespacedPath,
1667  sep: posix.sep,
1668  delimiter: posix.delimiter,
1669});
1670
1671const win32Base = ObjectFreeze({
1672  join: win32.join,
1673  resolve: win32.resolve,
1674  normalize: win32.normalize,
1675  relative: win32.relative,
1676  dirname: win32.dirname,
1677  basename: win32.basename,
1678  extname: win32.extname,
1679  isAbsolute: win32.isAbsolute,
1680  parse: win32.parse,
1681  format: win32.format,
1682  toNamespacedPath: win32.toNamespacedPath,
1683  sep: win32.sep,
1684  delimiter: win32.delimiter,
1685});
1686
1687export { posixBase, win32Base };
1688export { splitOnChar, validateString };