@@ -69,7 +69,7 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
69
69
if err != nil {
70
70
return nil , err
71
71
}
72
- quickFixes , err := quickFixesForDiagnostics (ctx , snapshot , diagnostics , diags )
72
+ quickFixes , err := codeActionsMatchingDiagnostics (ctx , snapshot , diagnostics , diags )
73
73
if err != nil {
74
74
return nil , err
75
75
}
@@ -128,75 +128,68 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
128
128
if err != nil {
129
129
return nil , err
130
130
}
131
- if (wanted [protocol .QuickFix ] || wanted [protocol .SourceFixAll ]) && len (diagnostics ) > 0 {
132
- analysisDiags , err := source .Analyze (ctx , snapshot , pkg )
131
+
132
+ pkgDiagnostics , err := snapshot .DiagnosePackage (ctx , pkg )
133
+ if err != nil {
134
+ return nil , err
135
+ }
136
+ analysisDiags , err := source .Analyze (ctx , snapshot , pkg , true )
137
+ if err != nil {
138
+ return nil , err
139
+ }
140
+ fileDiags := append (pkgDiagnostics [uri ], analysisDiags [uri ]... )
141
+ modURI := snapshot .GoModForFile (fh .URI ())
142
+ if modURI != "" {
143
+ modFH , err := snapshot .GetVersionedFile (ctx , modURI )
133
144
if err != nil {
134
145
return nil , err
135
146
}
136
-
137
- if wanted [protocol .QuickFix ] {
138
- pkgDiagnostics , err := snapshot .DiagnosePackage (ctx , pkg )
139
- if err != nil {
140
- return nil , err
141
- }
142
- quickFixDiags := append (pkgDiagnostics [uri ], analysisDiags [uri ]... )
143
- modURI := snapshot .GoModForFile (fh .URI ())
144
- if modURI != "" {
145
- modFH , err := snapshot .GetVersionedFile (ctx , modURI )
146
- if err != nil {
147
- return nil , err
148
- }
149
- modDiags , err := mod .DiagnosticsForMod (ctx , snapshot , modFH )
150
- if err != nil && ! source .IsNonFatalGoModError (err ) {
151
- // Not a fatal error.
152
- event .Error (ctx , "module suggested fixes failed" , err , tag .Directory .Of (snapshot .View ().Folder ()))
153
- }
154
- quickFixDiags = append (quickFixDiags , modDiags ... )
155
- }
156
- quickFixes , err := quickFixesForDiagnostics (ctx , snapshot , diagnostics , quickFixDiags )
157
- if err != nil {
158
- return nil , err
159
- }
160
- codeActions = append (codeActions , quickFixes ... )
161
-
147
+ modDiags , err := mod .DiagnosticsForMod (ctx , snapshot , modFH )
148
+ if err != nil && ! source .IsNonFatalGoModError (err ) {
149
+ // Not a fatal error.
150
+ event .Error (ctx , "module suggested fixes failed" , err , tag .Directory .Of (snapshot .View ().Folder ()))
162
151
}
163
- if wanted [protocol .SourceFixAll ] {
164
- var fixAllEdits []protocol.TextDocumentEdit
165
- for _ , ad := range analysisDiags [uri ] {
166
- if ad .Analyzer == nil || ! ad .Analyzer .HighConfidence {
167
- continue
168
- }
169
- for _ , fix := range ad .SuggestedFixes {
170
- edits := fix .Edits [fh .URI ()]
171
- if len (edits ) == 0 {
172
- continue
173
- }
174
- fixAllEdits = append (fixAllEdits , documentChanges (fh , edits )... )
175
- }
152
+ fileDiags = append (fileDiags , modDiags ... )
153
+ }
176
154
177
- }
178
- if len (fixAllEdits ) != 0 {
179
- codeActions = append (codeActions , protocol.CodeAction {
180
- Title : "Simplifications" ,
181
- Kind : protocol .SourceFixAll ,
182
- Edit : protocol.WorkspaceEdit {
183
- DocumentChanges : fixAllEdits ,
184
- },
185
- })
186
- }
155
+ // Split diagnostics into fixes, which must match incoming diagnostics,
156
+ // and non-fixes, which must match the requested range. Build actions
157
+ // for all of them.
158
+ var fixDiags , nonFixDiags []* source.Diagnostic
159
+ for _ , d := range fileDiags {
160
+ if len (d .SuggestedFixes ) == 0 {
161
+ continue
162
+ }
163
+ kind := protocol .QuickFix
164
+ if d .Analyzer != nil && d .Analyzer .ActionKind != "" {
165
+ kind = d .Analyzer .ActionKind
166
+ }
167
+ if kind == protocol .QuickFix || kind == protocol .SourceFixAll {
168
+ fixDiags = append (fixDiags , d )
169
+ } else {
170
+ nonFixDiags = append (nonFixDiags , d )
187
171
}
188
172
}
189
- if ctx .Err () != nil {
190
- return nil , ctx .Err ()
173
+
174
+ fixActions , err := codeActionsMatchingDiagnostics (ctx , snapshot , diagnostics , fixDiags )
175
+ if err != nil {
176
+ return nil , err
191
177
}
192
- // Add any suggestions that do not necessarily fix any diagnostics.
193
- if wanted [protocol .RefactorRewrite ] {
194
- fixes , err := convenienceFixes (ctx , snapshot , pkg , uri , params .Range )
178
+ codeActions = append (codeActions , fixActions ... )
179
+
180
+ for _ , nonfix := range nonFixDiags {
181
+ // For now, only show diagnostics for matching lines. Maybe we should
182
+ // alter this behavior in the future, depending on the user experience.
183
+ if ! protocol .Intersect (nonfix .Range , params .Range ) {
184
+ continue
185
+ }
186
+ actions , err := codeActionsForDiagnostic (ctx , snapshot , nonfix , nil )
195
187
if err != nil {
196
188
return nil , err
197
189
}
198
- codeActions = append (codeActions , fixes ... )
190
+ codeActions = append (codeActions , actions ... )
199
191
}
192
+
200
193
if wanted [protocol .RefactorExtract ] {
201
194
fixes , err := extractionFixes (ctx , snapshot , pkg , uri , params .Range )
202
195
if err != nil {
@@ -217,7 +210,14 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
217
210
// Unsupported file kind for a code action.
218
211
return nil , nil
219
212
}
220
- return codeActions , nil
213
+
214
+ var filtered []protocol.CodeAction
215
+ for _ , action := range codeActions {
216
+ if wanted [action .Kind ] {
217
+ filtered = append (filtered , action )
218
+ }
219
+ }
220
+ return filtered , nil
221
221
}
222
222
223
223
func (s * Server ) getSupportedCodeActions () []protocol.CodeActionKind {
@@ -278,94 +278,6 @@ func importDiagnostics(fix *imports.ImportFix, diagnostics []protocol.Diagnostic
278
278
return results
279
279
}
280
280
281
- // diagnosticToAnalyzer return the analyzer associated with a given diagnostic.
282
- // It assumes that the diagnostic's source will be the name of the analyzer.
283
- // If this changes, this approach will need to be reworked.
284
- func diagnosticToAnalyzer (snapshot source.Snapshot , src , msg string ) (analyzer * source.Analyzer ) {
285
- // Make sure that the analyzer we found is enabled.
286
- defer func () {
287
- if analyzer != nil && ! analyzer .IsEnabled (snapshot .View ()) {
288
- analyzer = nil
289
- }
290
- }()
291
- if a , ok := snapshot .View ().Options ().DefaultAnalyzers [src ]; ok {
292
- return a
293
- }
294
- if a , ok := snapshot .View ().Options ().StaticcheckAnalyzers [src ]; ok {
295
- return a
296
- }
297
- if a , ok := snapshot .View ().Options ().ConvenienceAnalyzers [src ]; ok {
298
- return a
299
- }
300
- return nil
301
- }
302
-
303
- func convenienceFixes (ctx context.Context , snapshot source.Snapshot , pkg source.Package , uri span.URI , rng protocol.Range ) ([]protocol.CodeAction , error ) {
304
- var analyzers []* source.Analyzer
305
- for _ , a := range snapshot .View ().Options ().ConvenienceAnalyzers {
306
- if ! a .IsEnabled (snapshot .View ()) {
307
- continue
308
- }
309
- if a .Fix == "" {
310
- event .Error (ctx , "convenienceFixes" , fmt .Errorf ("no suggested fixes for convenience analyzer %s" , a .Analyzer .Name ))
311
- continue
312
- }
313
- analyzers = append (analyzers , a )
314
- }
315
- diagnostics , err := snapshot .Analyze (ctx , pkg .ID (), analyzers )
316
- if err != nil {
317
- return nil , err
318
- }
319
- var codeActions []protocol.CodeAction
320
- for _ , d := range diagnostics {
321
- // For now, only show diagnostics for matching lines. Maybe we should
322
- // alter this behavior in the future, depending on the user experience.
323
- if d .URI != uri {
324
- continue
325
- }
326
-
327
- if ! protocol .Intersect (d .Range , rng ) {
328
- continue
329
- }
330
- action , err := diagnosticToCommandCodeAction (ctx , snapshot , d , nil , protocol .RefactorRewrite )
331
- if err != nil {
332
- return nil , err
333
- }
334
- codeActions = append (codeActions , * action )
335
- }
336
- return codeActions , nil
337
- }
338
-
339
- func diagnosticToCommandCodeAction (ctx context.Context , snapshot source.Snapshot , sd * source.Diagnostic , pd * protocol.Diagnostic , kind protocol.CodeActionKind ) (* protocol.CodeAction , error ) {
340
- // The fix depends on the category of the analyzer. The diagnostic may be
341
- // nil, so use the error's category.
342
- analyzer := diagnosticToAnalyzer (snapshot , string (sd .Source ), sd .Message )
343
- if analyzer == nil {
344
- return nil , fmt .Errorf ("no convenience analyzer for source %s" , sd .Source )
345
- }
346
- if analyzer .Fix == "" {
347
- return nil , fmt .Errorf ("no fix for convenience analyzer %s" , analyzer .Analyzer .Name )
348
- }
349
- cmd , err := command .NewApplyFixCommand (sd .Message , command.ApplyFixArgs {
350
- URI : protocol .URIFromSpanURI (sd .URI ),
351
- Range : sd .Range ,
352
- Fix : analyzer .Fix ,
353
- })
354
- if err != nil {
355
- return nil , err
356
- }
357
- var diagnostics []protocol.Diagnostic
358
- if pd != nil {
359
- diagnostics = append (diagnostics , * pd )
360
- }
361
- return & protocol.CodeAction {
362
- Title : sd .Message ,
363
- Kind : kind ,
364
- Diagnostics : diagnostics ,
365
- Command : & cmd ,
366
- }, nil
367
- }
368
-
369
281
func extractionFixes (ctx context.Context , snapshot source.Snapshot , pkg source.Package , uri span.URI , rng protocol.Range ) ([]protocol.CodeAction , error ) {
370
282
if rng .Start == rng .End {
371
283
return nil , nil
@@ -431,47 +343,63 @@ func documentChanges(fh source.VersionedFileHandle, edits []protocol.TextEdit) [
431
343
}
432
344
}
433
345
434
- func quickFixesForDiagnostics (ctx context.Context , snapshot source.Snapshot , pdiags []protocol.Diagnostic , sdiags []* source.Diagnostic ) ([]protocol.CodeAction , error ) {
435
- var quickFixes []protocol.CodeAction
436
- for _ , e := range sdiags {
346
+ func codeActionsMatchingDiagnostics (ctx context.Context , snapshot source.Snapshot , pdiags []protocol.Diagnostic , sdiags []* source.Diagnostic ) ([]protocol.CodeAction , error ) {
347
+ var actions []protocol.CodeAction
348
+ for _ , sd := range sdiags {
437
349
var diag * protocol.Diagnostic
438
- for _ , d := range pdiags {
439
- if sameDiagnostic (d , e ) {
440
- diag = & d
350
+ for _ , pd := range pdiags {
351
+ if sameDiagnostic (pd , sd ) {
352
+ diag = & pd
441
353
break
442
354
}
443
355
}
444
356
if diag == nil {
445
357
continue
446
358
}
447
- for _ , fix := range e .SuggestedFixes {
448
- action := protocol.CodeAction {
449
- Title : fix .Title ,
450
- Kind : protocol .QuickFix ,
451
- Diagnostics : []protocol.Diagnostic {* diag },
452
- Edit : protocol.WorkspaceEdit {},
453
- Command : fix .Command ,
454
- }
359
+ diagActions , err := codeActionsForDiagnostic (ctx , snapshot , sd , diag )
360
+ if err != nil {
361
+ return nil , err
362
+ }
363
+ actions = append (actions , diagActions ... )
455
364
456
- for uri , edits := range fix .Edits {
457
- fh , err := snapshot .GetVersionedFile (ctx , uri )
458
- if err != nil {
459
- return nil , err
460
- }
461
- action .Edit .DocumentChanges = append (action .Edit .DocumentChanges , protocol.TextDocumentEdit {
462
- TextDocument : protocol.OptionalVersionedTextDocumentIdentifier {
463
- Version : fh .Version (),
464
- TextDocumentIdentifier : protocol.TextDocumentIdentifier {
465
- URI : protocol .URIFromSpanURI (uri ),
466
- },
467
- },
468
- Edits : edits ,
469
- })
365
+ }
366
+ return actions , nil
367
+ }
368
+
369
+ func codeActionsForDiagnostic (ctx context.Context , snapshot source.Snapshot , sd * source.Diagnostic , pd * protocol.Diagnostic ) ([]protocol.CodeAction , error ) {
370
+ var actions []protocol.CodeAction
371
+ for _ , fix := range sd .SuggestedFixes {
372
+ action := protocol.CodeAction {
373
+ Title : fix .Title ,
374
+ Kind : protocol .QuickFix ,
375
+ Edit : protocol.WorkspaceEdit {},
376
+ Command : fix .Command ,
377
+ }
378
+ if pd != nil {
379
+ action .Diagnostics = []protocol.Diagnostic {* pd }
380
+ }
381
+ if sd .Analyzer != nil && sd .Analyzer .ActionKind != "" {
382
+ action .Kind = sd .Analyzer .ActionKind
383
+ }
384
+
385
+ for uri , edits := range fix .Edits {
386
+ fh , err := snapshot .GetVersionedFile (ctx , uri )
387
+ if err != nil {
388
+ return nil , err
470
389
}
471
- quickFixes = append (quickFixes , action )
390
+ action .Edit .DocumentChanges = append (action .Edit .DocumentChanges , protocol.TextDocumentEdit {
391
+ TextDocument : protocol.OptionalVersionedTextDocumentIdentifier {
392
+ Version : fh .Version (),
393
+ TextDocumentIdentifier : protocol.TextDocumentIdentifier {
394
+ URI : protocol .URIFromSpanURI (uri ),
395
+ },
396
+ },
397
+ Edits : edits ,
398
+ })
472
399
}
400
+ actions = append (actions , action )
473
401
}
474
- return quickFixes , nil
402
+ return actions , nil
475
403
}
476
404
477
405
func sameDiagnostic (pd protocol.Diagnostic , sd * source.Diagnostic ) bool {
0 commit comments