Skip to content

Commit

Permalink
Updates isDescendant to handle folder globs
Browse files Browse the repository at this point in the history
Adds unit tests
  • Loading branch information
eamodio committed Feb 28, 2025
1 parent 0e3cb8f commit dd2303b
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 34 deletions.
84 changes: 84 additions & 0 deletions src/system/-webview/__tests__/path.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as assert from 'node:assert';
import { FileType, Uri, workspace } from 'vscode';
import { isDescendant, isFolderGlobUri, isFolderUri } from '../path';

suite('Path Test Suite', () => {
suite('isDescendant Tests', () => {
test('string paths - basic functionality', () => {
// Basic case
assert.strictEqual(isDescendant('/path/to/file', '/path'), true);
assert.strictEqual(isDescendant('/path/to/file', '/other'), false);

// Root path
assert.strictEqual(isDescendant('/anything', '/'), true);

// Trailing slashes in base
assert.strictEqual(isDescendant('/path/to/file', '/path/'), true);
// Folder glob in base
assert.strictEqual(isDescendant('/path/to/file', '/path/*'), true);

// Exact match should not match
assert.strictEqual(isDescendant('/path', '/path'), false);
// Partial path segment should not match
assert.strictEqual(isDescendant('/pathExtra/to/file', '/path'), false);
});

test('URI paths - basic functionality', () => {
const baseUri = Uri.parse('file:///path');
const fileUri = Uri.parse('file:///path/to/file');
const otherUri = Uri.parse('file:///other/path');

assert.strictEqual(isDescendant(fileUri, baseUri), true);
assert.strictEqual(isDescendant(otherUri, baseUri), false);

// Different schemes
const httpUri = Uri.parse('http:///path/to/file');
assert.strictEqual(isDescendant(httpUri, baseUri), false);

// Different authorities
const diffAuthorityUri = Uri.parse('file://server1/path/to/file');
const baseAuthorityUri = Uri.parse('file://server2/path');
assert.strictEqual(isDescendant(diffAuthorityUri, baseAuthorityUri), false);
});

test('mixed string and URI paths', () => {
const baseUri = Uri.parse('file:///base/path');

assert.strictEqual(isDescendant('/base/path/to/file', baseUri), true);
assert.strictEqual(isDescendant('/other/path', baseUri), false);

assert.strictEqual(isDescendant(Uri.parse('file:///base/path/file'), '/base/path'), true);
assert.strictEqual(isDescendant(Uri.parse('file:///other/path'), '/base/path'), false);
});

test('edge cases', () => {
// Empty paths
assert.strictEqual(isDescendant('', '/'), true);
assert.strictEqual(isDescendant('/', ''), true);

// URI with query parameters
const baseUri = Uri.parse('file:///base/path');
const uriWithQuery = Uri.parse('file:///base/path/file?query=value');

assert.strictEqual(isDescendant(uriWithQuery, baseUri), true);
});
});

suite('isFolderGlobUri Tests', () => {
test('URI with glob pattern', () => {
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///path/*')), true);
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///path/to/*')), true);
});

test('URI without glob pattern', () => {
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///path')), false);
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///path/file.txt')), false);
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///path/dir/')), false);
});

test('Edge cases', () => {
assert.strictEqual(isFolderGlobUri(Uri.parse('file:///*')), true);
assert.strictEqual(isFolderGlobUri(Uri.parse('http:///path/*')), true);
});
});
});
55 changes: 21 additions & 34 deletions src/system/-webview/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,68 +27,55 @@ export function isChild(path: string, base: string | Uri): boolean;
export function isChild(uri: Uri, base: string | Uri): boolean;
export function isChild(pathOrUri: string | Uri, base: string | Uri): boolean {
if (typeof base === 'string') {
if (base.charCodeAt(0) !== slash) {
if (!base.startsWith('/')) {
base = `/${base}`;
}

return (
isDescendant(pathOrUri, base) &&
(typeof pathOrUri === 'string' ? pathOrUri : pathOrUri.path)
.substring(base.length + (base.charCodeAt(base.length - 1) === slash ? 0 : 1))
.substring(base.length + (base.endsWith('/') ? 0 : 1))
.split('/').length === 1
);
}

return (
isDescendant(pathOrUri, base) &&
(typeof pathOrUri === 'string' ? pathOrUri : pathOrUri.path)
.substring(base.path.length + (base.path.charCodeAt(base.path.length - 1) === slash ? 0 : 1))
.substring(base.path.length + (base.path.endsWith('/') ? 0 : 1))
.split('/').length === 1
);
}

export function isDescendant(path: string, base: string | Uri): boolean;
export function isDescendant(uri: Uri, base: string | Uri): boolean;
export function isDescendant(pathOrUri: string | Uri, base: string | Uri): boolean;
export function isDescendant(pathOrUri: string | Uri, base: string | Uri): boolean {
if (typeof base === 'string') {
base = normalizePath(base);
if (base.charCodeAt(0) !== slash) {
base = `/${base}`;
export function isDescendant(pathOrUri: string | Uri, baseOrUri: string | Uri): boolean {
// If both are URIs, ensure the scheme and authority match
if (typeof pathOrUri !== 'string' && typeof baseOrUri !== 'string') {
if (pathOrUri.scheme !== baseOrUri.scheme || pathOrUri.authority !== baseOrUri.authority) {
return false;
}
}

if (typeof pathOrUri === 'string') {
pathOrUri = normalizePath(pathOrUri);
if (pathOrUri.charCodeAt(0) !== slash) {
pathOrUri = `/${pathOrUri}`;
}
let base = getBestPath(baseOrUri);
if (!base.startsWith('/')) {
base = `${base}/`;
}

if (typeof base === 'string') {
return (
base.length === 1 ||
(typeof pathOrUri === 'string' ? pathOrUri : pathOrUri.path).startsWith(
base.charCodeAt(base.length - 1) === slash ? base : `${base}/`,
)
);
// Handles folder globs and ensure ending with a trailing slash
if (base.endsWith('/*')) {
base = base.substring(0, base.length - 1);
} else if (!base.endsWith('/')) {
base = `${base}/`;
}

if (typeof pathOrUri === 'string') {
return (
base.path.length === 1 ||
pathOrUri.startsWith(base.path.charCodeAt(base.path.length - 1) === slash ? base.path : `${base.path}/`)
);
let path = getBestPath(pathOrUri);
if (!path.startsWith('/')) {
path = `${path}/`;
}

return (
base.scheme === pathOrUri.scheme &&
base.authority === pathOrUri.authority &&
(base.path.length === 1 ||
pathOrUri.path.startsWith(
base.path.charCodeAt(base.path.length - 1) === slash ? base.path : `${base.path}/`,
))
);
return path.startsWith(base);
}

export function isFolderGlobUri(uri: Uri): boolean {
Expand Down Expand Up @@ -144,7 +131,7 @@ export function splitPath(
if (index > 0) {
root = pathOrUri.substring(0, index);
pathOrUri = pathOrUri.substring(index + 1);
} else if (pathOrUri.charCodeAt(0) === slash) {
} else if (pathOrUri.startsWith('/')) {
pathOrUri = pathOrUri.slice(1);
}

Expand Down

0 comments on commit dd2303b

Please sign in to comment.