From 8424c4d1ab48cbf278f9a3057cb1de2e417a1f84 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 27 Apr 2018 13:58:45 -0700 Subject: [PATCH] Partial migration of some shared vpath functionality to core --- src/compiler/core.ts | 128 ++++++++++++++++---------- src/compiler/moduleNameResolver.ts | 2 +- src/harness/vpath.ts | 142 +++-------------------------- src/services/utilities.ts | 5 - 4 files changed, 96 insertions(+), 181 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index e13ec4537a2..78d80ab8ade 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1928,6 +1928,9 @@ namespace ts { return text1 ? Comparison.GreaterThan : Comparison.LessThan; } + /** + * Normalize path separators. + */ export function normalizeSlashes(path: string): string { return path.replace(/\\/g, "/"); } @@ -1968,27 +1971,6 @@ namespace ts { * we expect the host to correctly handle paths in our specified format. */ export const directorySeparator = "/"; - const directorySeparatorCharCode = CharacterCodes.slash; - function getNormalizedParts(normalizedSlashedPath: string, rootLength: number): string[] { - const parts = normalizedSlashedPath.substr(rootLength).split(directorySeparator); - const normalized: string[] = []; - for (const part of parts) { - if (part !== ".") { - if (part === ".." && normalized.length > 0 && lastOrUndefined(normalized) !== "..") { - normalized.pop(); - } - else { - // A part may be an empty string (which is 'falsy') if the path had consecutive slashes, - // e.g. "path//file.ts". Drop these before re-joining the parts. - if (part) { - normalized.push(part); - } - } - } - } - - return normalized; - } export function normalizePath(path: string): string { return normalizePathAndParts(path).path; @@ -1996,23 +1978,16 @@ namespace ts { export function normalizePathAndParts(path: string): { path: string, parts: string[] } { path = normalizeSlashes(path); - const rootLength = getRootLength(path); - const root = path.substr(0, rootLength); - const parts = getNormalizedParts(path, rootLength); + const [root, ...parts] = reducePathComponents(getPathComponents(path)); if (parts.length) { const joinedParts = root + parts.join(directorySeparator); - return { path: pathEndsWithDirectorySeparator(path) ? joinedParts + directorySeparator : joinedParts, parts }; + return { path: hasTrailingDirectorySeparator(path) ? joinedParts + directorySeparator : joinedParts, parts }; } else { return { path: root, parts }; } } - /** A path ending with '/' refers to a directory only, never a file. */ - export function pathEndsWithDirectorySeparator(path: string): boolean { - return path.charCodeAt(path.length - 1) === directorySeparatorCharCode; - } - /** * Returns the path except for its basename. Eg: * @@ -2085,41 +2060,88 @@ namespace ts { return true; } + /** + * Determines whether a path is an absolute path (e.g. starts with `/`, or a dos path + * like `c:`, `c:\` or `c:/`). + */ export function isRootedDiskPath(path: string) { return path && getRootLength(path) !== 0; } + /** + * Determines whether a path consists only of a path root. + */ + export function isDiskPathRoot(path: string) { + const rootLength = getRootLength(path); + return rootLength > 0 && rootLength === path.length; + } + export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string { return !isRootedDiskPath(absoluteOrRelativePath) ? absoluteOrRelativePath : getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); } - function normalizedPathComponents(path: string, rootLength: number) { - const normalizedParts = getNormalizedParts(path, rootLength); - return [path.substr(0, rootLength)].concat(normalizedParts); + function pathComponents(path: string, rootLength: number) { + const root = path.substring(0, rootLength); + const rest = path.substring(rootLength).split(directorySeparator); + if (rest.length && !lastOrUndefined(rest)) rest.pop(); + return [root, ...rest]; } - export function getNormalizedPathComponents(path: string, currentDirectory: string) { - path = normalizeSlashes(path); - let rootLength = getRootLength(path); - if (rootLength === 0) { - // If the path is not rooted it is relative to current directory - path = combinePaths(normalizeSlashes(currentDirectory), path); - rootLength = getRootLength(path); - } + /** + * Parse a path into an array containing a root component (at index 0) and zero or more path + * components (at indices > 0). The result is not normalized. + * If the path is relative, the root component is `""`. + * If the path is absolute, the root component includes the first path separator (`/`). + */ + export function getPathComponents(path: string, currentDirectory = "") { + path = combinePaths(currentDirectory, path); + const rootLength = getRootLength(path); + return pathComponents(path, rootLength); + } - return normalizedPathComponents(path, rootLength); + export function reducePathComponents(components: ReadonlyArray) { + const reduced = [components[0]]; + for (let i = 1; i < components.length; i++) { + const component = components[i]; + if (component === ".") continue; + if (component === "..") { + if (reduced.length > 1) { + if (reduced[reduced.length - 1] !== "..") { + reduced.pop(); + continue; + } + } + else if (reduced[0]) continue; + } + reduced.push(component); + } + return reduced; + } + + /** + * Parse a path into an array containing a root component (at index 0) and zero or more path + * components (at indices > 0). The result is normalized. + * If the path is relative, the root component is `""`. + * If the path is absolute, the root component includes the first path separator (`/`). + */ + export function getNormalizedPathComponents(path: string, currentDirectory: string) { + return reducePathComponents(getPathComponents(path, currentDirectory)); } export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string) { return getNormalizedPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); } + /** + * Formats a parsed path consisting of a root component and zero or more path segments. + */ export function getNormalizedPathFromPathComponents(pathComponents: ReadonlyArray) { if (pathComponents && pathComponents.length) { return pathComponents[0] + pathComponents.slice(1).join(directorySeparator); } + return ""; } function getNormalizedPathComponentsOfUrl(url: string) { @@ -2153,7 +2175,7 @@ namespace ts { // Found the "/" after the website.com so the root is length of http://www.website.com/ // and get components after the root normally like any other folder components rootLength = indexOfNextSlash + 1; - return normalizedPathComponents(url, rootLength); + return reducePathComponents(pathComponents(url, rootLength)); } else { // Can't find the host assume the rest of the string as component @@ -2229,14 +2251,28 @@ namespace ts { return i < 0 ? path : path.substring(i + 1); } + /** + * Combines two paths. If a path is absolute, it replaces any previous path. + */ export function combinePaths(path1: string, path2: string): string { + if (path1) path1 = normalizeSlashes(path1); + if (path2) path2 = normalizeSlashes(path2); if (!(path1 && path1.length)) return path2; if (!(path2 && path2.length)) return path1; if (getRootLength(path2) !== 0) return path2; - if (path1.charAt(path1.length - 1) === directorySeparator) return path1 + path2; + if (hasTrailingDirectorySeparator(path1)) return path1 + path2; return path1 + directorySeparator + path2; } + /** + * Determines whether a path has a trailing separator (`/` or `\\`). + */ + export function hasTrailingDirectorySeparator(path: string) { + if (path.length === 0) return false; + const ch = path.charCodeAt(path.length - 1); + return ch === CharacterCodes.slash || ch === CharacterCodes.backslash; + } + /** * Removes a trailing directory separator from a path. * @param path The path. @@ -2244,7 +2280,7 @@ namespace ts { export function removeTrailingDirectorySeparator(path: Path): Path; export function removeTrailingDirectorySeparator(path: string): string; export function removeTrailingDirectorySeparator(path: string) { - if (path.charAt(path.length - 1) === directorySeparator) { + if (hasTrailingDirectorySeparator(path)) { return path.substr(0, path.length - 1); } @@ -2258,7 +2294,7 @@ namespace ts { export function ensureTrailingDirectorySeparator(path: Path): Path; export function ensureTrailingDirectorySeparator(path: string): string; export function ensureTrailingDirectorySeparator(path: string) { - if (path.charAt(path.length - 1) !== directorySeparator) { + if (!hasTrailingDirectorySeparator(path)) { return path + directorySeparator; } diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 81aed522490..9ede13888e8 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -806,7 +806,7 @@ namespace ts { if (state.traceEnabled) { trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]); } - if (!pathEndsWithDirectorySeparator(candidate)) { + if (!hasTrailingDirectorySeparator(candidate)) { if (!onlyRecordFailures) { const parentOfCandidate = getDirectoryPath(candidate); if (!directoryProbablyExists(parentOfCandidate, state.host)) { diff --git a/src/harness/vpath.ts b/src/harness/vpath.ts index 02675ccd95a..b6f4065d854 100644 --- a/src/harness/vpath.ts +++ b/src/harness/vpath.ts @@ -1,17 +1,4 @@ namespace vpath { - /** - * Virtual path separator. - */ - export import sep = ts.directorySeparator; - - /** - * Normalize path separators. - */ - export import normalizeSeparators = ts.normalizeSlashes; - // export function normalizeSeparators(path: string): string { - // return ts.normalizeSlashes(path); - // } - const invalidRootComponentRegExp = /^(?!(\/|\/\/\w+\/|[a-zA-Z]:\/?|)$)/; const invalidNavigableComponentRegExp = /[:*?"<>|]/; const invalidNonNavigableComponentRegExp = /^\.{1,2}$|[:*?"<>|]/; @@ -93,103 +80,26 @@ namespace vpath { return true; } - const absolutePathRegExp = /^[\\/]([\\/](.*?[\\/](.*?[\\/])?)?)?|^[a-zA-Z]:[\\/]?|^\w+:\/{2}[^\\/]*\/?/; + import getRootLength = ts.getRootLength; - // NOTE: this differs from `ts.getRootLength` in that it doesn't support URIs. - function getRootLength(path: string) { - const match = absolutePathRegExp.exec(path); - return match ? match[0].length : 0; - } - - /** - * Determines whether a path is an absolute path (e.g. starts with `/`, `\\`, or a dos path - * like `c:`). - */ + export import sep = ts.directorySeparator; + export import normalizeSeparators = ts.normalizeSlashes; export import isAbsolute = ts.isRootedDiskPath; - // export function isAbsolute(path: string) { - // return absolutePathRegExp.test(path); - // } - - /** - * Determines whether a path consists only of a path root. - */ - export function isRoot(path: string) { - const rootLength = getRootLength(path); - return rootLength > 0 && rootLength === path.length; - } - - const trailingSeperatorRegExp = /[\\/]$/; - - /** - * Determines whether a path has a trailing separator (`/`). - */ - export function hasTrailingSeparator(path: string) { - return trailingSeperatorRegExp.test(path) && !isRoot(path); - } - - /** - * Adds a trailing separator (`/`) to a path if it doesn't have one. - */ + export import isRoot = ts.isDiskPathRoot; + export import hasTrailingSeparator = ts.hasTrailingDirectorySeparator; export import addTrailingSeparator = ts.ensureTrailingDirectorySeparator; - // export function addTrailingSeparator(path: string) { - // return !trailingSeperatorRegExp.test(path) && path ? path + "/" : path; - // } - - /** - * Removes a trailing separator (`/`) from a path if it has one. - */ export import removeTrailingSeparator = ts.removeTrailingDirectorySeparator; - // export function removeTrailingSeparator(path: string) { - // return trailingSeperatorRegExp.test(path) && !isRoot(path) ? path.slice(0, -1) : path; - // } - - function reduce(components: ReadonlyArray) { - const normalized = [components[0]]; - for (let i = 1; i < components.length; i++) { - const component = components[i]; - if (component === ".") continue; - if (component === "..") { - if (normalized.length > 1) { - if (normalized[normalized.length - 1] !== "..") { - normalized.pop(); - continue; - } - } - else if (normalized[0]) continue; - } - normalized.push(component); - } - return normalized; - } + export import normalize = ts.normalizePath; + export import combine = ts.combinePaths; + export import parse = ts.getPathComponents; + export import reduce = ts.reducePathComponents; + export import format = ts.getNormalizedPathFromPathComponents; /** - * Normalize a path containing path traversal components (`.` or `..`). + * Combines and normalizes two paths. */ - export function normalize(path: string): string { - const components = reduce(parse(path)); - return components.length > 1 && hasTrailingSeparator(path) ? format(components) + sep : format(components); - } - - /** - * Combines two or more paths. If a path is absolute, it replaces any previous path. - */ - export function combine(path: string, ...paths: string[]) { - path = normalizeSeparators(path); - for (const name of paths) { - path = ts.combinePaths(path, normalizeSeparators(name)); - // name = normalizeSeparators(name); - // if (name.length === 0) continue; - // path = path.length === 0 || isAbsolute(name) ? name : - // addTrailingSeparator(path) + name; - } - return path; - } - - /** - * Combines and normalizes two or more paths. - */ - export function resolve(path: string, ...paths: string[]) { - return normalize(combine(path, ...paths)); + export function resolve(path1: string, path2: string) { + return normalize(combine(path1, path2)); } // NOTE: this differs from `ts.getRelativePathToDirectoryOrUrl` in that it requires both paths @@ -322,32 +232,6 @@ namespace vpath { return ignoreCase ? beneathCaseInsensitive(ancestor, descendant) : beneathCaseSensitive(ancestor, descendant); } - /** - * Parse a path into a root component and zero or more path segments. - * If the path is relative, the root component is `""`. - * If the path is absolute, the root component includes the first path separator (`/`). - */ - // NOTE: this differs from `ts.getNormalizedPathComponents` due to the fact that `parse` does - // not automatically normalize relative paths and does not perform path normalization. This is - // necessary to support proper path navigation in `vfs`. - export function parse(path: string) { - path = normalizeSeparators(path); - const rootLength = getRootLength(path); - const root = path.substring(0, rootLength); - const rest = path.substring(rootLength).split(/\/+/g); - if (rest.length && !rest[rest.length - 1]) rest.pop(); - return [root, ...rest.map(component => component.trim())]; - } - - /** - * Formats a parsed path consisting of a root component and zero or more path segments. - */ - // NOTE: this differs from `ts.getNormalizedPathFromPathComponents` in that this function - // always returns a string. - export function format(components: ReadonlyArray) { - return components.length ? components[0] + components.slice(1).join(sep) : ""; - } - /** * Gets the parent directory name of a path. */ diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 78f18869b37..0c14599d8bc 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1122,11 +1122,6 @@ namespace ts { return false; } - export function hasTrailingDirectorySeparator(path: string) { - const lastCharacter = path.charAt(path.length - 1); - return lastCharacter === "/" || lastCharacter === "\\"; - } - export function isInReferenceComment(sourceFile: SourceFile, position: number): boolean { return isInComment(sourceFile, position, /*tokenAtPosition*/ undefined, c => { const commentText = sourceFile.text.substring(c.pos, c.end);