From b20a65a3a7c42b486de0179205af22f6a25b45d8 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Mon, 8 May 2023 13:56:11 +0200 Subject: [PATCH] Use `QueryLanguage` type for representing query languages This will make both the `askForLanguage` and `findLanguage` functions return a `QueryLanguage` instead of a `string`. This will make it harder to make mistakes when using these functions. There are also some other changes with regards to `QueryLanguage` such that we never need to use `as QueryLanguage` explicitly anymore, except for the new `isQueryLanguage` function. The only remaining place that I know of where we're using a `string` to represent the `QueryLanguage` is in a database item's language, but this is harder to change and may be relied upon by language authors. --- .../external-api-usage-query.ts | 11 ++++++-- .../src/databases/local-databases.ts | 11 ++++++-- extensions/ql-vscode/src/helpers.ts | 28 ++++++++++++++++--- .../ql-vscode/src/skeleton-query-wizard.ts | 12 ++++++-- .../src/variant-analysis/run-remote-query.ts | 3 +- 5 files changed, 53 insertions(+), 12 deletions(-) diff --git a/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts b/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts index 9fbc7b293..b2f269f0c 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts @@ -4,6 +4,7 @@ import { writeFile } from "fs-extra"; import { dump as dumpYaml } from "js-yaml"; import { getOnDiskWorkspaceFolders, + isQueryLanguage, showAndLogExceptionWithTelemetry, } from "../helpers"; import { TeeLogger } from "../common"; @@ -15,7 +16,6 @@ import { fetchExternalApiQueries } from "./queries"; import { QueryResultType } from "../pure/new-messages"; import { join } from "path"; import { redactableError } from "../pure/errors"; -import { QueryLanguage } from "../common/query-language"; export type RunQueryOptions = { cliServer: Pick; @@ -41,7 +41,14 @@ export async function runQuery({ // 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 - const query = fetchExternalApiQueries[databaseItem.language as QueryLanguage]; + if (!isQueryLanguage(databaseItem.language)) { + void showAndLogExceptionWithTelemetry( + redactableError`Unsupported database language ${databaseItem.language}`, + ); + return; + } + + const query = fetchExternalApiQueries[databaseItem.language]; if (!query) { void showAndLogExceptionWithTelemetry( redactableError`No external API usage query found for language ${databaseItem.language}`, diff --git a/extensions/ql-vscode/src/databases/local-databases.ts b/extensions/ql-vscode/src/databases/local-databases.ts index 84886d11b..f63f0ecec 100644 --- a/extensions/ql-vscode/src/databases/local-databases.ts +++ b/extensions/ql-vscode/src/databases/local-databases.ts @@ -12,6 +12,7 @@ import { isFolderAlreadyInWorkspace, getFirstWorkspaceFolder, showNeverAskAgainDialog, + isQueryLanguage, } from "../helpers"; import { ProgressCallback, withProgress } from "../common/vscode/progress"; import { @@ -32,7 +33,6 @@ import { setAutogenerateQlPacks, } from "../config"; import { QlPackGenerator } from "../qlpack-generator"; -import { QueryLanguage } from "../common/query-language"; import { App } from "../common/app"; import { existsSync } from "fs"; @@ -739,6 +739,13 @@ export class DatabaseManager extends DisposableObject { return; } + if (!isQueryLanguage(databaseItem.language)) { + void this.logger.log( + "Could not create skeleton QL pack because the selected database's language is not supported.", + ); + return; + } + const firstWorkspaceFolder = getFirstWorkspaceFolder(); const folderName = `codeql-custom-queries-${databaseItem.language}`; @@ -769,7 +776,7 @@ export class DatabaseManager extends DisposableObject { try { const qlPackGenerator = new QlPackGenerator( folderName, - databaseItem.language as QueryLanguage, + databaseItem.language, this.cli, firstWorkspaceFolder, ); diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index e7e33f206..b15511f9f 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -25,7 +25,7 @@ import { QueryMetadata } from "./pure/interface-types"; import { telemetryListener } from "./telemetry"; import { RedactableError } from "./pure/errors"; import { getQlPackPath } from "./pure/ql"; -import { dbSchemeToLanguage } from "./common/query-language"; +import { dbSchemeToLanguage, QueryLanguage } from "./common/query-language"; import { isCodespacesTemplate } from "./config"; import { AppCommandManager } from "./common/commands"; @@ -726,6 +726,10 @@ export async function isLikelyDbLanguageFolder(dbPath: string) { ); } +export function isQueryLanguage(language: string): language is QueryLanguage { + return Object.values(QueryLanguage).includes(language as QueryLanguage); +} + /** * Finds the language that a query targets. * If it can't be autodetected, prompt the user to specify the language manually. @@ -733,7 +737,7 @@ export async function isLikelyDbLanguageFolder(dbPath: string) { export async function findLanguage( cliServer: CodeQLCliServer, queryUri: Uri | undefined, -): Promise { +): Promise { const uri = queryUri || Window.activeTextEditor?.document.uri; if (uri !== undefined) { try { @@ -743,7 +747,14 @@ export async function findLanguage( ); const language = Object.keys(queryInfo.byLanguage)[0]; void extLogger.log(`Detected query language: ${language}`); - return language; + + if (isQueryLanguage(language)) { + return language; + } + + void extLogger.log( + "Query language is unsupported. Select language manually.", + ); } catch (e) { void extLogger.log( "Could not autodetect query language. Select language manually.", @@ -758,7 +769,7 @@ export async function findLanguage( export async function askForLanguage( cliServer: CodeQLCliServer, throwOnEmpty = true, -): Promise { +): Promise { const language = await Window.showQuickPick( await cliServer.getSupportedLanguages(), { @@ -775,7 +786,16 @@ export async function askForLanguage( "Language not found. Language must be specified manually.", ); } + return undefined; } + + if (!isQueryLanguage(language)) { + void showAndLogErrorMessage( + `Language '${language}' is not supported. Language must be specified manually.`, + ); + return undefined; + } + return language; } diff --git a/extensions/ql-vscode/src/skeleton-query-wizard.ts b/extensions/ql-vscode/src/skeleton-query-wizard.ts index c9061c10b..259318e34 100644 --- a/extensions/ql-vscode/src/skeleton-query-wizard.ts +++ b/extensions/ql-vscode/src/skeleton-query-wizard.ts @@ -41,7 +41,7 @@ export const QUERY_LANGUAGE_TO_DATABASE_REPO: QueryLanguagesToDatabaseMap = { }; export class SkeletonQueryWizard { - private language: string | undefined; + private language: QueryLanguage | undefined; private fileName = "example.ql"; private qlPackStoragePath: string | undefined; @@ -154,6 +154,9 @@ export class SkeletonQueryWizard { if (this.folderName === undefined) { throw new Error("Folder name is undefined"); } + if (this.language === undefined) { + throw new Error("Language is undefined"); + } this.progress({ message: "Creating skeleton QL pack around query", @@ -164,7 +167,7 @@ export class SkeletonQueryWizard { try { const qlPackGenerator = new QlPackGenerator( this.folderName, - this.language as QueryLanguage, + this.language, this.cliServer, this.qlPackStoragePath, ); @@ -181,6 +184,9 @@ export class SkeletonQueryWizard { if (this.folderName === undefined) { throw new Error("Folder name is undefined"); } + if (this.language === undefined) { + throw new Error("Language is undefined"); + } this.progress({ message: @@ -192,7 +198,7 @@ export class SkeletonQueryWizard { try { const qlPackGenerator = new QlPackGenerator( this.folderName, - this.language as QueryLanguage, + this.language, this.cliServer, this.qlPackStoragePath, ); diff --git a/extensions/ql-vscode/src/variant-analysis/run-remote-query.ts b/extensions/ql-vscode/src/variant-analysis/run-remote-query.ts index aa6285cfe..41402767d 100644 --- a/extensions/ql-vscode/src/variant-analysis/run-remote-query.ts +++ b/extensions/ql-vscode/src/variant-analysis/run-remote-query.ts @@ -39,6 +39,7 @@ import { QLPACK_FILENAMES, QLPACK_LOCK_FILENAMES, } from "../pure/ql"; +import { QueryLanguage } from "../common/query-language"; export interface QlPack { name: string; @@ -76,7 +77,7 @@ async function generateQueryPack( const targetQueryFileName = join(queryPackDir, packRelativePath); const workspaceFolders = getOnDiskWorkspaceFolders(); - let language: string | undefined; + let language: QueryLanguage | undefined; // Check if the query is already in a query pack. // If so, copy the entire query pack to the temporary directory.