Initial implementation of sourcemap-based jump-to-QL command
This commit is contained in:
@@ -110,6 +110,12 @@
|
|||||||
"extensions": [
|
"extensions": [
|
||||||
".qhelp"
|
".qhelp"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ql-summary",
|
||||||
|
"filenames": [
|
||||||
|
"evaluator-log.summary"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grammars": [
|
"grammars": [
|
||||||
@@ -608,6 +614,11 @@
|
|||||||
"light": "media/light/clear-all.svg",
|
"light": "media/light/clear-all.svg",
|
||||||
"dark": "media/dark/clear-all.svg"
|
"dark": "media/dark/clear-all.svg"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "codeQL.gotoQL",
|
||||||
|
"title": "Go to QL Code",
|
||||||
|
"enablement": "codeql.hasQLSource"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"menus": {
|
"menus": {
|
||||||
@@ -1084,6 +1095,10 @@
|
|||||||
{
|
{
|
||||||
"command": "codeQL.previewQueryHelp",
|
"command": "codeQL.previewQueryHelp",
|
||||||
"when": "resourceExtname == .qhelp && isWorkspaceTrusted"
|
"when": "resourceExtname == .qhelp && isWorkspaceTrusted"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "codeQL.gotoQL",
|
||||||
|
"when": "editorLangId == ql-summary"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -683,6 +683,7 @@ export class CodeQLCliServer implements Disposable {
|
|||||||
const subcommandArgs = [
|
const subcommandArgs = [
|
||||||
'--format=text',
|
'--format=text',
|
||||||
`--end-summary=${endSummaryPath}`,
|
`--end-summary=${endSummaryPath}`,
|
||||||
|
`--sourcemap`,
|
||||||
inputPath,
|
inputPath,
|
||||||
outputPath
|
outputPath
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ import { handleDownloadPacks, handleInstallPackDependencies } from './packaging'
|
|||||||
import { HistoryItemLabelProvider } from './history-item-label-provider';
|
import { HistoryItemLabelProvider } from './history-item-label-provider';
|
||||||
import { exportRemoteQueryResults } from './remote-queries/export-results';
|
import { exportRemoteQueryResults } from './remote-queries/export-results';
|
||||||
import { RemoteQuery } from './remote-queries/remote-query';
|
import { RemoteQuery } from './remote-queries/remote-query';
|
||||||
|
import { SummaryLanguageSupport } from './log-insights/summary-language-support';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* extension.ts
|
* extension.ts
|
||||||
@@ -1045,6 +1046,8 @@ async function activateWithInstalledDistribution(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ctx.subscriptions.push(new SummaryLanguageSupport());
|
||||||
|
|
||||||
void logger.log('Starting language server.');
|
void logger.log('Starting language server.');
|
||||||
ctx.subscriptions.push(client.start());
|
ctx.subscriptions.push(client.start());
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import { RawSourceMap, SourceMapConsumer } from 'source-map';
|
||||||
|
import { commands, Position, Selection, TextDocument, TextEditor, TextEditorRevealType, TextEditorSelectionChangeEvent, ViewColumn, window, workspace } from 'vscode';
|
||||||
|
import { DisposableObject } from '../pure/disposable-object';
|
||||||
|
import { commandRunner } from '../commandRunner';
|
||||||
|
|
||||||
|
/** A `Position` within a specified file on disk. */
|
||||||
|
interface PositionInFile {
|
||||||
|
filePath: string;
|
||||||
|
position: Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the specified source location in a text editor.
|
||||||
|
* @param position The position (including file path) to show.
|
||||||
|
*/
|
||||||
|
async function showSourceLocation(position: PositionInFile): Promise<void> {
|
||||||
|
const document = await workspace.openTextDocument(position.filePath);
|
||||||
|
const editor = await window.showTextDocument(document, ViewColumn.Active);
|
||||||
|
editor.selection = new Selection(position.position, position.position);
|
||||||
|
editor.revealRange(editor.selection, TextEditorRevealType.InCenterIfOutsideViewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple language support for human-readable evaluator log summaries.
|
||||||
|
*
|
||||||
|
* This class implements the `codeQL.gotoQL` command, which jumps from RA code to the corresponding
|
||||||
|
* QL code that generated it. It also tracks the current selection and active editor to enable and
|
||||||
|
* disable that command based on whether there is a QL mapping for the current selection.
|
||||||
|
*/
|
||||||
|
export class SummaryLanguageSupport extends DisposableObject {
|
||||||
|
/**
|
||||||
|
* The last `TextDocument` (with language `ql-summary`) for which we tried to find a sourcemap, or
|
||||||
|
* `undefined` if we have not seen such a document yet.
|
||||||
|
*/
|
||||||
|
private lastDocument : TextDocument | undefined = undefined;
|
||||||
|
/**
|
||||||
|
* The sourcemap for `lastDocument`, or `undefined` if there was no such sourcemap or document.
|
||||||
|
*/
|
||||||
|
private sourceMap : SourceMapConsumer | undefined = undefined;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.push(window.onDidChangeActiveTextEditor(this.handleDidChangeActiveTextEditor));
|
||||||
|
this.push(window.onDidChangeTextEditorSelection(this.handleDidChangeTextEditorSelection));
|
||||||
|
|
||||||
|
this.push(commandRunner('codeQL.gotoQL', this.handleGotoQL));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the location of the QL code that generated the RA at the current selection in the active
|
||||||
|
* editor, or `undefined` if there is no mapping.
|
||||||
|
*/
|
||||||
|
private async getQLSourceLocation(): Promise<PositionInFile | undefined> {
|
||||||
|
const editor = window.activeTextEditor;
|
||||||
|
if (editor === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const document = editor.document;
|
||||||
|
if (document.languageId !== 'ql-summary') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.uri.scheme !== 'file') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.lastDocument !== document) {
|
||||||
|
if (this.sourceMap !== undefined) {
|
||||||
|
this.sourceMap.destroy();
|
||||||
|
this.sourceMap = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapPath = document.uri.fsPath + '.map';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sourceMapText = await fs.promises.readFile(mapPath, 'utf-8');
|
||||||
|
const rawMap: RawSourceMap = JSON.parse(sourceMapText);
|
||||||
|
this.sourceMap = await new SourceMapConsumer(rawMap);
|
||||||
|
} catch {
|
||||||
|
// Error reading sourcemap. Pretend there was no sourcemap
|
||||||
|
this.sourceMap = undefined;
|
||||||
|
}
|
||||||
|
this.lastDocument = document;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sourceMap === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qlPosition = this.sourceMap.originalPositionFor({
|
||||||
|
line: editor.selection.start.line + 1,
|
||||||
|
column: editor.selection.start.character,
|
||||||
|
bias: SourceMapConsumer.GREATEST_LOWER_BOUND
|
||||||
|
});
|
||||||
|
|
||||||
|
if ((qlPosition.source === null) || (qlPosition.line === null)) {
|
||||||
|
// No position found.
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const line = qlPosition.line - 1; // In `source-map`, lines are 1-based...
|
||||||
|
const column = qlPosition.column ?? 0; // ...but columns are 0-based :(
|
||||||
|
|
||||||
|
return {
|
||||||
|
filePath: qlPosition.source,
|
||||||
|
position: new Position(line, column)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the `codeql.hasQLSource` context variable based on the current selection. This variable
|
||||||
|
* controls whether or not the `codeQL.gotoQL` command is enabled.
|
||||||
|
*/
|
||||||
|
private async updateContext(): Promise<void> {
|
||||||
|
const position = await this.getQLSourceLocation();
|
||||||
|
|
||||||
|
const result = await commands.executeCommand('setContext', 'codeql.hasQLSource', position !== undefined);
|
||||||
|
void result;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDidChangeActiveTextEditor = async (editor: TextEditor | undefined): Promise<void> => {
|
||||||
|
void editor;
|
||||||
|
await this.updateContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDidChangeTextEditorSelection = async (e: TextEditorSelectionChangeEvent): Promise<void> => {
|
||||||
|
void e;
|
||||||
|
await this.updateContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGotoQL = async (): Promise<void> => {
|
||||||
|
const position = await this.getQLSourceLocation();
|
||||||
|
if (position !== undefined) {
|
||||||
|
await showSourceLocation(position);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -155,6 +155,10 @@ export interface CompilationOptions {
|
|||||||
* get reported anyway. Useful for universal compilation options.
|
* get reported anyway. Useful for universal compilation options.
|
||||||
*/
|
*/
|
||||||
computeDefaultStrings: boolean;
|
computeDefaultStrings: boolean;
|
||||||
|
/**
|
||||||
|
* Emit debug information in compiled query.
|
||||||
|
*/
|
||||||
|
emitDebugInfo: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -248,7 +248,8 @@ export class QueryEvaluationInfo {
|
|||||||
localChecking: false,
|
localChecking: false,
|
||||||
noComputeGetUrl: false,
|
noComputeGetUrl: false,
|
||||||
noComputeToString: false,
|
noComputeToString: false,
|
||||||
computeDefaultStrings: true
|
computeDefaultStrings: true,
|
||||||
|
emitDebugInfo: true
|
||||||
},
|
},
|
||||||
extraOptions: {
|
extraOptions: {
|
||||||
timeoutSecs: qs.config.timeoutSecs
|
timeoutSecs: qs.config.timeoutSecs
|
||||||
|
|||||||
@@ -151,7 +151,8 @@ describe('using the query server', function() {
|
|||||||
localChecking: false,
|
localChecking: false,
|
||||||
noComputeGetUrl: false,
|
noComputeGetUrl: false,
|
||||||
noComputeToString: false,
|
noComputeToString: false,
|
||||||
computeDefaultStrings: true
|
computeDefaultStrings: true,
|
||||||
|
emitDebugInfo: true
|
||||||
},
|
},
|
||||||
queryToCheck: qlProgram,
|
queryToCheck: qlProgram,
|
||||||
resultPath: COMPILED_QUERY_PATH,
|
resultPath: COMPILED_QUERY_PATH,
|
||||||
|
|||||||
Reference in New Issue
Block a user