Skip to content

Commit 2f9a314

Browse files
author
Dave Bartolomeo
committed
Merge from master
2 parents 9e6100f + 513d763 commit 2f9a314

14 files changed

Lines changed: 409 additions & 150 deletions

.github/workflows/release.yml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,25 @@ jobs:
8787
# Get the `vsix_path` and `ref_name` from the `prepare-artifacts` step above.
8888
asset_path: ${{ steps.prepare-artifacts.outputs.vsix_path }}
8989
asset_name: ${{ format('vscode-codeql-{0}.vsix', steps.prepare-artifacts.outputs.ref_name) }}
90-
asset_content_type: application/zip
90+
asset_content_type: application/zip
91+
92+
- name: Bump patch version
93+
id: bump-patch-version
94+
if: success()
95+
run: |
96+
cd extensions/ql-vscode
97+
# Bump to the next patch version. Major or minor version bumps will have to be done manually.
98+
# Record the next version number as an output of this step.
99+
NEXT_VERSION="$(npm version patch)"
100+
echo "::set-output name=next_version::$NEXT_VERSION"
101+
102+
- name: Create version bump PR
103+
uses: peter-evans/create-pull-request@7531167f24e3914996c8d5110b5e08478ddadff9 # v1.8.0
104+
if: success()
105+
with:
106+
token: ${{ secrets.GITHUB_TOKEN }}
107+
commit-message: Bump version to ${{ steps.bump-patch-version.outputs.next_version }}
108+
title: Bump version to ${{ steps.bump-patch-version.outputs.next_version }}
109+
body: This PR was automatically generated by the GitHub Actions release workflow in this repository.
110+
branch: ${{ format('version/bump-to-{0}', steps.bump-patch-version.outputs.next_version) }}
111+
branch-suffix: none

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ out/
77
server/
88
node_modules/
99
gen/
10+
artifacts/
1011

1112
# Integration test artifacts
1213
**/.vscode-test/**

extensions/ql-vscode/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@
197197
"command": "codeQLQueryHistory.itemClicked",
198198
"title": "Query History Item"
199199
},
200+
{
201+
"command": "codeQLQueryResults.nextPathStep",
202+
"title": "CodeQL: Show Next Step on Path"
203+
},
204+
{
205+
"command": "codeQLQueryResults.previousPathStep",
206+
"title": "CodeQL: Show Previous Step on Path"
207+
},
200208
{
201209
"command": "codeQLTests.showOutputDifferences",
202210
"title": "CodeQL: Show Test Output Differences"

extensions/ql-vscode/src/distribution.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,13 @@ export class ReleasesApiConsumer {
400400
Object.assign({}, this._defaultHeaders, additionalHeaders));
401401

402402
if (!response.ok) {
403+
// Check for rate limiting
404+
const rateLimitResetValue = response.headers.get("X-RateLimit-Reset");
405+
if (response.status === 403 && rateLimitResetValue) {
406+
const secondsToMillisecondsFactor = 1000;
407+
const rateLimitResetDate = new Date(parseInt(rateLimitResetValue, 10) * secondsToMillisecondsFactor);
408+
throw new GithubRateLimitedError(response.status, await response.text(), rateLimitResetDate);
409+
}
403410
throw new GithubApiError(response.status, await response.text());
404411
}
405412
return response;
@@ -673,3 +680,9 @@ export class GithubApiError extends Error {
673680
super(`API call failed with status code ${status}, body: ${body}`);
674681
}
675682
}
683+
684+
export class GithubRateLimitedError extends GithubApiError {
685+
constructor(public status: number, public body: string, public rateLimitResetDate: Date) {
686+
super(status, body);
687+
}
688+
}

extensions/ql-vscode/src/extension.ts

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import * as archiveFilesystemProvider from './archive-filesystem-provider';
44
import { DistributionConfigListener, QueryServerConfigListener } from './config';
55
import { DatabaseManager } from './databases';
66
import { DatabaseUI } from './databases-ui';
7-
import { DistributionUpdateCheckResultKind, DistributionManager, FindDistributionResult, FindDistributionResultKind, GithubApiError, DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT } from './distribution';
7+
import { DistributionUpdateCheckResultKind, DistributionManager, FindDistributionResult, FindDistributionResultKind, GithubApiError,
8+
DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT, GithubRateLimitedError } from './distribution';
89
import * as helpers from './helpers';
910
import { spawnIdeServer } from './ide-server';
1011
import { InterfaceManager, WebviewReveal } from './interface';
@@ -82,19 +83,24 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
8283
const shouldUpdateOnNextActivationKey = "shouldUpdateOnNextActivation";
8384

8485
registerErrorStubs(ctx, [checkForUpdatesCommand], command => () => {
85-
Window.showErrorMessage(`Can't execute ${command}: waiting to finish loading CodeQL CLI.`);
86+
helpers.showAndLogErrorMessage(`Can't execute ${command}: waiting to finish loading CodeQL CLI.`);
8687
});
8788

88-
async function installOrUpdateDistributionWithProgressTitle(progressTitle: string, isSilentIfCannotUpdate: boolean): Promise<void> {
89+
interface ReportingConfig {
90+
shouldDisplayMessageWhenNoUpdates: boolean;
91+
shouldErrorIfUpdateFails: boolean;
92+
}
93+
94+
async function installOrUpdateDistributionWithProgressTitle(progressTitle: string, reportingConfig: ReportingConfig): Promise<void> {
8995
const result = await distributionManager.checkForUpdatesToExtensionManagedDistribution();
9096
switch (result.kind) {
9197
case DistributionUpdateCheckResultKind.AlreadyUpToDate:
92-
if (!isSilentIfCannotUpdate) {
98+
if (reportingConfig.shouldDisplayMessageWhenNoUpdates) {
9399
helpers.showAndLogInformationMessage("CodeQL CLI already up to date.");
94100
}
95101
break;
96102
case DistributionUpdateCheckResultKind.InvalidDistributionLocation:
97-
if (!isSilentIfCannotUpdate) {
103+
if (reportingConfig.shouldDisplayMessageWhenNoUpdates) {
98104
helpers.showAndLogErrorMessage("CodeQL CLI is installed externally so could not be updated.");
99105
}
100106
break;
@@ -124,34 +130,32 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
124130
}
125131
}
126132

127-
async function installOrUpdateDistribution(isSilentIfCannotUpdate: boolean): Promise<void> {
133+
async function installOrUpdateDistribution(reportingConfig: ReportingConfig): Promise<void> {
128134
if (isInstallingOrUpdatingDistribution) {
129135
throw new Error("Already installing or updating CodeQL CLI");
130136
}
131137
isInstallingOrUpdatingDistribution = true;
138+
const codeQlInstalled = await distributionManager.getCodeQlPathWithoutVersionCheck() !== undefined;
139+
const willUpdateCodeQl = ctx.globalState.get(shouldUpdateOnNextActivationKey);
140+
const messageText = willUpdateCodeQl ? "Updating CodeQL CLI" :
141+
codeQlInstalled ? "Checking for updates to CodeQL CLI" : "Installing CodeQL CLI";
132142
try {
133-
const codeQlInstalled = await distributionManager.getCodeQlPathWithoutVersionCheck() !== undefined;
134-
const messageText = ctx.globalState.get(shouldUpdateOnNextActivationKey) ? "Updating CodeQL CLI" :
135-
codeQlInstalled ? "Checking for updates to CodeQL CLI" : "Installing CodeQL CLI";
136-
await installOrUpdateDistributionWithProgressTitle(messageText, isSilentIfCannotUpdate);
143+
await installOrUpdateDistributionWithProgressTitle(messageText, reportingConfig);
137144
} catch (e) {
138145
// Don't rethrow the exception, because if the config is changed, we want to be able to retry installing
139146
// or updating the distribution.
140-
if (e instanceof GithubApiError && (e.status == 404 || e.status == 403 || e.status === 401)) {
141-
const errorMessageResponse = Window.showErrorMessage("Unable to download CodeQL CLI. See " +
142-
"https://github.com/github/vscode-codeql/blob/master/extensions/ql-vscode/README.md for more details about how " +
143-
"to obtain CodeQL CLI.", "Edit Settings");
144-
// We're deliberately not `await`ing this promise, just
145-
// asynchronously letting the user follow the convenience link
146-
// if they want to.
147-
errorMessageResponse.then(response => {
148-
if (response !== undefined) {
149-
commands.executeCommand('workbench.action.openSettingsJson');
150-
}
151-
});
152-
} else {
153-
helpers.showAndLogErrorMessage("Unable to download CodeQL CLI. " + e);
147+
const alertFunction = (codeQlInstalled && !reportingConfig.shouldErrorIfUpdateFails) ?
148+
helpers.showAndLogWarningMessage : helpers.showAndLogErrorMessage;
149+
const taskDescription = (willUpdateCodeQl ? "update" :
150+
codeQlInstalled ? "check for updates to" : "install") + " CodeQL CLI";
151+
152+
if (e instanceof GithubRateLimitedError) {
153+
alertFunction(`Rate limited while trying to ${taskDescription}. Please try again after ` +
154+
`your rate limit window resets at ${e.rateLimitResetDate.toLocaleString()}.`);
155+
} else if (e instanceof GithubApiError) {
156+
alertFunction(`Encountered GitHub API error while trying to ${taskDescription}. ` + e);
154157
}
158+
alertFunction(`Unable to ${taskDescription}. ` + e);
155159
} finally {
156160
isInstallingOrUpdatingDistribution = false;
157161
}
@@ -179,10 +183,8 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
179183
return result;
180184
}
181185

182-
async function installOrUpdateThenTryActivate(isSilentIfCannotUpdate: boolean): Promise<void> {
183-
if (!isInstallingOrUpdatingDistribution) {
184-
await installOrUpdateDistribution(isSilentIfCannotUpdate);
185-
}
186+
async function installOrUpdateThenTryActivate(reportingConfig: ReportingConfig): Promise<void> {
187+
await installOrUpdateDistribution(reportingConfig);
186188

187189
// Display the warnings even if the extension has already activated.
188190
const distributionResult = await getDistributionDisplayingDistributionWarnings();
@@ -192,18 +194,30 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
192194
} else if (distributionResult.kind === FindDistributionResultKind.NoDistribution) {
193195
registerErrorStubs(ctx, [checkForUpdatesCommand], command => async () => {
194196
const installActionName = "Install CodeQL CLI";
195-
const chosenAction = await Window.showErrorMessage(`Can't execute ${command}: missing CodeQL CLI.`, installActionName);
197+
const chosenAction = await helpers.showAndLogErrorMessage(`Can't execute ${command}: missing CodeQL CLI.`, installActionName);
196198
if (chosenAction === installActionName) {
197-
installOrUpdateThenTryActivate(true);
199+
installOrUpdateThenTryActivate({
200+
shouldDisplayMessageWhenNoUpdates: false,
201+
shouldErrorIfUpdateFails: true
202+
});
198203
}
199204
});
200205
}
201206
}
202207

203-
ctx.subscriptions.push(distributionConfigListener.onDidChangeDistributionConfiguration(() => installOrUpdateThenTryActivate(true)));
204-
ctx.subscriptions.push(commands.registerCommand(checkForUpdatesCommand, () => installOrUpdateThenTryActivate(false)));
205-
206-
await installOrUpdateThenTryActivate(true);
208+
ctx.subscriptions.push(distributionConfigListener.onDidChangeDistributionConfiguration(() => installOrUpdateThenTryActivate({
209+
shouldDisplayMessageWhenNoUpdates: false,
210+
shouldErrorIfUpdateFails: true
211+
})));
212+
ctx.subscriptions.push(commands.registerCommand(checkForUpdatesCommand, () => installOrUpdateThenTryActivate({
213+
shouldDisplayMessageWhenNoUpdates: true,
214+
shouldErrorIfUpdateFails: true
215+
})));
216+
217+
await installOrUpdateThenTryActivate({
218+
shouldDisplayMessageWhenNoUpdates: false,
219+
shouldErrorIfUpdateFails: !!ctx.globalState.get(shouldUpdateOnNextActivationKey)
220+
});
207221
}
208222

209223
async function activateWithInstalledDistribution(ctx: ExtensionContext, distributionManager: DistributionManager) {

extensions/ql-vscode/src/interface-types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,15 @@ export interface SetStateMsg {
6565
shouldKeepOldResultsWhileRendering: boolean;
6666
};
6767

68-
export type IntoResultsViewMsg = ResultsUpdatingMsg | SetStateMsg;
68+
/** Advance to the next or previous path no in the path viewer */
69+
export interface NavigatePathMsg {
70+
t: 'navigatePath',
71+
72+
/** 1 for next, -1 for previous */
73+
direction: number;
74+
}
75+
76+
export type IntoResultsViewMsg = ResultsUpdatingMsg | SetStateMsg | NavigatePathMsg;
6977

7078
export type FromResultsViewMsg = ViewSourceFileMsg | ToggleDiagnostics | ChangeSortMsg | ResultViewLoaded;
7179

extensions/ql-vscode/src/interface.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ export class InterfaceManager extends DisposableObject {
9999
super();
100100
this.push(this._diagnosticCollection);
101101
this.push(vscode.window.onDidChangeTextEditorSelection(this.handleSelectionChange.bind(this)));
102+
this.push(vscode.commands.registerCommand('codeQLQueryResults.nextPathStep', this.navigatePathStep.bind(this, 1)));
103+
this.push(vscode.commands.registerCommand('codeQLQueryResults.previousPathStep', this.navigatePathStep.bind(this, -1)));
104+
}
105+
106+
navigatePathStep(direction: number) {
107+
this.postMessage({ t: "navigatePath", direction });
102108
}
103109

104110
// Returns the webview panel, creating it if it doesn't already
@@ -236,15 +242,8 @@ export class InterfaceManager extends DisposableObject {
236242
// user's workflow by immediately revealing the panel.
237243
const showButton = 'View Results';
238244
const queryName = helpers.getQueryName(info);
239-
let queryNameForMessage: string;
240-
if (queryName.length > 0) {
241-
// lower case the first character
242-
queryNameForMessage = queryName.charAt(0).toLowerCase() + queryName.substring(1);
243-
} else {
244-
queryNameForMessage = 'query';
245-
}
246245
const resultPromise = vscode.window.showInformationMessage(
247-
`Finished running ${queryNameForMessage}.`,
246+
`Finished running query ${(queryName.length > 0) ? ` “${queryName}”` : ''}.`,
248247
showButton
249248
);
250249
// Address this click asynchronously so we still update the

extensions/ql-vscode/src/queries.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export const tmpDirDisposal = {
3030
}
3131
};
3232

33-
let queryCounter = 0;
3433

3534
export class UserCancellationException extends Error { }
3635

@@ -43,30 +42,32 @@ export class UserCancellationException extends Error { }
4342
export class QueryInfo {
4443
compiledQueryPath: string;
4544
resultsInfo: ResultsInfo;
45+
private static nextQueryId = 0;
46+
4647
/**
4748
* Map from result set name to SortedResultSetInfo.
4849
*/
4950
sortedResultsInfo: Map<string, SortedResultSetInfo>;
5051
dataset: vscode.Uri; // guarantee the existence of a well-defined dataset dir at this point
51-
52+
queryId: number;
5253
constructor(
5354
public program: messages.QlProgram,
5455
public dbItem: DatabaseItem,
5556
public queryDbscheme: string, // the dbscheme file the query expects, based on library path resolution
5657
public quickEvalPosition?: messages.Position,
5758
public metadata?: cli.QueryMetadata,
5859
) {
59-
this.compiledQueryPath = path.join(tmpDir.name, `compiledQuery${queryCounter}.qlo`);
60+
this.queryId = QueryInfo.nextQueryId++;
61+
this.compiledQueryPath = path.join(tmpDir.name, `compiledQuery${this.queryId}.qlo`);
6062
this.resultsInfo = {
61-
resultsPath: path.join(tmpDir.name, `results${queryCounter}.bqrs`),
62-
interpretedResultsPath: path.join(tmpDir.name, `interpretedResults${queryCounter}.sarif`)
63+
resultsPath: path.join(tmpDir.name, `results${this.queryId}.bqrs`),
64+
interpretedResultsPath: path.join(tmpDir.name, `interpretedResults${this.queryId}.sarif`)
6365
};
6466
this.sortedResultsInfo = new Map();
6567
if (dbItem.contents === undefined) {
6668
throw new Error('Can\'t run query on invalid database.');
6769
}
6870
this.dataset = dbItem.contents.datasetUri;
69-
queryCounter++;
7071
}
7172

7273
async run(
@@ -160,7 +161,7 @@ export class QueryInfo {
160161
}
161162

162163
const sortedResultSetInfo: SortedResultSetInfo = {
163-
resultsPath: path.join(tmpDir.name, `sortedResults${queryCounter}-${resultSetName}.bqrs`),
164+
resultsPath: path.join(tmpDir.name, `sortedResults${this.queryId}-${resultSetName}.bqrs`),
164165
sortState
165166
};
166167

0 commit comments

Comments
 (0)