Skip to content

Commit e46a7b9

Browse files
committed
internal/lsp: add support for sourceFixAll analyzers
This change adds support for analyzers that have suggested fixes of the kind Source.FixAll. This will allow these fixes to be applied on save if the user desires. To auto apply these fixes on save, make sure your settings.json looks like: "[go]": { "editor.codeActionsOnSave": { ... "source.fixAll": true, ... }, ... } Update golang/go#37221 Change-Id: I534e4f6c8c51ec2848cf2899aab68f587ba68423 Reviewed-on: https://go-review.googlesource.com/c/tools/+/223658 Run-TryBot: Rohan Challa <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent d58d27d commit e46a7b9

File tree

4 files changed

+67
-25
lines changed

4 files changed

+67
-25
lines changed

internal/lsp/cache/pkg.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -125,22 +125,23 @@ func (p *pkg) Module() *packagesinternal.Module {
125125
return p.module
126126
}
127127

128-
func (s *snapshot) FindAnalysisError(ctx context.Context, pkgID, analyzerName, msg string, rng protocol.Range) (*source.Error, error) {
128+
func (s *snapshot) FindAnalysisError(ctx context.Context, pkgID, analyzerName, msg string, rng protocol.Range) (*source.Error, *source.Analyzer, error) {
129+
var analyzer source.Analyzer
129130
analyzer, ok := s.View().Options().Analyzers[analyzerName]
130131
if !ok {
131-
return nil, errors.Errorf("unexpected analyzer: %s", analyzerName)
132+
return nil, nil, errors.Errorf("unexpected analyzer: %s", analyzerName)
132133
}
133134
if !analyzer.Enabled {
134-
return nil, errors.Errorf("disabled analyzer: %s", analyzerName)
135+
return nil, nil, errors.Errorf("disabled analyzer: %s", analyzerName)
135136
}
136137

137138
act, err := s.actionHandle(ctx, packageID(pkgID), analyzer.Analyzer)
138139
if err != nil {
139-
return nil, err
140+
return nil, nil, err
140141
}
141142
errs, _, err := act.analyze(ctx)
142143
if err != nil {
143-
return nil, err
144+
return nil, nil, err
144145
}
145146
for _, err := range errs {
146147
if err.Category != analyzerName {
@@ -152,7 +153,7 @@ func (s *snapshot) FindAnalysisError(ctx context.Context, pkgID, analyzerName, m
152153
if protocol.CompareRange(err.Range, rng) != 0 {
153154
continue
154155
}
155-
return err, nil
156+
return err, &analyzer, nil
156157
}
157-
return nil, errors.Errorf("no matching diagnostic for %s:%v", pkgID, analyzerName)
158+
return nil, nil, errors.Errorf("no matching diagnostic for %s:%v", pkgID, analyzerName)
158159
}

internal/lsp/code_action.go

+51-17
Original file line numberDiff line numberDiff line change
@@ -65,23 +65,37 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
6565
})
6666
}
6767
case source.Go:
68-
edits, editsPerFix, err := source.AllImportsFixes(ctx, snapshot, fh)
69-
if err != nil {
70-
return nil, err
68+
diagnostics := params.Context.Diagnostics
69+
70+
var importEdits []protocol.TextEdit
71+
var importEditsPerFix []*source.ImportFix
72+
var analysisQuickFixes []protocol.CodeAction
73+
var highConfidenceEdits []protocol.TextDocumentEdit
74+
75+
// Retrieve any necessary import edits or fixes.
76+
if wanted[protocol.QuickFix] && len(diagnostics) > 0 || wanted[protocol.SourceOrganizeImports] {
77+
importEdits, importEditsPerFix, err = source.AllImportsFixes(ctx, snapshot, fh)
78+
if err != nil {
79+
return nil, err
80+
}
7181
}
72-
if diagnostics := params.Context.Diagnostics; wanted[protocol.QuickFix] && len(diagnostics) > 0 {
73-
// First, add the quick fixes reported by go/analysis.
74-
qf, err := quickFixes(ctx, snapshot, fh, diagnostics)
82+
// Retrieve any necessary analysis fixes or edits.
83+
if (wanted[protocol.QuickFix] || wanted[protocol.SourceFixAll]) && len(diagnostics) > 0 {
84+
analysisQuickFixes, highConfidenceEdits, err = analysisFixes(ctx, snapshot, fh, diagnostics)
7585
if err != nil {
76-
event.Error(ctx, "quick fixes failed", err, tag.URI.Of(uri))
86+
event.Error(ctx, "analysis fixes failed", err, tag.URI.Of(uri))
7787
}
78-
codeActions = append(codeActions, qf...)
88+
}
89+
90+
if wanted[protocol.QuickFix] && len(diagnostics) > 0 {
91+
// First, add the quick fixes reported by go/analysis.
92+
codeActions = append(codeActions, analysisQuickFixes...)
7993

8094
// If we also have diagnostics for missing imports, we can associate them with quick fixes.
8195
if findImportErrors(diagnostics) {
8296
// Separate this into a set of codeActions per diagnostic, where
8397
// each action is the addition, removal, or renaming of one import.
84-
for _, importFix := range editsPerFix {
98+
for _, importFix := range importEditsPerFix {
8599
// Get the diagnostics this fix would affect.
86100
if fixDiagnostics := importDiagnostics(importFix.Fix, diagnostics); len(fixDiagnostics) > 0 {
87101
codeActions = append(codeActions, protocol.CodeAction{
@@ -95,6 +109,8 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
95109
}
96110
}
97111
}
112+
113+
// Get any actions that might be attributed to missing modules in the go.mod file.
98114
actions, err := mod.SuggestedGoFixes(ctx, snapshot, fh, diagnostics)
99115
if err != nil {
100116
event.Error(ctx, "quick fixes failed", err, tag.URI.Of(uri))
@@ -103,12 +119,21 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
103119
codeActions = append(codeActions, actions...)
104120
}
105121
}
106-
if wanted[protocol.SourceOrganizeImports] && len(edits) > 0 {
122+
if wanted[protocol.SourceOrganizeImports] && len(importEdits) > 0 {
107123
codeActions = append(codeActions, protocol.CodeAction{
108124
Title: "Organize Imports",
109125
Kind: protocol.SourceOrganizeImports,
110126
Edit: protocol.WorkspaceEdit{
111-
DocumentChanges: documentChanges(fh, edits),
127+
DocumentChanges: documentChanges(fh, importEdits),
128+
},
129+
})
130+
}
131+
if wanted[protocol.SourceFixAll] && len(highConfidenceEdits) > 0 {
132+
codeActions = append(codeActions, protocol.CodeAction{
133+
Title: "Simplifications",
134+
Kind: protocol.SourceFixAll,
135+
Edit: protocol.WorkspaceEdit{
136+
DocumentChanges: highConfidenceEdits,
112137
},
113138
})
114139
}
@@ -199,23 +224,28 @@ func importDiagnostics(fix *imports.ImportFix, diagnostics []protocol.Diagnostic
199224
return results
200225
}
201226

202-
func quickFixes(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
227+
func analysisFixes(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, []protocol.TextDocumentEdit, error) {
228+
if len(diagnostics) == 0 {
229+
return nil, nil, nil
230+
}
231+
203232
var codeActions []protocol.CodeAction
233+
var sourceFixAllEdits []protocol.TextDocumentEdit
204234

205235
phs, err := snapshot.PackageHandles(ctx, fh)
206236
if err != nil {
207-
return nil, err
237+
return nil, nil, err
208238
}
209239
// We get the package that source.Diagnostics would've used. This is hack.
210240
// TODO(golang/go#32443): The correct solution will be to cache diagnostics per-file per-snapshot.
211241
ph, err := source.WidestPackageHandle(phs)
212242
if err != nil {
213-
return nil, err
243+
return nil, nil, err
214244
}
215245
for _, diag := range diagnostics {
216246
// This code assumes that the analyzer name is the Source of the diagnostic.
217247
// If this ever changes, this will need to be addressed.
218-
srcErr, err := snapshot.FindAnalysisError(ctx, ph.ID(), diag.Source, diag.Message, diag.Range)
248+
srcErr, analyzer, err := snapshot.FindAnalysisError(ctx, ph.ID(), diag.Source, diag.Message, diag.Range)
219249
if err != nil {
220250
continue
221251
}
@@ -232,12 +262,16 @@ func quickFixes(ctx context.Context, snapshot source.Snapshot, fh source.FileHan
232262
event.Error(ctx, "no file", err, tag.URI.Of(uri))
233263
continue
234264
}
235-
action.Edit.DocumentChanges = append(action.Edit.DocumentChanges, documentChanges(fh, edits)...)
265+
docChanges := documentChanges(fh, edits)
266+
if analyzer.HighConfidence {
267+
sourceFixAllEdits = append(sourceFixAllEdits, docChanges...)
268+
}
269+
action.Edit.DocumentChanges = append(action.Edit.DocumentChanges, docChanges...)
236270
}
237271
codeActions = append(codeActions, action)
238272
}
239273
}
240-
return codeActions, nil
274+
return codeActions, sourceFixAllEdits, nil
241275
}
242276

243277
func documentChanges(fh source.FileHandle, edits []protocol.TextEdit) []protocol.TextDocumentEdit {

internal/lsp/source/options.go

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func DefaultOptions() Options {
5757
ServerOptions: ServerOptions{
5858
SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{
5959
Go: {
60+
protocol.SourceFixAll: true,
6061
protocol.SourceOrganizeImports: true,
6162
protocol.QuickFix: true,
6263
},
@@ -508,5 +509,7 @@ func defaultAnalyzers() map[string]Analyzer {
508509
deepequalerrors.Analyzer.Name: {Analyzer: deepequalerrors.Analyzer, Enabled: true},
509510
sortslice.Analyzer.Name: {Analyzer: sortslice.Analyzer, Enabled: true},
510511
testinggoroutine.Analyzer.Name: {Analyzer: testinggoroutine.Analyzer, Enabled: true},
512+
513+
// gofmt -s suite:
511514
}
512515
}

internal/lsp/source/view.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type Snapshot interface {
4646

4747
// FindAnalysisError returns the analysis error represented by the diagnostic.
4848
// This is used to get the SuggestedFixes associated with that error.
49-
FindAnalysisError(ctx context.Context, pkgID, analyzerName, msg string, rng protocol.Range) (*Error, error)
49+
FindAnalysisError(ctx context.Context, pkgID, analyzerName, msg string, rng protocol.Range) (*Error, *Analyzer, error)
5050

5151
// ModTidyHandle returns a ModTidyHandle for the given go.mod file handle.
5252
// This function can have no data or error if there is no modfile detected.
@@ -368,6 +368,10 @@ const (
368368
type Analyzer struct {
369369
Analyzer *analysis.Analyzer
370370
Enabled bool
371+
372+
// If this is true, then we can apply the suggested fixes
373+
// as part of a source.FixAll codeaction.
374+
HighConfidence bool
371375
}
372376

373377
// Package represents a Go package that has been type-checked. It maintains

0 commit comments

Comments
 (0)