1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
14 "golang.org/x/tools/go/analysis"
15 "golang.org/x/tools/internal/lsp/analysis/fillstruct"
16 "golang.org/x/tools/internal/lsp/analysis/undeclaredname"
17 "golang.org/x/tools/internal/lsp/protocol"
18 "golang.org/x/tools/internal/span"
19 errors "golang.org/x/xerrors"
26 // Async controls whether the command executes asynchronously.
29 // appliesFn is an optional field to indicate whether or not a command can
30 // be applied to the given inputs. If it returns false, we should not
31 // suggest this command for these inputs.
34 // suggestedFixFn is an optional field to generate the edits that the
35 // command produces for the given inputs.
36 suggestedFixFn SuggestedFixFunc
39 // CommandPrefix is the prefix of all command names gopls uses externally.
40 const CommandPrefix = "gopls."
42 // ID adds the CommandPrefix to the command name, in order to avoid
43 // collisions with other language servers.
44 func (c Command) ID() string {
45 return CommandPrefix + c.Name
48 type AppliesFunc func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) bool
50 // SuggestedFixFunc is a function used to get the suggested fixes for a given
51 // gopls command, some of which are provided by go/analysis.Analyzers. Some of
52 // the analyzers in internal/lsp/analysis are not efficient enough to include
53 // suggested fixes with their diagnostics, so we have to compute them
54 // separately. Such analyzers should provide a function with a signature of
56 type SuggestedFixFunc func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error)
58 // Commands are the commands currently supported by gopls.
59 var Commands = []*Command{
66 CommandUndeclaredName,
69 CommandUpgradeDependency,
70 CommandRemoveDependency,
72 CommandExtractVariable,
73 CommandExtractFunction,
75 CommandGenerateGoplsMod,
79 // CommandTest runs `go test` for a specific test function.
80 CommandTest = &Command{
86 // CommandGenerate runs `go generate` for a given directory.
87 CommandGenerate = &Command{
89 Title: "Run go generate",
92 // CommandTidy runs `go mod tidy` for a module.
93 CommandTidy = &Command{
95 Title: "Run go mod tidy",
98 // CommandVendor runs `go mod vendor` for a module.
99 CommandVendor = &Command{
101 Title: "Run go mod vendor",
104 // CommandGoGetPackage runs `go get` to fetch a package.
105 CommandGoGetPackage = &Command{
106 Name: "go_get_package",
107 Title: "go get package",
110 // CommandUpdateGoSum updates the go.sum file for a module.
111 CommandUpdateGoSum = &Command{
112 Name: "update_go_sum",
113 Title: "Update go.sum",
116 // CommandAddDependency adds a dependency.
117 CommandAddDependency = &Command{
118 Name: "add_dependency",
119 Title: "Add dependency",
122 // CommandUpgradeDependency upgrades a dependency.
123 CommandUpgradeDependency = &Command{
124 Name: "upgrade_dependency",
125 Title: "Upgrade dependency",
128 // CommandRemoveDependency removes a dependency.
129 CommandRemoveDependency = &Command{
130 Name: "remove_dependency",
131 Title: "Remove dependency",
134 // CommandRegenerateCgo regenerates cgo definitions.
135 CommandRegenerateCgo = &Command{
136 Name: "regenerate_cgo",
137 Title: "Regenerate cgo",
140 // CommandToggleDetails controls calculation of gc annotations.
141 CommandToggleDetails = &Command{
143 Title: "Toggle gc_details",
146 // CommandFillStruct is a gopls command to fill a struct with default
148 CommandFillStruct = &Command{
150 Title: "Fill struct",
151 suggestedFixFn: fillstruct.SuggestedFix,
154 // CommandUndeclaredName adds a variable declaration for an undeclared
156 CommandUndeclaredName = &Command{
157 Name: "undeclared_name",
158 Title: "Undeclared name",
159 suggestedFixFn: undeclaredname.SuggestedFix,
162 // CommandExtractVariable extracts an expression to a variable.
163 CommandExtractVariable = &Command{
164 Name: "extract_variable",
165 Title: "Extract to variable",
166 suggestedFixFn: extractVariable,
167 appliesFn: func(_ *token.FileSet, rng span.Range, _ []byte, file *ast.File, _ *types.Package, _ *types.Info) bool {
168 _, _, ok, _ := canExtractVariable(rng, file)
173 // CommandExtractFunction extracts statements to a function.
174 CommandExtractFunction = &Command{
175 Name: "extract_function",
176 Title: "Extract to function",
177 suggestedFixFn: extractFunction,
178 appliesFn: func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, _ *types.Package, info *types.Info) bool {
179 _, ok, _ := canExtractFunction(fset, rng, src, file, info)
184 // CommandGenerateGoplsMod (re)generates the gopls.mod file.
185 CommandGenerateGoplsMod = &Command{
186 Name: "generate_gopls_mod",
187 Title: "Generate gopls.mod",
191 // Applies reports whether the command c implements a suggested fix that is
192 // relevant to the given rng.
193 func (c *Command) Applies(ctx context.Context, snapshot Snapshot, fh FileHandle, pRng protocol.Range) bool {
194 // If there is no applies function, assume that the command applies.
195 if c.appliesFn == nil {
198 fset, rng, src, file, _, pkg, info, err := getAllSuggestedFixInputs(ctx, snapshot, fh, pRng)
202 return c.appliesFn(fset, rng, src, file, pkg, info)
205 // IsSuggestedFix reports whether the given command is intended to work as a
206 // suggested fix. Suggested fix commands are intended to return edits which are
207 // then applied to the workspace.
208 func (c *Command) IsSuggestedFix() bool {
209 return c.suggestedFixFn != nil
212 // SuggestedFix applies the command's suggested fix to the given file and
213 // range, returning the resulting edits.
214 func (c *Command) SuggestedFix(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) ([]protocol.TextDocumentEdit, error) {
215 if c.suggestedFixFn == nil {
216 return nil, fmt.Errorf("no suggested fix function for %s", c.Name)
218 fset, rng, src, file, m, pkg, info, err := getAllSuggestedFixInputs(ctx, snapshot, fh, pRng)
222 fix, err := c.suggestedFixFn(fset, rng, src, file, pkg, info)
230 var edits []protocol.TextDocumentEdit
231 for _, edit := range fix.TextEdits {
232 rng := span.NewRange(fset, edit.Pos, edit.End)
233 spn, err := rng.Span()
237 clRng, err := m.Range(spn)
241 edits = append(edits, protocol.TextDocumentEdit{
242 TextDocument: protocol.VersionedTextDocumentIdentifier{
243 Version: fh.Version(),
244 TextDocumentIdentifier: protocol.TextDocumentIdentifier{
245 URI: protocol.URIFromSpanURI(fh.URI()),
248 Edits: []protocol.TextEdit{
251 NewText: string(edit.NewText),
259 // getAllSuggestedFixInputs is a helper function to collect all possible needed
260 // inputs for an AppliesFunc or SuggestedFixFunc.
261 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) {
262 pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
264 return nil, span.Range{}, nil, nil, nil, nil, nil, errors.Errorf("getting file for Identifier: %w", err)
266 spn, err := pgf.Mapper.RangeSpan(pRng)
268 return nil, span.Range{}, nil, nil, nil, nil, nil, err
270 rng, err := spn.Range(pgf.Mapper.Converter)
272 return nil, span.Range{}, nil, nil, nil, nil, nil, err
274 src, err := fh.Read()
276 return nil, span.Range{}, nil, nil, nil, nil, nil, err
278 return snapshot.FileSet(), rng, src, pgf.File, pgf.Mapper, pkg.GetTypes(), pkg.GetTypesInfo(), nil