Add codeQL.model.packLocation setting
This adds the `codeQL.model.packLocation` setting, which allows users to
specify the location of the CodeQL extension pack. This setting replaces
the `codeQL.model.extensionsDirectory` setting, which has been removed.
The pack location supports variable substitutions and supports both
absolute and relative paths. The default value is
`.github/codeql/extensions/${name}-${language}` which matches the
previous defaults.
This commit is contained in:
@@ -463,8 +463,20 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "Log insights",
|
"title": "Model Editor",
|
||||||
"order": 9,
|
"order": 9,
|
||||||
|
"properties": {
|
||||||
|
"codeQL.model.packLocation": {
|
||||||
|
"type": "string",
|
||||||
|
"default": ".github/codeql/extensions/${name}-${language}",
|
||||||
|
"markdownDescription": "Location for newly created CodeQL model packs. The location can be either absolute or relative. If relative, it is relative to the workspace root.\n\nThe following variables are supported:\n* **${database}** - the name of the database\n* **${language}** - the name of the language\n* **${name}** - the name of the GitHub repository, or the name of the database if the database was not downloaded from GitHub\n* **${owner}** - the owner of the GitHub repository, or an empty string if the database was not downloaded from GitHub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"title": "Log insights",
|
||||||
|
"order": 10,
|
||||||
"properties": {
|
"properties": {
|
||||||
"codeQL.logInsights.joinOrderWarningThreshold": {
|
"codeQL.logInsights.joinOrderWarningThreshold": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
@@ -478,7 +490,7 @@
|
|||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "Telemetry",
|
"title": "Telemetry",
|
||||||
"order": 10,
|
"order": 11,
|
||||||
"properties": {
|
"properties": {
|
||||||
"codeQL.telemetry.enableTelemetry": {
|
"codeQL.telemetry.enableTelemetry": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
FilterKey,
|
FilterKey,
|
||||||
SortKey,
|
SortKey,
|
||||||
} from "./variant-analysis/shared/variant-analysis-filter-sort";
|
} from "./variant-analysis/shared/variant-analysis-filter-sort";
|
||||||
|
import { substituteConfigVariables } from "./common/config-template";
|
||||||
|
|
||||||
export const ALL_SETTINGS: Setting[] = [];
|
export const ALL_SETTINGS: Setting[] = [];
|
||||||
|
|
||||||
@@ -734,18 +735,28 @@ const LLM_GENERATION_DEV_ENDPOINT = new Setting(
|
|||||||
MODEL_SETTING,
|
MODEL_SETTING,
|
||||||
);
|
);
|
||||||
const MODEL_EVALUATION = new Setting("evaluation", MODEL_SETTING);
|
const MODEL_EVALUATION = new Setting("evaluation", MODEL_SETTING);
|
||||||
const EXTENSIONS_DIRECTORY = new Setting("extensionsDirectory", MODEL_SETTING);
|
const MODEL_PACK_LOCATION = new Setting("packLocation", MODEL_SETTING);
|
||||||
const ENABLE_PYTHON = new Setting("enablePython", MODEL_SETTING);
|
const ENABLE_PYTHON = new Setting("enablePython", MODEL_SETTING);
|
||||||
const ENABLE_ACCESS_PATH_SUGGESTIONS = new Setting(
|
const ENABLE_ACCESS_PATH_SUGGESTIONS = new Setting(
|
||||||
"enableAccessPathSuggestions",
|
"enableAccessPathSuggestions",
|
||||||
MODEL_SETTING,
|
MODEL_SETTING,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type ModelConfigPackVariables = {
|
||||||
|
database: string;
|
||||||
|
owner: string;
|
||||||
|
name: string;
|
||||||
|
language: string;
|
||||||
|
};
|
||||||
|
|
||||||
export interface ModelConfig {
|
export interface ModelConfig {
|
||||||
flowGeneration: boolean;
|
flowGeneration: boolean;
|
||||||
llmGeneration: boolean;
|
llmGeneration: boolean;
|
||||||
showTypeModels: boolean;
|
showTypeModels: boolean;
|
||||||
getExtensionsDirectory(languageId: string): string | undefined;
|
getPackLocation(
|
||||||
|
languageId: string,
|
||||||
|
variables: ModelConfigPackVariables,
|
||||||
|
): string;
|
||||||
enablePython: boolean;
|
enablePython: boolean;
|
||||||
enableAccessPathSuggestions: boolean;
|
enableAccessPathSuggestions: boolean;
|
||||||
}
|
}
|
||||||
@@ -787,10 +798,16 @@ export class ModelConfigListener extends ConfigListener implements ModelConfig {
|
|||||||
return !!MODEL_EVALUATION.getValue<boolean>();
|
return !!MODEL_EVALUATION.getValue<boolean>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getExtensionsDirectory(languageId: string): string | undefined {
|
public getPackLocation(
|
||||||
return EXTENSIONS_DIRECTORY.getValue<string>({
|
languageId: string,
|
||||||
languageId,
|
variables: ModelConfigPackVariables,
|
||||||
});
|
): string {
|
||||||
|
return substituteConfigVariables(
|
||||||
|
MODEL_PACK_LOCATION.getValue<string>({
|
||||||
|
languageId,
|
||||||
|
}),
|
||||||
|
variables,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get enablePython(): boolean {
|
public get enablePython(): boolean {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { join } from "path";
|
|||||||
import { outputFile, pathExists, readFile } from "fs-extra";
|
import { outputFile, pathExists, readFile } from "fs-extra";
|
||||||
import { dump as dumpYaml, load as loadYaml } from "js-yaml";
|
import { dump as dumpYaml, load as loadYaml } from "js-yaml";
|
||||||
import type { CancellationToken } from "vscode";
|
import type { CancellationToken } from "vscode";
|
||||||
import { Uri } from "vscode";
|
|
||||||
import Ajv from "ajv";
|
import Ajv from "ajv";
|
||||||
import type { CodeQLCliServer } from "../codeql-cli/cli";
|
import type { CodeQLCliServer } from "../codeql-cli/cli";
|
||||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||||
@@ -14,10 +13,13 @@ import { getErrorMessage } from "../common/helpers-pure";
|
|||||||
import type { ExtensionPack } from "./shared/extension-pack";
|
import type { ExtensionPack } from "./shared/extension-pack";
|
||||||
import type { NotificationLogger } from "../common/logging";
|
import type { NotificationLogger } from "../common/logging";
|
||||||
import { showAndLogErrorMessage } from "../common/logging";
|
import { showAndLogErrorMessage } from "../common/logging";
|
||||||
import type { ModelConfig } from "../config";
|
import type { ModelConfig, ModelConfigPackVariables } from "../config";
|
||||||
import type { ExtensionPackName } from "./extension-pack-name";
|
import type { ExtensionPackName } from "./extension-pack-name";
|
||||||
import { autoNameExtensionPack, formatPackName } from "./extension-pack-name";
|
import { autoNameExtensionPack, formatPackName } from "./extension-pack-name";
|
||||||
import { autoPickExtensionsDirectory } from "./extensions-workspace-folder";
|
import {
|
||||||
|
ensurePackLocationIsInWorkspaceFolder,
|
||||||
|
packLocationToAbsolute,
|
||||||
|
} from "./extensions-workspace-folder";
|
||||||
|
|
||||||
import type { ExtensionPackMetadata } from "./extension-pack-metadata";
|
import type { ExtensionPackMetadata } from "./extension-pack-metadata";
|
||||||
import extensionPackMetadataSchemaJson from "./extension-pack-metadata.schema.json";
|
import extensionPackMetadataSchemaJson from "./extension-pack-metadata.schema.json";
|
||||||
@@ -27,7 +29,7 @@ const extensionPackValidate = ajv.compile(extensionPackMetadataSchemaJson);
|
|||||||
|
|
||||||
export async function pickExtensionPack(
|
export async function pickExtensionPack(
|
||||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">,
|
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">,
|
||||||
databaseItem: Pick<DatabaseItem, "name" | "language">,
|
databaseItem: Pick<DatabaseItem, "name" | "language" | "origin">,
|
||||||
modelConfig: ModelConfig,
|
modelConfig: ModelConfig,
|
||||||
logger: NotificationLogger,
|
logger: NotificationLogger,
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
@@ -64,20 +66,20 @@ export async function pickExtensionPack(
|
|||||||
maxStep,
|
maxStep,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the `codeQL.model.extensionsDirectory` setting for the language
|
// The default is .github/codeql/extensions/${name}-${language}
|
||||||
const userExtensionsDirectory = modelConfig.getExtensionsDirectory(
|
const packPath = await packLocationToAbsolute(
|
||||||
databaseItem.language,
|
modelConfig.getPackLocation(
|
||||||
|
databaseItem.language,
|
||||||
|
getModelConfigPackVariables(databaseItem),
|
||||||
|
),
|
||||||
|
logger,
|
||||||
);
|
);
|
||||||
|
if (!packPath) {
|
||||||
// If the setting is not set, automatically pick a suitable directory
|
|
||||||
const extensionsDirectory = userExtensionsDirectory
|
|
||||||
? Uri.file(userExtensionsDirectory)
|
|
||||||
: await autoPickExtensionsDirectory(logger);
|
|
||||||
|
|
||||||
if (!extensionsDirectory) {
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ensurePackLocationIsInWorkspaceFolder(packPath, modelConfig, logger);
|
||||||
|
|
||||||
// Generate the name of the extension pack
|
// Generate the name of the extension pack
|
||||||
const packName = autoNameExtensionPack(
|
const packName = autoNameExtensionPack(
|
||||||
databaseItem.name,
|
databaseItem.name,
|
||||||
@@ -139,14 +141,12 @@ export async function pickExtensionPack(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const packPath = join(extensionsDirectory.fsPath, packName.name);
|
|
||||||
|
|
||||||
if (await pathExists(packPath)) {
|
if (await pathExists(packPath)) {
|
||||||
void showAndLogErrorMessage(
|
void showAndLogErrorMessage(
|
||||||
logger,
|
logger,
|
||||||
`Directory ${packPath} already exists for extension pack ${formatPackName(
|
`Directory ${packPath} already exists for extension pack ${formatPackName(
|
||||||
packName,
|
packName,
|
||||||
)}`,
|
)}, but wasn't returned by codeql resolve qlpacks --kind extension --no-recursive`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -155,6 +155,26 @@ export async function pickExtensionPack(
|
|||||||
return writeExtensionPack(packPath, packName, databaseItem.language);
|
return writeExtensionPack(packPath, packName, databaseItem.language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getModelConfigPackVariables(
|
||||||
|
databaseItem: Pick<DatabaseItem, "name" | "language" | "origin">,
|
||||||
|
): ModelConfigPackVariables {
|
||||||
|
const database = databaseItem.name;
|
||||||
|
const language = databaseItem.language;
|
||||||
|
let name = databaseItem.name;
|
||||||
|
let owner = "";
|
||||||
|
|
||||||
|
if (databaseItem.origin?.type === "github") {
|
||||||
|
[owner, name] = databaseItem.origin.repository.split("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
database,
|
||||||
|
language,
|
||||||
|
name,
|
||||||
|
owner,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function writeExtensionPack(
|
async function writeExtensionPack(
|
||||||
packPath: string,
|
packPath: string,
|
||||||
packName: ExtensionPackName,
|
packName: ExtensionPackName,
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import type { WorkspaceFolder } from "vscode";
|
import type { WorkspaceFolder } from "vscode";
|
||||||
import { FileType, Uri, workspace } from "vscode";
|
import { FileType, Uri, workspace } from "vscode";
|
||||||
import { getOnDiskWorkspaceFoldersObjects } from "../common/vscode/workspace-folders";
|
import { getOnDiskWorkspaceFoldersObjects } from "../common/vscode/workspace-folders";
|
||||||
import { tmpdir } from "../common/files";
|
import { containsPath, tmpdir } from "../common/files";
|
||||||
import type { NotificationLogger } from "../common/logging";
|
import type { NotificationLogger } from "../common/logging";
|
||||||
import { showAndLogErrorMessage } from "../common/logging";
|
import { showAndLogErrorMessage } from "../common/logging";
|
||||||
|
import { isAbsolute, normalize, resolve } from "path";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import type { ModelConfig } from "../config";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ancestors of this path in order from furthest to closest (i.e. root of filesystem to parent directory)
|
* Returns the ancestors of this path in order from furthest to closest (i.e. root of filesystem to parent directory)
|
||||||
@@ -22,26 +25,17 @@ function getAncestors(uri: Uri): Uri[] {
|
|||||||
return ancestors;
|
return ancestors;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getRootWorkspaceDirectory(): Promise<Uri | undefined> {
|
function findCommonAncestor(uris: Uri[]): Uri | undefined {
|
||||||
// If there is a valid workspace file, just use its directory as the directory for the extensions
|
if (uris.length === 0) {
|
||||||
const workspaceFile = workspace.workspaceFile;
|
return undefined;
|
||||||
if (workspaceFile?.scheme === "file") {
|
|
||||||
return Uri.joinPath(workspaceFile, "..");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const allWorkspaceFolders = getOnDiskWorkspaceFoldersObjects();
|
if (uris.length === 1) {
|
||||||
|
return uris[0];
|
||||||
// Get the system temp directory and convert it to a URI so it's normalized
|
}
|
||||||
const systemTmpdir = Uri.file(tmpdir());
|
|
||||||
|
|
||||||
const workspaceFolders = allWorkspaceFolders.filter((folder) => {
|
|
||||||
// Never use a workspace folder that is in the system temp directory
|
|
||||||
return !folder.uri.fsPath.startsWith(systemTmpdir.fsPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Find the common root directory of all workspace folders by finding the longest common prefix
|
// Find the common root directory of all workspace folders by finding the longest common prefix
|
||||||
const commonRoot = workspaceFolders.reduce((commonRoot, folder) => {
|
const commonRoot = uris.reduce((commonRoot, folderUri) => {
|
||||||
const folderUri = folder.uri;
|
|
||||||
const ancestors = getAncestors(folderUri);
|
const ancestors = getAncestors(folderUri);
|
||||||
|
|
||||||
const minLength = Math.min(commonRoot.length, ancestors.length);
|
const minLength = Math.min(commonRoot.length, ancestors.length);
|
||||||
@@ -55,10 +49,10 @@ async function getRootWorkspaceDirectory(): Promise<Uri | undefined> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return commonRoot.slice(0, commonLength);
|
return commonRoot.slice(0, commonLength);
|
||||||
}, getAncestors(workspaceFolders[0].uri));
|
}, getAncestors(uris[0]));
|
||||||
|
|
||||||
if (commonRoot.length === 0) {
|
if (commonRoot.length === 0) {
|
||||||
return await findGitFolder(workspaceFolders);
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The path closest to the workspace folders is the last element of the common root
|
// The path closest to the workspace folders is the last element of the common root
|
||||||
@@ -67,6 +61,54 @@ async function getRootWorkspaceDirectory(): Promise<Uri | undefined> {
|
|||||||
// If we are at the root of the filesystem, we can't go up any further and there's something
|
// If we are at the root of the filesystem, we can't go up any further and there's something
|
||||||
// wrong, so just return undefined
|
// wrong, so just return undefined
|
||||||
if (commonRootUri.fsPath === Uri.joinPath(commonRootUri, "..").fsPath) {
|
if (commonRootUri.fsPath === Uri.joinPath(commonRootUri, "..").fsPath) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return commonRootUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the root directory of this workspace. It is determined
|
||||||
|
* heuristically based on the on-disk workspace folders.
|
||||||
|
*
|
||||||
|
* The heuristic is as follows:
|
||||||
|
* 1. If there is a workspace file (`<basename>.code-workspace`), use the directory containing that file
|
||||||
|
* 1. If there is only 1 workspace folder, use that folder=
|
||||||
|
* 3. If there is a common root directory for all workspace folders, use that directory
|
||||||
|
* - Workspace folders in the system temp directory are ignored
|
||||||
|
* - If the common root directory is the root of the filesystem, then it's not used
|
||||||
|
* 5. If there is a .git directory in any workspace folder, use the directory containing that .git directory
|
||||||
|
* for which the .git directory is closest to a workspace folder
|
||||||
|
* 6. If none of the above apply, return `undefined`
|
||||||
|
*/
|
||||||
|
export async function getRootWorkspaceDirectory(): Promise<Uri | undefined> {
|
||||||
|
// If there is a valid workspace file, just use its directory as the directory for the extensions
|
||||||
|
const workspaceFile = workspace.workspaceFile;
|
||||||
|
if (workspaceFile?.scheme === "file") {
|
||||||
|
return Uri.joinPath(workspaceFile, "..");
|
||||||
|
}
|
||||||
|
|
||||||
|
const allWorkspaceFolders = getOnDiskWorkspaceFoldersObjects();
|
||||||
|
|
||||||
|
if (allWorkspaceFolders.length === 1) {
|
||||||
|
return allWorkspaceFolders[0].uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the system temp directory and convert it to a URI so it's normalized
|
||||||
|
const systemTmpdir = Uri.file(tmpdir());
|
||||||
|
|
||||||
|
const workspaceFolders = allWorkspaceFolders.filter((folder) => {
|
||||||
|
// Never use a workspace folder that is in the system temp directory
|
||||||
|
return !folder.uri.fsPath.startsWith(systemTmpdir.fsPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The path closest to the workspace folders is the last element of the common root
|
||||||
|
const commonRootUri = findCommonAncestor(
|
||||||
|
workspaceFolders.map((folder) => folder.uri),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If there is no common root URI, try to find a .git folder in the workspace folders
|
||||||
|
if (commonRootUri === undefined) {
|
||||||
return await findGitFolder(workspaceFolders);
|
return await findGitFolder(workspaceFolders);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,90 +168,130 @@ async function findGitFolder(
|
|||||||
return closestFolder?.[1];
|
return closestFolder?.[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function packLocationToAbsolute(
|
||||||
* Finds a suitable directory for extension packs to be created in. This will
|
packLocation: string,
|
||||||
* always be a path ending in `.github/codeql/extensions`. The parent directory
|
|
||||||
* will be determined heuristically based on the on-disk workspace folders.
|
|
||||||
*
|
|
||||||
* The heuristic is as follows (`.github/codeql/extensions` is added automatically unless
|
|
||||||
* otherwise specified):
|
|
||||||
* 1. If there is only 1 workspace folder, use that folder
|
|
||||||
* 2. If there is a workspace folder for which the path ends in `.github/codeql/extensions`, use that folder
|
|
||||||
* - If there are multiple such folders, use the first one
|
|
||||||
* - Does not append `.github/codeql/extensions` to the path
|
|
||||||
* 3. If there is a workspace file (`<basename>.code-workspace`), use the directory containing that file
|
|
||||||
* 4. If there is a common root directory for all workspace folders, use that directory
|
|
||||||
* - Workspace folders in the system temp directory are ignored
|
|
||||||
* - If the common root directory is the root of the filesystem, then it's not used
|
|
||||||
* 5. If there is a .git directory in any workspace folder, use the directory containing that .git directory
|
|
||||||
* for which the .git directory is closest to a workspace folder
|
|
||||||
* 6. If none of the above apply, return `undefined`
|
|
||||||
*/
|
|
||||||
export async function autoPickExtensionsDirectory(
|
|
||||||
logger: NotificationLogger,
|
logger: NotificationLogger,
|
||||||
): Promise<Uri | undefined> {
|
): Promise<string | undefined> {
|
||||||
const workspaceFolders = getOnDiskWorkspaceFoldersObjects();
|
let userPackLocation = packLocation.trim();
|
||||||
|
|
||||||
// If there are no on-disk workspace folders, we can't do anything
|
if (!isAbsolute(userPackLocation)) {
|
||||||
if (workspaceFolders.length === 0) {
|
const rootDirectory = await getRootWorkspaceDirectory();
|
||||||
|
if (!rootDirectory) {
|
||||||
|
void logger.log("Unable to determine root workspace directory");
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
userPackLocation = resolve(rootDirectory.fsPath, userPackLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
userPackLocation = normalize(userPackLocation);
|
||||||
|
|
||||||
|
if (!isAbsolute(userPackLocation)) {
|
||||||
|
// This shouldn't happen, but just in case
|
||||||
void showAndLogErrorMessage(
|
void showAndLogErrorMessage(
|
||||||
logger,
|
logger,
|
||||||
`Could not find any on-disk workspace folders. Please ensure that you have opened a folder or workspace.`,
|
`Invalid pack location: ${userPackLocation}`,
|
||||||
);
|
);
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's only 1 workspace folder, use the `.github/codeql/extensions` directory in that folder
|
|
||||||
if (workspaceFolders.length === 1) {
|
|
||||||
return Uri.joinPath(
|
|
||||||
workspaceFolders[0].uri,
|
|
||||||
".github",
|
|
||||||
"codeql",
|
|
||||||
"extensions",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try to find a workspace folder for which the path ends in `.github/codeql/extensions`
|
|
||||||
const workspaceFolderForExtensions = workspaceFolders.find((folder) =>
|
|
||||||
// Using path instead of fsPath because path always uses forward slashes
|
|
||||||
folder.uri.path.endsWith(".github/codeql/extensions"),
|
|
||||||
);
|
|
||||||
if (workspaceFolderForExtensions) {
|
|
||||||
return workspaceFolderForExtensions.uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the root workspace directory, i.e. the common root directory of all workspace folders
|
|
||||||
const rootDirectory = await getRootWorkspaceDirectory();
|
|
||||||
if (!rootDirectory) {
|
|
||||||
void logger.log("Unable to determine root workspace directory");
|
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll create a new workspace folder for the extensions in the root workspace directory
|
// If we are at the root of the filesystem, then something is wrong since
|
||||||
// at `.github/codeql/extensions`
|
// this should never be the location of a pack
|
||||||
const extensionsUri = Uri.joinPath(
|
if (userPackLocation === resolve(userPackLocation, "..")) {
|
||||||
rootDirectory,
|
void showAndLogErrorMessage(
|
||||||
".github",
|
logger,
|
||||||
"codeql",
|
`Invalid pack location: ${userPackLocation}`,
|
||||||
"extensions",
|
);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return userPackLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function will try to add the pack location as a workspace folder if it's not already in a
|
||||||
|
* workspace folder and the workspace is a multi-root workspace.
|
||||||
|
*/
|
||||||
|
export async function ensurePackLocationIsInWorkspaceFolder(
|
||||||
|
packLocation: string,
|
||||||
|
modelConfig: ModelConfig,
|
||||||
|
logger: NotificationLogger,
|
||||||
|
): Promise<void> {
|
||||||
|
const workspaceFolders = getOnDiskWorkspaceFoldersObjects();
|
||||||
|
|
||||||
|
const existsInWorkspaceFolder = workspaceFolders.some((folder) =>
|
||||||
|
containsPath(folder.uri.fsPath, packLocation),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (existsInWorkspaceFolder) {
|
||||||
|
// If the pack location is already in a workspace folder, we don't need to do anything
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspace.workspaceFile === undefined) {
|
||||||
|
// If we're not in a workspace, we can't add a workspace folder without reloading the window,
|
||||||
|
// so we'll not do anything
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To find the "correct" directory to add as a workspace folder, we'll generate a few different
|
||||||
|
// pack locations and find the common ancestor of the directories. This is the directory that
|
||||||
|
// we'll add as a workspace folder.
|
||||||
|
|
||||||
|
// Generate a few different pack locations to get an accurate common ancestor
|
||||||
|
const otherPackLocations = await Promise.all(
|
||||||
|
Array.from({ length: 3 }).map(() =>
|
||||||
|
packLocationToAbsolute(
|
||||||
|
modelConfig.getPackLocation(nanoid(), {
|
||||||
|
database: nanoid(),
|
||||||
|
language: nanoid(),
|
||||||
|
name: nanoid(),
|
||||||
|
owner: nanoid(),
|
||||||
|
}),
|
||||||
|
logger,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const otherPackLocationUris = otherPackLocations
|
||||||
|
.filter((loc): loc is string => loc !== undefined)
|
||||||
|
.map((loc) => Uri.file(loc));
|
||||||
|
|
||||||
|
if (otherPackLocationUris.length === 0) {
|
||||||
|
void logger.log(
|
||||||
|
`Failed to generate different pack locations, not adding workspace folder.`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commonRootUri = findCommonAncestor([
|
||||||
|
Uri.file(packLocation),
|
||||||
|
...otherPackLocationUris,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (commonRootUri === undefined) {
|
||||||
|
void logger.log(
|
||||||
|
`Failed to find common ancestor for ${packLocation} and ${otherPackLocationUris[0].fsPath}, not adding workspace folder.`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!workspace.updateWorkspaceFolders(
|
!workspace.updateWorkspaceFolders(
|
||||||
workspace.workspaceFolders?.length ?? 0,
|
workspace.workspaceFolders?.length ?? 0,
|
||||||
0,
|
0,
|
||||||
{
|
{
|
||||||
name: "CodeQL Extension Packs",
|
name: "CodeQL Extension Packs",
|
||||||
uri: extensionsUri,
|
uri: commonRootUri,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
void logger.log(
|
void logger.log(
|
||||||
`Failed to add workspace folder for extensions at ${extensionsUri.fsPath}`,
|
`Failed to add workspace folder for extensions at ${commonRootUri.fsPath}`,
|
||||||
);
|
);
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return extensionsUri;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { ExtensionPack } from "../../../../src/model-editor/shared/extensio
|
|||||||
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||||
import type { ModelConfig } from "../../../../src/config";
|
import type { ModelConfig } from "../../../../src/config";
|
||||||
import { mockedObject } from "../../utils/mocking.helpers";
|
import { mockedObject } from "../../utils/mocking.helpers";
|
||||||
|
import type { DatabaseItem } from "../../../../src/databases/local-databases";
|
||||||
|
|
||||||
describe("pickExtensionPack", () => {
|
describe("pickExtensionPack", () => {
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
@@ -19,9 +20,16 @@ describe("pickExtensionPack", () => {
|
|||||||
let autoExtensionPack: ExtensionPack;
|
let autoExtensionPack: ExtensionPack;
|
||||||
|
|
||||||
let qlPacks: QlpacksInfo;
|
let qlPacks: QlpacksInfo;
|
||||||
const databaseItem = {
|
const databaseItem: Pick<DatabaseItem, "name" | "language" | "origin"> = {
|
||||||
name: "github/vscode-codeql",
|
name: "github/vscode-codeql",
|
||||||
language: "java",
|
language: "java",
|
||||||
|
origin: {
|
||||||
|
type: "github",
|
||||||
|
repository: "github/vscode-codeql",
|
||||||
|
databaseId: 123578,
|
||||||
|
databaseCreatedAt: "2021-01-01T00:00:00Z",
|
||||||
|
commitOid: "1234567890abcdef",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const progress = jest.fn();
|
const progress = jest.fn();
|
||||||
@@ -67,7 +75,12 @@ describe("pickExtensionPack", () => {
|
|||||||
.mockReturnValue([workspaceFolder]);
|
.mockReturnValue([workspaceFolder]);
|
||||||
|
|
||||||
modelConfig = mockedObject<ModelConfig>({
|
modelConfig = mockedObject<ModelConfig>({
|
||||||
getExtensionsDirectory: jest.fn().mockReturnValue(undefined),
|
getPackLocation: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(
|
||||||
|
(language, { name }) =>
|
||||||
|
`.github/codeql/extensions/${name}-${language}`,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -90,10 +103,15 @@ describe("pickExtensionPack", () => {
|
|||||||
additionalPacks,
|
additionalPacks,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
expect(modelConfig.getExtensionsDirectory).toHaveBeenCalledWith("java");
|
expect(modelConfig.getPackLocation).toHaveBeenCalledWith("java", {
|
||||||
|
database: "github/vscode-codeql",
|
||||||
|
language: "java",
|
||||||
|
name: "vscode-codeql",
|
||||||
|
owner: "github",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates a new extension pack using default extensions directory", async () => {
|
it("creates a new extension pack using default pack location", async () => {
|
||||||
const tmpDir = await dir({
|
const tmpDir = await dir({
|
||||||
unsafeCleanup: true,
|
unsafeCleanup: true,
|
||||||
});
|
});
|
||||||
@@ -154,7 +172,12 @@ describe("pickExtensionPack", () => {
|
|||||||
dataExtensions: ["models/**/*.yml"],
|
dataExtensions: ["models/**/*.yml"],
|
||||||
});
|
});
|
||||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
expect(modelConfig.getExtensionsDirectory).toHaveBeenCalledWith("java");
|
expect(modelConfig.getPackLocation).toHaveBeenCalledWith("java", {
|
||||||
|
database: "github/vscode-codeql",
|
||||||
|
language: "java",
|
||||||
|
name: "vscode-codeql",
|
||||||
|
owner: "github",
|
||||||
|
});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
|
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
|
||||||
@@ -169,22 +192,147 @@ describe("pickExtensionPack", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates a new extension pack when extensions directory is set in config", async () => {
|
it("creates a new extension pack when absolute custom pack location is set in config", async () => {
|
||||||
|
const packLocation = join(Uri.file(tmpDir).fsPath, "java/ql/lib");
|
||||||
|
|
||||||
|
const modelConfig = mockedObject<ModelConfig>({
|
||||||
|
getPackLocation: jest.fn().mockReturnValue(packLocation),
|
||||||
|
});
|
||||||
|
|
||||||
|
const cliServer = mockCliServer({});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPack(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
|
logger,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
maxStep,
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
path: packLocation,
|
||||||
|
yamlPath: join(packLocation, "codeql-pack.yml"),
|
||||||
|
name: autoExtensionPackName,
|
||||||
|
version: "0.0.0",
|
||||||
|
language: "java",
|
||||||
|
extensionTargets: {
|
||||||
|
"codeql/java-all": "*",
|
||||||
|
},
|
||||||
|
dataExtensions: ["models/**/*.yml"],
|
||||||
|
});
|
||||||
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
|
expect(modelConfig.getPackLocation).toHaveBeenCalledWith("java", {
|
||||||
|
database: "github/vscode-codeql",
|
||||||
|
language: "java",
|
||||||
|
name: "vscode-codeql",
|
||||||
|
owner: "github",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
loadYaml(await readFile(join(packLocation, "codeql-pack.yml"), "utf8")),
|
||||||
|
).toEqual({
|
||||||
|
name: autoExtensionPackName,
|
||||||
|
version: "0.0.0",
|
||||||
|
library: true,
|
||||||
|
extensionTargets: {
|
||||||
|
"codeql/java-all": "*",
|
||||||
|
},
|
||||||
|
dataExtensions: ["models/**/*.yml"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("creates a new extension pack when relative custom pack location is set in config", async () => {
|
||||||
|
const packLocation = join(Uri.file(tmpDir).fsPath, "java/ql/lib");
|
||||||
|
|
||||||
|
const modelConfig = mockedObject<ModelConfig>({
|
||||||
|
getPackLocation: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((language) => `${language}/ql/lib`),
|
||||||
|
});
|
||||||
|
|
||||||
|
const cliServer = mockCliServer({});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await pickExtensionPack(
|
||||||
|
cliServer,
|
||||||
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
|
logger,
|
||||||
|
progress,
|
||||||
|
token,
|
||||||
|
maxStep,
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
path: packLocation,
|
||||||
|
yamlPath: join(packLocation, "codeql-pack.yml"),
|
||||||
|
name: autoExtensionPackName,
|
||||||
|
version: "0.0.0",
|
||||||
|
language: "java",
|
||||||
|
extensionTargets: {
|
||||||
|
"codeql/java-all": "*",
|
||||||
|
},
|
||||||
|
dataExtensions: ["models/**/*.yml"],
|
||||||
|
});
|
||||||
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
|
expect(modelConfig.getPackLocation).toHaveBeenCalledWith("java", {
|
||||||
|
database: "github/vscode-codeql",
|
||||||
|
language: "java",
|
||||||
|
name: "vscode-codeql",
|
||||||
|
owner: "github",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
loadYaml(await readFile(join(packLocation, "codeql-pack.yml"), "utf8")),
|
||||||
|
).toEqual({
|
||||||
|
name: autoExtensionPackName,
|
||||||
|
version: "0.0.0",
|
||||||
|
library: true,
|
||||||
|
extensionTargets: {
|
||||||
|
"codeql/java-all": "*",
|
||||||
|
},
|
||||||
|
dataExtensions: ["models/**/*.yml"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("creates a new extension pack with non-github origin database", async () => {
|
||||||
|
const databaseItem: Pick<DatabaseItem, "name" | "language" | "origin"> = {
|
||||||
|
name: "vscode-codeql",
|
||||||
|
language: "java",
|
||||||
|
origin: {
|
||||||
|
type: "archive",
|
||||||
|
path: "/path/to/codeql-database.zip",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const tmpDir = await dir({
|
const tmpDir = await dir({
|
||||||
unsafeCleanup: true,
|
unsafeCleanup: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const configExtensionsDir = join(
|
workspaceFoldersSpy.mockReturnValue([
|
||||||
|
{
|
||||||
|
uri: Uri.joinPath(Uri.file(tmpDir.path), "codeql-custom-queries-java"),
|
||||||
|
name: "codeql-custom-queries-java",
|
||||||
|
index: 2,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspace, "workspaceFile", "get")
|
||||||
|
.mockReturnValue(
|
||||||
|
Uri.joinPath(Uri.file(tmpDir.path), "workspace.code-workspace"),
|
||||||
|
);
|
||||||
|
jest.spyOn(workspace, "updateWorkspaceFolders").mockReturnValue(true);
|
||||||
|
|
||||||
|
const newPackDir = join(
|
||||||
Uri.file(tmpDir.path).fsPath,
|
Uri.file(tmpDir.path).fsPath,
|
||||||
"my-custom-extensions-directory",
|
".github",
|
||||||
|
"codeql",
|
||||||
|
"extensions",
|
||||||
|
"vscode-codeql-java",
|
||||||
);
|
);
|
||||||
|
|
||||||
const modelConfig = mockedObject<ModelConfig>({
|
|
||||||
getExtensionsDirectory: jest.fn().mockReturnValue(configExtensionsDir),
|
|
||||||
});
|
|
||||||
|
|
||||||
const newPackDir = join(configExtensionsDir, "vscode-codeql-java");
|
|
||||||
|
|
||||||
const cliServer = mockCliServer({});
|
const cliServer = mockCliServer({});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -200,7 +348,7 @@ describe("pickExtensionPack", () => {
|
|||||||
).toEqual({
|
).toEqual({
|
||||||
path: newPackDir,
|
path: newPackDir,
|
||||||
yamlPath: join(newPackDir, "codeql-pack.yml"),
|
yamlPath: join(newPackDir, "codeql-pack.yml"),
|
||||||
name: autoExtensionPackName,
|
name: "pack/vscode-codeql-java",
|
||||||
version: "0.0.0",
|
version: "0.0.0",
|
||||||
language: "java",
|
language: "java",
|
||||||
extensionTargets: {
|
extensionTargets: {
|
||||||
@@ -209,12 +357,17 @@ describe("pickExtensionPack", () => {
|
|||||||
dataExtensions: ["models/**/*.yml"],
|
dataExtensions: ["models/**/*.yml"],
|
||||||
});
|
});
|
||||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
expect(modelConfig.getExtensionsDirectory).toHaveBeenCalledWith("java");
|
expect(modelConfig.getPackLocation).toHaveBeenCalledWith("java", {
|
||||||
|
database: "vscode-codeql",
|
||||||
|
language: "java",
|
||||||
|
name: "vscode-codeql",
|
||||||
|
owner: "",
|
||||||
|
});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
|
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
name: autoExtensionPackName,
|
name: "pack/vscode-codeql-java",
|
||||||
version: "0.0.0",
|
version: "0.0.0",
|
||||||
library: true,
|
library: true,
|
||||||
extensionTargets: {
|
extensionTargets: {
|
||||||
|
|||||||
@@ -3,27 +3,28 @@ import { Uri, workspace } from "vscode";
|
|||||||
import type { DirectoryResult } from "tmp-promise";
|
import type { DirectoryResult } from "tmp-promise";
|
||||||
import { dir } from "tmp-promise";
|
import { dir } from "tmp-promise";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { autoPickExtensionsDirectory } from "../../../../src/model-editor/extensions-workspace-folder";
|
import {
|
||||||
|
ensurePackLocationIsInWorkspaceFolder,
|
||||||
|
getRootWorkspaceDirectory,
|
||||||
|
packLocationToAbsolute,
|
||||||
|
} from "../../../../src/model-editor/extensions-workspace-folder";
|
||||||
import * as files from "../../../../src/common/files";
|
import * as files from "../../../../src/common/files";
|
||||||
import { mkdirp } from "fs-extra";
|
import { mkdirp } from "fs-extra";
|
||||||
import type { NotificationLogger } from "../../../../src/common/logging";
|
import type { NotificationLogger } from "../../../../src/common/logging";
|
||||||
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||||
|
import { mockedObject } from "../../../mocked-object";
|
||||||
|
import type { ModelConfig } from "../../../../src/config";
|
||||||
|
|
||||||
describe("autoPickExtensionsDirectory", () => {
|
describe("getRootWorkspaceDirectory", () => {
|
||||||
let tmpDir: DirectoryResult;
|
let tmpDir: DirectoryResult;
|
||||||
let rootDirectory: Uri;
|
let rootDirectory: Uri;
|
||||||
let extensionsDirectory: Uri;
|
|
||||||
|
|
||||||
let workspaceFoldersSpy: jest.SpyInstance<
|
let workspaceFoldersSpy: jest.SpyInstance<
|
||||||
readonly WorkspaceFolder[] | undefined,
|
readonly WorkspaceFolder[] | undefined,
|
||||||
[]
|
[]
|
||||||
>;
|
>;
|
||||||
let workspaceFileSpy: jest.SpyInstance<Uri | undefined, []>;
|
let workspaceFileSpy: jest.SpyInstance<Uri | undefined, []>;
|
||||||
let updateWorkspaceFoldersSpy: jest.SpiedFunction<
|
|
||||||
typeof workspace.updateWorkspaceFolders
|
|
||||||
>;
|
|
||||||
let mockedTmpDirUri: Uri;
|
let mockedTmpDirUri: Uri;
|
||||||
let logger: NotificationLogger;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tmpDir = await dir({
|
tmpDir = await dir({
|
||||||
@@ -31,12 +32,6 @@ describe("autoPickExtensionsDirectory", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
rootDirectory = Uri.joinPath(Uri.file(tmpDir.path), "root");
|
rootDirectory = Uri.joinPath(Uri.file(tmpDir.path), "root");
|
||||||
extensionsDirectory = Uri.joinPath(
|
|
||||||
rootDirectory,
|
|
||||||
".github",
|
|
||||||
"codeql",
|
|
||||||
"extensions",
|
|
||||||
);
|
|
||||||
|
|
||||||
const mockedTmpDir = join(tmpDir.path, ".tmp", "tmp");
|
const mockedTmpDir = join(tmpDir.path, ".tmp", "tmp");
|
||||||
mockedTmpDirUri = Uri.file(mockedTmpDir);
|
mockedTmpDirUri = Uri.file(mockedTmpDir);
|
||||||
@@ -47,44 +42,14 @@ describe("autoPickExtensionsDirectory", () => {
|
|||||||
workspaceFileSpy = jest
|
workspaceFileSpy = jest
|
||||||
.spyOn(workspace, "workspaceFile", "get")
|
.spyOn(workspace, "workspaceFile", "get")
|
||||||
.mockReturnValue(undefined);
|
.mockReturnValue(undefined);
|
||||||
updateWorkspaceFoldersSpy = jest
|
|
||||||
.spyOn(workspace, "updateWorkspaceFolders")
|
|
||||||
.mockReturnValue(true);
|
|
||||||
|
|
||||||
jest.spyOn(files, "tmpdir").mockReturnValue(mockedTmpDir);
|
jest.spyOn(files, "tmpdir").mockReturnValue(mockedTmpDir);
|
||||||
|
|
||||||
logger = createMockLogger();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await tmpDir.cleanup();
|
await tmpDir.cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when a workspace folder with the correct path exists", async () => {
|
|
||||||
workspaceFoldersSpy.mockReturnValue([
|
|
||||||
{
|
|
||||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
|
|
||||||
name: "codeql-custom-queries-java",
|
|
||||||
index: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
|
||||||
name: "codeql-custom-queries-python",
|
|
||||||
index: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: extensionsDirectory,
|
|
||||||
name: "CodeQL Extension Packs",
|
|
||||||
index: 2,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(await autoPickExtensionsDirectory(logger)).toEqual(
|
|
||||||
extensionsDirectory,
|
|
||||||
);
|
|
||||||
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when a workspace file exists", async () => {
|
it("when a workspace file exists", async () => {
|
||||||
workspaceFoldersSpy.mockReturnValue([
|
workspaceFoldersSpy.mockReturnValue([
|
||||||
{
|
{
|
||||||
@@ -103,36 +68,7 @@ describe("autoPickExtensionsDirectory", () => {
|
|||||||
Uri.joinPath(rootDirectory, "workspace.code-workspace"),
|
Uri.joinPath(rootDirectory, "workspace.code-workspace"),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(await autoPickExtensionsDirectory(logger)).toEqual(
|
expect(await getRootWorkspaceDirectory()).toEqual(rootDirectory);
|
||||||
extensionsDirectory,
|
|
||||||
);
|
|
||||||
expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(2, 0, {
|
|
||||||
name: "CodeQL Extension Packs",
|
|
||||||
uri: extensionsDirectory,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when updating the workspace folders fails", async () => {
|
|
||||||
updateWorkspaceFoldersSpy.mockReturnValue(false);
|
|
||||||
|
|
||||||
workspaceFoldersSpy.mockReturnValue([
|
|
||||||
{
|
|
||||||
uri: Uri.file("/a/b/c"),
|
|
||||||
name: "codeql-custom-queries-java",
|
|
||||||
index: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
|
||||||
name: "codeql-custom-queries-python",
|
|
||||||
index: 1,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
workspaceFileSpy.mockReturnValue(
|
|
||||||
Uri.joinPath(rootDirectory, "workspace.code-workspace"),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(await autoPickExtensionsDirectory(logger)).toEqual(undefined);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when a workspace file does not exist and there is a common root directory", async () => {
|
it("when a workspace file does not exist and there is a common root directory", async () => {
|
||||||
@@ -149,13 +85,9 @@ describe("autoPickExtensionsDirectory", () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(await autoPickExtensionsDirectory(logger)).toEqual(
|
expect((await getRootWorkspaceDirectory())?.fsPath).toEqual(
|
||||||
extensionsDirectory,
|
rootDirectory.fsPath,
|
||||||
);
|
);
|
||||||
expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(2, 0, {
|
|
||||||
name: "CodeQL Extension Packs",
|
|
||||||
uri: extensionsDirectory,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when a workspace file does not exist and there is a temp dir as workspace folder", async () => {
|
it("when a workspace file does not exist and there is a temp dir as workspace folder", async () => {
|
||||||
@@ -177,13 +109,9 @@ describe("autoPickExtensionsDirectory", () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(await autoPickExtensionsDirectory(logger)).toEqual(
|
expect((await getRootWorkspaceDirectory())?.fsPath).toEqual(
|
||||||
extensionsDirectory,
|
rootDirectory.fsPath,
|
||||||
);
|
);
|
||||||
expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(3, 0, {
|
|
||||||
name: "CodeQL Extension Packs",
|
|
||||||
uri: extensionsDirectory,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when a workspace file does not exist and there is no common root directory", async () => {
|
it("when a workspace file does not exist and there is no common root directory", async () => {
|
||||||
@@ -200,8 +128,7 @@ describe("autoPickExtensionsDirectory", () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(await autoPickExtensionsDirectory(logger)).toEqual(undefined);
|
expect(await getRootWorkspaceDirectory()).toBeUndefined();
|
||||||
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when a workspace file does not exist and there is a .git folder", async () => {
|
it("when a workspace file does not exist and there is a .git folder", async () => {
|
||||||
@@ -220,13 +147,7 @@ describe("autoPickExtensionsDirectory", () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(await autoPickExtensionsDirectory(logger)).toEqual(
|
expect(await getRootWorkspaceDirectory()).toEqual(rootDirectory);
|
||||||
extensionsDirectory,
|
|
||||||
);
|
|
||||||
expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(2, 0, {
|
|
||||||
name: "CodeQL Extension Packs",
|
|
||||||
uri: extensionsDirectory,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when there is no on-disk workspace folder", async () => {
|
it("when there is no on-disk workspace folder", async () => {
|
||||||
@@ -238,10 +159,268 @@ describe("autoPickExtensionsDirectory", () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(await autoPickExtensionsDirectory(logger)).toEqual(undefined);
|
expect(await getRootWorkspaceDirectory()).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("packLocationToAbsolute", () => {
|
||||||
|
let tmpDir: DirectoryResult;
|
||||||
|
let rootDirectory: Uri;
|
||||||
|
let extensionsDirectory: Uri;
|
||||||
|
|
||||||
|
let logger: NotificationLogger;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
rootDirectory = Uri.joinPath(Uri.file(tmpDir.path), "root");
|
||||||
|
extensionsDirectory = Uri.joinPath(
|
||||||
|
rootDirectory,
|
||||||
|
".github",
|
||||||
|
"codeql",
|
||||||
|
"extensions",
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockedTmpDir = join(tmpDir.path, ".tmp", "tmp");
|
||||||
|
|
||||||
|
jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([]);
|
||||||
|
jest
|
||||||
|
.spyOn(workspace, "workspaceFile", "get")
|
||||||
|
.mockReturnValue(Uri.joinPath(rootDirectory, "workspace.code-workspace"));
|
||||||
|
|
||||||
|
jest.spyOn(files, "tmpdir").mockReturnValue(mockedTmpDir);
|
||||||
|
|
||||||
|
logger = createMockLogger();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await tmpDir.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when the location is absolute", async () => {
|
||||||
|
expect(
|
||||||
|
await packLocationToAbsolute(extensionsDirectory.fsPath, logger),
|
||||||
|
).toEqual(extensionsDirectory.fsPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when the location is relative", async () => {
|
||||||
|
expect(
|
||||||
|
await packLocationToAbsolute(".github/codeql/extensions/my-pack", logger),
|
||||||
|
).toEqual(Uri.joinPath(extensionsDirectory, "my-pack").fsPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when the location is invalid", async () => {
|
||||||
|
expect(
|
||||||
|
await packLocationToAbsolute("../".repeat(100), logger),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("ensurePackLocationIsInWorkspaceFolder", () => {
|
||||||
|
let tmpDir: DirectoryResult;
|
||||||
|
let rootDirectory: Uri;
|
||||||
|
let extensionsDirectory: Uri;
|
||||||
|
let packLocation: string;
|
||||||
|
|
||||||
|
let workspaceFoldersSpy: jest.SpyInstance<
|
||||||
|
readonly WorkspaceFolder[] | undefined,
|
||||||
|
[]
|
||||||
|
>;
|
||||||
|
let workspaceFileSpy: jest.SpyInstance<Uri | undefined, []>;
|
||||||
|
let updateWorkspaceFoldersSpy: jest.SpiedFunction<
|
||||||
|
typeof workspace.updateWorkspaceFolders
|
||||||
|
>;
|
||||||
|
|
||||||
|
let getPackLocation: jest.MockedFunction<ModelConfig["getPackLocation"]>;
|
||||||
|
let modelConfig: ModelConfig;
|
||||||
|
let logger: NotificationLogger;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
tmpDir = await dir({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
rootDirectory = Uri.joinPath(Uri.file(tmpDir.path), "root");
|
||||||
|
extensionsDirectory = Uri.joinPath(
|
||||||
|
rootDirectory,
|
||||||
|
".github",
|
||||||
|
"codeql",
|
||||||
|
"extensions",
|
||||||
|
);
|
||||||
|
packLocation = Uri.joinPath(extensionsDirectory, "my-pack").fsPath;
|
||||||
|
|
||||||
|
workspaceFoldersSpy = jest
|
||||||
|
.spyOn(workspace, "workspaceFolders", "get")
|
||||||
|
.mockReturnValue([
|
||||||
|
{
|
||||||
|
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
|
||||||
|
name: "codeql-custom-queries-java",
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||||
|
name: "codeql-custom-queries-python",
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
workspaceFileSpy = jest
|
||||||
|
.spyOn(workspace, "workspaceFile", "get")
|
||||||
|
.mockReturnValue(Uri.joinPath(rootDirectory, "workspace.code-workspace"));
|
||||||
|
updateWorkspaceFoldersSpy = jest
|
||||||
|
.spyOn(workspace, "updateWorkspaceFolders")
|
||||||
|
.mockReturnValue(true);
|
||||||
|
|
||||||
|
logger = createMockLogger();
|
||||||
|
|
||||||
|
getPackLocation = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(
|
||||||
|
(language, { name }) =>
|
||||||
|
Uri.joinPath(extensionsDirectory, `${name}-${language}`).fsPath,
|
||||||
|
);
|
||||||
|
modelConfig = mockedObject<ModelConfig>({
|
||||||
|
getPackLocation,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await tmpDir.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when there is no workspace file", async () => {
|
||||||
|
workspaceFileSpy.mockReturnValue(undefined);
|
||||||
|
|
||||||
|
await ensurePackLocationIsInWorkspaceFolder(
|
||||||
|
packLocation,
|
||||||
|
modelConfig,
|
||||||
|
logger,
|
||||||
|
);
|
||||||
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
|
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
|
||||||
expect(logger.showErrorMessage).toHaveBeenCalledWith(
|
});
|
||||||
"Could not find any on-disk workspace folders. Please ensure that you have opened a folder or workspace.",
|
|
||||||
|
it("when a workspace folder with the correct path exists", async () => {
|
||||||
|
workspaceFoldersSpy.mockReturnValue([
|
||||||
|
{
|
||||||
|
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
|
||||||
|
name: "codeql-custom-queries-java",
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||||
|
name: "codeql-custom-queries-python",
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: extensionsDirectory,
|
||||||
|
name: "CodeQL Extension Packs",
|
||||||
|
index: 2,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await ensurePackLocationIsInWorkspaceFolder(
|
||||||
|
packLocation,
|
||||||
|
modelConfig,
|
||||||
|
logger,
|
||||||
|
);
|
||||||
|
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when a workspace folder with the correct path does not exist in an unsaved workspace", async () => {
|
||||||
|
workspaceFileSpy.mockReturnValue(Uri.parse("untitled:1555503116870"));
|
||||||
|
workspaceFoldersSpy.mockReturnValue([
|
||||||
|
{
|
||||||
|
uri: Uri.file("/a/b/c"),
|
||||||
|
name: "codeql-custom-queries-java",
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||||
|
name: "codeql-custom-queries-python",
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await ensurePackLocationIsInWorkspaceFolder(
|
||||||
|
packLocation,
|
||||||
|
modelConfig,
|
||||||
|
logger,
|
||||||
|
);
|
||||||
|
expect(updateWorkspaceFoldersSpy).toHaveBeenLastCalledWith(2, 0, {
|
||||||
|
name: "CodeQL Extension Packs",
|
||||||
|
uri: expect.any(Uri),
|
||||||
|
});
|
||||||
|
expect(updateWorkspaceFoldersSpy.mock.lastCall?.[2].uri.fsPath).toEqual(
|
||||||
|
extensionsDirectory.fsPath,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when a workspace folder with the correct path does not exist in a saved workspace", async () => {
|
||||||
|
workspaceFoldersSpy.mockReturnValue([
|
||||||
|
{
|
||||||
|
uri: Uri.file("/a/b/c"),
|
||||||
|
name: "codeql-custom-queries-java",
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||||
|
name: "codeql-custom-queries-python",
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await ensurePackLocationIsInWorkspaceFolder(
|
||||||
|
packLocation,
|
||||||
|
modelConfig,
|
||||||
|
logger,
|
||||||
|
);
|
||||||
|
expect(updateWorkspaceFoldersSpy).toHaveBeenLastCalledWith(2, 0, {
|
||||||
|
name: "CodeQL Extension Packs",
|
||||||
|
uri: expect.any(Uri),
|
||||||
|
});
|
||||||
|
expect(updateWorkspaceFoldersSpy.mock.lastCall?.[2].uri.fsPath).toEqual(
|
||||||
|
extensionsDirectory.fsPath,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when all other pack locations are invalid", async () => {
|
||||||
|
getPackLocation.mockReturnValue("/");
|
||||||
|
|
||||||
|
await ensurePackLocationIsInWorkspaceFolder(
|
||||||
|
packLocation,
|
||||||
|
modelConfig,
|
||||||
|
logger,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when there is no common root directory", async () => {
|
||||||
|
getPackLocation.mockImplementation(
|
||||||
|
(language, { name }) => `/${name}-${language}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await ensurePackLocationIsInWorkspaceFolder(
|
||||||
|
packLocation,
|
||||||
|
modelConfig,
|
||||||
|
logger,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when updating the workspace folders fails", async () => {
|
||||||
|
updateWorkspaceFoldersSpy.mockReturnValue(false);
|
||||||
|
|
||||||
|
await ensurePackLocationIsInWorkspaceFolder(
|
||||||
|
packLocation,
|
||||||
|
modelConfig,
|
||||||
|
logger,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(logger.log).toHaveBeenCalledWith(
|
||||||
|
`Failed to add workspace folder for extensions at ${extensionsDirectory.fsPath}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user