From 0e058c7e5cba763e7b3a48a199192d5e17acee58 Mon Sep 17 00:00:00 2001 From: Christian Schneider Date: Wed, 26 Feb 2025 13:27:10 +0100 Subject: [PATCH] Improve Windows drive letter URI handling (#1816) --- packages/langium/src/utils/uri-utils.ts | 23 +++++++++++++++--- packages/langium/test/utils/uri-utils.test.ts | 24 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/langium/src/utils/uri-utils.ts b/packages/langium/src/utils/uri-utils.ts index 81fb80d43..8fa081e1b 100644 --- a/packages/langium/src/utils/uri-utils.ts +++ b/packages/langium/src/utils/uri-utils.ts @@ -16,15 +16,32 @@ export namespace UriUtils { export const joinPath = Utils.joinPath; export const resolvePath = Utils.resolvePath; + const isWindows = typeof process === 'object' && process?.platform === 'win32'; + export function equals(a?: URI | string, b?: URI | string): boolean { return a?.toString() === b?.toString(); } export function relative(from: URI | string, to: URI | string): string { - const fromPath = typeof from === 'string' ? from : from.path; - const toPath = typeof to === 'string' ? to : to.path; + const fromPath = typeof from === 'string' ? URI.parse(from).path : from.path; + const toPath = typeof to === 'string' ? URI.parse(to).path : to.path; const fromParts = fromPath.split('/').filter(e => e.length > 0); - const toParts = toPath.split('/').filter(e => e.length > 0); + const toParts = toPath.split('/').filter(e => e.length > 0); + + if (isWindows) { + const upperCaseDriveLetter = /^[A-Z]:$/; + if (fromParts[0] && upperCaseDriveLetter.test(fromParts[0])) { + fromParts[0] = fromParts[0].toLowerCase(); + } + if (toParts[0] && upperCaseDriveLetter.test(toParts[0])) { + toParts[0] = toParts[0].toLowerCase(); + } + if (fromParts[0] !== toParts[0]) { + // in case of different drive letters, we cannot compute a relative path, so... + return toPath.substring(1); // fall back to full 'to' path, drop the leading '/', keep everything else as is for good comparability + } + } + let i = 0; for (; i < fromParts.length; i++) { if (fromParts[i] !== toParts[i]) { diff --git a/packages/langium/test/utils/uri-utils.test.ts b/packages/langium/test/utils/uri-utils.test.ts index 8f52f4324..17c180b9f 100644 --- a/packages/langium/test/utils/uri-utils.test.ts +++ b/packages/langium/test/utils/uri-utils.test.ts @@ -27,6 +27,24 @@ describe('URI Utils', () => { expect(UriUtils.relative(from, to)).toBe('../d.txt'); }); + test.skipIf(process.platform !== 'win32')('relative path in parent directory win32, uppercase drive letters', () => { + const from = URI.file('C:\\a\\b'); + const to = URI.file('C:\\a\\d.txt'); + expect(UriUtils.relative(from, to)).toBe('../d.txt'); + }); + + test.skipIf(process.platform !== 'win32')('relative path in parent directory win32, mixed drive letter cases 1', () => { + const from = URI.file('C:\\a\\b'); + const to = URI.file('c:\\a\\d.txt'); + expect(UriUtils.relative(from, to)).toBe('../d.txt'); + }); + + test.skipIf(process.platform !== 'win32')('relative path in parent directory win32, mixed drive letter cases 2', () => { + const from = URI.file('c:\\a\\b'); + const to = URI.file('C:\\a\\d.txt'); + expect(UriUtils.relative(from, to)).toBe('../d.txt'); + }); + test('relative path in sub directory', () => { const from = URI.file('/a'); const to = URI.file('/a/b/c.txt'); @@ -51,6 +69,12 @@ describe('URI Utils', () => { expect(UriUtils.relative(from, to)).toBe('../c/d.txt'); }); + test.skipIf(process.platform !== 'win32')('different win32 drive letters', () => { + const from = URI.file('c:\\a\\b'); + const to = URI.file('D:\\a\\c\\d.txt'); + expect(UriUtils.relative(from, to)).toBe('D:/a/c/d.txt'); + }); + test('Equal uris are equal', () => { const uri1 = 'file:///a/b'; const uri2 = 'file:///a/b';