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.
This commit is contained in:
Koen Vlaswinkel
2023-05-08 13:56:11 +02:00
parent bf9eb2469b
commit b20a65a3a7
5 changed files with 53 additions and 12 deletions

View File

@@ -4,6 +4,7 @@ import { writeFile } from "fs-extra";
import { dump as dumpYaml } from "js-yaml"; import { dump as dumpYaml } from "js-yaml";
import { import {
getOnDiskWorkspaceFolders, getOnDiskWorkspaceFolders,
isQueryLanguage,
showAndLogExceptionWithTelemetry, showAndLogExceptionWithTelemetry,
} from "../helpers"; } from "../helpers";
import { TeeLogger } from "../common"; import { TeeLogger } from "../common";
@@ -15,7 +16,6 @@ import { fetchExternalApiQueries } from "./queries";
import { QueryResultType } from "../pure/new-messages"; import { QueryResultType } from "../pure/new-messages";
import { join } from "path"; import { join } from "path";
import { redactableError } from "../pure/errors"; import { redactableError } from "../pure/errors";
import { QueryLanguage } from "../common/query-language";
export type RunQueryOptions = { export type RunQueryOptions = {
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">; cliServer: Pick<CodeQLCliServer, "resolveQlpacks">;
@@ -41,7 +41,14 @@ export async function runQuery({
// For a reference of what this should do in the future, see the previous implementation in // 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 // 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) { if (!query) {
void showAndLogExceptionWithTelemetry( void showAndLogExceptionWithTelemetry(
redactableError`No external API usage query found for language ${databaseItem.language}`, redactableError`No external API usage query found for language ${databaseItem.language}`,

View File

@@ -12,6 +12,7 @@ import {
isFolderAlreadyInWorkspace, isFolderAlreadyInWorkspace,
getFirstWorkspaceFolder, getFirstWorkspaceFolder,
showNeverAskAgainDialog, showNeverAskAgainDialog,
isQueryLanguage,
} from "../helpers"; } from "../helpers";
import { ProgressCallback, withProgress } from "../common/vscode/progress"; import { ProgressCallback, withProgress } from "../common/vscode/progress";
import { import {
@@ -32,7 +33,6 @@ import {
setAutogenerateQlPacks, setAutogenerateQlPacks,
} from "../config"; } from "../config";
import { QlPackGenerator } from "../qlpack-generator"; import { QlPackGenerator } from "../qlpack-generator";
import { QueryLanguage } from "../common/query-language";
import { App } from "../common/app"; import { App } from "../common/app";
import { existsSync } from "fs"; import { existsSync } from "fs";
@@ -739,6 +739,13 @@ export class DatabaseManager extends DisposableObject {
return; 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 firstWorkspaceFolder = getFirstWorkspaceFolder();
const folderName = `codeql-custom-queries-${databaseItem.language}`; const folderName = `codeql-custom-queries-${databaseItem.language}`;
@@ -769,7 +776,7 @@ export class DatabaseManager extends DisposableObject {
try { try {
const qlPackGenerator = new QlPackGenerator( const qlPackGenerator = new QlPackGenerator(
folderName, folderName,
databaseItem.language as QueryLanguage, databaseItem.language,
this.cli, this.cli,
firstWorkspaceFolder, firstWorkspaceFolder,
); );

View File

@@ -25,7 +25,7 @@ import { QueryMetadata } from "./pure/interface-types";
import { telemetryListener } from "./telemetry"; import { telemetryListener } from "./telemetry";
import { RedactableError } from "./pure/errors"; import { RedactableError } from "./pure/errors";
import { getQlPackPath } from "./pure/ql"; import { getQlPackPath } from "./pure/ql";
import { dbSchemeToLanguage } from "./common/query-language"; import { dbSchemeToLanguage, QueryLanguage } from "./common/query-language";
import { isCodespacesTemplate } from "./config"; import { isCodespacesTemplate } from "./config";
import { AppCommandManager } from "./common/commands"; 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. * Finds the language that a query targets.
* If it can't be autodetected, prompt the user to specify the language manually. * 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( export async function findLanguage(
cliServer: CodeQLCliServer, cliServer: CodeQLCliServer,
queryUri: Uri | undefined, queryUri: Uri | undefined,
): Promise<string | undefined> { ): Promise<QueryLanguage | undefined> {
const uri = queryUri || Window.activeTextEditor?.document.uri; const uri = queryUri || Window.activeTextEditor?.document.uri;
if (uri !== undefined) { if (uri !== undefined) {
try { try {
@@ -743,7 +747,14 @@ export async function findLanguage(
); );
const language = Object.keys(queryInfo.byLanguage)[0]; const language = Object.keys(queryInfo.byLanguage)[0];
void extLogger.log(`Detected query language: ${language}`); 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) { } catch (e) {
void extLogger.log( void extLogger.log(
"Could not autodetect query language. Select language manually.", "Could not autodetect query language. Select language manually.",
@@ -758,7 +769,7 @@ export async function findLanguage(
export async function askForLanguage( export async function askForLanguage(
cliServer: CodeQLCliServer, cliServer: CodeQLCliServer,
throwOnEmpty = true, throwOnEmpty = true,
): Promise<string | undefined> { ): Promise<QueryLanguage | undefined> {
const language = await Window.showQuickPick( const language = await Window.showQuickPick(
await cliServer.getSupportedLanguages(), await cliServer.getSupportedLanguages(),
{ {
@@ -775,7 +786,16 @@ export async function askForLanguage(
"Language not found. Language must be specified manually.", "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; return language;
} }

View File

@@ -41,7 +41,7 @@ export const QUERY_LANGUAGE_TO_DATABASE_REPO: QueryLanguagesToDatabaseMap = {
}; };
export class SkeletonQueryWizard { export class SkeletonQueryWizard {
private language: string | undefined; private language: QueryLanguage | undefined;
private fileName = "example.ql"; private fileName = "example.ql";
private qlPackStoragePath: string | undefined; private qlPackStoragePath: string | undefined;
@@ -154,6 +154,9 @@ export class SkeletonQueryWizard {
if (this.folderName === undefined) { if (this.folderName === undefined) {
throw new Error("Folder name is undefined"); throw new Error("Folder name is undefined");
} }
if (this.language === undefined) {
throw new Error("Language is undefined");
}
this.progress({ this.progress({
message: "Creating skeleton QL pack around query", message: "Creating skeleton QL pack around query",
@@ -164,7 +167,7 @@ export class SkeletonQueryWizard {
try { try {
const qlPackGenerator = new QlPackGenerator( const qlPackGenerator = new QlPackGenerator(
this.folderName, this.folderName,
this.language as QueryLanguage, this.language,
this.cliServer, this.cliServer,
this.qlPackStoragePath, this.qlPackStoragePath,
); );
@@ -181,6 +184,9 @@ export class SkeletonQueryWizard {
if (this.folderName === undefined) { if (this.folderName === undefined) {
throw new Error("Folder name is undefined"); throw new Error("Folder name is undefined");
} }
if (this.language === undefined) {
throw new Error("Language is undefined");
}
this.progress({ this.progress({
message: message:
@@ -192,7 +198,7 @@ export class SkeletonQueryWizard {
try { try {
const qlPackGenerator = new QlPackGenerator( const qlPackGenerator = new QlPackGenerator(
this.folderName, this.folderName,
this.language as QueryLanguage, this.language,
this.cliServer, this.cliServer,
this.qlPackStoragePath, this.qlPackStoragePath,
); );

View File

@@ -39,6 +39,7 @@ import {
QLPACK_FILENAMES, QLPACK_FILENAMES,
QLPACK_LOCK_FILENAMES, QLPACK_LOCK_FILENAMES,
} from "../pure/ql"; } from "../pure/ql";
import { QueryLanguage } from "../common/query-language";
export interface QlPack { export interface QlPack {
name: string; name: string;
@@ -76,7 +77,7 @@ async function generateQueryPack(
const targetQueryFileName = join(queryPackDir, packRelativePath); const targetQueryFileName = join(queryPackDir, packRelativePath);
const workspaceFolders = getOnDiskWorkspaceFolders(); const workspaceFolders = getOnDiskWorkspaceFolders();
let language: string | undefined; let language: QueryLanguage | undefined;
// Check if the query is already in a query pack. // Check if the query is already in a query pack.
// If so, copy the entire query pack to the temporary directory. // If so, copy the entire query pack to the temporary directory.