From 70a69de21be709a95610db9f43e3c63d224726b3 Mon Sep 17 00:00:00 2001 From: matt1097 <48395861+matt1097@users.noreply.github.com> Date: Mon, 27 Mar 2023 23:45:59 -0400 Subject: [PATCH] Add handling of Windows absolute paths. Improve tests for fromFileSystemPath() --- lib/refs.ts | 5 ++-- lib/util/is-windows.ts | 7 ++++++ lib/util/url.ts | 24 ++++++++++--------- test/specs/util/url.spec.ts | 47 +++++++++++++++++++++++++++++++++++-- 4 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 lib/util/is-windows.ts diff --git a/lib/refs.ts b/lib/refs.ts index ec1a5e4b..067217d4 100644 --- a/lib/refs.ts +++ b/lib/refs.ts @@ -1,12 +1,13 @@ import { ono } from "@jsdevtools/ono"; import $Ref from "./ref.js"; import * as url from "./util/url.js"; +import { isWindows } from "./util/is-windows.js"; import type { JSONSchema4Type, JSONSchema6Type, JSONSchema7Type } from "json-schema"; import type { JSONSchema } from "./types/index.js"; import type $RefParserOptions from "./options.js"; -const isWindows = /^win/.test(globalThis.process ? globalThis.process.platform : ""); -const getPathFromOs = (filePath: string): string => (isWindows ? filePath.replace(/\\/g, "/") : filePath); + +const getPathFromOs = (filePath: string): string => (isWindows() ? filePath.replace(/\\/g, "/") : filePath); interface $RefsMap { [url: string]: $Ref; diff --git a/lib/util/is-windows.ts b/lib/util/is-windows.ts new file mode 100644 index 00000000..ddfb50c6 --- /dev/null +++ b/lib/util/is-windows.ts @@ -0,0 +1,7 @@ +/** + * Returns if the system is a Windows system or not + * @returns + */ +export function isWindows() { + return /^win/.test(globalThis.process ? globalThis.process.platform : ""); +} \ No newline at end of file diff --git a/lib/util/url.ts b/lib/util/url.ts index c63974bc..b9f2a801 100644 --- a/lib/util/url.ts +++ b/lib/util/url.ts @@ -1,9 +1,9 @@ -const isWindows = /^win/.test(globalThis.process ? globalThis.process.platform : ""), - forwardSlashPattern = /\//g, +const forwardSlashPattern = /\//g, protocolPattern = /^(\w{2,}):\/\//i, jsonPointerSlash = /~1/g, jsonPointerTilde = /~0/g; -import { join } from "path"; +import { join, isAbsolute } from "path"; +import { isWindows } from "./is-windows"; const projectDir = join(__dirname, "..", ".."); // RegExp patterns to URL-encode special characters in local filesystem paths @@ -177,14 +177,16 @@ export function isFileSystemPath(path: any) { export function fromFileSystemPath(path: any) { // Step 1: On Windows, replace backslashes with forward slashes, // rather than encoding them as "%5C" - if (isWindows) { + if (isWindows()) { const hasProjectDir = path.toUpperCase().includes(projectDir.replace(/\\/g, "\\").toUpperCase()); const hasProjectUri = path.toUpperCase().includes(projectDir.replace(/\\/g, "/").toUpperCase()); - if (hasProjectDir || hasProjectUri) { - path = path.replace(/\\/g, "/"); - } else { - path = `${projectDir}/${path}`.replace(/\\/g, "/"); + const isAbsolutePath = isAbsolute(path); + + if (!(hasProjectDir || hasProjectUri || isAbsolutePath)) { + path = join(projectDir,path); } + + path = path.replace(/\\/g, "/"); } // Step 2: `encodeURI` will take care of MOST characters @@ -222,7 +224,7 @@ export function toFileSystemPath(path: string | undefined, keepFileProtocol?: bo path = path[7] === "/" ? path.substr(8) : path.substr(7); // insert a colon (":") after the drive letter on Windows - if (isWindows && path[1] === "/") { + if (isWindows() && path[1] === "/") { path = path[0] + ":" + path.substr(1); } @@ -234,12 +236,12 @@ export function toFileSystemPath(path: string | undefined, keepFileProtocol?: bo // On Windows, it will start with something like "C:/". // On Posix, it will start with "/" isFileUrl = false; - path = isWindows ? path : "/" + path; + path = isWindows() ? path : "/" + path; } } // Step 4: Normalize Windows paths (unless it's a "file://" URL) - if (isWindows && !isFileUrl) { + if (isWindows() && !isFileUrl) { // Replace forward slashes with backslashes path = path.replace(forwardSlashPattern, "\\"); diff --git a/test/specs/util/url.spec.ts b/test/specs/util/url.spec.ts index 8500954d..06a516d3 100644 --- a/test/specs/util/url.spec.ts +++ b/test/specs/util/url.spec.ts @@ -1,6 +1,6 @@ -import { describe, it } from "vitest"; -import { expect } from "vitest"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import * as $url from "../../../lib/util/url.js"; +import * as isWin from "../../../lib/util/is-windows.js"; describe("Return the extension of a URL", () => { it("should return an empty string if there isn't any extension", async () => { @@ -18,3 +18,46 @@ describe("Return the extension of a URL", () => { expect(extension).to.equal(".yml"); }); }); + +describe("Handle Windows file paths", () => { + beforeAll(function (this: any) { + //Force isWindows to always be true for this section of the test + vi.spyOn(isWin, 'isWindows').mockReturnValue(true); + }); + + afterAll(function (this: any) { + vi.restoreAllMocks(); + }); + + it("should handle absolute paths", async () => { + const result = $url.fromFileSystemPath('Y:\\A\\Random\\Path\\file.json'); + expect(result).to.be.a('string').and.toSatisfy((msg: string) => msg.startsWith('Y:/A/Random/Path')); + }); + + it("should handle relative paths", async () => { + const result = $url.fromFileSystemPath('Path\\file.json'); + const pwd = process.cwd().replace(/\\/g, '/'); + expect(result).to.be.a('string').and.toSatisfy((msg: string) => msg.startsWith(pwd)); + }); +}); + +describe("Handle Linux file paths", () => { + beforeAll(function (this: any) { + //Force isWindows to always be false for this section of the test + vi.spyOn(isWin, 'isWindows').mockReturnValue(false); + }); + + afterAll(function (this: any) { + vi.restoreAllMocks(); + }); + + it("should handle absolute paths", async () => { + const result = $url.fromFileSystemPath('/a/random/Path/file.json'); + expect(result).to.be.a('string').and.toSatisfy((msg: string) => msg.startsWith('/a/random/Path/file.json')); + }); + + it("should handle relative paths", async () => { + const result = $url.fromFileSystemPath('Path/file.json'); + expect(result).to.be.a('string').and.toSatisfy((msg: string) => msg.startsWith('Path/file.json')); + }); +}); \ No newline at end of file