diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index 439f7595798..0e0acad451e 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -455,8 +455,11 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider if (detail && item.useCodeSnippet) { const shouldCompleteFunction = await this.isValidFunctionCompletionContext(filepath, item.position, token); if (shouldCompleteFunction) { - item.insertText = snippetForFunctionCall(item, detail.displayParts); - commands.push({ title: 'triggerParameterHints', command: 'editor.action.triggerParameterHints' }); + const { snippet, parameterCount } = snippetForFunctionCall(item, detail.displayParts); + item.insertText = snippet; + if (parameterCount > 0) { + commands.push({ title: 'triggerParameterHints', command: 'editor.action.triggerParameterHints' }); + } } } diff --git a/extensions/typescript-language-features/src/protocol.const.ts b/extensions/typescript-language-features/src/protocol.const.ts index 2baece66a72..efded538f77 100644 --- a/extensions/typescript-language-features/src/protocol.const.ts +++ b/extensions/typescript-language-features/src/protocol.const.ts @@ -66,6 +66,7 @@ export class DisplayPartKind { public static readonly functionName = 'functionName'; public static readonly methodName = 'methodName'; public static readonly parameterName = 'parameterName'; + public static readonly propertyName = 'propertyName'; public static readonly punctuation = 'punctuation'; public static readonly text = 'text'; } \ No newline at end of file diff --git a/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts b/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts index e8dfeb8ba16..f8707491b9d 100644 --- a/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts +++ b/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts @@ -14,7 +14,7 @@ suite('typescript function call snippets', () => { snippetForFunctionCall( { label: 'abc', }, [] - ).value, + ).snippet.value, 'abc()$0'); }); @@ -23,7 +23,7 @@ suite('typescript function call snippets', () => { snippetForFunctionCall( { label: 'abc', insertText: 'def' }, [] - ).value, + ).snippet.value, 'def()$0'); }); @@ -32,7 +32,7 @@ suite('typescript function call snippets', () => { snippetForFunctionCall( { label: 'abc', insertText: new vscode.SnippetString('bla()$0') }, [] - ).value, + ).snippet.value, 'bla()$0'); }); @@ -41,7 +41,7 @@ suite('typescript function call snippets', () => { snippetForFunctionCall( { label: 'abc', insertText: new vscode.SnippetString('bla()$0') }, [] - ).value, + ).snippet.value, 'bla()$0'); }); @@ -50,7 +50,7 @@ suite('typescript function call snippets', () => { snippetForFunctionCall( { label: 'activate' }, [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "activate", "kind": "text" }, { "text": "(", "kind": "punctuation" }, { "text": "context", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "vscode", "kind": "aliasName" }, { "text": ".", "kind": "punctuation" }, { "text": "ExtensionContext", "kind": "interfaceName" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] - ).value, + ).snippet.value, 'activate(${1:context})$0'); }); @@ -59,7 +59,7 @@ suite('typescript function call snippets', () => { snippetForFunctionCall( { label: 'foo' }, [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foo", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "a", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "b", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "c", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "boolean", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] - ).value, + ).snippet.value, 'foo(${1:a}, ${2:b}, ${3:c})$0'); }); @@ -68,7 +68,7 @@ suite('typescript function call snippets', () => { snippetForFunctionCall( { label: 'foo' }, [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foo", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "a", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "string", "kind": "keyword" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "...", "kind": "punctuation" }, { "text": "rest", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "any", "kind": "keyword" }, { "text": "[", "kind": "punctuation" }, { "text": "]", "kind": "punctuation" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] - ).value, + ).snippet.value, 'foo(${1:a}$2)$0'); }); @@ -77,7 +77,7 @@ suite('typescript function call snippets', () => { snippetForFunctionCall( { label: 'foo' }, [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foo", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "a", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "(", "kind": "punctuation" }, { "text": "x", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "=>", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "{", "kind": "punctuation" }, { "text": "\n", "kind": "lineBreak" }, { "text": " ", "kind": "space" }, { "text": "f", "kind": "propertyName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "(", "kind": "punctuation" }, { "text": ")", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "=>", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }, { "text": ";", "kind": "punctuation" }, { "text": "\n", "kind": "lineBreak" }, { "text": "}", "kind": "punctuation" }, { "text": ",", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "b", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "{", "kind": "punctuation" }, { "text": "\n", "kind": "lineBreak" }, { "text": " ", "kind": "space" }, { "text": "f", "kind": "propertyName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "(", "kind": "punctuation" }, { "text": ")", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "=>", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }, { "text": ";", "kind": "punctuation" }, { "text": "\n", "kind": "lineBreak" }, { "text": "}", "kind": "punctuation" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] - ).value, + ).snippet.value, 'foo(${1:a}, ${2:b})$0'); }); @@ -86,7 +86,7 @@ suite('typescript function call snippets', () => { snippetForFunctionCall( { label: 'foo' }, [{ "text": "function", "kind": "keyword" }, { "text": " ", "kind": "space" }, { "text": "foo", "kind": "functionName" }, { "text": "(", "kind": "punctuation" }, { "text": "a", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "{", "kind": "punctuation" }, { "text": "\n", "kind": "lineBreak" }, { "text": " ", "kind": "space" }, { "text": "f", "kind": "propertyName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "(", "kind": "punctuation" }, { "text": "b", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "=>", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": ";", "kind": "punctuation" }, { "text": "\n", "kind": "lineBreak" }, { "text": "}", "kind": "punctuation" }] - ).value, + ).snippet.value, 'foo(${1:a})$0'); }); @@ -95,7 +95,16 @@ suite('typescript function call snippets', () => { 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, + ).snippet.value, 'foo(${1:searchElement}$2)$0'); }); + + test('Should complete property names', async () => { + assert.strictEqual( + snippetForFunctionCall( + { label: 'methoda' }, + [{ "text": "(", "kind": "punctuation" }, { "text": "method", "kind": "text" }, { "text": ")", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "methoda", "kind": "propertyName" }, { "text": "(", "kind": "punctuation" }, { "text": "x", "kind": "parameterName" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "number", "kind": "keyword" }, { "text": ")", "kind": "punctuation" }, { "text": ":", "kind": "punctuation" }, { "text": " ", "kind": "space" }, { "text": "void", "kind": "keyword" }] + ).snippet.value, + 'methoda(${1:x})$0'); + }); }); diff --git a/extensions/typescript-language-features/src/utils/snippetForFunctionCall.ts b/extensions/typescript-language-features/src/utils/snippetForFunctionCall.ts index 7167b8b6429..37853b0d4a9 100644 --- a/extensions/typescript-language-features/src/utils/snippetForFunctionCall.ts +++ b/extensions/typescript-language-features/src/utils/snippetForFunctionCall.ts @@ -10,9 +10,9 @@ import * as PConst from '../protocol.const'; export function snippetForFunctionCall( item: { insertText?: string | vscode.SnippetString; label: string; }, displayParts: ReadonlyArray -): vscode.SnippetString { +): { snippet: vscode.SnippetString, parameterCount: number } { if (item.insertText && typeof item.insertText !== 'string') { - return item.insertText; + return { snippet: item.insertText, parameterCount: 0 }; } const parameterListParts = getParameterListParts(displayParts, item.label); @@ -23,7 +23,7 @@ export function snippetForFunctionCall( } snippet.appendText(')'); snippet.appendTabstop(0); - return snippet; + return { snippet, parameterCount: parameterListParts.parts.length + (parameterListParts.hasOptionalParameters ? 1 : 0) }; } function appendJoinedPlaceholders( @@ -59,6 +59,7 @@ function getParameterListParts( case PConst.DisplayPartKind.methodName: case PConst.DisplayPartKind.functionName: case PConst.DisplayPartKind.text: + case PConst.DisplayPartKind.propertyName: if (part.text === label && parenCount === 0) { isInMethod = true; }