Add command to run multiple queries at once from file explorer
New command called `codeQL.runQueries`. When invoked, gather all selected files and folders, and recursively search for ql files to run. Warn the user if a directory is selected. See comment inline for reason.
This commit is contained in:
Родитель
52c6ee4477
Коммит
3a23f05a0a
|
@ -170,6 +170,10 @@
|
|||
"command": "codeQL.runQuery",
|
||||
"title": "CodeQL: Run Query"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueries",
|
||||
"title": "CodeQL: Run Queries in Selected Files"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.quickEval",
|
||||
"title": "CodeQL: Quick Evaluation"
|
||||
|
@ -443,9 +447,8 @@
|
|||
"when": "resourceScheme == codeql-zip-archive || explorerResourceIsFolder || resourceExtname == .zip"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQuery",
|
||||
"group": "9_qlCommands",
|
||||
"when": "resourceLangId == ql && resourceExtname == .ql"
|
||||
"command": "codeQL.runQueries",
|
||||
"group": "9_qlCommands"
|
||||
}
|
||||
],
|
||||
"commandPalette": [
|
||||
|
@ -453,6 +456,10 @@
|
|||
"command": "codeQL.runQuery",
|
||||
"when": "resourceLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueries",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.quickEval",
|
||||
"when": "editorLangId == ql"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { commands, Disposable, ExtensionContext, extensions, languages, ProgressLocation, ProgressOptions, Uri, window as Window, env } from 'vscode';
|
||||
import { LanguageClient } from 'vscode-languageclient';
|
||||
import * as path from 'path';
|
||||
import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api';
|
||||
import * as archiveFilesystemProvider from './archive-filesystem-provider';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
|
@ -32,6 +33,7 @@ import { compileAndRunQueryAgainstDatabase, tmpDirDisposal, UserCancellationExce
|
|||
import { QLTestAdapterFactory } from './test-adapter';
|
||||
import { TestUIService } from './test-ui';
|
||||
import { CompareInterfaceManager } from './compare/compare-interface';
|
||||
import { gatherQlFiles } from './files';
|
||||
|
||||
/**
|
||||
* extension.ts
|
||||
|
@ -438,6 +440,27 @@ async function activateWithInstalledDistribution(
|
|||
async (uri: Uri | undefined) => await compileAndRunQuery(false, uri)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
'codeQL.runQueries',
|
||||
async (_: Uri | undefined, multi: Uri[]) => {
|
||||
const [files, dirFound] = await gatherQlFiles(multi.map(uri => uri.fsPath));
|
||||
// warn user and display selected files when a directory is selected because some ql
|
||||
// files may be hidden from the user.
|
||||
if (dirFound) {
|
||||
const fileString = files.map(file => path.basename(file)).join(', ');
|
||||
const res = await helpers.showBinaryChoiceDialog(
|
||||
`You are about to run ${files.length} queries: ${fileString} Do you want to continue?`
|
||||
);
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const queryUris = files.map(path => Uri.parse(`file:${path}`, true));
|
||||
await Promise.all(queryUris.map(uri => compileAndRunQuery(false, uri)));
|
||||
}
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
'codeQL.quickEval',
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
|
||||
/**
|
||||
* Recursively finds all .ql files in this set of Uris.
|
||||
*
|
||||
* @param paths The list of Uris to search through
|
||||
*
|
||||
* @returns list of ql files and a boolean describing whether or not a directory was found/
|
||||
*/
|
||||
export async function gatherQlFiles(paths: string[]): Promise<[string[], boolean]> {
|
||||
const gatheredUris: Set<string> = new Set();
|
||||
let dirFound = false;
|
||||
for (const nextPath of paths) {
|
||||
if (
|
||||
(await fs.pathExists(nextPath)) &&
|
||||
(await fs.stat(nextPath)).isDirectory()
|
||||
) {
|
||||
dirFound = true;
|
||||
const subPaths = await fs.readdir(nextPath);
|
||||
const fullPaths = subPaths.map(p => path.join(nextPath, p));
|
||||
const nestedFiles = (await gatherQlFiles(fullPaths))[0];
|
||||
nestedFiles.forEach(nested => gatheredUris.add(nested));
|
||||
} else if (nextPath.endsWith('.ql')) {
|
||||
gatheredUris.add(nextPath);
|
||||
}
|
||||
}
|
||||
return [Array.from(gatheredUris), dirFound];
|
||||
}
|
|
@ -419,7 +419,6 @@ export async function compileAndRunQueryAgainstDatabase(
|
|||
selectedQueryUri: vscode.Uri | undefined,
|
||||
templates?: messages.TemplateDefinitions,
|
||||
): Promise<QueryWithResults> {
|
||||
|
||||
if (!db.contents || !db.contents.dbSchemeUri) {
|
||||
throw new Error(`Database ${db.databaseUri} does not have a CodeQL database scheme.`);
|
||||
}
|
||||
|
@ -427,12 +426,12 @@ export async function compileAndRunQueryAgainstDatabase(
|
|||
// Determine which query to run, based on the selection and the active editor.
|
||||
const { queryPath, quickEvalPosition, quickEvalText } = await determineSelectedQuery(selectedQueryUri, quickEval);
|
||||
|
||||
// If this is quick query, store the query text
|
||||
const historyItemOptions: QueryHistoryItemOptions = {};
|
||||
historyItemOptions.queryText = await fs.readFile(queryPath, 'utf8');
|
||||
historyItemOptions.isQuickQuery === isQuickQueryPath(queryPath);
|
||||
if (quickEval) {
|
||||
historyItemOptions.queryText = quickEvalText;
|
||||
} else {
|
||||
historyItemOptions.queryText = await fs.readFile(queryPath, 'utf8');
|
||||
}
|
||||
|
||||
// Get the workspace folder paths.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
select 1
|
|
@ -0,0 +1 @@
|
|||
select 1
|
|
@ -0,0 +1,76 @@
|
|||
import * as chai from 'chai';
|
||||
import 'chai/register-should';
|
||||
import * as sinonChai from 'sinon-chai';
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
|
||||
import { gatherQlFiles } from '../../src/files';
|
||||
|
||||
chai.use(sinonChai);
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('files', () => {
|
||||
const dataDir = path.join(path.dirname(__dirname), 'data');
|
||||
const data2Dir = path.join(path.dirname(__dirname), 'data2');
|
||||
|
||||
it('should pass', () => {
|
||||
expect(true).to.be.eq(true);
|
||||
});
|
||||
it('should find one file', async () => {
|
||||
const singleFile = path.join(dataDir, 'query.ql');
|
||||
const result = await gatherQlFiles([singleFile]);
|
||||
expect(result).to.deep.equal([[singleFile], false]);
|
||||
});
|
||||
|
||||
it('should find no files', async () => {
|
||||
const result = await gatherQlFiles([]);
|
||||
expect(result).to.deep.equal([[], false]);
|
||||
});
|
||||
|
||||
it('should find no files', async () => {
|
||||
const singleFile = path.join(dataDir, 'library.qll');
|
||||
const result = await gatherQlFiles([singleFile]);
|
||||
expect(result).to.deep.equal([[], false]);
|
||||
});
|
||||
|
||||
it('should handle invalid file', async () => {
|
||||
const singleFile = path.join(dataDir, 'xxx');
|
||||
const result = await gatherQlFiles([singleFile]);
|
||||
expect(result).to.deep.equal([[], false]);
|
||||
});
|
||||
|
||||
it('should find two files', async () => {
|
||||
const singleFile = path.join(dataDir, 'query.ql');
|
||||
const otherFile = path.join(dataDir, 'multiple-result-sets.ql');
|
||||
const notFile = path.join(dataDir, 'library.qll');
|
||||
const invalidFile = path.join(dataDir, 'xxx');
|
||||
|
||||
const result = await gatherQlFiles([singleFile, otherFile, notFile, invalidFile]);
|
||||
expect(result.sort()).to.deep.equal([[singleFile, otherFile], false]);
|
||||
});
|
||||
|
||||
it('should scan a directory', async () => {
|
||||
const singleFile = path.join(dataDir, 'query.ql');
|
||||
const otherFile = path.join(dataDir, 'multiple-result-sets.ql');
|
||||
|
||||
const result = await gatherQlFiles([dataDir]);
|
||||
expect(result.sort()).to.deep.equal([[otherFile, singleFile], true]);
|
||||
});
|
||||
|
||||
it('should scan a directory and some files', async () => {
|
||||
const singleFile = path.join(dataDir, 'query.ql');
|
||||
const empty1File = path.join(data2Dir, 'empty1.ql');
|
||||
const empty2File = path.join(data2Dir, 'sub-folder', 'empty2.ql');
|
||||
|
||||
const result = await gatherQlFiles([singleFile, data2Dir]);
|
||||
expect(result.sort()).to.deep.equal([[singleFile, empty1File, empty2File], true]);
|
||||
});
|
||||
|
||||
it('should avoid duplicates', async () => {
|
||||
const singleFile = path.join(dataDir, 'query.ql');
|
||||
const otherFile = path.join(dataDir, 'multiple-result-sets.ql');
|
||||
|
||||
const result = await gatherQlFiles([singleFile, dataDir, otherFile]);
|
||||
expect(result.sort()).to.deep.equal([[singleFile, otherFile], true]);
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче