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 <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Suzy Mueller 2023-12-07 16:51:04 -08:00
Родитель f2d3f78aad
Коммит 592d9e1e87
23 изменённых файлов: 1344 добавлений и 124 удалений

Просмотреть файл

@ -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,
}
```

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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.

Просмотреть файл

@ -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

Просмотреть файл

@ -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}.

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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

Просмотреть файл

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

Просмотреть файл

@ -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.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}",
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",

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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, &params.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 {

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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

Просмотреть файл

@ -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)

Просмотреть файл

@ -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

Просмотреть файл

@ -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)

Просмотреть файл

@ -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)

Просмотреть файл

@ -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)
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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()
}

Просмотреть файл

@ -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

Просмотреть файл

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