Skip to content

Commit e294def

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/extensions/ql-vscode/markdownlint-cli2-0.22.1
2 parents 5316259 + 24b8313 commit e294def

19 files changed

Lines changed: 459 additions & 192 deletions

File tree

extensions/ql-vscode/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Remove support for CodeQL CLI versions older than 2.22.4. [#4344](https://github.com/github/vscode-codeql/pull/4344)
66
- Added support for selection-based result filtering via a checkbox in the result viewer. When enabled, only results from the currently-viewed file are shown. Additionally, if the editor selection is non-empty, only results within the selection range are shown. [#4362](https://github.com/github/vscode-codeql/pull/4362)
7+
- Added a new "CodeQL: Go to File in Selected Database" command that allows you to open a file from the source archive of the currently selected database. [#4390](https://github.com/github/vscode-codeql/pull/4390)
78

89
## 1.17.7 - 5 December 2025
910

extensions/ql-vscode/package-lock.json

Lines changed: 139 additions & 146 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/ql-vscode/package.json

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,10 @@
528528
"command": "codeQL.runQueryContextEditor",
529529
"title": "CodeQL: Run Query on Selected Database"
530530
},
531+
{
532+
"command": "codeQL.goToFile",
533+
"title": "CodeQL: Go to File in Selected Database"
534+
},
531535
{
532536
"command": "codeQL.runWarmOverlayBaseCacheForQuery",
533537
"title": "CodeQL: Warm Overlay-Base Cache for Query"
@@ -1874,6 +1878,9 @@
18741878
"command": "codeQL.gotoQLContextEditor",
18751879
"when": "false"
18761880
},
1881+
{
1882+
"command": "codeQL.goToFile"
1883+
},
18771884
{
18781885
"command": "codeQL.trimCache"
18791886
},
@@ -2063,7 +2070,7 @@
20632070
"dependencies": {
20642071
"@floating-ui/react": "^0.27.19",
20652072
"@hpcc-js/wasm-graphviz": "^1.21.1",
2066-
"@octokit/plugin-retry": "^7.2.0",
2073+
"@octokit/plugin-retry": "^8.1.0",
20672074
"@octokit/plugin-throttling": "^9.6.0",
20682075
"@octokit/rest": "^22.0.1",
20692076
"@vscode-elements/react-elements": "^0.9.0",
@@ -2088,7 +2095,7 @@
20882095
"source-map-support": "^0.5.21",
20892096
"stream-json": "^1.9.1",
20902097
"styled-components": "^6.4.1",
2091-
"tmp": "^0.2.5",
2098+
"tmp": "^0.2.6",
20922099
"tmp-promise": "^3.0.2",
20932100
"tree-kill": "^1.2.2",
20942101
"vscode-extension-telemetry": "^0.1.6",
@@ -2098,7 +2105,7 @@
20982105
"zip-a-folder": "^4.0.4"
20992106
},
21002107
"devDependencies": {
2101-
"@babel/core": "^7.28.3",
2108+
"@babel/core": "^7.29.0",
21022109
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
21032110
"@babel/preset-env": "^7.29.0",
21042111
"@babel/preset-react": "^7.27.1",
@@ -2110,13 +2117,13 @@
21102117
"@jest/environment-jsdom-abstract": "^30.3.0",
21112118
"@microsoft/eslint-formatter-sarif": "^3.1.0",
21122119
"@playwright/test": "^1.59.1",
2113-
"@storybook/addon-a11y": "^10.4.0",
2114-
"@storybook/addon-docs": "^10.4.0",
2115-
"@storybook/addon-links": "^10.4.0",
2120+
"@storybook/addon-a11y": "^10.4.1",
2121+
"@storybook/addon-docs": "^10.4.1",
2122+
"@storybook/addon-links": "^10.4.1",
21162123
"@storybook/csf": "^0.1.13",
21172124
"@storybook/icons": "^2.0.2",
2118-
"@storybook/react": "^10.4.0",
2119-
"@storybook/react-vite": "^10.4.0",
2125+
"@storybook/react": "^10.4.1",
2126+
"@storybook/react-vite": "^10.4.1",
21202127
"@testing-library/dom": "^10.4.1",
21212128
"@testing-library/jest-dom": "^6.9.1",
21222129
"@testing-library/react": "^16.3.2",
@@ -2176,7 +2183,7 @@
21762183
"npm-run-all": "^4.1.5",
21772184
"patch-package": "^8.0.1",
21782185
"prettier": "^3.6.1",
2179-
"storybook": "^10.4.0",
2186+
"storybook": "^10.4.1",
21802187
"tar-stream": "^3.1.8",
21812188
"through2": "^4.0.2",
21822189
"ts-jest": "^29.4.6",

extensions/ql-vscode/src/common/commands.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@ export type LocalDatabasesCommands = {
260260
// Internal commands
261261
"codeQLDatabases.removeOrphanedDatabases": () => Promise<void>;
262262
"codeQL.getCurrentDatabase": () => Promise<string | undefined>;
263+
264+
// Source archive file search
265+
"codeQL.goToFile": () => Promise<void>;
263266
};
264267

265268
// Commands tied to variant analysis

extensions/ql-vscode/src/common/vscode/multi-file-system-watcher.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class WatcherCollection extends DisposableObject {
2020
*/
2121
public addWatcher(pattern: GlobPattern, listener: (e: Uri) => void): void {
2222
const watcher = workspace.createFileSystemWatcher(pattern);
23+
this.push(watcher);
2324
this.push(watcher.onDidCreate(listener));
2425
this.push(watcher.onDidChange(listener));
2526
this.push(watcher.onDidDelete(listener));

extensions/ql-vscode/src/databases/local-databases-ui.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import type { QueryRunner } from "../query-server";
4545
import type { App } from "../common/app";
4646
import { redactableError } from "../common/errors";
4747
import type { LocalDatabasesCommands } from "../common/commands";
48+
import { searchSourceArchiveFiles } from "./source-archive-file-search";
4849
import {
4950
createMultiSelectionCommand,
5051
createSingleSelectionCommand,
@@ -317,9 +318,22 @@ export class DatabaseUI extends DisposableObject {
317318
),
318319
"codeQLDatabases.removeOrphanedDatabases":
319320
this.handleRemoveOrphanedDatabases.bind(this),
321+
"codeQL.goToFile": this.handleGoToFile.bind(this),
320322
};
321323
}
322324

325+
private async handleGoToFile(): Promise<void> {
326+
const currentDb = this.databaseManager.currentDatabaseItem;
327+
if (!currentDb) {
328+
void showAndLogErrorMessage(
329+
this.app.logger,
330+
"No CodeQL database selected. Please select a database first.",
331+
);
332+
return;
333+
}
334+
await searchSourceArchiveFiles(currentDb);
335+
}
336+
323337
private async handleMakeCurrentDatabase(
324338
databaseItem: DatabaseItem,
325339
): Promise<void> {

extensions/ql-vscode/src/databases/local-databases/database-item-impl.ts

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Exported for testing
22
import type { CodeQLCliServer, DbInfo } from "../../codeql-cli/cli";
3-
import { Uri, workspace } from "vscode";
3+
import { FileType, Uri, workspace } from "vscode";
44
import type { FullDatabaseOptions } from "./database-options";
55
import { basename, dirname, extname, join } from "path";
66
import {
@@ -9,7 +9,11 @@ import {
99
encodeSourceArchiveUri,
1010
zipArchiveScheme,
1111
} from "../../common/vscode/archive-filesystem-provider";
12-
import type { DatabaseItem, PersistedDatabaseItem } from "./database-item";
12+
import type {
13+
DatabaseItem,
14+
PersistedDatabaseItem,
15+
SourceArchiveFile,
16+
} from "./database-item";
1317
import { isLikelyDatabaseRoot } from "./db-contents-heuristics";
1418
import { stat } from "fs-extra";
1519
import { containsPath, pathsEqual } from "../../common/files";
@@ -22,6 +26,8 @@ export class DatabaseItemImpl implements DatabaseItem {
2226
public contents: DatabaseContents | undefined;
2327
/** A cache of database info */
2428
private _dbinfo: DbInfo | undefined;
29+
/** A cache of source archive files */
30+
private _sourceArchiveFiles: SourceArchiveFile[] | undefined;
2531

2632
public constructor(
2733
public readonly databaseUri: Uri,
@@ -234,4 +240,66 @@ export class DatabaseItemImpl implements DatabaseItem {
234240
return false;
235241
}
236242
}
243+
244+
public async getSourceArchiveFiles(): Promise<SourceArchiveFile[]> {
245+
if (this._sourceArchiveFiles === undefined) {
246+
this._sourceArchiveFiles = await this.collectSourceArchiveFiles();
247+
}
248+
return this._sourceArchiveFiles;
249+
}
250+
251+
private async collectSourceArchiveFiles(): Promise<SourceArchiveFile[]> {
252+
const explorerUri = this.getSourceArchiveExplorerUri();
253+
const sourceArchiveZipPath =
254+
decodeSourceArchiveUri(explorerUri).sourceArchiveZipPath;
255+
256+
const items: SourceArchiveFile[] = [];
257+
await this.collectFilesRecursive(
258+
explorerUri,
259+
sourceArchiveZipPath,
260+
"",
261+
items,
262+
);
263+
// Sort by file name, then by path
264+
items.sort((a, b) => {
265+
const nameCmp = a.name.localeCompare(b.name);
266+
if (nameCmp !== 0) {
267+
return nameCmp;
268+
}
269+
return a.path.localeCompare(b.path);
270+
});
271+
return items;
272+
}
273+
274+
private async collectFilesRecursive(
275+
dirUri: Uri,
276+
sourceArchiveZipPath: string,
277+
prefix: string,
278+
items: SourceArchiveFile[],
279+
): Promise<void> {
280+
const entries = await workspace.fs.readDirectory(dirUri);
281+
282+
for (const [name, type] of entries) {
283+
const childPath = prefix ? `${prefix}/${name}` : name;
284+
const childUri = encodeSourceArchiveUri({
285+
sourceArchiveZipPath,
286+
pathWithinSourceArchive: `${decodeSourceArchiveUri(dirUri).pathWithinSourceArchive}/${name}`,
287+
});
288+
289+
if (type === FileType.File) {
290+
items.push({
291+
name,
292+
path: prefix,
293+
uri: childUri,
294+
});
295+
} else if (type === FileType.Directory) {
296+
await this.collectFilesRecursive(
297+
childUri,
298+
sourceArchiveZipPath,
299+
childPath,
300+
items,
301+
);
302+
}
303+
}
304+
}
237305
}

extensions/ql-vscode/src/databases/local-databases/database-item.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ import type { DatabaseContents } from "./database-contents";
44
import type { DatabaseOptions } from "./database-options";
55
import type { DatabaseOrigin } from "./database-origin";
66

7+
/** A file entry from the database's source archive. */
8+
export interface SourceArchiveFile {
9+
/** The file name (basename). */
10+
name: string;
11+
/** The path prefix (directory path relative to the source archive root). */
12+
path: string;
13+
/** The URI that can be used to open the file. */
14+
uri: Uri;
15+
}
16+
717
/** An item in the list of available databases */
818
export interface DatabaseItem {
919
/** The URI of the database */
@@ -92,6 +102,12 @@ export interface DatabaseItem {
92102
* Verifies that this database item has a zipped source folder. Returns an error message if it does not.
93103
*/
94104
verifyZippedSources(): string | undefined;
105+
106+
/**
107+
* Returns all files in the database's source archive.
108+
* The result is lazily computed and cached.
109+
*/
110+
getSourceArchiveFiles(): Promise<SourceArchiveFile[]>;
95111
}
96112

97113
export interface PersistedDatabaseItem {

extensions/ql-vscode/src/databases/local-databases/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export {
44
DatabaseKind,
55
} from "./database-contents";
66
export { DatabaseChangedEvent, DatabaseEventKind } from "./database-events";
7-
export { DatabaseItem } from "./database-item";
7+
export { DatabaseItem, SourceArchiveFile } from "./database-item";
88
export { DatabaseItemImpl } from "./database-item-impl";
99
export { DatabaseManager } from "./database-manager";
1010
export { DatabaseResolver } from "./database-resolver";
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { QuickPickItem, Uri } from "vscode";
2+
import { window, workspace } from "vscode";
3+
import type { DatabaseItem } from "./local-databases";
4+
5+
interface SourceArchiveFileQuickPickItem extends QuickPickItem {
6+
uri: Uri;
7+
}
8+
9+
/**
10+
* Shows a Quick Pick to search for and open a file from the source archive
11+
* of the given database.
12+
*/
13+
export async function searchSourceArchiveFiles(
14+
databaseItem: DatabaseItem,
15+
): Promise<void> {
16+
const filesPromise = databaseItem.getSourceArchiveFiles();
17+
18+
const quickPick = window.createQuickPick<SourceArchiveFileQuickPickItem>();
19+
quickPick.placeholder = "Go to File in Selected Database...";
20+
quickPick.matchOnDescription = true;
21+
quickPick.busy = true;
22+
quickPick.show();
23+
24+
try {
25+
const files = await filesPromise;
26+
quickPick.items = files.map((f) => ({
27+
label: f.name,
28+
description: f.path,
29+
uri: f.uri,
30+
}));
31+
quickPick.busy = false;
32+
} catch (e) {
33+
quickPick.dispose();
34+
void window.showErrorMessage(
35+
`Failed to read source archive: ${e instanceof Error ? e.message : String(e)}`,
36+
);
37+
return;
38+
}
39+
40+
return new Promise<void>((resolve) => {
41+
quickPick.onDidAccept(async () => {
42+
const selected = quickPick.selectedItems[0];
43+
quickPick.dispose();
44+
try {
45+
if (selected) {
46+
const doc = await workspace.openTextDocument(selected.uri);
47+
await window.showTextDocument(doc);
48+
}
49+
} catch (e) {
50+
void window.showErrorMessage(
51+
`Failed to open source archive file: ${e instanceof Error ? e.message : String(e)}`,
52+
);
53+
} finally {
54+
resolve();
55+
}
56+
});
57+
58+
quickPick.onDidHide(() => {
59+
quickPick.dispose();
60+
resolve();
61+
});
62+
});
63+
}

0 commit comments

Comments
 (0)