GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Add method signature completions by gabritto · Pull Request #46370 · microsoft/TypeScript

Conversation

@gabritto

@gabritto Gabriela Araujo Britto (gabritto) commented Oct 14, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Fixes #45670.

Overview

Currently, when we offer completions for methods inside a class-like declaration, we only offer method names:

abstract class Base {
    abstract foo(param1: string, param2: boolean): Promise<void>;
}

class Sub extends Base {
    | /* completions offered: "foo" */
}

but in many cases, we have enough information to offer the whole method signature/declaration, like so:

class Sub extends Base {
    foo(param1: string, param2: boolean): Promise<void> {
        | // cursor ends up here
    }
}

This PR makes it so that we offer method and property declarations inside class declarations whenever possible.
The rough outline of how it works is:

  • At some point in completions.ts, when answering a completions request, we gather symbols to be offered as completions, and then we call createCompletionEntry for each of those symbols. createCompletionEntry then converts a symbol into a proper CompletionEntry, which is what we will send back in response to a completion request.
  • Ultimately, this PR changes how a CompletionEntry is created for a symbol, in the cases mentioned above.
    The main thing is the insertText property of a CompletionEntry: insertTextis the text that is inserted when a user accepts a completion, so for this new feature we want theinsertText` to be a whole signature/declaration, instead of just a name/identifier.
  • To achieve that, there are two new functions used inside createCompletionEntry:
    • isClassLikeMemberCompletion(symbol): this decides whether or not a completion symbol is a class member and we should offer whole signature completions for it;
    • if the above function returns true, we call getEntryForMemberCompletion(symbol) for that completion symbol. This function will define what the insertText should be for that completion.

Core functionality

The main functionality of converting a symbol into a member node is provided by codefix.addNewNodeForMemberSymbol, called inside getEntryForMemberCompletion. This function already existed and is used by multiple codefixes, like classIncorrectlyImplementsInterface, to generate the member nodes that are missing in a class-like declaration.

Since codefix.addNewNodeForMemberSymbol was implemented for this missing member node scenario, there are some things we need to add on top of it to make everything work for a completions scenario, e.g. checking whether we should add an override modifier. Those additional constraints are mostly dealt with in the callback function passed to codefix.addNewNodeForMemberSymbol.

Snippets

Completions can use snippets to control where the cursor should go to when the completion is inserted (i.e. tab stops), to indicate the user should fill in some part of the completion, and to make it easier for the user to change parts of the completion-inserted text (i.e. placeholders). Snippet syntax reference can be found here: https://code.visualstudio.com/docs/editor/userdefinedsnippets#_snippet-syntax.

This new completion, so far, inserts method signatures or declarations. With that in mind, there are many places in such a signature/declaration where we could add snippet placeholders, to make it easy for the user to modify parts of the completion.

First, a final tabstop (i.e. $0) is always placed in the completion declaration's implementation body, if there is one. This means that the cursor will be at the empty body as its final position, once the completion is inserted.
Other than that, several parts of the declaration can be modified in ways that make sense (i.e. the code will type check). For example, the parameter and return types can be modified, as long as they conform to the type of the declaration being overridden/implemented.

The question of where to add placeholders/tabstops was discussed in a meeting. One of the initial points was that there were some cases when it was very likely that the user would want to change parts of the declaration, e.g. when changing the parameter types of an overload implementation, and in the rest of the cases it was less likely that the user would want to do so.

However, it was pointed out that snippets rely on "muscle memory" and predictability about where there will be tabstops/placeholders. So even though for the same declaration position (e.g. parameter type), we sometimes want to have a placeholder and sometimes not, it is better to favor uniformity and have the same tabstops/placeholders in the same declaration parts.

  • Open question: what parts of a member declaration should be snippet placeholders? e.g. parameter names, parameter types, return type...

Emitter changes

The main concern implementation-wise with snippet support is emitting an AST into a string properly.
Some completions already use snippets (e.g. import completions), but these completion snippets are manipulated as strings. The completion feature in this PR requires a more complex completion text (e.g. a method signature), which becomes very tricky to manipulate as string, and is better to handle as an AST. So I decided to add support for snippets in our AST and emitter directly.

There is a new property, SnippetElement, added to EmitNode. An EmitNode is an optional property of nodes that gathers information pertinent to the emitter. The new SnippetElement property, when present in the emit node of a node, indicates that that node should be interpreted as some type of snippet element, e.g. a placeholder or a tabstop. The emitter will then handle printing that node into an appropriate string.

Example:
In the code

interface I {
    fun(a: string | number): number;
}

class C {
    fun(a: string | number): number {
           ^^^^^^^^^^^^^^^ // placeholder
    }
}

the string | number type node will have a SnippetElement of kind SnippetKind.Placeholder attached to its emit node.
Then, when the emitter is printing this type node, it will first print ${n:, then it will recursively print the (escaped) actual node, i.e. string | number, and then it will print }. In the end, the node will be printed as ${n:string | number}, which is the syntax for a placeholder whose text is string | number. (Note: n refers to a natural number here, corresponding to the order in which this placeholder/tabstop will be visited when the user presses tab sequentially).

Another thing added to the emitter is support for escaping text that would make the editor incorrectly understand a node as a snippet element.
For instance, if we have a method declaration like "$usd"(a: number): number, if we don't escape the $, the editor will think that $usd is a snippet variable instead of an identifier name.

Future work

  • Support some edge cases pointed out in this PR's comments
  • Support this feature in JS files (including support for JSDoc)
  • Support offering those declaration completions for class properties?
  • Support offering those declaration completions in objects/interfaces/object types and not only class-like declarations?
  • Improve overloads support:
    • heuristics for determining default types in implementation signature
    • snippet placeholder sync?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

alexrock, DanielRosenwasser, mjbvz, FilipeBeck, wsmd, worstpractice, and RyanCavanaugh reacted with thumbs up emoji mjbvz, worstpractice, and RyanCavanaugh reacted with hooray emoji DanielRosenwasser, FilipeBeck, and worstpractice reacted with heart emoji
All reactions
  • 👍 7 reactions
  • 🎉 3 reactions
  • ❤️ 3 reactions
Gabriela Araujo Britto (gabritto) added 23 commits September 13, 2021 09:44
@gabritto
prototype creation for method override completion snippet
701c5f3
@gabritto
WIP: start using codefix addNewNodeForMemberSymbol to create method…
2c84d88
… decl node
@gabritto
update type of addNewNodeForMemberSymbol
4d2d476
@gabritto
add more tests and support more cases
c608a6e
@gabritto
add more tests and fix some details
2fa43e8
@gabritto
wip: more fixes and tests
1d04720
@gabritto
expose check override modifier in checker
905caad
@gabritto
fix test
8ad4042
@gabritto
WIP: add snippet support
d20eb68
@gabritto
WIP: snippet support on emitter, adding snippets in completions
c81f385
@gabritto
make add snippets work with overloads (not synced)
b124c84
@gabritto
fix snippet adding
db408cc
@gabritto
rebase
d7809c1
@gabritto
WIP: try to add snippet escaping in emitter
41a3c19
@gabritto
support escaping in snippets
69ee9fc
@gabritto
small fixes; fixed tests
c3a64bc
@gabritto
more tests and fixes
e079f71
@gabritto
fix new tests
d7c26c4
@gabritto
fix modifier inheritance for overloads
fe4fce8
@gabritto
Merge branch 'main' into gabritto/issue45670
b8bb27a
@gabritto
merge conflict fixes; remove comments
952aac1
@gabritto
throw error if setOptions is called but not implemented
00dc206
@gabritto
fix newline handling
28539b6
@typescript-bot

TypeScript Bot (typescript-bot) commented Oct 14, 2021

Copy link
Copy Markdown
Contributor

Thanks for the PR! It looks like you've changed the TSServer protocol in some way. Please ensure that any changes here don't break consumers of the current TSServer API. For some extra review, we'll ping Sheetal Nandi (@sheetalkamat), Andrew Casey (@amcasey), Matt Bierner (@mjbvz), Mine Starks (@minestarks) for you. Feel free to loop in other consumers/maintainers if necessary

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@typescript-bot TypeScript Bot (typescript-bot) assigned Gabriela Araujo Britto (gabritto) Oct 14, 2021
@typescript-bot TypeScript Bot (typescript-bot) added Author: Team For Milestone Bug PRs that fix a bug with a specific milestone labels Oct 14, 2021
@andrewbranch

Andrew Branch (andrewbranch) commented Oct 14, 2021

Copy link
Copy Markdown
Member

Fair to say I significantly underestimated how much work this feature would take 😅

orta and RyanCavanaugh reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gabritto
fix weird stuff
01eb2bb
amcasey
Andrew Casey (amcasey) reviewed Oct 15, 2021
View reviewed changes
Comment thread src/server/protocol.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Gabriela Araujo Britto (gabritto) added 2 commits October 25, 2021 14:08
@gabritto
fix isParameterDeclaration changes
a639aef
@gabritto
rename diagnostic to status and remove snippets from public api
4571e98
@beepepep

This comment has been minimized.

Sign in to view
Gabriela Araujo Britto (gabritto) added 3 commits October 25, 2021 15:01
@gabritto
rename emitter functions + fix indentation
3a6b6bf
@gabritto
check completion kind before calling isclasslikemembercompletion
302c0fc
@gabritto
fix missing type parameters
7bdeaa8
@gabritto

Gabriela Araujo Britto (gabritto) commented Oct 26, 2021

Copy link
Copy Markdown
Member Author
abstract class A<T> {
    abstract M(t: T): void;
    abstract M(t: number): void;
}

abstract class B extends A<number> {
    M(t: number): void;
    M(t: number): void;
    M(t: any): void {
        
    }
}

Probably out of scope, but this doesn't seem ideal.

Yes, this is a bug in codefix.addNewNodeForMemberSymbol. I think this function can't tell if two signatures will be the same because we are instantiating the extended class... Maybe it would be easier to deduplicate signatures somehow than trying to detect all of the possible cases involving instantiated generics? In any case, probably better addressed in a future PR.

andrewbranch reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Gabriela Araujo Britto (gabritto) added 2 commits October 26, 2021 15:42
@gabritto
Revert "fix missing type parameters"
68e5913
This reverts commit 7bdeaa8.
@gabritto
add isAmbient flag to addNewNodeForMemberSymbol
d796043
@gabritto

Gabriela Araujo Britto (gabritto) commented Oct 26, 2021

Copy link
Copy Markdown
Member Author

I think the third overload on the abstract case is a bug in general (likely a bug in the original codefix, not in this PR specifically?), since abstract declarations don't have an implementation signature. But either way, the T is not referenced in the last signature, whether it is a correct implementation signature or an extra overload. I believe it would be a noUnusedLocals error, or at least the type parameter would report as being unused, if it were added to that signature but never referenced within.

I fixed the bug of the extra overload.
Regarding type parameters: but in the scenario where we have to construct an implementation signature (because there isn't one available), we don't do anything but the very basics: we create as many parameters as we could need, all of them with type any, and the return type is just the union of each overload's return type.
So it's pretty hard to add type parameters: they would not show up in the parameters' types, they could only show up in the return type. But it's not clear to me that we can add type parameters to the implementation signature in a way that is useful to users more than it is annoying.

I'm open to suggestions, but I think we should leave this as is for now, and revisit later if/when we are improving the generation of implementation signatures.

andrewbranch and worstpractice reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Gabriela Araujo Britto (gabritto) added 2 commits October 26, 2021 16:29
@gabritto
add test for abstract overloads
f6ea5f2
@gabritto
refactor snippet escaping support
221edac
@andrewbranch

Andrew Branch (andrewbranch) commented Oct 27, 2021

Copy link
Copy Markdown
Member

Moving the escaping to a custom writer looks great! Throw this behind a user preference and I think this is good to go. We'll put the rest of Andrew Casey (@amcasey)’s suggestions in a follow-up bug to improve the pre-existing “add missing member” codefix.

amcasey and gabritto reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Gabriela Araujo Britto (gabritto) added 2 commits October 27, 2021 14:11
@gabritto
add user preference flag for enabling class member snippets
8e9cac9
@gabritto
update API baseline
c5f1166
@gabritto Gabriela Araujo Britto (gabritto) mentioned this pull request Oct 28, 2021
[typescript-language-features] Add includeCompletionsWithClassMemberSnippets preference microsoft/vscode#136045
Merged
@gabritto

Gabriela Araujo Britto (gabritto) commented Oct 28, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author
interface I {
    M(x: number): void;
}

interface J {
    M(x: string): void;
}

abstract class B implements I, J {
    M(x: number): void {
        
    }
    
}

I'm not sure what the intended behavior was here, but this produces an error.

Also, note the trailing newline - we'll probably get complaints about that.

Edit:
I considered an easy fix for the trailing newline, but that makes it so that when we use completions to add in a property or a signature, what's added is something like foo: number| or foo(): void|, with the cursor at the end of the declaration and no semicolons. So the extra newline seemed less annoying.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gabritto

Gabriela Araujo Britto (gabritto) commented Oct 28, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

Probably for a subsequent PR, but it won't add an import if one's needed for a type in the completed signature.

For future reference: I looked into this and that's because the needed imports get added to a TextChanges object. This works in a codefix scenario, but not in completions.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@andrewbranch

Andrew Branch (andrewbranch) commented Oct 28, 2021

Copy link
Copy Markdown
Member

Ah, that is something we should be able to fix later—that’s how auto-import completions work; the changes are calculated during CompletionDetails and attached to the that response on codeActions. Since you’re actually calculating them up front, we just need to copy that part of the CompletionDetails protocol to CompletionInfo (and probably make a small corresponding change to typescript-language-features). In LSP, CompletionInfo is allowed to eagerly return anything that CompletionDetails supports. The fact that our CompletionEntryDetails lacks codeActions is just a relic of the fact that we’ve never computed those actions up front for performance reasons.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@andrewbranch

Andrew Branch (andrewbranch) commented Oct 28, 2021

Copy link
Copy Markdown
Member

Gabriela Araujo Britto (@gabritto) are you planning to change the order of the tabstops before merging? Anything else you want to get in before a final review?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gabritto
update tabstop order
a2665d8
andrewbranch
Andrew Branch (andrewbranch) approved these changes Oct 28, 2021
View reviewed changes

@andrewbranch Andrew Branch (andrewbranch) left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work 🌟

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@andrewbranch Andrew Branch (andrewbranch) mentioned this pull request Oct 28, 2021
Method snippet follow-up #46568
Closed
3 of 5 tasks
@andrewbranch
Andrew Branch (andrewbranch) merged commit fd620c9 into main Oct 28, 2021
@andrewbranch
Andrew Branch (andrewbranch) deleted the gabritto/issue45670 branch October 28, 2021 23:05
andrewbranch
Andrew Branch (andrewbranch) reviewed Oct 29, 2021
View reviewed changes
Comment thread tests/cases/fourslash/completionsOverridingMethod6.ts
Comment on lines +85 to +89
replacementSpan: {
fileName: "",
pos: 0,
end: 0,
},

@andrewbranch Andrew Branch (andrewbranch) Oct 29, 2021

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess VS Code is ignoring this, but this doesn’t look right 🤔

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@gabritto Gabriela Araujo Britto (gabritto) Oct 29, 2021

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I thought this was the default replacement span. Should this have been undefined?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@andrewbranch Andrew Branch (andrewbranch) Oct 29, 2021

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think so.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@gabritto Gabriela Araujo Britto (gabritto) mentioned this pull request Oct 29, 2021
Change snippet in class member completions #46593
Closed
@gabritto Gabriela Araujo Britto (gabritto) mentioned this pull request Nov 8, 2021
Support class member snippet completions in JS files #46738
Open
5 tasks done
Martin Probst (mprobst) pushed a commit to mprobst/TypeScript that referenced this pull request Jan 10, 2022
@gabritto @andrewbranch @mprobst
Add method signature completions (microsoft#46370)
5f7406e
* prototype creation for method override completion snippet

* WIP: start using codefix `addNewNodeForMemberSymbol` to create method decl node

* update type of addNewNodeForMemberSymbol

* add more tests and support more cases

* add more tests and fix some details

* wip: more fixes and tests

* expose check override modifier in checker

* fix test

* WIP: add snippet support

* WIP: snippet support on emitter, adding snippets in completions

* make add snippets work with overloads (not synced)

* fix snippet adding

* rebase

* WIP: try to add snippet escaping in emitter

* support escaping in snippets

* small fixes; fixed tests

* more tests and fixes

* fix new tests

* fix modifier inheritance for overloads

* merge conflict fixes; remove comments

* throw error if setOptions is called but not implemented

* fix newline handling

* fix weird stuff

* fix tests

* fix more tests

* Fix unbound host.getNewLine

* fix isParameterDeclaration changes

* rename diagnostic to status and remove snippets from public api

* rename emitter functions + fix indentation

* check completion kind before calling isclasslikemembercompletion

* fix missing type parameters

* Revert "fix missing type parameters"

This reverts commit 7bdeaa8.

* add isAmbient flag to addNewNodeForMemberSymbol

* add test for abstract overloads

* refactor snippet escaping support

* add user preference flag for enabling class member snippets

* update API baseline

* update tabstop order

Co-authored-by: Andrew Branch <[email protected]>
@microsoft Microsoft (microsoft) locked as resolved and limited conversation to collaborators Oct 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

@andrewbranch Andrew Branch (andrewbranch) andrewbranch approved these changes
@sandersn Nathan Shively-Sanders (sandersn) Awaiting requested review from sandersn
+2 more reviewers
@amcasey Andrew Casey (amcasey) amcasey left review comments
@rbuckton Ron Buckton (rbuckton) rbuckton requested changes
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@andrewbranch Andrew Branch (andrewbranch)

@gabritto Gabriela Araujo Britto (gabritto)

Labels

Author: Team For Milestone Bug PRs that fix a bug with a specific milestone

Projects

PR Backlog
Archived in project

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Method override completions

Uh oh!

There was an error while loading. Please reload this page.

7 participants

@gabritto @typescript-bot @andrewbranch @orta @amcasey @beepepep @rbuckton
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.