Skip to content

Commit dfef810

Browse files
authored
Merge pull request #2485 from github/koesie10/move-location-utils
Move functions for resolving locations
2 parents d485ff0 + ba9f5e3 commit dfef810

File tree

8 files changed

+273
-276
lines changed

8 files changed

+273
-276
lines changed

extensions/ql-vscode/src/compare/compare-view.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { Logger } from "../common";
99
import { CodeQLCliServer } from "../codeql-cli/cli";
1010
import { DatabaseManager } from "../databases/local-databases";
11-
import { jumpToLocation } from "../interface-utils";
11+
import { jumpToLocation } from "../databases/local-databases/locations";
1212
import {
1313
transformBqrsResultSet,
1414
RawResultSet,
@@ -130,7 +130,12 @@ export class CompareView extends AbstractWebview<
130130
break;
131131

132132
case "viewSourceFile":
133-
await jumpToLocation(msg, this.databaseManager, this.logger);
133+
await jumpToLocation(
134+
msg.databaseUri,
135+
msg.loc,
136+
this.databaseManager,
137+
this.logger,
138+
);
134139
break;
135140

136141
case "openQuery":

extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { generateFlowModel } from "./generate-flow-model";
3131
import { promptImportGithubDatabase } from "../databases/database-fetcher";
3232
import { App } from "../common/app";
3333
import { ResolvableLocationValue } from "../pure/bqrs-cli-types";
34-
import { showResolvableLocation } from "../interface-utils";
34+
import { showResolvableLocation } from "../databases/local-databases/locations";
3535
import { decodeBqrsToExternalApiUsages } from "./bqrs";
3636
import { redactableError } from "../pure/errors";
3737
import { readQueryResults, runQuery } from "./external-api-usage-query";
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import {
2+
Location,
3+
Range,
4+
Selection,
5+
TextEditorRevealType,
6+
ThemeColor,
7+
Uri,
8+
ViewColumn,
9+
window as Window,
10+
workspace,
11+
} from "vscode";
12+
import {
13+
LineColumnLocation,
14+
ResolvableLocationValue,
15+
UrlValue,
16+
WholeFileLocation,
17+
} from "../../pure/bqrs-cli-types";
18+
import {
19+
isLineColumnLoc,
20+
tryGetResolvableLocation,
21+
} from "../../pure/bqrs-utils";
22+
import { getErrorMessage } from "../../pure/helpers-pure";
23+
import { Logger } from "../../common";
24+
import { DatabaseItem } from "./database-item";
25+
import { DatabaseManager } from "./database-manager";
26+
27+
const findMatchBackground = new ThemeColor("editor.findMatchBackground");
28+
const findRangeHighlightBackground = new ThemeColor(
29+
"editor.findRangeHighlightBackground",
30+
);
31+
32+
export const shownLocationDecoration = Window.createTextEditorDecorationType({
33+
backgroundColor: findMatchBackground,
34+
});
35+
36+
export const shownLocationLineDecoration =
37+
Window.createTextEditorDecorationType({
38+
backgroundColor: findRangeHighlightBackground,
39+
isWholeLine: true,
40+
});
41+
42+
/**
43+
* Resolves the specified CodeQL location to a URI into the source archive.
44+
* @param loc CodeQL location to resolve. Must have a non-empty value for `loc.file`.
45+
* @param databaseItem Database in which to resolve the file location.
46+
*/
47+
function resolveFivePartLocation(
48+
loc: LineColumnLocation,
49+
databaseItem: DatabaseItem,
50+
): Location {
51+
// `Range` is a half-open interval, and is zero-based. CodeQL locations are closed intervals, and
52+
// are one-based. Adjust accordingly.
53+
const range = new Range(
54+
Math.max(0, loc.startLine - 1),
55+
Math.max(0, loc.startColumn - 1),
56+
Math.max(0, loc.endLine - 1),
57+
Math.max(1, loc.endColumn),
58+
);
59+
60+
return new Location(databaseItem.resolveSourceFile(loc.uri), range);
61+
}
62+
63+
/**
64+
* Resolves the specified CodeQL filesystem resource location to a URI into the source archive.
65+
* @param loc CodeQL location to resolve, corresponding to an entire filesystem resource. Must have a non-empty value for `loc.file`.
66+
* @param databaseItem Database in which to resolve the filesystem resource location.
67+
*/
68+
function resolveWholeFileLocation(
69+
loc: WholeFileLocation,
70+
databaseItem: DatabaseItem,
71+
): Location {
72+
// A location corresponding to the start of the file.
73+
const range = new Range(0, 0, 0, 0);
74+
return new Location(databaseItem.resolveSourceFile(loc.uri), range);
75+
}
76+
77+
/**
78+
* Try to resolve the specified CodeQL location to a URI into the source archive. If no exact location
79+
* can be resolved, returns `undefined`.
80+
* @param loc CodeQL location to resolve
81+
* @param databaseItem Database in which to resolve the file location.
82+
*/
83+
export function tryResolveLocation(
84+
loc: UrlValue | undefined,
85+
databaseItem: DatabaseItem,
86+
): Location | undefined {
87+
const resolvableLoc = tryGetResolvableLocation(loc);
88+
if (!resolvableLoc || typeof resolvableLoc === "string") {
89+
return;
90+
} else if (isLineColumnLoc(resolvableLoc)) {
91+
return resolveFivePartLocation(resolvableLoc, databaseItem);
92+
} else {
93+
return resolveWholeFileLocation(resolvableLoc, databaseItem);
94+
}
95+
}
96+
97+
export async function showResolvableLocation(
98+
loc: ResolvableLocationValue,
99+
databaseItem: DatabaseItem,
100+
): Promise<void> {
101+
await showLocation(tryResolveLocation(loc, databaseItem));
102+
}
103+
104+
export async function showLocation(location?: Location) {
105+
if (!location) {
106+
return;
107+
}
108+
109+
const doc = await workspace.openTextDocument(location.uri);
110+
const editorsWithDoc = Window.visibleTextEditors.filter(
111+
(e) => e.document === doc,
112+
);
113+
const editor =
114+
editorsWithDoc.length > 0
115+
? editorsWithDoc[0]
116+
: await Window.showTextDocument(doc, {
117+
// avoid preview mode so editor is sticky and will be added to navigation and search histories.
118+
preview: false,
119+
viewColumn: ViewColumn.One,
120+
});
121+
122+
const range = location.range;
123+
// When highlighting the range, vscode's occurrence-match and bracket-match highlighting will
124+
// trigger based on where we place the cursor/selection, and will compete for the user's attention.
125+
// For reference:
126+
// - Occurences are highlighted when the cursor is next to or inside a word or a whole word is selected.
127+
// - Brackets are highlighted when the cursor is next to a bracket and there is an empty selection.
128+
// - Multi-line selections explicitly highlight line-break characters, but multi-line decorators do not.
129+
//
130+
// For single-line ranges, select the whole range, mainly to disable bracket highlighting.
131+
// For multi-line ranges, place the cursor at the beginning to avoid visual artifacts from selected line-breaks.
132+
// Multi-line ranges are usually large enough to overshadow the noise from bracket highlighting.
133+
const selectionEnd =
134+
range.start.line === range.end.line ? range.end : range.start;
135+
editor.selection = new Selection(range.start, selectionEnd);
136+
editor.revealRange(range, TextEditorRevealType.InCenter);
137+
editor.setDecorations(shownLocationDecoration, [range]);
138+
editor.setDecorations(shownLocationLineDecoration, [range]);
139+
}
140+
141+
export async function jumpToLocation(
142+
databaseUri: string,
143+
loc: ResolvableLocationValue,
144+
databaseManager: DatabaseManager,
145+
logger: Logger,
146+
) {
147+
const databaseItem = databaseManager.findDatabaseItem(Uri.parse(databaseUri));
148+
if (databaseItem !== undefined) {
149+
try {
150+
await showResolvableLocation(loc, databaseItem);
151+
} catch (e) {
152+
if (e instanceof Error && e.message.match(/File not found/)) {
153+
void Window.showErrorMessage(
154+
"Original file of this result is not in the database's source archive.",
155+
);
156+
} else {
157+
void logger.log(`Unable to jump to location: ${getErrorMessage(e)}`);
158+
}
159+
}
160+
}
161+
}
Lines changed: 1 addition & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,4 @@
1-
import {
2-
Uri,
3-
Location,
4-
Range,
5-
WebviewPanel,
6-
workspace,
7-
window as Window,
8-
ViewColumn,
9-
Selection,
10-
TextEditorRevealType,
11-
ThemeColor,
12-
} from "vscode";
13-
import { tryGetResolvableLocation, isLineColumnLoc } from "./pure/bqrs-utils";
14-
import { DatabaseItem, DatabaseManager } from "./databases/local-databases";
15-
import { ViewSourceFileMsg } from "./pure/interface-types";
16-
import { Logger } from "./common";
17-
import {
18-
LineColumnLocation,
19-
WholeFileLocation,
20-
UrlValue,
21-
ResolvableLocationValue,
22-
} from "./pure/bqrs-cli-types";
1+
import { Uri, WebviewPanel } from "vscode";
232

243
/**
254
* This module contains functions and types that are sharedd between
@@ -44,144 +23,3 @@ export function fileUriToWebviewUri(
4423
): string {
4524
return panel.webview.asWebviewUri(fileUriOnDisk).toString();
4625
}
47-
48-
/**
49-
* Resolves the specified CodeQL location to a URI into the source archive.
50-
* @param loc CodeQL location to resolve. Must have a non-empty value for `loc.file`.
51-
* @param databaseItem Database in which to resolve the file location.
52-
*/
53-
function resolveFivePartLocation(
54-
loc: LineColumnLocation,
55-
databaseItem: DatabaseItem,
56-
): Location {
57-
// `Range` is a half-open interval, and is zero-based. CodeQL locations are closed intervals, and
58-
// are one-based. Adjust accordingly.
59-
const range = new Range(
60-
Math.max(0, loc.startLine - 1),
61-
Math.max(0, loc.startColumn - 1),
62-
Math.max(0, loc.endLine - 1),
63-
Math.max(1, loc.endColumn),
64-
);
65-
66-
return new Location(databaseItem.resolveSourceFile(loc.uri), range);
67-
}
68-
69-
/**
70-
* Resolves the specified CodeQL filesystem resource location to a URI into the source archive.
71-
* @param loc CodeQL location to resolve, corresponding to an entire filesystem resource. Must have a non-empty value for `loc.file`.
72-
* @param databaseItem Database in which to resolve the filesystem resource location.
73-
*/
74-
function resolveWholeFileLocation(
75-
loc: WholeFileLocation,
76-
databaseItem: DatabaseItem,
77-
): Location {
78-
// A location corresponding to the start of the file.
79-
const range = new Range(0, 0, 0, 0);
80-
return new Location(databaseItem.resolveSourceFile(loc.uri), range);
81-
}
82-
83-
/**
84-
* Try to resolve the specified CodeQL location to a URI into the source archive. If no exact location
85-
* can be resolved, returns `undefined`.
86-
* @param loc CodeQL location to resolve
87-
* @param databaseItem Database in which to resolve the file location.
88-
*/
89-
export function tryResolveLocation(
90-
loc: UrlValue | undefined,
91-
databaseItem: DatabaseItem,
92-
): Location | undefined {
93-
const resolvableLoc = tryGetResolvableLocation(loc);
94-
if (!resolvableLoc || typeof resolvableLoc === "string") {
95-
return;
96-
} else if (isLineColumnLoc(resolvableLoc)) {
97-
return resolveFivePartLocation(resolvableLoc, databaseItem);
98-
} else {
99-
return resolveWholeFileLocation(resolvableLoc, databaseItem);
100-
}
101-
}
102-
103-
export async function showResolvableLocation(
104-
loc: ResolvableLocationValue,
105-
databaseItem: DatabaseItem,
106-
): Promise<void> {
107-
await showLocation(tryResolveLocation(loc, databaseItem));
108-
}
109-
110-
export async function showLocation(location?: Location) {
111-
if (!location) {
112-
return;
113-
}
114-
115-
const doc = await workspace.openTextDocument(location.uri);
116-
const editorsWithDoc = Window.visibleTextEditors.filter(
117-
(e) => e.document === doc,
118-
);
119-
const editor =
120-
editorsWithDoc.length > 0
121-
? editorsWithDoc[0]
122-
: await Window.showTextDocument(doc, {
123-
// avoid preview mode so editor is sticky and will be added to navigation and search histories.
124-
preview: false,
125-
viewColumn: ViewColumn.One,
126-
});
127-
128-
const range = location.range;
129-
// When highlighting the range, vscode's occurrence-match and bracket-match highlighting will
130-
// trigger based on where we place the cursor/selection, and will compete for the user's attention.
131-
// For reference:
132-
// - Occurences are highlighted when the cursor is next to or inside a word or a whole word is selected.
133-
// - Brackets are highlighted when the cursor is next to a bracket and there is an empty selection.
134-
// - Multi-line selections explicitly highlight line-break characters, but multi-line decorators do not.
135-
//
136-
// For single-line ranges, select the whole range, mainly to disable bracket highlighting.
137-
// For multi-line ranges, place the cursor at the beginning to avoid visual artifacts from selected line-breaks.
138-
// Multi-line ranges are usually large enough to overshadow the noise from bracket highlighting.
139-
const selectionEnd =
140-
range.start.line === range.end.line ? range.end : range.start;
141-
editor.selection = new Selection(range.start, selectionEnd);
142-
editor.revealRange(range, TextEditorRevealType.InCenter);
143-
editor.setDecorations(shownLocationDecoration, [range]);
144-
editor.setDecorations(shownLocationLineDecoration, [range]);
145-
}
146-
147-
const findMatchBackground = new ThemeColor("editor.findMatchBackground");
148-
const findRangeHighlightBackground = new ThemeColor(
149-
"editor.findRangeHighlightBackground",
150-
);
151-
152-
export const shownLocationDecoration = Window.createTextEditorDecorationType({
153-
backgroundColor: findMatchBackground,
154-
});
155-
156-
export const shownLocationLineDecoration =
157-
Window.createTextEditorDecorationType({
158-
backgroundColor: findRangeHighlightBackground,
159-
isWholeLine: true,
160-
});
161-
162-
export async function jumpToLocation(
163-
msg: ViewSourceFileMsg,
164-
databaseManager: DatabaseManager,
165-
logger: Logger,
166-
) {
167-
const databaseItem = databaseManager.findDatabaseItem(
168-
Uri.parse(msg.databaseUri),
169-
);
170-
if (databaseItem !== undefined) {
171-
try {
172-
await showResolvableLocation(msg.loc, databaseItem);
173-
} catch (e) {
174-
if (e instanceof Error) {
175-
if (e.message.match(/File not found/)) {
176-
void Window.showErrorMessage(
177-
"Original file of this result is not in the database's source archive.",
178-
);
179-
} else {
180-
void logger.log(`Unable to handleMsgFromView: ${e.message}`);
181-
}
182-
} else {
183-
void logger.log(`Unable to handleMsgFromView: ${e}`);
184-
}
185-
}
186-
}
187-
}

extensions/ql-vscode/src/interface.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,13 @@ import {
5353
parseSarifLocation,
5454
parseSarifPlainTextMessage,
5555
} from "./pure/sarif-utils";
56+
import { WebviewReveal, fileUriToWebviewUri } from "./interface-utils";
5657
import {
57-
WebviewReveal,
58-
fileUriToWebviewUri,
5958
tryResolveLocation,
6059
shownLocationDecoration,
6160
shownLocationLineDecoration,
6261
jumpToLocation,
63-
} from "./interface-utils";
62+
} from "./databases/local-databases/locations";
6463
import {
6564
RawResultSet,
6665
transformBqrsResultSet,
@@ -259,7 +258,12 @@ export class ResultsView extends AbstractWebview<
259258
this.onWebViewLoaded();
260259
break;
261260
case "viewSourceFile": {
262-
await jumpToLocation(msg, this.databaseManager, this.logger);
261+
await jumpToLocation(
262+
msg.databaseUri,
263+
msg.loc,
264+
this.databaseManager,
265+
this.logger,
266+
);
263267
break;
264268
}
265269
case "toggleDiagnostics": {

0 commit comments

Comments
 (0)