Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 37 additions & 15 deletions extensions/ql-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
"onView:codeQLQueryHistory",
"onView:test-explorer",
"onCommand:codeQL.checkForUpdatesToCLI",
"onCommand:codeQLDatabases.chooseDatabaseFolder",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had thought the difference between the codeQL namespace and the codeQLDatabases namespace was that the latter accepted an existing database as an argument (and so was only applicable in the databases view). Whereas the former could be invoked in any context.

I see you're disabling them as global commands, so maybe that's what we want.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to be persuaded about choice of convention, but my intent so far about the codeQL$FOO.$BAR name structure (where $FOO is nonempty) was just about scoping to portions of the interface. Consider for example codeQLDatabases.sortByName which does not take a database as an argument, and things like codeQLQueryHistory.openQuery which are relevant to user events taking place in the query history view, but don't imply any particular constraints about the types of those handlers.

"onCommand:codeQLDatabases.chooseDatabaseArchive",
"onCommand:codeQLDatabases.chooseDatabaseInternet",
"onCommand:codeQL.setCurrentDatabase",
"onCommand:codeQL.chooseDatabaseFolder",
"onCommand:codeQL.chooseDatabaseArchive",
"onCommand:codeQL.chooseDatabaseInternet",
"onCommand:codeQL.setCurrentDatabase",
"onCommand:codeQL.downloadDatabase",
"onCommand:codeQLDatabases.chooseDatabase",
"onCommand:codeQLDatabases.setCurrentDatabase",
"onCommand:codeQL.quickQuery",
Expand Down Expand Up @@ -175,24 +177,24 @@
"title": "CodeQL: Quick Query"
},
{
"command": "codeQL.chooseDatabaseFolder",
"command": "codeQLDatabases.chooseDatabaseFolder",
"title": "Choose Database from Folder",
"icon": {
"light": "media/light/folder-opened-plus.svg",
"dark": "media/dark/folder-opened-plus.svg"
}
},
{
"command": "codeQL.chooseDatabaseArchive",
"command": "codeQLDatabases.chooseDatabaseArchive",
"title": "Choose Database from Archive",
"icon": {
"light": "media/light/archive-plus.svg",
"dark": "media/dark/archive-plus.svg"
}
},
{
"command": "codeQL.chooseDatabaseInternet",
"title": "Download database",
"command": "codeQLDatabases.chooseDatabaseInternet",
"title": "Download Database",
"icon": {
"light": "media/light/cloud-download.svg",
"dark": "media/dark/cloud-download.svg"
Expand Down Expand Up @@ -231,8 +233,16 @@
"title": "Show Database Directory"
},
{
"command": "codeQL.downloadDatabase",
"title": "CodeQL: Download database"
"command": "codeQL.chooseDatabaseFolder",
"title": "CodeQL: Choose Database from Folder"
},
{
"command": "codeQL.chooseDatabaseArchive",
"title": "CodeQL: Choose Database from Archive"
},
{
"command": "codeQL.chooseDatabaseInternet",
"title": "CodeQL: Download Database"
},
{
"command": "codeQLDatabases.sortByName",
Expand Down Expand Up @@ -312,17 +322,17 @@
"group": "navigation"
},
{
"command": "codeQL.chooseDatabaseFolder",
"command": "codeQLDatabases.chooseDatabaseFolder",
"when": "view == codeQLDatabases",
"group": "navigation"
},
{
"command": "codeQL.chooseDatabaseArchive",
"command": "codeQLDatabases.chooseDatabaseArchive",
"when": "view == codeQLDatabases",
"group": "navigation"
},
{
"command": "codeQL.chooseDatabaseInternet",
"command": "codeQLDatabases.chooseDatabaseInternet",
"when": "view == codeQLDatabases",
"group": "navigation"
}
Expand Down Expand Up @@ -406,10 +416,6 @@
"command": "codeQL.runQuery",
"when": "resourceLangId == ql && resourceExtname == .ql"
},
{
"command": "codeQL.downloadDatabase",
"when": "true"
},
{
"command": "codeQL.quickEval",
"when": "editorLangId == ql"
Expand Down Expand Up @@ -442,6 +448,22 @@
"command": "codeQLDatabases.removeDatabase",
"when": "false"
},
{
"command": "codeQLDatabases.chooseDatabaseFolder",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to disable these as global commands?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely do want to disable these commands if we want to consistently maintain the pattern that the hover text over the buttons is not be prefixed, and the command palette command names is prefixed with CodeQL: . And I think both of those patterns are desirable.

However, we totally could also have command-palette-visible commands (which would be named, like, codeQL.chooseDatabaseFolder) which do the same thing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Maybe we don't want to pollute the global scope here with commands that are so tightly related to the databases view (I'm contradicting what I said earlier). So, what you have right now is fine.

"when": "false"
},
{
"command": "codeQLDatabases.chooseDatabaseArchive",
"when": "false"
},
{
"command": "codeQLDatabases.chooseDatabaseInternet",
"when": "false"
},
{
"command": "codeQLDatabases.upgradeDatabase",
"when": "false"
},
{
"command": "codeQLQueryHistory.openQuery",
"when": "false"
Expand Down
12 changes: 6 additions & 6 deletions extensions/ql-vscode/src/databases-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ export class DatabaseUI extends DisposableObject {
this.treeDataProvider = this.push(new DatabaseTreeDataProvider(ctx, databaseManager));
this.push(window.createTreeView('codeQLDatabases', { treeDataProvider: this.treeDataProvider }));

ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseFolder', this.handleChooseDatabaseFolder));
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseArchive', this.handleChooseDatabaseArchive));
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseInternet', this.handleChooseDatabaseInternet));
ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.chooseDatabaseFolder', this.handleChooseDatabaseFolder));
ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.chooseDatabaseArchive', this.handleChooseDatabaseArchive));
ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.chooseDatabaseInternet', this.handleChooseDatabaseInternet));
ctx.subscriptions.push(commands.registerCommand('codeQL.setCurrentDatabase', this.handleSetCurrentDatabase));
ctx.subscriptions.push(commands.registerCommand('codeQL.upgradeCurrentDatabase', this.handleUpgradeCurrentDatabase));
ctx.subscriptions.push(commands.registerCommand('codeQL.clearCache', this.handleClearCache));
Expand All @@ -193,7 +193,7 @@ export class DatabaseUI extends DisposableObject {
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
}

private handleChooseDatabaseFolder = async (): Promise<DatabaseItem | undefined> => {
handleChooseDatabaseFolder = async (): Promise<DatabaseItem | undefined> => {
try {
return await this.chooseAndSetDatabase(true);
} catch (e) {
Expand All @@ -202,7 +202,7 @@ export class DatabaseUI extends DisposableObject {
}
}

private handleChooseDatabaseArchive = async (): Promise<DatabaseItem | undefined> => {
handleChooseDatabaseArchive = async (): Promise<DatabaseItem | undefined> => {
try {
return await this.chooseAndSetDatabase(false);
} catch (e) {
Expand All @@ -211,7 +211,7 @@ export class DatabaseUI extends DisposableObject {
}
}

private handleChooseDatabaseInternet = async (): Promise<DatabaseItem | undefined> => {
handleChooseDatabaseInternet = async (): Promise<DatabaseItem | undefined> => {
return await promptImportInternetDatabase(this.databaseManager, this.storagePath);
}

Expand Down
5 changes: 3 additions & 2 deletions extensions/ql-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { displayQuickQuery } from './quick-query';
import { compileAndRunQueryAgainstDatabase, tmpDirDisposal, UserCancellationException } from './run-queries';
import { QLTestAdapterFactory } from './test-adapter';
import { TestUIService } from './test-ui';
import { promptImportInternetDatabase } from './databaseFetcher';

/**
* extension.ts
Expand Down Expand Up @@ -335,7 +334,9 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
await qs.restartQueryServer();
helpers.showAndLogInformationMessage('CodeQL Query Server restarted.', { outputLogger: queryServerLogger });
}));
ctx.subscriptions.push(commands.registerCommand('codeQL.downloadDatabase', () => promptImportInternetDatabase(dbm, getContextStoragePath(ctx))));
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseFolder', () => databaseUI.handleChooseDatabaseFolder()));
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseArchive', () => databaseUI.handleChooseDatabaseArchive()));
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseInternet', () => databaseUI.handleChooseDatabaseInternet()));

ctx.subscriptions.push(client.start());

Expand Down
101 changes: 101 additions & 0 deletions extensions/ql-vscode/test/pure-tests/command-lint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { expect } from 'chai';
import * as path from 'path';
import * as fs from 'fs-extra';

type CmdDecl = {
command: string;
when?: string;
title?: string;
}

describe('commands declared in package.json', function() {
const manifest = fs.readJsonSync(path.join(__dirname, '../../package.json'));
const commands = manifest.contributes.commands;
const menus = manifest.contributes.menus;

const disabledInPalette: Set<string> = new Set<string>();

// These commands should appear in the command palette, and so
// should be prefixed with 'CodeQL: '.
const paletteCmds: Set<string> = new Set<string>();

// These commands arising on context menus in non-CodeQL controlled
// panels, (e.g. file browser) and so should be prefixed with 'CodeQL: '.
const contribContextMenuCmds: Set<string> = new Set<string>();

// These are commands used in CodeQL controlled panels, and so don't need any prefixing in their title.
const scopedCmds: Set<string> = new Set<string>();
const commandTitles: { [cmd: string]: string } = {};

commands.forEach((commandDecl: CmdDecl) => {
const { command, title } = commandDecl;
if (command.match(/^codeQL\./)
|| command.match(/^codeQLQueryResults\./)
|| command.match(/^codeQLTests\./)) {
paletteCmds.add(command);
expect(title).not.to.be.undefined;
commandTitles[command] = title!;
}
else if (command.match(/^codeQLDatabases\./)
|| command.match(/^codeQLQueryHistory\./)) {
scopedCmds.add(command);
expect(title).not.to.be.undefined;
commandTitles[command] = title!;
}
else {
expect.fail(`Unexpected command name ${command}`);
}
});

menus['explorer/context'].forEach((commandDecl: CmdDecl) => {
const { command } = commandDecl;
paletteCmds.delete(command);
contribContextMenuCmds.add(command);
});

menus['editor/context'].forEach((commandDecl: CmdDecl) => {
const { command } = commandDecl;
paletteCmds.delete(command);
contribContextMenuCmds.add(command);
});

menus.commandPalette.forEach((commandDecl: CmdDecl) => {
if (commandDecl.when === 'false')
disabledInPalette.add(commandDecl.command);
});



it('should have commands appropriately prefixed', function() {
paletteCmds.forEach(command => {
expect(commandTitles[command], `command ${command} should be prefixed with 'CodeQL: ', since it is accessible from the command palette`).to.match(/^CodeQL: /);
});

contribContextMenuCmds.forEach(command => {
expect(commandTitles[command], `command ${command} should be prefixed with 'CodeQL: ', since it is accessible from a context menu in a non-extension-controlled context`).to.match(/^CodeQL: /);
});

scopedCmds.forEach(command => {
expect(commandTitles[command], `command ${command} should not be prefixed with 'CodeQL: ', since it is accessible from an extension-controlled context`).not.to.match(/^CodeQL: /);
});
});

it('should have the right commands accessible from the command palette', function() {
paletteCmds.forEach(command => {
expect(disabledInPalette.has(command), `command ${command} should be enabled in the command palette`).to.be.false;
});

// Commands in contribContextMenuCmds may reasonbly be enabled or
// disabled in the command palette; for example, codeQL.runQuery
// is available there, since we heuristically figure out which
// query to run, but codeQL.setCurrentDatabase is not.

scopedCmds.forEach(command => {
expect(disabledInPalette.has(command), `command ${command} should be disabled in the command palette`).to.be.true;
});
});


});