From b9c5e3839eef0032037ea3774175616d9c9caf72 Mon Sep 17 00:00:00 2001 From: marcnjaramillo Date: Thu, 2 Dec 2021 11:27:13 -0800 Subject: [PATCH 1/5] Add code lens for quick eval command --- extensions/ql-vscode/CHANGELOG.md | 2 + extensions/ql-vscode/src/extension.ts | 29 +++++++++++++- .../src/quickEvalCodeLensProvider.ts | 39 +++++++++++++++++++ extensions/ql-vscode/src/run-queries.ts | 31 ++++++++------- 4 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 extensions/ql-vscode/src/quickEvalCodeLensProvider.ts diff --git a/extensions/ql-vscode/CHANGELOG.md b/extensions/ql-vscode/CHANGELOG.md index 17ba96f06e7..7673e761ae4 100644 --- a/extensions/ql-vscode/CHANGELOG.md +++ b/extensions/ql-vscode/CHANGELOG.md @@ -2,6 +2,8 @@ ## [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) + ## 1.5.8 - 2 December 2021 - Emit a more explicit error message when a user tries to add a database with an unzipped source folder to the workspace. [#1021](https://github.com/github/vscode-codeql/pull/1021) diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index 920d5e8283b..aca65ee4eab 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -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'; @@ -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, @@ -156,6 +158,7 @@ export interface CodeQLExtensionInterface { * @returns CodeQLExtensionInterface */ export async function activate(ctx: ExtensionContext): Promise> { + void logger.log(`Starting ${extensionId} extension`); if (extension === undefined) { throw new Error(`Can't find extension ${extensionId}`); @@ -166,6 +169,9 @@ export async function activate(ctx: ExtensionContext): Promise { if (qs !== undefined) { // If no databaseItem is specified, use the database currently selected in the Databases UI @@ -485,7 +492,9 @@ async function activateWithInstalledDistribution( quickEval, selectedQuery, progress, - token + token, + undefined, + range ); const item = qhm.buildCompletedQuery(info); await showResultsForCompletedQuery(item, WebviewReveal.NotForced); @@ -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, diff --git a/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts b/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts new file mode 100644 index 00000000000..391c8eb50cc --- /dev/null +++ b/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts @@ -0,0 +1,39 @@ +import { + CodeLensProvider, + TextDocument, + CodeLens, + Command, + Range +} from 'vscode'; + +class QuickEvalCodeLensProvider implements CodeLensProvider { + async provideCodeLenses(document: TextDocument): Promise { + + 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. + const regex = new RegExp(/(\w+)\s*\(\s*.*(?:,\s*)*\)\s*\{/); + const matches = textLine.text.match(regex); + + if (matches) { + 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; \ No newline at end of file diff --git a/extensions/ql-vscode/src/run-queries.ts b/extensions/ql-vscode/src/run-queries.ts index c638581958c..5146861b487 100644 --- a/extensions/ql-vscode/src/run-queries.ts +++ b/extensions/ql-vscode/src/run-queries.ts @@ -5,6 +5,7 @@ import * as tmp from 'tmp-promise'; import { CancellationToken, ConfigurationTarget, + Range, TextDocument, TextEditor, Uri, @@ -332,17 +333,18 @@ async function convertToQlPath(filePath: string): Promise { /** Gets the selected position within the given editor. */ -async function getSelectedPosition(editor: TextEditor): Promise { - 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 { + 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 + }; } /** @@ -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 { +export async function determineSelectedQuery(selectedResourceUri: Uri | undefined, quickEval: boolean, range?: Range): Promise { const editor = window.activeTextEditor; // Choose which QL file to use. @@ -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); } @@ -560,13 +562,14 @@ export async function compileAndRunQueryAgainstDatabase( progress: ProgressCallback, token: CancellationToken, templates?: messages.TemplateDefinitions, + range?: Range ): Promise { 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); From 5d40748712a09f62eaa8c0b99100aa63f5eedcc2 Mon Sep 17 00:00:00 2001 From: Marc Jaramillo Date: Thu, 9 Dec 2021 15:04:59 -0800 Subject: [PATCH 2/5] Ensure commented out predicates do not have code lens --- extensions/ql-vscode/src/quickEvalCodeLensProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts b/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts index 391c8eb50cc..e21e5242539 100644 --- a/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts +++ b/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts @@ -17,7 +17,7 @@ class QuickEvalCodeLensProvider implements CodeLensProvider { const regex = new RegExp(/(\w+)\s*\(\s*.*(?:,\s*)*\)\s*\{/); const matches = textLine.text.match(regex); - if (matches) { + if (matches && textLine.text.search(/^\s*\//)) { const range: Range = new Range( textLine.range.start.line, matches.index!, textLine.range.end.line, matches.index! + 1 From 0a27a725a71aaea3b20b4c3bd575cd321b25db8e Mon Sep 17 00:00:00 2001 From: Marc Jaramillo Date: Thu, 9 Dec 2021 16:48:23 -0800 Subject: [PATCH 3/5] Improve conditional check for commented out predicate detection --- extensions/ql-vscode/src/quickEvalCodeLensProvider.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts b/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts index e21e5242539..5f04c1ebb82 100644 --- a/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts +++ b/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts @@ -13,11 +13,18 @@ class QuickEvalCodeLensProvider implements CodeLensProvider { 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*.*(?:,\s*)*\)\s*\{/); + + // Evaluates to 'true' if a comment is detected. + const commentedOut = (/^\s*\/\//).test(textLine.text); + const matches = textLine.text.match(regex); - if (matches && textLine.text.search(/^\s*\//)) { + // Make sure that a code lens is not generated for any predicate that is commented out. + if (matches && !commentedOut) { const range: Range = new Range( textLine.range.start.line, matches.index!, textLine.range.end.line, matches.index! + 1 From 452ad4dbcf2e1f18141dfa2038b7f49ba293b404 Mon Sep 17 00:00:00 2001 From: Marc Jaramillo Date: Thu, 9 Dec 2021 17:04:07 -0800 Subject: [PATCH 4/5] Refactor regex --- extensions/ql-vscode/src/quickEvalCodeLensProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts b/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts index 5f04c1ebb82..f027094bb30 100644 --- a/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts +++ b/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts @@ -16,7 +16,7 @@ class QuickEvalCodeLensProvider implements CodeLensProvider { // 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*.*(?:,\s*)*\)\s*\{/); + const regex = new RegExp(/(\w+)\s*\([^()]*\)\s*\{/); // Evaluates to 'true' if a comment is detected. const commentedOut = (/^\s*\/\//).test(textLine.text); From 806e594a3d2352e79c133c9b2c5eed2a99c88cce Mon Sep 17 00:00:00 2001 From: Marc Jaramillo Date: Fri, 10 Dec 2021 11:02:26 -0800 Subject: [PATCH 5/5] Move comment check to eliminate evaluating regex more than once --- extensions/ql-vscode/src/quickEvalCodeLensProvider.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts b/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts index f027094bb30..eb93e63c229 100644 --- a/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts +++ b/extensions/ql-vscode/src/quickEvalCodeLensProvider.ts @@ -18,13 +18,10 @@ class QuickEvalCodeLensProvider implements CodeLensProvider { // This currently does not match predicates that span multiple lines. const regex = new RegExp(/(\w+)\s*\([^()]*\)\s*\{/); - // Evaluates to 'true' if a comment is detected. - const commentedOut = (/^\s*\/\//).test(textLine.text); - const matches = textLine.text.match(regex); // Make sure that a code lens is not generated for any predicate that is commented out. - if (matches && !commentedOut) { + if (matches && !(/^\s*\/\//).test(textLine.text)) { const range: Range = new Range( textLine.range.start.line, matches.index!, textLine.range.end.line, matches.index! + 1