Skip to content

Commit 0447832

Browse files
authored
Remove virtualDocUri() as a footgun (#713)
* Remove unused imports * Add `withVirtualDocUri()` return type * Document try-finally without catch rationale * Use `withVirtualDocUri()` in `embeddedHoverProvider()` * Use `withVirtualDocUri()` in `embeddedSignatureHelpProvider()` * Use `withVirtualDocUri()` in `embeddedGoToDefinitionProvider()` * Remove unused import * Un-`export` the footgun that is `virtualDocUri()` * Document `withVirtualDocUri()` and `virtualDocUri()`
1 parent 3c666f0 commit 0447832

File tree

6 files changed

+86
-83
lines changed

6 files changed

+86
-83
lines changed

apps/vscode/src/host/hooks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { ExtensionHost, HostWebviewPanel, HostStatementRangeProvider, HostHelpTo
2020
import { CellExecutor, cellExecutorForLanguage, executableLanguages, isKnitrDocument, pythonWithReticulate } from './executors';
2121
import { ExecuteQueue } from './execute-queue';
2222
import { MarkdownEngine } from '../markdown/engine';
23-
import { virtualDoc, virtualDocUri, adjustedPosition, unadjustedRange, withVirtualDocUri } from "../vdoc/vdoc";
23+
import { virtualDoc, adjustedPosition, unadjustedRange, withVirtualDocUri } from "../vdoc/vdoc";
2424
import { EmbeddedLanguage } from '../vdoc/languages';
2525

2626
declare global {

apps/vscode/src/lsp/client.ts

Lines changed: 66 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
LocationLink,
2424
Definition,
2525
LogOutputChannel,
26+
Uri
2627
} from "vscode";
2728
import {
2829
LanguageClient,
@@ -51,7 +52,7 @@ import {
5152
adjustedPosition,
5253
unadjustedRange,
5354
virtualDoc,
54-
virtualDocUri,
55+
withVirtualDocUri,
5556
} from "../vdoc/vdoc";
5657
import { activateVirtualDocEmbeddedContent } from "../vdoc/vdoc-content";
5758
import { vdocCompletions } from "../vdoc/vdoc-completion";
@@ -225,19 +226,13 @@ function embeddedHoverProvider(engine: MarkdownEngine) {
225226

226227
const vdoc = await virtualDoc(document, position, engine);
227228
if (vdoc) {
228-
// get uri for hover
229-
const vdocUri = await virtualDocUri(vdoc, document.uri, "hover");
230-
231-
// execute hover
232-
try {
233-
return getHover(vdocUri.uri, vdoc.language, position);
234-
} catch (error) {
235-
console.log(error);
236-
} finally {
237-
if (vdocUri.cleanup) {
238-
await vdocUri.cleanup();
229+
return await withVirtualDocUri(vdoc, document.uri, "hover", async (uri: Uri) => {
230+
try {
231+
return await getHover(uri, vdoc.language, position);
232+
} catch (error) {
233+
console.log(error);
239234
}
240-
}
235+
});
241236
}
242237

243238
// default to server delegation
@@ -255,16 +250,13 @@ function embeddedSignatureHelpProvider(engine: MarkdownEngine) {
255250
) => {
256251
const vdoc = await virtualDoc(document, position, engine);
257252
if (vdoc) {
258-
const vdocUri = await virtualDocUri(vdoc, document.uri, "signature");
259-
try {
260-
return getSignatureHelpHover(vdocUri.uri, vdoc.language, position, context.triggerCharacter);
261-
} catch (error) {
262-
return undefined;
263-
} finally {
264-
if (vdocUri.cleanup) {
265-
await vdocUri.cleanup();
253+
return await withVirtualDocUri(vdoc, document.uri, "signature", async (uri: Uri) => {
254+
try {
255+
return await getSignatureHelpHover(uri, vdoc.language, position, context.triggerCharacter);
256+
} catch (error) {
257+
return undefined;
266258
}
267-
}
259+
});
268260
} else {
269261
return await next(document, position, context, token);
270262
}
@@ -280,64 +272,61 @@ function embeddedGoToDefinitionProvider(engine: MarkdownEngine) {
280272
): Promise<Definition | LocationLink[] | null | undefined> => {
281273
const vdoc = await virtualDoc(document, position, engine);
282274
if (vdoc) {
283-
const vdocUri = await virtualDocUri(vdoc, document.uri, "definition");
284-
try {
285-
const definitions = await commands.executeCommand<
286-
ProviderResult<Definition | LocationLink[]>
287-
>(
288-
"vscode.executeDefinitionProvider",
289-
vdocUri.uri,
290-
adjustedPosition(vdoc.language, position)
291-
);
292-
const resolveLocation = (location: Location) => {
293-
if (location.uri.toString() === vdocUri.uri.toString()) {
294-
return new Location(
295-
document.uri,
296-
unadjustedRange(vdoc.language, location.range)
297-
);
298-
} else {
299-
return location;
300-
}
301-
};
302-
const resolveLocationLink = (location: LocationLink) => {
303-
if (location.targetUri.toString() === vdocUri.uri.toString()) {
304-
const locationLink: LocationLink = {
305-
targetRange: unadjustedRange(vdoc.language, location.targetRange),
306-
originSelectionRange: location.originSelectionRange
307-
? unadjustedRange(vdoc.language, location.originSelectionRange)
308-
: undefined,
309-
targetSelectionRange: location.targetSelectionRange
310-
? unadjustedRange(vdoc.language, location.targetSelectionRange)
311-
: undefined,
312-
targetUri: document.uri,
313-
};
314-
return locationLink;
315-
} else {
316-
return location;
317-
}
318-
};
319-
if (definitions instanceof Location) {
320-
return resolveLocation(definitions);
321-
} else if (Array.isArray(definitions) && definitions.length > 0) {
322-
if (definitions[0] instanceof Location) {
323-
return definitions.map((definition) =>
324-
resolveLocation(definition as Location)
325-
);
275+
return await withVirtualDocUri(vdoc, document.uri, "definition", async (uri: Uri) => {
276+
try {
277+
const definitions = await commands.executeCommand<
278+
ProviderResult<Definition | LocationLink[]>
279+
>(
280+
"vscode.executeDefinitionProvider",
281+
uri,
282+
adjustedPosition(vdoc.language, position)
283+
);
284+
const resolveLocation = (location: Location) => {
285+
if (location.uri.toString() === uri.toString()) {
286+
return new Location(
287+
document.uri,
288+
unadjustedRange(vdoc.language, location.range)
289+
);
290+
} else {
291+
return location;
292+
}
293+
};
294+
const resolveLocationLink = (location: LocationLink) => {
295+
if (location.targetUri.toString() === uri.toString()) {
296+
const locationLink: LocationLink = {
297+
targetRange: unadjustedRange(vdoc.language, location.targetRange),
298+
originSelectionRange: location.originSelectionRange
299+
? unadjustedRange(vdoc.language, location.originSelectionRange)
300+
: undefined,
301+
targetSelectionRange: location.targetSelectionRange
302+
? unadjustedRange(vdoc.language, location.targetSelectionRange)
303+
: undefined,
304+
targetUri: document.uri,
305+
};
306+
return locationLink;
307+
} else {
308+
return location;
309+
}
310+
};
311+
if (definitions instanceof Location) {
312+
return resolveLocation(definitions);
313+
} else if (Array.isArray(definitions) && definitions.length > 0) {
314+
if (definitions[0] instanceof Location) {
315+
return definitions.map((definition) =>
316+
resolveLocation(definition as Location)
317+
);
318+
} else {
319+
return definitions.map((definition) =>
320+
resolveLocationLink(definition as LocationLink)
321+
);
322+
}
326323
} else {
327-
return definitions.map((definition) =>
328-
resolveLocationLink(definition as LocationLink)
329-
);
324+
return definitions;
330325
}
331-
} else {
332-
return definitions;
333-
}
334-
} catch (error) {
335-
return undefined;
336-
} finally {
337-
if (vdocUri.cleanup) {
338-
await vdocUri.cleanup();
326+
} catch (error) {
327+
return undefined;
339328
}
340-
}
329+
});
341330
} else {
342331
return await next(document, position, token);
343332
}

apps/vscode/src/providers/assist/render-assist.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
import { JsonRpcRequestTransport, escapeRegExpCharacters } from "core";
3636
import { CodeViewCellContext, kCodeViewAssist } from "editor-types";
3737
import { embeddedLanguage } from "../../vdoc/languages";
38-
import { virtualDocForCode, virtualDocUri, withVirtualDocUri } from "../../vdoc/vdoc";
38+
import { virtualDocForCode, withVirtualDocUri } from "../../vdoc/vdoc";
3939
import { getHover, getSignatureHelpHover } from "../../core/hover";
4040
import { Hover as LspHover, MarkupKind } from "vscode-languageserver-types";
4141
import { MarkupContent } from "vscode-languageclient";

apps/vscode/src/providers/format.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import {
4444
VirtualDoc,
4545
virtualDocForCode,
4646
virtualDocForLanguage,
47-
virtualDocUri,
4847
withVirtualDocUri,
4948
} from "../vdoc/vdoc";
5049

apps/vscode/src/vdoc/vdoc-completion.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import { commands, Position, Uri, CompletionList, CompletionItem, Range } from "vscode";
1717
import { EmbeddedLanguage } from "./languages";
18-
import { adjustedPosition, unadjustedRange, VirtualDoc, virtualDocUri, withVirtualDocUri } from "./vdoc";
18+
import { adjustedPosition, unadjustedRange, VirtualDoc, withVirtualDocUri } from "./vdoc";
1919

2020
export async function vdocCompletions(
2121
vdoc: VirtualDoc,

apps/vscode/src/vdoc/vdoc.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,26 @@ export type VirtualDocAction =
122122

123123
export type VirtualDocUri = { uri: Uri, cleanup?: () => Promise<void> };
124124

125+
/**
126+
* Execute a callback on a virtual document's temporary URI
127+
*
128+
* This method automatically cleans up the temporary URI after executing `f`.
129+
*
130+
* @param vdoc The virtual document to create a temporary URI for
131+
* @param parentUri The virtual document's original URI it was virtualized from
132+
* @param f The callback to execute
133+
* @returns A Promise evaluating to an object of type `T` returned by `f`
134+
*/
125135
export async function withVirtualDocUri<T>(
126136
vdoc: VirtualDoc,
127137
parentUri: Uri,
128138
action: VirtualDocAction,
129139
f: (uri: Uri) => Promise<T>
130-
) {
140+
): Promise<T> {
131141
const vdocUri = await virtualDocUri(vdoc, parentUri, action);
132142

143+
// try-finally without a catch allows `f()` to propagate an exception up to the caller
144+
// while still allowing us to clean up the vdoc tempfile.
133145
try {
134146
return await f(vdocUri.uri);
135147
} finally {
@@ -139,7 +151,10 @@ export async function withVirtualDocUri<T>(
139151
}
140152
}
141153

142-
export async function virtualDocUri(
154+
// To be used through `withVirtualDocUri()`. Not safe to export on its own! The
155+
// cleanup hook must be called, and relying on the caller to do this is a huge
156+
// footgun.
157+
async function virtualDocUri(
143158
virtualDoc: VirtualDoc,
144159
parentUri: Uri,
145160
action: VirtualDocAction

0 commit comments

Comments
 (0)