/* eslint-disable no-param-reassign */ /* eslint-disable no-continue */ const splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^/]+?|)(\.[^./]*|))(?:[/]*)$/; function splitPath(filename: string) { return splitPathRe.exec(filename).slice(1); } // resolves . and .. elements in a path array with directory names there // must be no slashes or device names (c:\) in the array // (so also no leading and trailing slashes - it does not distinguish // relative and absolute paths) function normalizeArray(parts: string[], allowAboveRoot?: boolean) { const res = []; for (let i = 0; i < parts.length; i += 1) { const p = parts[i]; // ignore empty parts if (!p || p === '.') continue; // eslint-disable-line no-continue if (p === '..') { if (res.length && res[res.length - 1] !== '..') { res.pop(); } else if (allowAboveRoot) { res.push('..'); } } else { res.push(p); } } return res; } export function isAbsolute(path: string) { return path.charAt(0) === '/'; } export function normalize(path: string) { const isAbs = isAbsolute(path); const trailingSlash = path && path[path.length - 1] === '/'; let newPath = path; // Normalize the path newPath = normalizeArray(newPath.split('/'), !isAbs).join('/'); if (!newPath && !isAbs) { newPath = '.'; } if (newPath && trailingSlash) { newPath += '/'; } return (isAbs ? '/' : '') + newPath; } export function join(...paths: Array) { let path = ''; for (let i = 0; i < paths.length; i += 1) { const segment = paths[i]; if (typeof segment !== 'string') { throw new TypeError('Arguments to path.join must be strings'); } if (segment) { if (!path) { path += segment; } else { path += `/${segment}`; } } } return normalize(path); } export function dirname(path: string) { const result = splitPath(path); const root = result[0]; let dir = result[1]; if (!root && !dir) { // No dirname whatsoever return '.'; } if (dir) { // It has a dirname, strip trailing slash dir = dir.substr(0, dir.length - 1); } return root + dir; } export function basename(p: string, ext: string = '') { // Special case: Normalize will modify this to '.' if (p === '') { return p; } // Normalize the string first to remove any weirdness. const path = normalize(p); // Get the last part of the string. const sections = path.split('/'); const lastPart = sections[sections.length - 1]; // Special case: If it's empty, then we have a string like so: foo/ // Meaning, 'foo' is guaranteed to be a directory. if (lastPart === '' && sections.length > 1) { return sections[sections.length - 2]; } // Remove the extension, if need be. if (ext.length > 0) { const lastPartExt = lastPart.substr(lastPart.length - ext.length); if (lastPartExt === ext) { return lastPart.substr(0, lastPart.length - ext.length); } } return lastPart; } export function absolute(path: string) { if (path.startsWith('/')) { return path; } if (path.startsWith('./')) { return path.replace('./', '/'); } return '/' + path; } function assertPath(path) { if (typeof path !== 'string') { throw new TypeError( 'Path must be a string. Received ' + JSON.stringify(path) ); } } export function extname(path: string): string { assertPath(path); let startDot = -1; let startPart = 0; let end = -1; let matchedSlash = true; // Track the state of characters (if any) we see before our first dot and // after any path separator we find let preDotState = 0; for (let i = path.length - 1; i >= 0; --i) { const code = path.charCodeAt(i); if (code === 47) { // If we reached a path separator that was not part of a set of path // separators at the end of the string, stop now if (!matchedSlash) { startPart = i + 1; break; } // eslint-disable-next-line continue; } if (end === -1) { // We saw the first non-path separator, mark this as the end of our // extension matchedSlash = false; end = i + 1; } if (code === 46 /* . */) { // If this is our first dot, mark it as the start of our extension if (startDot === -1) startDot = i; else if (preDotState !== 1) preDotState = 1; } else if (startDot !== -1) { // We saw a non-dot and non-path separator before our dot, so we should // have a good chance at having a non-empty extension preDotState = -1; } } if ( startDot === -1 || end === -1 || // We saw a non-dot character immediately before the dot preDotState === 0 || // The (right-most) trimmed path component is exactly '..' (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) ) { return ''; } return path.slice(startDot, end); } export function resolve(...args) { let resolvedPath = ''; let resolvedAbsolute = false; for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) { const path = i >= 0 ? args[i] : process.cwd(); // Skip empty and invalid entries if (typeof path !== 'string') { throw new TypeError('Arguments to path.resolve must be strings'); } else if (!path) { continue; } resolvedPath = path + '/' + resolvedPath; resolvedAbsolute = path[0] === '/'; } // At this point the path should be resolved to a full absolute path, but // handle relative paths to be safe (might happen when process.cwd() fails) // Normalize the path resolvedPath = normalizeArray( resolvedPath.split('/'), !resolvedAbsolute ).join('/'); return (resolvedAbsolute ? '/' : '') + resolvedPath || '.'; } function trimArray(arr) { const lastIndex = arr.length - 1; let start = 0; for (; start <= lastIndex; start++) { if (arr[start]) break; } let end = lastIndex; for (; end >= 0; end--) { if (arr[end]) break; } if (start === 0 && end === lastIndex) return arr; if (start > end) return []; return arr.slice(start, end + 1); } export function relative(from: string, to: string) { from = resolve(from).substr(1); to = resolve(to).substr(1); const fromParts = trimArray(from.split('/')); const toParts = trimArray(to.split('/')); const length = Math.min(fromParts.length, toParts.length); let samePartsLength = length; for (let i = 0; i < length; i++) { if (fromParts[i] !== toParts[i]) { samePartsLength = i; break; } } let outputParts = []; for (let i = samePartsLength; i < fromParts.length; i++) { outputParts.push('..'); } outputParts = outputParts.concat(toParts.slice(samePartsLength)); return outputParts.join('/'); }