UNPKG

typescript

Version:

TypeScript is a language for application scale JavaScript development

884 lines • 34 kB
import { CaseSensitivity } from "../../enums/caseSensitivity.js"; export { CaseSensitivity } from "../../enums/caseSensitivity.js"; const CharacterCodesSlash = "/".charCodeAt(0); const CharacterCodesBackslash = "\\".charCodeAt(0); const CharacterCodesColon = ":".charCodeAt(0); const CharacterCodesPercent = "%".charCodeAt(0); const CharacterCodesCaret = "^".charCodeAt(0); const CharacterCodes3 = "3".charCodeAt(0); const CharacterCodesa = "a".charCodeAt(0); const CharacterCodesz = "z".charCodeAt(0); const CharacterCodesA = "A".charCodeAt(0); const CharacterCodesZ = "Z".charCodeAt(0); const CharacterCodesDot = ".".charCodeAt(0); const directorySeparator = "/"; const altDirectorySeparator = "\\"; const urlSchemeSeparator = "://"; const backslashRegExp = /\\/g; // Preserve U+0130 as case-sensitive while avoiding work for common lowercase paths. const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_. ]+/g; // check path for these segments: '', '.'. '..' const relativePathSegmentRegExp = /\/\/|(?:^|\/)\.\.?(?:$|\/)/; /** * Determines whether a charCode corresponds to `/` or `\\`. */ function isAnyDirectorySeparator(charCode) { return charCode === CharacterCodesSlash || charCode === CharacterCodesBackslash; } function isVolumeCharacter(charCode) { return (charCode >= CharacterCodesa && charCode <= CharacterCodesz) || (charCode >= CharacterCodesA && charCode <= CharacterCodesZ); } function getFileUrlVolumeSeparatorEnd(url, start) { const ch0 = url.charCodeAt(start); if (ch0 === CharacterCodesColon) return start + 1; if (ch0 === CharacterCodesPercent && url.charCodeAt(start + 1) === CharacterCodes3) { const ch2 = url.charCodeAt(start + 2); if (ch2 === CharacterCodesa || ch2 === CharacterCodesA) return start + 3; } return -1; } /** * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). * * For example: * ```ts * getRootLength("a") === 0 // "" * getRootLength("/") === 1 // "/" * getRootLength("c:") === 2 // "c:" * getRootLength("c:d") === 0 // "" * getRootLength("c:/") === 3 // "c:/" * getRootLength("c:\\") === 3 // "c:\\" * getRootLength("//server") === 7 // "//server" * getRootLength("//server/share") === 8 // "//server/" * getRootLength("\\\\server") === 7 // "\\\\server" * getRootLength("\\\\server\\share") === 8 // "\\\\server\\" * getRootLength("file:///path") === 8 // "file:///" * getRootLength("file:///c:") === 10 // "file:///c:" * getRootLength("file:///c:d") === 8 // "file:///" * getRootLength("file:///c:/path") === 11 // "file:///c:/" * getRootLength("file://server") === 13 // "file://server" * getRootLength("file://server/path") === 14 // "file://server/" * getRootLength("http://server") === 13 // "http://server" * getRootLength("http://server/path") === 14 // "http://server/" * getRootLength("^/untitled/ts-nul-authority/Untitled-1") === 2 // "^/" * ``` * * @internal */ export function getRootLength(path) { const rootLength = getEncodedRootLength(path); return rootLength < 0 ? ~rootLength : rootLength; } /** * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). * If the root is part of a URL, the twos-complement of the root length is returned. */ function getEncodedRootLength(path) { if (!path) return 0; const ch0 = path.charCodeAt(0); // POSIX or UNC if (ch0 === CharacterCodesSlash || ch0 === CharacterCodesBackslash) { if (path.charCodeAt(1) !== ch0) return 1; // POSIX: "/" (or non-normalized "\") const p1 = path.indexOf(ch0 === CharacterCodesSlash ? directorySeparator : altDirectorySeparator, 2); if (p1 < 0) return path.length; // UNC: "//server" or "\\server" return p1 + 1; // UNC: "//server/" or "\\server\" } // Dynamic/virtual compiler file name. if (ch0 === CharacterCodesCaret && path.charCodeAt(1) === CharacterCodesSlash) { if (path.startsWith(dynamicURIFileNamePrefix)) { const schemeEnd = path.indexOf(directorySeparator, dynamicURIFileNamePrefix.length); if (schemeEnd !== -1) { const authorityEnd = path.indexOf(directorySeparator, schemeEnd + 1); if (authorityEnd !== -1) { return authorityEnd + 1; } return ~path.length; } } return 2; } // DOS if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodesColon) { const ch2 = path.charCodeAt(2); if (ch2 === CharacterCodesSlash || ch2 === CharacterCodesBackslash) return 3; // DOS: "c:/" or "c:\" if (path.length === 2) return 2; // DOS: "c:" (but not "c:d") } if (isDynamicFileName(path)) return 2; // URL const schemeEnd = path.indexOf(urlSchemeSeparator); if (schemeEnd !== -1) { const authorityStart = schemeEnd + urlSchemeSeparator.length; const authorityEnd = path.indexOf(directorySeparator, authorityStart); if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path" // For local "file" URLs, include the leading DOS volume (if present). // Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a // special case interpreted as "the machine from which the URL is being interpreted". const scheme = path.slice(0, schemeEnd); const authority = path.slice(authorityStart, authorityEnd); if (scheme.toLowerCase() === "file" && (authority === "" || authority.toLowerCase() === "localhost") && isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) { const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2); if (volumeSeparatorEnd !== -1) { if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodesSlash) { // URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/" return ~(volumeSeparatorEnd + 1); } if (volumeSeparatorEnd === path.length) { // URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a" // but not "file:///c:d" or "file:///c%3ad" return ~volumeSeparatorEnd; } } } return ~(authorityEnd + 1); // URL: "file://server/", "http://server/" } return ~path.length; // URL: "file://server", "http://server" } // relative return 0; } export function getPathComponents(path) { return pathComponents(path, getRootLength(path)); } function pathComponents(path, rootLength) { const root = path.substring(0, rootLength); const rest = path.substring(rootLength).split("/"); if (rest.length && !lastOrUndefined(rest)) rest.pop(); return [root, ...rest]; } function lastOrUndefined(array) { return array.length ? array[array.length - 1] : undefined; } /** * Determines whether a path has a trailing separator (`/` or `\\`). */ export function hasTrailingDirectorySeparator(path) { return path.length > 0 && isAnyDirectorySeparator(path.charCodeAt(path.length - 1)); } /** * Removes a trailing directory separator from a path, if it does not already have one. */ export function removeTrailingDirectorySeparator(path) { if (hasTrailingDirectorySeparator(path)) { return path.substr(0, path.length - 1); } return path; } /** * Adds a trailing directory separator to a path, if it does not already have one. */ export function ensureTrailingDirectorySeparator(path) { if (!hasTrailingDirectorySeparator(path)) { return path + directorySeparator; } return path; } /** * Normalize path separators, converting `\\` into `/`. */ export function normalizeSlashes(path) { return path.includes("\\") ? path.replace(backslashRegExp, directorySeparator) : path; } /** * Combines paths. If a path is absolute, it replaces any previous path. Relative paths are not simplified. */ export function combinePaths(path, ...paths) { if (path) path = normalizeSlashes(path); for (let relativePath of paths) { if (!relativePath) continue; relativePath = normalizeSlashes(relativePath); if (!path || getRootLength(relativePath) !== 0) { path = relativePath; } else { path = ensureTrailingDirectorySeparator(path) + relativePath; } } return path; } function simpleNormalizePath(path) { // Most paths don't require normalization if (!relativePathSegmentRegExp.test(path)) { return path; } // Some paths only require cleanup of `/./` or leading `./` let simplified = path.replace(/\/\.\//g, "/"); if (simplified.startsWith("./")) { simplified = simplified.slice(2); } if (simplified !== path) { path = simplified; if (!relativePathSegmentRegExp.test(path)) { return path; } } return undefined; } /** * Returns the normalized absolute path, resolving `.` and `..` segments. */ export function getNormalizedAbsolutePath(path, currentDirectory) { let rootLength = getRootLength(path); if (rootLength === 0 && currentDirectory) { path = combinePaths(currentDirectory, path); rootLength = getRootLength(path); } else { // combinePaths normalizes slashes, so not necessary in the other branch path = normalizeSlashes(path); } const simpleNormalized = simpleNormalizePath(path); if (simpleNormalized !== undefined) { return simpleNormalized.length > rootLength ? removeTrailingDirectorySeparator(simpleNormalized) : simpleNormalized; } const length = path.length; const root = path.substring(0, rootLength); // `normalized` is only initialized once `path` is determined to be non-normalized let normalized; let index = rootLength; let segmentStart = index; let normalizedUpTo = index; let seenNonDotDotSegment = rootLength !== 0; while (index < length) { // At beginning of segment segmentStart = index; let ch = path.charCodeAt(index); while (ch === CharacterCodesSlash && index + 1 < length) { index++; ch = path.charCodeAt(index); } if (index > segmentStart) { // Seen superfluous separator normalized ??= path.substring(0, segmentStart - 1); segmentStart = index; } // Past any superfluous separators let segmentEnd = path.indexOf(directorySeparator, index + 1); if (segmentEnd === -1) { segmentEnd = length; } const segmentLength = segmentEnd - segmentStart; if (segmentLength === 1 && path.charCodeAt(index) === CharacterCodesDot) { // "." segment (skip) normalized ??= path.substring(0, normalizedUpTo); } else if (segmentLength === 2 && path.charCodeAt(index) === CharacterCodesDot && path.charCodeAt(index + 1) === CharacterCodesDot) { // ".." segment if (!seenNonDotDotSegment) { if (normalized !== undefined) { normalized += normalized.length === rootLength ? ".." : "/.."; } else { normalizedUpTo = index + 2; } } else if (normalized === undefined) { if (normalizedUpTo - 2 >= 0) { normalized = path.substring(0, Math.max(rootLength, path.lastIndexOf(directorySeparator, normalizedUpTo - 2))); } else { normalized = path.substring(0, normalizedUpTo); } } else { const lastSlash = normalized.lastIndexOf(directorySeparator); if (lastSlash !== -1) { normalized = normalized.substring(0, Math.max(rootLength, lastSlash)); } else { normalized = root; } if (normalized.length === rootLength) { seenNonDotDotSegment = rootLength !== 0; } } } else if (normalized !== undefined) { if (normalized.length !== rootLength) { normalized += directorySeparator; } seenNonDotDotSegment = true; normalized += path.substring(segmentStart, segmentEnd); } else { seenNonDotDotSegment = true; normalizedUpTo = segmentEnd; } index = segmentEnd + 1; } return normalized ?? (length > rootLength ? removeTrailingDirectorySeparator(path) : path); } /** * Normalizes a path, resolving `.` and `..` segments and converting backslashes to forward slashes. */ export function normalizePath(path) { path = normalizeSlashes(path); let normalized = simpleNormalizePath(path); if (normalized !== undefined) { return normalized; } normalized = getNormalizedAbsolutePath(path, ""); return normalized && hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalized) : normalized; } /** * Determines whether a path is an absolute disk path (e.g. starts with `/`, or a DOS path * like `c:`, `c:\\` or `c:/`). */ export function isRootedDiskPath(path) { return getEncodedRootLength(path) > 0; } export function convertToRelativePath(absoluteOrRelativePath, basePath, getCanonicalFileName) { if (!isRootedDiskPath(absoluteOrRelativePath)) { return absoluteOrRelativePath; } const fromComponents = getPathComponents(getNormalizedAbsolutePath(basePath, "")); const toComponents = getPathComponents(getNormalizedAbsolutePath(absoluteOrRelativePath, "")); let start = 0; for (; start < fromComponents.length && start < toComponents.length; start++) { const fromComponent = getCanonicalFileName(fromComponents[start]); const toComponent = getCanonicalFileName(toComponents[start]); const equal = start === 0 ? fromComponent.toLowerCase() === toComponent.toLowerCase() : fromComponent === toComponent; if (!equal) { break; } } if (start === 0) { return pathFromComponents(toComponents); } const relative = Array(fromComponents.length - start).fill(".."); return pathFromComponents(["", ...relative, ...toComponents.slice(start)]); } function pathFromComponents(components) { if (components.length === 0) { return ""; } const root = components[0] && ensureTrailingDirectorySeparator(components[0]); return root + components.slice(1).join(directorySeparator); } /** * Returns the canonical key for an already rooted, normalized path under * caseSensitivity. */ export function pathKey(path, caseSensitivity) { if (path.startsWith(dynamicURIFileNamePrefix)) { let canonicalPath = path; if (getRootLength(path) === path.length && !hasTrailingDirectorySeparator(path)) { canonicalPath += directorySeparator; } return canonicalize(canonicalPath, CaseSensitivity.Sensitive); } return canonicalize(path, caseSensitivity); } /** * Validates a canonical path key received from a trusted producer without * applying case sensitivity again. */ export function tryPathKeyFromCanonical(path) { return tryRootedPathFromNormalized(path) === undefined ? undefined : path; } /** * Resolves path against currentDirectory and normalizes it. */ export function toRootedPath(path, currentDirectory) { if (path === "") { throw new Error("Path must not be empty"); } if (hasRootedURLSuffix(path)) { throw new Error(`Path must not contain a URL query or fragment: ${path}`); } if (getEncodedRootLength(path) === 0 && currentDirectory !== undefined && hasURLRoot(currentDirectory) && /[?#]/.test(path)) { throw new Error(`Relative URL path must not contain a query or fragment: ${path}`); } let normalized = getNormalizedAbsolutePath(path, currentDirectory); if (normalized === "" || getRootLength(normalized) === 0 || hasRootedURLSuffix(normalized)) { throw new Error(`Path is not rooted: ${normalized}`); } if (getRootLength(normalized) === normalized.length && !hasTrailingDirectorySeparator(normalized)) { normalized += directorySeparator; } return normalized; } /** * Validates a path that is already rooted and normalized without changing it. */ export function rootedPathFromNormalized(path) { const result = tryRootedPathFromNormalized(path); if (result === undefined) { throw new Error(`Path is not rooted and normalized: ${path}`); } return result; } /** * Attempts to validate a path that is already rooted and normalized without * changing it. */ export function tryRootedPathFromNormalized(path) { if (hasRootedURLSuffix(path)) { return undefined; } const rootLength = getRootLength(path); if (path === "" || rootLength === 0 || path.includes("\\") || hasRelativePathSegment(path, rootLength) || path.length === rootLength && !hasTrailingDirectorySeparator(path) || path.length > rootLength && hasTrailingDirectorySeparator(path)) { return undefined; } return path; } function hasRootedURLSuffix(path) { if (!hasURLRoot(path)) { return false; } const schemeEnd = path.indexOf(urlSchemeSeparator); const suffixStart = path.search(/[?#]/); return suffixStart >= schemeEnd + urlSchemeSeparator.length; } function hasURLRoot(path) { return getEncodedRootLength(path) < 0 && path.includes(urlSchemeSeparator); } function hasRelativePathSegment(path, start) { if (start === path.length) { return false; } let segmentStart = start; for (let index = start; index <= path.length; index++) { if (index !== path.length && path.charCodeAt(index) !== CharacterCodesSlash) { continue; } const segmentLength = index - segmentStart; if (segmentLength === 0 || segmentLength === 1 && path.charCodeAt(segmentStart) === CharacterCodesDot || segmentLength === 2 && path.charCodeAt(segmentStart) === CharacterCodesDot && path.charCodeAt(segmentStart + 1) === CharacterCodesDot) { return true; } segmentStart = index + 1; } return false; } /** * Resolves fileName against currentDirectory, normalizes it, and gives it file * intent. */ export function toRootedFilePath(fileName, currentDirectory) { return toRootedPath(fileName, currentDirectory); } /** * Resolves directory against currentDirectory, normalizes it, and gives it * directory intent. */ export function toRootedDirectoryPath(directory, currentDirectory) { return toRootedPath(directory, currentDirectory); } /** * Gives a RootedPath file intent without changing it. */ export function rootedFilePathFromPath(path) { return path; } /** * Gives a RootedPath directory intent without changing it. */ export function rootedDirectoryPathFromPath(path) { return path; } /** * Applies caseSensitivity to text used for path comparison or keys. */ export function canonicalize(text, caseSensitivity) { return isCaseSensitive(caseSensitivity) ? text : toFileNameLowerCase(text); } export function isCaseSensitive(caseSensitivity) { return caseSensitivity === CaseSensitivity.Sensitive; } export function isCaseInsensitive(caseSensitivity) { return caseSensitivity === CaseSensitivity.Insensitive; } function toLowerCasePerCodePoint(text) { let result = ""; for (const char of text) { result += char.toLowerCase(); } return result; } function toFileNameLowerCase(fileName) { return fileNameLowerCaseRegExp.test(fileName) ? fileName.replace(fileNameLowerCaseRegExp, toLowerCasePerCodePoint) : fileName; } const bundledScheme = "bundled:///"; const dynamicURIFileNamePrefix = "^/~ts-uri~/"; const dynamicURIPathSegmentEscapePrefix = "~ts-uri-escape~"; const dynamicURIModuleSpecifierEscapePrefix = "~ts-uri-spec~"; const dynamicURINoPathEscapePrefix = "~ts-uri-no-path~"; const dynamicURIPathSegmentEscapeRegExp = /(?:^|\/)(?:\.{1,2}(?:\/|$)|~ts-uri-escape~|~ts-uri-spec~|~ts-uri-no-path~)/; /** * Returns true if the path refers to a bundled library file. */ export function isBundled(path) { return path.startsWith(bundledScheme); } /** * Returns true if the file name represents a dynamic/virtual file * that doesn't exist on disk (e.g., untitled files with paths like "^/untitled/..."). */ export function isDynamicFileName(fileName) { return fileName.startsWith("^/"); } /** * Splits a Windows volume (e.g., "c:") from the rest of the path. * Returns [volume, rest, ok] where ok is true if a volume was found. */ export function splitVolumePath(path) { if (path.length >= 2 && isVolumeCharacter(path.charCodeAt(0)) && path.charCodeAt(1) === CharacterCodesColon) { return [path.substring(0, 2).toLowerCase(), path.substring(2), true]; } return ["", path, false]; } function splitVolumePathPreservingCase(path) { if (path.length >= 2 && isVolumeCharacter(path.charCodeAt(0)) && path.charCodeAt(1) === CharacterCodesColon) { return [path.substring(0, 2), path.substring(2), true]; } return ["", path, false]; } // Characters that need extra escaping in URI path segments // https://github.com/microsoft/vscode-uri/blob/edfdccd976efaf4bb8fdeca87e97c47257721729/src/uri.ts#L455 const extraEscapeChars = { ":": "%3A", "/": "%2F", "?": "%3F", "#": "%23", "[": "%5B", "]": "%5D", "@": "%40", "!": "%21", "$": "%24", "&": "%26", "'": "%27", "(": "%28", ")": "%29", "*": "%2A", "+": "%2B", ",": "%2C", ";": "%3B", "=": "%3D", " ": "%20", }; function extraEscape(s) { let result = s; for (const [char, escape] of Object.entries(extraEscapeChars)) { result = result.replaceAll(char, escape); } return result; } /** * Converts a file name to a document URI. * * @example * fileNameToDocumentURI("/path/to/file.ts") === "file:///path/to/file.ts" * fileNameToDocumentURI("c:/path/to/file.ts") === "file:///c%3A/path/to/file.ts" * fileNameToDocumentURI("^/untitled/ts-nul-authority/Untitled-1") === "untitled:Untitled-1" * fileNameToDocumentURI("^/vscode-vfs/github/microsoft/TypeScript/file.ts") === "vscode-vfs://github/microsoft/TypeScript/file.ts" */ export function fileNameToDocumentURI(fileName) { // Bundled files are returned as-is if (isBundled(fileName)) { return fileName; } // Dynamic/virtual files (untitled, vscode-vfs, etc.) need special handling if (isDynamicFileName(fileName)) { const encoded = fileName.startsWith(dynamicURIFileNamePrefix); const withoutPrefix = fileName.substring(encoded ? dynamicURIFileNamePrefix.length : 2); const firstSlash = withoutPrefix.indexOf("/"); if (firstSlash === -1) { throw new Error("invalid file name: " + fileName); } const scheme = withoutPrefix.substring(0, firstSlash); const rest = withoutPrefix.substring(firstSlash + 1); const secondSlash = rest.indexOf("/"); if (secondSlash === -1) { throw new Error("invalid file name: " + fileName); } const encodedAuthority = rest.substring(0, secondSlash); const hasAuthority = encodedAuthority !== "ts-nul-authority"; const authority = encoded ? decodeDynamicURIPathSegment(encodedAuthority) : encodedAuthority; const encodedPath = rest.substring(secondSlash + 1); if (encoded && hasAuthority) { const suffix = decodeDynamicURINoPath(encodedPath); if (suffix !== undefined) { return scheme + "://" + authority + suffix; } } const path = encoded ? decodeDynamicURIPath(encodedPath) : encodedPath; // ts-nul-authority is a placeholder for URIs without an authority if (!hasAuthority) { return scheme + ":" + path; } return scheme + "://" + authority + "/" + path; } // Regular file path - convert to file:// URI let [volume, rest] = splitVolumePath(fileName); if (volume !== "") { volume = "/" + extraEscape(volume); } // Remove leading // for UNC paths (already handled by file://) if (rest.startsWith("//")) { rest = rest.substring(2); } const parts = rest.split("/"); const encodedParts = parts.map(part => extraEscape(encodeURIComponent(part))); return "file://" + volume + encodedParts.join("/"); } /** * Converts a document URI to a file name. * * @example * documentURIToFileName("file:///path/to/file.ts") === "/path/to/file.ts" * documentURIToFileName("file:///c%3A/path/to/file.ts") === "c:/path/to/file.ts" * documentURIToFileName("untitled:Untitled-1") === "^/untitled/ts-nul-authority/Untitled-1" * documentURIToFileName("vscode-vfs://github/microsoft/TypeScript/file.ts") === "^/vscode-vfs/github/microsoft/TypeScript/file.ts" */ export function documentURIToFileName(uri) { // Bundled files are returned as-is if (isBundled(uri)) { return uri; } // Handle file:// URIs if (uri.startsWith("file://")) { let parsed; try { parsed = new URL(uri); } catch { throw new Error("invalid file URI: " + uri); } const path = decodeURIComponent(parsed.pathname); // UNC path: file://server/share/... if (parsed.host !== "") { return "//" + parsed.host + path; } // Local file - fix Windows path by removing leading slash before volume if (path.length >= 3 && path.charCodeAt(0) === CharacterCodesSlash) { const [volume, rest, ok] = splitVolumePathPreservingCase(path.substring(1)); if (ok) { return volume + rest; } } return path; } // Leave all other URIs escaped so we can round-trip them. // Convert to dynamic file name format: ^/scheme/authority/path const colonIndex = uri.indexOf(":"); if (colonIndex === -1) { throw new Error("invalid URI: " + uri); } const scheme = uri.substring(0, colonIndex); let path = uri.substring(colonIndex + 1); let suffix = ""; const suffixStart = path.search(/[?#]/); if (suffixStart !== -1) { suffix = path.substring(suffixStart); path = path.substring(0, suffixStart); } let authority = "ts-nul-authority"; let hasAuthority = false; let hasPath = true; if (path.startsWith("//")) { hasAuthority = true; const rest = path.substring(2); const slashIndex = rest.indexOf("/"); if (slashIndex === -1) { authority = rest; path = ""; hasPath = false; } else { authority = rest.substring(0, slashIndex); path = rest.substring(slashIndex + 1); } } let encodedAuthority = authority; if (hasAuthority) { encodedAuthority = authority === "ts-nul-authority" ? forceEncodeDynamicURIPathSegment(authority, false) : encodeDynamicURIPath(authority); } const encodedPath = hasPath ? encodeDynamicURIPathWithSuffix(path, suffix) : encodeDynamicURINoPath(suffix); return dynamicURIFileNamePrefix + scheme + "/" + encodedAuthority + "/" + encodedPath; } function encodeDynamicURIPath(path, preserveFinalExtension = true) { if (!dynamicURIPathNeedsEncoding(path)) { if (!isRootedDiskPath(path)) { return path; } } const segments = path.split("/"); for (let i = 0; i < segments.length; i++) { segments[i] = encodeDynamicURIPathSegment(segments[i], preserveFinalExtension && i === segments.length - 1); } if (isRootedDiskPath(segments.join("/"))) { segments[0] = forceEncodeDynamicURIPathSegment(segments[0], segments.length === 1 && preserveFinalExtension); } return segments.join("/"); } function encodeDynamicURIPathWithSuffix(path, suffix) { if (suffix === "") { return encodeDynamicURIPath(path); } const slash = path.lastIndexOf("/"); const before = slash === -1 ? "" : encodeDynamicURIPath(path.substring(0, slash), false) + "/"; return before + forceEncodeDynamicURIPathSegmentWithSuffix(path.substring(slash + 1), suffix); } function dynamicURIPathNeedsEncoding(path) { return path === "" || path.startsWith("/") || path.endsWith("/") || path.includes("//") || path.includes("\\") || dynamicURIPathSegmentEscapeRegExp.test(path); } function dynamicURIPathSegmentNeedsEncoding(segment) { return segment === "" || segment === "." || segment === ".." || segment.startsWith(dynamicURIPathSegmentEscapePrefix) || segment.startsWith(dynamicURIModuleSpecifierEscapePrefix) || segment.startsWith(dynamicURINoPathEscapePrefix) || segment.includes("\\"); } function encodeDynamicURIPathSegment(segment, preserveExtension) { if (dynamicURIPathSegmentNeedsEncoding(segment)) { return forceEncodeDynamicURIPathSegment(segment, preserveExtension); } return segment; } function forceEncodeDynamicURIPathSegment(segment, preserveExtension) { let extension = ""; if (preserveExtension && segment !== "." && segment !== "..") { [segment, extension] = splitDynamicURIFileExtension(segment); } let hex = ""; const encoded = encodeURIComponent(segment); for (let i = 0; i < encoded.length; i++) { if (encoded.charCodeAt(i) === CharacterCodesPercent) { hex += encoded.slice(i + 1, i + 3).toLowerCase(); i += 2; } else { hex += encoded.charCodeAt(i).toString(16).padStart(2, "0"); } } return dynamicURIPathSegmentEscapePrefix + hex + "~" + extension; } function forceEncodeDynamicURIPathSegmentWithSuffix(segment, suffix) { const [base, extension] = splitDynamicURIFileExtension(segment); return forceEncodeDynamicURIPathSegment(base + "\0" + suffix, false) + extension; } function splitDynamicURIFileExtension(segment) { const extension = getDynamicURIFileExtension(segment); return extension === "" ? [segment, extension] : [segment.substring(0, segment.length - extension.length), extension]; } function getDynamicURIFileExtension(segment) { const baseStart = segment.lastIndexOf("\\") + 1; for (const extension of [".d.ts", ".d.mts", ".d.cts"]) { if (segment.endsWith(extension) && segment.length - extension.length >= baseStart) { return extension; } } if (segment.endsWith(".ts")) { const declaration = segment.indexOf(".d.", baseStart); if (declaration !== -1) { return segment.substring(declaration); } } const dot = segment.lastIndexOf("."); return dot > segment.lastIndexOf("\\") ? segment.substring(dot) : ""; } function decodeDynamicURIPath(path) { if (!path.includes(dynamicURIPathSegmentEscapePrefix)) { return path; } return path.split("/").map(decodeDynamicURIPathSegment).join("/"); } function decodeDynamicURIPathSegment(segment) { if (!segment.startsWith(dynamicURIPathSegmentEscapePrefix)) { return segment; } const separator = segment.indexOf("~", dynamicURIPathSegmentEscapePrefix.length); if (separator === -1) { return segment; } const encoded = segment.slice(dynamicURIPathSegmentEscapePrefix.length, separator); const extension = segment.slice(separator + 1); if (encoded.length % 2 !== 0 || !/^[0-9a-f]*$/i.test(encoded)) { return segment; } try { const decoded = decodeURIComponent(encoded.replace(/../g, value => `%${value}`)); const suffix = decoded.indexOf("\0"); return suffix === -1 ? decoded + extension : decoded.substring(0, suffix) + extension + decoded.substring(suffix + 1); } catch { return segment; } } function encodeDynamicURINoPath(suffix) { let hex = ""; const encoded = encodeURIComponent(suffix); for (let i = 0; i < encoded.length; i++) { if (encoded.charCodeAt(i) === CharacterCodesPercent) { hex += encoded.substring(i + 1, i + 3).toLowerCase(); i += 2; } else { hex += encoded.charCodeAt(i).toString(16).padStart(2, "0"); } } return dynamicURINoPathEscapePrefix + hex + "~"; } function decodeDynamicURINoPath(path) { if (!path.startsWith(dynamicURINoPathEscapePrefix) || !path.endsWith("~")) { return undefined; } const encoded = path.substring(dynamicURINoPathEscapePrefix.length, path.length - 1); if (encoded.length % 2 !== 0 || !/^[0-9a-f]*$/i.test(encoded)) { return undefined; } try { return decodeURIComponent(encoded.replace(/../g, value => `%${value}`)); } catch { return undefined; } } //# sourceMappingURL=path.js.map