working on unit tests

This commit is contained in:
Oleg Solomko
2025-03-13 15:40:38 -07:00
parent ba779b2ea7
commit 0f1ff22ca3
3 changed files with 1014 additions and 494 deletions
+5 -2
View File
@@ -115,9 +115,12 @@ export namespace PromptsConfig {
// copy all the enabled paths to the result list
for (const [path, enabled] of Object.entries(value)) {
if (enabled && path !== DEFAULT_SOURCE_FOLDER) {
paths.push(path);
// we already added the default source folder, so skip it
if ((enabled === false) || (path === DEFAULT_SOURCE_FOLDER)) {
continue;
}
paths.push(path);
}
return paths;
@@ -4,13 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from '../../../../../../base/common/uri.js';
import { match } from '../../../../../../base/common/glob.js';
import { assert } from '../../../../../../base/common/assert.js';
import { isAbsolute } from '../../../../../../base/common/path.js';
import { ResourceSet } from '../../../../../../base/common/map.js';
import { dirname, extUri } from '../../../../../../base/common/resources.js';
import { IFileService } from '../../../../../../platform/files/common/files.js';
import { PromptsConfig } from '../../../../../../platform/prompts/common/config.js';
import { isPromptFile } from '../../../../../../platform/prompts/common/constants.js';
import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js';
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
import { isPromptFile, PROMPT_FILE_EXTENSION } from '../../../../../../platform/prompts/common/constants.js';
/**
* Utility class to locate prompt files.
@@ -153,4 +156,230 @@ export class PromptFilesLocator {
return files;
}
/**
* TODO: @lego
*
* @throws if any of the provided locations is not an `absolute path`.
*/
public async findAllMatchingPromptFiles(
// configuredLocations: readonly string[],
// fileService: IFileService,
// workspaceService: IWorkspaceContextService,
): Promise<readonly URI[]> {
const configuredLocations = PromptsConfig.promptSourceFolders(this.configService);
// convert locations from settings to absolute paths based on the current workspace folders
const absoluteLocations = toAbsoluteLocations(configuredLocations, this.workspaceService);
// find all prompt files in the provided locations, then match
// the found file paths against (possible) glob patterns
const paths = new ResourceSet();
for (const absoluteLocation of absoluteLocations) {
// assert(
// isAbsolute(location),
// `Provided location must be an absolute path, got '${location}'.`,
// );
// normalize the glob pattern to always end with "any prompt file" pattern
const location = (absoluteLocation.path.endsWith(PROMPT_FILE_EXTENSION))
? absoluteLocation
: extUri.joinPath(absoluteLocation, `*${PROMPT_FILE_EXTENSION}`);
// find all prompt files in entire file tree, starting from
// a first parent folder that does not contain a glob pattern
const promptFiles = await findAllPromptFiles(
firstNonGlobParent(location),
this.fileService,
);
// filter out found prompt files to only include those that match
// the original glob pattern specified in the settings (if any)
for (const file of promptFiles) {
if (match(location.path, file.path)) {
paths.add(file);
}
}
}
return [...paths];
}
}
/**
* Checks if the provided `pattern` could be a valid glob pattern.
*/
export const isValidGlob = (pattern: string): boolean => {
let squareBrackets = false;
let squareBracketsCount = 0;
let curlyBrackets = false;
let curlyBracketsCount = 0;
for (const char of pattern) {
if (char === '*') {
return true;
}
if (char === '?') {
return true;
}
// TODO: @lego - check that these are not escaped?
if (char === '[') {
squareBrackets = true;
squareBracketsCount++;
}
if (char === ']') {
squareBrackets = true;
// if there are no matching opening square brackets, this is an `invalid glob`
if (squareBracketsCount % 2 === 0) {
return false;
}
squareBracketsCount--;
}
if (char === '{') {
curlyBrackets = true;
curlyBracketsCount++;
}
if (char === '}') {
curlyBrackets = true;
// if there are no matching opening curly brackets, this is an `invalid glob`
if (curlyBracketsCount % 2 === 0) {
return false;
}
curlyBracketsCount--;
}
}
// if square brackets exist and are in pairs, this is a `valid glob`
if (squareBrackets && squareBracketsCount === 0) {
return true;
}
// if curly brackets exist and are in pairs, this is a `valid glob`
if (curlyBrackets && curlyBracketsCount === 0) {
return true;
}
return false;
};
/**
* TODO: @lego - notes
* - if path `starts with a glob`, it must be relative to current workspace folders, otherwise we would need to scan through entire filesystem of the machine
* - likewise if a `relative path`, it must be relative to current workspace folders
* - only if path is `absolute`, we can try to resolve all prompt files in it and its subfolders, and then try to match the files against a glob pattern
*/
/**
* Finds the first parent of the provided location that does not contain a `glob pattern`.
*
* @throws if the provided location is not an `absolute path`.
*/
// TODO: @lego - add examples?
export const firstNonGlobParent = (
location: URI,
): URI => {
// sanity check of the provided location
assert(
isAbsolute(location.path),
`Provided location must be an absolute path, got '${location.path}'.`,
);
// note! if though the folder name can be `invalid glob` here, it is still OK to
// use it as we don't really known if that is a glob pattern, or the folder
// name contains characters that can also be used in a glob pattern
if (isValidGlob(location.path) === false) {
return location;
}
// if location is the root of the filesystem, we are done
const parent = dirname(location);
if (extUri.isEqual(parent, location)) {
return location;
}
// otherwise, try again starting with the parent folder
return firstNonGlobParent(parent);
};
/**
* TODO: @lego
*/
const findAllPromptFiles = async (
location: URI,
fileService: IFileService,
): Promise<readonly URI[]> => {
const result: URI[] = [];
const info = await fileService.resolve(location);
if (info.isFile && isPromptFile(info.resource)) {
result.push(info.resource);
return result;
}
if (info.isDirectory && info.children) {
for (const child of info.children) {
if (child.isFile && isPromptFile(child.resource)) {
result.push(child.resource);
continue;
}
if (child.isDirectory) {
const promptFiles = await findAllPromptFiles(child.resource, fileService);
result.push(...promptFiles);
continue;
}
}
return result;
}
return result;
};
/**
* Converts locations from settings to absolute paths
* based on the current workspace folders.
*/
const toAbsoluteLocations = (
configuredLocations: readonly string[],
workspaceService: IWorkspaceContextService,
): readonly URI[] => {
const { folders } = workspaceService.getWorkspace();
const result: URI[] = [];
for (const configuredLocation of configuredLocations) {
if (isAbsolute(configuredLocation)) {
result.push(URI.file(configuredLocation));
continue;
}
for (const workspaceFolder of folders) {
const absolutePath = extUri.resolvePath(workspaceFolder.uri, configuredLocation);
// a sanity check on the expected outcome of the `resolvePath()` call
assert(
isAbsolute(absolutePath.path),
`Provided location must be an absolute path, got '${absolutePath.path}'.`,
);
result.push(absolutePath);
}
}
return result;
};