diff --git a/extensions/ql-vscode/src/model-editor/external-api-usage-queries.ts b/extensions/ql-vscode/src/model-editor/external-api-usage-queries.ts deleted file mode 100644 index 7d5b0575257..00000000000 --- a/extensions/ql-vscode/src/model-editor/external-api-usage-queries.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { QueryRunner } from "../query-server"; -import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders"; -import { extLogger } from "../common/logging/vscode"; -import { showAndLogExceptionWithTelemetry } from "../common/logging"; -import { CancellationToken } from "vscode"; -import { CodeQLCliServer } from "../codeql-cli/cli"; -import { DatabaseItem } from "../databases/local-databases"; -import { ProgressCallback } from "../common/vscode/progress"; -import { redactableError } from "../common/errors"; -import { telemetryListener } from "../common/vscode/telemetry"; -import { join } from "path"; -import { Mode } from "./shared/mode"; -import { writeFile } from "fs-extra"; -import { QueryLanguage } from "../common/query-language"; -import { fetchExternalApiQueries } from "./queries"; -import { Method } from "./method"; -import { runQuery } from "../local-queries/run-query"; -import { decodeBqrsToMethods } from "./bqrs"; -import { - resolveEndpointsQuery, - syntheticQueryPackName, -} from "./model-editor-queries"; - -type RunQueryOptions = { - cliServer: CodeQLCliServer; - queryRunner: QueryRunner; - databaseItem: DatabaseItem; - queryStorageDir: string; - queryDir: string; - - progress: ProgressCallback; - token: CancellationToken; -}; - -export async function prepareExternalApiQuery( - queryDir: string, - language: QueryLanguage, -): Promise { - // Resolve the query that we want to run. - const query = fetchExternalApiQueries[language]; - if (!query) { - void showAndLogExceptionWithTelemetry( - extLogger, - telemetryListener, - redactableError`No external API usage query found for language ${language}`, - ); - return false; - } - // Create the query file. - Object.values(Mode).map(async (mode) => { - const queryFile = join(queryDir, queryNameFromMode(mode)); - await writeFile(queryFile, query[`${mode}ModeQuery`], "utf8"); - }); - - // Create any dependencies - if (query.dependencies) { - for (const [filename, contents] of Object.entries(query.dependencies)) { - const dependencyFile = join(queryDir, filename); - await writeFile(dependencyFile, contents, "utf8"); - } - } - return true; -} - -export const externalApiQueriesProgressMaxStep = 2000; - -export async function runExternalApiQueries( - mode: Mode, - { - cliServer, - queryRunner, - databaseItem, - queryStorageDir, - queryDir, - progress, - token, - }: RunQueryOptions, -): Promise { - // The below code is temporary to allow for rapid prototyping of the queries. Once the queries are stabilized, we will - // move these queries into the `github/codeql` repository and use them like any other contextual (e.g. AST) queries. - // This is intentionally not pretty code, as it will be removed soon. - // For a reference of what this should do in the future, see the previous implementation in - // https://github.com/github/vscode-codeql/blob/089d3566ef0bc67d9b7cc66e8fd6740b31c1c0b0/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts#L33-L72 - - progress({ - message: "Resolving QL packs", - step: 1, - maxStep: externalApiQueriesProgressMaxStep, - }); - const additionalPacks = getOnDiskWorkspaceFolders(); - const extensionPacks = Object.keys( - await cliServer.resolveQlpacks(additionalPacks, true), - ); - - progress({ - message: "Resolving query", - step: 2, - maxStep: externalApiQueriesProgressMaxStep, - }); - - // Resolve the queries from either codeql/java-queries or from the temporary queryDir - const queryPath = await resolveEndpointsQuery( - cliServer, - databaseItem.language, - mode, - [syntheticQueryPackName], - [queryDir], - ); - if (!queryPath) { - void showAndLogExceptionWithTelemetry( - extLogger, - telemetryListener, - redactableError`The ${mode} model editor query could not be found. Try re-opening the model editor. If that doesn't work, try upgrading the CodeQL libraries.`, - ); - return; - } - - // Run the actual query - const completedQuery = await runQuery({ - queryRunner, - databaseItem, - queryPath, - queryStorageDir, - additionalPacks, - extensionPacks, - progress: (update) => - progress({ - step: update.step + 500, - maxStep: externalApiQueriesProgressMaxStep, - message: update.message, - }), - token, - }); - - if (!completedQuery) { - return; - } - - // Read the results and covert to internal representation - progress({ - message: "Decoding results", - step: 1600, - maxStep: externalApiQueriesProgressMaxStep, - }); - - const bqrsChunk = await readQueryResults({ - cliServer, - bqrsPath: completedQuery.outputDir.bqrsPath, - }); - if (!bqrsChunk) { - return; - } - - progress({ - message: "Finalizing results", - step: 1950, - maxStep: externalApiQueriesProgressMaxStep, - }); - - return decodeBqrsToMethods(bqrsChunk, mode); -} - -type GetResultsOptions = { - cliServer: Pick; - bqrsPath: string; -}; - -export async function readQueryResults({ - cliServer, - bqrsPath, -}: GetResultsOptions) { - const bqrsInfo = await cliServer.bqrsInfo(bqrsPath); - if (bqrsInfo["result-sets"].length !== 1) { - void showAndLogExceptionWithTelemetry( - extLogger, - telemetryListener, - redactableError`Expected exactly one result set, got ${bqrsInfo["result-sets"].length}`, - ); - return undefined; - } - - const resultSet = bqrsInfo["result-sets"][0]; - - return cliServer.bqrsDecode(bqrsPath, resultSet.name); -} - -function queryNameFromMode(mode: Mode): string { - return `${mode.charAt(0).toUpperCase() + mode.slice(1)}ModeEndpoints.ql`; -} diff --git a/extensions/ql-vscode/src/model-editor/model-editor-module.ts b/extensions/ql-vscode/src/model-editor/model-editor-module.ts index 7b77b46193c..06837e35074 100644 --- a/extensions/ql-vscode/src/model-editor/model-editor-module.ts +++ b/extensions/ql-vscode/src/model-editor/model-editor-module.ts @@ -15,7 +15,7 @@ import { isQueryLanguage } from "../common/query-language"; import { DisposableObject } from "../common/disposable-object"; import { MethodsUsagePanel } from "./methods-usage/methods-usage-panel"; import { Method, Usage } from "./method"; -import { setUpPack } from "./model-editor-queries"; +import { setUpPack } from "./model-editor-queries-setup"; import { MethodModelingPanel } from "./method-modeling/method-modeling-panel"; import { ModelingStore } from "./modeling-store"; import { showResolvableLocation } from "../databases/local-databases/locations"; diff --git a/extensions/ql-vscode/src/model-editor/model-editor-queries-setup.ts b/extensions/ql-vscode/src/model-editor/model-editor-queries-setup.ts new file mode 100644 index 00000000000..1c615f859c1 --- /dev/null +++ b/extensions/ql-vscode/src/model-editor/model-editor-queries-setup.ts @@ -0,0 +1,140 @@ +import { join } from "path"; +import { QueryLanguage } from "../common/query-language"; +import { writeFile } from "fs-extra"; +import { dump } from "js-yaml"; +import { prepareModelEditorQueries } from "./model-editor-queries"; +import { CodeQLCliServer } from "../codeql-cli/cli"; +import { ModelConfig } from "../config"; +import { Mode } from "./shared/mode"; +import { resolveQueriesFromPacks } from "../local-queries"; +import { modeTag } from "./mode-tag"; + +export const syntheticQueryPackName = "codeql/model-editor-queries"; + +/** + * setUpPack sets up a directory to use for the data extension editor queries if required. + * + * There are two cases (example language is Java): + * - In case the queries are present in the codeql/java-queries, we don't need to write our own queries + * to disk. We still need to create a synthetic query pack so we can pass the queryDir to the query + * resolver without caring about whether the queries are present in the pack or not. + * - In case the queries are not present in the codeql/java-queries, we need to write our own queries + * to disk. We will create a synthetic query pack and install its dependencies so it is fully independent + * and we can simply pass it through when resolving the queries. + * + * These steps together ensure that later steps of the process don't need to keep track of whether the queries + * are present in codeql/java-queries or in our own query pack. They just need to resolve the query. + * + * @param cliServer The CodeQL CLI server to use. + * @param queryDir The directory to set up. + * @param language The language to use for the queries. + * @param modelConfig The model config to use. + * @returns true if the setup was successful, false otherwise. + */ +export async function setUpPack( + cliServer: CodeQLCliServer, + queryDir: string, + language: QueryLanguage, + modelConfig: ModelConfig, +): Promise { + // Download the required query packs + await cliServer.packDownload([`codeql/${language}-queries`]); + + // We'll only check if the application mode query exists in the pack and assume that if it does, + // the framework mode query will also exist. + const applicationModeQuery = await resolveEndpointsQuery( + cliServer, + language, + Mode.Application, + [], + [], + ); + + if (applicationModeQuery) { + // Set up a synthetic pack so CodeQL doesn't crash later when we try + // to resolve a query within this directory + const syntheticQueryPack = { + name: syntheticQueryPackName, + version: "0.0.0", + dependencies: {}, + }; + + const qlpackFile = join(queryDir, "codeql-pack.yml"); + await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8"); + } else { + // If we can't resolve the query, we need to write them to desk ourselves. + const externalApiQuerySuccess = await prepareModelEditorQueries( + queryDir, + language, + ); + if (!externalApiQuerySuccess) { + return false; + } + + // Set up a synthetic pack so that the query can be resolved later. + const syntheticQueryPack = { + name: syntheticQueryPackName, + version: "0.0.0", + dependencies: { + [`codeql/${language}-all`]: "*", + }, + }; + + const qlpackFile = join(queryDir, "codeql-pack.yml"); + await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8"); + await cliServer.packInstall(queryDir); + } + + // Download any other required packs + if (language === "java" && modelConfig.llmGeneration) { + await cliServer.packDownload([`codeql/${language}-automodel-queries`]); + } + + return true; +} + +/** + * Resolve the query path to the model editor endpoints query. All queries are tagged like this: + * modeleditor endpoints + * Example: modeleditor endpoints framework-mode + * + * @param cliServer The CodeQL CLI server to use. + * @param language The language of the query pack to use. + * @param mode The mode to resolve the query for. + * @param additionalPackNames Additional pack names to search. + * @param additionalPackPaths Additional pack paths to search. + */ +export async function resolveEndpointsQuery( + cliServer: CodeQLCliServer, + language: string, + mode: Mode, + additionalPackNames: string[] = [], + additionalPackPaths: string[] = [], +): Promise { + const packsToSearch = [`codeql/${language}-queries`, ...additionalPackNames]; + + // First, resolve the query that we want to run. + // All queries are tagged like this: + // internal extract automodel + // Example: internal extract automodel framework-mode candidates + const queries = await resolveQueriesFromPacks( + cliServer, + packsToSearch, + { + kind: "table", + "tags contain all": ["modeleditor", "endpoints", modeTag(mode)], + }, + additionalPackPaths, + ); + if (queries.length > 1) { + throw new Error( + `Found multiple endpoints queries for ${mode}. Can't continue`, + ); + } + + if (queries.length === 0) { + return undefined; + } + + return queries[0]; +} diff --git a/extensions/ql-vscode/src/model-editor/model-editor-queries.ts b/extensions/ql-vscode/src/model-editor/model-editor-queries.ts index 7fc0e756299..2ff617fc3a6 100644 --- a/extensions/ql-vscode/src/model-editor/model-editor-queries.ts +++ b/extensions/ql-vscode/src/model-editor/model-editor-queries.ts @@ -1,140 +1,189 @@ -import { join } from "path"; -import { QueryLanguage } from "../common/query-language"; -import { writeFile } from "fs-extra"; -import { dump } from "js-yaml"; -import { prepareExternalApiQuery } from "./external-api-usage-queries"; +import { QueryRunner } from "../query-server"; +import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders"; +import { extLogger } from "../common/logging/vscode"; +import { showAndLogExceptionWithTelemetry } from "../common/logging"; +import { CancellationToken } from "vscode"; import { CodeQLCliServer } from "../codeql-cli/cli"; -import { ModelConfig } from "../config"; +import { DatabaseItem } from "../databases/local-databases"; +import { ProgressCallback } from "../common/vscode/progress"; +import { redactableError } from "../common/errors"; +import { telemetryListener } from "../common/vscode/telemetry"; +import { join } from "path"; import { Mode } from "./shared/mode"; -import { resolveQueriesFromPacks } from "../local-queries"; -import { modeTag } from "./mode-tag"; - -export const syntheticQueryPackName = "codeql/external-api-usage"; - -/** - * setUpPack sets up a directory to use for the data extension editor queries if required. - * - * There are two cases (example language is Java): - * - In case the queries are present in the codeql/java-queries, we don't need to write our own queries - * to disk. We still need to create a synthetic query pack so we can pass the queryDir to the query - * resolver without caring about whether the queries are present in the pack or not. - * - In case the queries are not present in the codeql/java-queries, we need to write our own queries - * to disk. We will create a synthetic query pack and install its dependencies so it is fully independent - * and we can simply pass it through when resolving the queries. - * - * These steps together ensure that later steps of the process don't need to keep track of whether the queries - * are present in codeql/java-queries or in our own query pack. They just need to resolve the query. - * - * @param cliServer The CodeQL CLI server to use. - * @param queryDir The directory to set up. - * @param language The language to use for the queries. - * @param modelConfig The model config to use. - * @returns true if the setup was successful, false otherwise. - */ -export async function setUpPack( - cliServer: CodeQLCliServer, +import { writeFile } from "fs-extra"; +import { QueryLanguage } from "../common/query-language"; +import { fetchExternalApiQueries } from "./queries"; +import { Method } from "./method"; +import { runQuery } from "../local-queries/run-query"; +import { decodeBqrsToMethods } from "./bqrs"; +import { + resolveEndpointsQuery, + syntheticQueryPackName, +} from "./model-editor-queries-setup"; + +type RunQueryOptions = { + cliServer: CodeQLCliServer; + queryRunner: QueryRunner; + databaseItem: DatabaseItem; + queryStorageDir: string; + queryDir: string; + + progress: ProgressCallback; + token: CancellationToken; +}; + +export async function prepareModelEditorQueries( queryDir: string, language: QueryLanguage, - modelConfig: ModelConfig, ): Promise { - // Download the required query packs - await cliServer.packDownload([`codeql/${language}-queries`]); - - // We'll only check if the application mode query exists in the pack and assume that if it does, - // the framework mode query will also exist. - const applicationModeQuery = await resolveEndpointsQuery( - cliServer, - language, - Mode.Application, - [], - [], - ); - - if (applicationModeQuery) { - // Set up a synthetic pack so CodeQL doesn't crash later when we try - // to resolve a query within this directory - const syntheticQueryPack = { - name: syntheticQueryPackName, - version: "0.0.0", - dependencies: {}, - }; - - const qlpackFile = join(queryDir, "codeql-pack.yml"); - await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8"); - } else { - // If we can't resolve the query, we need to write them to desk ourselves. - const externalApiQuerySuccess = await prepareExternalApiQuery( - queryDir, - language, + // Resolve the query that we want to run. + const query = fetchExternalApiQueries[language]; + if (!query) { + void showAndLogExceptionWithTelemetry( + extLogger, + telemetryListener, + redactableError`No bundled model editor query found for language ${language}`, ); - if (!externalApiQuerySuccess) { - return false; - } - - // Set up a synthetic pack so that the query can be resolved later. - const syntheticQueryPack = { - name: syntheticQueryPackName, - version: "0.0.0", - dependencies: { - [`codeql/${language}-all`]: "*", - }, - }; - - const qlpackFile = join(queryDir, "codeql-pack.yml"); - await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8"); - await cliServer.packInstall(queryDir); + return false; } + // Create the query file. + Object.values(Mode).map(async (mode) => { + const queryFile = join(queryDir, queryNameFromMode(mode)); + await writeFile(queryFile, query[`${mode}ModeQuery`], "utf8"); + }); - // Download any other required packs - if (language === "java" && modelConfig.llmGeneration) { - await cliServer.packDownload([`codeql/${language}-automodel-queries`]); + // Create any dependencies + if (query.dependencies) { + for (const [filename, contents] of Object.entries(query.dependencies)) { + const dependencyFile = join(queryDir, filename); + await writeFile(dependencyFile, contents, "utf8"); + } } - return true; } -/** - * Resolve the query path to the model editor endpoints query. All queries are tagged like this: - * modeleditor endpoints - * Example: modeleditor endpoints framework-mode - * - * @param cliServer The CodeQL CLI server to use. - * @param language The language of the query pack to use. - * @param mode The mode to resolve the query for. - * @param additionalPackNames Additional pack names to search. - * @param additionalPackPaths Additional pack paths to search. - */ -export async function resolveEndpointsQuery( - cliServer: CodeQLCliServer, - language: string, +export const externalApiQueriesProgressMaxStep = 2000; + +export async function runModelEditorQueries( mode: Mode, - additionalPackNames: string[] = [], - additionalPackPaths: string[] = [], -): Promise { - const packsToSearch = [`codeql/${language}-queries`, ...additionalPackNames]; - - // First, resolve the query that we want to run. - // All queries are tagged like this: - // internal extract automodel - // Example: internal extract automodel framework-mode candidates - const queries = await resolveQueriesFromPacks( + { + cliServer, + queryRunner, + databaseItem, + queryStorageDir, + queryDir, + progress, + token, + }: RunQueryOptions, +): Promise { + // The below code is temporary to allow for rapid prototyping of the queries. Once the queries are stabilized, we will + // move these queries into the `github/codeql` repository and use them like any other contextual (e.g. AST) queries. + // This is intentionally not pretty code, as it will be removed soon. + // For a reference of what this should do in the future, see the previous implementation in + // https://github.com/github/vscode-codeql/blob/089d3566ef0bc67d9b7cc66e8fd6740b31c1c0b0/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts#L33-L72 + + progress({ + message: "Resolving QL packs", + step: 1, + maxStep: externalApiQueriesProgressMaxStep, + }); + const additionalPacks = getOnDiskWorkspaceFolders(); + const extensionPacks = Object.keys( + await cliServer.resolveQlpacks(additionalPacks, true), + ); + + progress({ + message: "Resolving query", + step: 2, + maxStep: externalApiQueriesProgressMaxStep, + }); + + // Resolve the queries from either codeql/java-queries or from the temporary queryDir + const queryPath = await resolveEndpointsQuery( cliServer, - packsToSearch, - { - kind: "table", - "tags contain all": ["modeleditor", "endpoints", modeTag(mode)], - }, - additionalPackPaths, + databaseItem.language, + mode, + [syntheticQueryPackName], + [queryDir], ); - if (queries.length > 1) { - throw new Error( - `Found multiple endpoints queries for ${mode}. Can't continue`, + if (!queryPath) { + void showAndLogExceptionWithTelemetry( + extLogger, + telemetryListener, + redactableError`The ${mode} model editor query could not be found. Try re-opening the model editor. If that doesn't work, try upgrading the CodeQL libraries.`, ); + return; + } + + // Run the actual query + const completedQuery = await runQuery({ + queryRunner, + databaseItem, + queryPath, + queryStorageDir, + additionalPacks, + extensionPacks, + progress: (update) => + progress({ + step: update.step + 500, + maxStep: externalApiQueriesProgressMaxStep, + message: update.message, + }), + token, + }); + + if (!completedQuery) { + return; } - if (queries.length === 0) { + // Read the results and covert to internal representation + progress({ + message: "Decoding results", + step: 1600, + maxStep: externalApiQueriesProgressMaxStep, + }); + + const bqrsChunk = await readQueryResults({ + cliServer, + bqrsPath: completedQuery.outputDir.bqrsPath, + }); + if (!bqrsChunk) { + return; + } + + progress({ + message: "Finalizing results", + step: 1950, + maxStep: externalApiQueriesProgressMaxStep, + }); + + return decodeBqrsToMethods(bqrsChunk, mode); +} + +type GetResultsOptions = { + cliServer: Pick; + bqrsPath: string; +}; + +export async function readQueryResults({ + cliServer, + bqrsPath, +}: GetResultsOptions) { + const bqrsInfo = await cliServer.bqrsInfo(bqrsPath); + if (bqrsInfo["result-sets"].length !== 1) { + void showAndLogExceptionWithTelemetry( + extLogger, + telemetryListener, + redactableError`Expected exactly one result set, got ${bqrsInfo["result-sets"].length}`, + ); return undefined; } - return queries[0]; + const resultSet = bqrsInfo["result-sets"][0]; + + return cliServer.bqrsDecode(bqrsPath, resultSet.name); +} + +function queryNameFromMode(mode: Mode): string { + return `${mode.charAt(0).toUpperCase() + mode.slice(1)}ModeEndpoints.ql`; } diff --git a/extensions/ql-vscode/src/model-editor/model-editor-view.ts b/extensions/ql-vscode/src/model-editor/model-editor-view.ts index cb5d5a40d48..99d643c0ea6 100644 --- a/extensions/ql-vscode/src/model-editor/model-editor-view.ts +++ b/extensions/ql-vscode/src/model-editor/model-editor-view.ts @@ -29,8 +29,8 @@ import { App } from "../common/app"; import { redactableError } from "../common/errors"; import { externalApiQueriesProgressMaxStep, - runExternalApiQueries, -} from "./external-api-usage-queries"; + runModelEditorQueries, +} from "./model-editor-queries"; import { Method } from "./method"; import { ModeledMethod } from "./modeled-method"; import { ExtensionPack } from "./shared/extension-pack"; @@ -411,7 +411,7 @@ export class ModelEditorView extends AbstractWebview< try { const cancellationTokenSource = new CancellationTokenSource(); - const queryResult = await runExternalApiQueries(mode, { + const queryResult = await runModelEditorQueries(mode, { cliServer: this.cliServer, queryRunner: this.queryRunner, databaseItem: this.databaseItem, @@ -433,9 +433,9 @@ export class ModelEditorView extends AbstractWebview< void showAndLogExceptionWithTelemetry( this.app.logger, this.app.telemetry, - redactableError( - asError(err), - )`Failed to load external API usages: ${getErrorMessage(err)}`, + redactableError(asError(err))`Failed to load results: ${getErrorMessage( + err, + )}`, ); } } diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/external-api-usage-query.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/external-api-usage-query.test.ts index 5641474f2a6..9650390d66e 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/external-api-usage-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/external-api-usage-query.test.ts @@ -1,7 +1,7 @@ import { readQueryResults, - runExternalApiQueries, -} from "../../../../src/model-editor/external-api-usage-queries"; + runModelEditorQueries, +} from "../../../../src/model-editor/model-editor-queries"; import { createMockLogger } from "../../../__mocks__/loggerMock"; import { DatabaseItem, @@ -21,274 +21,272 @@ import { CodeQLCliServer } from "../../../../src/codeql-cli/cli"; import { QueryRunner } from "../../../../src/query-server"; import { QueryOutputDir } from "../../../../src/run-queries-shared"; -describe("external api usage query", () => { - describe("runQuery", () => { - const language = Object.keys(fetchExternalApiQueries)[ - Math.floor(Math.random() * Object.keys(fetchExternalApiQueries).length) - ] as QueryLanguage; +describe("runModelEditorQueries", () => { + const language = Object.keys(fetchExternalApiQueries)[ + Math.floor(Math.random() * Object.keys(fetchExternalApiQueries).length) + ] as QueryLanguage; - const queryDir = dirSync({ unsafeCleanup: true }).name; + const queryDir = dirSync({ unsafeCleanup: true }).name; - it("should log an error", async () => { - const showAndLogExceptionWithTelemetrySpy: jest.SpiedFunction< - typeof showAndLogExceptionWithTelemetry - > = jest.spyOn(log, "showAndLogExceptionWithTelemetry"); + it("should log an error", async () => { + const showAndLogExceptionWithTelemetrySpy: jest.SpiedFunction< + typeof showAndLogExceptionWithTelemetry + > = jest.spyOn(log, "showAndLogExceptionWithTelemetry"); - const outputDir = new QueryOutputDir(join((await file()).path, "1")); + const outputDir = new QueryOutputDir(join((await file()).path, "1")); - const query = fetchExternalApiQueries[language]; - if (!query) { - throw new Error(`No query found for language ${language}`); - } + const query = fetchExternalApiQueries[language]; + if (!query) { + throw new Error(`No query found for language ${language}`); + } - const options = { - cliServer: mockedObject({ - resolveQlpacks: jest.fn().mockResolvedValue({ - "my/extensions": "/a/b/c/", - }), - resolveQueriesInSuite: jest - .fn() - .mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]), - packPacklist: jest - .fn() - .mockResolvedValue([ - "/a/b/c/qlpack.yml", - "/a/b/c/qlpack.lock.yml", - "/a/b/c/qlpack2.yml", - ]), + const options = { + cliServer: mockedObject({ + resolveQlpacks: jest.fn().mockResolvedValue({ + "my/extensions": "/a/b/c/", }), - queryRunner: mockedObject({ - createQueryRun: jest.fn().mockReturnValue({ - evaluate: jest.fn().mockResolvedValue({ - resultType: QueryResultType.CANCELLATION, - }), - outputDir, + resolveQueriesInSuite: jest + .fn() + .mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]), + packPacklist: jest + .fn() + .mockResolvedValue([ + "/a/b/c/qlpack.yml", + "/a/b/c/qlpack.lock.yml", + "/a/b/c/qlpack2.yml", + ]), + }), + queryRunner: mockedObject({ + createQueryRun: jest.fn().mockReturnValue({ + evaluate: jest.fn().mockResolvedValue({ + resultType: QueryResultType.CANCELLATION, }), - logger: createMockLogger(), + outputDir, }), - databaseItem: mockedObject({ - databaseUri: mockedUri("/a/b/c/src.zip"), - contents: { - kind: DatabaseKind.Database, - name: "foo", - datasetUri: mockedUri(), - }, - language, - }), - queryStorageDir: "/tmp/queries", - queryDir, - progress: jest.fn(), - token: { - isCancellationRequested: false, - onCancellationRequested: jest.fn(), + logger: createMockLogger(), + }), + databaseItem: mockedObject({ + databaseUri: mockedUri("/a/b/c/src.zip"), + contents: { + kind: DatabaseKind.Database, + name: "foo", + datasetUri: mockedUri(), }, - }; + language, + }), + queryStorageDir: "/tmp/queries", + queryDir, + progress: jest.fn(), + token: { + isCancellationRequested: false, + onCancellationRequested: jest.fn(), + }, + }; - expect( - await runExternalApiQueries(Mode.Application, options), - ).toBeUndefined(); - expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith( - expect.anything(), - undefined, - expect.any(RedactableError), - ); - }); + expect( + await runModelEditorQueries(Mode.Application, options), + ).toBeUndefined(); + expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith( + expect.anything(), + undefined, + expect.any(RedactableError), + ); + }); - it("should run query for random language", async () => { - const outputDir = new QueryOutputDir(join((await file()).path, "1")); + it("should run query for random language", async () => { + const outputDir = new QueryOutputDir(join((await file()).path, "1")); - const query = fetchExternalApiQueries[language]; - if (!query) { - throw new Error(`No query found for language ${language}`); - } + const query = fetchExternalApiQueries[language]; + if (!query) { + throw new Error(`No query found for language ${language}`); + } - const options = { - cliServer: mockedObject({ - resolveQlpacks: jest.fn().mockResolvedValue({ - "my/extensions": "/a/b/c/", - }), - resolveQueriesInSuite: jest - .fn() - .mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]), - packPacklist: jest - .fn() - .mockResolvedValue([ - "/a/b/c/qlpack.yml", - "/a/b/c/qlpack.lock.yml", - "/a/b/c/qlpack2.yml", - ]), - bqrsInfo: jest.fn().mockResolvedValue({ - "result-sets": [], - }), + const options = { + cliServer: mockedObject({ + resolveQlpacks: jest.fn().mockResolvedValue({ + "my/extensions": "/a/b/c/", + }), + resolveQueriesInSuite: jest + .fn() + .mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]), + packPacklist: jest + .fn() + .mockResolvedValue([ + "/a/b/c/qlpack.yml", + "/a/b/c/qlpack.lock.yml", + "/a/b/c/qlpack2.yml", + ]), + bqrsInfo: jest.fn().mockResolvedValue({ + "result-sets": [], }), - queryRunner: mockedObject({ - createQueryRun: jest.fn().mockReturnValue({ - evaluate: jest.fn().mockResolvedValue({ - resultType: QueryResultType.SUCCESS, - outputDir, - }), + }), + queryRunner: mockedObject({ + createQueryRun: jest.fn().mockReturnValue({ + evaluate: jest.fn().mockResolvedValue({ + resultType: QueryResultType.SUCCESS, outputDir, }), - logger: createMockLogger(), + outputDir, }), - databaseItem: mockedObject({ - databaseUri: mockedUri("/a/b/c/src.zip"), - contents: { - kind: DatabaseKind.Database, - name: "foo", - datasetUri: mockedUri(), - }, - language, - }), - queryStorageDir: "/tmp/queries", - queryDir, - progress: jest.fn(), - token: { - isCancellationRequested: false, - onCancellationRequested: jest.fn(), + logger: createMockLogger(), + }), + databaseItem: mockedObject({ + databaseUri: mockedUri("/a/b/c/src.zip"), + contents: { + kind: DatabaseKind.Database, + name: "foo", + datasetUri: mockedUri(), }, - }; + language, + }), + queryStorageDir: "/tmp/queries", + queryDir, + progress: jest.fn(), + token: { + isCancellationRequested: false, + onCancellationRequested: jest.fn(), + }, + }; - const result = await runExternalApiQueries(Mode.Framework, options); + const result = await runModelEditorQueries(Mode.Framework, options); - expect(result).not.toBeUndefined; + expect(result).not.toBeUndefined; - expect(options.cliServer.resolveQlpacks).toHaveBeenCalledTimes(1); - expect(options.cliServer.resolveQlpacks).toHaveBeenCalledWith([], true); - expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith( - "/a/b/c/src.zip", - { - queryPath: expect.stringMatching(/\S*ModeEndpoints\.ql/), - quickEvalPosition: undefined, - quickEvalCountOnly: false, - }, - false, - [], - ["my/extensions"], - {}, - "/tmp/queries", - undefined, - undefined, - ); - }); + expect(options.cliServer.resolveQlpacks).toHaveBeenCalledTimes(1); + expect(options.cliServer.resolveQlpacks).toHaveBeenCalledWith([], true); + expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith( + "/a/b/c/src.zip", + { + queryPath: expect.stringMatching(/\S*ModeEndpoints\.ql/), + quickEvalPosition: undefined, + quickEvalCountOnly: false, + }, + false, + [], + ["my/extensions"], + {}, + "/tmp/queries", + undefined, + undefined, + ); }); +}); - describe("readQueryResults", () => { - const options = { - cliServer: { - bqrsInfo: jest.fn(), - bqrsDecode: jest.fn(), - }, - bqrsPath: "/tmp/results.bqrs", - }; +describe("readQueryResults", () => { + const options = { + cliServer: { + bqrsInfo: jest.fn(), + bqrsDecode: jest.fn(), + }, + bqrsPath: "/tmp/results.bqrs", + }; - let showAndLogExceptionWithTelemetrySpy: jest.SpiedFunction< - typeof showAndLogExceptionWithTelemetry - >; + let showAndLogExceptionWithTelemetrySpy: jest.SpiedFunction< + typeof showAndLogExceptionWithTelemetry + >; - beforeEach(() => { - showAndLogExceptionWithTelemetrySpy = jest.spyOn( - log, - "showAndLogExceptionWithTelemetry", - ); + beforeEach(() => { + showAndLogExceptionWithTelemetrySpy = jest.spyOn( + log, + "showAndLogExceptionWithTelemetry", + ); + }); + + it("returns undefined when there are no results", async () => { + options.cliServer.bqrsInfo.mockResolvedValue({ + "result-sets": [], }); - it("returns undefined when there are no results", async () => { - options.cliServer.bqrsInfo.mockResolvedValue({ - "result-sets": [], - }); + expect(await readQueryResults(options)).toBeUndefined(); + expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith( + expect.anything(), + undefined, + expect.any(RedactableError), + ); + }); - expect(await readQueryResults(options)).toBeUndefined(); - expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith( - expect.anything(), - undefined, - expect.any(RedactableError), - ); + it("returns undefined when there are multiple result sets", async () => { + options.cliServer.bqrsInfo.mockResolvedValue({ + "result-sets": [ + { + name: "#select", + rows: 10, + columns: [ + { name: "usage", kind: "e" }, + { name: "apiName", kind: "s" }, + { kind: "s" }, + { kind: "s" }, + ], + }, + { + name: "#select2", + rows: 10, + columns: [ + { name: "usage", kind: "e" }, + { name: "apiName", kind: "s" }, + { kind: "s" }, + { kind: "s" }, + ], + }, + ], }); - it("returns undefined when there are multiple result sets", async () => { - options.cliServer.bqrsInfo.mockResolvedValue({ - "result-sets": [ - { - name: "#select", - rows: 10, - columns: [ - { name: "usage", kind: "e" }, - { name: "apiName", kind: "s" }, - { kind: "s" }, - { kind: "s" }, - ], - }, - { - name: "#select2", - rows: 10, - columns: [ - { name: "usage", kind: "e" }, - { name: "apiName", kind: "s" }, - { kind: "s" }, - { kind: "s" }, - ], - }, - ], - }); + expect(await readQueryResults(options)).toBeUndefined(); + expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith( + expect.anything(), + undefined, + expect.any(RedactableError), + ); + }); - expect(await readQueryResults(options)).toBeUndefined(); - expect(showAndLogExceptionWithTelemetrySpy).toHaveBeenCalledWith( - expect.anything(), - undefined, - expect.any(RedactableError), - ); + it("gets the result set", async () => { + options.cliServer.bqrsInfo.mockResolvedValue({ + "result-sets": [ + { + name: "#select", + rows: 10, + columns: [ + { name: "usage", kind: "e" }, + { name: "apiName", kind: "s" }, + { kind: "s" }, + { kind: "s" }, + ], + }, + ], + "compatible-query-kinds": ["Table", "Tree", "Graph"], }); - - it("gets the result set", async () => { - options.cliServer.bqrsInfo.mockResolvedValue({ - "result-sets": [ + const decodedResultSet = { + columns: [ + { name: "usage", kind: "e" }, + { name: "apiName", kind: "s" }, + { kind: "s" }, + { kind: "s" }, + ], + tuples: [ + [ + "java.io.PrintStream#println(String)", + true, { - name: "#select", - rows: 10, - columns: [ - { name: "usage", kind: "e" }, - { name: "apiName", kind: "s" }, - { kind: "s" }, - { kind: "s" }, - ], - }, - ], - "compatible-query-kinds": ["Table", "Tree", "Graph"], - }); - const decodedResultSet = { - columns: [ - { name: "usage", kind: "e" }, - { name: "apiName", kind: "s" }, - { kind: "s" }, - { kind: "s" }, - ], - tuples: [ - [ - "java.io.PrintStream#println(String)", - true, - { - label: "println(...)", - url: { - uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", - startLine: 29, - startColumn: 9, - endLine: 29, - endColumn: 49, - }, + label: "println(...)", + url: { + uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java", + startLine: 29, + startColumn: 9, + endLine: 29, + endColumn: 49, }, - ], + }, ], - }; - options.cliServer.bqrsDecode.mockResolvedValue(decodedResultSet); + ], + }; + options.cliServer.bqrsDecode.mockResolvedValue(decodedResultSet); - const result = await readQueryResults(options); - expect(result).toEqual(decodedResultSet); - expect(options.cliServer.bqrsInfo).toHaveBeenCalledWith(options.bqrsPath); - expect(options.cliServer.bqrsDecode).toHaveBeenCalledWith( - options.bqrsPath, - "#select", - ); - }); + const result = await readQueryResults(options); + expect(result).toEqual(decodedResultSet); + expect(options.cliServer.bqrsInfo).toHaveBeenCalledWith(options.bqrsPath); + expect(options.cliServer.bqrsDecode).toHaveBeenCalledWith( + options.bqrsPath, + "#select", + ); }); }); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/model-editor-queries.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/model-editor-queries.test.ts index ccbafc1867a..ccbf5420a57 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/model-editor-queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/model-editor-queries.test.ts @@ -1,7 +1,7 @@ import { readFile, readFileSync, readdir } from "fs-extra"; import { join } from "path"; import { load } from "js-yaml"; -import { setUpPack } from "../../../../src/model-editor/model-editor-queries"; +import { setUpPack } from "../../../../src/model-editor/model-editor-queries-setup"; import { dirSync } from "tmp-promise"; import { fetchExternalApiQueries } from "../../../../src/model-editor/queries"; import { QueryLanguage } from "../../../../src/common/query-language"; @@ -57,7 +57,7 @@ describe("setUpPack", () => { ); const suiteYaml = load(suiteFileContents); expect(suiteYaml).toEqual({ - name: "codeql/external-api-usage", + name: "codeql/model-editor-queries", version: "0.0.0", dependencies: { [`codeql/${language}-all`]: "*", @@ -108,7 +108,7 @@ describe("setUpPack", () => { ); const suiteYaml = load(suiteFileContents); expect(suiteYaml).toEqual({ - name: "codeql/external-api-usage", + name: "codeql/model-editor-queries", version: "0.0.0", dependencies: {}, });