char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
38d5bbf
main
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 { 30ArrayIsArray , 31ArrayPrototypeJoin , 32ArrayPrototypePop , 33ArrayPrototypePush , 34ArrayPrototypeSlice , 35ObjectFreeze , 36StringPrototypeCharCodeAt , 37StringPrototypeIncludes , 38StringPrototypeIndexOf , 39StringPrototypeLastIndexOf , 40StringPrototypeRepeat , 41StringPrototypeSlice , 42StringPrototypeToLowerCase , 43TypeError , 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 { 61if ( value === null ) return "null" ; 62if ( typeof value !== "object" ) return typeof value ; 63return ArrayIsArray ( value ) ?"an instance of Array" :"an instance of Object" ; 64} 65 66function validateString ( value :unknown , name :string ) : assertsvalue isstring { 67if ( typeof value !== "string" ) { 68throw new TypeError ( 69`The " ${ name } " argument must be of type string. Received ${ describeReceived ( value )} ` , 70); 71} 72} 73 74function validateObject ( value :unknown , name :string ) : assertsvalue isRecord < string , unknown > { 75if ( value === null || ArrayIsArray ( value ) || typeof value !== "object" ) { 76throw 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 [] { 85const parts :string [] = []; 86let start = 0 ; 87for ( let i = 0 ; i < input . length ; i ++ ) { 88if ( StringPrototypeCharCodeAt ( input , i ) === code ) { 89ArrayPrototypePush ( parts , StringPrototypeSlice ( input , start , i )); 90start = i + 1 ; 91} 92} 93ArrayPrototypePush ( parts , StringPrototypeSlice ( input , start , input . length )); 94return 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 () { 102return result ( op_process_cwd ()); 103} 104 105function isPathSeparator ( code :number ) :boolean { 106return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH ; 107} 108 109function isPosixPathSeparator ( code :number ) :boolean { 110return code === CHAR_FORWARD_SLASH ; 111} 112 113function isWindowsDeviceRoot ( code :number ) :boolean { 114return ( 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 { 120let res = "" ; 121let lastSegmentLength = 0 ; 122let lastSlash = - 1 ; 123let dots = 0 ; 124let code = 0 ; 125for ( let i = 0 ; i <= path . length ; ++ i ) { 126if ( i < path . length ) { 127code = StringPrototypeCharCodeAt ( path , i ); 128} else if ( isPathSeparator ( code )) { 129break ; 130} else { 131code = CHAR_FORWARD_SLASH ; 132} 133 134if ( isPathSeparator ( code )) { 135if ( lastSlash === i - 1 || dots === 1 ) { 136// NOOP 137} else if ( dots === 2 ) { 138if ( 139res . length < 2 || lastSegmentLength !== 2 || 140StringPrototypeCharCodeAt ( res , res . length - 1 ) !== CHAR_DOT || 141StringPrototypeCharCodeAt ( res , res . length - 2 ) !== CHAR_DOT 142) { 143if ( res . length > 2 ) { 144const lastSlashIndex = StringPrototypeLastIndexOf ( res , separator ); 145if ( lastSlashIndex === - 1 ) { 146res = "" ; 147lastSegmentLength = 0 ; 148} else { 149res = StringPrototypeSlice ( res , 0 , lastSlashIndex ); 150lastSegmentLength = 151res . length - 1 - StringPrototypeLastIndexOf ( res , separator ); 152} 153lastSlash = i ; 154dots = 0 ; 155continue ; 156} else if ( res . length !== 0 ) { 157res = "" ; 158lastSegmentLength = 0 ; 159lastSlash = i ; 160dots = 0 ; 161continue ; 162} 163} 164if ( allowAboveRoot ) { 165res += res . length > 0 ?` ${ separator } ..` :".." ; 166lastSegmentLength = 2 ; 167} 168} else { 169if ( res . length > 0 ) { 170res += ` ${ separator } ${ StringPrototypeSlice ( path , lastSlash + 1 , i )} ` ; 171} else { 172res = StringPrototypeSlice ( path , lastSlash + 1 , i ); 173} 174lastSegmentLength = i - lastSlash - 1 ; 175} 176lastSlash = i ; 177dots = 0 ; 178} else if ( code === CHAR_DOT && dots !== - 1 ) { 179++ dots ; 180} else { 181dots = - 1 ; 182} 183} 184return res ; 185} 186 187function formatExt ( ext :string | undefined ) :string { 188return ext ?` ${ ext [ 0 ] === "." ? "" : "." } ${ ext } ` :"" ; 189} 190 191function _format ( sep :string , pathObject :PathFormatObject ) :string { 192validateObject ( pathObject , "pathObject" ); 193const dir = pathObject . dir || pathObject . root ; 194const base = pathObject . base || 195` ${ pathObject . name || "" } ${ formatExt ( pathObject . ext )} ` ; 196if ( ! dir ) { 197return base ; 198} 199return 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 { 207const cwd = nativeCwd (); 208if ( 209cwd . length >= 2 && 210StringPrototypeToLowerCase ( StringPrototypeSlice ( cwd , 0 , 2 )) === 211StringPrototypeToLowerCase ( device ) && 212( cwd . length === 2 || 213StringPrototypeCharCodeAt ( cwd , 2 ) === CHAR_BACKWARD_SLASH || 214StringPrototypeCharCodeAt ( cwd , 2 ) === CHAR_FORWARD_SLASH ) 215) { 216return cwd ; 217} 218return ` ${ 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 = { 224resolve ( ...paths :string []) :string ; 225normalize ( path :string ) :string ; 226isAbsolute ( path :string ) :boolean ; 227join ( ...paths :string []) :string ; 228relative ( from :string , to :string ) :string ; 229toNamespacedPath ( path :string ) :string ; 230dirname ( path :string ) :string ; 231basename ( path :string , suffix ?:string ) :string ; 232extname ( path :string ) :string ; 233format ( pathObject :PathFormatObject ) :string ; 234parse ( path :string ) :ParsedPath ; 235sep :string ; 236delimiter :string ; 237}; 238 239const win32 :PathApi = { 240resolve ( ...args ) { 241let resolvedDevice = "" ; 242let resolvedTail = "" ; 243let resolvedAbsolute = false ; 244 245for ( let i = args . length - 1 ; i >= - 1 ; i -- ) { 246let path ; 247if ( i >= 0 ) { 248path = args [ i ] ! ; 249validateString ( path , `paths[ ${ i } ]` ); 250 251// Skip empty entries 252if ( path . length === 0 ) { 253continue ; 254} 255} else if ( resolvedDevice . length === 0 ) { 256path = nativeCwd (); 257} else { 258// We're sure the device is not a UNC path at this point, because UNC 259// paths are always absolute. 260path = cwdForDrive ( resolvedDevice ); 261} 262 263const len = path . length ; 264let rootEnd = 0 ; 265let device = "" ; 266let isAbsolute = false ; 267const code = StringPrototypeCharCodeAt ( path , 0 ); 268 269// Try to match a root 270if ( len === 1 ) { 271if ( isPathSeparator ( code )) { 272// `path` contains just a path separator 273rootEnd = 1 ; 274isAbsolute = 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) 281isAbsolute = true ; 282 283if ( isPathSeparator ( StringPrototypeCharCodeAt ( path , 1 ))) { 284// Matched double path separator at beginning 285let j = 2 ; 286let last = j ; 287// Match 1 or more non-path separators 288while ( j < len && ! isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 289j ++ ; 290} 291if ( j < len && j !== last ) { 292const firstPart = StringPrototypeSlice ( path , last , j ); 293// Matched! 294last = j ; 295// Match 1 or more path separators 296while ( j < len && isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 297j ++ ; 298} 299if ( j < len && j !== last ) { 300// Matched! 301last = j ; 302// Match 1 or more non-path separators 303while ( j < len && ! isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 304j ++ ; 305} 306if ( j === len || j !== last ) { 307// We matched a UNC root 308device = `\\\\ ${ firstPart } \\ ${ StringPrototypeSlice ( path , last , j )} ` ; 309rootEnd = j ; 310} 311} 312} 313} else { 314rootEnd = 1 ; 315} 316} else if ( 317isWindowsDeviceRoot ( code ) && 318StringPrototypeCharCodeAt ( path , 1 ) === CHAR_COLON 319) { 320// Possible device root 321device = StringPrototypeSlice ( path , 0 , 2 ); 322rootEnd = 2 ; 323if ( len > 2 && isPathSeparator ( StringPrototypeCharCodeAt ( path , 2 ))) { 324// Treat separator following drive name as an absolute path 325// indicator 326isAbsolute = true ; 327rootEnd = 3 ; 328} 329} 330 331if ( device . length > 0 ) { 332if ( resolvedDevice . length > 0 ) { 333if ( 334StringPrototypeToLowerCase ( device ) !== 335StringPrototypeToLowerCase ( resolvedDevice ) 336) { 337// This path points to another device so it is not applicable 338continue ; 339} 340} else { 341resolvedDevice = device ; 342} 343} 344 345if ( resolvedAbsolute ) { 346if ( resolvedDevice . length > 0 ) { 347break ; 348} 349} else { 350resolvedTail = ` ${ StringPrototypeSlice ( path , rootEnd )} \\ ${ resolvedTail } ` ; 351resolvedAbsolute = isAbsolute ; 352if ( isAbsolute && resolvedDevice . length > 0 ) { 353break ; 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 363resolvedTail = normalizeString ( 364resolvedTail , 365! resolvedAbsolute , 366"\\" , 367isPathSeparator , 368); 369 370return resolvedAbsolute 371 ?` ${ resolvedDevice } \\ ${ resolvedTail } ` 372 :` ${ resolvedDevice } ${ resolvedTail } ` || "." ; 373}, 374 375normalize ( path ) { 376validateString ( path , "path" ); 377const len = path . length ; 378if ( len === 0 ) { 379return "." ; 380} 381let rootEnd = 0 ; 382let device ; 383let isAbsolute = false ; 384const code = StringPrototypeCharCodeAt ( path , 0 ); 385 386// Try to match a root 387if ( len === 1 ) { 388// `path` contains just a single char, exit early to avoid 389// unnecessary work 390return isPosixPathSeparator ( code ) ?"\\" :path ; 391} 392if ( 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) 397isAbsolute = true ; 398 399if ( isPathSeparator ( StringPrototypeCharCodeAt ( path , 1 ))) { 400// Matched double path separator at beginning 401let j = 2 ; 402let last = j ; 403// Match 1 or more non-path separators 404while ( j < len && ! isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 405j ++ ; 406} 407if ( j < len && j !== last ) { 408const firstPart = StringPrototypeSlice ( path , last , j ); 409// Matched! 410last = j ; 411// Match 1 or more path separators 412while ( j < len && isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 413j ++ ; 414} 415if ( j < len && j !== last ) { 416// Matched! 417last = j ; 418// Match 1 or more non-path separators 419while ( j < len && ! isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 420j ++ ; 421} 422if ( 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 426return `\\\\ ${ firstPart } \\ ${ StringPrototypeSlice ( path , last )} \\` ; 427} 428if ( j !== last ) { 429// We matched a UNC root with leftovers 430device = `\\\\ ${ firstPart } \\ ${ StringPrototypeSlice ( path , last , j )} ` ; 431rootEnd = j ; 432} 433} 434} 435} else { 436rootEnd = 1 ; 437} 438} else if ( 439isWindowsDeviceRoot ( code ) && 440StringPrototypeCharCodeAt ( path , 1 ) === CHAR_COLON 441) { 442// Possible device root 443device = StringPrototypeSlice ( path , 0 , 2 ); 444rootEnd = 2 ; 445if ( len > 2 && isPathSeparator ( StringPrototypeCharCodeAt ( path , 2 ))) { 446// Treat separator following drive name as an absolute path 447// indicator 448isAbsolute = true ; 449rootEnd = 3 ; 450} 451} 452 453let tail = rootEnd < len 454 ?normalizeString ( 455StringPrototypeSlice ( path , rootEnd ), 456! isAbsolute , 457"\\" , 458isPathSeparator , 459) 460 :"" ; 461if ( tail . length === 0 && ! isAbsolute ) { 462tail = "." ; 463} 464if ( 465tail . length > 0 && 466isPathSeparator ( StringPrototypeCharCodeAt ( path , len - 1 )) 467) { 468tail += "\\" ; 469} 470if ( ! 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. 475if ( 476tail . length >= 2 && 477isWindowsDeviceRoot ( StringPrototypeCharCodeAt ( tail , 0 )) && 478StringPrototypeCharCodeAt ( tail , 1 ) === CHAR_COLON 479) { 480return `.\\ ${ tail } ` ; 481} 482let index = StringPrototypeIndexOf ( path , ":" ); 483do { 484if ( 485index === len - 1 || 486isPathSeparator ( StringPrototypeCharCodeAt ( path , index + 1 )) 487) { 488return `.\\ ${ tail } ` ; 489} 490} while (( index = StringPrototypeIndexOf ( path , ":" , index + 1 )) !== - 1 ); 491} 492if ( device === undefined ) { 493return isAbsolute ?`\\ ${ tail } ` :tail ; 494} 495return isAbsolute ?` ${ device } \\ ${ tail } ` :` ${ device } ${ tail } ` ; 496}, 497 498isAbsolute ( path ) { 499validateString ( path , "path" ); 500const len = path . length ; 501if ( len === 0 ) { 502return false ; 503} 504 505const code = StringPrototypeCharCodeAt ( path , 0 ); 506return isPathSeparator ( code ) || 507// Possible device root 508( len > 2 && 509isWindowsDeviceRoot ( code ) && 510StringPrototypeCharCodeAt ( path , 1 ) === CHAR_COLON && 511isPathSeparator ( StringPrototypeCharCodeAt ( path , 2 ))); 512}, 513 514join ( ...args ) { 515if ( args . length === 0 ) { 516return "." ; 517} 518 519let joined ; 520let firstPart :string ; 521for ( let i = 0 ; i < args . length ; ++ i ) { 522const arg = args [ i ]; 523validateString ( arg , "path" ); 524if ( arg . length > 0 ) { 525if ( joined === undefined ) { 526joined = firstPart = arg ; 527} else { 528joined += `\\ ${ arg } ` ; 529} 530} 531} 532 533if ( joined === undefined ) { 534return "." ; 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\\') 550let needsReplace = true ; 551let slashCount = 0 ; 552if ( isPathSeparator ( StringPrototypeCharCodeAt ( firstPart ! , 0 ))) { 553++ slashCount ; 554const firstLen = firstPart ! . length ; 555if ( 556firstLen > 1 && 557isPathSeparator ( StringPrototypeCharCodeAt ( firstPart ! , 1 )) 558) { 559++ slashCount ; 560if ( firstLen > 2 ) { 561if ( isPathSeparator ( StringPrototypeCharCodeAt ( firstPart ! , 2 ))) { 562++ slashCount ; 563} else { 564// We matched a UNC path in the first part 565needsReplace = false ; 566} 567} 568} 569} 570if ( needsReplace ) { 571// Find any more consecutive slashes we need to replace 572while ( 573slashCount < joined . length && 574isPathSeparator ( StringPrototypeCharCodeAt ( joined , slashCount )) 575) { 576slashCount ++ ; 577} 578 579// Replace the slashes if needed 580if ( slashCount >= 2 ) { 581joined = `\\ ${ StringPrototypeSlice ( joined , slashCount )} ` ; 582} 583} 584 585return win32 . normalize ( joined ); 586}, 587 588relative ( from , to ) { 589validateString ( from , "from" ); 590validateString ( to , "to" ); 591 592if ( from === to ) { 593return "" ; 594} 595 596const fromOrig = win32 . resolve ( from ); 597const toOrig = win32 . resolve ( to ); 598 599if ( fromOrig === toOrig ) { 600return "" ; 601} 602 603from = StringPrototypeToLowerCase ( fromOrig ); 604to = StringPrototypeToLowerCase ( toOrig ); 605 606if ( from === to ) { 607return "" ; 608} 609 610if ( 611fromOrig . length !== from . length || toOrig . length !== to . length 612) { 613const fromSplit = splitOnChar ( fromOrig , CHAR_BACKWARD_SLASH ); 614const toSplit = splitOnChar ( toOrig , CHAR_BACKWARD_SLASH ); 615if ( fromSplit [ fromSplit . length - 1 ] === "" ) { 616ArrayPrototypePop ( fromSplit ); 617} 618if ( toSplit [ toSplit . length - 1 ] === "" ) { 619ArrayPrototypePop ( toSplit ); 620} 621 622const fromLen = fromSplit . length ; 623const toLen = toSplit . length ; 624const length = fromLen < toLen ?fromLen :toLen ; 625 626let i ; 627for ( i = 0 ; i < length ; i ++ ) { 628if ( 629StringPrototypeToLowerCase ( fromSplit [ i ]) !== 630StringPrototypeToLowerCase ( toSplit [ i ]) 631) { 632break ; 633} 634} 635 636if ( i === 0 ) { 637return toOrig ; 638} else if ( i === length ) { 639if ( toLen > length ) { 640return ArrayPrototypeJoin ( ArrayPrototypeSlice ( toSplit , i ), "\\" ); 641} 642if ( fromLen > length ) { 643return StringPrototypeRepeat ( "..\\" , fromLen - 1 - i ) + ".." ; 644} 645return "" ; 646} 647 648return StringPrototypeRepeat ( "..\\" , fromLen - i ) + 649ArrayPrototypeJoin ( ArrayPrototypeSlice ( toSplit , i ), "\\" ); 650} 651 652// Trim any leading backslashes 653let fromStart = 0 ; 654while ( 655fromStart < from . length && 656StringPrototypeCharCodeAt ( from , fromStart ) === CHAR_BACKWARD_SLASH 657) { 658fromStart ++ ; 659} 660// Trim trailing backslashes (applicable to UNC paths only) 661let fromEnd = from . length ; 662while ( 663fromEnd - 1 > fromStart && 664StringPrototypeCharCodeAt ( from , fromEnd - 1 ) === CHAR_BACKWARD_SLASH 665) { 666fromEnd -- ; 667} 668const fromLen = fromEnd - fromStart ; 669 670// Trim any leading backslashes 671let toStart = 0 ; 672while ( 673toStart < to . length && 674StringPrototypeCharCodeAt ( to , toStart ) === CHAR_BACKWARD_SLASH 675) { 676toStart ++ ; 677} 678// Trim trailing backslashes (applicable to UNC paths only) 679let toEnd = to . length ; 680while ( 681toEnd - 1 > toStart && 682StringPrototypeCharCodeAt ( to , toEnd - 1 ) === CHAR_BACKWARD_SLASH 683) { 684toEnd -- ; 685} 686const toLen = toEnd - toStart ; 687 688// Compare paths to find the longest common path from root 689const length = fromLen < toLen ?fromLen :toLen ; 690let lastCommonSep = - 1 ; 691let i = 0 ; 692for (; i < length ; i ++ ) { 693const fromCode = StringPrototypeCharCodeAt ( from , fromStart + i ); 694if ( fromCode !== StringPrototypeCharCodeAt ( to , toStart + i )) { 695break ; 696} else if ( fromCode === CHAR_BACKWARD_SLASH ) { 697lastCommonSep = i ; 698} 699} 700 701// We found a mismatch before the first common path separator was seen, so 702// return the original `to`. 703if ( i !== length ) { 704if ( lastCommonSep === - 1 ) { 705return toOrig ; 706} 707} else { 708if ( toLen > length ) { 709if ( 710StringPrototypeCharCodeAt ( 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' 714return StringPrototypeSlice ( toOrig , toStart + i + 1 ); 715} 716if ( i === 2 ) { 717// We get here if `from` is the device root. 718// For example: from='C:\\'; to='C:\\foo' 719return StringPrototypeSlice ( toOrig , toStart + i ); 720} 721} 722if ( fromLen > length ) { 723if ( 724StringPrototypeCharCodeAt ( from , fromStart + i ) === 725CHAR_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' 729lastCommonSep = 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:\\' 733lastCommonSep = 3 ; 734} 735} 736if ( lastCommonSep === - 1 ) { 737lastCommonSep = 0 ; 738} 739} 740 741let out = "" ; 742// Generate the relative path based on the path difference between `to` 743// and `from` 744for ( i = fromStart + lastCommonSep + 1 ; i <= fromEnd ; ++ i ) { 745if ( 746i === fromEnd || 747StringPrototypeCharCodeAt ( from , i ) === CHAR_BACKWARD_SLASH 748) { 749out += out . length === 0 ?".." :"\\.." ; 750} 751} 752 753toStart += lastCommonSep ; 754 755// Lastly, append the rest of the destination (`to`) path that comes after 756// the common path parts 757if ( out . length > 0 ) { 758return ` ${ out } ${ StringPrototypeSlice ( toOrig , toStart , toEnd )} ` ; 759} 760 761if ( StringPrototypeCharCodeAt ( toOrig , toStart ) === CHAR_BACKWARD_SLASH ) { 762++ toStart ; 763} 764return StringPrototypeSlice ( toOrig , toStart , toEnd ); 765}, 766 767toNamespacedPath ( path ) { 768// Note: this will *probably* throw somewhere. 769if ( typeof path !== "string" || path . length === 0 ) { 770return path ; 771} 772 773const resolvedPath = win32 . resolve ( path ); 774 775if ( resolvedPath . length <= 2 ) { 776return path ; 777} 778 779if ( StringPrototypeCharCodeAt ( resolvedPath , 0 ) === CHAR_BACKWARD_SLASH ) { 780// Possible UNC root 781if ( StringPrototypeCharCodeAt ( resolvedPath , 1 ) === CHAR_BACKWARD_SLASH ) { 782const code = StringPrototypeCharCodeAt ( resolvedPath , 2 ); 783if ( code !== CHAR_QUESTION_MARK && code !== CHAR_DOT ) { 784// Matched non-long UNC root, convert the path to a long UNC path 785return `\\\\?\\UNC\\ ${ StringPrototypeSlice ( resolvedPath , 2 )} ` ; 786} 787} 788} else if ( 789isWindowsDeviceRoot ( StringPrototypeCharCodeAt ( resolvedPath , 0 )) && 790StringPrototypeCharCodeAt ( resolvedPath , 1 ) === CHAR_COLON && 791StringPrototypeCharCodeAt ( resolvedPath , 2 ) === CHAR_BACKWARD_SLASH 792) { 793// Matched device root, convert the path to a long UNC path 794return `\\\\?\\ ${ resolvedPath } ` ; 795} 796 797return resolvedPath ; 798}, 799 800dirname ( path ) { 801validateString ( path , "path" ); 802const len = path . length ; 803if ( len === 0 ) { 804return "." ; 805} 806let rootEnd = - 1 ; 807let offset = 0 ; 808const code = StringPrototypeCharCodeAt ( path , 0 ); 809 810if ( len === 1 ) { 811// `path` contains just a path separator, exit early to avoid 812// unnecessary work or a dot. 813return isPathSeparator ( code ) ?path :"." ; 814} 815 816// Try to match a root 817if ( isPathSeparator ( code )) { 818// Possible UNC root 819 820rootEnd = offset = 1 ; 821 822if ( isPathSeparator ( StringPrototypeCharCodeAt ( path , 1 ))) { 823// Matched double path separator at beginning 824let j = 2 ; 825let last = j ; 826// Match 1 or more non-path separators 827while ( j < len && ! isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 828j ++ ; 829} 830if ( j < len && j !== last ) { 831// Matched! 832last = j ; 833// Match 1 or more path separators 834while ( j < len && isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 835j ++ ; 836} 837if ( j < len && j !== last ) { 838// Matched! 839last = j ; 840// Match 1 or more non-path separators 841while ( j < len && ! isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 842j ++ ; 843} 844if ( j === len ) { 845// We matched a UNC root only 846return path ; 847} 848if ( 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 853rootEnd = offset = j + 1 ; 854} 855} 856} 857} 858// Possible device root 859} else if ( 860isWindowsDeviceRoot ( code ) && 861StringPrototypeCharCodeAt ( path , 1 ) === CHAR_COLON 862) { 863rootEnd = len > 2 && isPathSeparator ( StringPrototypeCharCodeAt ( path , 2 )) ?3 :2 ; 864offset = rootEnd ; 865} 866 867let end = - 1 ; 868let matchedSlash = true ; 869for ( let i = len - 1 ; i >= offset ; -- i ) { 870if ( isPathSeparator ( StringPrototypeCharCodeAt ( path , i ))) { 871if ( ! matchedSlash ) { 872end = i ; 873break ; 874} 875} else { 876// We saw the first non-path separator 877matchedSlash = false ; 878} 879} 880 881if ( end === - 1 ) { 882if ( rootEnd === - 1 ) { 883return "." ; 884} 885 886end = rootEnd ; 887} 888return StringPrototypeSlice ( path , 0 , end ); 889}, 890 891basename ( path , suffix ) { 892if ( suffix !== undefined ) { 893validateString ( suffix , "suffix" ); 894} 895validateString ( path , "path" ); 896let start = 0 ; 897let end = - 1 ; 898let 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 903if ( 904path . length >= 2 && 905isWindowsDeviceRoot ( StringPrototypeCharCodeAt ( path , 0 )) && 906StringPrototypeCharCodeAt ( path , 1 ) === CHAR_COLON 907) { 908start = 2 ; 909} 910 911if ( suffix !== undefined && suffix . length > 0 && suffix . length <= path . length ) { 912if ( suffix === path ) { 913return "" ; 914} 915let extIdx = suffix . length - 1 ; 916let firstNonSlashEnd = - 1 ; 917for ( let i = path . length - 1 ; i >= start ; -- i ) { 918const code = StringPrototypeCharCodeAt ( path , i ); 919if ( 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 922if ( ! matchedSlash ) { 923start = i + 1 ; 924break ; 925} 926} else { 927if ( 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 930matchedSlash = false ; 931firstNonSlashEnd = i + 1 ; 932} 933if ( extIdx >= 0 ) { 934// Try to match the explicit extension 935if ( code === StringPrototypeCharCodeAt ( suffix , extIdx )) { 936if ( -- extIdx === - 1 ) { 937// We matched the extension, so mark this as the end of our path 938// component 939end = i ; 940} 941} else { 942// Extension does not match, so our result is the entire path 943// component 944extIdx = - 1 ; 945end = firstNonSlashEnd ; 946} 947} 948} 949} 950 951if ( start === end ) { 952end = firstNonSlashEnd ; 953} else if ( end === - 1 ) { 954end = path . length ; 955} 956return StringPrototypeSlice ( path , start , end ); 957} 958for ( let i = path . length - 1 ; i >= start ; -- i ) { 959if ( 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 962if ( ! matchedSlash ) { 963start = i + 1 ; 964break ; 965} 966} else if ( end === - 1 ) { 967// We saw the first non-path separator, mark this as the end of our 968// path component 969matchedSlash = false ; 970end = i + 1 ; 971} 972} 973 974if ( end === - 1 ) { 975return "" ; 976} 977return StringPrototypeSlice ( path , start , end ); 978}, 979 980extname ( path ) { 981validateString ( path , "path" ); 982let start = 0 ; 983let startDot = - 1 ; 984let startPart = 0 ; 985let end = - 1 ; 986let matchedSlash = true ; 987// Track the state of characters (if any) we see before our first dot and 988// after any path separator we find 989let 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 995if ( 996path . length >= 2 && 997StringPrototypeCharCodeAt ( path , 1 ) === CHAR_COLON && 998isWindowsDeviceRoot ( StringPrototypeCharCodeAt ( path , 0 )) 999) { 1000start = startPart = 2 ; 1001} 1002 1003for ( let i = path . length - 1 ; i >= start ; -- i ) { 1004const code = StringPrototypeCharCodeAt ( path , i ); 1005if ( 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 1008if ( ! matchedSlash ) { 1009startPart = i + 1 ; 1010break ; 1011} 1012continue ; 1013} 1014if ( end === - 1 ) { 1015// We saw the first non-path separator, mark this as the end of our 1016// extension 1017matchedSlash = false ; 1018end = i + 1 ; 1019} 1020if ( code === CHAR_DOT ) { 1021// If this is our first dot, mark it as the start of our extension 1022if ( startDot === - 1 ) { 1023startDot = i ; 1024} else if ( preDotState !== 1 ) { 1025preDotState = 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 1030preDotState = - 1 ; 1031} 1032} 1033 1034if ( 1035startDot === - 1 || end === - 1 || 1036// We saw a non-dot character immediately before the dot 1037preDotState === 0 || 1038// The (right-most) trimmed path component is exactly '..' 1039( preDotState === 1 && startDot === end - 1 && startDot === startPart + 1 ) 1040) { 1041return "" ; 1042} 1043return StringPrototypeSlice ( path , startDot , end ); 1044}, 1045 1046format ( pathObject ) { 1047return _format ( "\\" , pathObject ); 1048}, 1049 1050parse ( path ) { 1051validateString ( path , "path" ); 1052 1053const ret = { root :"" , dir :"" , base :"" , ext :"" , name :"" }; 1054if ( path . length === 0 ) { 1055return ret ; 1056} 1057 1058const len = path . length ; 1059let rootEnd = 0 ; 1060let code = StringPrototypeCharCodeAt ( path , 0 ); 1061 1062if ( len === 1 ) { 1063if ( isPathSeparator ( code )) { 1064// `path` contains just a path separator, exit early to avoid 1065// unnecessary work 1066ret . root = ret . dir = path ; 1067return ret ; 1068} 1069ret . base = ret . name = path ; 1070return ret ; 1071} 1072// Try to match a root 1073if ( isPathSeparator ( code )) { 1074// Possible UNC root 1075 1076rootEnd = 1 ; 1077if ( isPathSeparator ( StringPrototypeCharCodeAt ( path , 1 ))) { 1078// Matched double path separator at beginning 1079let j = 2 ; 1080let last = j ; 1081// Match 1 or more non-path separators 1082while ( j < len && ! isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 1083j ++ ; 1084} 1085if ( j < len && j !== last ) { 1086// Matched! 1087last = j ; 1088// Match 1 or more path separators 1089while ( j < len && isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 1090j ++ ; 1091} 1092if ( j < len && j !== last ) { 1093// Matched! 1094last = j ; 1095// Match 1 or more non-path separators 1096while ( j < len && ! isPathSeparator ( StringPrototypeCharCodeAt ( path , j ))) { 1097j ++ ; 1098} 1099if ( j === len ) { 1100// We matched a UNC root only 1101rootEnd = j ; 1102} else if ( j !== last ) { 1103// We matched a UNC root with leftovers 1104rootEnd = j + 1 ; 1105} 1106} 1107} 1108} 1109} else if ( 1110isWindowsDeviceRoot ( code ) && 1111StringPrototypeCharCodeAt ( path , 1 ) === CHAR_COLON 1112) { 1113// Possible device root 1114if ( len <= 2 ) { 1115// `path` contains just a drive root, exit early to avoid 1116// unnecessary work 1117ret . root = ret . dir = path ; 1118return ret ; 1119} 1120rootEnd = 2 ; 1121if ( isPathSeparator ( StringPrototypeCharCodeAt ( path , 2 ))) { 1122if ( len === 3 ) { 1123// `path` contains just a drive root, exit early to avoid 1124// unnecessary work 1125ret . root = ret . dir = path ; 1126return ret ; 1127} 1128rootEnd = 3 ; 1129} 1130} 1131if ( rootEnd > 0 ) { 1132ret . root = StringPrototypeSlice ( path , 0 , rootEnd ); 1133} 1134 1135let startDot = - 1 ; 1136let startPart = rootEnd ; 1137let end = - 1 ; 1138let matchedSlash = true ; 1139let 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 1143let preDotState = 0 ; 1144 1145// Get non-dir info 1146for (; i >= rootEnd ; -- i ) { 1147code = StringPrototypeCharCodeAt ( path , i ); 1148if ( 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 1151if ( ! matchedSlash ) { 1152startPart = i + 1 ; 1153break ; 1154} 1155continue ; 1156} 1157if ( end === - 1 ) { 1158// We saw the first non-path separator, mark this as the end of our 1159// extension 1160matchedSlash = false ; 1161end = i + 1 ; 1162} 1163if ( code === CHAR_DOT ) { 1164// If this is our first dot, mark it as the start of our extension 1165if ( startDot === - 1 ) { 1166startDot = i ; 1167} else if ( preDotState !== 1 ) { 1168preDotState = 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 1173preDotState = - 1 ; 1174} 1175} 1176 1177if ( end !== - 1 ) { 1178if ( 1179startDot === - 1 || 1180// We saw a non-dot character immediately before the dot 1181preDotState === 0 || 1182// The (right-most) trimmed path component is exactly '..' 1183( preDotState === 1 && startDot === end - 1 && startDot === startPart + 1 ) 1184) { 1185ret . base = ret . name = StringPrototypeSlice ( path , startPart , end ); 1186} else { 1187ret . name = StringPrototypeSlice ( path , startPart , startDot ); 1188ret . base = StringPrototypeSlice ( path , startPart , end ); 1189ret . 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`). 1196if ( startPart > 0 && startPart !== rootEnd ) { 1197ret . dir = StringPrototypeSlice ( path , 0 , startPart - 1 ); 1198} else { 1199ret . dir = ret . root ; 1200} 1201 1202return ret ; 1203}, 1204 1205sep :"\\" , 1206delimiter :";" , 1207}; 1208 1209// Node views a Windows working directory through POSIX eyes by converting 1210// separators and truncating any drive indicator. 1211function posixCwd () { 1212if ( ! hostIsWindows ) { 1213return nativeCwd (); 1214} 1215const cwd = nativeCwd (); 1216const forward = ArrayPrototypeJoin ( splitOnChar ( cwd , CHAR_BACKWARD_SLASH ), "/" ); 1217return StringPrototypeSlice ( forward , StringPrototypeIndexOf ( forward , "/" )); 1218} 1219 1220const posix :PathApi = { 1221resolve ( ...args ) { 1222let resolvedPath = "" ; 1223let resolvedAbsolute = false ; 1224 1225for ( let i = args . length - 1 ; i >= 0 && ! resolvedAbsolute ; i -- ) { 1226const path = args [ i ] ! ; 1227validateString ( path , `paths[ ${ i } ]` ); 1228 1229// Skip empty entries 1230if ( path . length === 0 ) { 1231continue ; 1232} 1233 1234resolvedPath = ` ${ path } / ${ resolvedPath } ` ; 1235resolvedAbsolute = 1236StringPrototypeCharCodeAt ( path , 0 ) === CHAR_FORWARD_SLASH ; 1237} 1238 1239if ( ! resolvedAbsolute ) { 1240const cwd = posixCwd (); 1241resolvedPath = ` ${ cwd } / ${ resolvedPath } ` ; 1242resolvedAbsolute = 1243StringPrototypeCharCodeAt ( 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 1251resolvedPath = normalizeString ( 1252resolvedPath , 1253! resolvedAbsolute , 1254"/" , 1255isPosixPathSeparator , 1256); 1257 1258if ( resolvedAbsolute ) { 1259return `/ ${ resolvedPath } ` ; 1260} 1261return resolvedPath . length > 0 ?resolvedPath :"." ; 1262}, 1263 1264normalize ( path ) { 1265validateString ( path , "path" ); 1266 1267if ( path . length === 0 ) { 1268return "." ; 1269} 1270 1271const isAbsolute = 1272StringPrototypeCharCodeAt ( path , 0 ) === CHAR_FORWARD_SLASH ; 1273const trailingSeparator = 1274StringPrototypeCharCodeAt ( path , path . length - 1 ) === CHAR_FORWARD_SLASH ; 1275 1276// Normalize the path 1277path = normalizeString ( path , ! isAbsolute , "/" , isPosixPathSeparator ); 1278 1279if ( path . length === 0 ) { 1280if ( isAbsolute ) { 1281return "/" ; 1282} 1283return trailingSeparator ?"./" :"." ; 1284} 1285if ( trailingSeparator ) { 1286path += "/" ; 1287} 1288 1289return isAbsolute ?`/ ${ path } ` :path ; 1290}, 1291 1292isAbsolute ( path ) { 1293validateString ( path , "path" ); 1294return path . length > 0 && 1295StringPrototypeCharCodeAt ( path , 0 ) === CHAR_FORWARD_SLASH ; 1296}, 1297 1298join ( ...args ) { 1299if ( args . length === 0 ) { 1300return "." ; 1301} 1302 1303const path :string [] = []; 1304for ( let i = 0 ; i < args . length ; ++ i ) { 1305const arg = args [ i ]; 1306validateString ( arg , "path" ); 1307if ( arg . length > 0 ) { 1308ArrayPrototypePush ( path , arg ); 1309} 1310} 1311 1312if ( path . length === 0 ) { 1313return "." ; 1314} 1315 1316return posix . normalize ( ArrayPrototypeJoin ( path , "/" )); 1317}, 1318 1319relative ( from , to ) { 1320validateString ( from , "from" ); 1321validateString ( to , "to" ); 1322 1323if ( from === to ) { 1324return "" ; 1325} 1326 1327// Trim leading forward slashes. 1328from = posix . resolve ( from ); 1329to = posix . resolve ( to ); 1330 1331if ( from === to ) { 1332return "" ; 1333} 1334 1335const fromStart = 1 ; 1336const fromEnd = from . length ; 1337const fromLen = fromEnd - fromStart ; 1338const toStart = 1 ; 1339const toLen = to . length - toStart ; 1340 1341// Compare paths to find the longest common path from root 1342const length = ( fromLen < toLen ?fromLen :toLen ); 1343let lastCommonSep = - 1 ; 1344let i = 0 ; 1345for (; i < length ; i ++ ) { 1346const fromCode = StringPrototypeCharCodeAt ( from , fromStart + i ); 1347if ( fromCode !== StringPrototypeCharCodeAt ( to , toStart + i )) { 1348break ; 1349} else if ( fromCode === CHAR_FORWARD_SLASH ) { 1350lastCommonSep = i ; 1351} 1352} 1353if ( i === length ) { 1354if ( toLen > length ) { 1355if ( 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' 1358return StringPrototypeSlice ( to , toStart + i + 1 ); 1359} 1360if ( i === 0 ) { 1361// We get here if `from` is the root 1362// For example: from='/'; to='/foo' 1363return StringPrototypeSlice ( to , toStart + i ); 1364} 1365} else if ( fromLen > length ) { 1366if ( 1367StringPrototypeCharCodeAt ( from , fromStart + i ) === 1368CHAR_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' 1372lastCommonSep = i ; 1373} else if ( i === 0 ) { 1374// We get here if `to` is the root. 1375// For example: from='/foo/bar'; to='/' 1376lastCommonSep = 0 ; 1377} 1378} 1379} 1380 1381let out = "" ; 1382// Generate the relative path based on the path difference between `to` 1383// and `from`. 1384for ( i = fromStart + lastCommonSep + 1 ; i <= fromEnd ; ++ i ) { 1385if ( 1386i === fromEnd || 1387StringPrototypeCharCodeAt ( from , i ) === CHAR_FORWARD_SLASH 1388) { 1389out += out . length === 0 ?".." :"/.." ; 1390} 1391} 1392 1393// Lastly, append the rest of the destination (`to`) path that comes after 1394// the common path parts. 1395return ` ${ out } ${ StringPrototypeSlice ( to , toStart + lastCommonSep )} ` ; 1396}, 1397 1398toNamespacedPath ( path ) { 1399// Non-op on posix systems 1400return path ; 1401}, 1402 1403dirname ( path ) { 1404validateString ( path , "path" ); 1405if ( path . length === 0 ) { 1406return "." ; 1407} 1408const hasRoot = StringPrototypeCharCodeAt ( path , 0 ) === CHAR_FORWARD_SLASH ; 1409let end = - 1 ; 1410let matchedSlash = true ; 1411for ( let i = path . length - 1 ; i >= 1 ; -- i ) { 1412if ( StringPrototypeCharCodeAt ( path , i ) === CHAR_FORWARD_SLASH ) { 1413if ( ! matchedSlash ) { 1414end = i ; 1415break ; 1416} 1417} else { 1418// We saw the first non-path separator 1419matchedSlash = false ; 1420} 1421} 1422 1423if ( end === - 1 ) { 1424return hasRoot ?"/" :"." ; 1425} 1426if ( hasRoot && end === 1 ) { 1427return "//" ; 1428} 1429return StringPrototypeSlice ( path , 0 , end ); 1430}, 1431 1432basename ( path , suffix ) { 1433if ( suffix !== undefined ) { 1434validateString ( suffix , "suffix" ); 1435} 1436validateString ( path , "path" ); 1437 1438let start = 0 ; 1439let end = - 1 ; 1440let matchedSlash = true ; 1441 1442if ( suffix !== undefined && suffix . length > 0 && suffix . length <= path . length ) { 1443if ( suffix === path ) { 1444return "" ; 1445} 1446let extIdx = suffix . length - 1 ; 1447let firstNonSlashEnd = - 1 ; 1448for ( let i = path . length - 1 ; i >= 0 ; -- i ) { 1449const code = StringPrototypeCharCodeAt ( path , i ); 1450if ( 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 1453if ( ! matchedSlash ) { 1454start = i + 1 ; 1455break ; 1456} 1457} else { 1458if ( 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 1461matchedSlash = false ; 1462firstNonSlashEnd = i + 1 ; 1463} 1464if ( extIdx >= 0 ) { 1465// Try to match the explicit extension 1466if ( code === StringPrototypeCharCodeAt ( suffix , extIdx )) { 1467if ( -- extIdx === - 1 ) { 1468// We matched the extension, so mark this as the end of our path 1469// component 1470end = i ; 1471} 1472} else { 1473// Extension does not match, so our result is the entire path 1474// component 1475extIdx = - 1 ; 1476end = firstNonSlashEnd ; 1477} 1478} 1479} 1480} 1481 1482if ( start === end ) { 1483end = firstNonSlashEnd ; 1484} else if ( end === - 1 ) { 1485end = path . length ; 1486} 1487return StringPrototypeSlice ( path , start , end ); 1488} 1489for ( let i = path . length - 1 ; i >= 0 ; -- i ) { 1490if ( 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 1493if ( ! matchedSlash ) { 1494start = i + 1 ; 1495break ; 1496} 1497} else if ( end === - 1 ) { 1498// We saw the first non-path separator, mark this as the end of our 1499// path component 1500matchedSlash = false ; 1501end = i + 1 ; 1502} 1503} 1504 1505if ( end === - 1 ) { 1506return "" ; 1507} 1508return StringPrototypeSlice ( path , start , end ); 1509}, 1510 1511extname ( path ) { 1512validateString ( path , "path" ); 1513let startDot = - 1 ; 1514let startPart = 0 ; 1515let end = - 1 ; 1516let matchedSlash = true ; 1517// Track the state of characters (if any) we see before our first dot and 1518// after any path separator we find 1519let preDotState = 0 ; 1520for ( let i = path . length - 1 ; i >= 0 ; -- i ) { 1521const code = StringPrototypeCharCodeAt ( path , i ); 1522if ( 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 1525if ( ! matchedSlash ) { 1526startPart = i + 1 ; 1527break ; 1528} 1529continue ; 1530} 1531if ( end === - 1 ) { 1532// We saw the first non-path separator, mark this as the end of our 1533// extension 1534matchedSlash = false ; 1535end = i + 1 ; 1536} 1537if ( code === CHAR_DOT ) { 1538// If this is our first dot, mark it as the start of our extension 1539if ( startDot === - 1 ) { 1540startDot = i ; 1541} else if ( preDotState !== 1 ) { 1542preDotState = 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 1547preDotState = - 1 ; 1548} 1549} 1550 1551if ( 1552startDot === - 1 || end === - 1 || 1553// We saw a non-dot character immediately before the dot 1554preDotState === 0 || 1555// The (right-most) trimmed path component is exactly '..' 1556( preDotState === 1 && startDot === end - 1 && startDot === startPart + 1 ) 1557) { 1558return "" ; 1559} 1560return StringPrototypeSlice ( path , startDot , end ); 1561}, 1562 1563format ( pathObject ) { 1564return _format ( "/" , pathObject ); 1565}, 1566 1567parse ( path ) { 1568validateString ( path , "path" ); 1569 1570const ret = { root :"" , dir :"" , base :"" , ext :"" , name :"" }; 1571if ( path . length === 0 ) { 1572return ret ; 1573} 1574const isAbsolute = 1575StringPrototypeCharCodeAt ( path , 0 ) === CHAR_FORWARD_SLASH ; 1576let start ; 1577if ( isAbsolute ) { 1578ret . root = "/" ; 1579start = 1 ; 1580} else { 1581start = 0 ; 1582} 1583let startDot = - 1 ; 1584let startPart = 0 ; 1585let end = - 1 ; 1586let matchedSlash = true ; 1587let 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 1591let preDotState = 0 ; 1592 1593// Get non-dir info 1594for (; i >= start ; -- i ) { 1595const code = StringPrototypeCharCodeAt ( path , i ); 1596if ( 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 1599if ( ! matchedSlash ) { 1600startPart = i + 1 ; 1601break ; 1602} 1603continue ; 1604} 1605if ( end === - 1 ) { 1606// We saw the first non-path separator, mark this as the end of our 1607// extension 1608matchedSlash = false ; 1609end = i + 1 ; 1610} 1611if ( code === CHAR_DOT ) { 1612// If this is our first dot, mark it as the start of our extension 1613if ( startDot === - 1 ) { 1614startDot = i ; 1615} else if ( preDotState !== 1 ) { 1616preDotState = 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 1621preDotState = - 1 ; 1622} 1623} 1624 1625if ( end !== - 1 ) { 1626const baseStart = startPart === 0 && isAbsolute ?1 :startPart ; 1627if ( 1628startDot === - 1 || 1629// We saw a non-dot character immediately before the dot 1630preDotState === 0 || 1631// The (right-most) trimmed path component is exactly '..' 1632( preDotState === 1 && startDot === end - 1 && startDot === startPart + 1 ) 1633) { 1634ret . base = ret . name = StringPrototypeSlice ( path , baseStart , end ); 1635} else { 1636ret . name = StringPrototypeSlice ( path , baseStart , startDot ); 1637ret . base = StringPrototypeSlice ( path , baseStart , end ); 1638ret . ext = StringPrototypeSlice ( path , startDot , end ); 1639} 1640} 1641 1642if ( startPart > 0 ) { 1643ret . dir = StringPrototypeSlice ( path , 0 , startPart - 1 ); 1644} else if ( isAbsolute ) { 1645ret . dir = "/" ; 1646} 1647 1648return ret ; 1649}, 1650 1651sep :"/" , 1652delimiter :":" , 1653}; 1654 1655const posixBase = ObjectFreeze ({ 1656join :posix . join , 1657resolve :posix . resolve , 1658normalize :posix . normalize , 1659relative :posix . relative , 1660dirname :posix . dirname , 1661basename :posix . basename , 1662extname :posix . extname , 1663isAbsolute :posix . isAbsolute , 1664parse :posix . parse , 1665format :posix . format , 1666toNamespacedPath :posix . toNamespacedPath , 1667sep :posix . sep , 1668delimiter :posix . delimiter , 1669}); 1670 1671const win32Base = ObjectFreeze ({ 1672join :win32 . join , 1673resolve :win32 . resolve , 1674normalize :win32 . normalize , 1675relative :win32 . relative , 1676dirname :win32 . dirname , 1677basename :win32 . basename , 1678extname :win32 . extname , 1679isAbsolute :win32 . isAbsolute , 1680parse :win32 . parse , 1681format :win32 . format , 1682toNamespacedPath :win32 . toNamespacedPath , 1683sep :win32 . sep , 1684delimiter :win32 . delimiter , 1685}); 1686 1687export { posixBase , win32Base }; 1688export { splitOnChar , validateString };