From 41913b76e2d3e2aad2089394d3260c931bb89dad Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 23 Feb 2022 12:30:29 -0800 Subject: [PATCH 01/36] skeleton of new feature --- src/services/completions.ts | 89 +++++++++++++------ ...ompletionsObjectLiteralFunctionProperty.ts | 48 ++++++++++ 2 files changed, 112 insertions(+), 25 deletions(-) create mode 100644 tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index ca10ba189b5a6..b93b490de2f38 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -775,6 +775,10 @@ namespace ts.Completions { } } + if (isObjectLiteralFunctionPropertyCompletion(symbol, location, contextToken, program.getTypeChecker())) { + getEntryForObjectLiteralFunctionCompletion(symbol, location); + } + if (isJsxIdentifierExpected && !isRightOfOpenTag && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { let useBraces = preferences.jsxAttributeCompletionStyle === "braces"; const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location); @@ -1059,6 +1063,41 @@ namespace ts.Completions { return undefined; } + function isObjectLiteralFunctionPropertyCompletion(symbol: Symbol, location: Node, contextToken: Node | undefined, checker: TypeChecker): boolean { + // >> TODO: check completion kind is memberlike + // TODO: support JS files. + if (isInJSFile(location)) { + return false; + } + + /* + For an object type + `type Foo = { + bar(x: number): void; + foo: (x: string) => string; + }`, + `bar` will have symbol flag `Method`, + `foo` will have symbol flag `Property`. + */ + if (!(symbol.flags & (SymbolFlags.Property | SymbolFlags.Method))) { + return false; + } + + const type = checker.getTypeOfSymbol(symbol); + // We ignore non-function properties. + if (!type.getCallSignatures().length) { + return false; + } + + // Check if we are in a position for object literal completion. + const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); + return !!objectLikeContainer && isObjectLiteralExpression(objectLikeContainer); + } + + function getEntryForObjectLiteralFunctionCompletion(_symbol: Symbol, _location: Node) { + // >> TODO: implement this + }; + function createSnippetPrinter( printerOptions: PrinterOptions, ) { @@ -2963,31 +3002,6 @@ namespace ts.Completions { return GlobalsSearch.Success; } - /** - * Returns the immediate owning object literal or binding pattern of a context token, - * on the condition that one exists and that the context implies completion should be given. - */ - function tryGetObjectLikeCompletionContainer(contextToken: Node): ObjectLiteralExpression | ObjectBindingPattern | undefined { - if (contextToken) { - const { parent } = contextToken; - switch (contextToken.kind) { - case SyntaxKind.OpenBraceToken: // const x = { | - case SyntaxKind.CommaToken: // const x = { a: 0, | - if (isObjectLiteralExpression(parent) || isObjectBindingPattern(parent)) { - return parent; - } - break; - case SyntaxKind.AsteriskToken: - return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined; - case SyntaxKind.Identifier: - return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) - ? contextToken.parent.parent : undefined; - } - } - - return undefined; - } - function isConstructorParameterCompletion(node: Node): boolean { return !!node.parent && isParameter(node.parent) && isConstructorDeclaration(node.parent.parent) && (isParameterPropertyModifier(node.kind) || isDeclarationName(node)); @@ -3463,6 +3477,31 @@ namespace ts.Completions { } } + /** + * Returns the immediate owning object literal or binding pattern of a context token, + * on the condition that one exists and that the context implies completion should be given. + */ + function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): ObjectLiteralExpression | ObjectBindingPattern | undefined { + if (contextToken) { + const { parent } = contextToken; + switch (contextToken.kind) { + case SyntaxKind.OpenBraceToken: // const x = { | + case SyntaxKind.CommaToken: // const x = { a: 0, | + if (isObjectLiteralExpression(parent) || isObjectBindingPattern(parent)) { + return parent; + } + break; + case SyntaxKind.AsteriskToken: + return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined; + case SyntaxKind.Identifier: + return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) + ? contextToken.parent.parent : undefined; + } + } + + return undefined; + } + function getRelevantTokens(position: number, sourceFile: SourceFile): { contextToken: Node, previousToken: Node } | { contextToken: undefined, previousToken: undefined } { const previousToken = findPrecedingToken(position, sourceFile); if (previousToken && position <= previousToken.end && (isMemberName(previousToken) || isKeyword(previousToken.kind))) { diff --git a/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts b/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts new file mode 100644 index 0000000000000..ec7e4fa819103 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts @@ -0,0 +1,48 @@ +/// + +// @newline: LF +// @Filename: a.ts +// Case: Concrete class implements abstract method +////interface IFoo { +//// bar(x: number): void; +////} +//// +////const obj: IFoo = { +//// /*a*/ +////} +////type Foo = { +//// bar(x: number): void; +//// foo: (x: string) => string; +////} +//// +////const f: Foo = { +//// /*b*/ +////} +////const g: Foo = { +//// bar: (x: number) => { return; }, +//// /*c*/ +////} + + + +verify.completions({ + marker: "c", + isNewIdentifierLocation: true, + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + // includeCompletionsWithClassMemberSnippets: true, + }, + includes: [ + { + name: "bar", + sortText: completion.SortText.LocationPriority, + replacementSpan: { + fileName: "", + pos: 0, + end: 0, + }, + insertText: "bar(x: number): void {\n}", + } + ], +}); \ No newline at end of file From d5038cecc7322a54d3973e272d3baa529dd7e2c3 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 25 Feb 2022 14:59:28 -0800 Subject: [PATCH 02/36] working prototype --- src/services/codefixes/helpers.ts | 30 +- src/services/completions.ts | 256 +++++++++++++++++- ...ompletionsObjectLiteralFunctionProperty.ts | 95 ++++++- 3 files changed, 354 insertions(+), 27 deletions(-) diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index baeb1e9a1a535..b3703f9e7a2a2 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -146,6 +146,7 @@ namespace ts.codefix { break; } + // >> TODO: call `try get auto importable reference from type node` on type nodes? if (declarations.length === 1) { Debug.assert(signatures.length === 1, "One declaration implies one signature"); const signature = signatures[0]; @@ -177,6 +178,29 @@ namespace ts.codefix { } } + // export function addNewNodeObjectLiteralProperties( + // // changes: textChanges.ChangeTracker, + // // context: CodeFixContextBase, + // sourceFile: SourceFile, + // program: Program, + // preferences: UserPreferences, + // host: LanguageServiceHost, + // // info: ObjectLiteralInfo, + // properties: Symbol[], + // addNodes: (nodes: PropertyAssignment[]) => void, + // ): void { + // const importAdder = createImportAdder(sourceFile, program, preferences, host); + // const quotePreference = getQuotePreference(sourceFile, preferences); + // const target = getEmitScriptTarget(program.getCompilerOptions()); + // const checker = program.getTypeChecker(); + // const props = map(properties, prop => { + // const initializer = tryGetValueFromType(context, checker, importAdder, quotePreference, checker.getTypeOfSymbol(prop)); + // return factory.createPropertyAssignment(createPropertyNameNodeForIdentifierOrLiteral(prop.name, target, quotePreference === QuotePreference.Single), initializer); + // }); + + // addNodes(props); + // } + export function createSignatureDeclarationFromSignature( kind: SyntaxKind.MethodDeclaration | SyntaxKind.FunctionExpression | SyntaxKind.ArrowFunction, context: TypeConstructionContext, @@ -366,10 +390,10 @@ namespace ts.codefix { return parameters; } - function createMethodImplementingSignatures( + export function createMethodImplementingSignatures( checker: TypeChecker, context: TypeConstructionContext, - enclosingDeclaration: ClassLikeDeclaration, + enclosingDeclaration: Node, signatures: readonly Signature[], name: PropertyName, optional: boolean, @@ -420,7 +444,7 @@ namespace ts.codefix { body); } - function getReturnTypeFromSignatures(signatures: readonly Signature[], checker: TypeChecker, context: TypeConstructionContext, enclosingDeclaration: ClassLikeDeclaration): TypeNode | undefined { + function getReturnTypeFromSignatures(signatures: readonly Signature[], checker: TypeChecker, context: TypeConstructionContext, enclosingDeclaration: Node): TypeNode | undefined { if (length(signatures)) { const type = checker.getUnionType(map(signatures, checker.getReturnTypeOfSignature)); return checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context)); diff --git a/src/services/completions.ts b/src/services/completions.ts index b93b490de2f38..2a30fcc146a1b 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -77,6 +77,14 @@ namespace ts.Completions { SymbolMemberExport = SymbolMember | Export, } + const enum ObjectLiteralFunctionPropertyStyle { + // Auto, // >> ? + Method, + FunctionExpression, + ArrowFunction, + BracelessArrowFunction, + } + interface SymbolOriginInfo { kind: SymbolOriginInfoKind; isDefaultExport?: boolean; @@ -775,8 +783,15 @@ namespace ts.Completions { } } - if (isObjectLiteralFunctionPropertyCompletion(symbol, location, contextToken, program.getTypeChecker())) { - getEntryForObjectLiteralFunctionCompletion(symbol, location); + const objLit = isObjectLiteralFunctionPropertyCompletion(symbol, location, contextToken, program.getTypeChecker()); + if (objLit) { + let importAdder; + ({ insertText, isSnippet, importAdder } = getEntryForObjectLiteralFunctionCompletion(symbol, name, objLit, sourceFile, program, host, options, preferences, formatContext)); + if (importAdder?.hasFixes()) { + hasAction = true; + source = CompletionSource.ClassMemberSnippet; // >> TODO: revisit & fix; + // >> TODO: change to bundling action with the completion entry instead of getting it separately, if possible + } } if (isJsxIdentifierExpected && !isRightOfOpenTag && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { @@ -988,6 +1003,7 @@ namespace ts.Completions { // If we have access to formatting settings, we print the nodes using the emitter, // and then format the printed text. if (formatContext) { + // >> TODO: move this to snippet printer? const syntheticFile = { text: printer.printSnippetList( ListFormat.MultiLine | ListFormat.NoTrailingNewLine, @@ -1063,11 +1079,11 @@ namespace ts.Completions { return undefined; } - function isObjectLiteralFunctionPropertyCompletion(symbol: Symbol, location: Node, contextToken: Node | undefined, checker: TypeChecker): boolean { + function isObjectLiteralFunctionPropertyCompletion(symbol: Symbol, location: Node, contextToken: Node | undefined, checker: TypeChecker): ObjectLiteralExpression | undefined { // >> TODO: check completion kind is memberlike // TODO: support JS files. if (isInJSFile(location)) { - return false; + return undefined; } /* @@ -1075,29 +1091,229 @@ namespace ts.Completions { `type Foo = { bar(x: number): void; foo: (x: string) => string; + get prop(): number; + set prop(n: number); }`, `bar` will have symbol flag `Method`, `foo` will have symbol flag `Property`. + `prop` will have symbol flags `GetAccessor` and `SetAccessor`. */ - if (!(symbol.flags & (SymbolFlags.Property | SymbolFlags.Method))) { - return false; + if (!(symbol.flags & (SymbolFlags.Property | SymbolFlags.Method | SymbolFlags.Accessor))) { + return undefined; } - const type = checker.getTypeOfSymbol(symbol); - // We ignore non-function properties. - if (!type.getCallSignatures().length) { - return false; + if (symbol.flags & SymbolFlags.Property) { + const type = checker.getTypeOfSymbol(symbol); + // We ignore non-function properties. + if (!type.getCallSignatures().length) { + return undefined; + } } - // Check if we are in a position for object literal completion. const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); - return !!objectLikeContainer && isObjectLiteralExpression(objectLikeContainer); + return objectLikeContainer && tryCast(objectLikeContainer, isObjectLiteralExpression); } - function getEntryForObjectLiteralFunctionCompletion(_symbol: Symbol, _location: Node) { - // >> TODO: implement this + function getEntryForObjectLiteralFunctionCompletion( + symbol: Symbol, + name: string, + enclosingDeclaration: ObjectLiteralExpression, + sourceFile: SourceFile, + program: Program, + host: LanguageServiceHost, + options: CompilerOptions, + preferences: UserPreferences, + _formatContext: formatting.FormatContext | undefined, + ): { insertText: string, isSnippet?: true, importAdder: codefix.ImportAdder } { + let isSnippet: true | undefined; + let insertText: string = name; + + const style = ObjectLiteralFunctionPropertyStyle.ArrowFunction; // >> Get this from somewhere + const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); + const body = factory.createBlock([]); + const functionProp = createObjectLiteralFunctionProperty(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body, style); + + if (functionProp) { + const printer = createSnippetPrinter({ + removeComments: true, + module: options.module, + target: options.target, + omitTrailingSemicolon: false, // >> ?? + newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), + }); + + // insertText = printer.printSnippet(EmitHint.Unspecified, functionProp, sourceFile); + // ListFormat.ObjectLiteralExpressionProperties & ~(ListFormat.Braces | ListFormat.SpaceBetweenSiblings) + insertText = printer.printSnippetList(ListFormat.CommaDelimited, factory.createNodeArray(functionProp), sourceFile); + + // if (formatContext) { + // + // } + } + + return { isSnippet, insertText, importAdder }; }; + function createObjectLiteralFunctionProperty( + symbol: Symbol, + enclosingDeclaration: ObjectLiteralExpression, + sourceFile: SourceFile, + program: Program, + host: LanguageServiceHost, + preferences: UserPreferences, + importAdder: codefix.ImportAdder, + // addClassElement: (node: AddNode) => void, + body: Block, + style: ObjectLiteralFunctionPropertyStyle, // >> maybe this will be added to user preferences + ): (PropertyAssignment | MethodDeclaration | AccessorDeclaration)[] | undefined { + const declarations = symbol.getDeclarations(); + if (!(declarations && declarations.length)) { + return undefined; + } + const checker = program.getTypeChecker(); + const scriptTarget = getEmitScriptTarget(program.getCompilerOptions()); + const declaration = declarations[0]; + const name = getSynthesizedDeepClone(getNameOfDeclaration(declaration), /*includeTrivia*/ false) as PropertyName; + const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); + const quotePreference = getQuotePreference(sourceFile, preferences); + const builderFlags = quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : undefined; + // >> TODO: implement each case, based on `addNewNodeForMemberSymbol`. + + switch (declaration.kind) { + case SyntaxKind.PropertySignature: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.MethodDeclaration: { + const signatures = checker.getSignaturesOfType(type, SignatureKind.Call); + if (signatures.length > 1) { + // >> TODO: we have overloads? + return undefined; // >> TODO: should we opt out of supporting overloads? + // // >> refactor createmethodimplementingsignature to actually create a type node instead? + // // >> then we can reuse `createFunctionFromType`, and this also fixes the auto-import issue + // const method = codefix.createMethodImplementingSignatures( + // checker, + // { program, host }, + // enclosingDeclaration, + // signatures, + // name, + // /*optional*/ false, + // /*modifiers*/ undefined, + // quotePreference, + // body); + // // >> TODO: fix auto import of types, properly call `tryGetAutoImportableReferenceFromTypeNode` + // return [factory.createPropertyAssignment(name, factory.createArrowFunction( + // /*modifiers*/ undefined, + // method.typeParameters, + // method.parameters, + // method.type, + // /*equalsGreaterThanToken*/ undefined, + // method.body!))]; + } + const prop = createFunctionFromType(type, style); + return prop && [prop]; + } + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: { + // >> TODO: confusing to return both accessors in a completion scenario? They're kinda different functions. Return in different entries + let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); + const importableReference = codefix.tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); + if (importableReference) { + typeNode = importableReference.typeNode; + codefix.importSymbols(importAdder, importableReference.symbols); + } + const accessors = getAllAccessorDeclarations(declarations, declaration as AccessorDeclaration); + // >> Why does the order matter? Or maybe it doesn't, and we're just trying to get all the accessors? + const orderedAccessors = accessors.secondAccessor ? [accessors.firstAccessor, accessors.secondAccessor] : [accessors.firstAccessor]; + const nodes = []; + for (const accessor of orderedAccessors) { + if (isGetAccessorDeclaration(accessor)) { + nodes.push(factory.createGetAccessorDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + name, + emptyArray, + typeNode, + body || factory.createBlock([]))); + } + else { + Debug.assertNode(accessor, isSetAccessorDeclaration, "The counterpart to a getter should be a setter"); + const accessorParameter = getSetAccessorValueParameter(accessor); + const paramName = accessorParameter && isIdentifier(accessorParameter.name) ? idText(accessorParameter.name) : "arg"; + const newParameter = factory.createParameterDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + paramName, + /*questionToken*/ undefined, + typeNode); + nodes.push(factory.createSetAccessorDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + name, + [newParameter], + body || factory.createBlock([]))); + } + } + return nodes; + } + default: + return undefined; + } + + function createFunctionFromType(type: Type, style: ObjectLiteralFunctionPropertyStyle): PropertyAssignment | MethodDeclaration | undefined { + let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); + const importableReference = codefix.tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); + if (importableReference) { + typeNode = importableReference.typeNode; + codefix.importSymbols(importAdder, importableReference.symbols); + } + if (!typeNode) { + return undefined; + } + Debug.assert(isFunctionTypeNode(typeNode), `Expected function type node, got node of kind ${typeNode.kind}`); // >> TODO: properly display kind + switch (style) { + case ObjectLiteralFunctionPropertyStyle.Method: + return factory.createMethodDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + name, + /*questionToken*/ undefined, + typeNode.typeParameters, + typeNode.parameters, + typeNode.type, + body); + case ObjectLiteralFunctionPropertyStyle.FunctionExpression: + return factory.createPropertyAssignment(name, factory.createFunctionExpression( + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ undefined, + typeNode.typeParameters, + typeNode.parameters, + typeNode.type, + body)); + case ObjectLiteralFunctionPropertyStyle.ArrowFunction: + return factory.createPropertyAssignment(name, factory.createArrowFunction( + /*modifiers*/ undefined, + typeNode.typeParameters, + typeNode.parameters, + typeNode.type, + /*equalsGreaterThanToken*/ undefined, + body)); + case ObjectLiteralFunctionPropertyStyle.BracelessArrowFunction: + return factory.createPropertyAssignment(name, factory.createArrowFunction( + /*modifiers*/ undefined, + typeNode.typeParameters, + typeNode.parameters, + typeNode.type, + /*equalsGreaterThanToken*/ undefined, + factory.createOmittedExpression())); // >> TODO: figure out expression + default: + Debug.assertNever(style); + } + } + } + function createSnippetPrinter( printerOptions: PrinterOptions, ) { @@ -1117,6 +1333,7 @@ namespace ts.Completions { return { printSnippetList, + printSnippet, }; @@ -1130,6 +1347,17 @@ namespace ts.Completions { printer.writeList(format, list, sourceFile, writer); return writer.getText(); } + + // >> TODO: will we need this? + function printSnippet( + hint: EmitHint, + node: Node, + sourceFile: SourceFile | undefined, + ): string { + writer.clear(); + printer.writeNode(hint, node, sourceFile, writer); + return writer.getText(); + } } function originToCompletionEntryData(origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport): CompletionEntryData | undefined { diff --git a/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts b/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts index ec7e4fa819103..c39e8cd092014 100644 --- a/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts +++ b/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts @@ -2,7 +2,6 @@ // @newline: LF // @Filename: a.ts -// Case: Concrete class implements abstract method ////interface IFoo { //// bar(x: number): void; ////} @@ -18,20 +17,46 @@ ////const f: Foo = { //// /*b*/ ////} -////const g: Foo = { -//// bar: (x: number) => { return; }, +//// +////interface Overload { +//// buzz(a: number): number; +//// buzz(a: string): string; +////} +////const o: Overload = { //// /*c*/ ////} - - +////interface Accessor { +//// get prop(): number; +//// set prop(n: number); +////} +////const a: Accessor = { +//// /*d*/ +////} verify.completions({ - marker: "c", - isNewIdentifierLocation: true, + marker: "a", + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + }, + includes: [ + { + name: "bar", + sortText: completion.SortText.LocationPriority, + replacementSpan: { + fileName: "", + pos: 0, + end: 0, + }, + insertText: "bar: (x: number): void => { }", + }, + ], +}); +verify.completions({ + marker: "b", preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, - // includeCompletionsWithClassMemberSnippets: true, }, includes: [ { @@ -42,7 +67,57 @@ verify.completions({ pos: 0, end: 0, }, - insertText: "bar(x: number): void {\n}", + insertText: "bar: (x: number): void => { }", + }, + { + name: "foo", + sortText: completion.SortText.LocationPriority, + replacementSpan: { + fileName: "", + pos: 0, + end: 0, + }, + insertText: "foo: (x: string): string => { }", } ], -}); \ No newline at end of file +}); +verify.completions({ + marker: "c", + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + }, + includes: [ + { + name: "buzz", + sortText: completion.SortText.LocationPriority, + replacementSpan: { + fileName: "", + pos: 0, + end: 0, + }, + insertText: "buzz", + // no declaration insert text, because this property has overloads + }, + ], +}); +verify.completions({ + marker: "d", + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + }, + includes: [ + { + name: "prop", + sortText: completion.SortText.LocationPriority, + replacementSpan: { + fileName: "", + pos: 0, + end: 0, + }, + insertText: "get prop(): number { },set prop(n: number) { }", + }, + ], +}); + From fca74c1e74a92e8952d1c698981ff3b3bafc1113 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 25 Feb 2022 15:04:45 -0800 Subject: [PATCH 03/36] refactor print and format code into its own function --- src/services/completions.ts | 62 +++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 2a30fcc146a1b..b782de871b9d5 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1000,36 +1000,19 @@ namespace ts.Completions { isAbstract); if (completionNodes.length) { + const format = ListFormat.MultiLine | ListFormat.NoTrailingNewLine; // If we have access to formatting settings, we print the nodes using the emitter, // and then format the printed text. if (formatContext) { - // >> TODO: move this to snippet printer? - const syntheticFile = { - text: printer.printSnippetList( - ListFormat.MultiLine | ListFormat.NoTrailingNewLine, - factory.createNodeArray(completionNodes), - sourceFile), - getLineAndCharacterOfPosition(pos: number) { - return getLineAndCharacterOfPosition(this, pos); - }, - }; - - const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); - const changes = flatMap(completionNodes, node => { - const nodeWithPos = textChanges.assignPositionsToNode(node); - return formatting.formatNodeGivenIndentation( - nodeWithPos, - syntheticFile, - sourceFile.languageVariant, - /* indentation */ 0, - /* delta */ 0, - { ...formatContext, options: formatOptions }); - }); - insertText = textChanges.applyChanges(syntheticFile.text, changes); + insertText = printer.printAndFormatSnippetList( + format, + factory.createNodeArray(completionNodes), + sourceFile, + formatContext); } else { // Otherwise, just use emitter to print the new nodes. insertText = printer.printSnippetList( - ListFormat.MultiLine | ListFormat.NoTrailingNewLine, + format, factory.createNodeArray(completionNodes), sourceFile); } @@ -1333,6 +1316,7 @@ namespace ts.Completions { return { printSnippetList, + printAndFormatSnippetList, printSnippet, }; @@ -1348,6 +1332,36 @@ namespace ts.Completions { return writer.getText(); } + function printAndFormatSnippetList( + format: ListFormat, + list: NodeArray, + sourceFile: SourceFile, + formatContext: formatting.FormatContext, + ): string { + const syntheticFile = { + text: printSnippetList( + format, + factory.createNodeArray(list), + sourceFile), + getLineAndCharacterOfPosition(pos: number) { + return getLineAndCharacterOfPosition(this, pos); + }, + }; + + const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); + const changes = flatMap(list, node => { + const nodeWithPos = textChanges.assignPositionsToNode(node); + return formatting.formatNodeGivenIndentation( + nodeWithPos, + syntheticFile, + sourceFile.languageVariant, + /* indentation */ 0, + /* delta */ 0, + { ...formatContext, options: formatOptions }); + }); + return textChanges.applyChanges(syntheticFile.text, changes); + } + // >> TODO: will we need this? function printSnippet( hint: EmitHint, From 249c3baf584725571e85f4e9a704d6939de5e367 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 1 Mar 2022 15:21:37 -0800 Subject: [PATCH 04/36] minor changes; don't support overloads --- src/services/completions.ts | 47 +++++++++++-------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index b782de871b9d5..c49930aefb8ca 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1111,12 +1111,12 @@ namespace ts.Completions { let isSnippet: true | undefined; let insertText: string = name; - const style = ObjectLiteralFunctionPropertyStyle.ArrowFunction; // >> Get this from somewhere + const style = ObjectLiteralFunctionPropertyStyle.ArrowFunction; // >> TODO: Get this from somewhere const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); const body = factory.createBlock([]); - const functionProp = createObjectLiteralFunctionProperty(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body, style); + const functionProps = createObjectLiteralFunctionProperty(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body, style); - if (functionProp) { + if (functionProps) { const printer = createSnippetPrinter({ removeComments: true, module: options.module, @@ -1126,12 +1126,15 @@ namespace ts.Completions { }); // insertText = printer.printSnippet(EmitHint.Unspecified, functionProp, sourceFile); - // ListFormat.ObjectLiteralExpressionProperties & ~(ListFormat.Braces | ListFormat.SpaceBetweenSiblings) - insertText = printer.printSnippetList(ListFormat.CommaDelimited, factory.createNodeArray(functionProp), sourceFile); - + const format = ListFormat.CommaDelimited; // if (formatContext) { - // + // // >> TODO: this is breaking get/set pairs because they're on the same line when printing + // insertText = printer.printAndFormatSnippetList(format, factory.createNodeArray(functionProps), sourceFile, formatContext); // } + // else { + // insertText = printer.printSnippetList(format, factory.createNodeArray(functionProps), sourceFile); + // } + insertText = printer.printSnippetList(format, factory.createNodeArray(functionProps), sourceFile); } return { isSnippet, insertText, importAdder }; @@ -1145,9 +1148,8 @@ namespace ts.Completions { host: LanguageServiceHost, preferences: UserPreferences, importAdder: codefix.ImportAdder, - // addClassElement: (node: AddNode) => void, body: Block, - style: ObjectLiteralFunctionPropertyStyle, // >> maybe this will be added to user preferences + style: ObjectLiteralFunctionPropertyStyle, ): (PropertyAssignment | MethodDeclaration | AccessorDeclaration)[] | undefined { const declarations = symbol.getDeclarations(); if (!(declarations && declarations.length)) { @@ -1160,7 +1162,6 @@ namespace ts.Completions { const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); const quotePreference = getQuotePreference(sourceFile, preferences); const builderFlags = quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : undefined; - // >> TODO: implement each case, based on `addNewNodeForMemberSymbol`. switch (declaration.kind) { case SyntaxKind.PropertySignature: @@ -1169,28 +1170,8 @@ namespace ts.Completions { case SyntaxKind.MethodDeclaration: { const signatures = checker.getSignaturesOfType(type, SignatureKind.Call); if (signatures.length > 1) { - // >> TODO: we have overloads? - return undefined; // >> TODO: should we opt out of supporting overloads? - // // >> refactor createmethodimplementingsignature to actually create a type node instead? - // // >> then we can reuse `createFunctionFromType`, and this also fixes the auto-import issue - // const method = codefix.createMethodImplementingSignatures( - // checker, - // { program, host }, - // enclosingDeclaration, - // signatures, - // name, - // /*optional*/ false, - // /*modifiers*/ undefined, - // quotePreference, - // body); - // // >> TODO: fix auto import of types, properly call `tryGetAutoImportableReferenceFromTypeNode` - // return [factory.createPropertyAssignment(name, factory.createArrowFunction( - // /*modifiers*/ undefined, - // method.typeParameters, - // method.parameters, - // method.type, - // /*equalsGreaterThanToken*/ undefined, - // method.body!))]; + // We don't support overloads in object literals. + return undefined; } const prop = createFunctionFromType(type, style); return prop && [prop]; @@ -1341,7 +1322,7 @@ namespace ts.Completions { const syntheticFile = { text: printSnippetList( format, - factory.createNodeArray(list), + list, sourceFile), getLineAndCharacterOfPosition(pos: number) { return getLineAndCharacterOfPosition(this, pos); From ef15f8193eb8f09ae5aec7a1e19d6f46f002bfbc Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 2 Mar 2022 15:08:55 -0800 Subject: [PATCH 05/36] have two completion entries --- src/services/completions.ts | 82 +++++++++++++------ ...ompletionsObjectLiteralFunctionProperty.ts | 46 +++++------ 2 files changed, 75 insertions(+), 53 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index c49930aefb8ca..ea8a08bb14320 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -60,6 +60,8 @@ namespace ts.Completions { ThisProperty = "ThisProperty/", /** Auto-import that comes attached to a class member snippet */ ClassMemberSnippet = "ClassMemberSnippet/", + /** Auto-import that comes attached to an object literal method snippet */ + ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/", /** A type-only import that needs to be promoted in order to be used at the completion location */ TypeOnlyAlias = "TypeOnlyAlias/", } @@ -72,6 +74,7 @@ namespace ts.Completions { Nullable = 1 << 4, ResolvedExport = 1 << 5, TypeOnlyAlias = 1 << 6, + ObjectLiteralMember = 1 << 7, SymbolMemberNoExport = SymbolMember, SymbolMemberExport = SymbolMember | Export, @@ -147,6 +150,10 @@ namespace ts.Completions { return !!(origin && origin.kind & SymbolOriginInfoKind.TypeOnlyAlias); } + function originIsObjectLiteralMember(origin: SymbolOriginInfo | undefined): boolean { + return !!(origin && origin.kind & SymbolOriginInfoKind.ObjectLiteralMember); + } + interface UniqueNameSet { add(name: string): void; has(name: string): boolean; @@ -783,14 +790,17 @@ namespace ts.Completions { } } - const objLit = isObjectLiteralFunctionPropertyCompletion(symbol, location, contextToken, program.getTypeChecker()); + const objLit = isObjectLiteralFunctionPropertyCompletion(symbol, origin, location, contextToken, program.getTypeChecker()); if (objLit) { let importAdder; - ({ insertText, isSnippet, importAdder } = getEntryForObjectLiteralFunctionCompletion(symbol, name, objLit, sourceFile, program, host, options, preferences, formatContext)); + const entry = getEntryForObjectLiteralFunctionCompletion(symbol, name, objLit, sourceFile, program, host, options, preferences, formatContext); + if (!entry) { + return undefined; + } + ({ insertText, isSnippet, importAdder } = entry); + source = CompletionSource.ObjectLiteralMethodSnippet; // >> Needed for disambiguiation from normal completions (with just the method name). if (importAdder?.hasFixes()) { - hasAction = true; - source = CompletionSource.ClassMemberSnippet; // >> TODO: revisit & fix; - // >> TODO: change to bundling action with the completion entry instead of getting it separately, if possible + hasAction = true; // >> TODO: implement this computation in `getCompletionEntryDetails`. } } @@ -1062,7 +1072,10 @@ namespace ts.Completions { return undefined; } - function isObjectLiteralFunctionPropertyCompletion(symbol: Symbol, location: Node, contextToken: Node | undefined, checker: TypeChecker): ObjectLiteralExpression | undefined { + function isObjectLiteralFunctionPropertyCompletion(symbol: Symbol, origin: SymbolOriginInfo | undefined, location: Node, contextToken: Node | undefined, checker: TypeChecker): ObjectLiteralExpression | undefined { + if (!originIsObjectLiteralMember(origin)) { + return undefined; + } // >> TODO: check completion kind is memberlike // TODO: support JS files. if (isInJSFile(location)) { @@ -1107,7 +1120,7 @@ namespace ts.Completions { options: CompilerOptions, preferences: UserPreferences, _formatContext: formatting.FormatContext | undefined, - ): { insertText: string, isSnippet?: true, importAdder: codefix.ImportAdder } { + ): { insertText: string, isSnippet?: true, importAdder: codefix.ImportAdder } | undefined { let isSnippet: true | undefined; let insertText: string = name; @@ -1115,27 +1128,28 @@ namespace ts.Completions { const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); const body = factory.createBlock([]); const functionProps = createObjectLiteralFunctionProperty(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body, style); + if (!functionProps) { + return undefined; + } - if (functionProps) { - const printer = createSnippetPrinter({ - removeComments: true, - module: options.module, - target: options.target, - omitTrailingSemicolon: false, // >> ?? - newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), - }); + const printer = createSnippetPrinter({ + removeComments: true, + module: options.module, + target: options.target, + omitTrailingSemicolon: false, // >> ?? + newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), + }); - // insertText = printer.printSnippet(EmitHint.Unspecified, functionProp, sourceFile); - const format = ListFormat.CommaDelimited; - // if (formatContext) { - // // >> TODO: this is breaking get/set pairs because they're on the same line when printing - // insertText = printer.printAndFormatSnippetList(format, factory.createNodeArray(functionProps), sourceFile, formatContext); - // } - // else { - // insertText = printer.printSnippetList(format, factory.createNodeArray(functionProps), sourceFile); - // } - insertText = printer.printSnippetList(format, factory.createNodeArray(functionProps), sourceFile); - } + // insertText = printer.printSnippet(EmitHint.Unspecified, functionProp, sourceFile); + const format = ListFormat.CommaDelimited; + // if (formatContext) { + // // >> TODO: this is breaking get/set pairs because they're on the same line when printing + // insertText = printer.printAndFormatSnippetList(format, factory.createNodeArray(functionProps), sourceFile, formatContext); + // } + // else { + // insertText = printer.printSnippetList(format, factory.createNodeArray(functionProps), sourceFile); + // } + insertText = printer.printSnippetList(format, factory.createNodeArray(functionProps), sourceFile); return { isSnippet, insertText, importAdder }; }; @@ -2851,6 +2865,16 @@ namespace ts.Completions { symbols.push(symbol); } + /* Mutates `symbols` and `symbolToOriginInfoMap`. */ + function collectObjectLiteralSymbols(members: Symbol[]) { + // >> TODO: only do this if user preferences for this completion scenario is enabled + for (const member of members) { + const origin = { kind: SymbolOriginInfoKind.ObjectLiteralMember }; + symbolToOriginInfoMap[symbols.length] = origin; + symbols.push(member); + } + } + /** * Finds the first node that "embraces" the position, so that one may * accurately aggregate locals from the closest containing scope. @@ -3081,7 +3105,11 @@ namespace ts.Completions { if (typeMembers && typeMembers.length > 0) { // Add filtered items to the completion list - symbols = concatenate(symbols, filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers))); + const filteredMembers = filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers)); + if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { + collectObjectLiteralSymbols(filteredMembers); + } + symbols = concatenate(symbols, filteredMembers); } setSortTextToOptionalMember(); diff --git a/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts b/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts index c39e8cd092014..4f6ba7e79f904 100644 --- a/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts +++ b/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts @@ -43,13 +43,14 @@ verify.completions({ { name: "bar", sortText: completion.SortText.LocationPriority, - replacementSpan: { - fileName: "", - pos: 0, - end: 0, - }, + source: "ObjectLiteralMethodSnippet/", insertText: "bar: (x: number): void => { }", }, + { + name: "bar", + sortText: completion.SortText.LocationPriority, + insertText: undefined, + }, ], }); verify.completions({ @@ -62,22 +63,24 @@ verify.completions({ { name: "bar", sortText: completion.SortText.LocationPriority, - replacementSpan: { - fileName: "", - pos: 0, - end: 0, - }, + source: "ObjectLiteralMethodSnippet/", insertText: "bar: (x: number): void => { }", }, + { + name: "bar", + sortText: completion.SortText.LocationPriority, + insertText: undefined, + }, { name: "foo", sortText: completion.SortText.LocationPriority, - replacementSpan: { - fileName: "", - pos: 0, - end: 0, - }, + source: "ObjectLiteralMethodSnippet/", insertText: "foo: (x: string): string => { }", + }, + { + name: "foo", + sortText: completion.SortText.LocationPriority, + insertText: undefined, } ], }); @@ -91,13 +94,8 @@ verify.completions({ { name: "buzz", sortText: completion.SortText.LocationPriority, - replacementSpan: { - fileName: "", - pos: 0, - end: 0, - }, - insertText: "buzz", // no declaration insert text, because this property has overloads + insertText: undefined, }, ], }); @@ -111,11 +109,7 @@ verify.completions({ { name: "prop", sortText: completion.SortText.LocationPriority, - replacementSpan: { - fileName: "", - pos: 0, - end: 0, - }, + source: "ObjectLiteralMethodSnippet/", insertText: "get prop(): number { },set prop(n: number) { }", }, ], From 1f26a523b97ef7d0399a27a7f3f77febf5e1610a Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 3 Mar 2022 16:34:35 -0800 Subject: [PATCH 06/36] get rid of accessor support --- src/services/completions.ts | 63 +++++-------------------------------- 1 file changed, 7 insertions(+), 56 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index ea8a08bb14320..fdb4a000d3749 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1076,7 +1076,6 @@ namespace ts.Completions { if (!originIsObjectLiteralMember(origin)) { return undefined; } - // >> TODO: check completion kind is memberlike // TODO: support JS files. if (isInJSFile(location)) { return undefined; @@ -1087,14 +1086,11 @@ namespace ts.Completions { `type Foo = { bar(x: number): void; foo: (x: string) => string; - get prop(): number; - set prop(n: number); }`, `bar` will have symbol flag `Method`, `foo` will have symbol flag `Property`. - `prop` will have symbol flags `GetAccessor` and `SetAccessor`. */ - if (!(symbol.flags & (SymbolFlags.Property | SymbolFlags.Method | SymbolFlags.Accessor))) { + if (!(symbol.flags & (SymbolFlags.Property | SymbolFlags.Method))) { return undefined; } @@ -1127,8 +1123,8 @@ namespace ts.Completions { const style = ObjectLiteralFunctionPropertyStyle.ArrowFunction; // >> TODO: Get this from somewhere const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); const body = factory.createBlock([]); - const functionProps = createObjectLiteralFunctionProperty(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body, style); - if (!functionProps) { + const functionProp = createObjectLiteralFunctionProperty(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body, style); + if (!functionProp) { return undefined; } @@ -1141,7 +1137,6 @@ namespace ts.Completions { }); // insertText = printer.printSnippet(EmitHint.Unspecified, functionProp, sourceFile); - const format = ListFormat.CommaDelimited; // if (formatContext) { // // >> TODO: this is breaking get/set pairs because they're on the same line when printing // insertText = printer.printAndFormatSnippetList(format, factory.createNodeArray(functionProps), sourceFile, formatContext); @@ -1149,7 +1144,8 @@ namespace ts.Completions { // else { // insertText = printer.printSnippetList(format, factory.createNodeArray(functionProps), sourceFile); // } - insertText = printer.printSnippetList(format, factory.createNodeArray(functionProps), sourceFile); + // insertText = printer.printSnippet(format, factory.createNodeArray(functionProps), sourceFile); + insertText = printer.printSnippet(EmitHint.Unspecified, functionProp, sourceFile); return { isSnippet, insertText, importAdder }; }; @@ -1164,7 +1160,7 @@ namespace ts.Completions { importAdder: codefix.ImportAdder, body: Block, style: ObjectLiteralFunctionPropertyStyle, - ): (PropertyAssignment | MethodDeclaration | AccessorDeclaration)[] | undefined { + ): PropertyAssignment | MethodDeclaration | undefined { const declarations = symbol.getDeclarations(); if (!(declarations && declarations.length)) { return undefined; @@ -1187,52 +1183,7 @@ namespace ts.Completions { // We don't support overloads in object literals. return undefined; } - const prop = createFunctionFromType(type, style); - return prop && [prop]; - } - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: { - // >> TODO: confusing to return both accessors in a completion scenario? They're kinda different functions. Return in different entries - let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); - const importableReference = codefix.tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); - if (importableReference) { - typeNode = importableReference.typeNode; - codefix.importSymbols(importAdder, importableReference.symbols); - } - const accessors = getAllAccessorDeclarations(declarations, declaration as AccessorDeclaration); - // >> Why does the order matter? Or maybe it doesn't, and we're just trying to get all the accessors? - const orderedAccessors = accessors.secondAccessor ? [accessors.firstAccessor, accessors.secondAccessor] : [accessors.firstAccessor]; - const nodes = []; - for (const accessor of orderedAccessors) { - if (isGetAccessorDeclaration(accessor)) { - nodes.push(factory.createGetAccessorDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - name, - emptyArray, - typeNode, - body || factory.createBlock([]))); - } - else { - Debug.assertNode(accessor, isSetAccessorDeclaration, "The counterpart to a getter should be a setter"); - const accessorParameter = getSetAccessorValueParameter(accessor); - const paramName = accessorParameter && isIdentifier(accessorParameter.name) ? idText(accessorParameter.name) : "arg"; - const newParameter = factory.createParameterDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - paramName, - /*questionToken*/ undefined, - typeNode); - nodes.push(factory.createSetAccessorDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - name, - [newParameter], - body || factory.createBlock([]))); - } - } - return nodes; + return createFunctionFromType(type, style); } default: return undefined; From 1c2809080d585fb6aaef93ba18249af6ef671780 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 3 Mar 2022 17:03:04 -0800 Subject: [PATCH 07/36] add snippet support --- src/services/completions.ts | 101 ++++++------------ ...ompletionsObjectLiteralFunctionProperty.ts | 39 +++++-- 2 files changed, 63 insertions(+), 77 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index fdb4a000d3749..c5203753cc544 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -80,14 +80,6 @@ namespace ts.Completions { SymbolMemberExport = SymbolMember | Export, } - const enum ObjectLiteralFunctionPropertyStyle { - // Auto, // >> ? - Method, - FunctionExpression, - ArrowFunction, - BracelessArrowFunction, - } - interface SymbolOriginInfo { kind: SymbolOriginInfoKind; isDefaultExport?: boolean; @@ -1120,10 +1112,20 @@ namespace ts.Completions { let isSnippet: true | undefined; let insertText: string = name; - const style = ObjectLiteralFunctionPropertyStyle.ArrowFunction; // >> TODO: Get this from somewhere const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); - const body = factory.createBlock([]); - const functionProp = createObjectLiteralFunctionProperty(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body, style); + + let body; + if (preferences.includeCompletionsWithSnippetText) { + isSnippet = true; + const emptyStmt = factory.createEmptyStatement(); + body = factory.createBlock([emptyStmt], /* multiline */ true); + setSnippetElement(emptyStmt, { kind: SnippetKind.TabStop, order: 0 }); + } + else { + body = factory.createBlock([], /* multiline */ true); + } + + const functionProp = createObjectLiteralFunctionProperty(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body); if (!functionProp) { return undefined; } @@ -1159,7 +1161,6 @@ namespace ts.Completions { preferences: UserPreferences, importAdder: codefix.ImportAdder, body: Block, - style: ObjectLiteralFunctionPropertyStyle, ): PropertyAssignment | MethodDeclaration | undefined { const declarations = symbol.getDeclarations(); if (!(declarations && declarations.length)) { @@ -1183,64 +1184,30 @@ namespace ts.Completions { // We don't support overloads in object literals. return undefined; } - return createFunctionFromType(type, style); - } + let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); + const importableReference = codefix.tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); + if (importableReference) { + typeNode = importableReference.typeNode; + codefix.importSymbols(importAdder, importableReference.symbols); + } + if (!typeNode) { + return undefined; + } + Debug.assertNode(typeNode, isFunctionTypeNode); + return factory.createMethodDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + name, + /*questionToken*/ undefined, + typeNode.typeParameters, + typeNode.parameters, + typeNode.type, + body); + } default: return undefined; } - - function createFunctionFromType(type: Type, style: ObjectLiteralFunctionPropertyStyle): PropertyAssignment | MethodDeclaration | undefined { - let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); - const importableReference = codefix.tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); - if (importableReference) { - typeNode = importableReference.typeNode; - codefix.importSymbols(importAdder, importableReference.symbols); - } - if (!typeNode) { - return undefined; - } - Debug.assert(isFunctionTypeNode(typeNode), `Expected function type node, got node of kind ${typeNode.kind}`); // >> TODO: properly display kind - switch (style) { - case ObjectLiteralFunctionPropertyStyle.Method: - return factory.createMethodDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - name, - /*questionToken*/ undefined, - typeNode.typeParameters, - typeNode.parameters, - typeNode.type, - body); - case ObjectLiteralFunctionPropertyStyle.FunctionExpression: - return factory.createPropertyAssignment(name, factory.createFunctionExpression( - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ undefined, - typeNode.typeParameters, - typeNode.parameters, - typeNode.type, - body)); - case ObjectLiteralFunctionPropertyStyle.ArrowFunction: - return factory.createPropertyAssignment(name, factory.createArrowFunction( - /*modifiers*/ undefined, - typeNode.typeParameters, - typeNode.parameters, - typeNode.type, - /*equalsGreaterThanToken*/ undefined, - body)); - case ObjectLiteralFunctionPropertyStyle.BracelessArrowFunction: - return factory.createPropertyAssignment(name, factory.createArrowFunction( - /*modifiers*/ undefined, - typeNode.typeParameters, - typeNode.parameters, - typeNode.type, - /*equalsGreaterThanToken*/ undefined, - factory.createOmittedExpression())); // >> TODO: figure out expression - default: - Debug.assertNever(style); - } - } } function createSnippetPrinter( diff --git a/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts b/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts index 4f6ba7e79f904..774be15dd0d8c 100644 --- a/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts +++ b/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts @@ -25,11 +25,10 @@ ////const o: Overload = { //// /*c*/ ////} -////interface Accessor { -//// get prop(): number; -//// set prop(n: number); +////interface Prop { +//// "space bar"(): string; ////} -////const a: Accessor = { +////const p: Prop = { //// /*d*/ ////} @@ -44,7 +43,7 @@ verify.completions({ name: "bar", sortText: completion.SortText.LocationPriority, source: "ObjectLiteralMethodSnippet/", - insertText: "bar: (x: number): void => { }", + insertText: "bar(x: number): void {\n}", }, { name: "bar", @@ -64,7 +63,7 @@ verify.completions({ name: "bar", sortText: completion.SortText.LocationPriority, source: "ObjectLiteralMethodSnippet/", - insertText: "bar: (x: number): void => { }", + insertText: "bar(x: number): void {\n}", }, { name: "bar", @@ -75,7 +74,7 @@ verify.completions({ name: "foo", sortText: completion.SortText.LocationPriority, source: "ObjectLiteralMethodSnippet/", - insertText: "foo: (x: string): string => { }", + insertText: "foo(x: string): string {\n}", }, { name: "foo", @@ -107,11 +106,31 @@ verify.completions({ }, includes: [ { - name: "prop", + name: "\"space bar\"", sortText: completion.SortText.LocationPriority, source: "ObjectLiteralMethodSnippet/", - insertText: "get prop(): number { },set prop(n: number) { }", + insertText: "\"space bar\"(): string {\n}", + }, + { + name: "\"space bar\"", + sortText: completion.SortText.LocationPriority, + insertText: undefined, + }, + ], +}); +verify.completions({ + marker: "a", + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: true, + }, + includes: [ + { + name: "bar", + sortText: completion.SortText.LocationPriority, + source: "ObjectLiteralMethodSnippet/", + isSnippet: true, + insertText: "bar(x: number): void {\n $0\n}", }, ], }); - From e6285909eb503c857377a34f560f2de1a1c80bdb Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 3 Mar 2022 17:12:44 -0800 Subject: [PATCH 08/36] add formatting --- src/services/completions.ts | 53 +++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index c5203753cc544..bf2de4c2f5aff 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1107,7 +1107,7 @@ namespace ts.Completions { host: LanguageServiceHost, options: CompilerOptions, preferences: UserPreferences, - _formatContext: formatting.FormatContext | undefined, + formatContext: formatting.FormatContext | undefined, ): { insertText: string, isSnippet?: true, importAdder: codefix.ImportAdder } | undefined { let isSnippet: true | undefined; let insertText: string = name; @@ -1134,20 +1134,16 @@ namespace ts.Completions { removeComments: true, module: options.module, target: options.target, - omitTrailingSemicolon: false, // >> ?? + omitTrailingSemicolon: false, newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), }); - // insertText = printer.printSnippet(EmitHint.Unspecified, functionProp, sourceFile); - // if (formatContext) { - // // >> TODO: this is breaking get/set pairs because they're on the same line when printing - // insertText = printer.printAndFormatSnippetList(format, factory.createNodeArray(functionProps), sourceFile, formatContext); - // } - // else { - // insertText = printer.printSnippetList(format, factory.createNodeArray(functionProps), sourceFile); - // } - // insertText = printer.printSnippet(format, factory.createNodeArray(functionProps), sourceFile); - insertText = printer.printSnippet(EmitHint.Unspecified, functionProp, sourceFile); + if (formatContext) { + insertText = printer.printAndFormatSnippet(EmitHint.Unspecified, functionProp, sourceFile, formatContext); + } + else { + insertText = printer.printSnippet(EmitHint.Unspecified, functionProp, sourceFile); + } return { isSnippet, insertText, importAdder }; }; @@ -1231,9 +1227,9 @@ namespace ts.Completions { printSnippetList, printAndFormatSnippetList, printSnippet, + printAndFormatSnippet, }; - /* Snippet-escaping version of `printer.printList`. */ function printSnippetList( format: ListFormat, @@ -1275,7 +1271,6 @@ namespace ts.Completions { return textChanges.applyChanges(syntheticFile.text, changes); } - // >> TODO: will we need this? function printSnippet( hint: EmitHint, node: Node, @@ -1285,6 +1280,36 @@ namespace ts.Completions { printer.writeNode(hint, node, sourceFile, writer); return writer.getText(); } + + function printAndFormatSnippet( + hint: EmitHint, + node: Node, + sourceFile: SourceFile, + formatContext: formatting.FormatContext, + ): string { + const syntheticFile = { + text: printSnippet( + hint, + node, + sourceFile), + getLineAndCharacterOfPosition(pos: number) { + return getLineAndCharacterOfPosition(this, pos); + }, + }; + + const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); + + const nodeWithPos = textChanges.assignPositionsToNode(node); + const changes = formatting.formatNodeGivenIndentation( + nodeWithPos, + syntheticFile, + sourceFile.languageVariant, + /* indentation */ 0, + /* delta */ 0, + { ...formatContext, options: formatOptions }); + return textChanges.applyChanges(syntheticFile.text, changes); + } + } function originToCompletionEntryData(origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport): CompletionEntryData | undefined { From 515fc738ab861c4615dc5e5e3a0a0f3a1d5b7b98 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 3 Mar 2022 17:36:07 -0800 Subject: [PATCH 09/36] add trailing comma --- src/services/completions.ts | 5 +++-- .../completionsObjectLiteralFunctionProperty.ts | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index bf2de4c2f5aff..8e774d1e75c1e 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1139,10 +1139,10 @@ namespace ts.Completions { }); if (formatContext) { - insertText = printer.printAndFormatSnippet(EmitHint.Unspecified, functionProp, sourceFile, formatContext); + insertText = printer.printAndFormatSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([functionProp], /*hasTrailingComma*/ true), sourceFile, formatContext); } else { - insertText = printer.printSnippet(EmitHint.Unspecified, functionProp, sourceFile); + insertText = printer.printSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([functionProp], /*hasTrailingComma*/ true), sourceFile); } return { isSnippet, insertText, importAdder }; @@ -1271,6 +1271,7 @@ namespace ts.Completions { return textChanges.applyChanges(syntheticFile.text, changes); } + // >> TODO: do we need this? function printSnippet( hint: EmitHint, node: Node, diff --git a/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts b/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts index 774be15dd0d8c..df00acd1a68f3 100644 --- a/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts +++ b/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts @@ -43,7 +43,7 @@ verify.completions({ name: "bar", sortText: completion.SortText.LocationPriority, source: "ObjectLiteralMethodSnippet/", - insertText: "bar(x: number): void {\n}", + insertText: "bar(x: number): void {\n},", }, { name: "bar", @@ -63,7 +63,7 @@ verify.completions({ name: "bar", sortText: completion.SortText.LocationPriority, source: "ObjectLiteralMethodSnippet/", - insertText: "bar(x: number): void {\n}", + insertText: "bar(x: number): void {\n},", }, { name: "bar", @@ -74,7 +74,7 @@ verify.completions({ name: "foo", sortText: completion.SortText.LocationPriority, source: "ObjectLiteralMethodSnippet/", - insertText: "foo(x: string): string {\n}", + insertText: "foo(x: string): string {\n},", }, { name: "foo", @@ -109,7 +109,7 @@ verify.completions({ name: "\"space bar\"", sortText: completion.SortText.LocationPriority, source: "ObjectLiteralMethodSnippet/", - insertText: "\"space bar\"(): string {\n}", + insertText: "\"space bar\"(): string {\n},", }, { name: "\"space bar\"", @@ -130,7 +130,7 @@ verify.completions({ sortText: completion.SortText.LocationPriority, source: "ObjectLiteralMethodSnippet/", isSnippet: true, - insertText: "bar(x: number): void {\n $0\n}", + insertText: "bar(x: number): void {\n $0\n},", }, ], }); From 0229a7d8e149789a77f994f3fa3cc5d3ced89ffb Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 4 Mar 2022 16:04:15 -0800 Subject: [PATCH 10/36] add sourcedisplay --- src/services/completions.ts | 27 ++++++++++++------- src/services/utilities.ts | 8 ++++++ ...y.ts => completionsObjectLiteralMethod.ts} | 0 3 files changed, 26 insertions(+), 9 deletions(-) rename tests/cases/fourslash/{completionsObjectLiteralFunctionProperty.ts => completionsObjectLiteralMethod.ts} (100%) diff --git a/src/services/completions.ts b/src/services/completions.ts index 8e774d1e75c1e..42e57f4ea857b 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -789,7 +789,7 @@ namespace ts.Completions { if (!entry) { return undefined; } - ({ insertText, isSnippet, importAdder } = entry); + ({ insertText, isSnippet, importAdder, sourceDisplay } = entry); source = CompletionSource.ObjectLiteralMethodSnippet; // >> Needed for disambiguiation from normal completions (with just the method name). if (importAdder?.hasFixes()) { hasAction = true; // >> TODO: implement this computation in `getCompletionEntryDetails`. @@ -1108,7 +1108,7 @@ namespace ts.Completions { options: CompilerOptions, preferences: UserPreferences, formatContext: formatting.FormatContext | undefined, - ): { insertText: string, isSnippet?: true, importAdder: codefix.ImportAdder } | undefined { + ): { insertText: string, isSnippet?: true, importAdder: codefix.ImportAdder, sourceDisplay: SymbolDisplayPart[] } | undefined { let isSnippet: true | undefined; let insertText: string = name; @@ -1125,8 +1125,8 @@ namespace ts.Completions { body = factory.createBlock([], /* multiline */ true); } - const functionProp = createObjectLiteralFunctionProperty(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body); - if (!functionProp) { + const method = createObjectLiteralFunctionProperty(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body); + if (!method) { return undefined; } @@ -1137,15 +1137,24 @@ namespace ts.Completions { omitTrailingSemicolon: false, newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), }); - if (formatContext) { - insertText = printer.printAndFormatSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([functionProp], /*hasTrailingComma*/ true), sourceFile, formatContext); + insertText = printer.printAndFormatSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([method], /*hasTrailingComma*/ true), sourceFile, formatContext); } else { - insertText = printer.printSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([functionProp], /*hasTrailingComma*/ true), sourceFile); + insertText = printer.printSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([method], /*hasTrailingComma*/ true), sourceFile); } - return { isSnippet, insertText, importAdder }; + const methodSignature = factory.createMethodSignature( + method.modifiers, + method.name, + method.questionToken, + method.typeParameters, + method.parameters, + method.type); + const sourceDisplay = nodeToDisplayParts(methodSignature, enclosingDeclaration); + + return { isSnippet, insertText, importAdder, sourceDisplay }; + }; function createObjectLiteralFunctionProperty( @@ -1157,7 +1166,7 @@ namespace ts.Completions { preferences: UserPreferences, importAdder: codefix.ImportAdder, body: Block, - ): PropertyAssignment | MethodDeclaration | undefined { + ): MethodDeclaration | undefined { const declarations = symbol.getDeclarations(); if (!(declarations && declarations.length)) { return undefined; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index b3fcce814821d..f0b71ccef2fcd 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2395,6 +2395,14 @@ namespace ts { }); } + export function nodeToDisplayParts(node: Node, enclosingDeclaration: Node): SymbolDisplayPart[] { + const file = enclosingDeclaration.getSourceFile(); + return mapToDisplayParts(writer => { + const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); + printer.writeNode(EmitHint.Unspecified, node, file, writer); + }); + } + export function isImportOrExportSpecifierName(location: Node): location is Identifier { return !!location.parent && isImportOrExportSpecifier(location.parent) && location.parent.propertyName === location; } diff --git a/tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts b/tests/cases/fourslash/completionsObjectLiteralMethod.ts similarity index 100% rename from tests/cases/fourslash/completionsObjectLiteralFunctionProperty.ts rename to tests/cases/fourslash/completionsObjectLiteralMethod.ts From 9892dc8290826f7491e226c83041a390ad0dd102 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 4 Mar 2022 17:28:40 -0800 Subject: [PATCH 11/36] support auto-imports via completion details --- src/services/completions.ts | 34 ++++++++++-- ....ts => completionsObjectLiteralMethod1.ts} | 10 ++-- .../completionsObjectLiteralMethod2.ts | 52 +++++++++++++++++++ tests/cases/fourslash/fourslash.ts | 1 + 4 files changed, 87 insertions(+), 10 deletions(-) rename tests/cases/fourslash/{completionsObjectLiteralMethod.ts => completionsObjectLiteralMethod1.ts} (89%) create mode 100644 tests/cases/fourslash/completionsObjectLiteralMethod2.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 42e57f4ea857b..324d7c44ad836 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -60,10 +60,10 @@ namespace ts.Completions { ThisProperty = "ThisProperty/", /** Auto-import that comes attached to a class member snippet */ ClassMemberSnippet = "ClassMemberSnippet/", - /** Auto-import that comes attached to an object literal method snippet */ - ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/", /** A type-only import that needs to be promoted in order to be used at the completion location */ TypeOnlyAlias = "TypeOnlyAlias/", + /** Auto-import that comes attached to an object literal method snippet */ + ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/", } const enum SymbolOriginInfoKind { @@ -785,7 +785,7 @@ namespace ts.Completions { const objLit = isObjectLiteralFunctionPropertyCompletion(symbol, origin, location, contextToken, program.getTypeChecker()); if (objLit) { let importAdder; - const entry = getEntryForObjectLiteralFunctionCompletion(symbol, name, objLit, sourceFile, program, host, options, preferences, formatContext); + const entry = getEntryForObjectLiteralFunctionCompletion(symbol, name, objLit, program, host, options, preferences, formatContext); if (!entry) { return undefined; } @@ -1102,7 +1102,6 @@ namespace ts.Completions { symbol: Symbol, name: string, enclosingDeclaration: ObjectLiteralExpression, - sourceFile: SourceFile, program: Program, host: LanguageServiceHost, options: CompilerOptions, @@ -1112,6 +1111,7 @@ namespace ts.Completions { let isSnippet: true | undefined; let insertText: string = name; + const sourceFile = enclosingDeclaration.getSourceFile(); const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); let body; @@ -1652,7 +1652,10 @@ namespace ts.Completions { return firstDefined(symbols, (symbol, index): SymbolCompletion | undefined => { const origin = symbolToOriginInfoMap[index]; const info = getCompletionEntryDisplayNameForSymbol(symbol, getEmitScriptTarget(compilerOptions), origin, completionKind, completionData.isJsxIdentifierExpected); - return info && info.name === entryId.name && (entryId.source === CompletionSource.ClassMemberSnippet && symbol.flags & SymbolFlags.ClassMember || getSourceFromOrigin(origin) === entryId.source) + return info && info.name === entryId.name && ( + entryId.source === CompletionSource.ClassMemberSnippet && symbol.flags & SymbolFlags.ClassMember + || entryId.source === CompletionSource.ObjectLiteralMethodSnippet && symbol.flags & (SymbolFlags.Property | SymbolFlags.Method) + || getSourceFromOrigin(origin) === entryId.source) ? { type: "symbol" as const, symbol, location, origin, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } : undefined; }) || { type: "none" }; @@ -1788,6 +1791,27 @@ namespace ts.Completions { } } + if (source === CompletionSource.ObjectLiteralMethodSnippet) { + const enclosingDeclaration = tryGetObjectLikeCompletionContainer(contextToken) as ObjectLiteralExpression; + const { importAdder } = getEntryForObjectLiteralFunctionCompletion( + symbol, + name, + enclosingDeclaration, + program, + host, + compilerOptions, + preferences, + formatContext)!; + const changes = textChanges.ChangeTracker.with({ host, formatContext, preferences }, importAdder.writeFixes); + return { + sourceDisplay: undefined, + codeActions: [{ + changes, + description: diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]), + }], + }; + } + if (originIsTypeOnlyAlias(origin)) { const codeAction = codefix.getPromoteTypeOnlyCompletionAction( sourceFile, diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod.ts b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts similarity index 89% rename from tests/cases/fourslash/completionsObjectLiteralMethod.ts rename to tests/cases/fourslash/completionsObjectLiteralMethod1.ts index df00acd1a68f3..c6dfdf9d097a5 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts @@ -42,7 +42,7 @@ verify.completions({ { name: "bar", sortText: completion.SortText.LocationPriority, - source: "ObjectLiteralMethodSnippet/", + source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "bar(x: number): void {\n},", }, { @@ -62,7 +62,7 @@ verify.completions({ { name: "bar", sortText: completion.SortText.LocationPriority, - source: "ObjectLiteralMethodSnippet/", + source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "bar(x: number): void {\n},", }, { @@ -73,7 +73,7 @@ verify.completions({ { name: "foo", sortText: completion.SortText.LocationPriority, - source: "ObjectLiteralMethodSnippet/", + source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "foo(x: string): string {\n},", }, { @@ -108,7 +108,7 @@ verify.completions({ { name: "\"space bar\"", sortText: completion.SortText.LocationPriority, - source: "ObjectLiteralMethodSnippet/", + source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "\"space bar\"(): string {\n},", }, { @@ -128,7 +128,7 @@ verify.completions({ { name: "bar", sortText: completion.SortText.LocationPriority, - source: "ObjectLiteralMethodSnippet/", + source: completion.CompletionSource.ObjectLiteralMethodSnippet, isSnippet: true, insertText: "bar(x: number): void {\n $0\n},", }, diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts new file mode 100644 index 0000000000000..e6f0519c50f9b --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts @@ -0,0 +1,52 @@ +/// + +// @newline: LF +// @Filename: a.ts +////export interface IFoo { +//// bar(x: number): void; +////} + +// @Filename: b.ts +////import { IFoo } from "./a"; +////export interface IBar { +//// foo(f: IFoo): void; +////} + +// @Filename: c.ts +////import { IBar } from "./b"; +////const obj: IBar = { +//// /*a*/ +////} + +verify.completions({ + marker: "a", + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + }, + includes: [ + { + name: "foo", + sortText: completion.SortText.LocationPriority, + source: completion.CompletionSource.ObjectLiteralMethodSnippet, + insertText: "foo(f: IFoo): void {\n},", + hasAction: true, + }, + ], +}); + +verify.applyCodeActionFromCompletion("a", { + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + }, + name: "foo", + source: completion.CompletionSource.ObjectLiteralMethodSnippet, + description: "Includes imports of types referenced by 'foo'", + newFileContent: +`import { IFoo } from "./a"; +import { IBar } from "./b"; +const obj: IBar = { + +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 955d87c6f6346..a9b4ab383dce3 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -859,6 +859,7 @@ declare namespace completion { ThisProperty = "ThisProperty/", ClassMemberSnippet = "ClassMemberSnippet/", TypeOnlyAlias = "TypeOnlyAlias/", + ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/", } export const globalThisEntry: Entry; export const undefinedVarEntry: Entry; From 8550bba8f597281ed8041cdbafc19a829a942a0c Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 7 Mar 2022 12:58:06 -0800 Subject: [PATCH 12/36] add user preference option and fix ordering of entries --- src/compiler/types.ts | 1 + src/services/completions.ts | 49 ++++++++++--------- .../completionsObjectLiteralMethod1.ts | 35 ++++++++----- .../completionsObjectLiteralMethod2.ts | 7 +++ tests/cases/fourslash/fourslash.ts | 1 + 5 files changed, 59 insertions(+), 34 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 54936e787bc7a..83857ddfe289e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -8689,6 +8689,7 @@ namespace ts { readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; + readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ diff --git a/src/services/completions.ts b/src/services/completions.ts index 324d7c44ad836..d45d7612e36ba 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -74,7 +74,7 @@ namespace ts.Completions { Nullable = 1 << 4, ResolvedExport = 1 << 5, TypeOnlyAlias = 1 << 6, - ObjectLiteralMember = 1 << 7, + ObjectLiteralMethod = 1 << 7, SymbolMemberNoExport = SymbolMember, SymbolMemberExport = SymbolMember | Export, @@ -142,8 +142,8 @@ namespace ts.Completions { return !!(origin && origin.kind & SymbolOriginInfoKind.TypeOnlyAlias); } - function originIsObjectLiteralMember(origin: SymbolOriginInfo | undefined): boolean { - return !!(origin && origin.kind & SymbolOriginInfoKind.ObjectLiteralMember); + function originIsObjectLiteralMethod(origin: SymbolOriginInfo | undefined): boolean { + return !!(origin && origin.kind & SymbolOriginInfoKind.ObjectLiteralMethod); } interface UniqueNameSet { @@ -782,17 +782,20 @@ namespace ts.Completions { } } - const objLit = isObjectLiteralFunctionPropertyCompletion(symbol, origin, location, contextToken, program.getTypeChecker()); - if (objLit) { - let importAdder; - const entry = getEntryForObjectLiteralFunctionCompletion(symbol, name, objLit, program, host, options, preferences, formatContext); - if (!entry) { - return undefined; - } - ({ insertText, isSnippet, importAdder, sourceDisplay } = entry); - source = CompletionSource.ObjectLiteralMethodSnippet; // >> Needed for disambiguiation from normal completions (with just the method name). - if (importAdder?.hasFixes()) { - hasAction = true; // >> TODO: implement this computation in `getCompletionEntryDetails`. + if (preferences.includeCompletionsWithObjectLiteralMethodSnippets && + preferences.includeCompletionsWithInsertText) { + const objLit = isObjectLiteralFunctionPropertyCompletion(symbol, origin, location, contextToken, program.getTypeChecker()); + if (objLit) { + let importAdder; + const entry = getEntryForObjectLiteralFunctionCompletion(symbol, name, objLit, program, host, options, preferences, formatContext); + if (!entry) { + return undefined; + } + ({ insertText, isSnippet, importAdder, sourceDisplay } = entry); + source = CompletionSource.ObjectLiteralMethodSnippet; // >> Needed for disambiguiation from normal completions (with just the method name). + if (importAdder?.hasFixes()) { + hasAction = true; + } } } @@ -1065,7 +1068,7 @@ namespace ts.Completions { } function isObjectLiteralFunctionPropertyCompletion(symbol: Symbol, origin: SymbolOriginInfo | undefined, location: Node, contextToken: Node | undefined, checker: TypeChecker): ObjectLiteralExpression | undefined { - if (!originIsObjectLiteralMember(origin)) { + if (!originIsObjectLiteralMethod(origin)) { return undefined; } // TODO: support JS files. @@ -1464,7 +1467,7 @@ namespace ts.Completions { const symbol = symbols[i]; const origin = symbolToOriginInfoMap?.[i]; const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind, !!jsxIdentifierExpected); - if (!info || uniques.get(info.name) || kind === CompletionKind.Global && symbolToSortTextIdMap && !shouldIncludeSymbol(symbol, symbolToSortTextIdMap)) { + if (!info || (uniques.get(info.name) && (!origin || !originIsObjectLiteralMethod(origin))) || kind === CompletionKind.Global && symbolToSortTextIdMap && !shouldIncludeSymbol(symbol, symbolToSortTextIdMap)) { continue; } @@ -2843,10 +2846,11 @@ namespace ts.Completions { } /* Mutates `symbols` and `symbolToOriginInfoMap`. */ - function collectObjectLiteralSymbols(members: Symbol[]) { - // >> TODO: only do this if user preferences for this completion scenario is enabled + function collectObjectLiteralMethodSymbols(members: Symbol[]) { + // >> TODO: move symbol for method checks here instead of later, if possible + members = members.slice(); // Needed in case `symbol` and `members` are the same array. for (const member of members) { - const origin = { kind: SymbolOriginInfoKind.ObjectLiteralMember }; + const origin = { kind: SymbolOriginInfoKind.ObjectLiteralMethod }; symbolToOriginInfoMap[symbols.length] = origin; symbols.push(member); } @@ -3083,10 +3087,11 @@ namespace ts.Completions { if (typeMembers && typeMembers.length > 0) { // Add filtered items to the completion list const filteredMembers = filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers)); - if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { - collectObjectLiteralSymbols(filteredMembers); - } symbols = concatenate(symbols, filteredMembers); + if (preferences.includeCompletionsWithObjectLiteralMethodSnippets + && objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { + collectObjectLiteralMethodSymbols(filteredMembers); + } } setSortTextToOptionalMember(); diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts index c6dfdf9d097a5..36903e5bc29e5 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts @@ -37,18 +37,19 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { name: "bar", sortText: completion.SortText.LocationPriority, - source: completion.CompletionSource.ObjectLiteralMethodSnippet, - insertText: "bar(x: number): void {\n},", + insertText: undefined, }, { name: "bar", sortText: completion.SortText.LocationPriority, - insertText: undefined, + source: completion.CompletionSource.ObjectLiteralMethodSnippet, + insertText: "bar(x: number): void {\n},", }, ], }); @@ -57,8 +58,14 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ + { + name: "bar", + sortText: completion.SortText.LocationPriority, + insertText: undefined, + }, { name: "bar", sortText: completion.SortText.LocationPriority, @@ -66,7 +73,7 @@ verify.completions({ insertText: "bar(x: number): void {\n},", }, { - name: "bar", + name: "foo", sortText: completion.SortText.LocationPriority, insertText: undefined, }, @@ -76,11 +83,6 @@ verify.completions({ source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "foo(x: string): string {\n},", }, - { - name: "foo", - sortText: completion.SortText.LocationPriority, - insertText: undefined, - } ], }); verify.completions({ @@ -88,6 +90,7 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { @@ -95,6 +98,7 @@ verify.completions({ sortText: completion.SortText.LocationPriority, // no declaration insert text, because this property has overloads insertText: undefined, + // >> TODO: use `exact` instead of `includes` here }, ], }); @@ -103,18 +107,19 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { name: "\"space bar\"", sortText: completion.SortText.LocationPriority, - source: completion.CompletionSource.ObjectLiteralMethodSnippet, - insertText: "\"space bar\"(): string {\n},", + insertText: undefined, }, { name: "\"space bar\"", sortText: completion.SortText.LocationPriority, - insertText: undefined, + source: completion.CompletionSource.ObjectLiteralMethodSnippet, + insertText: "\"space bar\"(): string {\n},", }, ], }); @@ -123,8 +128,14 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: true, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ + { + name: "bar", + sortText: completion.SortText.LocationPriority, + insertText: undefined, + }, { name: "bar", sortText: completion.SortText.LocationPriority, diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts index e6f0519c50f9b..c6c7d03d8ebfa 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts @@ -23,8 +23,14 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ + { + name: "foo", + sortText: completion.SortText.LocationPriority, + insertText: undefined, + }, { name: "foo", sortText: completion.SortText.LocationPriority, @@ -39,6 +45,7 @@ verify.applyCodeActionFromCompletion("a", { preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, name: "foo", source: completion.CompletionSource.ObjectLiteralMethodSnippet, diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index a9b4ab383dce3..6e969e41c7fef 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -647,6 +647,7 @@ declare namespace FourSlashInterface { readonly includeCompletionsWithSnippetText?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; + readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; /** @deprecated use `includeCompletionsWithInsertText` */ readonly includeInsertTextCompletions?: boolean; From 78c7d9e9b4385a49538cb3454d2ee4f18d4ea304 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 7 Mar 2022 14:01:21 -0800 Subject: [PATCH 13/36] cleanup --- src/services/completions.ts | 143 ++++++------------ .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../completionsObjectLiteralMethod1.ts | 3 +- 4 files changed, 50 insertions(+), 98 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index d45d7612e36ba..adc17641d8e9b 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -782,20 +782,19 @@ namespace ts.Completions { } } - if (preferences.includeCompletionsWithObjectLiteralMethodSnippets && - preferences.includeCompletionsWithInsertText) { - const objLit = isObjectLiteralFunctionPropertyCompletion(symbol, origin, location, contextToken, program.getTypeChecker()); - if (objLit) { - let importAdder; - const entry = getEntryForObjectLiteralFunctionCompletion(symbol, name, objLit, program, host, options, preferences, formatContext); - if (!entry) { - return undefined; - } - ({ insertText, isSnippet, importAdder, sourceDisplay } = entry); - source = CompletionSource.ObjectLiteralMethodSnippet; // >> Needed for disambiguiation from normal completions (with just the method name). - if (importAdder?.hasFixes()) { - hasAction = true; - } + if (preferences.includeCompletionsWithObjectLiteralMethodSnippets + && preferences.includeCompletionsWithInsertText + && origin && originIsObjectLiteralMethod(origin)) { + const objectLiteral = cast(tryGetObjectLikeCompletionContainer(contextToken), isObjectLiteralExpression); + let importAdder; + const entry = getEntryForObjectLiteralMethodCompletion(symbol, name, objectLiteral, program, host, options, preferences, formatContext); + if (!entry) { + return undefined; + } + ({ insertText, isSnippet, importAdder, sourceDisplay } = entry); + source = CompletionSource.ObjectLiteralMethodSnippet; + if (importAdder.hasFixes()) { + hasAction = true; } } @@ -1067,41 +1066,7 @@ namespace ts.Completions { return undefined; } - function isObjectLiteralFunctionPropertyCompletion(symbol: Symbol, origin: SymbolOriginInfo | undefined, location: Node, contextToken: Node | undefined, checker: TypeChecker): ObjectLiteralExpression | undefined { - if (!originIsObjectLiteralMethod(origin)) { - return undefined; - } - // TODO: support JS files. - if (isInJSFile(location)) { - return undefined; - } - - /* - For an object type - `type Foo = { - bar(x: number): void; - foo: (x: string) => string; - }`, - `bar` will have symbol flag `Method`, - `foo` will have symbol flag `Property`. - */ - if (!(symbol.flags & (SymbolFlags.Property | SymbolFlags.Method))) { - return undefined; - } - - if (symbol.flags & SymbolFlags.Property) { - const type = checker.getTypeOfSymbol(symbol); - // We ignore non-function properties. - if (!type.getCallSignatures().length) { - return undefined; - } - } - // Check if we are in a position for object literal completion. - const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); - return objectLikeContainer && tryCast(objectLikeContainer, isObjectLiteralExpression); - } - - function getEntryForObjectLiteralFunctionCompletion( + function getEntryForObjectLiteralMethodCompletion( symbol: Symbol, name: string, enclosingDeclaration: ObjectLiteralExpression, @@ -1128,7 +1093,7 @@ namespace ts.Completions { body = factory.createBlock([], /* multiline */ true); } - const method = createObjectLiteralFunctionProperty(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body); + const method = createObjectLiteralMethod(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body); if (!method) { return undefined; } @@ -1160,7 +1125,7 @@ namespace ts.Completions { }; - function createObjectLiteralFunctionProperty( + function createObjectLiteralMethod( symbol: Symbol, enclosingDeclaration: ObjectLiteralExpression, sourceFile: SourceFile, @@ -1238,8 +1203,6 @@ namespace ts.Completions { return { printSnippetList, printAndFormatSnippetList, - printSnippet, - printAndFormatSnippet, }; /* Snippet-escaping version of `printer.printList`. */ @@ -1282,47 +1245,6 @@ namespace ts.Completions { }); return textChanges.applyChanges(syntheticFile.text, changes); } - - // >> TODO: do we need this? - function printSnippet( - hint: EmitHint, - node: Node, - sourceFile: SourceFile | undefined, - ): string { - writer.clear(); - printer.writeNode(hint, node, sourceFile, writer); - return writer.getText(); - } - - function printAndFormatSnippet( - hint: EmitHint, - node: Node, - sourceFile: SourceFile, - formatContext: formatting.FormatContext, - ): string { - const syntheticFile = { - text: printSnippet( - hint, - node, - sourceFile), - getLineAndCharacterOfPosition(pos: number) { - return getLineAndCharacterOfPosition(this, pos); - }, - }; - - const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); - - const nodeWithPos = textChanges.assignPositionsToNode(node); - const changes = formatting.formatNodeGivenIndentation( - nodeWithPos, - syntheticFile, - sourceFile.languageVariant, - /* indentation */ 0, - /* delta */ 0, - { ...formatContext, options: formatOptions }); - return textChanges.applyChanges(syntheticFile.text, changes); - } - } function originToCompletionEntryData(origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport): CompletionEntryData | undefined { @@ -1796,7 +1718,7 @@ namespace ts.Completions { if (source === CompletionSource.ObjectLiteralMethodSnippet) { const enclosingDeclaration = tryGetObjectLikeCompletionContainer(contextToken) as ObjectLiteralExpression; - const { importAdder } = getEntryForObjectLiteralFunctionCompletion( + const { importAdder } = getEntryForObjectLiteralMethodCompletion( symbol, name, enclosingDeclaration, @@ -2847,15 +2769,44 @@ namespace ts.Completions { /* Mutates `symbols` and `symbolToOriginInfoMap`. */ function collectObjectLiteralMethodSymbols(members: Symbol[]) { - // >> TODO: move symbol for method checks here instead of later, if possible + // TODO: support JS files. + if (isInJSFile(location)) { + return false; + } members = members.slice(); // Needed in case `symbol` and `members` are the same array. for (const member of members) { + if (!isObjectLiteralMethodSymbol(member)) { + continue; + } const origin = { kind: SymbolOriginInfoKind.ObjectLiteralMethod }; symbolToOriginInfoMap[symbols.length] = origin; symbols.push(member); } } + function isObjectLiteralMethodSymbol(symbol: Symbol): boolean { + /* + For an object type + `type Foo = { + bar(x: number): void; + foo: (x: string) => string; + }`, + `bar` will have symbol flag `Method`, + `foo` will have symbol flag `Property`. + */ + if (!(symbol.flags & (SymbolFlags.Property | SymbolFlags.Method))) { + return false; + } + if (symbol.flags & SymbolFlags.Property) { + const type = program.getTypeChecker().getTypeOfSymbol(symbol); + // We ignore non-function properties. + if (!type.getCallSignatures().length) { + return false; + } + } + return true; + } + /** * Finds the first node that "embraces" the position, so that one may * accurately aggregate locals from the closest containing scope. diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 255fb6a1305c7..e52fd1d5d0009 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4049,6 +4049,7 @@ declare namespace ts { readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; + readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 63a6a9bc49be3..77753ad66027b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4049,6 +4049,7 @@ declare namespace ts { readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; + readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts index 36903e5bc29e5..cdd399e43db7a 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts @@ -92,13 +92,12 @@ verify.completions({ includeCompletionsWithSnippetText: false, includeCompletionsWithObjectLiteralMethodSnippets: true, }, - includes: [ + exact: [ { name: "buzz", sortText: completion.SortText.LocationPriority, // no declaration insert text, because this property has overloads insertText: undefined, - // >> TODO: use `exact` instead of `includes` here }, ], }); From 1f67d97812e9b8829e2b0fc2ac280216277c34ef Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 7 Mar 2022 16:26:07 -0800 Subject: [PATCH 14/36] don't return code actions for no import fixes --- src/services/completions.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index adc17641d8e9b..89a1da740304a 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1727,14 +1727,16 @@ namespace ts.Completions { compilerOptions, preferences, formatContext)!; - const changes = textChanges.ChangeTracker.with({ host, formatContext, preferences }, importAdder.writeFixes); - return { - sourceDisplay: undefined, - codeActions: [{ - changes, - description: diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]), - }], - }; + if (importAdder.hasFixes()) { + const changes = textChanges.ChangeTracker.with({ host, formatContext, preferences }, importAdder.writeFixes); + return { + sourceDisplay: undefined, + codeActions: [{ + changes, + description: diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]), + }], + }; + } } if (originIsTypeOnlyAlias(origin)) { From 814561f64945eda6e31bacc78720035de9d3205e Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 8 Mar 2022 16:56:29 -0800 Subject: [PATCH 15/36] make sortText lower priority for snippets --- src/services/completions.ts | 1 + .../cases/fourslash/completionsObjectLiteralMethod1.ts | 10 +++++----- .../cases/fourslash/completionsObjectLiteralMethod2.ts | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index d10501c4e993f..7dd9ce429979a 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -793,6 +793,7 @@ namespace ts.Completions { } ({ insertText, isSnippet, importAdder, sourceDisplay } = entry); source = CompletionSource.ObjectLiteralMethodSnippet; + sortText = (parseInt(sortText, 10) + 1).toString() as SortText; if (importAdder.hasFixes()) { hasAction = true; } diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts index cdd399e43db7a..e36f966b6a894 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts @@ -47,7 +47,7 @@ verify.completions({ }, { name: "bar", - sortText: completion.SortText.LocationPriority, + sortText: completion.SortText.OptionalMember, source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "bar(x: number): void {\n},", }, @@ -68,7 +68,7 @@ verify.completions({ }, { name: "bar", - sortText: completion.SortText.LocationPriority, + sortText: completion.SortText.OptionalMember, source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "bar(x: number): void {\n},", }, @@ -79,7 +79,7 @@ verify.completions({ }, { name: "foo", - sortText: completion.SortText.LocationPriority, + sortText: completion.SortText.OptionalMember, source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "foo(x: string): string {\n},", }, @@ -116,7 +116,7 @@ verify.completions({ }, { name: "\"space bar\"", - sortText: completion.SortText.LocationPriority, + sortText: completion.SortText.OptionalMember, source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "\"space bar\"(): string {\n},", }, @@ -137,7 +137,7 @@ verify.completions({ }, { name: "bar", - sortText: completion.SortText.LocationPriority, + sortText: completion.SortText.OptionalMember, source: completion.CompletionSource.ObjectLiteralMethodSnippet, isSnippet: true, insertText: "bar(x: number): void {\n $0\n},", diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts index c6c7d03d8ebfa..49e2dcaef00a4 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts @@ -33,7 +33,7 @@ verify.completions({ }, { name: "foo", - sortText: completion.SortText.LocationPriority, + sortText: completion.SortText.OptionalMember, source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "foo(f: IFoo): void {\n},", hasAction: true, From 088904e71251241f45f77c4fb84fbf688c3d9a9e Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 9 Mar 2022 12:09:54 -0800 Subject: [PATCH 16/36] get rid of flag --- src/compiler/types.ts | 1 - src/services/completions.ts | 6 ++---- tests/cases/fourslash/completionsObjectLiteralMethod1.ts | 5 ----- tests/cases/fourslash/completionsObjectLiteralMethod2.ts | 2 -- tests/cases/fourslash/fourslash.ts | 1 - 5 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2059f5a48072a..dc31b5c79cf2f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -8710,7 +8710,6 @@ namespace ts { readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; - readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ diff --git a/src/services/completions.ts b/src/services/completions.ts index 7dd9ce429979a..0545b05485ae7 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -782,8 +782,7 @@ namespace ts.Completions { } } - if (preferences.includeCompletionsWithObjectLiteralMethodSnippets - && preferences.includeCompletionsWithInsertText + if (preferences.includeCompletionsWithInsertText && origin && originIsObjectLiteralMethod(origin)) { const objectLiteral = cast(tryGetObjectLikeCompletionContainer(contextToken), isObjectLiteralExpression); let importAdder; @@ -3043,8 +3042,7 @@ namespace ts.Completions { // Add filtered items to the completion list const filteredMembers = filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers)); symbols = concatenate(symbols, filteredMembers); - if (preferences.includeCompletionsWithObjectLiteralMethodSnippets - && objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { + if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { collectObjectLiteralMethodSymbols(filteredMembers); } } diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts index e36f966b6a894..fc713b7136c0c 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts @@ -37,7 +37,6 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, - includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { @@ -58,7 +57,6 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, - includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { @@ -90,7 +88,6 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, - includeCompletionsWithObjectLiteralMethodSnippets: true, }, exact: [ { @@ -106,7 +103,6 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, - includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { @@ -127,7 +123,6 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: true, - includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts index 49e2dcaef00a4..05c929513e410 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts @@ -23,7 +23,6 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, - includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { @@ -45,7 +44,6 @@ verify.applyCodeActionFromCompletion("a", { preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, - includeCompletionsWithObjectLiteralMethodSnippets: true, }, name: "foo", source: completion.CompletionSource.ObjectLiteralMethodSnippet, diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 6e969e41c7fef..a9b4ab383dce3 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -647,7 +647,6 @@ declare namespace FourSlashInterface { readonly includeCompletionsWithSnippetText?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; - readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; /** @deprecated use `includeCompletionsWithInsertText` */ readonly includeInsertTextCompletions?: boolean; From 4e632763501adc41879d9b0a93a0f425767bd709 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 9 Mar 2022 12:18:34 -0800 Subject: [PATCH 17/36] use optional member sort text --- src/services/completions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 0545b05485ae7..faddb42e50723 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -792,7 +792,7 @@ namespace ts.Completions { } ({ insertText, isSnippet, importAdder, sourceDisplay } = entry); source = CompletionSource.ObjectLiteralMethodSnippet; - sortText = (parseInt(sortText, 10) + 1).toString() as SortText; + sortText = SortText.OptionalMember; if (importAdder.hasFixes()) { hasAction = true; } From 474d9a9d61d817ecdcec78e2c37f54a7bc255b86 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 9 Mar 2022 12:36:11 -0800 Subject: [PATCH 18/36] update baselines --- tests/baselines/reference/api/tsserverlibrary.d.ts | 1 - tests/baselines/reference/api/typescript.d.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c50668a39b391..2cc99e632b4b5 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4067,7 +4067,6 @@ declare namespace ts { readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; - readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index eadb650e09d40..588f4a2d550f7 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4067,7 +4067,6 @@ declare namespace ts { readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; - readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ From b5d05c0a9a3cd043805b818ae40bdcfeaf5492d5 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 9 Mar 2022 13:22:42 -0800 Subject: [PATCH 19/36] don't collect method symbols if insert text is not supported --- src/services/completions.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index faddb42e50723..4d2ea4390b457 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -782,8 +782,7 @@ namespace ts.Completions { } } - if (preferences.includeCompletionsWithInsertText - && origin && originIsObjectLiteralMethod(origin)) { + if (origin && originIsObjectLiteralMethod(origin)) { const objectLiteral = cast(tryGetObjectLikeCompletionContainer(contextToken), isObjectLiteralExpression); let importAdder; const entry = getEntryForObjectLiteralMethodCompletion(symbol, name, objectLiteral, program, host, options, preferences, formatContext); @@ -2771,10 +2770,10 @@ namespace ts.Completions { } /* Mutates `symbols` and `symbolToOriginInfoMap`. */ - function collectObjectLiteralMethodSymbols(members: Symbol[]) { + function collectObjectLiteralMethodSymbols(members: Symbol[]): void { // TODO: support JS files. if (isInJSFile(location)) { - return false; + return; } members = members.slice(); // Needed in case `symbol` and `members` are the same array. for (const member of members) { @@ -3042,7 +3041,7 @@ namespace ts.Completions { // Add filtered items to the completion list const filteredMembers = filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers)); symbols = concatenate(symbols, filteredMembers); - if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { + if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression && preferences.includeCompletionsWithInsertText) { collectObjectLiteralMethodSymbols(filteredMembers); } } From 6914576e61256a3029664a1b8fdc1203b71b185e Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 16 Mar 2022 15:28:30 -0700 Subject: [PATCH 20/36] remove comment --- src/services/codefixes/helpers.ts | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index b3703f9e7a2a2..5c06f2161437a 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -146,7 +146,6 @@ namespace ts.codefix { break; } - // >> TODO: call `try get auto importable reference from type node` on type nodes? if (declarations.length === 1) { Debug.assert(signatures.length === 1, "One declaration implies one signature"); const signature = signatures[0]; @@ -178,29 +177,6 @@ namespace ts.codefix { } } - // export function addNewNodeObjectLiteralProperties( - // // changes: textChanges.ChangeTracker, - // // context: CodeFixContextBase, - // sourceFile: SourceFile, - // program: Program, - // preferences: UserPreferences, - // host: LanguageServiceHost, - // // info: ObjectLiteralInfo, - // properties: Symbol[], - // addNodes: (nodes: PropertyAssignment[]) => void, - // ): void { - // const importAdder = createImportAdder(sourceFile, program, preferences, host); - // const quotePreference = getQuotePreference(sourceFile, preferences); - // const target = getEmitScriptTarget(program.getCompilerOptions()); - // const checker = program.getTypeChecker(); - // const props = map(properties, prop => { - // const initializer = tryGetValueFromType(context, checker, importAdder, quotePreference, checker.getTypeOfSymbol(prop)); - // return factory.createPropertyAssignment(createPropertyNameNodeForIdentifierOrLiteral(prop.name, target, quotePreference === QuotePreference.Single), initializer); - // }); - - // addNodes(props); - // } - export function createSignatureDeclarationFromSignature( kind: SyntaxKind.MethodDeclaration | SyntaxKind.FunctionExpression | SyntaxKind.ArrowFunction, context: TypeConstructionContext, From 0e0ae05962a98a383bd477efa4f0540ff3d81403 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 16 Mar 2022 17:15:42 -0700 Subject: [PATCH 21/36] return undefined if type is not function type --- src/services/completions.ts | 15 +++--- .../completionsObjectLiteralMethod3.ts | 49 +++++++++++++++++++ 2 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 tests/cases/fourslash/completionsObjectLiteralMethod3.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 4d2ea4390b457..f3692756f7d46 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1157,25 +1157,24 @@ namespace ts.Completions { // We don't support overloads in object literals. return undefined; } - let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); + let typeNode: FunctionTypeNode | TypeNode | undefined = checker.typeToTypeNode(type, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); + if (!typeNode || !isFunctionTypeNode(typeNode)) { + return undefined; + } const importableReference = codefix.tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); if (importableReference) { typeNode = importableReference.typeNode; codefix.importSymbols(importAdder, importableReference.symbols); } - if (!typeNode) { - return undefined; - } - Debug.assertNode(typeNode, isFunctionTypeNode); return factory.createMethodDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*asteriskToken*/ undefined, name, /*questionToken*/ undefined, - typeNode.typeParameters, - typeNode.parameters, - typeNode.type, + (typeNode as FunctionTypeNode).typeParameters, + (typeNode as FunctionTypeNode).parameters, + (typeNode as FunctionTypeNode).type, body); } default: diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod3.ts b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts new file mode 100644 index 0000000000000..1899a453edb73 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts @@ -0,0 +1,49 @@ +/// + +// @newline: LF +// @Filename: a.ts +////interface I1{ +//// M(x: number): void; +////} +//// +////interface I2{ +//// M(x: number): void; +////} +//// +////const u: I1 | I2 = { +//// /*a*/ +////} +//// +////const i: I1 & I2 = { +//// /*b*/ +////} + +verify.completions({ + marker: "a", + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + }, + includes: [ + { + name: "M", + sortText: completion.SortText.LocationPriority, + insertText: undefined, + }, + ], +}); +verify.completions({ + marker: "b", + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + }, + includes: [ + { + name: "M", + sortText: completion.SortText.LocationPriority, + insertText: undefined, + }, + ], +}); + From 36eac73e89c2ced5ec1879cf9455a9301ad3ff09 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 16 Mar 2022 17:20:59 -0700 Subject: [PATCH 22/36] only slice if needed --- src/services/completions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index f3692756f7d46..867fe56b6f477 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -2774,7 +2774,9 @@ namespace ts.Completions { if (isInJSFile(location)) { return; } - members = members.slice(); // Needed in case `symbol` and `members` are the same array. + if (members === symbols) { + members = members.slice(); + } for (const member of members) { if (!isObjectLiteralMethodSymbol(member)) { continue; From b9cb70323027fdff4841e07a70d86af8c607e500 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 17 Mar 2022 14:48:16 -0700 Subject: [PATCH 23/36] use union reduction; more test cases --- src/harness/fourslashImpl.ts | 24 ++--- src/services/completions.ts | 5 +- .../completionsObjectLiteralMethod3.ts | 89 +++++++++++++++++-- 3 files changed, 99 insertions(+), 19 deletions(-) diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 47ed0e4a1aed8..7bde35b3113e1 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -960,7 +960,7 @@ namespace FourSlash { expected = typeof expected === "string" ? { name: expected } : expected; if (actual.insertText !== expected.insertText) { - this.raiseError(`Completion insert text did not match: ${showTextDiff(expected.insertText || "", actual.insertText || "")}`); + this.raiseError(`At entry ${actual.name}: Completion insert text did not match: ${showTextDiff(expected.insertText || "", actual.insertText || "")}`); } const convertedReplacementSpan = expected.replacementSpan && ts.createTextSpanFromRange(expected.replacementSpan); if (convertedReplacementSpan?.length) { @@ -968,28 +968,28 @@ namespace FourSlash { assert.deepEqual(actual.replacementSpan, convertedReplacementSpan); } catch { - this.raiseError(`Expected completion replacementSpan to be ${stringify(convertedReplacementSpan)}, got ${stringify(actual.replacementSpan)}`); + this.raiseError(`At entry ${actual.name}: Expected completion replacementSpan to be ${stringify(convertedReplacementSpan)}, got ${stringify(actual.replacementSpan)}`); } } if (expected.kind !== undefined || expected.kindModifiers !== undefined) { - assert.equal(actual.kind, expected.kind, `Expected 'kind' for ${actual.name} to match`); - assert.equal(actual.kindModifiers, expected.kindModifiers || "", `Expected 'kindModifiers' for ${actual.name} to match`); + assert.equal(actual.kind, expected.kind, `At entry ${actual.name}: Expected 'kind' for ${actual.name} to match`); + assert.equal(actual.kindModifiers, expected.kindModifiers || "", `At entry ${actual.name}: Expected 'kindModifiers' for ${actual.name} to match`); } if (expected.isFromUncheckedFile !== undefined) { - assert.equal(actual.isFromUncheckedFile, expected.isFromUncheckedFile, "Expected 'isFromUncheckedFile' properties to match"); + assert.equal(actual.isFromUncheckedFile, expected.isFromUncheckedFile, `At entry ${actual.name}: Expected 'isFromUncheckedFile' properties to match`); } if (expected.isPackageJsonImport !== undefined) { - assert.equal(actual.isPackageJsonImport, expected.isPackageJsonImport, "Expected 'isPackageJsonImport' properties to match"); + assert.equal(actual.isPackageJsonImport, expected.isPackageJsonImport, `At entry ${actual.name}: Expected 'isPackageJsonImport' properties to match`); } - assert.equal(actual.hasAction, expected.hasAction, `Expected 'hasAction' properties to match`); - assert.equal(actual.isRecommended, expected.isRecommended, `Expected 'isRecommended' properties to match'`); - assert.equal(actual.isSnippet, expected.isSnippet, `Expected 'isSnippet' properties to match`); - assert.equal(actual.source, expected.source, `Expected 'source' values to match`); - assert.equal(actual.sortText, expected.sortText || ts.Completions.SortText.LocationPriority, `Expected 'sortText' properties to match`); + assert.equal(actual.hasAction, expected.hasAction, `At entry ${actual.name}: Expected 'hasAction' properties to match`); + assert.equal(actual.isRecommended, expected.isRecommended, `At entry ${actual.name}: Expected 'isRecommended' properties to match'`); + assert.equal(actual.isSnippet, expected.isSnippet, `At entry ${actual.name}: Expected 'isSnippet' properties to match`); + assert.equal(actual.source, expected.source, `At entry ${actual.name}: Expected 'source' values to match`); + assert.equal(actual.sortText, expected.sortText || ts.Completions.SortText.LocationPriority, `At entry ${actual.name}: Expected 'sortText' properties to match`); if (expected.sourceDisplay && actual.sourceDisplay) { - assert.equal(ts.displayPartsToString(actual.sourceDisplay), expected.sourceDisplay, `Expected 'sourceDisplay' properties to match`); + assert.equal(ts.displayPartsToString(actual.sourceDisplay), expected.sourceDisplay, `At entry ${actual.name}: Expected 'sourceDisplay' properties to match`); } if (expected.text !== undefined) { diff --git a/src/services/completions.ts b/src/services/completions.ts index 867fe56b6f477..04ebbee290f1a 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1157,7 +1157,10 @@ namespace ts.Completions { // We don't support overloads in object literals. return undefined; } - let typeNode: FunctionTypeNode | TypeNode | undefined = checker.typeToTypeNode(type, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); + const effectiveType = type.flags & TypeFlags.Union && (type as UnionType).types.length < 10 + ? checker.getUnionType((type as UnionType).types, UnionReduction.Subtype) + : type; + let typeNode = checker.typeToTypeNode(effectiveType, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); if (!typeNode || !isFunctionTypeNode(typeNode)) { return undefined; } diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod3.ts b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts index 1899a453edb73..9dc0d37aa954a 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod3.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts @@ -2,21 +2,35 @@ // @newline: LF // @Filename: a.ts -////interface I1{ +////interface I1 { //// M(x: number): void; ////} -//// -////interface I2{ +////interface I2 { //// M(x: number): void; ////} -//// ////const u: I1 | I2 = { //// /*a*/ ////} -//// ////const i: I1 & I2 = { //// /*b*/ ////} +////interface U1 { +//// M(x: number): string; +////} +////interface U2 { +//// M(x: string): number; +////} +////const o: U1 | U2 = { +//// /*c*/ +////} +////interface Op { +//// M?(x: number): void; +//// N: (x: string) => void | null | undefined; +//// O?: () => void; +////} +////const op: Op = { +//// /*d*/ +////} verify.completions({ marker: "a", @@ -30,6 +44,12 @@ verify.completions({ sortText: completion.SortText.LocationPriority, insertText: undefined, }, + { + name: "M", + sortText: completion.SortText.OptionalMember, + source: completion.CompletionSource.ObjectLiteralMethodSnippet, + insertText: "M(x: number): void {\n},", + }, ], }); verify.completions({ @@ -44,6 +64,63 @@ verify.completions({ sortText: completion.SortText.LocationPriority, insertText: undefined, }, + // No signature completion because type of `M` is intersection type ], }); - +verify.completions({ + marker: "c", + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + }, + exact: [ + { + name: "M", + sortText: completion.SortText.LocationPriority, + insertText: undefined, + }, + // No signature completion because type of `M` is intersection type + ], +}); +verify.completions({ + marker: "d", + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + }, + includes: [ + { + name: "M", + sortText: completion.SortText.OptionalMember, + insertText: undefined, + }, + { + name: "M", + sortText: completion.SortText.OptionalMember, + source: completion.CompletionSource.ObjectLiteralMethodSnippet, + insertText: "M(x: number): void {\n},", + }, + { + name: "N", + sortText: completion.SortText.LocationPriority, + insertText: undefined, + }, + { + name: "N", + sortText: completion.SortText.OptionalMember, + source: completion.CompletionSource.ObjectLiteralMethodSnippet, + insertText: "N(x: string): void {\n},", + }, + { + name: "O", + sortText: completion.SortText.OptionalMember, + insertText: undefined, + }, + { + name: "O", + sortText: completion.SortText.OptionalMember, + source: completion.CompletionSource.ObjectLiteralMethodSnippet, + insertText: "O(): void {\n},", + }, + ], +}); \ No newline at end of file From 7c9cbdbfc2b5faafdf1f31a9b3e324685ea291f9 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 21 Mar 2022 15:53:56 -0700 Subject: [PATCH 24/36] WIP: modify sort text system --- src/harness/fourslashInterfaceImpl.ts | 4 +- src/services/completions.ts | 95 ++++++++++--------- .../completionsObjectLiteralMethod1.ts | 22 ++--- 3 files changed, 63 insertions(+), 58 deletions(-) diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 98b61cf2fb55a..46a70651a8b68 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -963,7 +963,7 @@ namespace FourSlashInterface { name, kind: "function", kindModifiers: "deprecated,declare", - sortText: SortText.DeprecatedGlobalsOrKeywords + sortText: "z15" as SortText, }); const varEntry = (name: string): ExpectedCompletionEntryObject => ({ name, @@ -992,7 +992,7 @@ namespace FourSlashInterface { name, kind: "method", kindModifiers: "deprecated,declare", - sortText: SortText.DeprecatedLocationPriority + sortText: "z11" as SortText, }); const propertyEntry = (name: string): ExpectedCompletionEntryObject => ({ name, diff --git a/src/services/completions.ts b/src/services/completions.ts index 04ebbee290f1a..da27517bd43dd 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -18,30 +18,6 @@ namespace ts.Completions { GlobalsOrKeywords = "15", AutoImportSuggestions = "16", JavascriptIdentifiers = "17", - DeprecatedLocalDeclarationPriority = "18", - DeprecatedLocationPriority = "19", - DeprecatedOptionalMember = "20", - DeprecatedMemberDeclaredBySpreadAssignment = "21", - DeprecatedSuggestedClassMembers = "22", - DeprecatedGlobalsOrKeywords = "23", - DeprecatedAutoImportSuggestions = "24" - } - - const enum SortTextId { - LocalDeclarationPriority = 10, - LocationPriority = 11, - OptionalMember = 12, - MemberDeclaredBySpreadAssignment = 13, - SuggestedClassMembers = 14, - GlobalsOrKeywords = 15, - AutoImportSuggestions = 16, - - // Don't use these directly. - _JavaScriptIdentifiers = 17, - _DeprecatedStart = 18, - _First = LocalDeclarationPriority, - - DeprecatedOffset = _DeprecatedStart - _First, } /** @@ -157,8 +133,8 @@ namespace ts.Completions { */ type SymbolOriginInfoMap = Record; - /** Map from symbol id -> SortTextId. */ - type SymbolSortTextIdMap = (SortTextId | undefined)[]; + /** Map from symbol id -> SortText. */ + type SymbolSortTextIdMap = (SortText | undefined)[]; const enum KeywordCompletionFilters { None, // No keywords @@ -288,7 +264,7 @@ namespace ts.Completions { return getLabelCompletionAtPosition(previousToken.parent); } - const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, preferences, /*detailsEntryId*/ undefined, host, cancellationToken); + const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, preferences, /*detailsEntryId*/ undefined, host, cancellationToken); if (!completionData) { return undefined; } @@ -791,7 +767,7 @@ namespace ts.Completions { } ({ insertText, isSnippet, importAdder, sourceDisplay } = entry); source = CompletionSource.ObjectLiteralMethodSnippet; - sortText = SortText.OptionalMember; + sortText = sortText + "1" as SortText; if (importAdder.hasFixes()) { hasAction = true; } @@ -1396,8 +1372,8 @@ namespace ts.Completions { } const { name, needsConvertPropertyAccess } = info; - const sortTextId = symbolToSortTextIdMap?.[getSymbolId(symbol)] ?? SortTextId.LocationPriority; - const sortText = (isDeprecated(symbol, typeChecker) ? SortTextId.DeprecatedOffset + sortTextId : sortTextId).toString() as SortText; + const originalSortText = symbolToSortTextIdMap?.[getSymbolId(symbol)] ?? SortText.LocationPriority; + const sortText = (isDeprecated(symbol, typeChecker) ? "z" + originalSortText : originalSortText) as SortText; const entry = createCompletionEntry( symbol, sortText, @@ -1465,9 +1441,9 @@ namespace ts.Completions { // Auto Imports are not available for scripts so this conditional is always false if (!!sourceFile.externalModuleIndicator && !compilerOptions.allowUmdGlobalAccess - && symbolToSortTextIdMap[getSymbolId(symbol)] === SortTextId.GlobalsOrKeywords - && (symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortTextId.AutoImportSuggestions - || symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortTextId.LocationPriority)) { + && symbolToSortTextIdMap[getSymbolId(symbol)] === SortText.GlobalsOrKeywords + && (symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortText.AutoImportSuggestions + || symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortText.LocationPriority)) { return false; } @@ -1559,7 +1535,7 @@ namespace ts.Completions { } const compilerOptions = program.getCompilerOptions(); - const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host); + const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host); if (!completionData) { return { type: "none" }; } @@ -1892,7 +1868,7 @@ namespace ts.Completions { program: Program, log: (message: string) => void, sourceFile: SourceFile, - isUncheckedFile: boolean, + compilerOptions: CompilerOptions, position: number, preferences: UserPreferences, detailsEntryId: CompletionEntryIdentifier | undefined, @@ -1900,7 +1876,7 @@ namespace ts.Completions { cancellationToken?: CancellationToken, ): CompletionData | Request | undefined { const typeChecker = program.getTypeChecker(); - + const inUncheckedFile = isUncheckedFile(sourceFile, compilerOptions); let start = timestamp(); let currentToken = getTokenAtPosition(sourceFile, position); // TODO: GH#15853 // We will check for jsdoc comments with insideComment and getJsDocTagAtPosition. (TODO: that seems rather inefficient to check the same thing so many times.) @@ -2350,7 +2326,7 @@ namespace ts.Completions { } const propertyAccess = node.kind === SyntaxKind.ImportType ? node as ImportTypeNode : node.parent as PropertyAccessExpression | QualifiedName; - if (isUncheckedFile) { + if (inUncheckedFile) { // In javascript files, for union types, we don't just get the members that // the individual types have in common, we also include all the members that // each individual type has. This is because we're going to add all identifiers @@ -2437,7 +2413,7 @@ namespace ts.Completions { function addSymbolSortInfo(symbol: Symbol) { if (isStaticProperty(symbol)) { - symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.LocalDeclarationPriority; + symbolToSortTextIdMap[getSymbolId(symbol)] = SortText.LocalDeclarationPriority; } } @@ -2557,7 +2533,7 @@ namespace ts.Completions { const symbol = symbols[i]; if (!typeChecker.isArgumentsSymbol(symbol) && !some(symbol.declarations, d => d.getSourceFile() === sourceFile)) { - symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.GlobalsOrKeywords; + symbolToSortTextIdMap[getSymbolId(symbol)] = SortText.GlobalsOrKeywords; } if (typeOnlyAliasNeedsPromotion && !(symbol.flags & SymbolFlags.Value)) { const typeOnlyAliasDeclaration = symbol.declarations && find(symbol.declarations, isTypeOnlyImportOrExportDeclaration); @@ -2575,7 +2551,7 @@ namespace ts.Completions { for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) { symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.ThisType }; symbols.push(symbol); - symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.SuggestedClassMembers; + symbolToSortTextIdMap[getSymbolId(symbol)] = SortText.SuggestedClassMembers; } } } @@ -2762,12 +2738,12 @@ namespace ts.Completions { function pushAutoImportSymbol(symbol: Symbol, origin: SymbolOriginInfoResolvedExport | SymbolOriginInfoExport) { const symbolId = getSymbolId(symbol); - if (symbolToSortTextIdMap[symbolId] === SortTextId.GlobalsOrKeywords) { + if (symbolToSortTextIdMap[symbolId] === SortText.GlobalsOrKeywords) { // If an auto-importable symbol is available as a global, don't add the auto import return; } symbolToOriginInfoMap[symbols.length] = origin; - symbolToSortTextIdMap[symbolId] = importCompletionNode ? SortTextId.LocationPriority : SortTextId.AutoImportSuggestions; + symbolToSortTextIdMap[symbolId] = importCompletionNode ? SortText.LocationPriority : SortText.AutoImportSuggestions; symbols.push(symbol); } @@ -3050,6 +3026,10 @@ namespace ts.Completions { } } setSortTextToOptionalMember(); + if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression && preferences.includeCompletionsWithInsertText) { + // >> TODO: specify `symbols` range? + transformObjectLiteralMembersSortText(); + } return GlobalsSearch.Success; } @@ -3131,7 +3111,7 @@ namespace ts.Completions { localsContainer.locals?.forEach((symbol, name) => { symbols.push(symbol); if (localsContainer.symbol?.exports?.has(name)) { - symbolToSortTextIdMap[getSymbolId(symbol)] = SortTextId.OptionalMember; + symbolToSortTextIdMap[getSymbolId(symbol)] = SortText.OptionalMember; } }); return GlobalsSearch.Success; @@ -3571,7 +3551,7 @@ namespace ts.Completions { symbols.forEach(m => { if (m.flags & SymbolFlags.Optional) { const symbolId = getSymbolId(m); - symbolToSortTextIdMap[symbolId] = symbolToSortTextIdMap[symbolId] ?? SortTextId.OptionalMember; + symbolToSortTextIdMap[symbolId] = symbolToSortTextIdMap[symbolId] ?? SortText.OptionalMember; } }); } @@ -3583,7 +3563,32 @@ namespace ts.Completions { } for (const contextualMemberSymbol of contextualMemberSymbols) { if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) { - symbolToSortTextIdMap[getSymbolId(contextualMemberSymbol)] = SortTextId.MemberDeclaredBySpreadAssignment; + symbolToSortTextIdMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment; + } + } + } + + function transformObjectLiteralMembersSortText(): void { + const pastSymbolIds: Set = new Set(); + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + const symbolId = getSymbolId(symbol); + if (pastSymbolIds.has(symbolId)) { + continue; + } + pastSymbolIds.add(symbolId); + const origin = symbolToOriginInfoMap?.[i]; + const target = getEmitScriptTarget(compilerOptions); + const displayName = getCompletionEntryDisplayNameForSymbol( + symbol, + target, + origin, + CompletionKind.ObjectPropertyDeclaration, + /*jsxIdentifierExpected*/ false); + if (displayName) { + const originalSortText = symbolToSortTextIdMap[symbolId] ?? SortText.LocationPriority; + const { name } = displayName; + symbolToSortTextIdMap[symbolId] = `${originalSortText}\0${name}\0` as SortText; } } } diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts index fc713b7136c0c..472ac57a4a7e6 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts @@ -41,12 +41,12 @@ verify.completions({ includes: [ { name: "bar", - sortText: completion.SortText.LocationPriority, + sortText: `${completion.SortText.LocationPriority}\0bar\0` as completion.SortText, insertText: undefined, }, { name: "bar", - sortText: completion.SortText.OptionalMember, + sortText: `${completion.SortText.LocationPriority}\0bar\0${1}` as completion.SortText, source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "bar(x: number): void {\n},", }, @@ -61,23 +61,23 @@ verify.completions({ includes: [ { name: "bar", - sortText: completion.SortText.LocationPriority, + sortText: `${completion.SortText.LocationPriority}\0bar\0` as completion.SortText, insertText: undefined, }, { name: "bar", - sortText: completion.SortText.OptionalMember, + sortText: `${completion.SortText.LocationPriority}\0bar\0${1}` as completion.SortText, source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "bar(x: number): void {\n},", }, { name: "foo", - sortText: completion.SortText.LocationPriority, + sortText: `${completion.SortText.LocationPriority}\0foo\0` as completion.SortText, insertText: undefined, }, { name: "foo", - sortText: completion.SortText.OptionalMember, + sortText: `${completion.SortText.LocationPriority}\0foo\0${1}` as completion.SortText, source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "foo(x: string): string {\n},", }, @@ -92,7 +92,7 @@ verify.completions({ exact: [ { name: "buzz", - sortText: completion.SortText.LocationPriority, + sortText: `${completion.SortText.LocationPriority}\0buzz\0` as completion.SortText, // no declaration insert text, because this property has overloads insertText: undefined, }, @@ -107,12 +107,12 @@ verify.completions({ includes: [ { name: "\"space bar\"", - sortText: completion.SortText.LocationPriority, + sortText: `${completion.SortText.LocationPriority}\0${"\"space bar\""}\0` as completion.SortText, insertText: undefined, }, { name: "\"space bar\"", - sortText: completion.SortText.OptionalMember, + sortText: `${completion.SortText.LocationPriority}\0${"\"space bar\""}\0${1}` as completion.SortText, source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "\"space bar\"(): string {\n},", }, @@ -127,12 +127,12 @@ verify.completions({ includes: [ { name: "bar", - sortText: completion.SortText.LocationPriority, + sortText: `${completion.SortText.LocationPriority}\0bar\0` as completion.SortText, insertText: undefined, }, { name: "bar", - sortText: completion.SortText.OptionalMember, + sortText: `${completion.SortText.LocationPriority}\0bar\0${1}` as completion.SortText, source: completion.CompletionSource.ObjectLiteralMethodSnippet, isSnippet: true, insertText: "bar(x: number): void {\n $0\n},", From 07c4fcf3d9bf0a0c457838e0ff3d953b7aa95f4d Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 22 Mar 2022 12:10:30 -0700 Subject: [PATCH 25/36] Improve new sort text system --- src/harness/fourslashInterfaceImpl.ts | 28 +++++++++- src/services/completions.ts | 53 +++++++++++-------- .../completionsObjectLiteralMethod1.ts | 27 ++++++---- .../completionsObjectLiteralMethod2.ts | 5 +- .../completionsObjectLiteralMethod3.ts | 24 +++++---- tests/cases/fourslash/fourslash.ts | 33 ++++++------ 6 files changed, 108 insertions(+), 62 deletions(-) diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 46a70651a8b68..e198f22878e80 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -950,9 +950,35 @@ namespace FourSlashInterface { } export namespace Completion { - export import SortText = ts.Completions.SortText; + import SortTextType = ts.Completions.SortText; + export type SortText = SortTextType; export import CompletionSource = ts.Completions.CompletionSource; + export const SortText = { + // Presets + LocalDeclarationPriority: "10" as SortText, + LocationPriority: "11" as SortText, + OptionalMember: "12" as SortText, + MemberDeclaredBySpreadAssignment: "13" as SortText, + SuggestedClassMembers: "14" as SortText, + GlobalsOrKeywords: "15" as SortText, + AutoImportSuggestions: "16" as SortText, + JavascriptIdentifiers: "17" as SortText, + + // Transformations + Deprecated(sortText: SortText): SortText { + return "z" + sortText as SortText; + }, + + ObjectLiteralProperty(presetSortText: SortText, symbolDisplayName: string): SortText { + return `${presetSortText}\0${symbolDisplayName}\0` as SortText; + }, + + AddIsSnippetSuffix(sortText: SortText): SortText { + return sortText + "1" as SortText; + }, + }; + const functionEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "function", diff --git a/src/services/completions.ts b/src/services/completions.ts index da27517bd43dd..4f4f55427bd13 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -6,19 +6,31 @@ namespace ts.Completions { export type Log = (message: string) => void; - // NOTE: Make sure that each entry has the exact same number of digits - // since many implementations will sort by string contents, - // where "10" is considered less than "2". - export enum SortText { - LocalDeclarationPriority = "10", - LocationPriority = "11", - OptionalMember = "12", - MemberDeclaredBySpreadAssignment = "13", - SuggestedClassMembers = "14", - GlobalsOrKeywords = "15", - AutoImportSuggestions = "16", - JavascriptIdentifiers = "17", - } + export type SortText = string & { __sortText: any }; + export const SortText = { + // Presets + LocalDeclarationPriority: "10" as SortText, + LocationPriority: "11" as SortText, + OptionalMember: "12" as SortText, + MemberDeclaredBySpreadAssignment: "13" as SortText, + SuggestedClassMembers: "14" as SortText, + GlobalsOrKeywords: "15" as SortText, + AutoImportSuggestions: "16" as SortText, + JavascriptIdentifiers: "17" as SortText, + + // Transformations + Deprecated(sortText: SortText): SortText { + return "z" + sortText as SortText; + }, + + ObjectLiteralProperty(presetSortText: SortText, symbolDisplayName: string): SortText { + return `${presetSortText}\0${symbolDisplayName}\0` as SortText; + }, + + AddIsSnippetSuffix(sortText: SortText): SortText { + return sortText + "1" as SortText; + }, + }; /** * Special values for `CompletionInfo['source']` used to disambiguate @@ -129,7 +141,6 @@ namespace ts.Completions { /** * Map from symbol index in `symbols` -> SymbolOriginInfo. - * Only populated for symbols that come from other modules. */ type SymbolOriginInfoMap = Record; @@ -767,7 +778,7 @@ namespace ts.Completions { } ({ insertText, isSnippet, importAdder, sourceDisplay } = entry); source = CompletionSource.ObjectLiteralMethodSnippet; - sortText = sortText + "1" as SortText; + sortText = SortText.AddIsSnippetSuffix(sortText); if (importAdder.hasFixes()) { hasAction = true; } @@ -1373,7 +1384,7 @@ namespace ts.Completions { const { name, needsConvertPropertyAccess } = info; const originalSortText = symbolToSortTextIdMap?.[getSymbolId(symbol)] ?? SortText.LocationPriority; - const sortText = (isDeprecated(symbol, typeChecker) ? "z" + originalSortText : originalSortText) as SortText; + const sortText = (isDeprecated(symbol, typeChecker) ? SortText.Deprecated(originalSortText) : originalSortText); const entry = createCompletionEntry( symbol, sortText, @@ -2950,6 +2961,7 @@ namespace ts.Completions { * @returns true if 'symbols' was successfully populated; false otherwise. */ function tryGetObjectLikeCompletionSymbols(): GlobalsSearch | undefined { + const symbolsStartIndex = symbols.length; const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); if (!objectLikeContainer) return GlobalsSearch.Continue; @@ -3027,8 +3039,7 @@ namespace ts.Completions { } setSortTextToOptionalMember(); if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression && preferences.includeCompletionsWithInsertText) { - // >> TODO: specify `symbols` range? - transformObjectLiteralMembersSortText(); + transformObjectLiteralMembersSortText(symbolsStartIndex); } return GlobalsSearch.Success; @@ -3568,9 +3579,9 @@ namespace ts.Completions { } } - function transformObjectLiteralMembersSortText(): void { + function transformObjectLiteralMembersSortText(start: number): void { const pastSymbolIds: Set = new Set(); - for (let i = 0; i < symbols.length; i++) { + for (let i = start; i < symbols.length; i++) { const symbol = symbols[i]; const symbolId = getSymbolId(symbol); if (pastSymbolIds.has(symbolId)) { @@ -3588,7 +3599,7 @@ namespace ts.Completions { if (displayName) { const originalSortText = symbolToSortTextIdMap[symbolId] ?? SortText.LocationPriority; const { name } = displayName; - symbolToSortTextIdMap[symbolId] = `${originalSortText}\0${name}\0` as SortText; + symbolToSortTextIdMap[symbolId] = SortText.ObjectLiteralProperty(originalSortText, name); } } } diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts index 472ac57a4a7e6..7b1f96830756c 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts @@ -41,12 +41,13 @@ verify.completions({ includes: [ { name: "bar", - sortText: `${completion.SortText.LocationPriority}\0bar\0` as completion.SortText, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "bar"), insertText: undefined, }, { name: "bar", - sortText: `${completion.SortText.LocationPriority}\0bar\0${1}` as completion.SortText, + sortText: completion.SortText.AddIsSnippetSuffix( + completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "bar")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "bar(x: number): void {\n},", }, @@ -61,23 +62,25 @@ verify.completions({ includes: [ { name: "bar", - sortText: `${completion.SortText.LocationPriority}\0bar\0` as completion.SortText, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "bar"), insertText: undefined, }, { name: "bar", - sortText: `${completion.SortText.LocationPriority}\0bar\0${1}` as completion.SortText, + sortText: completion.SortText.AddIsSnippetSuffix( + completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "bar")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "bar(x: number): void {\n},", }, { name: "foo", - sortText: `${completion.SortText.LocationPriority}\0foo\0` as completion.SortText, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "foo"), insertText: undefined, }, { name: "foo", - sortText: `${completion.SortText.LocationPriority}\0foo\0${1}` as completion.SortText, + sortText: completion.SortText.AddIsSnippetSuffix( + completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "foo")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "foo(x: string): string {\n},", }, @@ -92,7 +95,7 @@ verify.completions({ exact: [ { name: "buzz", - sortText: `${completion.SortText.LocationPriority}\0buzz\0` as completion.SortText, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "buzz"), // no declaration insert text, because this property has overloads insertText: undefined, }, @@ -107,12 +110,13 @@ verify.completions({ includes: [ { name: "\"space bar\"", - sortText: `${completion.SortText.LocationPriority}\0${"\"space bar\""}\0` as completion.SortText, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "\"space bar\""), insertText: undefined, }, { name: "\"space bar\"", - sortText: `${completion.SortText.LocationPriority}\0${"\"space bar\""}\0${1}` as completion.SortText, + sortText: completion.SortText.AddIsSnippetSuffix( + completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "\"space bar\"")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "\"space bar\"(): string {\n},", }, @@ -127,12 +131,13 @@ verify.completions({ includes: [ { name: "bar", - sortText: `${completion.SortText.LocationPriority}\0bar\0` as completion.SortText, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "bar"), insertText: undefined, }, { name: "bar", - sortText: `${completion.SortText.LocationPriority}\0bar\0${1}` as completion.SortText, + sortText: completion.SortText.AddIsSnippetSuffix( + completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "bar")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, isSnippet: true, insertText: "bar(x: number): void {\n $0\n},", diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts index 05c929513e410..3235e83a8b593 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts @@ -27,12 +27,13 @@ verify.completions({ includes: [ { name: "foo", - sortText: completion.SortText.LocationPriority, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "foo"), insertText: undefined, }, { name: "foo", - sortText: completion.SortText.OptionalMember, + sortText: completion.SortText.AddIsSnippetSuffix( + completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "foo")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "foo(f: IFoo): void {\n},", hasAction: true, diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod3.ts b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts index 9dc0d37aa954a..0770ee3dbc6a3 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod3.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts @@ -41,12 +41,13 @@ verify.completions({ includes: [ { name: "M", - sortText: completion.SortText.LocationPriority, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "M"), insertText: undefined, }, { name: "M", - sortText: completion.SortText.OptionalMember, + sortText: completion.SortText.AddIsSnippetSuffix( + completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "M")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "M(x: number): void {\n},", }, @@ -61,7 +62,7 @@ verify.completions({ includes: [ { name: "M", - sortText: completion.SortText.LocationPriority, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "M"), insertText: undefined, }, // No signature completion because type of `M` is intersection type @@ -76,7 +77,7 @@ verify.completions({ exact: [ { name: "M", - sortText: completion.SortText.LocationPriority, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "M"), insertText: undefined, }, // No signature completion because type of `M` is intersection type @@ -91,34 +92,37 @@ verify.completions({ includes: [ { name: "M", - sortText: completion.SortText.OptionalMember, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.OptionalMember, "M"), insertText: undefined, }, { name: "M", - sortText: completion.SortText.OptionalMember, + sortText: completion.SortText.AddIsSnippetSuffix( + completion.SortText.ObjectLiteralProperty(completion.SortText.OptionalMember, "M")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "M(x: number): void {\n},", }, { name: "N", - sortText: completion.SortText.LocationPriority, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "N"), insertText: undefined, }, { name: "N", - sortText: completion.SortText.OptionalMember, + sortText: completion.SortText.AddIsSnippetSuffix( + completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "N")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "N(x: string): void {\n},", }, { name: "O", - sortText: completion.SortText.OptionalMember, + sortText: completion.SortText.ObjectLiteralProperty(completion.SortText.OptionalMember, "O"), insertText: undefined, }, { name: "O", - sortText: completion.SortText.OptionalMember, + sortText: completion.SortText.AddIsSnippetSuffix( + completion.SortText.ObjectLiteralProperty(completion.SortText.OptionalMember, "O")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "O(): void {\n},", }, diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index a9b4ab383dce3..a8d14afcc033a 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -838,23 +838,22 @@ declare namespace completion { interface GlobalsPlusOptions { noLib?: boolean; } - export const enum SortText { - LocalDeclarationPriority = "10", - LocationPriority = "11", - OptionalMember = "12", - MemberDeclaredBySpreadAssignment = "13", - SuggestedClassMembers = "14", - GlobalsOrKeywords = "15", - AutoImportSuggestions = "16", - JavascriptIdentifiers = "17", - DeprecatedLocalDeclarationPriority = "18", - DeprecatedLocationPriority = "19", - DeprecatedOptionalMember = "20", - DeprecatedMemberDeclaredBySpreadAssignment = "21", - DeprecatedSuggestedClassMembers = "22", - DeprecatedGlobalsOrKeywords = "23", - DeprecatedAutoImportSuggestions = "24" - } + export type SortText = string & { __sortText: any }; + export const SortText: { + LocalDeclarationPriority: SortText, + LocationPriority: SortText, + OptionalMember: SortText, + MemberDeclaredBySpreadAssignment: SortText, + SuggestedClassMembers: SortText, + GlobalsOrKeywords: SortText, + AutoImportSuggestions: SortText, + JavascriptIdentifiers: SortText, + + Deprecated(sortText: SortText): SortText, + ObjectLiteralProperty(presetSortText: SortText, symbolDisplayName: string): SortText, + AddIsSnippetSuffix(sortText: SortText): SortText, + }; + export const enum CompletionSource { ThisProperty = "ThisProperty/", ClassMemberSnippet = "ClassMemberSnippet/", From 5b0d86df70cc0c939568ada020920f348b637c0f Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 22 Mar 2022 16:43:52 -0700 Subject: [PATCH 26/36] add signature and union type check --- src/services/completions.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 4f4f55427bd13..4035747f94f51 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1140,13 +1140,23 @@ namespace ts.Completions { case SyntaxKind.MethodSignature: case SyntaxKind.MethodDeclaration: { const signatures = checker.getSignaturesOfType(type, SignatureKind.Call); - if (signatures.length > 1) { + if (signatures.length !== 1) { // We don't support overloads in object literals. return undefined; } - const effectiveType = type.flags & TypeFlags.Union && (type as UnionType).types.length < 10 + let effectiveType = type.flags & TypeFlags.Union && (type as UnionType).types.length < 10 ? checker.getUnionType((type as UnionType).types, UnionReduction.Subtype) : type; + if (effectiveType.flags & TypeFlags.Union) { + // Only offer the completion if there's a single function type component. + const functionTypes = filter((effectiveType as UnionType).types, type => checker.getSignaturesOfType(type, SignatureKind.Call).length > 0); + if (functionTypes.length === 1) { + effectiveType = functionTypes[0]; + } + else { + return undefined; + } + } let typeNode = checker.typeToTypeNode(effectiveType, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); if (!typeNode || !isFunctionTypeNode(typeNode)) { return undefined; From 08a55c34f4cde46861d364ec991d75d92a91048b Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 23 Mar 2022 12:46:48 -0700 Subject: [PATCH 27/36] re-add flag --- src/compiler/types.ts | 1 + src/services/completions.ts | 8 ++++++-- tests/cases/fourslash/completionsObjectLiteralMethod1.ts | 5 +++++ tests/cases/fourslash/completionsObjectLiteralMethod2.ts | 2 ++ tests/cases/fourslash/completionsObjectLiteralMethod3.ts | 4 ++++ tests/cases/fourslash/fourslash.ts | 1 + 6 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0f17e9fef860c..0be7c2c543ce9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -8756,6 +8756,7 @@ namespace ts { readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; + readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ diff --git a/src/services/completions.ts b/src/services/completions.ts index 1b74fb1e50bfa..e96e9c7566e50 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -3045,12 +3045,16 @@ namespace ts.Completions { // Add filtered items to the completion list const filteredMembers = filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers)); symbols = concatenate(symbols, filteredMembers); - if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression && preferences.includeCompletionsWithInsertText) { + if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression + && preferences.includeCompletionsWithObjectLiteralMethodSnippets + && preferences.includeCompletionsWithInsertText) { collectObjectLiteralMethodSymbols(filteredMembers); } } setSortTextToOptionalMember(); - if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression && preferences.includeCompletionsWithInsertText) { + if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression + && preferences.includeCompletionsWithObjectLiteralMethodSnippets + && preferences.includeCompletionsWithInsertText) { transformObjectLiteralMembersSortText(symbolsStartIndex); } diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts index 7b1f96830756c..637457d8628c1 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts @@ -37,6 +37,7 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { @@ -58,6 +59,7 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { @@ -91,6 +93,7 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, exact: [ { @@ -106,6 +109,7 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { @@ -127,6 +131,7 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: true, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts index 3235e83a8b593..b6b28906f2ca1 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts @@ -23,6 +23,7 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { @@ -45,6 +46,7 @@ verify.applyCodeActionFromCompletion("a", { preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, name: "foo", source: completion.CompletionSource.ObjectLiteralMethodSnippet, diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod3.ts b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts index 0770ee3dbc6a3..5ec6c2ec7c53d 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod3.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts @@ -37,6 +37,7 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { @@ -58,6 +59,7 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { @@ -73,6 +75,7 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, exact: [ { @@ -88,6 +91,7 @@ verify.completions({ preferences: { includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: false, + includeCompletionsWithObjectLiteralMethodSnippets: true, }, includes: [ { diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index ef4e3cb1672b7..26f6a046a7b64 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -647,6 +647,7 @@ declare namespace FourSlashInterface { readonly includeCompletionsWithSnippetText?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; + readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; /** @deprecated use `includeCompletionsWithInsertText` */ readonly includeInsertTextCompletions?: boolean; From 6ebe361b38546458cb46f53f16d2077551bdb6db Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 23 Mar 2022 15:02:09 -0700 Subject: [PATCH 28/36] fix tests --- .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../completionsCommentsClass.baseline | 4 +- .../completionsCommentsClassMembers.baseline | 80 +++++++++---------- ...completionsCommentsCommentParsing.baseline | 28 +++---- ...etionsCommentsFunctionDeclaration.baseline | 12 +-- ...letionsCommentsFunctionExpression.baseline | 16 ++-- .../completionsStringMethods.baseline | 2 +- .../fourslash/completionAfterGlobalThis.ts | 4 +- ...tionListAfterRegularExpressionLiteral01.ts | 14 +++- ...etionListAfterRegularExpressionLiteral1.ts | 14 +++- .../completionListAfterStringLiteral1.ts | 2 +- .../completionsWithDeprecatedTag1.ts | 10 +-- .../completionsWithDeprecatedTag10.ts | 2 +- .../completionsWithDeprecatedTag2.ts | 2 +- .../completionsWithDeprecatedTag4.ts | 2 +- .../completionsWithDeprecatedTag5.ts | 2 +- .../completionsWithDeprecatedTag6.ts | 2 +- .../completionsWithDeprecatedTag7.ts | 2 +- .../completionsWithDeprecatedTag8.ts | 2 +- .../completionsWithDeprecatedTag9.ts | 2 +- 21 files changed, 115 insertions(+), 89 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3fce914915dc4..1795fb98e1384 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4099,6 +4099,7 @@ declare namespace ts { readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; + readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 9e1c635ac7499..9f9f42ebdfa62 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4099,6 +4099,7 @@ declare namespace ts { readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithClassMemberSnippets?: boolean; + readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ diff --git a/tests/baselines/reference/completionsCommentsClass.baseline b/tests/baselines/reference/completionsCommentsClass.baseline index 5f4294590d9f2..66766ee70bef1 100644 --- a/tests/baselines/reference/completionsCommentsClass.baseline +++ b/tests/baselines/reference/completionsCommentsClass.baseline @@ -3760,7 +3760,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -3850,7 +3850,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", diff --git a/tests/baselines/reference/completionsCommentsClassMembers.baseline b/tests/baselines/reference/completionsCommentsClassMembers.baseline index 0486ae6854a7a..af89101b92aed 100644 --- a/tests/baselines/reference/completionsCommentsClassMembers.baseline +++ b/tests/baselines/reference/completionsCommentsClassMembers.baseline @@ -4632,7 +4632,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -4722,7 +4722,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -11688,7 +11688,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -11778,7 +11778,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -16500,7 +16500,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -16590,7 +16590,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -23556,7 +23556,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -23646,7 +23646,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -27620,7 +27620,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -27710,7 +27710,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -32839,7 +32839,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -32929,7 +32929,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -36857,7 +36857,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -36947,7 +36947,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -42030,7 +42030,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -42120,7 +42120,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -47249,7 +47249,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -47339,7 +47339,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -52468,7 +52468,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -52558,7 +52558,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -57687,7 +57687,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -57777,7 +57777,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -61746,7 +61746,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -61836,7 +61836,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -65805,7 +65805,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -65895,7 +65895,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -69864,7 +69864,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -69954,7 +69954,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -73923,7 +73923,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -74013,7 +74013,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -77982,7 +77982,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -78072,7 +78072,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -82041,7 +82041,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -82131,7 +82131,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -86596,7 +86596,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -86686,7 +86686,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -91952,7 +91952,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -92042,7 +92042,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -96407,7 +96407,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -96497,7 +96497,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", diff --git a/tests/baselines/reference/completionsCommentsCommentParsing.baseline b/tests/baselines/reference/completionsCommentsCommentParsing.baseline index ea44b2c255f9a..1ec7d5e207235 100644 --- a/tests/baselines/reference/completionsCommentsCommentParsing.baseline +++ b/tests/baselines/reference/completionsCommentsCommentParsing.baseline @@ -5467,7 +5467,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -5557,7 +5557,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -11910,7 +11910,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -12000,7 +12000,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -17533,7 +17533,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -17623,7 +17623,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -23233,7 +23233,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -23323,7 +23323,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -28975,7 +28975,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -29065,7 +29065,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -35418,7 +35418,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -35508,7 +35508,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -41118,7 +41118,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -41208,7 +41208,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", diff --git a/tests/baselines/reference/completionsCommentsFunctionDeclaration.baseline b/tests/baselines/reference/completionsCommentsFunctionDeclaration.baseline index 9f0294c4e8286..ae41c9cc50c9e 100644 --- a/tests/baselines/reference/completionsCommentsFunctionDeclaration.baseline +++ b/tests/baselines/reference/completionsCommentsFunctionDeclaration.baseline @@ -4130,7 +4130,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -4220,7 +4220,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -7606,7 +7606,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -7696,7 +7696,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -11916,7 +11916,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -12006,7 +12006,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", diff --git a/tests/baselines/reference/completionsCommentsFunctionExpression.baseline b/tests/baselines/reference/completionsCommentsFunctionExpression.baseline index ddada8f7b64a3..9c1bc3392836e 100644 --- a/tests/baselines/reference/completionsCommentsFunctionExpression.baseline +++ b/tests/baselines/reference/completionsCommentsFunctionExpression.baseline @@ -3667,7 +3667,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -3757,7 +3757,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -8389,7 +8389,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -8479,7 +8479,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -12055,7 +12055,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -12145,7 +12145,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -16549,7 +16549,7 @@ "name": "escape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", @@ -16639,7 +16639,7 @@ "name": "unescape", "kind": "function", "kindModifiers": "deprecated,declare", - "sortText": "24", + "sortText": "z15", "displayParts": [ { "text": "function", diff --git a/tests/baselines/reference/completionsStringMethods.baseline b/tests/baselines/reference/completionsStringMethods.baseline index 4e12c7480abd9..327daf9f0d9a2 100644 --- a/tests/baselines/reference/completionsStringMethods.baseline +++ b/tests/baselines/reference/completionsStringMethods.baseline @@ -2164,7 +2164,7 @@ "name": "substr", "kind": "method", "kindModifiers": "deprecated,declare", - "sortText": "20", + "sortText": "z11", "displayParts": [ { "text": "(", diff --git a/tests/cases/fourslash/completionAfterGlobalThis.ts b/tests/cases/fourslash/completionAfterGlobalThis.ts index ae4056c0b60a0..f1c8aa59302f1 100644 --- a/tests/cases/fourslash/completionAfterGlobalThis.ts +++ b/tests/cases/fourslash/completionAfterGlobalThis.ts @@ -9,8 +9,8 @@ verify.completions({ ...completion.globalsVars, completion.undefinedVarEntry ].map(e => { - if (e.sortText === completion.SortText.DeprecatedGlobalsOrKeywords) { - return { ...e, sortText: completion.SortText.DeprecatedLocationPriority }; + if (e.sortText === completion.SortText.Deprecated(completion.SortText.GlobalsOrKeywords)) { + return { ...e, sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) }; } return { ...e, sortText: completion.SortText.LocationPriority }; }) diff --git a/tests/cases/fourslash/completionListAfterRegularExpressionLiteral01.ts b/tests/cases/fourslash/completionListAfterRegularExpressionLiteral01.ts index baf71f0533fb5..9451280e237b1 100644 --- a/tests/cases/fourslash/completionListAfterRegularExpressionLiteral01.ts +++ b/tests/cases/fourslash/completionListAfterRegularExpressionLiteral01.ts @@ -3,4 +3,16 @@ ////let v = 100; /////a/./**/ -verify.completions({ marker: "", unsorted: ["exec", "test", "source", "global", "ignoreCase", "multiline", "lastIndex", { name: "compile", sortText: completion.SortText.DeprecatedLocationPriority }] }); +verify.completions({ + marker: "", + unsorted: [ + "exec", + "test", + "source", + "global", + "ignoreCase", + "multiline", + "lastIndex", + { name: "compile", sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) } + ] +}); diff --git a/tests/cases/fourslash/completionListAfterRegularExpressionLiteral1.ts b/tests/cases/fourslash/completionListAfterRegularExpressionLiteral1.ts index 6c3f631dde5d2..872b216462510 100644 --- a/tests/cases/fourslash/completionListAfterRegularExpressionLiteral1.ts +++ b/tests/cases/fourslash/completionListAfterRegularExpressionLiteral1.ts @@ -2,4 +2,16 @@ /////a/./**/ -verify.completions({ marker: "", unsorted: ["exec", "test", "source", "global", "ignoreCase", "multiline", "lastIndex", { name: "compile", sortText: completion.SortText.DeprecatedLocationPriority }] }); +verify.completions({ + marker: "", + unsorted: [ + "exec", + "test", + "source", + "global", + "ignoreCase", + "multiline", + "lastIndex", + { name: "compile", sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) }, + ] +}); diff --git a/tests/cases/fourslash/completionListAfterStringLiteral1.ts b/tests/cases/fourslash/completionListAfterStringLiteral1.ts index 74cb1bb1c7eaa..51b5903be0193 100644 --- a/tests/cases/fourslash/completionListAfterStringLiteral1.ts +++ b/tests/cases/fourslash/completionListAfterStringLiteral1.ts @@ -6,6 +6,6 @@ verify.completions({ marker: "", unsorted: [ "toString", "charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "localeCompare", "match", "replace", "search", "slice", - "split", "substring", "toLowerCase", "toLocaleLowerCase", "toUpperCase", "toLocaleUpperCase", "trim", "length", { name: "substr", sortText: completion.SortText.DeprecatedLocationPriority }, "valueOf", + "split", "substring", "toLowerCase", "toLocaleLowerCase", "toUpperCase", "toLocaleUpperCase", "trim", "length", { name: "substr", sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) }, "valueOf", ], }); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag1.ts b/tests/cases/fourslash/completionsWithDeprecatedTag1.ts index 1ce322b6a1c4c..b72752f6671f4 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag1.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag1.ts @@ -25,26 +25,26 @@ verify.completions({ marker: "1", includes: [ - { name: "Foo", kind: "interface", kindModifiers: "deprecated", sortText: completion.SortText.DeprecatedLocationPriority } + { name: "Foo", kind: "interface", kindModifiers: "deprecated", sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) } ] }, { marker: "2", includes: [ - { name: "bar", kind: "method", kindModifiers: "deprecated", sortText: completion.SortText.DeprecatedLocationPriority } + { name: "bar", kind: "method", kindModifiers: "deprecated", sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) } ] }, { marker: "3", includes: [ - { name: "prop", kind: "property", kindModifiers: "deprecated", sortText: completion.SortText.DeprecatedLocationPriority } + { name: "prop", kind: "property", kindModifiers: "deprecated", sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) } ] }, { marker: "4", includes: [ - { name: "foobar", kind: "function", kindModifiers: "export,deprecated", sortText: completion.SortText.DeprecatedLocationPriority } + { name: "foobar", kind: "function", kindModifiers: "export,deprecated", sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) } ] }, { marker: "5", includes: [ - { name: "foobar", kind: "alias", kindModifiers: "export,deprecated", sortText: completion.SortText.DeprecatedLocationPriority } + { name: "foobar", kind: "alias", kindModifiers: "export,deprecated", sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority) } ] }); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag10.ts b/tests/cases/fourslash/completionsWithDeprecatedTag10.ts index f12e33feef458..dd44edab2a1f1 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag10.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag10.ts @@ -16,7 +16,7 @@ verify.completions({ hasAction: true, kind: "const", kindModifiers: "export,deprecated", - sortText: completion.SortText.DeprecatedAutoImportSuggestions + sortText: completion.SortText.Deprecated(completion.SortText.AutoImportSuggestions), }], preferences: { includeCompletionsForModuleExports: true, diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag2.ts b/tests/cases/fourslash/completionsWithDeprecatedTag2.ts index 9561a9a56c502..5d9357cd089fe 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag2.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag2.ts @@ -13,6 +13,6 @@ verify.completions({ name: "foo", kind: "function", kindModifiers: "deprecated,declare", - sortText: completion.SortText.DeprecatedLocationPriority + sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority), }] }); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag4.ts b/tests/cases/fourslash/completionsWithDeprecatedTag4.ts index 9df8bc60e91ba..ecd7571c7f4b1 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag4.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag4.ts @@ -18,6 +18,6 @@ verify.completions({ name: "abc", kind: "property", kindModifiers: "deprecated,declare,optional", - sortText: completion.SortText.DeprecatedOptionalMember + sortText: completion.SortText.Deprecated(completion.SortText.OptionalMember), }], }); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag5.ts b/tests/cases/fourslash/completionsWithDeprecatedTag5.ts index e99be425e8a90..dcdf45626d9c2 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag5.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag5.ts @@ -17,7 +17,7 @@ verify.completions({ name: "m", kind: "method", kindModifiers: "static,deprecated", - sortText: completion.SortText.DeprecatedLocalDeclarationPriority + sortText: completion.SortText.Deprecated(completion.SortText.LocalDeclarationPriority), }, ]) }); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag6.ts b/tests/cases/fourslash/completionsWithDeprecatedTag6.ts index b174e97660378..c3dfcc799d39a 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag6.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag6.ts @@ -12,6 +12,6 @@ verify.completions({ name: "foo", kind: "var", kindModifiers: "export,deprecated", - sortText: completion.SortText.DeprecatedLocationPriority + sortText: completion.SortText.Deprecated(completion.SortText.LocationPriority), }] }); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag7.ts b/tests/cases/fourslash/completionsWithDeprecatedTag7.ts index dc1bf9bdac521..72d9c53d83b18 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag7.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag7.ts @@ -20,7 +20,7 @@ verify.completions({ marker: "", exact: [{ name: "a", - sortText: completion.SortText.DeprecatedMemberDeclaredBySpreadAssignment, + sortText: completion.SortText.Deprecated(completion.SortText.MemberDeclaredBySpreadAssignment), kind: 'property', kindModifiers: "deprecated" }] diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag8.ts b/tests/cases/fourslash/completionsWithDeprecatedTag8.ts index d326d0daf0931..1c9338b26d94f 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag8.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag8.ts @@ -15,7 +15,7 @@ verify.completions({ kind: "property", kindModifiers: "deprecated", insertText: "this.p", - sortText: completion.SortText.DeprecatedSuggestedClassMembers, + sortText: completion.SortText.Deprecated(completion.SortText.SuggestedClassMembers), source: completion.CompletionSource.ThisProperty }], preferences: { diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag9.ts b/tests/cases/fourslash/completionsWithDeprecatedTag9.ts index 819aeb1802587..0a1e2c0c5ad94 100644 --- a/tests/cases/fourslash/completionsWithDeprecatedTag9.ts +++ b/tests/cases/fourslash/completionsWithDeprecatedTag9.ts @@ -21,7 +21,7 @@ verify.completions({ name: "foo", kind: "var", kindModifiers: "deprecated,declare", - sortText: completion.SortText.DeprecatedGlobalsOrKeywords + sortText: completion.SortText.Deprecated(completion.SortText.GlobalsOrKeywords), }] }, { preferences: { From 411bc18581826904f41c9aab964c72de68f37b53 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 23 Mar 2022 15:12:28 -0700 Subject: [PATCH 29/36] rename sort text helper --- src/harness/fourslashInterfaceImpl.ts | 2 +- src/services/completions.ts | 4 ++-- .../cases/fourslash/completionsObjectLiteralMethod1.ts | 10 +++++----- .../cases/fourslash/completionsObjectLiteralMethod2.ts | 2 +- .../cases/fourslash/completionsObjectLiteralMethod3.ts | 8 ++++---- tests/cases/fourslash/fourslash.ts | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index e7a6cfe903e52..976fdf9220ba3 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -975,7 +975,7 @@ namespace FourSlashInterface { return `${presetSortText}\0${symbolDisplayName}\0` as SortText; }, - AddIsSnippetSuffix(sortText: SortText): SortText { + SortBelow(sortText: SortText): SortText { return sortText + "1" as SortText; }, }; diff --git a/src/services/completions.ts b/src/services/completions.ts index e96e9c7566e50..237b1a9d8d886 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -28,7 +28,7 @@ namespace ts.Completions { return `${presetSortText}\0${symbolDisplayName}\0` as SortText; }, - AddIsSnippetSuffix(sortText: SortText): SortText { + SortBelow(sortText: SortText): SortText { return sortText + "1" as SortText; }, }; @@ -780,7 +780,7 @@ namespace ts.Completions { } ({ insertText, isSnippet, importAdder, sourceDisplay } = entry); source = CompletionSource.ObjectLiteralMethodSnippet; - sortText = SortText.AddIsSnippetSuffix(sortText); + sortText = SortText.SortBelow(sortText); if (importAdder.hasFixes()) { hasAction = true; } diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts index 637457d8628c1..e6fef55cfa4ff 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod1.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod1.ts @@ -47,7 +47,7 @@ verify.completions({ }, { name: "bar", - sortText: completion.SortText.AddIsSnippetSuffix( + sortText: completion.SortText.SortBelow( completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "bar")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "bar(x: number): void {\n},", @@ -69,7 +69,7 @@ verify.completions({ }, { name: "bar", - sortText: completion.SortText.AddIsSnippetSuffix( + sortText: completion.SortText.SortBelow( completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "bar")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "bar(x: number): void {\n},", @@ -81,7 +81,7 @@ verify.completions({ }, { name: "foo", - sortText: completion.SortText.AddIsSnippetSuffix( + sortText: completion.SortText.SortBelow( completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "foo")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "foo(x: string): string {\n},", @@ -119,7 +119,7 @@ verify.completions({ }, { name: "\"space bar\"", - sortText: completion.SortText.AddIsSnippetSuffix( + sortText: completion.SortText.SortBelow( completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "\"space bar\"")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "\"space bar\"(): string {\n},", @@ -141,7 +141,7 @@ verify.completions({ }, { name: "bar", - sortText: completion.SortText.AddIsSnippetSuffix( + sortText: completion.SortText.SortBelow( completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "bar")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, isSnippet: true, diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts index b6b28906f2ca1..08e2fa824a79c 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod2.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod2.ts @@ -33,7 +33,7 @@ verify.completions({ }, { name: "foo", - sortText: completion.SortText.AddIsSnippetSuffix( + sortText: completion.SortText.SortBelow( completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "foo")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "foo(f: IFoo): void {\n},", diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod3.ts b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts index 5ec6c2ec7c53d..c053e9a7368de 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod3.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts @@ -47,7 +47,7 @@ verify.completions({ }, { name: "M", - sortText: completion.SortText.AddIsSnippetSuffix( + sortText: completion.SortText.SortBelow( completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "M")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "M(x: number): void {\n},", @@ -101,7 +101,7 @@ verify.completions({ }, { name: "M", - sortText: completion.SortText.AddIsSnippetSuffix( + sortText: completion.SortText.SortBelow( completion.SortText.ObjectLiteralProperty(completion.SortText.OptionalMember, "M")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "M(x: number): void {\n},", @@ -113,7 +113,7 @@ verify.completions({ }, { name: "N", - sortText: completion.SortText.AddIsSnippetSuffix( + sortText: completion.SortText.SortBelow( completion.SortText.ObjectLiteralProperty(completion.SortText.LocationPriority, "N")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "N(x: string): void {\n},", @@ -125,7 +125,7 @@ verify.completions({ }, { name: "O", - sortText: completion.SortText.AddIsSnippetSuffix( + sortText: completion.SortText.SortBelow( completion.SortText.ObjectLiteralProperty(completion.SortText.OptionalMember, "O")), source: completion.CompletionSource.ObjectLiteralMethodSnippet, insertText: "O(): void {\n},", diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 26f6a046a7b64..147bb243091fe 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -853,7 +853,7 @@ declare namespace completion { Deprecated(sortText: SortText): SortText, ObjectLiteralProperty(presetSortText: SortText, symbolDisplayName: string): SortText, - AddIsSnippetSuffix(sortText: SortText): SortText, + SortBelow(sortText: SortText): SortText, }; export const enum CompletionSource { From 1ebdf3d52719944b2348a5030382e86e38904483 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 23 Mar 2022 18:36:09 -0700 Subject: [PATCH 30/36] fix test and code for union case --- src/services/completions.ts | 17 +++++------------ .../completionsObjectLiteralMethod3.ts | 3 ++- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 237b1a9d8d886..a0b3acaee7f7c 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1141,11 +1141,6 @@ namespace ts.Completions { case SyntaxKind.PropertyDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.MethodDeclaration: { - const signatures = checker.getSignaturesOfType(type, SignatureKind.Call); - if (signatures.length !== 1) { - // We don't support overloads in object literals. - return undefined; - } let effectiveType = type.flags & TypeFlags.Union && (type as UnionType).types.length < 10 ? checker.getUnionType((type as UnionType).types, UnionReduction.Subtype) : type; @@ -1159,6 +1154,11 @@ namespace ts.Completions { return undefined; } } + const signatures = checker.getSignaturesOfType(effectiveType, SignatureKind.Call); + if (signatures.length !== 1) { + // We don't support overloads in object literals. + return undefined; + } let typeNode = checker.typeToTypeNode(effectiveType, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); if (!typeNode || !isFunctionTypeNode(typeNode)) { return undefined; @@ -2802,13 +2802,6 @@ namespace ts.Completions { if (!(symbol.flags & (SymbolFlags.Property | SymbolFlags.Method))) { return false; } - if (symbol.flags & SymbolFlags.Property) { - const type = program.getTypeChecker().getTypeOfSymbol(symbol); - // We ignore non-function properties. - if (!type.getCallSignatures().length) { - return false; - } - } return true; } diff --git a/tests/cases/fourslash/completionsObjectLiteralMethod3.ts b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts index c053e9a7368de..96aa3467540f1 100644 --- a/tests/cases/fourslash/completionsObjectLiteralMethod3.ts +++ b/tests/cases/fourslash/completionsObjectLiteralMethod3.ts @@ -1,6 +1,7 @@ /// // @newline: LF +// @strictNullChecks: true // @Filename: a.ts ////interface I1 { //// M(x: number): void; @@ -25,7 +26,7 @@ ////} ////interface Op { //// M?(x: number): void; -//// N: (x: string) => void | null | undefined; +//// N: ((x: string) => void) | null | undefined; //// O?: () => void; ////} ////const op: Op = { From 54774085faa67ccff2981f1b93ba26423ca86f83 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 24 Mar 2022 11:25:52 -0700 Subject: [PATCH 31/36] add new flag to protocol type --- src/server/protocol.ts | 7 +++++++ tests/baselines/reference/api/tsserverlibrary.d.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index ca8dbd1cac63b..e4278aa664b7b 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -3406,6 +3406,13 @@ namespace ts.server.protocol { * `class A { foo }`. */ readonly includeCompletionsWithClassMemberSnippets?: boolean; + /** + * If enabled, object literal methods will have a method declaration completion entry in addition + * to the regular completion entry containing just the method name. + * E.g., `const objectLiteral: T = { f| }` could be completed to `const objectLiteral: T = { foo(): void {} }`, + * in addition to `const objectLiteral: T = { foo }`. + */ + readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 1795fb98e1384..5e90e71d98984 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -9629,6 +9629,13 @@ declare namespace ts.server.protocol { * `class A { foo }`. */ readonly includeCompletionsWithClassMemberSnippets?: boolean; + /** + * If enabled, object literal methods will have a method declaration completion entry in addition + * to the regular completion entry containing just the method name. + * E.g., `const objectLiteral: T = { f| }` could be completed to `const objectLiteral: T = { foo(): void {} }`, + * in addition to `const objectLiteral: T = { foo }`. + */ + readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ From af7de76d6c1f4d9b5adbeacb8d1eb87eb2f05051 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 24 Mar 2022 12:04:18 -0700 Subject: [PATCH 32/36] fix spaces --- src/server/protocol.ts | 4 ++-- tests/baselines/reference/api/tsserverlibrary.d.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index e4278aa664b7b..35e941cabe253 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -3409,8 +3409,8 @@ namespace ts.server.protocol { /** * If enabled, object literal methods will have a method declaration completion entry in addition * to the regular completion entry containing just the method name. - * E.g., `const objectLiteral: T = { f| }` could be completed to `const objectLiteral: T = { foo(): void {} }`, - * in addition to `const objectLiteral: T = { foo }`. + * E.g., `const objectLiteral: T = { f| }` could be completed to `const objectLiteral: T = { foo(): void {} }`, + * in addition to `const objectLiteral: T = { foo }`. */ readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 5e90e71d98984..32bbdc4c79a39 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -9632,8 +9632,8 @@ declare namespace ts.server.protocol { /** * If enabled, object literal methods will have a method declaration completion entry in addition * to the regular completion entry containing just the method name. - * E.g., `const objectLiteral: T = { f| }` could be completed to `const objectLiteral: T = { foo(): void {} }`, - * in addition to `const objectLiteral: T = { foo }`. + * E.g., `const objectLiteral: T = { f| }` could be completed to `const objectLiteral: T = { foo(): void {} }`, + * in addition to `const objectLiteral: T = { foo }`. */ readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; From 1b884c4012eddb2faa5b7b87eb6f0a0907e923ae Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 29 Mar 2022 13:06:00 -0700 Subject: [PATCH 33/36] CR: minor fixes --- src/services/codefixes/helpers.ts | 4 +-- src/services/completions.ts | 50 +++++++++++++++---------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index f52b8abe01831..4949bf7f88289 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -370,7 +370,7 @@ namespace ts.codefix { export function createMethodImplementingSignatures( checker: TypeChecker, context: TypeConstructionContext, - enclosingDeclaration: Node, + enclosingDeclaration: ClassLikeDeclaration, signatures: readonly Signature[], name: PropertyName, optional: boolean, @@ -421,7 +421,7 @@ namespace ts.codefix { body); } - function getReturnTypeFromSignatures(signatures: readonly Signature[], checker: TypeChecker, context: TypeConstructionContext, enclosingDeclaration: Node): TypeNode | undefined { + function getReturnTypeFromSignatures(signatures: readonly Signature[], checker: TypeChecker, context: TypeConstructionContext, enclosingDeclaration: ClassLikeDeclaration): TypeNode | undefined { if (length(signatures)) { const type = checker.getUnionType(map(signatures, checker.getReturnTypeOfSignature)); return checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context)); diff --git a/src/services/completions.ts b/src/services/completions.ts index a0b3acaee7f7c..0364ae580ad0b 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -146,7 +146,7 @@ namespace ts.Completions { type SymbolOriginInfoMap = Record; /** Map from symbol id -> SortText. */ - type SymbolSortTextIdMap = (SortText | undefined)[]; + type SymbolSortTextMap = (SortText | undefined)[]; const enum KeywordCompletionFilters { None, // No keywords @@ -467,7 +467,7 @@ namespace ts.Completions { isRightOfOpenTag, importCompletionNode, insideJsDocTagTypeExpression, - symbolToSortTextIdMap, + symbolToSortTextMap: symbolToSortTextMap, hasUnresolvedAutoImports, } = completionData; @@ -504,7 +504,7 @@ namespace ts.Completions { importCompletionNode, recommendedCompletion, symbolToOriginInfoMap, - symbolToSortTextIdMap, + symbolToSortTextMap, isJsxIdentifierExpected, isRightOfOpenTag, ); @@ -537,7 +537,7 @@ namespace ts.Completions { importCompletionNode, recommendedCompletion, symbolToOriginInfoMap, - symbolToSortTextIdMap, + symbolToSortTextMap, isJsxIdentifierExpected, isRightOfOpenTag, ); @@ -1373,7 +1373,7 @@ namespace ts.Completions { importCompletionNode?: Node, recommendedCompletion?: Symbol, symbolToOriginInfoMap?: SymbolOriginInfoMap, - symbolToSortTextIdMap?: SymbolSortTextIdMap, + symbolToSortTextMap?: SymbolSortTextMap, isJsxIdentifierExpected?: boolean, isRightOfOpenTag?: boolean, ): UniqueNameSet { @@ -1390,12 +1390,12 @@ namespace ts.Completions { const symbol = symbols[i]; const origin = symbolToOriginInfoMap?.[i]; const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind, !!jsxIdentifierExpected); - if (!info || (uniques.get(info.name) && (!origin || !originIsObjectLiteralMethod(origin))) || kind === CompletionKind.Global && symbolToSortTextIdMap && !shouldIncludeSymbol(symbol, symbolToSortTextIdMap)) { + if (!info || (uniques.get(info.name) && (!origin || !originIsObjectLiteralMethod(origin))) || kind === CompletionKind.Global && symbolToSortTextMap && !shouldIncludeSymbol(symbol, symbolToSortTextMap)) { continue; } const { name, needsConvertPropertyAccess } = info; - const originalSortText = symbolToSortTextIdMap?.[getSymbolId(symbol)] ?? SortText.LocationPriority; + const originalSortText = symbolToSortTextMap?.[getSymbolId(symbol)] ?? SortText.LocationPriority; const sortText = (isDeprecated(symbol, typeChecker) ? SortText.Deprecated(originalSortText) : originalSortText); const entry = createCompletionEntry( symbol, @@ -1441,7 +1441,7 @@ namespace ts.Completions { add: name => uniques.set(name, true), }; - function shouldIncludeSymbol(symbol: Symbol, symbolToSortTextIdMap: SymbolSortTextIdMap): boolean { + function shouldIncludeSymbol(symbol: Symbol, symbolToSortTextMap: SymbolSortTextMap): boolean { let allFlags = symbol.flags; if (!isSourceFile(location)) { // export = /**/ here we want to get all meanings, so any symbol is ok @@ -1464,9 +1464,9 @@ namespace ts.Completions { // Auto Imports are not available for scripts so this conditional is always false if (!!sourceFile.externalModuleIndicator && !compilerOptions.allowUmdGlobalAccess - && symbolToSortTextIdMap[getSymbolId(symbol)] === SortText.GlobalsOrKeywords - && (symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortText.AutoImportSuggestions - || symbolToSortTextIdMap[getSymbolId(symbolOrigin)] === SortText.LocationPriority)) { + && symbolToSortTextMap[getSymbolId(symbol)] === SortText.GlobalsOrKeywords + && (symbolToSortTextMap[getSymbolId(symbolOrigin)] === SortText.AutoImportSuggestions + || symbolToSortTextMap[getSymbolId(symbolOrigin)] === SortText.LocationPriority)) { return false; } @@ -1809,7 +1809,7 @@ namespace ts.Completions { readonly contextToken: Node | undefined; readonly isJsxInitializer: IsJsxInitializer; readonly insideJsDocTagTypeExpression: boolean; - readonly symbolToSortTextIdMap: SymbolSortTextIdMap; + readonly symbolToSortTextMap: SymbolSortTextMap; readonly isTypeOnlyLocation: boolean; /** In JSX tag name and attribute names, identifiers like "my-tag" or "aria-name" is valid identifier. */ readonly isJsxIdentifierExpected: boolean; @@ -2159,7 +2159,7 @@ namespace ts.Completions { // This also gets mutated in nested-functions after the return let symbols: Symbol[] = []; const symbolToOriginInfoMap: SymbolOriginInfoMap = []; - const symbolToSortTextIdMap: SymbolSortTextIdMap = []; + const symbolToSortTextMap: SymbolSortTextMap = []; const seenPropertySymbols = new Map(); const isTypeOnlyLocation = isTypeOnlyCompletion(); const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => { @@ -2220,7 +2220,7 @@ namespace ts.Completions { contextToken, isJsxInitializer, insideJsDocTagTypeExpression, - symbolToSortTextIdMap, + symbolToSortTextMap, isTypeOnlyLocation, isJsxIdentifierExpected, isRightOfOpenTag, @@ -2436,7 +2436,7 @@ namespace ts.Completions { function addSymbolSortInfo(symbol: Symbol) { if (isStaticProperty(symbol)) { - symbolToSortTextIdMap[getSymbolId(symbol)] = SortText.LocalDeclarationPriority; + symbolToSortTextMap[getSymbolId(symbol)] = SortText.LocalDeclarationPriority; } } @@ -2556,7 +2556,7 @@ namespace ts.Completions { const symbol = symbols[i]; if (!typeChecker.isArgumentsSymbol(symbol) && !some(symbol.declarations, d => d.getSourceFile() === sourceFile)) { - symbolToSortTextIdMap[getSymbolId(symbol)] = SortText.GlobalsOrKeywords; + symbolToSortTextMap[getSymbolId(symbol)] = SortText.GlobalsOrKeywords; } if (typeOnlyAliasNeedsPromotion && !(symbol.flags & SymbolFlags.Value)) { const typeOnlyAliasDeclaration = symbol.declarations && find(symbol.declarations, isTypeOnlyImportOrExportDeclaration); @@ -2574,7 +2574,7 @@ namespace ts.Completions { for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) { symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.ThisType }; symbols.push(symbol); - symbolToSortTextIdMap[getSymbolId(symbol)] = SortText.SuggestedClassMembers; + symbolToSortTextMap[getSymbolId(symbol)] = SortText.SuggestedClassMembers; } } } @@ -2657,7 +2657,7 @@ namespace ts.Completions { return false; } - /** Mutates `symbols`, `symbolToOriginInfoMap`, and `symbolToSortTextIdMap` */ + /** Mutates `symbols`, `symbolToOriginInfoMap`, and `symbolToSortTextMap` */ function collectAutoImports() { if (!shouldOfferImportCompletions()) return; Debug.assert(!detailsEntryId?.data, "Should not run 'collectAutoImports' when faster path is available via `data`"); @@ -2761,12 +2761,12 @@ namespace ts.Completions { function pushAutoImportSymbol(symbol: Symbol, origin: SymbolOriginInfoResolvedExport | SymbolOriginInfoExport) { const symbolId = getSymbolId(symbol); - if (symbolToSortTextIdMap[symbolId] === SortText.GlobalsOrKeywords) { + if (symbolToSortTextMap[symbolId] === SortText.GlobalsOrKeywords) { // If an auto-importable symbol is available as a global, don't add the auto import return; } symbolToOriginInfoMap[symbols.length] = origin; - symbolToSortTextIdMap[symbolId] = importCompletionNode ? SortText.LocationPriority : SortText.AutoImportSuggestions; + symbolToSortTextMap[symbolId] = importCompletionNode ? SortText.LocationPriority : SortText.AutoImportSuggestions; symbols.push(symbol); } @@ -3131,7 +3131,7 @@ namespace ts.Completions { localsContainer.locals?.forEach((symbol, name) => { symbols.push(symbol); if (localsContainer.symbol?.exports?.has(name)) { - symbolToSortTextIdMap[getSymbolId(symbol)] = SortText.OptionalMember; + symbolToSortTextMap[getSymbolId(symbol)] = SortText.OptionalMember; } }); return GlobalsSearch.Success; @@ -3571,7 +3571,7 @@ namespace ts.Completions { symbols.forEach(m => { if (m.flags & SymbolFlags.Optional) { const symbolId = getSymbolId(m); - symbolToSortTextIdMap[symbolId] = symbolToSortTextIdMap[symbolId] ?? SortText.OptionalMember; + symbolToSortTextMap[symbolId] = symbolToSortTextMap[symbolId] ?? SortText.OptionalMember; } }); } @@ -3583,7 +3583,7 @@ namespace ts.Completions { } for (const contextualMemberSymbol of contextualMemberSymbols) { if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) { - symbolToSortTextIdMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment; + symbolToSortTextMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment; } } } @@ -3606,9 +3606,9 @@ namespace ts.Completions { CompletionKind.ObjectPropertyDeclaration, /*jsxIdentifierExpected*/ false); if (displayName) { - const originalSortText = symbolToSortTextIdMap[symbolId] ?? SortText.LocationPriority; + const originalSortText = symbolToSortTextMap[symbolId] ?? SortText.LocationPriority; const { name } = displayName; - symbolToSortTextIdMap[symbolId] = SortText.ObjectLiteralProperty(originalSortText, name); + symbolToSortTextMap[symbolId] = SortText.ObjectLiteralProperty(originalSortText, name); } } } From 3820f32ee8070e55b30a92cb784b7d38f3cb5a89 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 29 Mar 2022 13:18:25 -0700 Subject: [PATCH 34/36] CR: more fixes --- src/services/completions.ts | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 0364ae580ad0b..4d0be97e15b62 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1065,24 +1065,13 @@ namespace ts.Completions { preferences: UserPreferences, formatContext: formatting.FormatContext | undefined, ): { insertText: string, isSnippet?: true, importAdder: codefix.ImportAdder, sourceDisplay: SymbolDisplayPart[] } | undefined { - let isSnippet: true | undefined; + const isSnippet = preferences.includeCompletionsWithSnippetText || undefined; let insertText: string = name; const sourceFile = enclosingDeclaration.getSourceFile(); const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); - let body; - if (preferences.includeCompletionsWithSnippetText) { - isSnippet = true; - const emptyStmt = factory.createEmptyStatement(); - body = factory.createBlock([emptyStmt], /* multiline */ true); - setSnippetElement(emptyStmt, { kind: SnippetKind.TabStop, order: 0 }); - } - else { - body = factory.createBlock([], /* multiline */ true); - } - - const method = createObjectLiteralMethod(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder, /*body*/ body); + const method = createObjectLiteralMethod(symbol, enclosingDeclaration, sourceFile, program, host, preferences, importAdder); if (!method) { return undefined; } @@ -1122,7 +1111,6 @@ namespace ts.Completions { host: LanguageServiceHost, preferences: UserPreferences, importAdder: codefix.ImportAdder, - body: Block, ): MethodDeclaration | undefined { const declarations = symbol.getDeclarations(); if (!(declarations && declarations.length)) { @@ -1168,6 +1156,17 @@ namespace ts.Completions { typeNode = importableReference.typeNode; codefix.importSymbols(importAdder, importableReference.symbols); } + + let body; + if (preferences.includeCompletionsWithSnippetText) { + const emptyStmt = factory.createEmptyStatement(); + body = factory.createBlock([emptyStmt], /* multiline */ true); + setSnippetElement(emptyStmt, { kind: SnippetKind.TabStop, order: 0 }); + } + else { + body = factory.createBlock([], /* multiline */ true); + } + return factory.createMethodDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, @@ -2776,17 +2775,14 @@ namespace ts.Completions { if (isInJSFile(location)) { return; } - if (members === symbols) { - members = members.slice(); - } - for (const member of members) { + members.forEach(member => { if (!isObjectLiteralMethodSymbol(member)) { - continue; + return; } const origin = { kind: SymbolOriginInfoKind.ObjectLiteralMethod }; symbolToOriginInfoMap[symbols.length] = origin; symbols.push(member); - } + }); } function isObjectLiteralMethodSymbol(symbol: Symbol): boolean { From e7a51e6231a4e308a7acc0056b2d6e566449e2e8 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 29 Mar 2022 15:14:05 -0700 Subject: [PATCH 35/36] CR: restructure main flow --- src/services/completions.ts | 62 +++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 4d0be97e15b62..1944e7ee816b4 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -95,6 +95,13 @@ namespace ts.Completions { declaration: TypeOnlyAliasDeclaration; } + interface SymbolOriginInfoObjectLiteralMethod extends SymbolOriginInfo { + importAdder: codefix.ImportAdder, + insertText: string, + sourceDisplay: SymbolDisplayPart[], + isSnippet?: true, + } + function originIsThisType(origin: SymbolOriginInfo): boolean { return !!(origin.kind & SymbolOriginInfoKind.ThisType); } @@ -131,7 +138,7 @@ namespace ts.Completions { return !!(origin && origin.kind & SymbolOriginInfoKind.TypeOnlyAlias); } - function originIsObjectLiteralMethod(origin: SymbolOriginInfo | undefined): boolean { + function originIsObjectLiteralMethod(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoObjectLiteralMethod { return !!(origin && origin.kind & SymbolOriginInfoKind.ObjectLiteralMethod); } @@ -276,7 +283,7 @@ namespace ts.Completions { return getLabelCompletionAtPosition(previousToken.parent); } - const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, preferences, /*detailsEntryId*/ undefined, host, cancellationToken); + const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, preferences, /*detailsEntryId*/ undefined, host, formatContext, cancellationToken); if (!completionData) { return undefined; } @@ -772,13 +779,8 @@ namespace ts.Completions { } if (origin && originIsObjectLiteralMethod(origin)) { - const objectLiteral = cast(tryGetObjectLikeCompletionContainer(contextToken), isObjectLiteralExpression); let importAdder; - const entry = getEntryForObjectLiteralMethodCompletion(symbol, name, objectLiteral, program, host, options, preferences, formatContext); - if (!entry) { - return undefined; - } - ({ insertText, isSnippet, importAdder, sourceDisplay } = entry); + ({ insertText, isSnippet, importAdder, sourceDisplay } = origin); source = CompletionSource.ObjectLiteralMethodSnippet; sortText = SortText.SortBelow(sortText); if (importAdder.hasFixes()) { @@ -1557,7 +1559,7 @@ namespace ts.Completions { } const compilerOptions = program.getCompilerOptions(); - const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host); + const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host, /*formatContext*/ undefined); if (!completionData) { return { type: "none" }; } @@ -1895,6 +1897,7 @@ namespace ts.Completions { preferences: UserPreferences, detailsEntryId: CompletionEntryIdentifier | undefined, host: LanguageServiceHost, + formatContext: formatting.FormatContext | undefined, cancellationToken?: CancellationToken, ): CompletionData | Request | undefined { const typeChecker = program.getTypeChecker(); @@ -2770,7 +2773,7 @@ namespace ts.Completions { } /* Mutates `symbols` and `symbolToOriginInfoMap`. */ - function collectObjectLiteralMethodSymbols(members: Symbol[]): void { + function collectObjectLiteralMethodSymbols(members: Symbol[], enclosingDeclaration: ObjectLiteralExpression): void { // TODO: support JS files. if (isInJSFile(location)) { return; @@ -2779,7 +2782,29 @@ namespace ts.Completions { if (!isObjectLiteralMethodSymbol(member)) { return; } - const origin = { kind: SymbolOriginInfoKind.ObjectLiteralMethod }; + const displayName = getCompletionEntryDisplayNameForSymbol( + member, + getEmitScriptTarget(compilerOptions), + /*origin*/ undefined, + CompletionKind.ObjectPropertyDeclaration, + /*jsxIdentifierExpected*/ false); + if (!displayName) { + return; + } + const { name } = displayName; + const entryProps = getEntryForObjectLiteralMethodCompletion( + member, + name, + enclosingDeclaration, + program, + host, + compilerOptions, + preferences, + formatContext); + if (!entryProps) { + return; + } + const origin: SymbolOriginInfoObjectLiteralMethod = { kind: SymbolOriginInfoKind.ObjectLiteralMethod, ...entryProps }; symbolToOriginInfoMap[symbols.length] = origin; symbols.push(member); }); @@ -3034,18 +3059,14 @@ namespace ts.Completions { // Add filtered items to the completion list const filteredMembers = filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers)); symbols = concatenate(symbols, filteredMembers); + setSortTextToOptionalMember(); if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression && preferences.includeCompletionsWithObjectLiteralMethodSnippets && preferences.includeCompletionsWithInsertText) { - collectObjectLiteralMethodSymbols(filteredMembers); + transformObjectLiteralMembersSortText(symbolsStartIndex); + collectObjectLiteralMethodSymbols(filteredMembers, objectLikeContainer); } } - setSortTextToOptionalMember(); - if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression - && preferences.includeCompletionsWithObjectLiteralMethodSnippets - && preferences.includeCompletionsWithInsertText) { - transformObjectLiteralMembersSortText(symbolsStartIndex); - } return GlobalsSearch.Success; } @@ -3585,14 +3606,9 @@ namespace ts.Completions { } function transformObjectLiteralMembersSortText(start: number): void { - const pastSymbolIds: Set = new Set(); for (let i = start; i < symbols.length; i++) { const symbol = symbols[i]; const symbolId = getSymbolId(symbol); - if (pastSymbolIds.has(symbolId)) { - continue; - } - pastSymbolIds.add(symbolId); const origin = symbolToOriginInfoMap?.[i]; const target = getEmitScriptTarget(compilerOptions); const displayName = getCompletionEntryDisplayNameForSymbol( From 203aab00ccf4006d10b6730b7cb5959a4f1780d6 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 29 Mar 2022 15:18:29 -0700 Subject: [PATCH 36/36] minor fix --- src/services/codefixes/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 4949bf7f88289..c34148453b4f1 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -367,7 +367,7 @@ namespace ts.codefix { return parameters; } - export function createMethodImplementingSignatures( + function createMethodImplementingSignatures( checker: TypeChecker, context: TypeConstructionContext, enclosingDeclaration: ClassLikeDeclaration,