Skip to content

Commit dd2303b

Browse files
committed
Updates isDescendant to handle folder globs
Adds unit tests
1 parent 0e3cb8f commit dd2303b

File tree

2 files changed

+105
-34
lines changed

2 files changed

+105
-34
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import * as assert from 'node:assert';
2+
import { FileType, Uri, workspace } from 'vscode';
3+
import { isDescendant, isFolderGlobUri, isFolderUri } from '../path';
4+
5+
suite('Path Test Suite', () => {
6+
suite('isDescendant Tests', () => {
7+
test('string paths - basic functionality', () => {
8+
// Basic case
9+
assert.strictEqual(isDescendant('/path/to/file', '/path'), true);
10+
assert.strictEqual(isDescendant('/path/to/file', '/other'), false);
11+
12+
// Root path
13+
assert.strictEqual(isDescendant('/anything', '/'), true);
14+
15+
// Trailing slashes in base
16+
assert.strictEqual(isDescendant('/path/to/file', '/path/'), true);
17+
// Folder glob in base
18+
assert.strictEqual(isDescendant('/path/to/file', '/path/*'), true);
19+
20+
// Exact match should not match
21+
assert.strictEqual(isDescendant('/path', '/path'), false);
22+
// Partial path segment should not match
23+
assert.strictEqual(isDescendant('/pathExtra/to/file', '/path'), false);
24+
});
25+
26+
test('URI paths - basic functionality', () => {
27+
const baseUri = Uri.parse('file:///path');
28+
const fileUri = Uri.parse('file:///path/to/file');
29+
const otherUri = Uri.parse('file:///other/path');
30+
31+
assert.strictEqual(isDescendant(fileUri, baseUri), true);
32+
assert.strictEqual(isDescendant(otherUri, baseUri), false);
33+
34+
// Different schemes
35+
const httpUri = Uri.parse('http:///path/to/file');
36+
assert.strictEqual(isDescendant(httpUri, baseUri), false);
37+
38+
// Different authorities
39+
const diffAuthorityUri = Uri.parse('file://server1/path/to/file');
40+
const baseAuthorityUri = Uri.parse('file://server2/path');
41+
assert.strictEqual(isDescendant(diffAuthorityUri, baseAuthorityUri), false);
42+
});
43+
44+
test('mixed string and URI paths', () => {
45+
const baseUri = Uri.parse('file:///base/path');
46+
47+
assert.strictEqual(isDescendant('/base/path/to/file', baseUri), true);
48+
assert.strictEqual(isDescendant('/other/path', baseUri), false);
49+
50+
assert.strictEqual(isDescendant(Uri.parse('file:///base/path/file'), '/base/path'), true);
51+
assert.strictEqual(isDescendant(Uri.parse('file:///other/path'), '/base/path'), false);
52+
});
53+
54+
test('edge cases', () => {
55+
// Empty paths
56+
assert.strictEqual(isDescendant('', '/'), true);
57+
assert.strictEqual(isDescendant('/', ''), true);
58+
59+
// URI with query parameters
60+
const baseUri = Uri.parse('file:///base/path');
61+
const uriWithQuery = Uri.parse('file:///base/path/file?query=value');
62+
63+
assert.strictEqual(isDescendant(uriWithQuery, baseUri), true);
64+
});
65+
});
66+
67+
suite('isFolderGlobUri Tests', () => {
68+
test('URI with glob pattern', () => {
69+
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///path/*')), true);
70+
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///path/to/*')), true);
71+
});
72+
73+
test('URI without glob pattern', () => {
74+
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///path')), false);
75+
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///path/file.txt')), false);
76+
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///path/dir/')), false);
77+
});
78+
79+
test('Edge cases', () => {
80+
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///*')), true);
81+
assert.strictEqual(isFolderGlobUri(Uri.parse('http:///path/*')), true);
82+
});
83+
});
84+
});

src/system/-webview/path.ts

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,68 +27,55 @@ export function isChild(path: string, base: string | Uri): boolean;
2727
export function isChild(uri: Uri, base: string | Uri): boolean;
2828
export function isChild(pathOrUri: string | Uri, base: string | Uri): boolean {
2929
if (typeof base === 'string') {
30-
if (base.charCodeAt(0) !== slash) {
30+
if (!base.startsWith('/')) {
3131
base = `/${base}`;
3232
}
3333

3434
return (
3535
isDescendant(pathOrUri, base) &&
3636
(typeof pathOrUri === 'string' ? pathOrUri : pathOrUri.path)
37-
.substring(base.length + (base.charCodeAt(base.length - 1) === slash ? 0 : 1))
37+
.substring(base.length + (base.endsWith('/') ? 0 : 1))
3838
.split('/').length === 1
3939
);
4040
}
4141

4242
return (
4343
isDescendant(pathOrUri, base) &&
4444
(typeof pathOrUri === 'string' ? pathOrUri : pathOrUri.path)
45-
.substring(base.path.length + (base.path.charCodeAt(base.path.length - 1) === slash ? 0 : 1))
45+
.substring(base.path.length + (base.path.endsWith('/') ? 0 : 1))
4646
.split('/').length === 1
4747
);
4848
}
4949

5050
export function isDescendant(path: string, base: string | Uri): boolean;
5151
export function isDescendant(uri: Uri, base: string | Uri): boolean;
5252
export function isDescendant(pathOrUri: string | Uri, base: string | Uri): boolean;
53-
export function isDescendant(pathOrUri: string | Uri, base: string | Uri): boolean {
54-
if (typeof base === 'string') {
55-
base = normalizePath(base);
56-
if (base.charCodeAt(0) !== slash) {
57-
base = `/${base}`;
53+
export function isDescendant(pathOrUri: string | Uri, baseOrUri: string | Uri): boolean {
54+
// If both are URIs, ensure the scheme and authority match
55+
if (typeof pathOrUri !== 'string' && typeof baseOrUri !== 'string') {
56+
if (pathOrUri.scheme !== baseOrUri.scheme || pathOrUri.authority !== baseOrUri.authority) {
57+
return false;
5858
}
5959
}
6060

61-
if (typeof pathOrUri === 'string') {
62-
pathOrUri = normalizePath(pathOrUri);
63-
if (pathOrUri.charCodeAt(0) !== slash) {
64-
pathOrUri = `/${pathOrUri}`;
65-
}
61+
let base = getBestPath(baseOrUri);
62+
if (!base.startsWith('/')) {
63+
base = `${base}/`;
6664
}
6765

68-
if (typeof base === 'string') {
69-
return (
70-
base.length === 1 ||
71-
(typeof pathOrUri === 'string' ? pathOrUri : pathOrUri.path).startsWith(
72-
base.charCodeAt(base.length - 1) === slash ? base : `${base}/`,
73-
)
74-
);
66+
// Handles folder globs and ensure ending with a trailing slash
67+
if (base.endsWith('/*')) {
68+
base = base.substring(0, base.length - 1);
69+
} else if (!base.endsWith('/')) {
70+
base = `${base}/`;
7571
}
7672

77-
if (typeof pathOrUri === 'string') {
78-
return (
79-
base.path.length === 1 ||
80-
pathOrUri.startsWith(base.path.charCodeAt(base.path.length - 1) === slash ? base.path : `${base.path}/`)
81-
);
73+
let path = getBestPath(pathOrUri);
74+
if (!path.startsWith('/')) {
75+
path = `${path}/`;
8276
}
8377

84-
return (
85-
base.scheme === pathOrUri.scheme &&
86-
base.authority === pathOrUri.authority &&
87-
(base.path.length === 1 ||
88-
pathOrUri.path.startsWith(
89-
base.path.charCodeAt(base.path.length - 1) === slash ? base.path : `${base.path}/`,
90-
))
91-
);
78+
return path.startsWith(base);
9279
}
9380

9481
export function isFolderGlobUri(uri: Uri): boolean {
@@ -144,7 +131,7 @@ export function splitPath(
144131
if (index > 0) {
145132
root = pathOrUri.substring(0, index);
146133
pathOrUri = pathOrUri.substring(index + 1);
147-
} else if (pathOrUri.charCodeAt(0) === slash) {
134+
} else if (pathOrUri.startsWith('/')) {
148135
pathOrUri = pathOrUri.slice(1);
149136
}
150137

0 commit comments

Comments
 (0)