From 592d9e1e87bc2586e14758a58f5b8f6b05682be6 Mon Sep 17 00:00:00 2001 From: Suzy Mueller Date: Thu, 7 Dec 2023 16:51:04 -0800 Subject: [PATCH] internal/lsp: convert refactor code actions to use codeAction/resolve Allow apply fix and change signature commands to return edits instead of applying the edits. Added a marker test for removing parameters using the new resolve logic. We probably want most refactoring code actions to work both ways. This also updates the fill struct test to make sure that the capabilities of the editor are being correctly respected. For golang/go#64510 Change-Id: If58f7bdff52ec8e1621c007d029c5b9b60bbdd3a Reviewed-on: https://go-review.googlesource.com/c/tools/+/548276 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley Reviewed-by: Alan Donovan --- gopls/doc/commands.md | 82 +++ gopls/internal/lsp/command/command_gen.go | 4 +- gopls/internal/lsp/command/interface.go | 8 +- .../internal/lsp/protocol/generate/tables.go | 1 + gopls/internal/lsp/protocol/tsprotocol.go | 2 +- gopls/internal/server/code_action.go | 146 +++-- gopls/internal/server/command.go | 34 +- gopls/internal/server/general.go | 1 + gopls/internal/server/unimplemented.go | 4 - gopls/internal/settings/api_json.go | 22 +- gopls/internal/settings/settings.go | 6 + .../internal/test/integration/fake/editor.go | 111 ++-- .../test/integration/misc/fix_test.go | 59 +- gopls/internal/test/integration/options.go | 7 + gopls/internal/test/marker/marker_test.go | 17 + .../testdata/codeaction/extract_variable.txt | 1 + .../codeaction/extract_variable_resolve.txt | 81 +++ .../testdata/codeaction/fill_struct.txt | 9 +- .../codeaction/fill_struct_resolve.txt | 586 ++++++++++++++++++ .../testdata/codeaction/removeparam.txt | 1 + .../codeaction/removeparam_resolve.txt | 258 ++++++++ .../marker/testdata/stubmethods/basic.txt | 1 + .../testdata/stubmethods/basic_resolve.txt | 31 + 23 files changed, 1346 insertions(+), 126 deletions(-) create mode 100644 gopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt create mode 100644 gopls/internal/test/marker/testdata/codeaction/fill_struct_resolve.txt create mode 100644 gopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt create mode 100644 gopls/internal/test/marker/testdata/stubmethods/basic_resolve.txt diff --git a/gopls/doc/commands.md b/gopls/doc/commands.md index a838c73df6b..e1eeff89e74 100644 --- a/gopls/doc/commands.md +++ b/gopls/doc/commands.md @@ -81,6 +81,47 @@ Args: "character": uint32, }, }, + // Whether to resolve and return the edits. + "ResolveEdits": bool, +} +``` + +Result: + +``` +{ + // Holds changes to existing resources. + "changes": map[golang.org/x/tools/gopls/internal/lsp/protocol.DocumentURI][]golang.org/x/tools/gopls/internal/lsp/protocol.TextEdit, + // Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes + // are either an array of `TextDocumentEdit`s to express changes to n different text documents + // where each text document edit addresses a specific version of a text document. Or it can contain + // above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations. + // + // Whether a client supports versioned document edits is expressed via + // `workspace.workspaceEdit.documentChanges` client capability. + // + // If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then + // only plain `TextEdit`s using the `changes` property are supported. + "documentChanges": []{ + "TextDocumentEdit": { + "textDocument": { ... }, + "edits": { ... }, + }, + "RenameFile": { + "kind": string, + "oldUri": string, + "newUri": string, + "options": { ... }, + "ResourceOperation": { ... }, + }, + }, + // A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and + // delete file / folder operations. + // + // Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`. + // + // @since 3.16.0 + "changeAnnotations": map[string]golang.org/x/tools/gopls/internal/lsp/protocol.ChangeAnnotation, } ``` @@ -101,6 +142,47 @@ Args: "end": { ... }, }, }, + // Whether to resolve and return the edits. + "ResolveEdits": bool, +} +``` + +Result: + +``` +{ + // Holds changes to existing resources. + "changes": map[golang.org/x/tools/gopls/internal/lsp/protocol.DocumentURI][]golang.org/x/tools/gopls/internal/lsp/protocol.TextEdit, + // Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes + // are either an array of `TextDocumentEdit`s to express changes to n different text documents + // where each text document edit addresses a specific version of a text document. Or it can contain + // above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations. + // + // Whether a client supports versioned document edits is expressed via + // `workspace.workspaceEdit.documentChanges` client capability. + // + // If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then + // only plain `TextEdit`s using the `changes` property are supported. + "documentChanges": []{ + "TextDocumentEdit": { + "textDocument": { ... }, + "edits": { ... }, + }, + "RenameFile": { + "kind": string, + "oldUri": string, + "newUri": string, + "options": { ... }, + "ResourceOperation": { ... }, + }, + }, + // A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and + // delete file / folder operations. + // + // Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`. + // + // @since 3.16.0 + "changeAnnotations": map[string]golang.org/x/tools/gopls/internal/lsp/protocol.ChangeAnnotation, } ``` diff --git a/gopls/internal/lsp/command/command_gen.go b/gopls/internal/lsp/command/command_gen.go index fb518c71860..ae02543691b 100644 --- a/gopls/internal/lsp/command/command_gen.go +++ b/gopls/internal/lsp/command/command_gen.go @@ -118,13 +118,13 @@ func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Inte if err := UnmarshalArgs(params.Arguments, &a0); err != nil { return nil, err } - return nil, s.ApplyFix(ctx, a0) + return s.ApplyFix(ctx, a0) case "gopls.change_signature": var a0 ChangeSignatureArgs if err := UnmarshalArgs(params.Arguments, &a0); err != nil { return nil, err } - return nil, s.ChangeSignature(ctx, a0) + return s.ChangeSignature(ctx, a0) case "gopls.check_upgrades": var a0 CheckUpgradesArgs if err := UnmarshalArgs(params.Arguments, &a0); err != nil { diff --git a/gopls/internal/lsp/command/interface.go b/gopls/internal/lsp/command/interface.go index 152387c2053..473fca7e53b 100644 --- a/gopls/internal/lsp/command/interface.go +++ b/gopls/internal/lsp/command/interface.go @@ -44,7 +44,7 @@ type Interface interface { // ApplyFix: Apply a fix // // Applies a fix to a region of source code. - ApplyFix(context.Context, ApplyFixArgs) error + ApplyFix(context.Context, ApplyFixArgs) (*protocol.WorkspaceEdit, error) // Test: Run test(s) (legacy) // @@ -216,7 +216,7 @@ type Interface interface { // // This command is experimental, currently only supporting parameter removal. // Its signature will certainly change in the future (pun intended). - ChangeSignature(context.Context, ChangeSignatureArgs) error + ChangeSignature(context.Context, ChangeSignatureArgs) (*protocol.WorkspaceEdit, error) // DiagnoseFiles: Cause server to publish diagnostics for the specified files. // @@ -257,6 +257,8 @@ type ApplyFixArgs struct { URI protocol.DocumentURI // The document range to scan for fixes. Range protocol.Range + // Whether to resolve and return the edits. + ResolveEdits bool } type URIArg struct { @@ -500,6 +502,8 @@ type AddTelemetryCountersArgs struct { // ChangeSignatureArgs specifies a "change signature" refactoring to perform. type ChangeSignatureArgs struct { RemoveParameter protocol.Location + // Whether to resolve and return the edits. + ResolveEdits bool } // DiagnoseFilesArgs specifies a set of files for which diagnostics are wanted. diff --git a/gopls/internal/lsp/protocol/generate/tables.go b/gopls/internal/lsp/protocol/generate/tables.go index 2b744a5a052..5ed634a1abb 100644 --- a/gopls/internal/lsp/protocol/generate/tables.go +++ b/gopls/internal/lsp/protocol/generate/tables.go @@ -63,6 +63,7 @@ var renameProp = map[prop]string{ {"CancelParams", "id"}: "interface{}", {"Command", "arguments"}: "[]json.RawMessage", {"CompletionItem", "textEdit"}: "TextEdit", + {"CodeAction", "data"}: "json.RawMessage", // delay unmarshalling commands {"Diagnostic", "code"}: "interface{}", {"Diagnostic", "data"}: "json.RawMessage", // delay unmarshalling quickfixes diff --git a/gopls/internal/lsp/protocol/tsprotocol.go b/gopls/internal/lsp/protocol/tsprotocol.go index 48adb18afca..39d83726df1 100644 --- a/gopls/internal/lsp/protocol/tsprotocol.go +++ b/gopls/internal/lsp/protocol/tsprotocol.go @@ -489,7 +489,7 @@ type CodeAction struct { // a `textDocument/codeAction` and a `codeAction/resolve` request. // // @since 3.16.0 - Data interface{} `json:"data,omitempty"` + Data *json.RawMessage `json:"data,omitempty"` } // The Client Capabilities of a {@link CodeActionRequest}. diff --git a/gopls/internal/server/code_action.go b/gopls/internal/server/code_action.go index 4ec105fa34d..15911280c05 100644 --- a/gopls/internal/server/code_action.go +++ b/gopls/internal/server/code_action.go @@ -5,7 +5,9 @@ package server import ( + "bytes" "context" + "encoding/json" "fmt" "go/ast" "sort" @@ -24,6 +26,7 @@ import ( "golang.org/x/tools/gopls/internal/mod" "golang.org/x/tools/gopls/internal/settings" "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/slices" "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/event/tag" "golang.org/x/tools/internal/imports" @@ -179,7 +182,7 @@ func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionPara } if want[protocol.RefactorExtract] { - extractions, err := refactorExtract(pgf, params.Range) + extractions, err := refactorExtract(pgf, params.Range, snapshot.Options()) if err != nil { return nil, err } @@ -226,19 +229,15 @@ func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionPara return protocol.CodeAction{}, false, nil } cmd, err := command.NewApplyFixCommand(d.Message, command.ApplyFixArgs{ - URI: pgf.URI, - Fix: string(settings.StubMethods), - Range: pd.Range, + URI: pgf.URI, + Fix: string(settings.StubMethods), + Range: pd.Range, + ResolveEdits: supportsResolveEdits(snapshot.Options()), }) if err != nil { return protocol.CodeAction{}, false, err } - return protocol.CodeAction{ - Title: d.Message, - Kind: protocol.QuickFix, - Command: &cmd, - Diagnostics: []protocol.Diagnostic{pd}, - }, true, nil + return newCodeAction(d.Message, protocol.QuickFix, &cmd, nil, snapshot.Options()), true, nil }() if err != nil { return nil, err @@ -249,7 +248,7 @@ func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionPara } if want[protocol.RefactorRewrite] { - rewrites, err := refactorRewrite(snapshot, pkg, pgf, fh, params.Range) + rewrites, err := refactorRewrite(snapshot, pkg, pgf, fh, params.Range, snapshot.Options()) if err != nil { return nil, err } @@ -281,6 +280,54 @@ func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionPara } } +// ResolveCodeAction resolves missing Edit information (that is, computes the +// details of the necessary patch) in the given code action using the provided +// Data field of the CodeAction, which should contain the raw json of a protocol.Command. +// +// This should be called by the client before applying code actions, when the +// client has code action resolve support. +// +// This feature allows capable clients to preview and selectively apply the diff +// instead of applying the whole thing unconditionally through workspace/applyEdit. +func (s *server) ResolveCodeAction(ctx context.Context, ca *protocol.CodeAction) (*protocol.CodeAction, error) { + ctx, done := event.Start(ctx, "lsp.Server.resolveCodeAction") + defer done() + + // Only resolve the code action if there is Data provided. + // TODO(suzmue): publish protocol.unmarshalParams as protocol.UnmarshalJSON + // and use it consistently where we need to unmarshal to handle all null checks. + if ca.Data != nil && len(*ca.Data) != 0 && !bytes.Equal(*ca.Data, []byte("null")) { + var cmd protocol.Command + if err := json.Unmarshal(*ca.Data, &cmd); err != nil { + return nil, err + } + + params := &protocol.ExecuteCommandParams{ + Command: cmd.Command, + Arguments: cmd.Arguments, + } + + handler := &commandHandler{ + s: s, + params: params, + } + edit, err := command.Dispatch(ctx, params, handler) + if err != nil { + + return nil, err + } + var ok bool + if ca.Edit, ok = edit.(*protocol.WorkspaceEdit); !ok { + return nil, fmt.Errorf("unable to resolve code action %q", ca.Title) + } + } + return ca, nil +} + +func supportsResolveEdits(options *settings.Options) bool { + return options.CodeActionResolveOptions != nil && slices.Contains(options.CodeActionResolveOptions, "edit") +} + func (s *server) findMatchingDiagnostics(uri protocol.DocumentURI, pd protocol.Diagnostic) []*cache.Diagnostic { s.diagnosticsMu.Lock() defer s.diagnosticsMu.Unlock() @@ -367,7 +414,7 @@ func fixedByImportFix(fix *imports.ImportFix, diagnostics []protocol.Diagnostic) return results } -func refactorExtract(pgf *source.ParsedGoFile, rng protocol.Range) ([]protocol.CodeAction, error) { +func refactorExtract(pgf *source.ParsedGoFile, rng protocol.Range, options *settings.Options) ([]protocol.CodeAction, error) { if rng.Start == rng.End { return nil, nil } @@ -380,9 +427,10 @@ func refactorExtract(pgf *source.ParsedGoFile, rng protocol.Range) ([]protocol.C var commands []protocol.Command if _, ok, methodOk, _ := source.CanExtractFunction(pgf.Tok, start, end, pgf.Src, pgf.File); ok { cmd, err := command.NewApplyFixCommand("Extract function", command.ApplyFixArgs{ - URI: puri, - Fix: string(settings.ExtractFunction), - Range: rng, + URI: puri, + Fix: string(settings.ExtractFunction), + Range: rng, + ResolveEdits: supportsResolveEdits(options), }) if err != nil { return nil, err @@ -390,9 +438,10 @@ func refactorExtract(pgf *source.ParsedGoFile, rng protocol.Range) ([]protocol.C commands = append(commands, cmd) if methodOk { cmd, err := command.NewApplyFixCommand("Extract method", command.ApplyFixArgs{ - URI: puri, - Fix: string(settings.ExtractMethod), - Range: rng, + URI: puri, + Fix: string(settings.ExtractMethod), + Range: rng, + ResolveEdits: supportsResolveEdits(options), }) if err != nil { return nil, err @@ -402,9 +451,10 @@ func refactorExtract(pgf *source.ParsedGoFile, rng protocol.Range) ([]protocol.C } if _, _, ok, _ := source.CanExtractVariable(start, end, pgf.File); ok { cmd, err := command.NewApplyFixCommand("Extract variable", command.ApplyFixArgs{ - URI: puri, - Fix: string(settings.ExtractVariable), - Range: rng, + URI: puri, + Fix: string(settings.ExtractVariable), + Range: rng, + ResolveEdits: supportsResolveEdits(options), }) if err != nil { return nil, err @@ -413,16 +463,31 @@ func refactorExtract(pgf *source.ParsedGoFile, rng protocol.Range) ([]protocol.C } var actions []protocol.CodeAction for i := range commands { - actions = append(actions, protocol.CodeAction{ - Title: commands[i].Title, - Kind: protocol.RefactorExtract, - Command: &commands[i], - }) + actions = append(actions, newCodeAction(commands[i].Title, protocol.RefactorExtract, &commands[i], nil, options)) } return actions, nil } -func refactorRewrite(snapshot *cache.Snapshot, pkg *cache.Package, pgf *source.ParsedGoFile, fh file.Handle, rng protocol.Range) (_ []protocol.CodeAction, rerr error) { +func newCodeAction(title string, kind protocol.CodeActionKind, cmd *protocol.Command, diagnostics []protocol.Diagnostic, options *settings.Options) protocol.CodeAction { + action := protocol.CodeAction{ + Title: title, + Kind: kind, + Diagnostics: diagnostics, + } + if !supportsResolveEdits(options) { + action.Command = cmd + } else { + data, err := json.Marshal(cmd) + if err != nil { + panic("unable to marshal") + } + msg := json.RawMessage(data) + action.Data = &msg + } + return action +} + +func refactorRewrite(snapshot *cache.Snapshot, pkg *cache.Package, pgf *source.ParsedGoFile, fh file.Handle, rng protocol.Range, options *settings.Options) (_ []protocol.CodeAction, rerr error) { // golang/go#61693: code actions were refactored to run outside of the // analysis framework, but as a result they lost their panic recovery. // @@ -442,15 +507,12 @@ func refactorRewrite(snapshot *cache.Snapshot, pkg *cache.Package, pgf *source.P URI: pgf.URI, Range: rng, }, + ResolveEdits: supportsResolveEdits(options), }) if err != nil { return nil, err } - actions = append(actions, protocol.CodeAction{ - Title: "Refactor: remove unused parameter", - Kind: protocol.RefactorRewrite, - Command: &cmd, - }) + actions = append(actions, newCodeAction("Refactor: remove unused parameter", protocol.RefactorRewrite, &cmd, nil, options)) } if action, ok := source.ConvertStringLiteral(pgf, fh, rng); ok { @@ -465,9 +527,10 @@ func refactorRewrite(snapshot *cache.Snapshot, pkg *cache.Package, pgf *source.P var commands []protocol.Command if _, ok, _ := source.CanInvertIfCondition(pgf.File, start, end); ok { cmd, err := command.NewApplyFixCommand("Invert if condition", command.ApplyFixArgs{ - URI: pgf.URI, - Fix: string(settings.InvertIfCondition), - Range: rng, + URI: pgf.URI, + Fix: string(settings.InvertIfCondition), + Range: rng, + ResolveEdits: supportsResolveEdits(options), }) if err != nil { return nil, err @@ -487,9 +550,10 @@ func refactorRewrite(snapshot *cache.Snapshot, pkg *cache.Package, pgf *source.P return nil, err } cmd, err := command.NewApplyFixCommand(d.Message, command.ApplyFixArgs{ - URI: pgf.URI, - Fix: string(settings.FillStruct), - Range: rng, + URI: pgf.URI, + Fix: string(settings.FillStruct), + Range: rng, + ResolveEdits: supportsResolveEdits(options), }) if err != nil { return nil, err @@ -499,11 +563,7 @@ func refactorRewrite(snapshot *cache.Snapshot, pkg *cache.Package, pgf *source.P } for i := range commands { - actions = append(actions, protocol.CodeAction{ - Title: commands[i].Title, - Kind: protocol.RefactorRewrite, - Command: &commands[i], - }) + actions = append(actions, newCodeAction(commands[i].Title, protocol.RefactorRewrite, &commands[i], nil, options)) } if snapshot.Options().IsAnalyzerEnabled(infertypeargs.Analyzer.Name) { diff --git a/gopls/internal/server/command.go b/gopls/internal/server/command.go index 85149ca6fcd..5538ec8282c 100644 --- a/gopls/internal/server/command.go +++ b/gopls/internal/server/command.go @@ -199,8 +199,9 @@ func (c *commandHandler) run(ctx context.Context, cfg commandConfig, run command return runcmd() } -func (c *commandHandler) ApplyFix(ctx context.Context, args command.ApplyFixArgs) error { - return c.run(ctx, commandConfig{ +func (c *commandHandler) ApplyFix(ctx context.Context, args command.ApplyFixArgs) (*protocol.WorkspaceEdit, error) { + var result *protocol.WorkspaceEdit + err := c.run(ctx, commandConfig{ // Note: no progress here. Applying fixes should be quick. forURI: args.URI, }, func(ctx context.Context, deps commandDeps) error { @@ -215,10 +216,15 @@ func (c *commandHandler) ApplyFix(ctx context.Context, args command.ApplyFixArgs TextDocumentEdit: &edit, }) } + edit := protocol.WorkspaceEdit{ + DocumentChanges: changes, + } + if args.ResolveEdits { + result = &edit + return nil + } r, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{ - Edit: protocol.WorkspaceEdit{ - DocumentChanges: changes, - }, + Edit: edit, }) if err != nil { return err @@ -228,6 +234,7 @@ func (c *commandHandler) ApplyFix(ctx context.Context, args command.ApplyFixArgs } return nil }) + return result, err } func (c *commandHandler) RegenerateCgo(ctx context.Context, args command.URIArg) error { @@ -1266,8 +1273,9 @@ func showDocumentImpl(ctx context.Context, cli protocol.Client, url protocol.URI } } -func (c *commandHandler) ChangeSignature(ctx context.Context, args command.ChangeSignatureArgs) error { - return c.run(ctx, commandConfig{ +func (c *commandHandler) ChangeSignature(ctx context.Context, args command.ChangeSignatureArgs) (*protocol.WorkspaceEdit, error) { + var result *protocol.WorkspaceEdit + err := c.run(ctx, commandConfig{ forURI: args.RemoveParameter.URI, }, func(ctx context.Context, deps commandDeps) error { // For now, gopls only supports removing unused parameters. @@ -1275,10 +1283,15 @@ func (c *commandHandler) ChangeSignature(ctx context.Context, args command.Chang if err != nil { return err } + edit := protocol.WorkspaceEdit{ + DocumentChanges: changes, + } + if args.ResolveEdits { + result = &edit + return nil + } r, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{ - Edit: protocol.WorkspaceEdit{ - DocumentChanges: changes, - }, + Edit: edit, }) if !r.Applied { return fmt.Errorf("failed to apply edits: %v", r.FailureReason) @@ -1286,6 +1299,7 @@ func (c *commandHandler) ChangeSignature(ctx context.Context, args command.Chang return nil }) + return result, err } func (c *commandHandler) DiagnoseFiles(ctx context.Context, args command.DiagnoseFilesArgs) error { diff --git a/gopls/internal/server/general.go b/gopls/internal/server/general.go index 141fed947ae..371e2fa591d 100644 --- a/gopls/internal/server/general.go +++ b/gopls/internal/server/general.go @@ -114,6 +114,7 @@ func (s *server) Initialize(ctx context.Context, params *protocol.ParamInitializ // Using CodeActionOptions is only valid if codeActionLiteralSupport is set. codeActionProvider = &protocol.CodeActionOptions{ CodeActionKinds: s.getSupportedCodeActions(), + ResolveProvider: true, } } var renameOpts interface{} = true diff --git a/gopls/internal/server/unimplemented.go b/gopls/internal/server/unimplemented.go index 6f897adf564..d8e16a52d5f 100644 --- a/gopls/internal/server/unimplemented.go +++ b/gopls/internal/server/unimplemented.go @@ -102,10 +102,6 @@ func (s *server) Resolve(context.Context, *protocol.InlayHint) (*protocol.InlayH return nil, notImplemented("Resolve") } -func (s *server) ResolveCodeAction(context.Context, *protocol.CodeAction) (*protocol.CodeAction, error) { - return nil, notImplemented("ResolveCodeAction") -} - func (s *server) ResolveCodeLens(context.Context, *protocol.CodeLens) (*protocol.CodeLens, error) { return nil, notImplemented("ResolveCodeLens") } diff --git a/gopls/internal/settings/api_json.go b/gopls/internal/settings/api_json.go index 7f6760947d4..808df6d6a15 100644 --- a/gopls/internal/settings/api_json.go +++ b/gopls/internal/settings/api_json.go @@ -739,16 +739,18 @@ var GeneratedAPIJSON = &APIJSON{ ArgDoc: "{\n\t// Names and Values must have the same length.\n\t\"Names\": []string,\n\t\"Values\": []int64,\n}", }, { - Command: "gopls.apply_fix", - Title: "Apply a fix", - Doc: "Applies a fix to a region of source code.", - ArgDoc: "{\n\t// The fix to apply.\n\t\"Fix\": string,\n\t// The file URI for the document to fix.\n\t\"URI\": string,\n\t// The document range to scan for fixes.\n\t\"Range\": {\n\t\t\"start\": {\n\t\t\t\"line\": uint32,\n\t\t\t\"character\": uint32,\n\t\t},\n\t\t\"end\": {\n\t\t\t\"line\": uint32,\n\t\t\t\"character\": uint32,\n\t\t},\n\t},\n}", - }, - { - Command: "gopls.change_signature", - Title: "Perform a \"change signature\" refactoring", - Doc: "This command is experimental, currently only supporting parameter removal.\nIts signature will certainly change in the future (pun intended).", - ArgDoc: "{\n\t\"RemoveParameter\": {\n\t\t\"uri\": string,\n\t\t\"range\": {\n\t\t\t\"start\": { ... },\n\t\t\t\"end\": { ... },\n\t\t},\n\t},\n}", + Command: "gopls.apply_fix", + Title: "Apply a fix", + Doc: "Applies a fix to a region of source code.", + ArgDoc: "{\n\t// The fix to apply.\n\t\"Fix\": string,\n\t// The file URI for the document to fix.\n\t\"URI\": string,\n\t// The document range to scan for fixes.\n\t\"Range\": {\n\t\t\"start\": {\n\t\t\t\"line\": uint32,\n\t\t\t\"character\": uint32,\n\t\t},\n\t\t\"end\": {\n\t\t\t\"line\": uint32,\n\t\t\t\"character\": uint32,\n\t\t},\n\t},\n\t// Whether to resolve and return the edits.\n\t\"ResolveEdits\": bool,\n}", + ResultDoc: "{\n\t// Holds changes to existing resources.\n\t\"changes\": map[golang.org/x/tools/gopls/internal/lsp/protocol.DocumentURI][]golang.org/x/tools/gopls/internal/lsp/protocol.TextEdit,\n\t// Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes\n\t// are either an array of `TextDocumentEdit`s to express changes to n different text documents\n\t// where each text document edit addresses a specific version of a text document. Or it can contain\n\t// above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations.\n\t//\n\t// Whether a client supports versioned document edits is expressed via\n\t// `workspace.workspaceEdit.documentChanges` client capability.\n\t//\n\t// If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then\n\t// only plain `TextEdit`s using the `changes` property are supported.\n\t\"documentChanges\": []{\n\t\t\"TextDocumentEdit\": {\n\t\t\t\"textDocument\": { ... },\n\t\t\t\"edits\": { ... },\n\t\t},\n\t\t\"RenameFile\": {\n\t\t\t\"kind\": string,\n\t\t\t\"oldUri\": string,\n\t\t\t\"newUri\": string,\n\t\t\t\"options\": { ... },\n\t\t\t\"ResourceOperation\": { ... },\n\t\t},\n\t},\n\t// A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and\n\t// delete file / folder operations.\n\t//\n\t// Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`.\n\t//\n\t// @since 3.16.0\n\t\"changeAnnotations\": map[string]golang.org/x/tools/gopls/internal/lsp/protocol.ChangeAnnotation,\n}", + }, + { + Command: "gopls.change_signature", + Title: "Perform a \"change signature\" refactoring", + Doc: "This command is experimental, currently only supporting parameter removal.\nIts signature will certainly change in the future (pun intended).", + ArgDoc: "{\n\t\"RemoveParameter\": {\n\t\t\"uri\": string,\n\t\t\"range\": {\n\t\t\t\"start\": { ... },\n\t\t\t\"end\": { ... },\n\t\t},\n\t},\n\t// Whether to resolve and return the edits.\n\t\"ResolveEdits\": bool,\n}", + ResultDoc: "{\n\t// Holds changes to existing resources.\n\t\"changes\": map[golang.org/x/tools/gopls/internal/lsp/protocol.DocumentURI][]golang.org/x/tools/gopls/internal/lsp/protocol.TextEdit,\n\t// Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes\n\t// are either an array of `TextDocumentEdit`s to express changes to n different text documents\n\t// where each text document edit addresses a specific version of a text document. Or it can contain\n\t// above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations.\n\t//\n\t// Whether a client supports versioned document edits is expressed via\n\t// `workspace.workspaceEdit.documentChanges` client capability.\n\t//\n\t// If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then\n\t// only plain `TextEdit`s using the `changes` property are supported.\n\t\"documentChanges\": []{\n\t\t\"TextDocumentEdit\": {\n\t\t\t\"textDocument\": { ... },\n\t\t\t\"edits\": { ... },\n\t\t},\n\t\t\"RenameFile\": {\n\t\t\t\"kind\": string,\n\t\t\t\"oldUri\": string,\n\t\t\t\"newUri\": string,\n\t\t\t\"options\": { ... },\n\t\t\t\"ResourceOperation\": { ... },\n\t\t},\n\t},\n\t// A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and\n\t// delete file / folder operations.\n\t//\n\t// Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`.\n\t//\n\t// @since 3.16.0\n\t\"changeAnnotations\": map[string]golang.org/x/tools/gopls/internal/lsp/protocol.ChangeAnnotation,\n}", }, { Command: "gopls.check_upgrades", diff --git a/gopls/internal/settings/settings.go b/gopls/internal/settings/settings.go index a5afc6fecf1..2bf4e5051bf 100644 --- a/gopls/internal/settings/settings.go +++ b/gopls/internal/settings/settings.go @@ -131,6 +131,7 @@ type ClientOptions struct { CompletionTags bool CompletionDeprecated bool SupportedResourceOperations []protocol.ResourceOperationKind + CodeActionResolveOptions []string } // ServerOptions holds LSP-specific configuration that is provided by the @@ -748,6 +749,11 @@ func (o *Options) ForClientCapabilities(clientName *protocol.ClientInfo, caps pr } else if caps.TextDocument.Completion.CompletionItem.DeprecatedSupport { o.CompletionDeprecated = true } + + // Check if the client supports code actions resolving. + if caps.TextDocument.CodeAction.DataSupport && caps.TextDocument.CodeAction.ResolveSupport != nil { + o.CodeActionResolveOptions = caps.TextDocument.CodeAction.ResolveSupport.Properties + } } func (o *Options) Clone() *Options { diff --git a/gopls/internal/test/integration/fake/editor.go b/gopls/internal/test/integration/fake/editor.go index 6958564cd06..9c376d0219b 100644 --- a/gopls/internal/test/integration/fake/editor.go +++ b/gopls/internal/test/integration/fake/editor.go @@ -21,6 +21,7 @@ import ( "golang.org/x/tools/gopls/internal/lsp/protocol" "golang.org/x/tools/gopls/internal/test/integration/fake/glob" "golang.org/x/tools/gopls/internal/util/pathutil" + "golang.org/x/tools/gopls/internal/util/slices" "golang.org/x/tools/internal/jsonrpc2" "golang.org/x/tools/internal/jsonrpc2/servertest" "golang.org/x/tools/internal/xcontext" @@ -263,42 +264,11 @@ func (e *Editor) initialize(ctx context.Context) error { params.InitializationOptions = makeSettings(e.sandbox, config) params.WorkspaceFolders = makeWorkspaceFolders(e.sandbox, config.WorkspaceFolders) - // Set various client capabilities that are sought by gopls. - params.Capabilities.Workspace.Configuration = true // support workspace/configuration - params.Capabilities.Window.WorkDoneProgress = true // support window/workDoneProgress - params.Capabilities.TextDocument.Completion.CompletionItem.TagSupport = &protocol.CompletionItemTagOptions{} - params.Capabilities.TextDocument.Completion.CompletionItem.TagSupport.ValueSet = []protocol.CompletionItemTag{protocol.ComplDeprecated} - params.Capabilities.TextDocument.Completion.CompletionItem.SnippetSupport = true - params.Capabilities.TextDocument.SemanticTokens.Requests.Full = &protocol.Or_ClientSemanticTokensRequestOptions_full{Value: true} - params.Capabilities.TextDocument.SemanticTokens.TokenTypes = []string{ - "namespace", "type", "class", "enum", "interface", - "struct", "typeParameter", "parameter", "variable", "property", "enumMember", - "event", "function", "method", "macro", "keyword", "modifier", "comment", - "string", "number", "regexp", "operator", - } - params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = []string{ - "declaration", "definition", "readonly", "static", - "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary", - } - // The LSP tests have historically enabled this flag, - // but really we should test both ways for older editors. - params.Capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = true - // Glob pattern watching is enabled. - params.Capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration = true - // "rename" operations are used for package renaming. - // - // TODO(rfindley): add support for other resource operations (create, delete, ...) - params.Capabilities.Workspace.WorkspaceEdit = &protocol.WorkspaceEditClientCapabilities{ - ResourceOperations: []protocol.ResourceOperationKind{ - "rename", - }, - } - // Apply capabilities overlay. - if config.CapabilitiesJSON != nil { - if err := json.Unmarshal(config.CapabilitiesJSON, ¶ms.Capabilities); err != nil { - return fmt.Errorf("unmarshalling EditorConfig.CapabilitiesJSON: %v", err) - } + capabilities, err := clientCapabilities(config) + if err != nil { + return fmt.Errorf("unmarshalling EditorConfig.CapabilitiesJSON: %v", err) } + params.Capabilities = capabilities trace := protocol.TraceValues("messages") params.Trace = &trace @@ -325,6 +295,48 @@ func (e *Editor) initialize(ctx context.Context) error { return nil } +func clientCapabilities(cfg EditorConfig) (protocol.ClientCapabilities, error) { + var capabilities protocol.ClientCapabilities + // Set various client capabilities that are sought by gopls. + capabilities.Workspace.Configuration = true // support workspace/configuration + capabilities.TextDocument.Completion.CompletionItem.TagSupport = &protocol.CompletionItemTagOptions{} + capabilities.TextDocument.Completion.CompletionItem.TagSupport.ValueSet = []protocol.CompletionItemTag{protocol.ComplDeprecated} + capabilities.TextDocument.Completion.CompletionItem.SnippetSupport = true + capabilities.TextDocument.SemanticTokens.Requests.Full = &protocol.Or_ClientSemanticTokensRequestOptions_full{Value: true} + capabilities.Window.WorkDoneProgress = true // support window/workDoneProgress + capabilities.TextDocument.SemanticTokens.TokenTypes = []string{ + "namespace", "type", "class", "enum", "interface", + "struct", "typeParameter", "parameter", "variable", "property", "enumMember", + "event", "function", "method", "macro", "keyword", "modifier", "comment", + "string", "number", "regexp", "operator", + } + capabilities.TextDocument.SemanticTokens.TokenModifiers = []string{ + "declaration", "definition", "readonly", "static", + "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary", + } + // The LSP tests have historically enabled this flag, + // but really we should test both ways for older editors. + capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = true + // Glob pattern watching is enabled. + capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration = true + // "rename" operations are used for package renaming. + // + // TODO(rfindley): add support for other resource operations (create, delete, ...) + capabilities.Workspace.WorkspaceEdit = &protocol.WorkspaceEditClientCapabilities{ + ResourceOperations: []protocol.ResourceOperationKind{ + "rename", + }, + } + + // Apply capabilities overlay. + if cfg.CapabilitiesJSON != nil { + if err := json.Unmarshal(cfg.CapabilitiesJSON, &capabilities); err != nil { + return protocol.ClientCapabilities{}, fmt.Errorf("unmarshalling EditorConfig.CapabilitiesJSON: %v", err) + } + } + return capabilities, nil +} + // marshalUnmarshal is a helper to json Marshal and then Unmarshal as a // different type. Used to work around cases where our protocol types are not // specific. @@ -902,6 +914,21 @@ func (e *Editor) ApplyQuickFixes(ctx context.Context, loc protocol.Location, dia // ApplyCodeAction applies the given code action. func (e *Editor) ApplyCodeAction(ctx context.Context, action protocol.CodeAction) error { + // Resolve the code actions if necessary and supported. + if action.Edit == nil { + editSupport, err := e.EditResolveSupport() + if err != nil { + return err + } + if editSupport { + ca, err := e.Server.ResolveCodeAction(ctx, &action) + if err != nil { + return err + } + action.Edit = ca.Edit + } + } + if action.Edit != nil { for _, change := range action.Edit.DocumentChanges { if change.TextDocumentEdit != nil { @@ -932,11 +959,11 @@ func (e *Editor) ApplyCodeAction(ctx context.Context, action protocol.CodeAction // GetQuickFixes returns the available quick fix code actions. func (e *Editor) GetQuickFixes(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) { - return e.getCodeActions(ctx, loc, diagnostics, protocol.QuickFix, protocol.SourceFixAll) + return e.CodeActions(ctx, loc, diagnostics, protocol.QuickFix, protocol.SourceFixAll) } func (e *Editor) applyCodeActions(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) (int, error) { - actions, err := e.getCodeActions(ctx, loc, diagnostics, only...) + actions, err := e.CodeActions(ctx, loc, diagnostics, only...) if err != nil { return 0, err } @@ -963,7 +990,7 @@ func (e *Editor) applyCodeActions(ctx context.Context, loc protocol.Location, di return applied, nil } -func (e *Editor) getCodeActions(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) ([]protocol.CodeAction, error) { +func (e *Editor) CodeActions(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) ([]protocol.CodeAction, error) { if e.Server == nil { return nil, nil } @@ -1471,6 +1498,14 @@ func (e *Editor) CodeAction(ctx context.Context, loc protocol.Location, diagnost return lens, nil } +func (e *Editor) EditResolveSupport() (bool, error) { + capabilities, err := clientCapabilities(e.Config()) + if err != nil { + return false, err + } + return capabilities.TextDocument.CodeAction.ResolveSupport != nil && slices.Contains(capabilities.TextDocument.CodeAction.ResolveSupport.Properties, "edit"), nil +} + // Hover triggers a hover at the given position in an open buffer. func (e *Editor) Hover(ctx context.Context, loc protocol.Location) (*protocol.MarkupContent, protocol.Location, error) { if err := e.checkBufferLocation(loc); err != nil { diff --git a/gopls/internal/test/integration/misc/fix_test.go b/gopls/internal/test/integration/misc/fix_test.go index 81b855936e1..1217fe4d2ee 100644 --- a/gopls/internal/test/integration/misc/fix_test.go +++ b/gopls/internal/test/integration/misc/fix_test.go @@ -7,14 +7,24 @@ package misc import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" "golang.org/x/tools/gopls/internal/test/compare" + . "golang.org/x/tools/gopls/internal/test/integration" "golang.org/x/tools/gopls/internal/lsp/protocol" ) -// A basic test for fillstruct, now that it uses a command. +// A basic test for fillstruct, now that it uses a command and supports resolve edits. func TestFillStruct(t *testing.T) { + tc := []struct { + name string + capabilities string + wantCommand bool + }{ + {"default", "{}", true}, + {"no data", `{ "textDocument": {"codeAction": { "resolveSupport": { "properties": ["edit"] } } } }`, true}, + {"resolve support", `{ "textDocument": {"codeAction": { "dataSupport": true, "resolveSupport": { "properties": ["edit"] } } } }`, false}, + } + const basic = ` -- go.mod -- module mod.com @@ -32,12 +42,35 @@ func Foo() { _ = Info{} } ` - Run(t, basic, func(t *testing.T, env *Env) { - env.OpenFile("main.go") - if err := env.Editor.RefactorRewrite(env.Ctx, env.RegexpSearch("main.go", "Info{}")); err != nil { - t.Fatal(err) - } - want := `package main + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + runner := WithOptions(CapabilitiesJSON([]byte(tt.capabilities))) + + runner.Run(t, basic, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + fixes, err := env.Editor.CodeActions(env.Ctx, env.RegexpSearch("main.go", "Info{}"), nil, protocol.RefactorRewrite) + if err != nil { + t.Fatal(err) + } + if len(fixes) != 1 { + t.Fatalf("expected 1 code action, got %v", len(fixes)) + } + if tt.wantCommand { + if fixes[0].Command == nil || fixes[0].Data != nil { + t.Errorf("expected code action to have command not data, got %v", fixes[0]) + } + } else { + if fixes[0].Command != nil || fixes[0].Data == nil { + t.Errorf("expected code action to have command not data, got %v", fixes[0]) + } + } + + // Apply the code action (handles resolving the code action), and check that the result is correct. + if err := env.Editor.RefactorRewrite(env.Ctx, env.RegexpSearch("main.go", "Info{}")); err != nil { + t.Fatal(err) + } + want := `package main type Info struct { WordCounts map[string]int @@ -51,10 +84,12 @@ func Foo() { } } ` - if got := env.BufferText("main.go"); got != want { - t.Fatalf("TestFillStruct failed:\n%s", compare.Text(want, got)) - } - }) + if got := env.BufferText("main.go"); got != want { + t.Fatalf("TestFillStruct failed:\n%s", compare.Text(want, got)) + } + }) + }) + } } func TestFillReturns(t *testing.T) { diff --git a/gopls/internal/test/integration/options.go b/gopls/internal/test/integration/options.go index ded09b47c18..c558da2dfd9 100644 --- a/gopls/internal/test/integration/options.go +++ b/gopls/internal/test/integration/options.go @@ -73,6 +73,13 @@ func ClientName(name string) RunOption { }) } +// CapabilitiesJSON sets the capabalities json. +func CapabilitiesJSON(capabilities []byte) RunOption { + return optionSetter(func(opts *runConfig) { + opts.editor.CapabilitiesJSON = capabilities + }) +} + // Settings sets user-provided configuration for the LSP server. // // As a special case, the env setting must not be provided via Settings: use diff --git a/gopls/internal/test/marker/marker_test.go b/gopls/internal/test/marker/marker_test.go index 05a93e6e5dd..3440fa098a0 100644 --- a/gopls/internal/test/marker/marker_test.go +++ b/gopls/internal/test/marker/marker_test.go @@ -1919,6 +1919,23 @@ func codeActionChanges(env *integration.Env, uri protocol.DocumentURI, rng proto // applied in that order. But since applyDocumentChanges(env, // action.Edit.DocumentChanges) doesn't compose, for now we // assert that actions return one or the other. + + // Resolve code action edits first if the client has resolve support + // and the code action has no edits. + if action.Edit == nil { + editSupport, err := env.Editor.EditResolveSupport() + if err != nil { + return nil, err + } + if editSupport { + resolved, err := env.Editor.Server.ResolveCodeAction(env.Ctx, &action) + if err != nil { + return nil, err + } + action.Edit = resolved.Edit + } + } + if action.Edit != nil { if action.Edit.Changes != nil { env.T.Errorf("internal error: discarding unexpected CodeAction{Kind=%s, Title=%q}.Edit.Changes", action.Kind, action.Title) diff --git a/gopls/internal/test/marker/testdata/codeaction/extract_variable.txt b/gopls/internal/test/marker/testdata/codeaction/extract_variable.txt index c10b8185c9b..685b4ff9372 100644 --- a/gopls/internal/test/marker/testdata/codeaction/extract_variable.txt +++ b/gopls/internal/test/marker/testdata/codeaction/extract_variable.txt @@ -1,4 +1,5 @@ This test checks the behavior of the 'extract variable' code action. +See extract_variable_resolve.txt for the same test with resolve support. -- flags -- -ignore_extra_diags diff --git a/gopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt b/gopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt new file mode 100644 index 00000000000..dc6ad787afb --- /dev/null +++ b/gopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt @@ -0,0 +1,81 @@ +This test checks the behavior of the 'extract variable' code action, with resolve support. +See extract_variable.txt for the same test without resolve support. + +-- capabilities.json -- +{ + "textDocument": { + "codeAction": { + "dataSupport": true, + "resolveSupport": { + "properties": ["edit"] + } + } + } +} +-- flags -- +-ignore_extra_diags + +-- basic_lit.go -- +package extract + +func _() { + var _ = 1 + 2 //@codeactionedit("1", "refactor.extract", basic_lit1) + var _ = 3 + 4 //@codeactionedit("3 + 4", "refactor.extract", basic_lit2) +} + +-- @basic_lit1/basic_lit.go -- +@@ -4 +4,2 @@ +- var _ = 1 + 2 //@codeactionedit("1", "refactor.extract", basic_lit1) ++ x := 1 ++ var _ = x + 2 //@codeactionedit("1", "refactor.extract", basic_lit1) +-- @basic_lit2/basic_lit.go -- +@@ -5 +5,2 @@ +- var _ = 3 + 4 //@codeactionedit("3 + 4", "refactor.extract", basic_lit2) ++ x := 3 + 4 ++ var _ = x //@codeactionedit("3 + 4", "refactor.extract", basic_lit2) +-- func_call.go -- +package extract + +import "strconv" + +func _() { + x0 := append([]int{}, 1) //@codeactionedit("append([]int{}, 1)", "refactor.extract", func_call1) + str := "1" + b, err := strconv.Atoi(str) //@codeactionedit("strconv.Atoi(str)", "refactor.extract", func_call2) +} + +-- @func_call1/func_call.go -- +@@ -6 +6,2 @@ +- x0 := append([]int{}, 1) //@codeactionedit("append([]int{}, 1)", "refactor.extract", func_call1) ++ x := append([]int{}, 1) ++ x0 := x //@codeactionedit("append([]int{}, 1)", "refactor.extract", func_call1) +-- @func_call2/func_call.go -- +@@ -8 +8,2 @@ +- b, err := strconv.Atoi(str) //@codeactionedit("strconv.Atoi(str)", "refactor.extract", func_call2) ++ x, x1 := strconv.Atoi(str) ++ b, err := x, x1 //@codeactionedit("strconv.Atoi(str)", "refactor.extract", func_call2) +-- scope.go -- +package extract + +import "go/ast" + +func _() { + x0 := 0 + if true { + y := ast.CompositeLit{} //@codeactionedit("ast.CompositeLit{}", "refactor.extract", scope1) + } + if true { + x1 := !false //@codeactionedit("!false", "refactor.extract", scope2) + } +} + +-- @scope1/scope.go -- +@@ -8 +8,2 @@ +- y := ast.CompositeLit{} //@codeactionedit("ast.CompositeLit{}", "refactor.extract", scope1) ++ x := ast.CompositeLit{} ++ y := x //@codeactionedit("ast.CompositeLit{}", "refactor.extract", scope1) +-- @scope2/scope.go -- +@@ -11 +11,2 @@ +- x1 := !false //@codeactionedit("!false", "refactor.extract", scope2) ++ x := !false ++ x1 := x //@codeactionedit("!false", "refactor.extract", scope2) diff --git a/gopls/internal/test/marker/testdata/codeaction/fill_struct.txt b/gopls/internal/test/marker/testdata/codeaction/fill_struct.txt index 092a4275ffb..deac1d78507 100644 --- a/gopls/internal/test/marker/testdata/codeaction/fill_struct.txt +++ b/gopls/internal/test/marker/testdata/codeaction/fill_struct.txt @@ -1,4 +1,5 @@ This test checks the behavior of the 'fill struct' code action. +See fill_struct_resolve.txt for same test with resolve support. -- flags -- -ignore_extra_diags @@ -89,11 +90,11 @@ type funStruct struct { var _ = funStruct{} //@codeactionedit("}", "refactor.rewrite", a22) -type funStructCompex struct { +type funStructComplex struct { fn func(i int, s string) (string, int) } -var _ = funStructCompex{} //@codeactionedit("}", "refactor.rewrite", a23) +var _ = funStructComplex{} //@codeactionedit("}", "refactor.rewrite", a23) type funStructEmpty struct { fn func() @@ -120,8 +121,8 @@ var _ = funStructEmpty{} //@codeactionedit("}", "refactor.rewrite", a24) +} //@codeactionedit("}", "refactor.rewrite", a22) -- @a23/a2.go -- @@ -23 +23,4 @@ --var _ = funStructCompex{} //@codeactionedit("}", "refactor.rewrite", a23) -+var _ = funStructCompex{ +-var _ = funStructComplex{} //@codeactionedit("}", "refactor.rewrite", a23) ++var _ = funStructComplex{ + fn: func(i int, s string) (string, int) { + }, +} //@codeactionedit("}", "refactor.rewrite", a23) diff --git a/gopls/internal/test/marker/testdata/codeaction/fill_struct_resolve.txt b/gopls/internal/test/marker/testdata/codeaction/fill_struct_resolve.txt new file mode 100644 index 00000000000..e553d1c5993 --- /dev/null +++ b/gopls/internal/test/marker/testdata/codeaction/fill_struct_resolve.txt @@ -0,0 +1,586 @@ +This test checks the behavior of the 'fill struct' code action, with resolve support. +See fill_struct.txt for same test without resolve support. + +-- capabilities.json -- +{ + "textDocument": { + "codeAction": { + "dataSupport": true, + "resolveSupport": { + "properties": ["edit"] + } + } + } +} +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/fillstruct + +go 1.18 + +-- data/data.go -- +package data + +type B struct { + ExportedInt int + unexportedInt int +} + +-- a.go -- +package fillstruct + +import ( + "golang.org/lsptests/fillstruct/data" +) + +type basicStruct struct { + foo int +} + +var _ = basicStruct{} //@codeactionedit("}", "refactor.rewrite", a1) + +type twoArgStruct struct { + foo int + bar string +} + +var _ = twoArgStruct{} //@codeactionedit("}", "refactor.rewrite", a2) + +type nestedStruct struct { + bar string + basic basicStruct +} + +var _ = nestedStruct{} //@codeactionedit("}", "refactor.rewrite", a3) + +var _ = data.B{} //@codeactionedit("}", "refactor.rewrite", a4) +-- @a1/a.go -- +@@ -11 +11,3 @@ +-var _ = basicStruct{} //@codeactionedit("}", "refactor.rewrite", a1) ++var _ = basicStruct{ ++ foo: 0, ++} //@codeactionedit("}", "refactor.rewrite", a1) +-- @a2/a.go -- +@@ -18 +18,4 @@ +-var _ = twoArgStruct{} //@codeactionedit("}", "refactor.rewrite", a2) ++var _ = twoArgStruct{ ++ foo: 0, ++ bar: "", ++} //@codeactionedit("}", "refactor.rewrite", a2) +-- @a3/a.go -- +@@ -25 +25,4 @@ +-var _ = nestedStruct{} //@codeactionedit("}", "refactor.rewrite", a3) ++var _ = nestedStruct{ ++ bar: "", ++ basic: basicStruct{}, ++} //@codeactionedit("}", "refactor.rewrite", a3) +-- @a4/a.go -- +@@ -27 +27,3 @@ +-var _ = data.B{} //@codeactionedit("}", "refactor.rewrite", a4) ++var _ = data.B{ ++ ExportedInt: 0, ++} //@codeactionedit("}", "refactor.rewrite", a4) +-- a2.go -- +package fillstruct + +type typedStruct struct { + m map[string]int + s []int + c chan int + c1 <-chan int + a [2]string +} + +var _ = typedStruct{} //@codeactionedit("}", "refactor.rewrite", a21) + +type funStruct struct { + fn func(i int) int +} + +var _ = funStruct{} //@codeactionedit("}", "refactor.rewrite", a22) + +type funStructComplex struct { + fn func(i int, s string) (string, int) +} + +var _ = funStructComplex{} //@codeactionedit("}", "refactor.rewrite", a23) + +type funStructEmpty struct { + fn func() +} + +var _ = funStructEmpty{} //@codeactionedit("}", "refactor.rewrite", a24) + +-- @a21/a2.go -- +@@ -11 +11,7 @@ +-var _ = typedStruct{} //@codeactionedit("}", "refactor.rewrite", a21) ++var _ = typedStruct{ ++ m: map[string]int{}, ++ s: []int{}, ++ c: make(chan int), ++ c1: make(<-chan int), ++ a: [2]string{}, ++} //@codeactionedit("}", "refactor.rewrite", a21) +-- @a22/a2.go -- +@@ -17 +17,4 @@ +-var _ = funStruct{} //@codeactionedit("}", "refactor.rewrite", a22) ++var _ = funStruct{ ++ fn: func(i int) int { ++ }, ++} //@codeactionedit("}", "refactor.rewrite", a22) +-- @a23/a2.go -- +@@ -23 +23,4 @@ +-var _ = funStructComplex{} //@codeactionedit("}", "refactor.rewrite", a23) ++var _ = funStructComplex{ ++ fn: func(i int, s string) (string, int) { ++ }, ++} //@codeactionedit("}", "refactor.rewrite", a23) +-- @a24/a2.go -- +@@ -29 +29,4 @@ +-var _ = funStructEmpty{} //@codeactionedit("}", "refactor.rewrite", a24) ++var _ = funStructEmpty{ ++ fn: func() { ++ }, ++} //@codeactionedit("}", "refactor.rewrite", a24) +-- a3.go -- +package fillstruct + +import ( + "go/ast" + "go/token" +) + +type Foo struct { + A int +} + +type Bar struct { + X *Foo + Y *Foo +} + +var _ = Bar{} //@codeactionedit("}", "refactor.rewrite", a31) + +type importedStruct struct { + m map[*ast.CompositeLit]ast.Field + s []ast.BadExpr + a [3]token.Token + c chan ast.EmptyStmt + fn func(ast_decl ast.DeclStmt) ast.Ellipsis + st ast.CompositeLit +} + +var _ = importedStruct{} //@codeactionedit("}", "refactor.rewrite", a32) + +type pointerBuiltinStruct struct { + b *bool + s *string + i *int +} + +var _ = pointerBuiltinStruct{} //@codeactionedit("}", "refactor.rewrite", a33) + +var _ = []ast.BasicLit{ + {}, //@codeactionedit("}", "refactor.rewrite", a34) +} + +var _ = []ast.BasicLit{{}} //@codeactionedit("}", "refactor.rewrite", a35) +-- @a31/a3.go -- +@@ -17 +17,4 @@ +-var _ = Bar{} //@codeactionedit("}", "refactor.rewrite", a31) ++var _ = Bar{ ++ X: &Foo{}, ++ Y: &Foo{}, ++} //@codeactionedit("}", "refactor.rewrite", a31) +-- @a32/a3.go -- +@@ -28 +28,9 @@ +-var _ = importedStruct{} //@codeactionedit("}", "refactor.rewrite", a32) ++var _ = importedStruct{ ++ m: map[*ast.CompositeLit]ast.Field{}, ++ s: []ast.BadExpr{}, ++ a: [3]token.Token{}, ++ c: make(chan ast.EmptyStmt), ++ fn: func(ast_decl ast.DeclStmt) ast.Ellipsis { ++ }, ++ st: ast.CompositeLit{}, ++} //@codeactionedit("}", "refactor.rewrite", a32) +-- @a33/a3.go -- +@@ -36 +36,5 @@ +-var _ = pointerBuiltinStruct{} //@codeactionedit("}", "refactor.rewrite", a33) ++var _ = pointerBuiltinStruct{ ++ b: new(bool), ++ s: new(string), ++ i: new(int), ++} //@codeactionedit("}", "refactor.rewrite", a33) +-- @a34/a3.go -- +@@ -39 +39,5 @@ +- {}, //@codeactionedit("}", "refactor.rewrite", a34) ++ { ++ ValuePos: 0, ++ Kind: 0, ++ Value: "", ++ }, //@codeactionedit("}", "refactor.rewrite", a34) +-- @a35/a3.go -- +@@ -42 +42,5 @@ +-var _ = []ast.BasicLit{{}} //@codeactionedit("}", "refactor.rewrite", a35) ++var _ = []ast.BasicLit{{ ++ ValuePos: 0, ++ Kind: 0, ++ Value: "", ++}} //@codeactionedit("}", "refactor.rewrite", a35) +-- a4.go -- +package fillstruct + +import "go/ast" + +type iStruct struct { + X int +} + +type sStruct struct { + str string +} + +type multiFill struct { + num int + strin string + arr []int +} + +type assignStruct struct { + n ast.Node +} + +func fill() { + var x int + var _ = iStruct{} //@codeactionedit("}", "refactor.rewrite", a41) + + var s string + var _ = sStruct{} //@codeactionedit("}", "refactor.rewrite", a42) + + var n int + _ = []int{} + if true { + arr := []int{1, 2} + } + var _ = multiFill{} //@codeactionedit("}", "refactor.rewrite", a43) + + var node *ast.CompositeLit + var _ = assignStruct{} //@codeactionedit("}", "refactor.rewrite", a45) +} + +-- @a41/a4.go -- +@@ -25 +25,3 @@ +- var _ = iStruct{} //@codeactionedit("}", "refactor.rewrite", a41) ++ var _ = iStruct{ ++ X: x, ++ } //@codeactionedit("}", "refactor.rewrite", a41) +-- @a42/a4.go -- +@@ -28 +28,3 @@ +- var _ = sStruct{} //@codeactionedit("}", "refactor.rewrite", a42) ++ var _ = sStruct{ ++ str: s, ++ } //@codeactionedit("}", "refactor.rewrite", a42) +-- @a43/a4.go -- +@@ -35 +35,5 @@ +- var _ = multiFill{} //@codeactionedit("}", "refactor.rewrite", a43) ++ var _ = multiFill{ ++ num: n, ++ strin: s, ++ arr: []int{}, ++ } //@codeactionedit("}", "refactor.rewrite", a43) +-- @a45/a4.go -- +@@ -38 +38,3 @@ +- var _ = assignStruct{} //@codeactionedit("}", "refactor.rewrite", a45) ++ var _ = assignStruct{ ++ n: node, ++ } //@codeactionedit("}", "refactor.rewrite", a45) +-- fill_struct.go -- +package fillstruct + +type StructA struct { + unexportedIntField int + ExportedIntField int + MapA map[int]string + Array []int + StructB +} + +type StructA2 struct { + B *StructB +} + +type StructA3 struct { + B StructB +} + +func fill() { + a := StructA{} //@codeactionedit("}", "refactor.rewrite", fill_struct1) + b := StructA2{} //@codeactionedit("}", "refactor.rewrite", fill_struct2) + c := StructA3{} //@codeactionedit("}", "refactor.rewrite", fill_struct3) + if true { + _ = StructA3{} //@codeactionedit("}", "refactor.rewrite", fill_struct4) + } +} + +-- @fill_struct1/fill_struct.go -- +@@ -20 +20,7 @@ +- a := StructA{} //@codeactionedit("}", "refactor.rewrite", fill_struct1) ++ a := StructA{ ++ unexportedIntField: 0, ++ ExportedIntField: 0, ++ MapA: map[int]string{}, ++ Array: []int{}, ++ StructB: StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite", fill_struct1) +-- @fill_struct2/fill_struct.go -- +@@ -21 +21,3 @@ +- b := StructA2{} //@codeactionedit("}", "refactor.rewrite", fill_struct2) ++ b := StructA2{ ++ B: &StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite", fill_struct2) +-- @fill_struct3/fill_struct.go -- +@@ -22 +22,3 @@ +- c := StructA3{} //@codeactionedit("}", "refactor.rewrite", fill_struct3) ++ c := StructA3{ ++ B: StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite", fill_struct3) +-- @fill_struct4/fill_struct.go -- +@@ -24 +24,3 @@ +- _ = StructA3{} //@codeactionedit("}", "refactor.rewrite", fill_struct4) ++ _ = StructA3{ ++ B: StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite", fill_struct4) +-- fill_struct_anon.go -- +package fillstruct + +type StructAnon struct { + a struct{} + b map[string]interface{} + c map[string]struct { + d int + e bool + } +} + +func fill() { + _ := StructAnon{} //@codeactionedit("}", "refactor.rewrite", fill_struct_anon) +} +-- @fill_struct_anon/fill_struct_anon.go -- +@@ -13 +13,5 @@ +- _ := StructAnon{} //@codeactionedit("}", "refactor.rewrite", fill_struct_anon) ++ _ := StructAnon{ ++ a: struct{}{}, ++ b: map[string]interface{}{}, ++ c: map[string]struct{d int; e bool}{}, ++ } //@codeactionedit("}", "refactor.rewrite", fill_struct_anon) +-- fill_struct_nested.go -- +package fillstruct + +type StructB struct { + StructC +} + +type StructC struct { + unexportedInt int +} + +func nested() { + c := StructB{ + StructC: StructC{}, //@codeactionedit("}", "refactor.rewrite", fill_nested) + } +} + +-- @fill_nested/fill_struct_nested.go -- +@@ -13 +13,3 @@ +- StructC: StructC{}, //@codeactionedit("}", "refactor.rewrite", fill_nested) ++ StructC: StructC{ ++ unexportedInt: 0, ++ }, //@codeactionedit("}", "refactor.rewrite", fill_nested) +-- fill_struct_package.go -- +package fillstruct + +import ( + h2 "net/http" + + "golang.org/lsptests/fillstruct/data" +) + +func unexported() { + a := data.B{} //@codeactionedit("}", "refactor.rewrite", fill_struct_package1) + _ = h2.Client{} //@codeactionedit("}", "refactor.rewrite", fill_struct_package2) +} +-- @fill_struct_package1/fill_struct_package.go -- +@@ -10 +10,3 @@ +- a := data.B{} //@codeactionedit("}", "refactor.rewrite", fill_struct_package1) ++ a := data.B{ ++ ExportedInt: 0, ++ } //@codeactionedit("}", "refactor.rewrite", fill_struct_package1) +-- @fill_struct_package2/fill_struct_package.go -- +@@ -11 +11,7 @@ +- _ = h2.Client{} //@codeactionedit("}", "refactor.rewrite", fill_struct_package2) ++ _ = h2.Client{ ++ Transport: nil, ++ CheckRedirect: func(req *h2.Request, via []*h2.Request) error { ++ }, ++ Jar: nil, ++ Timeout: 0, ++ } //@codeactionedit("}", "refactor.rewrite", fill_struct_package2) +-- fill_struct_partial.go -- +package fillstruct + +type StructPartialA struct { + PrefilledInt int + UnfilledInt int + StructPartialB +} + +type StructPartialB struct { + PrefilledInt int + UnfilledInt int +} + +func fill() { + a := StructPartialA{ + PrefilledInt: 5, + } //@codeactionedit("}", "refactor.rewrite", fill_struct_partial1) + b := StructPartialB{ + /* this comment should disappear */ + PrefilledInt: 7, // This comment should be blown away. + /* As should + this one */ + } //@codeactionedit("}", "refactor.rewrite", fill_struct_partial2) +} + +-- @fill_struct_partial1/fill_struct_partial.go -- +@@ -16 +16,3 @@ +- PrefilledInt: 5, ++ PrefilledInt: 5, ++ UnfilledInt: 0, ++ StructPartialB: StructPartialB{}, +-- @fill_struct_partial2/fill_struct_partial.go -- +@@ -19,4 +19,2 @@ +- /* this comment should disappear */ +- PrefilledInt: 7, // This comment should be blown away. +- /* As should +- this one */ ++ PrefilledInt: 7, ++ UnfilledInt: 0, +-- fill_struct_spaces.go -- +package fillstruct + +type StructD struct { + ExportedIntField int +} + +func spaces() { + d := StructD{} //@codeactionedit("}", "refactor.rewrite", fill_struct_spaces) +} + +-- @fill_struct_spaces/fill_struct_spaces.go -- +@@ -8 +8,3 @@ +- d := StructD{} //@codeactionedit("}", "refactor.rewrite", fill_struct_spaces) ++ d := StructD{ ++ ExportedIntField: 0, ++ } //@codeactionedit("}", "refactor.rewrite", fill_struct_spaces) +-- fill_struct_unsafe.go -- +package fillstruct + +import "unsafe" + +type unsafeStruct struct { + x int + p unsafe.Pointer +} + +func fill() { + _ := unsafeStruct{} //@codeactionedit("}", "refactor.rewrite", fill_struct_unsafe) +} + +-- @fill_struct_unsafe/fill_struct_unsafe.go -- +@@ -11 +11,4 @@ +- _ := unsafeStruct{} //@codeactionedit("}", "refactor.rewrite", fill_struct_unsafe) ++ _ := unsafeStruct{ ++ x: 0, ++ p: nil, ++ } //@codeactionedit("}", "refactor.rewrite", fill_struct_unsafe) +-- typeparams.go -- +package fillstruct + +type emptyStructWithTypeParams[A any] struct{} + +var _ = emptyStructWithTypeParams[int]{} // no suggested fix + +type basicStructWithTypeParams[T any] struct { + foo T +} + +var _ = basicStructWithTypeParams[int]{} //@codeactionedit("}", "refactor.rewrite", typeparams1) + +type twoArgStructWithTypeParams[F, B any] struct { + foo F + bar B +} + +var _ = twoArgStructWithTypeParams[string, int]{} //@codeactionedit("}", "refactor.rewrite", typeparams2) + +var _ = twoArgStructWithTypeParams[int, string]{ + bar: "bar", +} //@codeactionedit("}", "refactor.rewrite", typeparams3) + +type nestedStructWithTypeParams struct { + bar string + basic basicStructWithTypeParams[int] +} + +var _ = nestedStructWithTypeParams{} //@codeactionedit("}", "refactor.rewrite", typeparams4) + +func _[T any]() { + type S struct{ t T } + _ = S{} //@codeactionedit("}", "refactor.rewrite", typeparams5) +} +-- @typeparams1/typeparams.go -- +@@ -11 +11,3 @@ +-var _ = basicStructWithTypeParams[int]{} //@codeactionedit("}", "refactor.rewrite", typeparams1) ++var _ = basicStructWithTypeParams[int]{ ++ foo: 0, ++} //@codeactionedit("}", "refactor.rewrite", typeparams1) +-- @typeparams2/typeparams.go -- +@@ -18 +18,4 @@ +-var _ = twoArgStructWithTypeParams[string, int]{} //@codeactionedit("}", "refactor.rewrite", typeparams2) ++var _ = twoArgStructWithTypeParams[string, int]{ ++ foo: "", ++ bar: 0, ++} //@codeactionedit("}", "refactor.rewrite", typeparams2) +-- @typeparams3/typeparams.go -- +@@ -21 +21 @@ ++ foo: 0, +-- @typeparams4/typeparams.go -- +@@ -29 +29,4 @@ +-var _ = nestedStructWithTypeParams{} //@codeactionedit("}", "refactor.rewrite", typeparams4) ++var _ = nestedStructWithTypeParams{ ++ bar: "", ++ basic: basicStructWithTypeParams{}, ++} //@codeactionedit("}", "refactor.rewrite", typeparams4) +-- @typeparams5/typeparams.go -- +@@ -33 +33,3 @@ +- _ = S{} //@codeactionedit("}", "refactor.rewrite", typeparams5) ++ _ = S{ ++ t: *new(T), ++ } //@codeactionedit("}", "refactor.rewrite", typeparams5) +-- issue63921.go -- +package fillstruct + +// Test for golang/go#63921: fillstruct panicked with invalid fields. +type invalidStruct struct { + F int + Undefined +} + +func _() { + // Note: the golden content for issue63921 is empty: fillstruct produces no + // edits, but does not panic. + invalidStruct{} //@codeactionedit("}", "refactor.rewrite", issue63921) +} diff --git a/gopls/internal/test/marker/testdata/codeaction/removeparam.txt b/gopls/internal/test/marker/testdata/codeaction/removeparam.txt index ad2289284d8..a5ce7ca2e43 100644 --- a/gopls/internal/test/marker/testdata/codeaction/removeparam.txt +++ b/gopls/internal/test/marker/testdata/codeaction/removeparam.txt @@ -1,4 +1,5 @@ This test exercises the refactoring to remove unused parameters. +See removeparam_resolve.txt for same test with resolve support. -- go.mod -- module unused.mod diff --git a/gopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt b/gopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt new file mode 100644 index 00000000000..7b9296eb569 --- /dev/null +++ b/gopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt @@ -0,0 +1,258 @@ +This test exercises the refactoring to remove unused parameters, with resolve support. +See removeparam.txt for same test without resolve support. + +-- capabilities.json -- +{ + "textDocument": { + "codeAction": { + "dataSupport": true, + "resolveSupport": { + "properties": ["edit"] + } + } + } +} +-- go.mod -- +module unused.mod + +go 1.18 + +-- a/a.go -- +package a + +func A(x, unused int) int { //@codeaction("unused", "unused", "refactor.rewrite", a) + return x +} + +-- @a/a/a.go -- +package a + +func A(x int) int { //@codeaction("unused", "unused", "refactor.rewrite", a) + return x +} + +-- a/a2.go -- +package a + +func _() { + A(1, 2) +} + +-- a/a_test.go -- +package a + +func _() { + A(1, 2) +} + +-- a/a_x_test.go -- +package a_test + +import "unused.mod/a" + +func _() { + a.A(1, 2) +} + +-- b/b.go -- +package b + +import "unused.mod/a" + +func f() int { + return 1 +} + +func g() int { + return 2 +} + +func _() { + a.A(f(), 1) +} + +-- @a/a/a2.go -- +package a + +func _() { + A(1) +} +-- @a/a/a_test.go -- +package a + +func _() { + A(1) +} +-- @a/a/a_x_test.go -- +package a_test + +import "unused.mod/a" + +func _() { + a.A(1) +} +-- @a/b/b.go -- +package b + +import "unused.mod/a" + +func f() int { + return 1 +} + +func g() int { + return 2 +} + +func _() { + a.A(f()) +} +-- field/field.go -- +package field + +func Field(x int, field int) { //@codeaction("int", "int", "refactor.rewrite", field) +} + +func _() { + Field(1, 2) +} +-- @field/field/field.go -- +package field + +func Field(field int) { //@codeaction("int", "int", "refactor.rewrite", field) +} + +func _() { + Field(2) +} +-- ellipsis/ellipsis.go -- +package ellipsis + +func Ellipsis(...any) { //@codeaction("any", "any", "refactor.rewrite", ellipsis) +} + +func _() { + // TODO(rfindley): investigate the broken formatting resulting from these inlinings. + Ellipsis() + Ellipsis(1) + Ellipsis(1, 2) + Ellipsis(1, f(), g()) + Ellipsis(h()) + Ellipsis(i()...) +} + +func f() int +func g() int +func h() (int, int) +func i() []any + +-- @ellipsis/ellipsis/ellipsis.go -- +package ellipsis + +func Ellipsis() { //@codeaction("any", "any", "refactor.rewrite", ellipsis) +} + +func _() { + // TODO(rfindley): investigate the broken formatting resulting from these inlinings. + Ellipsis() + Ellipsis() + Ellipsis() + var _ []any = []any{1, f(), g()} + Ellipsis() + func(_ ...any) { + Ellipsis() + }(h()) + var _ []any = i() + Ellipsis() +} + +func f() int +func g() int +func h() (int, int) +func i() []any +-- ellipsis2/ellipsis2.go -- +package ellipsis2 + +func Ellipsis2(_, _ int, rest ...int) { //@codeaction("_", "_", "refactor.rewrite", ellipsis2) +} + +func _() { + Ellipsis2(1,2,3) + Ellipsis2(h()) + Ellipsis2(1,2, []int{3, 4}...) +} + +func h() (int, int) + +-- @ellipsis2/ellipsis2/ellipsis2.go -- +package ellipsis2 + +func Ellipsis2(_ int, rest ...int) { //@codeaction("_", "_", "refactor.rewrite", ellipsis2) +} + +func _() { + Ellipsis2(2, []int{3}...) + func(_, blank0 int, rest ...int) { + Ellipsis2(blank0, rest...) + }(h()) + Ellipsis2(2, []int{3, 4}...) +} + +func h() (int, int) +-- overlapping/overlapping.go -- +package overlapping + +func Overlapping(i int) int { //@codeactionerr(re"(i) int", re"(i) int", "refactor.rewrite", re"overlapping") + return 0 +} + +func _() { + x := Overlapping(Overlapping(0)) + _ = x +} + +-- effects/effects.go -- +package effects + +func effects(x, y int) int { //@codeaction("y", "y", "refactor.rewrite", effects) + return x +} + +func f() int +func g() int + +func _() { + effects(f(), g()) + effects(f(), g()) +} +-- @effects/effects/effects.go -- +package effects + +func effects(x int) int { //@codeaction("y", "y", "refactor.rewrite", effects) + return x +} + +func f() int +func g() int + +func _() { + var x, _ int = f(), g() + effects(x) + { + var x, _ int = f(), g() + effects(x) + } +} +-- recursive/recursive.go -- +package recursive + +func Recursive(x int) int { //@codeaction("x", "x", "refactor.rewrite", recursive) + return Recursive(1) +} + +-- @recursive/recursive/recursive.go -- +package recursive + +func Recursive() int { //@codeaction("x", "x", "refactor.rewrite", recursive) + return Recursive() +} diff --git a/gopls/internal/test/marker/testdata/stubmethods/basic.txt b/gopls/internal/test/marker/testdata/stubmethods/basic.txt index 95b515299a6..e4cfb6d05a0 100644 --- a/gopls/internal/test/marker/testdata/stubmethods/basic.txt +++ b/gopls/internal/test/marker/testdata/stubmethods/basic.txt @@ -1,4 +1,5 @@ This test exercises basic 'stub methods' functionality. +See basic_resolve.txt for the same test with resolve support. -- go.mod -- module example.com diff --git a/gopls/internal/test/marker/testdata/stubmethods/basic_resolve.txt b/gopls/internal/test/marker/testdata/stubmethods/basic_resolve.txt new file mode 100644 index 00000000000..183b7d526eb --- /dev/null +++ b/gopls/internal/test/marker/testdata/stubmethods/basic_resolve.txt @@ -0,0 +1,31 @@ +This test exercises basic 'stub methods' functionality, with resolve support. +See basic.txt for the same test without resolve support. + +-- capabilities.json -- +{ + "textDocument": { + "codeAction": { + "dataSupport": true, + "resolveSupport": { + "properties": ["edit"] + } + } + } +} +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type C int + +var _ error = C(0) //@suggestedfix(re"C.0.", re"missing method Error", stub) +-- @stub/a/a.go -- +@@ -5 +5,5 @@ ++// Error implements error. ++func (c C) Error() string { ++ panic("unimplemented") ++} ++