Skip to content

Commit 0e058c7

Browse files
Improve Windows drive letter URI handling (#1816)
1 parent fa8371b commit 0e058c7

File tree

2 files changed

+44
-3
lines changed

2 files changed

+44
-3
lines changed

packages/langium/src/utils/uri-utils.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,32 @@ export namespace UriUtils {
1616
export const joinPath = Utils.joinPath;
1717
export const resolvePath = Utils.resolvePath;
1818

19+
const isWindows = typeof process === 'object' && process?.platform === 'win32';
20+
1921
export function equals(a?: URI | string, b?: URI | string): boolean {
2022
return a?.toString() === b?.toString();
2123
}
2224

2325
export function relative(from: URI | string, to: URI | string): string {
24-
const fromPath = typeof from === 'string' ? from : from.path;
25-
const toPath = typeof to === 'string' ? to : to.path;
26+
const fromPath = typeof from === 'string' ? URI.parse(from).path : from.path;
27+
const toPath = typeof to === 'string' ? URI.parse(to).path : to.path;
2628
const fromParts = fromPath.split('/').filter(e => e.length > 0);
27-
const toParts = toPath.split('/').filter(e => e.length > 0);
29+
const toParts = toPath.split('/').filter(e => e.length > 0);
30+
31+
if (isWindows) {
32+
const upperCaseDriveLetter = /^[A-Z]:$/;
33+
if (fromParts[0] && upperCaseDriveLetter.test(fromParts[0])) {
34+
fromParts[0] = fromParts[0].toLowerCase();
35+
}
36+
if (toParts[0] && upperCaseDriveLetter.test(toParts[0])) {
37+
toParts[0] = toParts[0].toLowerCase();
38+
}
39+
if (fromParts[0] !== toParts[0]) {
40+
// in case of different drive letters, we cannot compute a relative path, so...
41+
return toPath.substring(1); // fall back to full 'to' path, drop the leading '/', keep everything else as is for good comparability
42+
}
43+
}
44+
2845
let i = 0;
2946
for (; i < fromParts.length; i++) {
3047
if (fromParts[i] !== toParts[i]) {

packages/langium/test/utils/uri-utils.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,24 @@ describe('URI Utils', () => {
2727
expect(UriUtils.relative(from, to)).toBe('../d.txt');
2828
});
2929

30+
test.skipIf(process.platform !== 'win32')('relative path in parent directory win32, uppercase drive letters', () => {
31+
const from = URI.file('C:\\a\\b');
32+
const to = URI.file('C:\\a\\d.txt');
33+
expect(UriUtils.relative(from, to)).toBe('../d.txt');
34+
});
35+
36+
test.skipIf(process.platform !== 'win32')('relative path in parent directory win32, mixed drive letter cases 1', () => {
37+
const from = URI.file('C:\\a\\b');
38+
const to = URI.file('c:\\a\\d.txt');
39+
expect(UriUtils.relative(from, to)).toBe('../d.txt');
40+
});
41+
42+
test.skipIf(process.platform !== 'win32')('relative path in parent directory win32, mixed drive letter cases 2', () => {
43+
const from = URI.file('c:\\a\\b');
44+
const to = URI.file('C:\\a\\d.txt');
45+
expect(UriUtils.relative(from, to)).toBe('../d.txt');
46+
});
47+
3048
test('relative path in sub directory', () => {
3149
const from = URI.file('/a');
3250
const to = URI.file('/a/b/c.txt');
@@ -51,6 +69,12 @@ describe('URI Utils', () => {
5169
expect(UriUtils.relative(from, to)).toBe('../c/d.txt');
5270
});
5371

72+
test.skipIf(process.platform !== 'win32')('different win32 drive letters', () => {
73+
const from = URI.file('c:\\a\\b');
74+
const to = URI.file('D:\\a\\c\\d.txt');
75+
expect(UriUtils.relative(from, to)).toBe('D:/a/c/d.txt');
76+
});
77+
5478
test('Equal uris are equal', () => {
5579
const uri1 = 'file:///a/b';
5680
const uri2 = 'file:///a/b';

0 commit comments

Comments
 (0)