Path: blob/main/extensions/copilot/src/util/vs/base/common/path.ts
13405 views
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'12/*---------------------------------------------------------------------------------------------3* Copyright (c) Microsoft Corporation. All rights reserved.4* Licensed under the MIT License. See License.txt in the project root for license information.5*--------------------------------------------------------------------------------------------*/67// NOTE: VSCode's copy of nodejs path library to be usable in common (non-node) namespace8// Copied from: https://github.com/nodejs/node/commits/v22.15.0/lib/path.js9// Excluding: the change that adds primordials10// (https://github.com/nodejs/node/commit/187a862d221dec42fa9a5c4214e7034d9092792f and others)11// Excluding: the change that adds glob matching12// (https://github.com/nodejs/node/commit/57b8b8e18e5e2007114c63b71bf0baedc01936a6)1314/**15* Copyright Joyent, Inc. and other Node contributors.16*17* Permission is hereby granted, free of charge, to any person obtaining a18* copy of this software and associated documentation files (the19* "Software"), to deal in the Software without restriction, including20* without limitation the rights to use, copy, modify, merge, publish,21* distribute, sublicense, and/or sell copies of the Software, and to permit22* persons to whom the Software is furnished to do so, subject to the23* following conditions:24*25* The above copyright notice and this permission notice shall be included26* in all copies or substantial portions of the Software.27*28* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS29* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF30* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN31* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,32* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR33* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE34* USE OR OTHER DEALINGS IN THE SOFTWARE.35*/3637import * as process from './process';3839const CHAR_UPPERCASE_A = 65;/* A */40const CHAR_LOWERCASE_A = 97; /* a */41const CHAR_UPPERCASE_Z = 90; /* Z */42const CHAR_LOWERCASE_Z = 122; /* z */43const CHAR_DOT = 46; /* . */44const CHAR_FORWARD_SLASH = 47; /* / */45const CHAR_BACKWARD_SLASH = 92; /* \ */46const CHAR_COLON = 58; /* : */47const CHAR_QUESTION_MARK = 63; /* ? */4849class ErrorInvalidArgType extends Error {50code: 'ERR_INVALID_ARG_TYPE';51constructor(name: string, expected: string, actual: unknown) {52// determiner: 'must be' or 'must not be'53let determiner;54if (typeof expected === 'string' && expected.indexOf('not ') === 0) {55determiner = 'must not be';56expected = expected.replace(/^not /, '');57} else {58determiner = 'must be';59}6061const type = name.indexOf('.') !== -1 ? 'property' : 'argument';62let msg = `The "${name}" ${type} ${determiner} of type ${expected}`;6364msg += `. Received type ${typeof actual}`;65super(msg);6667this.code = 'ERR_INVALID_ARG_TYPE';68}69}7071function validateObject(pathObject: object, name: string) {72if (pathObject === null || typeof pathObject !== 'object') {73throw new ErrorInvalidArgType(name, 'Object', pathObject);74}75}7677function validateString(value: string, name: string) {78if (typeof value !== 'string') {79throw new ErrorInvalidArgType(name, 'string', value);80}81}8283const platformIsWin32 = (process.platform === 'win32');8485function isPathSeparator(code: number | undefined) {86return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;87}8889function isPosixPathSeparator(code: number | undefined) {90return code === CHAR_FORWARD_SLASH;91}9293function isWindowsDeviceRoot(code: number) {94return (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||95(code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z);96}9798// Resolves . and .. elements in a path with directory names99function normalizeString(path: string, allowAboveRoot: boolean, separator: string, isPathSeparator: (code?: number) => boolean) {100let res = '';101let lastSegmentLength = 0;102let lastSlash = -1;103let dots = 0;104let code = 0;105for (let i = 0; i <= path.length; ++i) {106if (i < path.length) {107code = path.charCodeAt(i);108}109else if (isPathSeparator(code)) {110break;111}112else {113code = CHAR_FORWARD_SLASH;114}115116if (isPathSeparator(code)) {117if (lastSlash === i - 1 || dots === 1) {118// NOOP119} else if (dots === 2) {120if (res.length < 2 || lastSegmentLength !== 2 ||121res.charCodeAt(res.length - 1) !== CHAR_DOT ||122res.charCodeAt(res.length - 2) !== CHAR_DOT) {123if (res.length > 2) {124const lastSlashIndex = res.lastIndexOf(separator);125if (lastSlashIndex === -1) {126res = '';127lastSegmentLength = 0;128} else {129res = res.slice(0, lastSlashIndex);130lastSegmentLength = res.length - 1 - res.lastIndexOf(separator);131}132lastSlash = i;133dots = 0;134continue;135} else if (res.length !== 0) {136res = '';137lastSegmentLength = 0;138lastSlash = i;139dots = 0;140continue;141}142}143if (allowAboveRoot) {144res += res.length > 0 ? `${separator}..` : '..';145lastSegmentLength = 2;146}147} else {148if (res.length > 0) {149res += `${separator}${path.slice(lastSlash + 1, i)}`;150}151else {152res = path.slice(lastSlash + 1, i);153}154lastSegmentLength = i - lastSlash - 1;155}156lastSlash = i;157dots = 0;158} else if (code === CHAR_DOT && dots !== -1) {159++dots;160} else {161dots = -1;162}163}164return res;165}166167function formatExt(ext: string): string {168return ext ? `${ext[0] === '.' ? '' : '.'}${ext}` : '';169}170171function _format(sep: string, pathObject: ParsedPath) {172validateObject(pathObject, 'pathObject');173const dir = pathObject.dir || pathObject.root;174const base = pathObject.base ||175`${pathObject.name || ''}${formatExt(pathObject.ext)}`;176if (!dir) {177return base;178}179return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`;180}181182export interface ParsedPath {183root: string;184dir: string;185base: string;186ext: string;187name: string;188}189190export interface IPath {191normalize(path: string): string;192isAbsolute(path: string): boolean;193join(...paths: string[]): string;194resolve(...pathSegments: string[]): string;195relative(from: string, to: string): string;196dirname(path: string): string;197basename(path: string, suffix?: string): string;198extname(path: string): string;199format(pathObject: ParsedPath): string;200parse(path: string): ParsedPath;201toNamespacedPath(path: string): string;202sep: '\\' | '/';203delimiter: string;204win32: IPath | null;205posix: IPath | null;206}207208export const win32: IPath = {209// path.resolve([from ...], to)210resolve(...pathSegments: string[]): string {211let resolvedDevice = '';212let resolvedTail = '';213let resolvedAbsolute = false;214215for (let i = pathSegments.length - 1; i >= -1; i--) {216let path;217if (i >= 0) {218path = pathSegments[i];219validateString(path, `paths[${i}]`);220221// Skip empty entries222if (path.length === 0) {223continue;224}225} else if (resolvedDevice.length === 0) {226path = process.cwd();227} else {228// Windows has the concept of drive-specific current working229// directories. If we've resolved a drive letter but not yet an230// absolute path, get cwd for that drive, or the process cwd if231// the drive cwd is not available. We're sure the device is not232// a UNC path at this points, because UNC paths are always absolute.233path = process.env[`=${resolvedDevice}`] || process.cwd();234235// Verify that a cwd was found and that it actually points236// to our drive. If not, default to the drive's root.237if (path === undefined ||238(path.slice(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&239path.charCodeAt(2) === CHAR_BACKWARD_SLASH)) {240path = `${resolvedDevice}\\`;241}242}243244const len = path.length;245let rootEnd = 0;246let device = '';247let isAbsolute = false;248const code = path.charCodeAt(0);249250// Try to match a root251if (len === 1) {252if (isPathSeparator(code)) {253// `path` contains just a path separator254rootEnd = 1;255isAbsolute = true;256}257} else if (isPathSeparator(code)) {258// Possible UNC root259260// If we started with a separator, we know we at least have an261// absolute path of some kind (UNC or otherwise)262isAbsolute = true;263264if (isPathSeparator(path.charCodeAt(1))) {265// Matched double path separator at beginning266let j = 2;267let last = j;268// Match 1 or more non-path separators269while (j < len && !isPathSeparator(path.charCodeAt(j))) {270j++;271}272if (j < len && j !== last) {273const firstPart = path.slice(last, j);274// Matched!275last = j;276// Match 1 or more path separators277while (j < len && isPathSeparator(path.charCodeAt(j))) {278j++;279}280if (j < len && j !== last) {281// Matched!282last = j;283// Match 1 or more non-path separators284while (j < len && !isPathSeparator(path.charCodeAt(j))) {285j++;286}287if (j === len || j !== last) {288// We matched a UNC root289device = `\\\\${firstPart}\\${path.slice(last, j)}`;290rootEnd = j;291}292}293}294} else {295rootEnd = 1;296}297} else if (isWindowsDeviceRoot(code) &&298path.charCodeAt(1) === CHAR_COLON) {299// Possible device root300device = path.slice(0, 2);301rootEnd = 2;302if (len > 2 && isPathSeparator(path.charCodeAt(2))) {303// Treat separator following drive name as an absolute path304// indicator305isAbsolute = true;306rootEnd = 3;307}308}309310if (device.length > 0) {311if (resolvedDevice.length > 0) {312if (device.toLowerCase() !== resolvedDevice.toLowerCase()) {313// This path points to another device so it is not applicable314continue;315}316} else {317resolvedDevice = device;318}319}320321if (resolvedAbsolute) {322if (resolvedDevice.length > 0) {323break;324}325} else {326resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`;327resolvedAbsolute = isAbsolute;328if (isAbsolute && resolvedDevice.length > 0) {329break;330}331}332}333334// At this point the path should be resolved to a full absolute path,335// but handle relative paths to be safe (might happen when process.cwd()336// fails)337338// Normalize the tail path339resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\',340isPathSeparator);341342return resolvedAbsolute ?343`${resolvedDevice}\\${resolvedTail}` :344`${resolvedDevice}${resolvedTail}` || '.';345},346347normalize(path: string): string {348validateString(path, 'path');349const len = path.length;350if (len === 0) {351return '.';352}353let rootEnd = 0;354let device;355let isAbsolute = false;356const code = path.charCodeAt(0);357358// Try to match a root359if (len === 1) {360// `path` contains just a single char, exit early to avoid361// unnecessary work362return isPosixPathSeparator(code) ? '\\' : path;363}364if (isPathSeparator(code)) {365// Possible UNC root366367// If we started with a separator, we know we at least have an absolute368// path of some kind (UNC or otherwise)369isAbsolute = true;370371if (isPathSeparator(path.charCodeAt(1))) {372// Matched double path separator at beginning373let j = 2;374let last = j;375// Match 1 or more non-path separators376while (j < len && !isPathSeparator(path.charCodeAt(j))) {377j++;378}379if (j < len && j !== last) {380const firstPart = path.slice(last, j);381// Matched!382last = j;383// Match 1 or more path separators384while (j < len && isPathSeparator(path.charCodeAt(j))) {385j++;386}387if (j < len && j !== last) {388// Matched!389last = j;390// Match 1 or more non-path separators391while (j < len && !isPathSeparator(path.charCodeAt(j))) {392j++;393}394if (j === len) {395// We matched a UNC root only396// Return the normalized version of the UNC root since there397// is nothing left to process398return `\\\\${firstPart}\\${path.slice(last)}\\`;399}400if (j !== last) {401// We matched a UNC root with leftovers402device = `\\\\${firstPart}\\${path.slice(last, j)}`;403rootEnd = j;404}405}406}407} else {408rootEnd = 1;409}410} else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {411// Possible device root412device = path.slice(0, 2);413rootEnd = 2;414if (len > 2 && isPathSeparator(path.charCodeAt(2))) {415// Treat separator following drive name as an absolute path416// indicator417isAbsolute = true;418rootEnd = 3;419}420}421422let tail = rootEnd < len ?423normalizeString(path.slice(rootEnd), !isAbsolute, '\\', isPathSeparator) :424'';425if (tail.length === 0 && !isAbsolute) {426tail = '.';427}428if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) {429tail += '\\';430}431if (!isAbsolute && device === undefined && path.includes(':')) {432// If the original path was not absolute and if we have not been able to433// resolve it relative to a particular device, we need to ensure that the434// `tail` has not become something that Windows might interpret as an435// absolute path. See CVE-2024-36139.436if (tail.length >= 2 &&437isWindowsDeviceRoot(tail.charCodeAt(0)) &&438tail.charCodeAt(1) === CHAR_COLON) {439return `.\\${tail}`;440}441let index = path.indexOf(':');442do {443if (index === len - 1 || isPathSeparator(path.charCodeAt(index + 1))) {444return `.\\${tail}`;445}446} while ((index = path.indexOf(':', index + 1)) !== -1);447}448if (device === undefined) {449return isAbsolute ? `\\${tail}` : tail;450}451return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`;452},453454isAbsolute(path: string): boolean {455validateString(path, 'path');456const len = path.length;457if (len === 0) {458return false;459}460461const code = path.charCodeAt(0);462return isPathSeparator(code) ||463// Possible device root464(len > 2 &&465isWindowsDeviceRoot(code) &&466path.charCodeAt(1) === CHAR_COLON &&467isPathSeparator(path.charCodeAt(2)));468},469470join(...paths: string[]): string {471if (paths.length === 0) {472return '.';473}474475let joined;476let firstPart: string | undefined;477for (let i = 0; i < paths.length; ++i) {478const arg = paths[i];479validateString(arg, 'path');480if (arg.length > 0) {481if (joined === undefined) {482joined = firstPart = arg;483}484else {485joined += `\\${arg}`;486}487}488}489490if (joined === undefined) {491return '.';492}493494// Make sure that the joined path doesn't start with two slashes, because495// normalize() will mistake it for a UNC path then.496//497// This step is skipped when it is very clear that the user actually498// intended to point at a UNC path. This is assumed when the first499// non-empty string arguments starts with exactly two slashes followed by500// at least one more non-slash character.501//502// Note that for normalize() to treat a path as a UNC path it needs to503// have at least 2 components, so we don't filter for that here.504// This means that the user can use join to construct UNC paths from505// a server name and a share name; for example:506// path.join('//server', 'share') -> '\\\\server\\share\\')507let needsReplace = true;508let slashCount = 0;509if (typeof firstPart === 'string' && isPathSeparator(firstPart.charCodeAt(0))) {510++slashCount;511const firstLen = firstPart.length;512if (firstLen > 1 && isPathSeparator(firstPart.charCodeAt(1))) {513++slashCount;514if (firstLen > 2) {515if (isPathSeparator(firstPart.charCodeAt(2))) {516++slashCount;517} else {518// We matched a UNC path in the first part519needsReplace = false;520}521}522}523}524if (needsReplace) {525// Find any more consecutive slashes we need to replace526while (slashCount < joined.length &&527isPathSeparator(joined.charCodeAt(slashCount))) {528slashCount++;529}530531// Replace the slashes if needed532if (slashCount >= 2) {533joined = `\\${joined.slice(slashCount)}`;534}535}536537return win32.normalize(joined);538},539540541// It will solve the relative path from `from` to `to`, for instance:542// from = 'C:\\orandea\\test\\aaa'543// to = 'C:\\orandea\\impl\\bbb'544// The output of the function should be: '..\\..\\impl\\bbb'545relative(from: string, to: string): string {546validateString(from, 'from');547validateString(to, 'to');548549if (from === to) {550return '';551}552553const fromOrig = win32.resolve(from);554const toOrig = win32.resolve(to);555556if (fromOrig === toOrig) {557return '';558}559560from = fromOrig.toLowerCase();561to = toOrig.toLowerCase();562563if (from === to) {564return '';565}566567if (fromOrig.length !== from.length || toOrig.length !== to.length) {568const fromSplit = fromOrig.split('\\');569const toSplit = toOrig.split('\\');570if (fromSplit[fromSplit.length - 1] === '') {571fromSplit.pop();572}573if (toSplit[toSplit.length - 1] === '') {574toSplit.pop();575}576577const fromLen = fromSplit.length;578const toLen = toSplit.length;579const length = fromLen < toLen ? fromLen : toLen;580581let i;582for (i = 0; i < length; i++) {583if (fromSplit[i].toLowerCase() !== toSplit[i].toLowerCase()) {584break;585}586}587588if (i === 0) {589return toOrig;590} else if (i === length) {591if (toLen > length) {592return toSplit.slice(i).join('\\');593}594if (fromLen > length) {595return '..\\'.repeat(fromLen - 1 - i) + '..';596}597return '';598}599600return '..\\'.repeat(fromLen - i) + toSplit.slice(i).join('\\');601}602603// Trim any leading backslashes604let fromStart = 0;605while (fromStart < from.length &&606from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {607fromStart++;608}609// Trim trailing backslashes (applicable to UNC paths only)610let fromEnd = from.length;611while (fromEnd - 1 > fromStart &&612from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {613fromEnd--;614}615const fromLen = fromEnd - fromStart;616617// Trim any leading backslashes618let toStart = 0;619while (toStart < to.length &&620to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {621toStart++;622}623// Trim trailing backslashes (applicable to UNC paths only)624let toEnd = to.length;625while (toEnd - 1 > toStart &&626to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {627toEnd--;628}629const toLen = toEnd - toStart;630631// Compare paths to find the longest common path from root632const length = fromLen < toLen ? fromLen : toLen;633let lastCommonSep = -1;634let i = 0;635for (; i < length; i++) {636const fromCode = from.charCodeAt(fromStart + i);637if (fromCode !== to.charCodeAt(toStart + i)) {638break;639} else if (fromCode === CHAR_BACKWARD_SLASH) {640lastCommonSep = i;641}642}643644// We found a mismatch before the first common path separator was seen, so645// return the original `to`.646if (i !== length) {647if (lastCommonSep === -1) {648return toOrig;649}650} else {651if (toLen > length) {652if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {653// We get here if `from` is the exact base path for `to`.654// For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz'655return toOrig.slice(toStart + i + 1);656}657if (i === 2) {658// We get here if `from` is the device root.659// For example: from='C:\\'; to='C:\\foo'660return toOrig.slice(toStart + i);661}662}663if (fromLen > length) {664if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {665// We get here if `to` is the exact base path for `from`.666// For example: from='C:\\foo\\bar'; to='C:\\foo'667lastCommonSep = i;668} else if (i === 2) {669// We get here if `to` is the device root.670// For example: from='C:\\foo\\bar'; to='C:\\'671lastCommonSep = 3;672}673}674if (lastCommonSep === -1) {675lastCommonSep = 0;676}677}678679let out = '';680// Generate the relative path based on the path difference between `to` and681// `from`682for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {683if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {684out += out.length === 0 ? '..' : '\\..';685}686}687688toStart += lastCommonSep;689690// Lastly, append the rest of the destination (`to`) path that comes after691// the common path parts692if (out.length > 0) {693return `${out}${toOrig.slice(toStart, toEnd)}`;694}695696if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {697++toStart;698}699700return toOrig.slice(toStart, toEnd);701},702703toNamespacedPath(path: string): string {704// Note: this will *probably* throw somewhere.705if (typeof path !== 'string' || path.length === 0) {706return path;707}708709const resolvedPath = win32.resolve(path);710711if (resolvedPath.length <= 2) {712return path;713}714715if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {716// Possible UNC root717if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {718const code = resolvedPath.charCodeAt(2);719if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {720// Matched non-long UNC root, convert the path to a long UNC path721return `\\\\?\\UNC\\${resolvedPath.slice(2)}`;722}723}724} else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&725resolvedPath.charCodeAt(1) === CHAR_COLON &&726resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH) {727// Matched device root, convert the path to a long UNC path728return `\\\\?\\${resolvedPath}`;729}730731return resolvedPath;732},733734dirname(path: string): string {735validateString(path, 'path');736const len = path.length;737if (len === 0) {738return '.';739}740let rootEnd = -1;741let offset = 0;742const code = path.charCodeAt(0);743744if (len === 1) {745// `path` contains just a path separator, exit early to avoid746// unnecessary work or a dot.747return isPathSeparator(code) ? path : '.';748}749750// Try to match a root751if (isPathSeparator(code)) {752// Possible UNC root753754rootEnd = offset = 1;755756if (isPathSeparator(path.charCodeAt(1))) {757// Matched double path separator at beginning758let j = 2;759let last = j;760// Match 1 or more non-path separators761while (j < len && !isPathSeparator(path.charCodeAt(j))) {762j++;763}764if (j < len && j !== last) {765// Matched!766last = j;767// Match 1 or more path separators768while (j < len && isPathSeparator(path.charCodeAt(j))) {769j++;770}771if (j < len && j !== last) {772// Matched!773last = j;774// Match 1 or more non-path separators775while (j < len && !isPathSeparator(path.charCodeAt(j))) {776j++;777}778if (j === len) {779// We matched a UNC root only780return path;781}782if (j !== last) {783// We matched a UNC root with leftovers784785// Offset by 1 to include the separator after the UNC root to786// treat it as a "normal root" on top of a (UNC) root787rootEnd = offset = j + 1;788}789}790}791}792// Possible device root793} else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {794rootEnd = len > 2 && isPathSeparator(path.charCodeAt(2)) ? 3 : 2;795offset = rootEnd;796}797798let end = -1;799let matchedSlash = true;800for (let i = len - 1; i >= offset; --i) {801if (isPathSeparator(path.charCodeAt(i))) {802if (!matchedSlash) {803end = i;804break;805}806} else {807// We saw the first non-path separator808matchedSlash = false;809}810}811812if (end === -1) {813if (rootEnd === -1) {814return '.';815}816817end = rootEnd;818}819return path.slice(0, end);820},821822basename(path: string, suffix?: string): string {823if (suffix !== undefined) {824validateString(suffix, 'suffix');825}826validateString(path, 'path');827let start = 0;828let end = -1;829let matchedSlash = true;830let i;831832// Check for a drive letter prefix so as not to mistake the following833// path separator as an extra separator at the end of the path that can be834// disregarded835if (path.length >= 2 &&836isWindowsDeviceRoot(path.charCodeAt(0)) &&837path.charCodeAt(1) === CHAR_COLON) {838start = 2;839}840841if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {842if (suffix === path) {843return '';844}845let extIdx = suffix.length - 1;846let firstNonSlashEnd = -1;847for (i = path.length - 1; i >= start; --i) {848const code = path.charCodeAt(i);849if (isPathSeparator(code)) {850// If we reached a path separator that was not part of a set of path851// separators at the end of the string, stop now852if (!matchedSlash) {853start = i + 1;854break;855}856} else {857if (firstNonSlashEnd === -1) {858// We saw the first non-path separator, remember this index in case859// we need it if the extension ends up not matching860matchedSlash = false;861firstNonSlashEnd = i + 1;862}863if (extIdx >= 0) {864// Try to match the explicit extension865if (code === suffix.charCodeAt(extIdx)) {866if (--extIdx === -1) {867// We matched the extension, so mark this as the end of our path868// component869end = i;870}871} else {872// Extension does not match, so our result is the entire path873// component874extIdx = -1;875end = firstNonSlashEnd;876}877}878}879}880881if (start === end) {882end = firstNonSlashEnd;883} else if (end === -1) {884end = path.length;885}886return path.slice(start, end);887}888for (i = path.length - 1; i >= start; --i) {889if (isPathSeparator(path.charCodeAt(i))) {890// If we reached a path separator that was not part of a set of path891// separators at the end of the string, stop now892if (!matchedSlash) {893start = i + 1;894break;895}896} else if (end === -1) {897// We saw the first non-path separator, mark this as the end of our898// path component899matchedSlash = false;900end = i + 1;901}902}903904if (end === -1) {905return '';906}907return path.slice(start, end);908},909910extname(path: string): string {911validateString(path, 'path');912let start = 0;913let startDot = -1;914let startPart = 0;915let end = -1;916let matchedSlash = true;917// Track the state of characters (if any) we see before our first dot and918// after any path separator we find919let preDotState = 0;920921// Check for a drive letter prefix so as not to mistake the following922// path separator as an extra separator at the end of the path that can be923// disregarded924925if (path.length >= 2 &&926path.charCodeAt(1) === CHAR_COLON &&927isWindowsDeviceRoot(path.charCodeAt(0))) {928start = startPart = 2;929}930931for (let i = path.length - 1; i >= start; --i) {932const code = path.charCodeAt(i);933if (isPathSeparator(code)) {934// If we reached a path separator that was not part of a set of path935// separators at the end of the string, stop now936if (!matchedSlash) {937startPart = i + 1;938break;939}940continue;941}942if (end === -1) {943// We saw the first non-path separator, mark this as the end of our944// extension945matchedSlash = false;946end = i + 1;947}948if (code === CHAR_DOT) {949// If this is our first dot, mark it as the start of our extension950if (startDot === -1) {951startDot = i;952}953else if (preDotState !== 1) {954preDotState = 1;955}956} else if (startDot !== -1) {957// We saw a non-dot and non-path separator before our dot, so we should958// have a good chance at having a non-empty extension959preDotState = -1;960}961}962963if (startDot === -1 ||964end === -1 ||965// We saw a non-dot character immediately before the dot966preDotState === 0 ||967// The (right-most) trimmed path component is exactly '..'968(preDotState === 1 &&969startDot === end - 1 &&970startDot === startPart + 1)) {971return '';972}973return path.slice(startDot, end);974},975976format: _format.bind(null, '\\'),977978parse(path) {979validateString(path, 'path');980981const ret = { root: '', dir: '', base: '', ext: '', name: '' };982if (path.length === 0) {983return ret;984}985986const len = path.length;987let rootEnd = 0;988let code = path.charCodeAt(0);989990if (len === 1) {991if (isPathSeparator(code)) {992// `path` contains just a path separator, exit early to avoid993// unnecessary work994ret.root = ret.dir = path;995return ret;996}997ret.base = ret.name = path;998return ret;999}1000// Try to match a root1001if (isPathSeparator(code)) {1002// Possible UNC root10031004rootEnd = 1;1005if (isPathSeparator(path.charCodeAt(1))) {1006// Matched double path separator at beginning1007let j = 2;1008let last = j;1009// Match 1 or more non-path separators1010while (j < len && !isPathSeparator(path.charCodeAt(j))) {1011j++;1012}1013if (j < len && j !== last) {1014// Matched!1015last = j;1016// Match 1 or more path separators1017while (j < len && isPathSeparator(path.charCodeAt(j))) {1018j++;1019}1020if (j < len && j !== last) {1021// Matched!1022last = j;1023// Match 1 or more non-path separators1024while (j < len && !isPathSeparator(path.charCodeAt(j))) {1025j++;1026}1027if (j === len) {1028// We matched a UNC root only1029rootEnd = j;1030} else if (j !== last) {1031// We matched a UNC root with leftovers1032rootEnd = j + 1;1033}1034}1035}1036}1037} else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {1038// Possible device root1039if (len <= 2) {1040// `path` contains just a drive root, exit early to avoid1041// unnecessary work1042ret.root = ret.dir = path;1043return ret;1044}1045rootEnd = 2;1046if (isPathSeparator(path.charCodeAt(2))) {1047if (len === 3) {1048// `path` contains just a drive root, exit early to avoid1049// unnecessary work1050ret.root = ret.dir = path;1051return ret;1052}1053rootEnd = 3;1054}1055}1056if (rootEnd > 0) {1057ret.root = path.slice(0, rootEnd);1058}10591060let startDot = -1;1061let startPart = rootEnd;1062let end = -1;1063let matchedSlash = true;1064let i = path.length - 1;10651066// Track the state of characters (if any) we see before our first dot and1067// after any path separator we find1068let preDotState = 0;10691070// Get non-dir info1071for (; i >= rootEnd; --i) {1072code = path.charCodeAt(i);1073if (isPathSeparator(code)) {1074// If we reached a path separator that was not part of a set of path1075// separators at the end of the string, stop now1076if (!matchedSlash) {1077startPart = i + 1;1078break;1079}1080continue;1081}1082if (end === -1) {1083// We saw the first non-path separator, mark this as the end of our1084// extension1085matchedSlash = false;1086end = i + 1;1087}1088if (code === CHAR_DOT) {1089// If this is our first dot, mark it as the start of our extension1090if (startDot === -1) {1091startDot = i;1092} else if (preDotState !== 1) {1093preDotState = 1;1094}1095} else if (startDot !== -1) {1096// We saw a non-dot and non-path separator before our dot, so we should1097// have a good chance at having a non-empty extension1098preDotState = -1;1099}1100}11011102if (end !== -1) {1103if (startDot === -1 ||1104// We saw a non-dot character immediately before the dot1105preDotState === 0 ||1106// The (right-most) trimmed path component is exactly '..'1107(preDotState === 1 &&1108startDot === end - 1 &&1109startDot === startPart + 1)) {1110ret.base = ret.name = path.slice(startPart, end);1111} else {1112ret.name = path.slice(startPart, startDot);1113ret.base = path.slice(startPart, end);1114ret.ext = path.slice(startDot, end);1115}1116}11171118// If the directory is the root, use the entire root as the `dir` including1119// the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the1120// trailing slash (`C:\abc\def` -> `C:\abc`).1121if (startPart > 0 && startPart !== rootEnd) {1122ret.dir = path.slice(0, startPart - 1);1123} else {1124ret.dir = ret.root;1125}11261127return ret;1128},11291130sep: '\\',1131delimiter: ';',1132win32: null,1133posix: null1134};11351136const posixCwd = (() => {1137if (platformIsWin32) {1138// Converts Windows' backslash path separators to POSIX forward slashes1139// and truncates any drive indicator1140const regexp = /\\/g;1141return () => {1142const cwd = process.cwd().replace(regexp, '/');1143return cwd.slice(cwd.indexOf('/'));1144};1145}11461147// We're already on POSIX, no need for any transformations1148return () => process.cwd();1149})();11501151export const posix: IPath = {1152// path.resolve([from ...], to)1153resolve(...pathSegments: string[]): string {1154let resolvedPath = '';1155let resolvedAbsolute = false;11561157for (let i = pathSegments.length - 1; i >= 0 && !resolvedAbsolute; i--) {1158const path = pathSegments[i];1159validateString(path, `paths[${i}]`);11601161// Skip empty entries1162if (path.length === 0) {1163continue;1164}11651166resolvedPath = `${path}/${resolvedPath}`;1167resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH;1168}11691170if (!resolvedAbsolute) {1171const cwd = posixCwd();1172resolvedPath = `${cwd}/${resolvedPath}`;1173resolvedAbsolute =1174cwd.charCodeAt(0) === CHAR_FORWARD_SLASH;1175}11761177// At this point the path should be resolved to a full absolute path, but1178// handle relative paths to be safe (might happen when process.cwd() fails)11791180// Normalize the path1181resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/',1182isPosixPathSeparator);11831184if (resolvedAbsolute) {1185return `/${resolvedPath}`;1186}1187return resolvedPath.length > 0 ? resolvedPath : '.';1188},11891190normalize(path: string): string {1191validateString(path, 'path');11921193if (path.length === 0) {1194return '.';1195}11961197const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH;1198const trailingSeparator =1199path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH;12001201// Normalize the path1202path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator);12031204if (path.length === 0) {1205if (isAbsolute) {1206return '/';1207}1208return trailingSeparator ? './' : '.';1209}1210if (trailingSeparator) {1211path += '/';1212}12131214return isAbsolute ? `/${path}` : path;1215},12161217isAbsolute(path: string): boolean {1218validateString(path, 'path');1219return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH;1220},12211222join(...paths: string[]): string {1223if (paths.length === 0) {1224return '.';1225}12261227const path = [];1228for (let i = 0; i < paths.length; ++i) {1229const arg = paths[i];1230validateString(arg, 'path');1231if (arg.length > 0) {1232path.push(arg);1233}1234}12351236if (path.length === 0) {1237return '.';1238}12391240return posix.normalize(path.join('/'));1241},12421243relative(from: string, to: string): string {1244validateString(from, 'from');1245validateString(to, 'to');12461247if (from === to) {1248return '';1249}12501251// Trim leading forward slashes.1252from = posix.resolve(from);1253to = posix.resolve(to);12541255if (from === to) {1256return '';1257}12581259const fromStart = 1;1260const fromEnd = from.length;1261const fromLen = fromEnd - fromStart;1262const toStart = 1;1263const toLen = to.length - toStart;12641265// Compare paths to find the longest common path from root1266const length = (fromLen < toLen ? fromLen : toLen);1267let lastCommonSep = -1;1268let i = 0;1269for (; i < length; i++) {1270const fromCode = from.charCodeAt(fromStart + i);1271if (fromCode !== to.charCodeAt(toStart + i)) {1272break;1273} else if (fromCode === CHAR_FORWARD_SLASH) {1274lastCommonSep = i;1275}1276}1277if (i === length) {1278if (toLen > length) {1279if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {1280// We get here if `from` is the exact base path for `to`.1281// For example: from='/foo/bar'; to='/foo/bar/baz'1282return to.slice(toStart + i + 1);1283}1284if (i === 0) {1285// We get here if `from` is the root1286// For example: from='/'; to='/foo'1287return to.slice(toStart + i);1288}1289} else if (fromLen > length) {1290if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {1291// We get here if `to` is the exact base path for `from`.1292// For example: from='/foo/bar/baz'; to='/foo/bar'1293lastCommonSep = i;1294} else if (i === 0) {1295// We get here if `to` is the root.1296// For example: from='/foo/bar'; to='/'1297lastCommonSep = 0;1298}1299}1300}13011302let out = '';1303// Generate the relative path based on the path difference between `to`1304// and `from`.1305for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {1306if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {1307out += out.length === 0 ? '..' : '/..';1308}1309}13101311// Lastly, append the rest of the destination (`to`) path that comes after1312// the common path parts.1313return `${out}${to.slice(toStart + lastCommonSep)}`;1314},13151316toNamespacedPath(path: string): string {1317// Non-op on posix systems1318return path;1319},13201321dirname(path: string): string {1322validateString(path, 'path');1323if (path.length === 0) {1324return '.';1325}1326const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH;1327let end = -1;1328let matchedSlash = true;1329for (let i = path.length - 1; i >= 1; --i) {1330if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {1331if (!matchedSlash) {1332end = i;1333break;1334}1335} else {1336// We saw the first non-path separator1337matchedSlash = false;1338}1339}13401341if (end === -1) {1342return hasRoot ? '/' : '.';1343}1344if (hasRoot && end === 1) {1345return '//';1346}1347return path.slice(0, end);1348},13491350basename(path: string, suffix?: string): string {1351if (suffix !== undefined) {1352validateString(suffix, 'suffix');1353}1354validateString(path, 'path');13551356let start = 0;1357let end = -1;1358let matchedSlash = true;1359let i;13601361if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {1362if (suffix === path) {1363return '';1364}1365let extIdx = suffix.length - 1;1366let firstNonSlashEnd = -1;1367for (i = path.length - 1; i >= 0; --i) {1368const code = path.charCodeAt(i);1369if (code === CHAR_FORWARD_SLASH) {1370// If we reached a path separator that was not part of a set of path1371// separators at the end of the string, stop now1372if (!matchedSlash) {1373start = i + 1;1374break;1375}1376} else {1377if (firstNonSlashEnd === -1) {1378// We saw the first non-path separator, remember this index in case1379// we need it if the extension ends up not matching1380matchedSlash = false;1381firstNonSlashEnd = i + 1;1382}1383if (extIdx >= 0) {1384// Try to match the explicit extension1385if (code === suffix.charCodeAt(extIdx)) {1386if (--extIdx === -1) {1387// We matched the extension, so mark this as the end of our path1388// component1389end = i;1390}1391} else {1392// Extension does not match, so our result is the entire path1393// component1394extIdx = -1;1395end = firstNonSlashEnd;1396}1397}1398}1399}14001401if (start === end) {1402end = firstNonSlashEnd;1403} else if (end === -1) {1404end = path.length;1405}1406return path.slice(start, end);1407}1408for (i = path.length - 1; i >= 0; --i) {1409if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {1410// If we reached a path separator that was not part of a set of path1411// separators at the end of the string, stop now1412if (!matchedSlash) {1413start = i + 1;1414break;1415}1416} else if (end === -1) {1417// We saw the first non-path separator, mark this as the end of our1418// path component1419matchedSlash = false;1420end = i + 1;1421}1422}14231424if (end === -1) {1425return '';1426}1427return path.slice(start, end);1428},14291430extname(path: string): string {1431validateString(path, 'path');1432let startDot = -1;1433let startPart = 0;1434let end = -1;1435let matchedSlash = true;1436// Track the state of characters (if any) we see before our first dot and1437// after any path separator we find1438let preDotState = 0;1439for (let i = path.length - 1; i >= 0; --i) {1440const char = path[i];1441if (char === '/') {1442// If we reached a path separator that was not part of a set of path1443// separators at the end of the string, stop now1444if (!matchedSlash) {1445startPart = i + 1;1446break;1447}1448continue;1449}1450if (end === -1) {1451// We saw the first non-path separator, mark this as the end of our1452// extension1453matchedSlash = false;1454end = i + 1;1455}1456if (char === '.') {1457// If this is our first dot, mark it as the start of our extension1458if (startDot === -1) {1459startDot = i;1460}1461else if (preDotState !== 1) {1462preDotState = 1;1463}1464} else if (startDot !== -1) {1465// We saw a non-dot and non-path separator before our dot, so we should1466// have a good chance at having a non-empty extension1467preDotState = -1;1468}1469}14701471if (startDot === -1 ||1472end === -1 ||1473// We saw a non-dot character immediately before the dot1474preDotState === 0 ||1475// The (right-most) trimmed path component is exactly '..'1476(preDotState === 1 &&1477startDot === end - 1 &&1478startDot === startPart + 1)) {1479return '';1480}1481return path.slice(startDot, end);1482},14831484format: _format.bind(null, '/'),14851486parse(path: string): ParsedPath {1487validateString(path, 'path');14881489const ret = { root: '', dir: '', base: '', ext: '', name: '' };1490if (path.length === 0) {1491return ret;1492}1493const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH;1494let start;1495if (isAbsolute) {1496ret.root = '/';1497start = 1;1498} else {1499start = 0;1500}1501let startDot = -1;1502let startPart = 0;1503let end = -1;1504let matchedSlash = true;1505let i = path.length - 1;15061507// Track the state of characters (if any) we see before our first dot and1508// after any path separator we find1509let preDotState = 0;15101511// Get non-dir info1512for (; i >= start; --i) {1513const code = path.charCodeAt(i);1514if (code === CHAR_FORWARD_SLASH) {1515// If we reached a path separator that was not part of a set of path1516// separators at the end of the string, stop now1517if (!matchedSlash) {1518startPart = i + 1;1519break;1520}1521continue;1522}1523if (end === -1) {1524// We saw the first non-path separator, mark this as the end of our1525// extension1526matchedSlash = false;1527end = i + 1;1528}1529if (code === CHAR_DOT) {1530// If this is our first dot, mark it as the start of our extension1531if (startDot === -1) {1532startDot = i;1533} else if (preDotState !== 1) {1534preDotState = 1;1535}1536} else if (startDot !== -1) {1537// We saw a non-dot and non-path separator before our dot, so we should1538// have a good chance at having a non-empty extension1539preDotState = -1;1540}1541}15421543if (end !== -1) {1544const start = startPart === 0 && isAbsolute ? 1 : startPart;1545if (startDot === -1 ||1546// We saw a non-dot character immediately before the dot1547preDotState === 0 ||1548// The (right-most) trimmed path component is exactly '..'1549(preDotState === 1 &&1550startDot === end - 1 &&1551startDot === startPart + 1)) {1552ret.base = ret.name = path.slice(start, end);1553} else {1554ret.name = path.slice(start, startDot);1555ret.base = path.slice(start, end);1556ret.ext = path.slice(startDot, end);1557}1558}15591560if (startPart > 0) {1561ret.dir = path.slice(0, startPart - 1);1562} else if (isAbsolute) {1563ret.dir = '/';1564}15651566return ret;1567},15681569sep: '/',1570delimiter: ':',1571win32: null,1572posix: null1573};15741575posix.win32 = win32.win32 = win32;1576posix.posix = win32.posix = posix;15771578export const normalize = (platformIsWin32 ? win32.normalize : posix.normalize);1579export const isAbsolute = (platformIsWin32 ? win32.isAbsolute : posix.isAbsolute);1580export const join = (platformIsWin32 ? win32.join : posix.join);1581export const resolve = (platformIsWin32 ? win32.resolve : posix.resolve);1582export const relative = (platformIsWin32 ? win32.relative : posix.relative);1583export const dirname = (platformIsWin32 ? win32.dirname : posix.dirname);1584export const basename = (platformIsWin32 ? win32.basename : posix.basename);1585export const extname = (platformIsWin32 ? win32.extname : posix.extname);1586export const format = (platformIsWin32 ? win32.format : posix.format);1587export const parse = (platformIsWin32 ? win32.parse : posix.parse);1588export const toNamespacedPath = (platformIsWin32 ? win32.toNamespacedPath : posix.toNamespacedPath);1589export const sep = (platformIsWin32 ? win32.sep : posix.sep);1590export const delimiter = (platformIsWin32 ? win32.delimiter : posix.delimiter);159115921593