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") ++} ++