diff --git a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts index e6e3a51e3..47e3f280d 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts @@ -9,22 +9,16 @@ import { join } from "path"; import { App } from "../common/app"; import { withProgress } from "../common/vscode/progress"; import { pickExtensionPack } from "./extension-pack-picker"; -import { - showAndLogErrorMessage, - showAndLogExceptionWithTelemetry, -} from "../common/logging"; +import { showAndLogErrorMessage } from "../common/logging"; import { dir } from "tmp-promise"; -import { fetchExternalApiQueries } from "./queries"; -import { telemetryListener } from "../common/vscode/telemetry"; -import { redactableError } from "../common/errors"; -import { extLogger } from "../common/logging/vscode"; + import { isQueryLanguage } from "../common/query-language"; -import { setUpPack } from "./external-api-usage-queries"; import { DisposableObject } from "../common/disposable-object"; import { ModelDetailsPanel } from "./methods-usage/methods-usage-panel"; import { Mode } from "./shared/mode"; import { showResolvableLocation } from "../databases/local-databases/locations"; import { Usage } from "./external-api-usage"; +import { setUpPack } from "./data-extensions-editor-queries"; const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"]; @@ -138,19 +132,12 @@ export class DataExtensionsEditorModule extends DisposableObject { return; } - const query = fetchExternalApiQueries[language]; - if (!query) { - void showAndLogExceptionWithTelemetry( - extLogger, - telemetryListener, - redactableError`No external API usage query found for language ${language}`, - ); - return; - } - // Create new temporary directory for query files and pack dependencies const queryDir = (await dir({ unsafeCleanup: true })).path; - await setUpPack(queryDir, query, language); + const success = await setUpPack(queryDir, language); + if (!success) { + return; + } await this.cliServer.packInstall(queryDir); const view = new DataExtensionsEditorView( diff --git a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-queries.ts b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-queries.ts new file mode 100644 index 000000000..a1059aa49 --- /dev/null +++ b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-queries.ts @@ -0,0 +1,38 @@ +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"; + +/** + * setUpPack sets up a directory to use for the data extension editor queries. + * @param queryDir The directory to set up. + * @param language The language to use for the queries. + * @returns true if the setup was successful, false otherwise. + */ +export async function setUpPack( + queryDir: string, + language: QueryLanguage, +): Promise { + // Create the external API query + const externalApiQuerySuccess = await prepareExternalApiQuery( + queryDir, + language, + ); + if (!externalApiQuerySuccess) { + return false; + } + + // Set up a synthetic query pack to resolve dependencies. + const syntheticQueryPack = { + name: "codeql/external-api-usage", + version: "0.0.0", + dependencies: { + [`codeql/${language}-all`]: "*", + }, + }; + + const qlpackFile = join(queryDir, "codeql-pack.yml"); + await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8"); + return true; +} diff --git a/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-queries.ts b/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-queries.ts index ae7f5e02e..0c847bcfd 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-queries.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-queries.ts @@ -12,9 +12,8 @@ import { telemetryListener } from "../common/vscode/telemetry"; import { join } from "path"; import { Mode } from "./shared/mode"; import { writeFile } from "fs-extra"; -import { Query } from "./queries/query"; import { QueryLanguage } from "../common/query-language"; -import { dump } from "js-yaml"; +import { fetchExternalApiQueries } from "./queries"; type RunQueryOptions = { cliServer: Pick; @@ -27,36 +26,34 @@ type RunQueryOptions = { token: CancellationToken; }; -export async function setUpPack( +export async function prepareExternalApiQuery( queryDir: string, - query: Query, language: QueryLanguage, -) { - Object.values(Mode).map(async (mode) => { - const queryFile = join( - queryDir, - `FetchExternalApis${mode.charAt(0).toUpperCase() + mode.slice(1)}Mode.ql`, +): 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"); } } - - const syntheticQueryPack = { - name: "codeql/external-api-usage", - version: "0.0.0", - dependencies: { - [`codeql/${language}-all`]: "*", - }, - }; - - const qlpackFile = join(queryDir, "codeql-pack.yml"); - await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8"); + return true; } export async function runQuery( @@ -145,3 +142,9 @@ export async function readQueryResults({ return cliServer.bqrsDecode(bqrsPath, resultSet.name); } + +function queryNameFromMode(mode: Mode): string { + return `FetchExternalApis${ + mode.charAt(0).toUpperCase() + mode.slice(1) + }Mode.ql`; +} diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/data-extensions-editor-queries.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/data-extensions-editor-queries.test.ts new file mode 100644 index 000000000..770f837a2 --- /dev/null +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/data-extensions-editor-queries.test.ts @@ -0,0 +1,72 @@ +import { readFile, readFileSync, readdir } from "fs-extra"; +import { join } from "path"; +import { load } from "js-yaml"; +import { setUpPack } from "../../../../src/data-extensions-editor/data-extensions-editor-queries"; +import { dirSync } from "tmp-promise"; +import { fetchExternalApiQueries } from "../../../../src/data-extensions-editor/queries"; +import { QueryLanguage } from "../../../../src/common/query-language"; +import { Mode } from "../../../../src/data-extensions-editor/shared/mode"; + +describe("setUpPack", () => { + const languages = Object.keys(fetchExternalApiQueries).flatMap((lang) => { + const queryDir = dirSync({ unsafeCleanup: true }).name; + const query = fetchExternalApiQueries[lang as QueryLanguage]; + if (!query) { + return []; + } + + return { language: lang as QueryLanguage, queryDir, query }; + }); + + test.each(languages)( + "should create files for $language", + async ({ language, queryDir, query }) => { + await setUpPack(queryDir, language); + + const queryFiles = await readdir(queryDir); + expect(queryFiles.sort()).toEqual( + [ + "codeql-pack.yml", + "FetchExternalApisApplicationMode.ql", + "FetchExternalApisFrameworkMode.ql", + "AutomodelVsCode.qll", + ].sort(), + ); + + const suiteFileContents = await readFile( + join(queryDir, "codeql-pack.yml"), + "utf8", + ); + const suiteYaml = load(suiteFileContents); + expect(suiteYaml).toEqual({ + name: "codeql/external-api-usage", + version: "0.0.0", + dependencies: { + [`codeql/${language}-all`]: "*", + }, + }); + + Object.values(Mode).forEach((mode) => { + expect( + readFileSync( + join( + queryDir, + `FetchExternalApis${ + mode.charAt(0).toUpperCase() + mode.slice(1) + }Mode.ql`, + ), + "utf8", + ), + ).toEqual(query[`${mode}ModeQuery`]); + }); + + for (const [filename, contents] of Object.entries( + query.dependencies ?? {}, + )) { + expect(await readFile(join(queryDir, filename), "utf8")).toEqual( + contents, + ); + } + }, + ); +}); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts index 4f150ea42..257e3301e 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts @@ -1,7 +1,6 @@ import { readQueryResults, runQuery, - setUpPack, } from "../../../../src/data-extensions-editor/external-api-usage-queries"; import { createMockLogger } from "../../../__mocks__/loggerMock"; import { DatabaseKind } from "../../../../src/databases/local-databases"; @@ -14,75 +13,8 @@ import { showAndLogExceptionWithTelemetry } from "../../../../src/common/logging import { QueryLanguage } from "../../../../src/common/query-language"; import { mockedUri } from "../../utils/mocking.helpers"; import { Mode } from "../../../../src/data-extensions-editor/shared/mode"; -import { readFile, readFileSync, readdir } from "fs-extra"; -import { join } from "path"; -import { load } from "js-yaml"; describe("external api usage query", () => { - describe("setUpPack", () => { - const languages = Object.keys(fetchExternalApiQueries).flatMap((lang) => { - const queryDir = dirSync({ unsafeCleanup: true }).name; - const query = fetchExternalApiQueries[lang as QueryLanguage]; - if (!query) { - return []; - } - - return { language: lang as QueryLanguage, queryDir, query }; - }); - - test.each(languages)( - "should create files for $language", - async ({ language, queryDir, query }) => { - await setUpPack(queryDir, query, language); - - const queryFiles = await readdir(queryDir); - expect(queryFiles.sort()).toEqual( - [ - "codeql-pack.yml", - "FetchExternalApisApplicationMode.ql", - "FetchExternalApisFrameworkMode.ql", - "AutomodelVsCode.qll", - ].sort(), - ); - - const suiteFileContents = await readFile( - join(queryDir, "codeql-pack.yml"), - "utf8", - ); - const suiteYaml = load(suiteFileContents); - expect(suiteYaml).toEqual({ - name: "codeql/external-api-usage", - version: "0.0.0", - dependencies: { - [`codeql/${language}-all`]: "*", - }, - }); - - Object.values(Mode).forEach((mode) => { - expect( - readFileSync( - join( - queryDir, - `FetchExternalApis${ - mode.charAt(0).toUpperCase() + mode.slice(1) - }Mode.ql`, - ), - "utf8", - ), - ).toEqual(query[`${mode}ModeQuery`]); - }); - - for (const [filename, contents] of Object.entries( - query.dependencies ?? {}, - )) { - expect(await readFile(join(queryDir, filename), "utf8")).toEqual( - contents, - ); - } - }, - ); - }); - describe("runQuery", () => { const language = Object.keys(fetchExternalApiQueries)[ Math.floor(Math.random() * Object.keys(fetchExternalApiQueries).length)