Skip to content

Commit b9c8983

Browse files
authored
Merge pull request #2345 from github/koesie10/filter-extension-packs-for-language
Filter extension packs by database item language
2 parents 6f11eca + 46fbaf0 commit b9c8983

File tree

2 files changed

+459
-318
lines changed

2 files changed

+459
-318
lines changed

extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts

Lines changed: 144 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { ProgressCallback } from "../progress";
1313
import { DatabaseItem } from "../local-databases";
1414
import { getQlPackPath, QLPACK_FILENAMES } from "../pure/ql";
15+
import { getErrorMessage } from "../pure/helpers-pure";
1516

1617
const maxStep = 3;
1718

@@ -22,8 +23,14 @@ const packNameRegex = new RegExp(
2223
const packNameLength = 128;
2324

2425
export interface ExtensionPack {
25-
name: string;
2626
path: string;
27+
yamlPath: string;
28+
29+
name: string;
30+
version: string;
31+
32+
extensionTargets: Record<string, string>;
33+
dataExtensions: string[];
2734
}
2835

2936
export interface ExtensionPackModelFile {
@@ -50,7 +57,7 @@ export async function pickExtensionPackModelFile(
5057
const modelFile = await pickModelFile(
5158
cliServer,
5259
databaseItem,
53-
extensionPack.path,
60+
extensionPack,
5461
progress,
5562
token,
5663
);
@@ -78,19 +85,72 @@ async function pickExtensionPack(
7885

7986
// Get all existing extension packs in the workspace
8087
const additionalPacks = getOnDiskWorkspaceFolders();
81-
const extensionPacks = await cliServer.resolveQlpacks(additionalPacks, true);
88+
const extensionPacksInfo = await cliServer.resolveQlpacks(
89+
additionalPacks,
90+
true,
91+
);
8292

83-
if (Object.keys(extensionPacks).length === 0) {
93+
if (Object.keys(extensionPacksInfo).length === 0) {
8494
return pickNewExtensionPack(databaseItem, token);
8595
}
8696

87-
const options: Array<{ label: string; extensionPack: string | null }> =
88-
Object.keys(extensionPacks).map((pack) => ({
89-
label: pack,
90-
extensionPack: pack,
91-
}));
97+
const extensionPacks = (
98+
await Promise.all(
99+
Object.entries(extensionPacksInfo).map(async ([name, paths]) => {
100+
if (paths.length !== 1) {
101+
void showAndLogErrorMessage(
102+
`Extension pack ${name} resolves to multiple paths`,
103+
{
104+
fullMessage: `Extension pack ${name} resolves to multiple paths: ${paths.join(
105+
", ",
106+
)}`,
107+
},
108+
);
109+
110+
return undefined;
111+
}
112+
113+
const path = paths[0];
114+
115+
let extensionPack: ExtensionPack;
116+
try {
117+
extensionPack = await readExtensionPack(path);
118+
} catch (e: unknown) {
119+
void showAndLogErrorMessage(`Could not read extension pack ${name}`, {
120+
fullMessage: `Could not read extension pack ${name} at ${path}: ${getErrorMessage(
121+
e,
122+
)}`,
123+
});
124+
125+
return undefined;
126+
}
127+
128+
return extensionPack;
129+
}),
130+
)
131+
).filter((info): info is ExtensionPack => info !== undefined);
132+
133+
const extensionPacksForLanguage = extensionPacks.filter(
134+
(pack) =>
135+
pack.extensionTargets[`codeql/${databaseItem.language}-all`] !==
136+
undefined,
137+
);
138+
139+
const options: Array<{
140+
label: string;
141+
description: string | undefined;
142+
detail: string | undefined;
143+
extensionPack: ExtensionPack | null;
144+
}> = extensionPacksForLanguage.map((pack) => ({
145+
label: pack.name,
146+
description: pack.version,
147+
detail: pack.path,
148+
extensionPack: pack,
149+
}));
92150
options.push({
93151
label: "Create new extension pack",
152+
description: undefined,
153+
detail: undefined,
94154
extensionPack: null,
95155
});
96156

@@ -115,57 +175,39 @@ async function pickExtensionPack(
115175
return pickNewExtensionPack(databaseItem, token);
116176
}
117177

118-
const extensionPackPaths = extensionPacks[extensionPackOption.extensionPack];
119-
if (extensionPackPaths.length !== 1) {
120-
void showAndLogErrorMessage(
121-
`Extension pack ${extensionPackOption.extensionPack} could not be resolved to a single location`,
122-
{
123-
fullMessage: `Extension pack ${
124-
extensionPackOption.extensionPack
125-
} could not be resolved to a single location. Found ${
126-
extensionPackPaths.length
127-
} locations: ${extensionPackPaths.join(", ")}.`,
128-
},
129-
);
130-
return undefined;
131-
}
132-
133-
return {
134-
name: extensionPackOption.extensionPack,
135-
path: extensionPackPaths[0],
136-
};
178+
return extensionPackOption.extensionPack;
137179
}
138180

139181
async function pickModelFile(
140182
cliServer: Pick<CodeQLCliServer, "resolveExtensions">,
141183
databaseItem: Pick<DatabaseItem, "name">,
142-
extensionPackPath: string,
184+
extensionPack: ExtensionPack,
143185
progress: ProgressCallback,
144186
token: CancellationToken,
145187
): Promise<string | undefined> {
146188
// Find the existing model files in the extension pack
147189
const additionalPacks = getOnDiskWorkspaceFolders();
148190
const extensions = await cliServer.resolveExtensions(
149-
extensionPackPath,
191+
extensionPack.path,
150192
additionalPacks,
151193
);
152194

153195
const modelFiles = new Set<string>();
154196

155-
if (extensionPackPath in extensions.data) {
156-
for (const extension of extensions.data[extensionPackPath]) {
197+
if (extensionPack.path in extensions.data) {
198+
for (const extension of extensions.data[extensionPack.path]) {
157199
modelFiles.add(extension.file);
158200
}
159201
}
160202

161203
if (modelFiles.size === 0) {
162-
return pickNewModelFile(databaseItem, extensionPackPath, token);
204+
return pickNewModelFile(databaseItem, extensionPack, token);
163205
}
164206

165207
const fileOptions: Array<{ label: string; file: string | null }> = [];
166208
for (const file of modelFiles) {
167209
fileOptions.push({
168-
label: relative(extensionPackPath, file).replaceAll(sep, "/"),
210+
label: relative(extensionPack.path, file).replaceAll(sep, "/"),
169211
file,
170212
});
171213
}
@@ -196,7 +238,7 @@ async function pickModelFile(
196238
return fileOption.file;
197239
}
198240

199-
return pickNewModelFile(databaseItem, extensionPackPath, token);
241+
return pickNewModelFile(databaseItem, extensionPack, token);
200242
}
201243

202244
async function pickNewExtensionPack(
@@ -266,66 +308,36 @@ async function pickNewExtensionPack(
266308

267309
const packYamlPath = join(packPath, "codeql-pack.yml");
268310

311+
const extensionPack: ExtensionPack = {
312+
path: packPath,
313+
yamlPath: packYamlPath,
314+
name,
315+
version: "0.0.0",
316+
extensionTargets: {
317+
[`codeql/${databaseItem.language}-all`]: "*",
318+
},
319+
dataExtensions: ["models/**/*.yml"],
320+
};
321+
269322
await outputFile(
270323
packYamlPath,
271324
dumpYaml({
272-
name,
273-
version: "0.0.0",
325+
name: extensionPack.name,
326+
version: extensionPack.version,
274327
library: true,
275-
extensionTargets: {
276-
[`codeql/${databaseItem.language}-all`]: "*",
277-
},
278-
dataExtensions: ["models/**/*.yml"],
328+
extensionTargets: extensionPack.extensionTargets,
329+
dataExtensions: extensionPack.dataExtensions,
279330
}),
280331
);
281332

282-
return {
283-
name: packName,
284-
path: packPath,
285-
};
333+
return extensionPack;
286334
}
287335

288336
async function pickNewModelFile(
289337
databaseItem: Pick<DatabaseItem, "name">,
290-
extensionPackPath: string,
338+
extensionPack: ExtensionPack,
291339
token: CancellationToken,
292340
) {
293-
const qlpackPath = await getQlPackPath(extensionPackPath);
294-
if (!qlpackPath) {
295-
void showAndLogErrorMessage(
296-
`Could not find any of ${QLPACK_FILENAMES.join(
297-
", ",
298-
)} in ${extensionPackPath}`,
299-
);
300-
return undefined;
301-
}
302-
303-
const qlpack = await loadYaml(await readFile(qlpackPath, "utf8"), {
304-
filename: qlpackPath,
305-
});
306-
if (typeof qlpack !== "object" || qlpack === null) {
307-
void showAndLogErrorMessage(`Could not parse ${qlpackPath}`);
308-
return undefined;
309-
}
310-
311-
const dataExtensionPatternsValue = qlpack.dataExtensions;
312-
if (
313-
!(
314-
Array.isArray(dataExtensionPatternsValue) ||
315-
typeof dataExtensionPatternsValue === "string"
316-
)
317-
) {
318-
void showAndLogErrorMessage(
319-
`Expected 'dataExtensions' to be a string or an array in ${qlpackPath}`,
320-
);
321-
return undefined;
322-
}
323-
324-
// The YAML allows either a string or an array of strings
325-
const dataExtensionPatterns = Array.isArray(dataExtensionPatternsValue)
326-
? dataExtensionPatternsValue
327-
: [dataExtensionPatternsValue];
328-
329341
const filename = await window.showInputBox(
330342
{
331343
title: "Enter the name of the new model file",
@@ -335,24 +347,25 @@ async function pickNewModelFile(
335347
return "File name must not be empty";
336348
}
337349

338-
const path = resolve(extensionPackPath, value);
350+
const path = resolve(extensionPack.path, value);
339351

340352
if (await pathExists(path)) {
341353
return "File already exists";
342354
}
343355

344-
const notInExtensionPack = relative(extensionPackPath, path).startsWith(
345-
"..",
346-
);
356+
const notInExtensionPack = relative(
357+
extensionPack.path,
358+
path,
359+
).startsWith("..");
347360
if (notInExtensionPack) {
348361
return "File must be in the extension pack";
349362
}
350363

351-
const matchesPattern = dataExtensionPatterns.some((pattern) =>
364+
const matchesPattern = extensionPack.dataExtensions.some((pattern) =>
352365
minimatch(value, pattern, { matchBase: true }),
353366
);
354367
if (!matchesPattern) {
355-
return `File must match one of the patterns in 'dataExtensions' in ${qlpackPath}`;
368+
return `File must match one of the patterns in 'dataExtensions' in ${extensionPack.yamlPath}`;
356369
}
357370

358371
return undefined;
@@ -364,5 +377,47 @@ async function pickNewModelFile(
364377
return undefined;
365378
}
366379

367-
return resolve(extensionPackPath, filename);
380+
return resolve(extensionPack.path, filename);
381+
}
382+
383+
async function readExtensionPack(path: string): Promise<ExtensionPack> {
384+
const qlpackPath = await getQlPackPath(path);
385+
if (!qlpackPath) {
386+
throw new Error(
387+
`Could not find any of ${QLPACK_FILENAMES.join(", ")} in ${path}`,
388+
);
389+
}
390+
391+
const qlpack = await loadYaml(await readFile(qlpackPath, "utf8"), {
392+
filename: qlpackPath,
393+
});
394+
if (typeof qlpack !== "object" || qlpack === null) {
395+
throw new Error(`Could not parse ${qlpackPath}`);
396+
}
397+
398+
const dataExtensionValue = qlpack.dataExtensions;
399+
if (
400+
!(
401+
Array.isArray(dataExtensionValue) ||
402+
typeof dataExtensionValue === "string"
403+
)
404+
) {
405+
throw new Error(
406+
`Expected 'dataExtensions' to be a string or an array in ${qlpackPath}`,
407+
);
408+
}
409+
410+
// The YAML allows either a string or an array of strings
411+
const dataExtensions = Array.isArray(dataExtensionValue)
412+
? dataExtensionValue
413+
: [dataExtensionValue];
414+
415+
return {
416+
path,
417+
yamlPath: qlpackPath,
418+
name: qlpack.name,
419+
version: qlpack.version,
420+
extensionTargets: qlpack.extensionTargets,
421+
dataExtensions,
422+
};
368423
}

0 commit comments

Comments
 (0)