Merge pull request #3543 from github/koesie10/pack-name-config

Add `codeQL.model.packName` setting
This commit is contained in:
Koen Vlaswinkel
2024-04-10 17:03:36 +02:00
committed by GitHub
6 changed files with 213 additions and 54 deletions

View File

@@ -470,6 +470,11 @@
"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"
},
"codeQL.model.packName": {
"type": "string",
"default": "${owner}/${name}-${language}",
"markdownDescription": "Name of newly created CodeQL model packs. If the result is not a valid pack name, it will be converted to a valid pack name.\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"
}
}
},

View File

@@ -736,6 +736,7 @@ const LLM_GENERATION_DEV_ENDPOINT = new Setting(
);
const MODEL_EVALUATION = new Setting("evaluation", MODEL_SETTING);
const MODEL_PACK_LOCATION = new Setting("packLocation", MODEL_SETTING);
const MODEL_PACK_NAME = new Setting("packName", MODEL_SETTING);
const ENABLE_PYTHON = new Setting("enablePython", MODEL_SETTING);
const ENABLE_ACCESS_PATH_SUGGESTIONS = new Setting(
"enableAccessPathSuggestions",
@@ -757,6 +758,7 @@ export interface ModelConfig {
languageId: string,
variables: ModelConfigPackVariables,
): string;
getPackName(languageId: string, variables: ModelConfigPackVariables): string;
enablePython: boolean;
enableAccessPathSuggestions: boolean;
}
@@ -810,6 +812,18 @@ export class ModelConfigListener extends ConfigListener implements ModelConfig {
);
}
public getPackName(
languageId: string,
variables: ModelConfigPackVariables,
): string {
return substituteConfigVariables(
MODEL_PACK_NAME.getValue<string>({
languageId,
}),
variables,
);
}
public get enablePython(): boolean {
return !!ENABLE_PYTHON.getValue<boolean>();
}

View File

@@ -15,11 +15,19 @@ export function formatPackName(packName: ExtensionPackName): string {
return `${packName.scope}/${packName.name}`;
}
export function autoNameExtensionPack(
name: string,
language: string,
): ExtensionPackName | undefined {
let packName = `${name}-${language}`;
export function sanitizePackName(userPackName: string): ExtensionPackName {
let packName = userPackName;
packName = packName.trim();
while (packName.startsWith("/")) {
packName = packName.slice(1);
}
while (packName.endsWith("/")) {
packName = packName.slice(0, -1);
}
if (!packName.includes("/")) {
packName = `pack/${packName}`;
}

View File

@@ -15,7 +15,11 @@ import type { NotificationLogger } from "../common/logging";
import { showAndLogErrorMessage } from "../common/logging";
import type { ModelConfig, ModelConfigPackVariables } from "../config";
import type { ExtensionPackName } from "./extension-pack-name";
import { autoNameExtensionPack, formatPackName } from "./extension-pack-name";
import {
validatePackName,
sanitizePackName,
formatPackName,
} from "./extension-pack-name";
import {
ensurePackLocationIsInWorkspaceFolder,
packLocationToAbsolute,
@@ -80,15 +84,20 @@ export async function pickExtensionPack(
await ensurePackLocationIsInWorkspaceFolder(packPath, modelConfig, logger);
// Generate the name of the extension pack
const packName = autoNameExtensionPack(
databaseItem.name,
const userPackName = modelConfig.getPackName(
databaseItem.language,
getModelConfigPackVariables(databaseItem),
);
if (!packName) {
// Generate the name of the extension pack
const packName = sanitizePackName(userPackName);
// Validate that the name isn't too long etc.
const packNameError = validatePackName(formatPackName(packName));
if (packNameError) {
void showAndLogErrorMessage(
logger,
`Could not automatically name extension pack for database ${databaseItem.name}`,
`Invalid model pack name '${formatPackName(packName)}' for database ${databaseItem.name}: ${packNameError}`,
);
return undefined;

View File

@@ -1,82 +1,81 @@
import {
autoNameExtensionPack,
sanitizePackName,
formatPackName,
parsePackName,
validatePackName,
} from "../../../src/model-editor/extension-pack-name";
describe("autoNameExtensionPack", () => {
describe("sanitizePackName", () => {
const testCases: Array<{
name: string;
language: string;
expected: string;
}> = [
{
name: "github/vscode-codeql",
language: "javascript",
name: "github/vscode-codeql-javascript",
expected: "github/vscode-codeql-javascript",
},
{
name: "vscode-codeql",
language: "a",
name: "vscode-codeql-a",
expected: "pack/vscode-codeql-a",
},
{
name: "b",
language: "java",
name: "b-java",
expected: "pack/b-java",
},
{
name: "a/b",
language: "csharp",
name: "a/b-csharp",
expected: "a/b-csharp",
},
{
name: "-/b",
language: "csharp",
name: "-/b-csharp",
expected: "pack/b-csharp",
},
{
name: "a/b/c/d",
language: "csharp",
name: "a/b/c/d-csharp",
expected: "a/b-c-d-csharp",
},
{
name: "JAVA/CodeQL",
language: "csharp",
name: "JAVA/CodeQL-csharp",
expected: "java/codeql-csharp",
},
{
name: "my new pack",
language: "swift",
name: "my new pack-swift",
expected: "pack/my-new-pack-swift",
},
{
name: "gïthub/vscode-codeql",
language: "javascript",
name: "gïthub/vscode-codeql-javascript",
expected: "gthub/vscode-codeql-javascript",
},
{
name: "a/b-",
language: "csharp",
name: "a/b-csharp",
expected: "a/b-csharp",
},
{
name: "-a-/b",
language: "ruby",
name: "-a-/b-ruby",
expected: "a/b-ruby",
},
{
name: "a/b--d--e-d-",
language: "csharp",
name: "a/b--d--e-d-csharp",
expected: "a/b-d-e-d-csharp",
},
{
name: "/github/vscode-codeql",
expected: "github/vscode-codeql",
},
{
name: "github/vscode-codeql/",
expected: "github/vscode-codeql",
},
{
name: "///github/vscode-codeql///",
expected: "github/vscode-codeql",
},
];
test.each(testCases)(
"$name with $language = $expected",
({ name, language, expected }) => {
const result = autoNameExtensionPack(name, language);
({ name, expected }) => {
const result = sanitizePackName(name);
expect(result).not.toBeUndefined();
if (!result) {
return;

View File

@@ -37,6 +37,9 @@ describe("pickExtensionPack", () => {
let workspaceFoldersSpy: jest.SpyInstance;
let additionalPacks: string[];
let workspaceFolder: WorkspaceFolder;
let getPackLocation: jest.MockedFunction<ModelConfig["getPackLocation"]>;
let getPackName: jest.MockedFunction<ModelConfig["getPackName"]>;
let modelConfig: ModelConfig;
const logger = createMockLogger();
@@ -74,13 +77,20 @@ describe("pickExtensionPack", () => {
.spyOn(workspace, "workspaceFolders", "get")
.mockReturnValue([workspaceFolder]);
getPackLocation = jest
.fn()
.mockImplementation(
(language, { name }) => `.github/codeql/extensions/${name}-${language}`,
);
getPackName = jest
.fn()
.mockImplementation(
(language, { name, owner }) => `${owner}/${name}-${language}`,
);
modelConfig = mockedObject<ModelConfig>({
getPackLocation: jest
.fn()
.mockImplementation(
(language, { name }) =>
`.github/codeql/extensions/${name}-${language}`,
),
getPackLocation,
getPackName,
});
});
@@ -178,6 +188,12 @@ describe("pickExtensionPack", () => {
name: "vscode-codeql",
owner: "github",
});
expect(modelConfig.getPackName).toHaveBeenCalledWith("java", {
database: "github/vscode-codeql",
language: "java",
name: "vscode-codeql",
owner: "github",
});
expect(
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
@@ -195,9 +211,7 @@ describe("pickExtensionPack", () => {
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),
});
getPackLocation.mockReturnValue(packLocation);
const cliServer = mockCliServer({});
@@ -246,11 +260,7 @@ describe("pickExtensionPack", () => {
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`),
});
getPackLocation.mockImplementation((language) => `${language}/ql/lib`);
const cliServer = mockCliServer({});
@@ -296,6 +306,120 @@ describe("pickExtensionPack", () => {
});
});
it("creates a new extension pack when valid custom pack name is set in config", async () => {
const packName = "codeql/java-extensions";
const packLocation = join(
Uri.file(tmpDir).fsPath,
".github",
"codeql",
"extensions",
"vscode-codeql-java",
);
getPackName.mockImplementation(
(language) => `codeql/${language}-extensions`,
);
const cliServer = mockCliServer({});
expect(
await pickExtensionPack(
cliServer,
databaseItem,
modelConfig,
logger,
progress,
token,
maxStep,
),
).toEqual({
path: packLocation,
yamlPath: join(packLocation, "codeql-pack.yml"),
name: packName,
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: packName,
version: "0.0.0",
library: true,
extensionTargets: {
"codeql/java-all": "*",
},
dataExtensions: ["models/**/*.yml"],
});
});
it("creates a new extension pack when invalid custom pack name is set in config", async () => {
const packName = "pack/java-extensions";
const packLocation = join(
Uri.file(tmpDir).fsPath,
".github",
"codeql",
"extensions",
"vscode-codeql-java",
);
getPackName.mockImplementation((language) => `${language} Extensions`);
const cliServer = mockCliServer({});
expect(
await pickExtensionPack(
cliServer,
databaseItem,
modelConfig,
logger,
progress,
token,
maxStep,
),
).toEqual({
path: packLocation,
yamlPath: join(packLocation, "codeql-pack.yml"),
name: packName,
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: packName,
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",