mirror of
https://github.com/golang/go
synced 2024-11-05 15:56:12 -07:00
c9619e8fac
FileHandle currently includes LSP-level information about Version and Session. That's dangerous, because the cache operates in terms of URIs and content only -- we explicitly want to share results across sessions and versions if they happen to be the same. Split the LSP information into separate types, VersionedFileHandle and VersionedFileIdentity. Change-Id: I158646b783375b58245468599301e2a29c657e71 Reviewed-on: https://go-review.googlesource.com/c/tools/+/245058 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
218 lines
6.8 KiB
Go
218 lines
6.8 KiB
Go
// 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"
|
|
)
|
|
|
|
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,
|
|
CommandToggleDetails,
|
|
}
|
|
|
|
var (
|
|
// CommandTest runs `go test` for a specific test function.
|
|
CommandTest = &Command{
|
|
Name: "test",
|
|
}
|
|
|
|
// CommandGenerate runs `go generate` for a given directory.
|
|
CommandGenerate = &Command{
|
|
Name: "generate",
|
|
}
|
|
|
|
// CommandTidy runs `go mod tidy` for a module.
|
|
CommandTidy = &Command{
|
|
Name: "tidy",
|
|
}
|
|
|
|
// CommandVendor runs `go mod vendor` for a module.
|
|
CommandVendor = &Command{
|
|
Name: "vendor",
|
|
}
|
|
|
|
// CommandUpgradeDependency upgrades a dependency.
|
|
CommandUpgradeDependency = &Command{
|
|
Name: "upgrade_dependency",
|
|
}
|
|
|
|
// CommandRegenerateCgo regenerates cgo definitions.
|
|
CommandRegenerateCgo = &Command{
|
|
Name: "regenerate_cgo",
|
|
}
|
|
|
|
// CommandToggleDetails controls calculation of gc annotations.
|
|
CommandToggleDetails = &Command{
|
|
Name: "gc_details",
|
|
}
|
|
|
|
// 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,
|
|
appliesFn: func(_ *token.FileSet, rng span.Range, _ []byte, file *ast.File, _ *types.Package, _ *types.Info) bool {
|
|
_, _, ok, _ := canExtractVariable(rng, file)
|
|
return ok
|
|
},
|
|
}
|
|
|
|
// CommandExtractFunction extracts statements to a function.
|
|
CommandExtractFunction = &Command{
|
|
Name: "extract_function",
|
|
Title: "Extract to function",
|
|
suggestedFixFn: extractFunction,
|
|
appliesFn: func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, _ *types.Package, info *types.Info) bool {
|
|
_, _, _, _, ok, _ := canExtractFunction(fset, rng, src, file, info)
|
|
return ok
|
|
},
|
|
}
|
|
)
|
|
|
|
// 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.
|
|
func (c *Command) SuggestedFix(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) ([]protocol.TextDocumentEdit, error) {
|
|
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
|
|
}
|
|
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) {
|
|
pkg, pgf, err := getParsedFile(ctx, snapshot, fh, NarrowestPackage)
|
|
if err != nil {
|
|
return nil, span.Range{}, nil, nil, nil, nil, nil, fmt.Errorf("getting file for Identifier: %w", err)
|
|
}
|
|
spn, err := pgf.Mapper.RangeSpan(pRng)
|
|
if err != nil {
|
|
return nil, span.Range{}, nil, nil, nil, nil, nil, err
|
|
}
|
|
rng, err := spn.Range(pgf.Mapper.Converter)
|
|
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
|
|
}
|
|
fset := snapshot.View().Session().Cache().FileSet()
|
|
return fset, rng, src, pgf.File, pgf.Mapper, pkg.GetTypes(), pkg.GetTypesInfo(), nil
|
|
}
|