@@ -1928,6 +1928,9 @@ namespace ts {
19281928 return text1 ? Comparison . GreaterThan : Comparison . LessThan ;
19291929 }
19301930
1931+ /**
1932+ * Normalize path separators.
1933+ */
19311934 export function normalizeSlashes ( path : string ) : string {
19321935 return path . replace ( / \\ / g, "/" ) ;
19331936 }
@@ -1968,51 +1971,23 @@ namespace ts {
19681971 * we expect the host to correctly handle paths in our specified format.
19691972 */
19701973 export const directorySeparator = "/" ;
1971- const directorySeparatorCharCode = CharacterCodes . slash ;
1972- function getNormalizedParts ( normalizedSlashedPath : string , rootLength : number ) : string [ ] {
1973- const parts = normalizedSlashedPath . substr ( rootLength ) . split ( directorySeparator ) ;
1974- const normalized : string [ ] = [ ] ;
1975- for ( const part of parts ) {
1976- if ( part !== "." ) {
1977- if ( part === ".." && normalized . length > 0 && lastOrUndefined ( normalized ) !== ".." ) {
1978- normalized . pop ( ) ;
1979- }
1980- else {
1981- // A part may be an empty string (which is 'falsy') if the path had consecutive slashes,
1982- // e.g. "path//file.ts". Drop these before re-joining the parts.
1983- if ( part ) {
1984- normalized . push ( part ) ;
1985- }
1986- }
1987- }
1988- }
1989-
1990- return normalized ;
1991- }
19921974
19931975 export function normalizePath ( path : string ) : string {
19941976 return normalizePathAndParts ( path ) . path ;
19951977 }
19961978
19971979 export function normalizePathAndParts ( path : string ) : { path : string , parts : string [ ] } {
19981980 path = normalizeSlashes ( path ) ;
1999- const rootLength = getRootLength ( path ) ;
2000- const root = path . substr ( 0 , rootLength ) ;
2001- const parts = getNormalizedParts ( path , rootLength ) ;
1981+ const [ root , ...parts ] = reducePathComponents ( getPathComponents ( path ) ) ;
20021982 if ( parts . length ) {
20031983 const joinedParts = root + parts . join ( directorySeparator ) ;
2004- return { path : pathEndsWithDirectorySeparator ( path ) ? joinedParts + directorySeparator : joinedParts , parts } ;
1984+ return { path : hasTrailingDirectorySeparator ( path ) ? joinedParts + directorySeparator : joinedParts , parts } ;
20051985 }
20061986 else {
20071987 return { path : root , parts } ;
20081988 }
20091989 }
20101990
2011- /** A path ending with '/' refers to a directory only, never a file. */
2012- export function pathEndsWithDirectorySeparator ( path : string ) : boolean {
2013- return path . charCodeAt ( path . length - 1 ) === directorySeparatorCharCode ;
2014- }
2015-
20161991 /**
20171992 * Returns the path except for its basename. Eg:
20181993 *
@@ -2085,41 +2060,88 @@ namespace ts {
20852060 return true ;
20862061 }
20872062
2063+ /**
2064+ * Determines whether a path is an absolute path (e.g. starts with `/`, or a dos path
2065+ * like `c:`, `c:\` or `c:/`).
2066+ */
20882067 export function isRootedDiskPath ( path : string ) {
20892068 return path && getRootLength ( path ) !== 0 ;
20902069 }
20912070
2071+ /**
2072+ * Determines whether a path consists only of a path root.
2073+ */
2074+ export function isDiskPathRoot ( path : string ) {
2075+ const rootLength = getRootLength ( path ) ;
2076+ return rootLength > 0 && rootLength === path . length ;
2077+ }
2078+
20922079 export function convertToRelativePath ( absoluteOrRelativePath : string , basePath : string , getCanonicalFileName : ( path : string ) => string ) : string {
20932080 return ! isRootedDiskPath ( absoluteOrRelativePath )
20942081 ? absoluteOrRelativePath
20952082 : getRelativePathToDirectoryOrUrl ( basePath , absoluteOrRelativePath , basePath , getCanonicalFileName , /*isAbsolutePathAnUrl*/ false ) ;
20962083 }
20972084
2098- function normalizedPathComponents ( path : string , rootLength : number ) {
2099- const normalizedParts = getNormalizedParts ( path , rootLength ) ;
2100- return [ path . substr ( 0 , rootLength ) ] . concat ( normalizedParts ) ;
2085+ function pathComponents ( path : string , rootLength : number ) {
2086+ const root = path . substring ( 0 , rootLength ) ;
2087+ const rest = path . substring ( rootLength ) . split ( directorySeparator ) ;
2088+ if ( rest . length && ! lastOrUndefined ( rest ) ) rest . pop ( ) ;
2089+ return [ root , ...rest ] ;
21012090 }
21022091
2103- export function getNormalizedPathComponents ( path : string , currentDirectory : string ) {
2104- path = normalizeSlashes ( path ) ;
2105- let rootLength = getRootLength ( path ) ;
2106- if ( rootLength === 0 ) {
2107- // If the path is not rooted it is relative to current directory
2108- path = combinePaths ( normalizeSlashes ( currentDirectory ) , path ) ;
2109- rootLength = getRootLength ( path ) ;
2092+ /**
2093+ * Parse a path into an array containing a root component (at index 0) and zero or more path
2094+ * components (at indices > 0). The result is not normalized.
2095+ * If the path is relative, the root component is `""`.
2096+ * If the path is absolute, the root component includes the first path separator (`/`).
2097+ */
2098+ export function getPathComponents ( path : string , currentDirectory = "" ) {
2099+ path = combinePaths ( currentDirectory , path ) ;
2100+ const rootLength = getRootLength ( path ) ;
2101+ return pathComponents ( path , rootLength ) ;
2102+ }
2103+
2104+ export function reducePathComponents ( components : ReadonlyArray < string > ) {
2105+ const reduced = [ components [ 0 ] ] ;
2106+ for ( let i = 1 ; i < components . length ; i ++ ) {
2107+ const component = components [ i ] ;
2108+ if ( component === "." ) continue ;
2109+ if ( component === ".." ) {
2110+ if ( reduced . length > 1 ) {
2111+ if ( reduced [ reduced . length - 1 ] !== ".." ) {
2112+ reduced . pop ( ) ;
2113+ continue ;
2114+ }
2115+ }
2116+ else if ( reduced [ 0 ] ) continue ;
2117+ }
2118+ reduced . push ( component ) ;
21102119 }
2120+ return reduced ;
2121+ }
21112122
2112- return normalizedPathComponents ( path , rootLength ) ;
2123+ /**
2124+ * Parse a path into an array containing a root component (at index 0) and zero or more path
2125+ * components (at indices > 0). The result is normalized.
2126+ * If the path is relative, the root component is `""`.
2127+ * If the path is absolute, the root component includes the first path separator (`/`).
2128+ */
2129+ export function getNormalizedPathComponents ( path : string , currentDirectory : string ) {
2130+ return reducePathComponents ( getPathComponents ( path , currentDirectory ) ) ;
21132131 }
21142132
21152133 export function getNormalizedAbsolutePath ( fileName : string , currentDirectory : string ) {
21162134 return getNormalizedPathFromPathComponents ( getNormalizedPathComponents ( fileName , currentDirectory ) ) ;
21172135 }
21182136
2137+ /**
2138+ * Formats a parsed path consisting of a root component and zero or more path segments.
2139+ */
21192140 export function getNormalizedPathFromPathComponents ( pathComponents : ReadonlyArray < string > ) {
21202141 if ( pathComponents && pathComponents . length ) {
21212142 return pathComponents [ 0 ] + pathComponents . slice ( 1 ) . join ( directorySeparator ) ;
21222143 }
2144+ return "" ;
21232145 }
21242146
21252147 function getNormalizedPathComponentsOfUrl ( url : string ) {
@@ -2153,7 +2175,7 @@ namespace ts {
21532175 // Found the "/" after the website.com so the root is length of http://www.website.com/
21542176 // and get components after the root normally like any other folder components
21552177 rootLength = indexOfNextSlash + 1 ;
2156- return normalizedPathComponents ( url , rootLength ) ;
2178+ return reducePathComponents ( pathComponents ( url , rootLength ) ) ;
21572179 }
21582180 else {
21592181 // Can't find the host assume the rest of the string as component
@@ -2229,22 +2251,36 @@ namespace ts {
22292251 return i < 0 ? path : path . substring ( i + 1 ) ;
22302252 }
22312253
2254+ /**
2255+ * Combines two paths. If a path is absolute, it replaces any previous path.
2256+ */
22322257 export function combinePaths ( path1 : string , path2 : string ) : string {
2258+ if ( path1 ) path1 = normalizeSlashes ( path1 ) ;
2259+ if ( path2 ) path2 = normalizeSlashes ( path2 ) ;
22332260 if ( ! ( path1 && path1 . length ) ) return path2 ;
22342261 if ( ! ( path2 && path2 . length ) ) return path1 ;
22352262 if ( getRootLength ( path2 ) !== 0 ) return path2 ;
2236- if ( path1 . charAt ( path1 . length - 1 ) === directorySeparator ) return path1 + path2 ;
2263+ if ( hasTrailingDirectorySeparator ( path1 ) ) return path1 + path2 ;
22372264 return path1 + directorySeparator + path2 ;
22382265 }
22392266
2267+ /**
2268+ * Determines whether a path has a trailing separator (`/` or `\\`).
2269+ */
2270+ export function hasTrailingDirectorySeparator ( path : string ) {
2271+ if ( path . length === 0 ) return false ;
2272+ const ch = path . charCodeAt ( path . length - 1 ) ;
2273+ return ch === CharacterCodes . slash || ch === CharacterCodes . backslash ;
2274+ }
2275+
22402276 /**
22412277 * Removes a trailing directory separator from a path.
22422278 * @param path The path.
22432279 */
22442280 export function removeTrailingDirectorySeparator ( path : Path ) : Path ;
22452281 export function removeTrailingDirectorySeparator ( path : string ) : string ;
22462282 export function removeTrailingDirectorySeparator ( path : string ) {
2247- if ( path . charAt ( path . length - 1 ) === directorySeparator ) {
2283+ if ( hasTrailingDirectorySeparator ( path ) ) {
22482284 return path . substr ( 0 , path . length - 1 ) ;
22492285 }
22502286
@@ -2258,7 +2294,7 @@ namespace ts {
22582294 export function ensureTrailingDirectorySeparator ( path : Path ) : Path ;
22592295 export function ensureTrailingDirectorySeparator ( path : string ) : string ;
22602296 export function ensureTrailingDirectorySeparator ( path : string ) {
2261- if ( path . charAt ( path . length - 1 ) !== directorySeparator ) {
2297+ if ( ! hasTrailingDirectorySeparator ( path ) ) {
22622298 return path + directorySeparator ;
22632299 }
22642300
0 commit comments