Skip to content

Commit ddd00d1

Browse files
committed
Introduce SkeletonWizard class
This will be triggered by a "Create Query" command. It will: - prompt the user for a language - create a skeleton pack based on the language chosen - download a database for the QL pack - open the new query file If the skeleton pack already exists, we just create a new query file in the existing folder. If the database is already downloaded, we just re-use it.
1 parent e63f0fc commit ddd00d1

2 files changed

Lines changed: 473 additions & 0 deletions

File tree

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import { join } from "path";
2+
import { CancellationToken, Uri, workspace } from "vscode";
3+
import { CodeQLCliServer } from "./cli";
4+
import { OutputChannelLogger } from "./common";
5+
import { Credentials } from "./common/authentication";
6+
import { QueryLanguage } from "./common/query-language";
7+
import { askForLanguage, isFolderAlreadyInWorkspace } from "./helpers";
8+
import { getErrorMessage } from "./pure/helpers-pure";
9+
import { QlPackGenerator } from "./qlpack-generator";
10+
import { DatabaseItem, DatabaseManager } from "./local-databases";
11+
import * as databaseFetcher from "./databaseFetcher";
12+
import { ProgressCallback } from "./progress";
13+
14+
type QueryLanguagesToDatabaseMap = {
15+
[id: string]: string;
16+
};
17+
18+
export class SkeletonQueryWizard {
19+
private language: string | undefined;
20+
private folderName: string | undefined;
21+
private fileName = "example.ql";
22+
23+
QUERY_LANGUAGE_TO_DATABASE_REPO: QueryLanguagesToDatabaseMap = {
24+
csharp: "github/codeql",
25+
python: "github/codeql",
26+
ruby: "github/codeql",
27+
javascript: "github/codeql",
28+
go: "github/codeql",
29+
ql: "github/codeql",
30+
};
31+
32+
constructor(
33+
private readonly cliServer: CodeQLCliServer,
34+
private readonly storagePath: string | undefined,
35+
private readonly progress: ProgressCallback,
36+
private readonly credentials: Credentials | undefined,
37+
private readonly extLogger: OutputChannelLogger,
38+
private readonly databaseManager: DatabaseManager,
39+
private readonly token: CancellationToken,
40+
) {}
41+
42+
public async execute() {
43+
// show quick pick to choose language
44+
await this.chooseLanguage();
45+
if (!this.language) {
46+
return;
47+
}
48+
49+
this.folderName = `codeql-custom-queries-${this.language}`;
50+
const skeletonPackAlreadyExists = isFolderAlreadyInWorkspace(
51+
this.folderName,
52+
);
53+
54+
if (skeletonPackAlreadyExists) {
55+
// just create a new example query file in skeleton QL pack
56+
await this.createExampleFile();
57+
// select existing database for language
58+
await this.selectExistingDatabase();
59+
} else {
60+
// generate a new skeleton QL pack with query file
61+
await this.createQlPack();
62+
// download database based on language and select it
63+
await this.downloadDatabase();
64+
}
65+
66+
// open a query file
67+
await this.openExampleFile();
68+
}
69+
70+
private async openExampleFile() {
71+
if (this.folderName === undefined || this.storagePath === undefined) {
72+
throw new Error("Path to folder is undefined");
73+
}
74+
75+
const queryFileUri = Uri.file(
76+
join(this.storagePath, this.folderName, this.fileName),
77+
);
78+
79+
void workspace.openTextDocument(queryFileUri).then((doc) => {
80+
void Window.showTextDocument(doc);
81+
});
82+
}
83+
84+
private async chooseLanguage() {
85+
this.progress({
86+
message: "Choose language",
87+
step: 1,
88+
maxStep: 1,
89+
});
90+
91+
this.language = await askForLanguage(this.cliServer, false);
92+
}
93+
94+
private async createQlPack() {
95+
if (this.folderName === undefined) {
96+
throw new Error("Folder name is undefined");
97+
}
98+
99+
this.progress({
100+
message: "Creating skeleton QL pack around query",
101+
step: 2,
102+
maxStep: 2,
103+
});
104+
105+
try {
106+
const qlPackGenerator = new QlPackGenerator(
107+
this.folderName,
108+
this.language as QueryLanguage,
109+
this.cliServer,
110+
this.storagePath,
111+
);
112+
113+
await qlPackGenerator.generate();
114+
} catch (e: unknown) {
115+
void this.extLogger.log(
116+
`Could not create skeleton QL pack: ${getErrorMessage(e)}`,
117+
);
118+
}
119+
}
120+
121+
private async createExampleFile() {
122+
if (this.folderName === undefined) {
123+
throw new Error("Folder name is undefined");
124+
}
125+
126+
this.progress({
127+
message:
128+
"Skeleton query pack already exists. Creating additional query example file.",
129+
step: 2,
130+
maxStep: 2,
131+
});
132+
133+
try {
134+
const qlPackGenerator = new QlPackGenerator(
135+
this.folderName,
136+
this.language as QueryLanguage,
137+
this.cliServer,
138+
this.storagePath,
139+
);
140+
141+
this.fileName = await this.workoutNextFileName(this.folderName);
142+
await qlPackGenerator.createExampleQlFile(this.fileName);
143+
} catch (e: unknown) {
144+
void this.extLogger.log(
145+
`Could not create skeleton QL pack: ${getErrorMessage(e)}`,
146+
);
147+
}
148+
}
149+
150+
private async workoutNextFileName(folderName: string): Promise<string> {
151+
if (this.storagePath === undefined) {
152+
throw new Error("Workspace storage path is undefined");
153+
}
154+
155+
const folderUri = Uri.file(join(this.storagePath, folderName));
156+
const files = await workspace.fs.readDirectory(folderUri);
157+
const qlFiles = files.filter((file) =>
158+
file[0].endsWith(".ql") ? true : false,
159+
);
160+
161+
return `example${qlFiles.length + 1}.ql`;
162+
}
163+
164+
private async downloadDatabase() {
165+
if (this.storagePath === undefined) {
166+
throw new Error("Workspace storage path is undefined");
167+
}
168+
169+
if (this.language === undefined) {
170+
throw new Error("Workspace storage path is undefined");
171+
}
172+
173+
this.progress({
174+
message: "Downloading database",
175+
step: 3,
176+
maxStep: 3,
177+
});
178+
179+
const githubRepoNwo = this.QUERY_LANGUAGE_TO_DATABASE_REPO[this.language];
180+
181+
await databaseFetcher.downloadGitHubDatabase(
182+
githubRepoNwo,
183+
this.databaseManager,
184+
this.storagePath,
185+
this.credentials,
186+
this.progress,
187+
this.token,
188+
this.cliServer,
189+
);
190+
}
191+
192+
private async selectExistingDatabase() {
193+
if (this.language === undefined) {
194+
throw new Error("Language is undefined");
195+
}
196+
197+
if (this.storagePath === undefined) {
198+
throw new Error("Workspace storage path is undefined");
199+
}
200+
201+
const databaseNwo = this.QUERY_LANGUAGE_TO_DATABASE_REPO[this.language];
202+
203+
const databaseItem = await this.databaseManager.digForDatabaseItem(
204+
this.language,
205+
databaseNwo,
206+
);
207+
208+
if (databaseItem) {
209+
// select the found database
210+
await this.databaseManager.setCurrentDatabaseItem(
211+
databaseItem as DatabaseItem,
212+
);
213+
} else {
214+
// download new database and select it
215+
await this.downloadDatabase();
216+
}
217+
}
218+
}

0 commit comments

Comments
 (0)