diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index 01dc6bb9d528f1dabdf843824db7be08c119481f..854f2fabfa30f9f85ebfea2ea0c2cecb1c192a83 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -632,7 +632,6 @@ export function snippetForFunctionCall( let hasAddedParameters = false; const snippet = new vscode.SnippetString(); - const methodName = displayParts.find(part => part.kind === 'methodName'); if (item.insertText) { if (typeof item.insertText === 'string') { snippet.appendText(item.insertText); @@ -640,17 +639,18 @@ export function snippetForFunctionCall( return item.insertText; } } else { - snippet.appendText((methodName && methodName.text) || item.label); + snippet.appendText(item.label); } snippet.appendText('('); + const functionSignatureDisplayParts = getFunctionSignatureDisplayParts(displayParts, item.label); let parenCount = 0; let i = 0; - for (; i < displayParts.length; ++i) { - const part = displayParts[i]; + for (; i < functionSignatureDisplayParts.length; ++i) { + const part = functionSignatureDisplayParts[i]; // Only take top level paren names if (part.kind === 'parameterName' && parenCount === 1) { - const next = displayParts[i + 1]; + const next = functionSignatureDisplayParts[i + 1]; // Skip optional parameters const nameIsFollowedByOptionalIndicator = next && next.text === '?'; if (!nameIsFollowedByOptionalIndicator) { @@ -684,6 +684,43 @@ export function snippetForFunctionCall( return snippet; } +function getFunctionSignatureDisplayParts( + displayParts: ReadonlyArray, + label: string, +): ReadonlyArray { + let start: number | undefined; + let end: number | undefined; + + let index = 0; + let nestingLevel = 0; + for (; index < displayParts.length; ++index) { + const part = displayParts[index]; + if (part.kind === 'methodName' || part.kind === 'functionName' || part.kind === 'text' && part.text === label) { + if (nestingLevel === 0) { + start = index; + } + } else if (part.kind === 'punctuation') { + switch (part.text) { + case '(': + ++nestingLevel; + break; + + case ')': + --nestingLevel; + if (nestingLevel <= 0) { + end = index; + } + + break; + } + } + } + if (typeof start === 'number' && typeof end === 'number') { + return displayParts.slice(start, end); + } + return []; +} + function shouldExcludeCompletionEntry( element: Proto.CompletionEntry, completionConfiguration: CompletionConfiguration diff --git a/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts b/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts index 069e96871c80af11526d8c0a02db16f54ba809a2..257b0890c26a3f8c4871346c8f97417ede1eedb4 100644 --- a/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts +++ b/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts @@ -81,7 +81,7 @@ suite('typescript function call snippets', () => { 'foo(${1:a}, ${2:b})$0'); }); - test('Should skip over return type', async () => { + test('Should skip over return type while extracting parameters', async () => { assert.strictEqual( snippetForFunctionCall( { label: 'foo' }, @@ -89,4 +89,13 @@ suite('typescript function call snippets', () => { ).value, 'foo(${1:a})$0'); }); + + test('Should skip over prefix type while extracting parameters', async () => { + assert.strictEqual( + snippetForFunctionCall( + { label: 'foo' }, + [{ "text": "(", "kind": "punctuation" }, { "text": "method", "kind": "text" }, { "text": ")", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "Array", "kind": "localName" }, { "text": "<", "kind": "punctuation" }, { "text": "{", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "dispose", "kind": "methodName" }, { "text": "(", "kind": "punctuation" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "any", "kind": "keyword" }, { "text": ";", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "}", "kind": "punctuation" }, { "text": ">", "kind": "punctuation" }, { "text": ".", "kind": "punctuation" }, { "text": "foo", "kind": "methodName" }, { "text": "(", "kind": "punctuation" }, { "text": "searchElement", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "{", "kind": "punctuation" }, { "text": "\n", "kind": "lineBreak" }, { "text": " ", "kind": "space" }, { "text": "dispose", "kind": "methodName" }, { "text": "(", "kind": "punctuation" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "any", "kind": "keyword" }, { "text": ";", "kind": "punctuation" }, { "text": "\n", "kind": "lineBreak" }, { "text": "}", "kind": "punctuation" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "fromIndex", "kind": "parameterName" }, { "text": "?", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }] + ).value, + 'foo(${1:searchElement}$2)$0'); + }); });