/** * The four path functions the include code needs, for slash-separated paths. They exist so code the extension carries does not import node:path * (the extension has no Node). Behavior is held to node's `path.posix` by test/unit/posixPath.test.ts. */ export const SEP = "/"; export const isAbsolute = (p: string): boolean => p.startsWith("/"); const normalize = (segments: string[]): string[] => { const out: string[] = []; for (const s of segments) { if (s === "" || s === ".") continue; if (s === "..") out.pop(); else out.push(s); } return out; }; /** Joins right to left until a path is absolute, then cleans it. A relative result is taken from "/" (the callers only pass absolute bases). */ export function resolve(...parts: string[]): string { let joined = ""; for (let i = parts.length - 1; i >= 0 && !isAbsolute(joined); i--) { const p = parts[i]!; if (p !== "") joined = joined === "" ? p : `${p}/${joined}`; } return `/${normalize(joined.split("/")).join("/")}`; } export function dirname(p: string): string { const end = p.replace(/\/+$/, ""); if (end === "") return p.startsWith("/") ? "/" : "."; const i = end.lastIndexOf("/"); if (i === -1) return "."; if (i === 0) return "/"; return end.slice(0, i).replace(/\/+$/, "") || "/"; } export function relative(from: string, to: string): string { const a = normalize(resolve(from).split("/")); const b = normalize(resolve(to).split("/")); let i = 0; while (i < a.length && i < b.length && a[i] === b[i]) i++; return [...a.slice(i).map(() => ".."), ...b.slice(i)].join("/"); }