diff --git a/src/services/completions.ts b/src/services/completions.ts index 28d29136dab89..a3df1474e669e 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -138,6 +138,7 @@ import { isBinaryExpression, isBindingElement, isBindingPattern, + isBlock, isBreakOrContinueStatement, isCallExpression, isCaseBlock, @@ -598,6 +599,7 @@ const enum KeywordCompletionFilters { InterfaceElementKeywords, // Keywords inside interface body ConstructorParameterKeywords, // Keywords at constructor parameter FunctionLikeBodyKeywords, // Keywords at function like body + ArrowFunctionExpressionBodyKeywords, // Keywords valid as expression-position completions in arrow function expression bodies TypeAssertionKeywords, TypeKeywords, TypeKeyword, // Literally just `type` @@ -3982,7 +3984,15 @@ function getCompletionData( } function getGlobalCompletions(): void { - keywordFilters = tryGetFunctionLikeBodyCompletionContainer(contextToken) ? KeywordCompletionFilters.FunctionLikeBodyKeywords : KeywordCompletionFilters.All; + if (isInArrowFunctionExpressionBody(contextToken)) { + keywordFilters = KeywordCompletionFilters.ArrowFunctionExpressionBodyKeywords; + } + else if (tryGetFunctionLikeBodyCompletionContainer(contextToken)) { + keywordFilters = KeywordCompletionFilters.FunctionLikeBodyKeywords; + } + else { + keywordFilters = KeywordCompletionFilters.All; + } // Get all entities in the current scope. completionKind = CompletionKind.Global; @@ -4831,6 +4841,10 @@ function getCompletionData( } } + function isInArrowFunctionExpressionBody(contextToken: Node): boolean { + return !!findAncestor(contextToken, (node: Node) => isArrowFunctionBody(node) && !isBlock(node)); + } + function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement | undefined { if (contextToken) { const parent = contextToken.parent; @@ -5488,6 +5502,8 @@ function getTypescriptKeywordCompletions(keywordFilter: KeywordCompletionFilters || isTypeKeyword(kind) && kind !== SyntaxKind.UndefinedKeyword; case KeywordCompletionFilters.FunctionLikeBodyKeywords: return isFunctionLikeBodyKeyword(kind); + case KeywordCompletionFilters.ArrowFunctionExpressionBodyKeywords: + return isArrowFunctionExpressionBodyKeyword(kind); case KeywordCompletionFilters.ClassElementKeywords: return isClassMemberCompletionKeyword(kind); case KeywordCompletionFilters.InterfaceElementKeywords: @@ -5571,6 +5587,32 @@ function isFunctionLikeBodyKeyword(kind: SyntaxKind) { || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind); } +function isArrowFunctionExpressionBodyKeyword(kind: SyntaxKind): boolean { + switch (kind) { + case SyntaxKind.AsyncKeyword: + case SyntaxKind.AwaitKeyword: + case SyntaxKind.ClassKeyword: + case SyntaxKind.DeleteKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.FunctionKeyword: + case SyntaxKind.ImportKeyword: + case SyntaxKind.InKeyword: + case SyntaxKind.InstanceOfKeyword: + case SyntaxKind.NewKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.ThisKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.TypeOfKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.AsKeyword: + case SyntaxKind.SatisfiesKeyword: + return true; + default: + return false; + } +} + function keywordForNode(node: Node): SyntaxKind { return isIdentifier(node) ? identifierToKeywordKind(node) ?? SyntaxKind.Unknown : node.kind; } diff --git a/tests/baselines/reference/completionsCommentsFunctionExpression.baseline b/tests/baselines/reference/completionsCommentsFunctionExpression.baseline index 0212ef5e5e0a5..6a833114cc811 100644 --- a/tests/baselines/reference/completionsCommentsFunctionExpression.baseline +++ b/tests/baselines/reference/completionsCommentsFunctionExpression.baseline @@ -10,64 +10,41 @@ // | (parameter) b: number // | var lambdaFoo: (a: number, b: number) => number // | var lambddaNoVarComment: (a: number, b: number) => number -// | abstract -// | any // | interface Array // | var Array: ArrayConstructor // | interface ArrayBuffer // | var ArrayBuffer: ArrayBufferConstructor // | as -// | asserts // | async // | await -// | bigint -// | boolean // | interface Boolean // | var Boolean: BooleanConstructor -// | break -// | case -// | catch // | class -// | const -// | continue // | interface DataView // | var DataView: DataViewConstructor // | interface Date // | var Date: DateConstructor -// | debugger -// | declare // | function decodeURI(encodedURI: string): string // | function decodeURIComponent(encodedURIComponent: string): string -// | default // | delete -// | do -// | else // | function encodeURI(uri: string): string // | function encodeURIComponent(uriComponent: string | number | boolean): string -// | enum // | interface Error // | var Error: ErrorConstructor // | function eval(x: string): any // | interface EvalError // | var EvalError: EvalErrorConstructor -// | export -// | extends // | false -// | finally // | interface Float32Array // | var Float32Array: Float32ArrayConstructor // | interface Float64Array // | var Float64Array: Float64ArrayConstructor -// | for // | function // | interface Function // | var Function: FunctionConstructor // | module globalThis -// | if -// | implements // | import // | in -// | infer // | var Infinity: number // | instanceof // | interface Int8Array @@ -76,53 +53,36 @@ // | var Int16Array: Int16ArrayConstructor // | interface Int32Array // | var Int32Array: Int32ArrayConstructor -// | interface // | namespace Intl // | function isFinite(number: number): boolean // | function isNaN(number: number): boolean // | interface JSON // | var JSON: JSON -// | keyof -// | let // | interface Math // | var Math: Math -// | module -// | namespace // | var NaN: number -// | never // | new // | null -// | number // | interface Number // | var Number: NumberConstructor -// | object // | interface Object // | var Object: ObjectConstructor -// | package // | function parseFloat(string: string): number // | function parseInt(string: string, radix?: number): number // | interface RangeError // | var RangeError: RangeErrorConstructor -// | readonly // | interface ReferenceError // | var ReferenceError: ReferenceErrorConstructor // | interface RegExp // | var RegExp: RegExpConstructor -// | return // | satisfies -// | string // | interface String // | var String: StringConstructor // | super -// | switch -// | symbol // | interface SyntaxError // | var SyntaxError: SyntaxErrorConstructor // | this -// | throw // | true -// | try -// | type // | interface TypeError // | var TypeError: TypeErrorConstructor // | typeof @@ -135,16 +95,9 @@ // | interface Uint32Array // | var Uint32Array: Uint32ArrayConstructor // | var undefined -// | unique -// | unknown // | interface URIError // | var URIError: URIErrorConstructor -// | using -// | var // | void -// | while -// | with -// | yield // | function escape(string: string): string // | function unescape(string: string): string // | ---------------------------------------------------------------------- @@ -1107,30 +1060,6 @@ } ] }, - { - "name": "abstract", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "abstract", - "kind": "keyword" - } - ] - }, - { - "name": "any", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "any", - "kind": "keyword" - } - ] - }, { "name": "Array", "kind": "var", @@ -1258,18 +1187,6 @@ } ] }, - { - "name": "asserts", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "asserts", - "kind": "keyword" - } - ] - }, { "name": "async", "kind": "keyword", @@ -1294,30 +1211,6 @@ } ] }, - { - "name": "bigint", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "bigint", - "kind": "keyword" - } - ] - }, - { - "name": "boolean", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "boolean", - "kind": "keyword" - } - ] - }, { "name": "Boolean", "kind": "var", @@ -1367,42 +1260,6 @@ ], "documentation": [] }, - { - "name": "break", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "break", - "kind": "keyword" - } - ] - }, - { - "name": "case", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "case", - "kind": "keyword" - } - ] - }, - { - "name": "catch", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "catch", - "kind": "keyword" - } - ] - }, { "name": "class", "kind": "keyword", @@ -1415,30 +1272,6 @@ } ] }, - { - "name": "const", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "const", - "kind": "keyword" - } - ] - }, - { - "name": "continue", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "continue", - "kind": "keyword" - } - ] - }, { "name": "DataView", "kind": "var", @@ -1586,30 +1419,6 @@ } ] }, - { - "name": "debugger", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "debugger", - "kind": "keyword" - } - ] - }, - { - "name": "declare", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "declare", - "kind": "keyword" - } - ] - }, { "name": "decodeURI", "kind": "function", @@ -1772,18 +1581,6 @@ } ] }, - { - "name": "default", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "default", - "kind": "keyword" - } - ] - }, { "name": "delete", "kind": "keyword", @@ -1796,30 +1593,6 @@ } ] }, - { - "name": "do", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "do", - "kind": "keyword" - } - ] - }, - { - "name": "else", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "else", - "kind": "keyword" - } - ] - }, { "name": "encodeURI", "kind": "function", @@ -2014,18 +1787,6 @@ } ] }, - { - "name": "enum", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "enum", - "kind": "keyword" - } - ] - }, { "name": "Error", "kind": "var", @@ -2206,61 +1967,25 @@ "documentation": [] }, { - "name": "export", + "name": "false", "kind": "keyword", "kindModifiers": "", "sortText": "15", "displayParts": [ { - "text": "export", + "text": "false", "kind": "keyword" } ] }, { - "name": "extends", - "kind": "keyword", - "kindModifiers": "", + "name": "Float32Array", + "kind": "var", + "kindModifiers": "declare", "sortText": "15", "displayParts": [ { - "text": "extends", - "kind": "keyword" - } - ] - }, - { - "name": "false", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "false", - "kind": "keyword" - } - ] - }, - { - "name": "finally", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "finally", - "kind": "keyword" - } - ] - }, - { - "name": "Float32Array", - "kind": "var", - "kindModifiers": "declare", - "sortText": "15", - "displayParts": [ - { - "text": "interface", + "text": "interface", "kind": "keyword" }, { @@ -2449,18 +2174,6 @@ } ] }, - { - "name": "for", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "for", - "kind": "keyword" - } - ] - }, { "name": "function", "kind": "keyword", @@ -2548,30 +2261,6 @@ ], "documentation": [] }, - { - "name": "if", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "if", - "kind": "keyword" - } - ] - }, - { - "name": "implements", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "implements", - "kind": "keyword" - } - ] - }, { "name": "import", "kind": "keyword", @@ -2596,18 +2285,6 @@ } ] }, - { - "name": "infer", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "infer", - "kind": "keyword" - } - ] - }, { "name": "Infinity", "kind": "var", @@ -2947,18 +2624,6 @@ } ] }, - { - "name": "interface", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "interface", - "kind": "keyword" - } - ] - }, { "name": "Intl", "kind": "module", @@ -3196,30 +2861,6 @@ } ] }, - { - "name": "keyof", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "keyof", - "kind": "keyword" - } - ] - }, - { - "name": "let", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "let", - "kind": "keyword" - } - ] - }, { "name": "Math", "kind": "var", @@ -3274,30 +2915,6 @@ } ] }, - { - "name": "module", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "module", - "kind": "keyword" - } - ] - }, - { - "name": "namespace", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "namespace", - "kind": "keyword" - } - ] - }, { "name": "NaN", "kind": "var", @@ -3331,18 +2948,6 @@ ], "documentation": [] }, - { - "name": "never", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "never", - "kind": "keyword" - } - ] - }, { "name": "new", "kind": "keyword", @@ -3367,18 +2972,6 @@ } ] }, - { - "name": "number", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "number", - "kind": "keyword" - } - ] - }, { "name": "Number", "kind": "var", @@ -3433,18 +3026,6 @@ } ] }, - { - "name": "object", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "object", - "kind": "keyword" - } - ] - }, { "name": "Object", "kind": "var", @@ -3499,18 +3080,6 @@ } ] }, - { - "name": "package", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "package", - "kind": "keyword" - } - ] - }, { "name": "parseFloat", "kind": "function", @@ -3767,18 +3336,6 @@ ], "documentation": [] }, - { - "name": "readonly", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "readonly", - "kind": "keyword" - } - ] - }, { "name": "ReferenceError", "kind": "var", @@ -3877,18 +3434,6 @@ ], "documentation": [] }, - { - "name": "return", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "return", - "kind": "keyword" - } - ] - }, { "name": "satisfies", "kind": "keyword", @@ -3901,18 +3446,6 @@ } ] }, - { - "name": "string", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "string", - "kind": "keyword" - } - ] - }, { "name": "String", "kind": "var", @@ -3979,30 +3512,6 @@ } ] }, - { - "name": "switch", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "switch", - "kind": "keyword" - } - ] - }, - { - "name": "symbol", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "symbol", - "kind": "keyword" - } - ] - }, { "name": "SyntaxError", "kind": "var", @@ -4064,18 +3573,6 @@ } ] }, - { - "name": "throw", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "throw", - "kind": "keyword" - } - ] - }, { "name": "true", "kind": "keyword", @@ -4088,30 +3585,6 @@ } ] }, - { - "name": "try", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "try", - "kind": "keyword" - } - ] - }, - { - "name": "type", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "type", - "kind": "keyword" - } - ] - }, { "name": "TypeError", "kind": "var", @@ -4586,30 +4059,6 @@ ], "documentation": [] }, - { - "name": "unique", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "unique", - "kind": "keyword" - } - ] - }, - { - "name": "unknown", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "unknown", - "kind": "keyword" - } - ] - }, { "name": "URIError", "kind": "var", @@ -4659,30 +4108,6 @@ ], "documentation": [] }, - { - "name": "using", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "using", - "kind": "keyword" - } - ] - }, - { - "name": "var", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "var", - "kind": "keyword" - } - ] - }, { "name": "void", "kind": "keyword", @@ -4695,42 +4120,6 @@ } ] }, - { - "name": "while", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "while", - "kind": "keyword" - } - ] - }, - { - "name": "with", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "with", - "kind": "keyword" - } - ] - }, - { - "name": "yield", - "kind": "keyword", - "kindModifiers": "", - "sortText": "15", - "displayParts": [ - { - "text": "yield", - "kind": "keyword" - } - ] - }, { "name": "escape", "kind": "function", diff --git a/tests/cases/fourslash/completionListAtEndOfWordInArrowFunction02.ts b/tests/cases/fourslash/completionListAtEndOfWordInArrowFunction02.ts index 690f01ab79375..0b87f35506d1a 100644 --- a/tests/cases/fourslash/completionListAtEndOfWordInArrowFunction02.ts +++ b/tests/cases/fourslash/completionListAtEndOfWordInArrowFunction02.ts @@ -4,10 +4,9 @@ verify.completions({ marker: "1", - // TODO: should not include 'default' keyword at an expression location includes: [ "d", "defaultIsAnInvalidParameterName", - { name: "default", sortText: completion.SortText.GlobalsOrKeywords } - ] + ], + excludes: ["default"], }); diff --git a/tests/cases/fourslash/completionListAtEndOfWordInArrowFunction03.ts b/tests/cases/fourslash/completionListAtEndOfWordInArrowFunction03.ts index 7aea1a726e28b..21e6340b4c812 100644 --- a/tests/cases/fourslash/completionListAtEndOfWordInArrowFunction03.ts +++ b/tests/cases/fourslash/completionListAtEndOfWordInArrowFunction03.ts @@ -6,7 +6,6 @@ verify.completions({ marker: "1", includes: [ "defaultIsAnInvalidParameterName", - // This should probably stop working in the future. - { name: "default", text: "default", kind: "keyword", sortText: completion.SortText.GlobalsOrKeywords }, ], + excludes: ["default"], }); diff --git a/tests/cases/fourslash/completionsArrowFunctionExpressionBody.ts b/tests/cases/fourslash/completionsArrowFunctionExpressionBody.ts new file mode 100644 index 0000000000000..b8ba0ebfbb6c0 --- /dev/null +++ b/tests/cases/fourslash/completionsArrowFunctionExpressionBody.ts @@ -0,0 +1,75 @@ +/// + +// Verify that statement-only keywords are excluded from completions in arrow function expression bodies, +// while expression-valid keywords remain available. + +// @Filename: /a.ts +////const f1 = (x: number) => /*expr1*/x; +////const f2 = async (x: number) => /*expr2*/x; +////const f3 = (x: number) => { return /*block1*/x; }; + +goTo.marker("expr1"); +// Statement-only keywords must not appear in expression-body completions +verify.completions({ + excludes: [ + "break", + "case", + "catch", + "continue", + "debugger", + "default", + "do", + "else", + "export", + "finally", + "for", + "if", + "return", + "switch", + "throw", + "try", + "var", + "while", + "with", + "yield", + "declare", + "using", + "type", + ], +}); +// Expression-valid keywords must still appear +verify.completions({ + includes: [ + { name: "new", sortText: completion.SortText.GlobalsOrKeywords }, + { name: "typeof", sortText: completion.SortText.GlobalsOrKeywords }, + { name: "void", sortText: completion.SortText.GlobalsOrKeywords }, + { name: "false", sortText: completion.SortText.GlobalsOrKeywords }, + { name: "true", sortText: completion.SortText.GlobalsOrKeywords }, + { name: "null", sortText: completion.SortText.GlobalsOrKeywords }, + { name: "function", sortText: completion.SortText.GlobalsOrKeywords }, + { name: "class", sortText: completion.SortText.GlobalsOrKeywords }, + { name: "delete", sortText: completion.SortText.GlobalsOrKeywords }, + ], +}); + +goTo.marker("expr2"); +// async arrow function expression body: await should be available +verify.completions({ + includes: [{ name: "await", sortText: completion.SortText.GlobalsOrKeywords }], + excludes: [ + "default", + "return", + "for", + "while", + ], +}); + +goTo.marker("block1"); +// In a block body, statement keywords ARE valid completions +verify.completions({ + includes: [ + { name: "return", sortText: completion.SortText.GlobalsOrKeywords }, + { name: "for", sortText: completion.SortText.GlobalsOrKeywords }, + { name: "if", sortText: completion.SortText.GlobalsOrKeywords }, + ], +});