diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index 8be2d5078..edfdd080c 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -790,7 +790,7 @@ async function activateWithInstalledDistribution( ); ctx.subscriptions.push(databaseUI); - QueriesModule.initialize(app, cliServer); + QueriesModule.initialize(app, languageContext, cliServer); void extLogger.log("Initializing evaluator log viewer."); const evalLogViewer = new EvalLogViewer(); diff --git a/extensions/ql-vscode/src/queries-panel/queries-module.ts b/extensions/ql-vscode/src/queries-panel/queries-module.ts index 69d70eba7..efb6d1e93 100644 --- a/extensions/ql-vscode/src/queries-panel/queries-module.ts +++ b/extensions/ql-vscode/src/queries-panel/queries-module.ts @@ -6,6 +6,7 @@ import { DisposableObject } from "../common/disposable-object"; import { QueriesPanel } from "./queries-panel"; import { QueryDiscovery } from "./query-discovery"; import { QueryPackDiscovery } from "./query-pack-discovery"; +import { LanguageContextStore } from "../language-context-store"; export class QueriesModule extends DisposableObject { private queriesPanel: QueriesPanel | undefined; @@ -16,16 +17,21 @@ export class QueriesModule extends DisposableObject { public static initialize( app: App, + languageContext: LanguageContextStore, cliServer: CodeQLCliServer, ): QueriesModule { const queriesModule = new QueriesModule(app); app.subscriptions.push(queriesModule); - queriesModule.initialize(app, cliServer); + queriesModule.initialize(app, languageContext, cliServer); return queriesModule; } - private initialize(app: App, cliServer: CodeQLCliServer): void { + private initialize( + app: App, + langauageContext: LanguageContextStore, + cliServer: CodeQLCliServer, + ): void { // Currently, we only want to expose the new panel when we are in canary mode // and the user has enabled the "Show queries panel" flag. if (!isCanary() || !showQueriesPanel()) { @@ -38,8 +44,9 @@ export class QueriesModule extends DisposableObject { void queryPackDiscovery.initialRefresh(); const queryDiscovery = new QueryDiscovery( - app.environment, + app, queryPackDiscovery, + langauageContext, ); this.push(queryDiscovery); void queryDiscovery.initialRefresh(); diff --git a/extensions/ql-vscode/src/queries-panel/query-discovery.ts b/extensions/ql-vscode/src/queries-panel/query-discovery.ts index 83db85532..0649dba73 100644 --- a/extensions/ql-vscode/src/queries-panel/query-discovery.ts +++ b/extensions/ql-vscode/src/queries-panel/query-discovery.ts @@ -1,6 +1,6 @@ import { dirname, basename, normalize, relative } from "path"; import { Event } from "vscode"; -import { EnvironmentContext } from "../common/app"; +import { App } from "../common/app"; import { FileTreeDirectory, FileTreeLeaf, @@ -11,6 +11,8 @@ import { FilePathDiscovery } from "../common/vscode/file-path-discovery"; import { containsPath } from "../common/files"; import { getOnDiskWorkspaceFoldersObjects } from "../common/vscode/workspace-folders"; import { QueryLanguage } from "../common/query-language"; +import { LanguageContextStore } from "../language-context-store"; +import { AppEvent, AppEventEmitter } from "../common/events"; const QUERY_FILE_EXTENSION = ".ql"; @@ -31,24 +33,36 @@ export class QueryDiscovery extends FilePathDiscovery implements QueryDiscoverer { + public readonly onDidChangeQueries: AppEvent; + private readonly onDidChangeQueriesEmitter: AppEventEmitter; + constructor( - private readonly env: EnvironmentContext, + private readonly app: App, private readonly queryPackDiscovery: QueryPackDiscoverer, + private readonly languageContext: LanguageContextStore, ) { super("Query Discovery", `**/*${QUERY_FILE_EXTENSION}`); + // Set up event emitters + this.onDidChangeQueriesEmitter = this.push(app.createEventEmitter()); + this.onDidChangeQueries = this.onDidChangeQueriesEmitter.event; + + // Handlers this.push( this.queryPackDiscovery.onDidChangeQueryPacks( this.recomputeAllData.bind(this), ), ); - } - - /** - * Event that fires when the set of queries in the workspace changes. - */ - public get onDidChangeQueries(): Event { - return this.onDidChangePathData; + this.push( + this.onDidChangePathData(() => { + this.onDidChangeQueriesEmitter.fire(); + }), + ); + this.push( + this.languageContext.onLanguageContextChanged(() => { + this.onDidChangeQueriesEmitter.fire(); + }), + ); } /** @@ -64,8 +78,10 @@ export class QueryDiscovery const roots = []; for (const workspaceFolder of getOnDiskWorkspaceFoldersObjects()) { - const queriesInRoot = pathData.filter((query) => - containsPath(workspaceFolder.uri.fsPath, query.path), + const queriesInRoot = pathData.filter( + (query) => + containsPath(workspaceFolder.uri.fsPath, query.path) && + this.languageContext.shouldInclude(query.language), ); if (queriesInRoot.length === 0) { continue; @@ -73,7 +89,7 @@ export class QueryDiscovery const root = new FileTreeDirectory( workspaceFolder.uri.fsPath, workspaceFolder.name, - this.env, + this.app.environment, ); for (const query of queriesInRoot) { const dirName = dirname(normalize(relative(root.path, query.path))); diff --git a/extensions/ql-vscode/test/__mocks__/appMock.ts b/extensions/ql-vscode/test/__mocks__/appMock.ts index 0a596ef33..797ea3d13 100644 --- a/extensions/ql-vscode/test/__mocks__/appMock.ts +++ b/extensions/ql-vscode/test/__mocks__/appMock.ts @@ -56,7 +56,7 @@ class MockAppEventEmitter implements AppEventEmitter { constructor() { this.event = () => { - return {} as Disposable; + return new MockAppEvent(); }; } @@ -69,7 +69,17 @@ class MockAppEventEmitter implements AppEventEmitter { } } -export function createMockEnvironmentContext(): EnvironmentContext { +class MockAppEvent implements Disposable { + public fire(): void { + // no-op + } + + public dispose() { + // no-op + } +} + +function createMockEnvironmentContext(): EnvironmentContext { return { language: "en-US", }; diff --git a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/queries-panel/query-discovery.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/queries-panel/query-discovery.test.ts index 24fd95a42..fc4dc57b4 100644 --- a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/queries-panel/query-discovery.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/queries-panel/query-discovery.test.ts @@ -3,8 +3,8 @@ import { QueryDiscovery, QueryPackDiscoverer, } from "../../../../src/queries-panel/query-discovery"; -import { createMockEnvironmentContext } from "../../../__mocks__/appMock"; -import { dirname, join } from "path"; +import { createMockApp } from "../../../__mocks__/appMock"; +import { basename, dirname, join } from "path"; import * as tmp from "tmp"; import { FileTreeDirectory, @@ -13,6 +13,7 @@ import { import { mkdirSync, writeFileSync } from "fs"; import { QueryLanguage } from "../../../../src/common/query-language"; import { sleep } from "../../../../src/common/time"; +import { LanguageContextStore } from "../../../../src/language-context-store"; describe("Query pack discovery", () => { let tmpDir: string; @@ -20,7 +21,10 @@ describe("Query pack discovery", () => { let workspacePath: string; - const env = createMockEnvironmentContext(); + const app = createMockApp({}); + const env = app.environment; + + const languageContext = new LanguageContextStore(app); const onDidChangeQueryPacks = new EventEmitter(); let queryPackDiscoverer: QueryPackDiscoverer; @@ -45,7 +49,7 @@ describe("Query pack discovery", () => { getLanguageForQueryFile: () => QueryLanguage.Java, onDidChangeQueryPacks: onDidChangeQueryPacks.event, }; - discovery = new QueryDiscovery(env, queryPackDiscoverer); + discovery = new QueryDiscovery(app, queryPackDiscoverer, languageContext); }); afterEach(() => { @@ -160,6 +164,52 @@ describe("Query pack discovery", () => { ]), ]); }); + + it("should respect the language context filter", async () => { + makeTestFile(join(workspacePath, "query1.ql")); + makeTestFile(join(workspacePath, "query2.ql")); + + queryPackDiscoverer.getLanguageForQueryFile = (path) => { + if (basename(path) === "query1.ql") { + return QueryLanguage.Java; + } else { + return QueryLanguage.Python; + } + }; + + await discovery.initialRefresh(); + + // Set the language to python-only + await languageContext.setLanguageContext(QueryLanguage.Python); + + expect(discovery.buildQueryTree()).toEqual([ + new FileTreeDirectory(workspacePath, "workspace", env, [ + new FileTreeLeaf( + join(workspacePath, "query2.ql"), + "query2.ql", + "python", + ), + ]), + ]); + + // Clear the language context filter + await languageContext.clearLanguageContext(); + + expect(discovery.buildQueryTree()).toEqual([ + new FileTreeDirectory(workspacePath, "workspace", env, [ + new FileTreeLeaf( + join(workspacePath, "query1.ql"), + "query1.ql", + "java", + ), + new FileTreeLeaf( + join(workspacePath, "query2.ql"), + "query2.ql", + "python", + ), + ]), + ]); + }); }); describe("recomputeAllQueryLanguages", () => {