2020-07-23 21:24:36 -06:00
|
|
|
// Copyright 2020 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package source
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/token"
|
|
|
|
"go/types"
|
|
|
|
|
|
|
|
"golang.org/x/tools/go/analysis"
|
|
|
|
"golang.org/x/tools/internal/lsp/analysis/fillstruct"
|
|
|
|
"golang.org/x/tools/internal/lsp/analysis/undeclaredname"
|
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
|
|
"golang.org/x/tools/internal/span"
|
2020-08-26 15:41:45 -06:00
|
|
|
errors "golang.org/x/xerrors"
|
2020-07-23 21:24:36 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
type Command struct {
|
|
|
|
Name, Title string
|
|
|
|
|
|
|
|
// appliesFn is an optional field to indicate whether or not a command can
|
|
|
|
// be applied to the given inputs. If it returns false, we should not
|
|
|
|
// suggest this command for these inputs.
|
|
|
|
appliesFn AppliesFunc
|
|
|
|
|
|
|
|
// suggestedFixFn is an optional field to generate the edits that the
|
|
|
|
// command produces for the given inputs.
|
|
|
|
suggestedFixFn SuggestedFixFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
type AppliesFunc func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) bool
|
|
|
|
|
|
|
|
// SuggestedFixFunc is a function used to get the suggested fixes for a given
|
|
|
|
// gopls command, some of which are provided by go/analysis.Analyzers. Some of
|
|
|
|
// the analyzers in internal/lsp/analysis are not efficient enough to include
|
|
|
|
// suggested fixes with their diagnostics, so we have to compute them
|
|
|
|
// separately. Such analyzers should provide a function with a signature of
|
|
|
|
// SuggestedFixFunc.
|
|
|
|
type SuggestedFixFunc func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error)
|
|
|
|
|
|
|
|
// Commands are the commands currently supported by gopls.
|
|
|
|
var Commands = []*Command{
|
|
|
|
CommandGenerate,
|
|
|
|
CommandFillStruct,
|
|
|
|
CommandRegenerateCgo,
|
|
|
|
CommandTest,
|
|
|
|
CommandTidy,
|
|
|
|
CommandUndeclaredName,
|
|
|
|
CommandUpgradeDependency,
|
|
|
|
CommandVendor,
|
|
|
|
CommandExtractVariable,
|
|
|
|
CommandExtractFunction,
|
2020-07-16 08:45:30 -06:00
|
|
|
CommandToggleDetails,
|
2020-07-23 21:24:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
// CommandTest runs `go test` for a specific test function.
|
|
|
|
CommandTest = &Command{
|
internal/lsp: improvements for command messages
When falling back to messages for progress reporting, don't try to
implement cancellation via ShowMessageCommand dialogs. They are an
imperfect solution, as the dialog stays open even after the command
completed. Also, among the LSP clients that don't support workDone
reporting, I suspect many also don't support ShowMessageCommand (for
example, govim), so the audience for this feature is probably quite
small.
Just remove it, and instead show a (non-cancellable) message. If clients
want cancellation, workDone progress support is the way to provide it.
Also remove a redundant message on go-generate success, and attach logs
when tests fail. Without logs on failure, I find that the test command
is not very useful. I tested a bit with very verbose test output, and
both VS Code and coc.nvim handled it gracefully.
Finally, fix a bug causing benchmarks not to be run.
Change-Id: I05422bcefc857c25cd99e643e614a0bc33870586
Reviewed-on: https://go-review.googlesource.com/c/tools/+/249702
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-08-21 12:18:37 -06:00
|
|
|
Name: "test",
|
|
|
|
Title: "Run test(s)",
|
2020-07-23 21:24:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// CommandGenerate runs `go generate` for a given directory.
|
|
|
|
CommandGenerate = &Command{
|
internal/lsp: improvements for command messages
When falling back to messages for progress reporting, don't try to
implement cancellation via ShowMessageCommand dialogs. They are an
imperfect solution, as the dialog stays open even after the command
completed. Also, among the LSP clients that don't support workDone
reporting, I suspect many also don't support ShowMessageCommand (for
example, govim), so the audience for this feature is probably quite
small.
Just remove it, and instead show a (non-cancellable) message. If clients
want cancellation, workDone progress support is the way to provide it.
Also remove a redundant message on go-generate success, and attach logs
when tests fail. Without logs on failure, I find that the test command
is not very useful. I tested a bit with very verbose test output, and
both VS Code and coc.nvim handled it gracefully.
Finally, fix a bug causing benchmarks not to be run.
Change-Id: I05422bcefc857c25cd99e643e614a0bc33870586
Reviewed-on: https://go-review.googlesource.com/c/tools/+/249702
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-08-21 12:18:37 -06:00
|
|
|
Name: "generate",
|
|
|
|
Title: "Run go generate",
|
2020-07-23 21:24:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// CommandTidy runs `go mod tidy` for a module.
|
|
|
|
CommandTidy = &Command{
|
internal/lsp: improvements for command messages
When falling back to messages for progress reporting, don't try to
implement cancellation via ShowMessageCommand dialogs. They are an
imperfect solution, as the dialog stays open even after the command
completed. Also, among the LSP clients that don't support workDone
reporting, I suspect many also don't support ShowMessageCommand (for
example, govim), so the audience for this feature is probably quite
small.
Just remove it, and instead show a (non-cancellable) message. If clients
want cancellation, workDone progress support is the way to provide it.
Also remove a redundant message on go-generate success, and attach logs
when tests fail. Without logs on failure, I find that the test command
is not very useful. I tested a bit with very verbose test output, and
both VS Code and coc.nvim handled it gracefully.
Finally, fix a bug causing benchmarks not to be run.
Change-Id: I05422bcefc857c25cd99e643e614a0bc33870586
Reviewed-on: https://go-review.googlesource.com/c/tools/+/249702
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-08-21 12:18:37 -06:00
|
|
|
Name: "tidy",
|
|
|
|
Title: "Run go mod tidy",
|
2020-07-23 21:24:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// CommandVendor runs `go mod vendor` for a module.
|
|
|
|
CommandVendor = &Command{
|
internal/lsp: improvements for command messages
When falling back to messages for progress reporting, don't try to
implement cancellation via ShowMessageCommand dialogs. They are an
imperfect solution, as the dialog stays open even after the command
completed. Also, among the LSP clients that don't support workDone
reporting, I suspect many also don't support ShowMessageCommand (for
example, govim), so the audience for this feature is probably quite
small.
Just remove it, and instead show a (non-cancellable) message. If clients
want cancellation, workDone progress support is the way to provide it.
Also remove a redundant message on go-generate success, and attach logs
when tests fail. Without logs on failure, I find that the test command
is not very useful. I tested a bit with very verbose test output, and
both VS Code and coc.nvim handled it gracefully.
Finally, fix a bug causing benchmarks not to be run.
Change-Id: I05422bcefc857c25cd99e643e614a0bc33870586
Reviewed-on: https://go-review.googlesource.com/c/tools/+/249702
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-08-21 12:18:37 -06:00
|
|
|
Name: "vendor",
|
|
|
|
Title: "Run go mod vendor",
|
2020-07-23 21:24:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// CommandUpgradeDependency upgrades a dependency.
|
|
|
|
CommandUpgradeDependency = &Command{
|
internal/lsp: improvements for command messages
When falling back to messages for progress reporting, don't try to
implement cancellation via ShowMessageCommand dialogs. They are an
imperfect solution, as the dialog stays open even after the command
completed. Also, among the LSP clients that don't support workDone
reporting, I suspect many also don't support ShowMessageCommand (for
example, govim), so the audience for this feature is probably quite
small.
Just remove it, and instead show a (non-cancellable) message. If clients
want cancellation, workDone progress support is the way to provide it.
Also remove a redundant message on go-generate success, and attach logs
when tests fail. Without logs on failure, I find that the test command
is not very useful. I tested a bit with very verbose test output, and
both VS Code and coc.nvim handled it gracefully.
Finally, fix a bug causing benchmarks not to be run.
Change-Id: I05422bcefc857c25cd99e643e614a0bc33870586
Reviewed-on: https://go-review.googlesource.com/c/tools/+/249702
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-08-21 12:18:37 -06:00
|
|
|
Name: "upgrade_dependency",
|
|
|
|
Title: "Upgrade dependency",
|
2020-07-23 21:24:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// CommandRegenerateCgo regenerates cgo definitions.
|
|
|
|
CommandRegenerateCgo = &Command{
|
internal/lsp: improvements for command messages
When falling back to messages for progress reporting, don't try to
implement cancellation via ShowMessageCommand dialogs. They are an
imperfect solution, as the dialog stays open even after the command
completed. Also, among the LSP clients that don't support workDone
reporting, I suspect many also don't support ShowMessageCommand (for
example, govim), so the audience for this feature is probably quite
small.
Just remove it, and instead show a (non-cancellable) message. If clients
want cancellation, workDone progress support is the way to provide it.
Also remove a redundant message on go-generate success, and attach logs
when tests fail. Without logs on failure, I find that the test command
is not very useful. I tested a bit with very verbose test output, and
both VS Code and coc.nvim handled it gracefully.
Finally, fix a bug causing benchmarks not to be run.
Change-Id: I05422bcefc857c25cd99e643e614a0bc33870586
Reviewed-on: https://go-review.googlesource.com/c/tools/+/249702
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-08-21 12:18:37 -06:00
|
|
|
Name: "regenerate_cgo",
|
|
|
|
Title: "Regenerate cgo",
|
2020-07-23 21:24:36 -06:00
|
|
|
}
|
|
|
|
|
2020-07-16 08:45:30 -06:00
|
|
|
// CommandToggleDetails controls calculation of gc annotations.
|
|
|
|
CommandToggleDetails = &Command{
|
internal/lsp: improvements for command messages
When falling back to messages for progress reporting, don't try to
implement cancellation via ShowMessageCommand dialogs. They are an
imperfect solution, as the dialog stays open even after the command
completed. Also, among the LSP clients that don't support workDone
reporting, I suspect many also don't support ShowMessageCommand (for
example, govim), so the audience for this feature is probably quite
small.
Just remove it, and instead show a (non-cancellable) message. If clients
want cancellation, workDone progress support is the way to provide it.
Also remove a redundant message on go-generate success, and attach logs
when tests fail. Without logs on failure, I find that the test command
is not very useful. I tested a bit with very verbose test output, and
both VS Code and coc.nvim handled it gracefully.
Finally, fix a bug causing benchmarks not to be run.
Change-Id: I05422bcefc857c25cd99e643e614a0bc33870586
Reviewed-on: https://go-review.googlesource.com/c/tools/+/249702
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-08-21 12:18:37 -06:00
|
|
|
Name: "gc_details",
|
|
|
|
Title: "Toggle gc_details",
|
2020-07-16 08:45:30 -06:00
|
|
|
}
|
|
|
|
|
2020-07-23 21:24:36 -06:00
|
|
|
// CommandFillStruct is a gopls command to fill a struct with default
|
|
|
|
// values.
|
|
|
|
CommandFillStruct = &Command{
|
|
|
|
Name: "fill_struct",
|
|
|
|
suggestedFixFn: fillstruct.SuggestedFix,
|
|
|
|
}
|
|
|
|
|
|
|
|
// CommandUndeclaredName adds a variable declaration for an undeclared
|
|
|
|
// name.
|
|
|
|
CommandUndeclaredName = &Command{
|
|
|
|
Name: "undeclared_name",
|
|
|
|
suggestedFixFn: undeclaredname.SuggestedFix,
|
|
|
|
}
|
|
|
|
|
|
|
|
// CommandExtractVariable extracts an expression to a variable.
|
|
|
|
CommandExtractVariable = &Command{
|
|
|
|
Name: "extract_variable",
|
|
|
|
Title: "Extract to variable",
|
|
|
|
suggestedFixFn: extractVariable,
|
2020-07-28 17:20:28 -06:00
|
|
|
appliesFn: func(_ *token.FileSet, rng span.Range, _ []byte, file *ast.File, _ *types.Package, _ *types.Info) bool {
|
|
|
|
_, _, ok, _ := canExtractVariable(rng, file)
|
2020-07-28 10:00:17 -06:00
|
|
|
return ok
|
|
|
|
},
|
2020-07-23 21:24:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// CommandExtractFunction extracts statements to a function.
|
|
|
|
CommandExtractFunction = &Command{
|
|
|
|
Name: "extract_function",
|
|
|
|
Title: "Extract to function",
|
|
|
|
suggestedFixFn: extractFunction,
|
2020-07-28 17:20:28 -06:00
|
|
|
appliesFn: func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, _ *types.Package, info *types.Info) bool {
|
2020-08-07 11:48:34 -06:00
|
|
|
_, _, _, _, _, ok, _ := canExtractFunction(fset, rng, src, file, info)
|
2020-07-28 17:20:28 -06:00
|
|
|
return ok
|
|
|
|
},
|
2020-07-23 21:24:36 -06:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Applies reports whether the command c implements a suggested fix that is
|
|
|
|
// relevant to the given rng.
|
|
|
|
func (c *Command) Applies(ctx context.Context, snapshot Snapshot, fh FileHandle, pRng protocol.Range) bool {
|
|
|
|
// If there is no applies function, assume that the command applies.
|
|
|
|
if c.appliesFn == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
fset, rng, src, file, _, pkg, info, err := getAllSuggestedFixInputs(ctx, snapshot, fh, pRng)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return c.appliesFn(fset, rng, src, file, pkg, info)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsSuggestedFix reports whether the given command is intended to work as a
|
|
|
|
// suggested fix. Suggested fix commands are intended to return edits which are
|
|
|
|
// then applied to the workspace.
|
|
|
|
func (c *Command) IsSuggestedFix() bool {
|
|
|
|
return c.suggestedFixFn != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SuggestedFix applies the command's suggested fix to the given file and
|
|
|
|
// range, returning the resulting edits.
|
2020-07-26 16:01:39 -06:00
|
|
|
func (c *Command) SuggestedFix(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) ([]protocol.TextDocumentEdit, error) {
|
2020-07-23 21:24:36 -06:00
|
|
|
if c.suggestedFixFn == nil {
|
|
|
|
return nil, fmt.Errorf("no suggested fix function for %s", c.Name)
|
|
|
|
}
|
|
|
|
fset, rng, src, file, m, pkg, info, err := getAllSuggestedFixInputs(ctx, snapshot, fh, pRng)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fix, err := c.suggestedFixFn(fset, rng, src, file, pkg, info)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-08-22 11:35:59 -06:00
|
|
|
if fix == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2020-07-23 21:24:36 -06:00
|
|
|
var edits []protocol.TextDocumentEdit
|
|
|
|
for _, edit := range fix.TextEdits {
|
|
|
|
rng := span.NewRange(fset, edit.Pos, edit.End)
|
|
|
|
spn, err := rng.Span()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
clRng, err := m.Range(spn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
edits = append(edits, protocol.TextDocumentEdit{
|
|
|
|
TextDocument: protocol.VersionedTextDocumentIdentifier{
|
|
|
|
Version: fh.Version(),
|
|
|
|
TextDocumentIdentifier: protocol.TextDocumentIdentifier{
|
|
|
|
URI: protocol.URIFromSpanURI(fh.URI()),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Edits: []protocol.TextEdit{
|
|
|
|
{
|
|
|
|
Range: clRng,
|
|
|
|
NewText: string(edit.NewText),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return edits, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getAllSuggestedFixInputs is a helper function to collect all possible needed
|
|
|
|
// inputs for an AppliesFunc or SuggestedFixFunc.
|
|
|
|
func getAllSuggestedFixInputs(ctx context.Context, snapshot Snapshot, fh FileHandle, pRng protocol.Range) (*token.FileSet, span.Range, []byte, *ast.File, *protocol.ColumnMapper, *types.Package, *types.Info, error) {
|
2020-07-22 09:32:32 -06:00
|
|
|
pkg, pgf, err := getParsedFile(ctx, snapshot, fh, NarrowestPackage)
|
2020-07-23 21:24:36 -06:00
|
|
|
if err != nil {
|
2020-08-26 15:41:45 -06:00
|
|
|
return nil, span.Range{}, nil, nil, nil, nil, nil, errors.Errorf("getting file for Identifier: %w", err)
|
2020-07-23 21:24:36 -06:00
|
|
|
}
|
2020-07-21 13:15:06 -06:00
|
|
|
spn, err := pgf.Mapper.RangeSpan(pRng)
|
2020-07-23 21:24:36 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, span.Range{}, nil, nil, nil, nil, nil, err
|
|
|
|
}
|
2020-07-21 13:15:06 -06:00
|
|
|
rng, err := spn.Range(pgf.Mapper.Converter)
|
2020-07-23 21:24:36 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, span.Range{}, nil, nil, nil, nil, nil, err
|
|
|
|
}
|
|
|
|
src, err := fh.Read()
|
|
|
|
if err != nil {
|
|
|
|
return nil, span.Range{}, nil, nil, nil, nil, nil, err
|
|
|
|
}
|
2020-07-28 15:00:10 -06:00
|
|
|
return snapshot.FileSet(), rng, src, pgf.File, pgf.Mapper, pkg.GetTypes(), pkg.GetTypesInfo(), nil
|
2020-07-23 21:24:36 -06:00
|
|
|
}
|