Skip to content

Commit

Permalink
Add code lens for quick evaluation (#1035)
Browse files Browse the repository at this point in the history
* Add code lens for quick eval command

* Ensure commented out predicates do not have code lens

* Improve conditional check for commented out predicate  detection

* Refactor regex

* Move comment check to eliminate evaluating regex more than once

Co-authored-by: marcnjaramillo <[email protected]>
  • Loading branch information
aeisenberg and marcnjaramillo authored Dec 10, 2021
1 parent 58f4a82 commit c8ed8b2
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 16 deletions.
1 change: 1 addition & 0 deletions extensions/ql-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [UNRELEASED]

- Add a CodeLens to make the Quick Evaluation command more accessible. Click the `Quick Evaluation` prompt above a predicate definition in the editor to evaluate that predicate on its own. [#1035](https://github.com/github/vscode-codeql/pull/1035)
- Fix a bug where the _Alerts_ option would show in the results view even if there is no alerts table available. [#1038](https://github.com/github/vscode-codeql/pull/1038)

## 1.5.8 - 2 December 2021
Expand Down
29 changes: 27 additions & 2 deletions extensions/ql-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
window as Window,
env,
window,
QuickPickItem
QuickPickItem,
Range
} from 'vscode';
import { LanguageClient } from 'vscode-languageclient';
import * as os from 'os';
Expand All @@ -21,6 +22,7 @@ import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api';

import { AstViewer } from './astViewer';
import * as archiveFilesystemProvider from './archive-filesystem-provider';
import QuickEvalCodeLensProvider from './quickEvalCodeLensProvider';
import { CodeQLCliServer, CliVersionConstraint } from './cli';
import {
CliConfigListener,
Expand Down Expand Up @@ -156,6 +158,7 @@ export interface CodeQLExtensionInterface {
* @returns CodeQLExtensionInterface
*/
export async function activate(ctx: ExtensionContext): Promise<CodeQLExtensionInterface | Record<string, never>> {

void logger.log(`Starting ${extensionId} extension`);
if (extension === undefined) {
throw new Error(`Can't find extension ${extensionId}`);
Expand All @@ -166,6 +169,9 @@ export async function activate(ctx: ExtensionContext): Promise<CodeQLExtensionIn
await initializeTelemetry(extension, ctx);
languageSupport.install();

const codelensProvider = new QuickEvalCodeLensProvider();
languages.registerCodeLensProvider({ scheme: 'file', language: 'ql' }, codelensProvider);

ctx.subscriptions.push(distributionConfigListener);
const codeQlVersionRange = DEFAULT_DISTRIBUTION_VERSION_RANGE;
const distributionManager = new DistributionManager(distributionConfigListener, codeQlVersionRange, ctx);
Expand Down Expand Up @@ -471,6 +477,7 @@ async function activateWithInstalledDistribution(
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem | undefined,
range?: Range
): Promise<void> {
if (qs !== undefined) {
// If no databaseItem is specified, use the database currently selected in the Databases UI
Expand All @@ -485,7 +492,9 @@ async function activateWithInstalledDistribution(
quickEval,
selectedQuery,
progress,
token
token,
undefined,
range
);
const item = qhm.buildCompletedQuery(info);
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
Expand Down Expand Up @@ -733,6 +742,22 @@ async function activateWithInstalledDistribution(
cancellable: true
})
);

ctx.subscriptions.push(
commandRunnerWithProgress(
'codeQL.codeLensQuickEval',
async (
progress: ProgressCallback,
token: CancellationToken,
uri: Uri,
range: Range
) => await compileAndRunQuery(true, uri, progress, token, undefined, range),
{
title: 'Running query',
cancellable: true
})
);

ctx.subscriptions.push(
commandRunnerWithProgress('codeQL.quickQuery', async (
progress: ProgressCallback,
Expand Down
43 changes: 43 additions & 0 deletions extensions/ql-vscode/src/quickEvalCodeLensProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
CodeLensProvider,
TextDocument,
CodeLens,
Command,
Range
} from 'vscode';

class QuickEvalCodeLensProvider implements CodeLensProvider {
async provideCodeLenses(document: TextDocument): Promise<CodeLens[]> {

const codeLenses: CodeLens[] = [];

for (let index = 0; index < document.lineCount; index++) {
const textLine = document.lineAt(index);

// Match a predicate signature, including predicate name, parameter list, and opening brace.
// This currently does not match predicates that span multiple lines.
const regex = new RegExp(/(\w+)\s*\([^()]*\)\s*\{/);

const matches = textLine.text.match(regex);

// Make sure that a code lens is not generated for any predicate that is commented out.
if (matches && !(/^\s*\/\//).test(textLine.text)) {
const range: Range = new Range(
textLine.range.start.line, matches.index!,
textLine.range.end.line, matches.index! + 1
);

const command: Command = {
command: 'codeQL.codeLensQuickEval',
title: `Quick Evaluation: ${matches[1]}`,
arguments: [document.uri, range]
};
const codeLens = new CodeLens(range, command);
codeLenses.push(codeLens);
}
}
return codeLenses;
}
}

export default QuickEvalCodeLensProvider;
31 changes: 17 additions & 14 deletions extensions/ql-vscode/src/run-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as tmp from 'tmp-promise';
import {
CancellationToken,
ConfigurationTarget,
Range,
TextDocument,
TextEditor,
Uri,
Expand Down Expand Up @@ -332,17 +333,18 @@ async function convertToQlPath(filePath: string): Promise<string> {


/** Gets the selected position within the given editor. */
async function getSelectedPosition(editor: TextEditor): Promise<messages.Position> {
const pos = editor.selection.start;
const posEnd = editor.selection.end;
// Convert from 0-based to 1-based line and column numbers.
return {
fileName: await convertToQlPath(editor.document.fileName),
line: pos.line + 1,
column: pos.character + 1,
endLine: posEnd.line + 1,
endColumn: posEnd.character + 1
};
async function getSelectedPosition(editor: TextEditor, range?: Range): Promise<messages.Position> {
const selectedRange = range || editor.selection;
const pos = selectedRange.start;
const posEnd = selectedRange.end;
// Convert from 0-based to 1-based line and column numbers.
return {
fileName: await convertToQlPath(editor.document.fileName),
line: pos.line + 1,
column: pos.character + 1,
endLine: posEnd.line + 1,
endColumn: posEnd.character + 1
};
}

/**
Expand Down Expand Up @@ -490,7 +492,7 @@ type SelectedQuery = {
* @param selectedResourceUri The selected resource when the command was run.
* @param quickEval Whether the command being run is `Quick Evaluation`.
*/
export async function determineSelectedQuery(selectedResourceUri: Uri | undefined, quickEval: boolean): Promise<SelectedQuery> {
export async function determineSelectedQuery(selectedResourceUri: Uri | undefined, quickEval: boolean, range?: Range): Promise<SelectedQuery> {
const editor = window.activeTextEditor;

// Choose which QL file to use.
Expand Down Expand Up @@ -544,7 +546,7 @@ export async function determineSelectedQuery(selectedResourceUri: Uri | undefine
// Report an error if we end up in this (hopefully unlikely) situation.
throw new Error('The selected resource for quick evaluation should match the active editor.');
}
quickEvalPosition = await getSelectedPosition(editor);
quickEvalPosition = await getSelectedPosition(editor, range);
quickEvalText = editor.document.getText(editor.selection);
}

Expand All @@ -560,13 +562,14 @@ export async function compileAndRunQueryAgainstDatabase(
progress: ProgressCallback,
token: CancellationToken,
templates?: messages.TemplateDefinitions,
range?: Range
): Promise<QueryWithResults> {
if (!db.contents || !db.contents.dbSchemeUri) {
throw new Error(`Database ${db.databaseUri} does not have a CodeQL database scheme.`);
}

// Determine which query to run, based on the selection and the active editor.
const { queryPath, quickEvalPosition, quickEvalText } = await determineSelectedQuery(selectedQueryUri, quickEval);
const { queryPath, quickEvalPosition, quickEvalText } = await determineSelectedQuery(selectedQueryUri, quickEval, range);

const historyItemOptions: QueryHistoryItemOptions = {};
historyItemOptions.isQuickQuery === isQuickQueryPath(queryPath);
Expand Down

0 comments on commit c8ed8b2

Please sign in to comment.