Add Infer function return type refactoring code action - #4749
Conversation
|
@microsoft-github-policy-service agree |
There was a problem hiding this comment.
Pull request overview
Adds an LSP refactoring that infers and inserts TypeScript function return types.
Changes:
- Registers and advertises the refactor code-action kind.
- Implements return-type inference and edit generation.
- Adds fourslash helpers and availability tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
internal/lsp/server.go |
Advertises rewrite refactoring support. |
internal/ls/codeactions.go |
Adds refactor providers and LSP conversion. |
internal/ls/codeactions_refactor_inferreturntype.go |
Implements return-type inference. |
internal/fourslash/fourslash.go |
Adds refactor availability assertions. |
internal/fourslash/tests/refactorInferReturnType_test.go |
Tests refactor availability. |
Comments suppressed due to low confidence (2)
internal/ls/codeactions_refactor_inferreturntype.go:126
- An inferred type predicate must be emitted as the full predicate, not just its target type. For
function isString(x: unknown) { return typeof x === "string"; }, this path turns the inferredx is stringinto: string, which both loses narrowing and conflicts with the boolean body. UseTypePredicateToTypePredicateNodeand carry that node through to the edit, as the existing isolated-declarations fixer does.
typePredicate := ch.GetTypePredicateOfSignature(signature)
if typePredicate != nil && typePredicate.Type() != nil {
return typePredicate.Type()
internal/ls/codeactions_refactor_inferreturntype.go:156
- The ancestor walk crosses function-body boundaries, so the refactor is offered at arbitrary positions inside a function (including inside an arrow's body). The TypeScript reference deliberately quits at a block or at an arrow body/
=>boundary; apply the same boundary checks here so the action remains contextual to the declaration.
node = node.Parent
5c01a6a to
ef5db47
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
internal/ls/codeactions.go:194
- The refactor category gate does not filter individual actions by
Context.Only: every registered action is invoked here, andRefactorAction.Kindsis never consulted. A request for onlyrefactor.extracttherefore still receives thisrefactor.rewrite.function.returnTypeaction, contrary to LSPonlysemantics. Match each requested kind against the action's advertised kinds before calling its factory.
for _, provider := range refactorProviders {
for _, action := range provider.RefactorActions {
providerActions, err := action.GetActions(ctx, refactorContext, action.ID)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
internal/ls/codeactions.go:194
- The new
Kindsmetadata is never consulted, so an LSP request withonly: ["refactor.extract"]still runs this rewrite provider and returnsrefactor.rewrite.function.returnType.onlymust be matched against each action's declared kinds (requested kind equals or contains an action kind) before callingGetActions; this also needs a filter-specific test.
for _, provider := range refactorProviders {
for _, action := range provider.RefactorActions {
providerActions, err := action.GetActions(ctx, refactorContext, action.ID)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
internal/ls/codeactions_refactor_inferreturntype.go:131
- The predicate path still omits
InternalFlagsAllowUnresolvedNames:Checker.TypePredicateToTypePredicateNodehardcodesInternalFlagsNone(internal/checker/printer.go:483-485), while unresolved computed members are dropped without the flag (internal/checker/nodecopy.go:648-657). Consequently, an inferred predicate can receive an incomplete target type even though the normal and overload paths now preserve these names. Add an overload that accepts internal flags and passAllowUnresolvedNames, matching the upstream refactor.
return ch.TypePredicateToTypePredicateNode(typePredicate, enclosingDecl, nodebuilder.FlagsNoTruncation, idToSymbol)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
internal/ls/codeactions_refactor_inferreturntype.go:170
- The same descendant lookup offers the action from a class property name, such as
class C { f/*caret*/ = () => 1 }, although the caret is not within the arrow function. This differs from the reference implementation’s ancestor-only applicability check. Restrict the fallback to positions inside the initializer, or remove it if unnecessary.
if ast.IsPropertyDeclaration(node) {
if init := node.AsPropertyDeclaration().Initializer; init != nil && convertibleDeclaration(init) {
return init
}
}
internal/ls/codeactions_refactor_inferreturntype.go:131
- This predicate conversion still omits
InternalFlagsAllowUnresolvedNames:Checker.TypePredicateToTypePredicateNodehard-codesInternalFlagsNoneininternal/checker/printer.go:483-485, while the reference infer-return-type implementation explicitly passes the allow-unresolved flag. Consequently, inferred predicates whose narrowed type contains unresolved/computed members can be dropped or rendered incompletely. Add anExvariant that accepts internal flags and use it here, matching the normal and overload paths.
return ch.TypePredicateToTypePredicateNode(typePredicate, enclosingDecl, nodebuilder.FlagsNoTruncation, idToSymbol)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
internal/ls/codeactions_refactor_inferreturntype.go:131
- The predicate conversion still uses
checker.TypePredicateToTypePredicateNode, whose implementation hardcodesInternalFlagsNone(internal/checker/printer.go:483-485). Unlike the normal and overload paths above—and the TypeScript reference—this can drop unresolved computed members from the narrowed type (internal/checker/nodecopy.go:655-657), producing an incompletex is {}-style annotation. Add anExvariant that accepts internal flags, passInternalFlagsAllowUnresolvedNameshere, and cover a predicate containing such a computed member.
return ch.TypePredicateToTypePredicateNode(typePredicate, enclosingDecl, nodebuilder.FlagsNoTruncation, idToSymbol)
Discussion #4747
Implementing one of the missing refactoring code action. I created a basic infrastructure, especially for testing with
fourslash.This is my first contribution. I'm new to Golang, and I used AI to support myself.
I reviewed and tested manually myself the refactoring code action on my personal playground project.
I would like to continue handling any further code review feedback by myself - will be happy to continue contributing!