Skip to content

Commit e52a9ce

Browse files
authored
Add Throws to doc comment template (#967)
Makes the behaviour consistent with Xcode, where if a function throws there is a templated field to describe the error. Adds tests for the existing behaviour, as well as this new behaviour.
1 parent a5335dc commit e52a9ce

File tree

2 files changed

+249
-4
lines changed

2 files changed

+249
-4
lines changed

src/editor/CommentCompletion.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ class FunctionDocumentationCompletionProvider implements vscode.CompletionItemPr
8282
const funcPosition = new vscode.Position(position.line + 1, 0);
8383
const details = this.getFunctionDetails(document, funcPosition);
8484
if (details) {
85-
if (details.parameters.length === 0 && details.returns === false) {
85+
if (
86+
details.parameters.length === 0 &&
87+
details.returns === false &&
88+
details.throws === false
89+
) {
8690
return undefined;
8791
}
8892
const snippet = this.constructSnippet(details, false);
@@ -109,7 +113,7 @@ class FunctionDocumentationCompletionProvider implements vscode.CompletionItemPr
109113
if (details) {
110114
const snippet = this.constructSnippet(details, true);
111115
const insertPosition = new vscode.Position(line, details.indent);
112-
editor.insertSnippet(snippet, insertPosition);
116+
await editor.insertSnippet(snippet, insertPosition);
113117
}
114118
}
115119

@@ -208,10 +212,10 @@ class FunctionDocumentationCompletionProvider implements vscode.CompletionItemPr
208212
snippetIndex++;
209213
}
210214
}
211-
/*if (details.throws) {
215+
if (details.throws) {
212216
string += `\n/// - Throws: $${snippetIndex}`;
213217
snippetIndex++;
214-
}*/
218+
}
215219
if (details.returns) {
216220
string += `\n/// - Returns: $${snippetIndex}`;
217221
}
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2023 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as assert from "assert";
16+
import * as vscode from "vscode";
17+
import { CommentCompletionProviders } from "../../../src/editor/CommentCompletion";
18+
19+
suite("CommentCompletion Test Suite", () => {
20+
let document: vscode.TextDocument | undefined;
21+
let provider: CommentCompletionProviders;
22+
23+
setup(() => {
24+
provider = new CommentCompletionProviders();
25+
});
26+
27+
teardown(async () => {
28+
const editor = vscode.window.visibleTextEditors.find(
29+
editor => editor.document === document
30+
);
31+
32+
if (editor && document) {
33+
await vscode.window.showTextDocument(document, editor.viewColumn);
34+
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
35+
}
36+
37+
provider.dispose();
38+
});
39+
40+
test("Completion on line that isn't a comment", async () => {
41+
const { document, positions } = await openDocument(`
42+
1️⃣
43+
func foo() {}`);
44+
const position = positions["1️⃣"];
45+
46+
const items = await provider.functionCommentCompletion.provideCompletionItems(
47+
document,
48+
position
49+
);
50+
assert.deepEqual(items, undefined);
51+
});
52+
53+
test("Comment completion on line that isn't a function", async () => {
54+
const { document, positions } = await openDocument(`
55+
/// 1️⃣
56+
let x = 1`);
57+
const position = positions["1️⃣"];
58+
59+
const items = await provider.functionCommentCompletion.provideCompletionItems(
60+
document,
61+
position
62+
);
63+
assert.deepEqual(items, undefined);
64+
});
65+
66+
test("Comment completion on func with no argument, no return should have no completions", async () => {
67+
const { document, positions } = await openDocument(`
68+
/// 1️⃣
69+
func foo() {}`);
70+
const position = positions["1️⃣"];
71+
72+
const items = await provider.functionCommentCompletion.provideCompletionItems(
73+
document,
74+
position
75+
);
76+
assert.deepEqual(items, undefined);
77+
});
78+
79+
test("Comment completion on single argument function, no return should have a completion", async () => {
80+
const { document, positions } = await openDocument(`
81+
/// 1️⃣
82+
func foo(bar: Int) {}`);
83+
const position = positions["1️⃣"];
84+
85+
const items = await provider.functionCommentCompletion.provideCompletionItems(
86+
document,
87+
position
88+
);
89+
assert.deepEqual(items, [
90+
expectedCompletionItem(` $1
91+
/// - Parameter bar: $2`),
92+
]);
93+
});
94+
95+
test("Comment completion on single argument function, with return should have a completion", async () => {
96+
const { document, positions } = await openDocument(`
97+
/// 1️⃣
98+
func foo(bar: Int) -> Int { return 0 }`);
99+
const position = positions["1️⃣"];
100+
101+
const items = await provider.functionCommentCompletion.provideCompletionItems(
102+
document,
103+
position
104+
);
105+
assert.deepEqual(items, [
106+
expectedCompletionItem(` $1
107+
/// - Parameter bar: $2
108+
/// - Returns: $3`),
109+
]);
110+
});
111+
112+
test("Comment completion on a throwing function", async () => {
113+
const { document, positions } = await openDocument(`
114+
/// 1️⃣
115+
func foo() throws {}`);
116+
const position = positions["1️⃣"];
117+
118+
const items = await provider.functionCommentCompletion.provideCompletionItems(
119+
document,
120+
position
121+
);
122+
assert.deepEqual(items, [
123+
expectedCompletionItem(` $1
124+
/// - Throws: $2`),
125+
]);
126+
});
127+
128+
test("Comment completion on single argument throwing function", async () => {
129+
const { document, positions } = await openDocument(`
130+
/// 1️⃣
131+
func foo(bar: Int) throws {}`);
132+
const position = positions["1️⃣"];
133+
134+
const items = await provider.functionCommentCompletion.provideCompletionItems(
135+
document,
136+
position
137+
);
138+
assert.deepEqual(items, [
139+
expectedCompletionItem(` $1
140+
/// - Parameter bar: $2
141+
/// - Throws: $3`),
142+
]);
143+
});
144+
145+
test("Comment completion on complex function", async () => {
146+
const { document, positions } = await openDocument(`
147+
/// 1️⃣
148+
func foo(bar: Int, baz: String) -> Data throws { return Data() }`);
149+
const position = positions["1️⃣"];
150+
151+
const items = await provider.functionCommentCompletion.provideCompletionItems(
152+
document,
153+
position
154+
);
155+
assert.deepEqual(items, [
156+
expectedCompletionItem(
157+
` $1
158+
/// - Parameters:
159+
/// - bar: $2
160+
/// - baz: $3
161+
/// - Returns: $4`
162+
),
163+
]);
164+
});
165+
166+
test("Comment Insertion", async () => {
167+
const { document, positions } = await openDocument(`
168+
/// 1️⃣
169+
func foo(bar: Int, baz: String) -> Data throws { return Data() }`);
170+
const position = positions["1️⃣"];
171+
172+
const editor = await vscode.window.showTextDocument(document);
173+
await provider.insert(editor, position.line + 1);
174+
175+
assert.deepEqual(
176+
editor.document.getText(),
177+
`
178+
/// !
179+
/// !
180+
/// - Parameters:
181+
/// - bar: !
182+
/// - baz: !
183+
/// - Returns: !
184+
func foo(bar: Int, baz: String) -> Data throws { return Data() }`.replace(/!/g, "")
185+
); // ! ensures trailing white space is not trimmed when this file is formatted.
186+
});
187+
188+
function expectedCompletionItem(snippet: string): vscode.CompletionItem {
189+
const expected = new vscode.CompletionItem(
190+
"/// - parameters:",
191+
vscode.CompletionItemKind.Text
192+
);
193+
expected.detail = "Function documentation comment";
194+
expected.insertText = new vscode.SnippetString(snippet);
195+
expected.sortText = undefined;
196+
return expected;
197+
}
198+
199+
async function openDocument(content: string): Promise<{
200+
document: vscode.TextDocument;
201+
positions: { [key: string]: vscode.Position };
202+
}> {
203+
function positionOf(str: string, content: string): vscode.Position | undefined {
204+
const lines = content.split("\n");
205+
const line = lines.findIndex(line => line.includes(str));
206+
if (line === -1) {
207+
return;
208+
}
209+
210+
const column = lines[line].indexOf(str);
211+
return new vscode.Position(line, column);
212+
}
213+
214+
let purgedContent = content;
215+
const needles = ["1️⃣", "2️⃣", "3️⃣", "4️⃣"];
216+
217+
// Find all the needles, capture their positions and then remove them from
218+
// the document before creating a vscode.TextDocument.
219+
const positions = needles.reduce(
220+
(prev, needle) => {
221+
const pos = positionOf(needle, content);
222+
if (pos) {
223+
purgedContent = purgedContent.replace(needle, "");
224+
prev[needle] = pos;
225+
}
226+
return prev;
227+
},
228+
{} as { [key: string]: vscode.Position }
229+
);
230+
231+
const doc = await vscode.workspace.openTextDocument({
232+
language: "swift",
233+
content: purgedContent,
234+
});
235+
236+
// Save the document so we can clean it up when the test finishes
237+
document = doc;
238+
239+
return { document: doc, positions };
240+
}
241+
});

0 commit comments

Comments
 (0)