Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/editor/CommentCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ class DocCommentCompletionProvider implements vscode.CompletionItemProvider {
position: vscode.Position
): Promise<vscode.CompletionItem[] | undefined> {
// Is line a '///' comment
if (position.line === 0 || isLineComment(document, position.line - 1) === false) {
if (
position.line === 0 ||
document.isClosed ||
isLineComment(document, position.line - 1) === false
) {
return undefined;
}
await this.continueExistingDocCommentBlock(document, position);
Expand Down
357 changes: 335 additions & 22 deletions test/integration-tests/editor/CommentCompletion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,28 +186,6 @@ suite("CommentCompletion Test Suite", () => {
]);
});

test("Comment Insertion", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
func foo(bar: Int, baz: String) -> Data throws { return Data() }`);
const position = positions["1️⃣"];

const editor = await vscode.window.showTextDocument(document);
await provider.insert(editor, position.line + 1);

assert.deepEqual(
editor.document.getText(),
`
/// !
/// !
/// - Parameters:
/// - bar: !
/// - baz: !
/// - Returns: !
func foo(bar: Int, baz: String) -> Data throws { return Data() }`.replace(/!/g, "")
); // ! ensures trailing white space is not trimmed when this file is formatted.
});

test("Comment completion on function with default parameter using #function", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
Expand Down Expand Up @@ -256,9 +234,209 @@ suite("CommentCompletion Test Suite", () => {
/// - Returns: $3`),
]);
});

test("Comment insertion", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
func foo(bar: Int, baz: String) -> Data throws { return Data() }`);
const position = positions["1️⃣"];

const editor = await vscode.window.showTextDocument(document);
await provider.insert(editor, position.line + 1);

assert.deepEqual(
editor.document.getText(),
`
/// !
/// !
/// - Parameters:
/// - bar: !
/// - baz: !
/// - Returns: !
func foo(bar: Int, baz: String) -> Data throws { return Data() }`.replace(/!/g, "")
); // ! ensures trailing white space is not trimmed when this file is formatted.
});

suite("Function Comment Completion - Edge Cases", () => {
test("Comment completion on generic function", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
func foo<T>(bar: T) -> T { return bar }`);
const position = positions["1️⃣"];

const items = await provider.functionCommentCompletion.provideCompletionItems(
document,
position
);
assert.deepEqual(items, [
expectedCompletionItem(` $1
/// - Parameter bar: $2
/// - Returns: $3`),
]);
});

test("Comment completion on generic function with multiple type parameters", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
func foo<T, U>(bar: T, baz: U) -> T { return bar }`);
const position = positions["1️⃣"];

const items = await provider.functionCommentCompletion.provideCompletionItems(
document,
position
);
assert.deepEqual(items, [
expectedCompletionItem(` $1
/// - Parameters:
/// - bar: $2
/// - baz: $3
/// - Returns: $4`),
]);
});

test("Comment completion on generic function with constraints", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
func foo<T: Equatable>(bar: T) -> T { return bar }`);
const position = positions["1️⃣"];

const items = await provider.functionCommentCompletion.provideCompletionItems(
document,
position
);
assert.deepEqual(items, [
expectedCompletionItem(` $1
/// - Parameter bar: $2
/// - Returns: $3`),
]);
});

test("Comment completion on malformed function - no function name", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
func (bar: Int) {}`);
const position = positions["1️⃣"];

const items = await provider.functionCommentCompletion.provideCompletionItems(
document,
position
);
assert.deepEqual(items, [
expectedCompletionItem(` $1
/// - Parameter bar: $2`),
]);
});

test("Comment completion on malformed function - no parameter name", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
func foo(: Int) {}`);
const position = positions["1️⃣"];

const items = await provider.functionCommentCompletion.provideCompletionItems(
document,
position
);
assert.deepEqual(items, [
expectedCompletionItem(` $1
/// - Parameter : $2`),
]);
});

test("Comment completion on malformed function - unclosed parameter list", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
func foo(bar: Int`);
const position = positions["1️⃣"];

const items = await provider.functionCommentCompletion.provideCompletionItems(
document,
position
);
assert.deepEqual(items, undefined);
});

test("Comment completion on malformed generic function - unclosed generic list", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
func foo<T(bar: Int) {}`);
const position = positions["1️⃣"];

const items = await provider.functionCommentCompletion.provideCompletionItems(
document,
position
);
assert.deepEqual(items, undefined);
});

test("Comment completion on init method", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
init(bar: Int) {}`);
const position = positions["1️⃣"];

const items = await provider.functionCommentCompletion.provideCompletionItems(
document,
position
);
assert.deepEqual(items, [
expectedCompletionItem(` $1
/// - Parameter bar: $2`),
]);
});

test("Comment completion on throwing init method", async () => {
const { document, positions } = await openDocument(`
/// 1️⃣
init(bar: Int) throws {}`);
const position = positions["1️⃣"];

const items = await provider.functionCommentCompletion.provideCompletionItems(
document,
position
);
assert.deepEqual(items, [
expectedCompletionItem(` $1
/// - Parameter bar: $2
/// - Throws: $3`),
]);
});
});
});

suite("Document Comment Completion", () => {
test("Should not provide completions on first line", async () => {
const { document, positions } = await openDocument(`1️⃣
public func foo() {}`);

const position = positions["1️⃣"];
const items = await provider.docCommentCompletion.provideCompletionItems(
document,
position
);

assert.strictEqual(items, undefined, "Should not provide completions on first line");
});

test("Should not provide completions when previous line is not a comment", async () => {
const { document, positions } = await openDocument(`
public func bar() {}
1️⃣
public func foo() {}`);

const position = positions["1️⃣"];
const items = await provider.docCommentCompletion.provideCompletionItems(
document,
position
);

assert.strictEqual(
items,
undefined,
"Should not provide completions when previous line is not a comment"
);
});

test("Should continue a documentation comment block on new line", async () => {
const { document, positions } = await openDocument(`
/// aaa
Expand Down Expand Up @@ -304,6 +482,141 @@ suite("CommentCompletion Test Suite", () => {

assert.deepEqual(documentText, originalText, "Document text should not change");
});

test("Should handle case when no active text editor", async () => {
const { document, positions } = await openDocument(`
/// aaa
1️⃣
public func foo() {}`);

const position = positions["1️⃣"];

// Close all editors to simulate no active text editor
await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS);

// This should not throw an error
const items = await provider.docCommentCompletion.provideCompletionItems(
document,
position
);

assert.equal(
items,
undefined,
"Should not provide completions when no active text editor"
);
});

test("Should specifically test continueExistingDocCommentBlock with no active editor", async () => {
const { document, positions } = await openDocument(`
/// aaa
1️⃣
public func foo() {}`);

const position = positions["1️⃣"];

// Close all editors to simulate no active text editor
await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS);

// Store original activeTextEditor property
const originalActiveTextEditor = Object.getOwnPropertyDescriptor(
vscode.window,
"activeTextEditor"
);

// Mock the activeTextEditor to be null for the specific method call
Object.defineProperty(vscode.window, "activeTextEditor", {
get: () => null,
configurable: true,
});

try {
// Call the method directly to ensure the branch is covered
await provider.docCommentCompletion["continueExistingDocCommentBlock"](
document,
position
);

// If we get here, the method didn't throw an error, which is what we want
assert.ok(true, "Method should not throw when there's no active editor");
} finally {
// Restore the original activeTextEditor property
if (originalActiveTextEditor) {
Object.defineProperty(
vscode.window,
"activeTextEditor",
originalActiveTextEditor
);
}
}
});

test("Should handle when previous line has // but not ///", async () => {
const { document, positions } = await openDocument(`
// aaa
1️⃣
public func foo() {}`);

const position = positions["1️⃣"];
const items = await provider.docCommentCompletion.provideCompletionItems(
document,
position
);

assert.strictEqual(
items,
undefined,
"Should not provide completions when previous line is not a doc comment"
);
});

test("Should handle when line has content after //", async () => {
const { document, positions } = await openDocument(`
/// aaa
1️⃣// bbb
public func foo() {}`);

const position = positions["1️⃣"];

// Show the document to ensure there's an active editor
await vscode.window.showTextDocument(document);

const items = await provider.docCommentCompletion.provideCompletionItems(
document,
position
);

// Check that the line was modified
const lineText = document.lineAt(position.line).text;
assert.strictEqual(lineText.trim(), "/// bbb", "Should convert // to ///");

assert.ok(items, "Should provide completions");
assert.strictEqual(items.length, 1, "Should provide one completion");
});

test("Should handle when line has no match for comment continuation", async () => {
const { document, positions } = await openDocument(`
/// aaa
1️⃣let x = 1
public func foo() {}`);

const position = positions["1️⃣"];

// Show the document to ensure there's an active editor
await vscode.window.showTextDocument(document);

const originalText = document.getText();
const items = await provider.docCommentCompletion.provideCompletionItems(
document,
position
);

// Document should not be modified
assert.strictEqual(document.getText(), originalText, "Document should not be modified");

assert.ok(items, "Should provide completions");
assert.strictEqual(items.length, 1, "Should provide one completion");
});
});

function expectedCompletionItem(snippet: string): vscode.CompletionItem {
Expand Down