From 701c5f3687634bdd51f4b7c2e6fb8be56b4dc81f Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 9 Sep 2021 21:00:38 -0700 Subject: [PATCH] prototype creation for method override completion snippet --- src/server/protocol.ts | 2 +- src/services/completions.ts | 64 ++++++++++++++++++- .../fourslash/completionsOverridingMethod.ts | 36 +++++++++++ 3 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 tests/cases/fourslash/completionsOverridingMethod.ts diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 6147077051b..46badf082fa 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2289,7 +2289,7 @@ namespace ts.server.protocol { /** * Human-readable description of the `source`. */ - sourceDisplay?: SymbolDisplayPart[]; + sourceDisplay?: SymbolDisplayPart[]; /** * If true, this completion should be highlighted as recommended. There will only be one of these. * This will be set when we know the user should write an expression with a certain type and that type is an enum or constructable class. diff --git a/src/services/completions.ts b/src/services/completions.ts index 764e64c32e1..c18ddff2cc5 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -662,10 +662,18 @@ namespace ts.Completions { sourceDisplay = [textPart(origin.moduleSpecifier)]; if (importCompletionNode) { ({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importCompletionNode, origin, useSemicolons, options, preferences)); - isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined; + isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined; // >> Using snippet here. } } + if (isMethodOverrideCompletion(symbol, location)) { + ({ insertText } = getInsertTextForMethodOverrideCompletion(typeChecker, options, name, symbol, location)); + // replacementSpan = undefined; // >> TODO + isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined; + hasAction = true; + } + + // >> Should this actually be here and not at the very end of the function? if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) { return undefined; } @@ -685,7 +693,7 @@ namespace ts.Completions { // entries (like JavaScript identifier entries). return { name, - kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location), // TODO: GH#18217 + kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location), kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), sortText, source: getSourceFromOrigin(origin), @@ -701,6 +709,58 @@ namespace ts.Completions { }; } + // >> TODO: Find better location for code + function isMethodOverrideCompletion(symbol: Symbol, location: Node): boolean { + return !!(symbol.flags & SymbolFlags.Method) && isPropertyDeclaration(location.parent); + // >> TODO: add more checks. e.g. the method suggestion could come from an interface the class implements, + // or some other possibilities? + } + + function getInsertTextForMethodOverrideCompletion(_typeChecker: TypeChecker, options: CompilerOptions, name: string, symbol: Symbol, location: Node) { + // const methodType = typeChecker.getTypeOfSymbolAtLocation(symbol, location); + // const signatures = methodType.getCallSignatures(); + const methodDeclarations = symbol.declarations?.filter(isMethodDeclaration); + // >> TODO: what should we do if we have more than 1 signature? when could that happen? + if (methodDeclarations?.length === 1) { + const originalDeclaration = methodDeclarations[0]; + const modifiers = originalDeclaration.modifiers?.filter(modifier => { + switch (modifier.kind) { // >> Simplify this if we only need to filter out "abstract" modifier. + case SyntaxKind.AbstractKeyword: + return false; + default: + return true; + } + }); + if (options.noImplicitOverride) { + modifiers?.push(factory.createModifier(SyntaxKind.OverrideKeyword)); + // Assuming it's ok if this modifier is duplicated. + } + + const completionDeclaration = factory.createMethodDeclaration( + /*decorators*/ undefined, // I'm guessing we don't want to deal with decorators? + /*modifiers*/ modifiers, + /*asteriskToken*/ originalDeclaration.asteriskToken, + /*name*/ name, + /*questionToken*/ originalDeclaration.questionToken, + /*typeParameters*/ originalDeclaration.typeParameters, + /*parameters*/ originalDeclaration.parameters, + /*type*/ originalDeclaration.type, + /*body*/ factory.createBlock([], /*multiLine*/ true)); + // const insertText = completionDeclaration.getText(location.getSourceFile()); + // const insertText = completionDeclaration.getText(); // Doesn't work with synthetic nodes + const printer = createPrinter({ + removeComments: true, + module: options.module, + target: options.target, + }); + const insertText = printer.printNode(EmitHint.Unspecified, completionDeclaration, location.getSourceFile()); + return { + insertText, + }; + } + return { }; + } + function originToCompletionEntryData(origin: SymbolOriginInfoExport): CompletionEntryData | undefined { return { exportName: origin.exportName, diff --git a/tests/cases/fourslash/completionsOverridingMethod.ts b/tests/cases/fourslash/completionsOverridingMethod.ts new file mode 100644 index 00000000000..75c1d6a6665 --- /dev/null +++ b/tests/cases/fourslash/completionsOverridingMethod.ts @@ -0,0 +1,36 @@ +/// + +////abstract class Base { +//// abstract foo(param1: string, param2: boolean): Promise; +////} +//// +////class Sub extends Base { +//// f/*a*/ +////} + + +verify.completions({ + marker: "a", + isNewIdentifierLocation: true, + preferences: { + includeCompletionsWithInsertText: true, + }, + // exact: [ + + // ], + includes: [ + { + name: "foo", + isRecommended: true, + sortText: completion.SortText.LocationPriority, + replacementSpan: { + fileName: "", + pos: 0, + end: 0, + }, + insertText: +`foo(param1: string, param2: boolean): Promise { +}`, + } + ], +});