Skip to content

Commit

Permalink
gopls/internal/lsp/cache: move quick-fix bundling logic to the cache pkg
Browse files Browse the repository at this point in the history
Change-Id: I49ee8d6a84c12be8e4e993c5f9a1fcd597c27e40
Reviewed-on: https://go-review.googlesource.com/c/tools/+/543718
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
  • Loading branch information
findleyr committed Nov 27, 2023
1 parent 1733061 commit d9b9452
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 83 deletions.
85 changes: 84 additions & 1 deletion gopls/internal/lsp/cache/diagnostics.go
Expand Up @@ -4,7 +4,12 @@

package cache

import "golang.org/x/tools/gopls/internal/lsp/protocol"
import (
"encoding/json"

"golang.org/x/tools/gopls/internal/bug"
"golang.org/x/tools/gopls/internal/lsp/protocol"
)

// SuggestedFixFromCommand returns a suggested fix to run the given command.
func SuggestedFixFromCommand(cmd protocol.Command, kind protocol.CodeActionKind) SuggestedFix {
Expand All @@ -14,3 +19,81 @@ func SuggestedFixFromCommand(cmd protocol.Command, kind protocol.CodeActionKind)
ActionKind: kind,
}
}

// quickFixesJSON is a JSON-serializable list of quick fixes
// to be saved in the protocol.Diagnostic.Data field.
type quickFixesJSON struct {
// TODO(rfindley): pack some sort of identifier here for later
// lookup/validation?
Fixes []protocol.CodeAction
}

// BundleQuickFixes attempts to bundle sd.SuggestedFixes into the
// sd.BundledFixes field, so that it can be round-tripped through the client.
// It returns false if the quick-fixes cannot be bundled.
func BundleQuickFixes(sd *Diagnostic) bool {
if len(sd.SuggestedFixes) == 0 {
return true
}
var actions []protocol.CodeAction
for _, fix := range sd.SuggestedFixes {
if fix.Edits != nil {
// For now, we only support bundled code actions that execute commands.
//
// In order to cleanly support bundled edits, we'd have to guarantee that
// the edits were generated on the current snapshot. But this naively
// implies that every fix would have to include a snapshot ID, which
// would require us to republish all diagnostics on each new snapshot.
//
// TODO(rfindley): in order to avoid this additional chatter, we'd need
// to build some sort of registry or other mechanism on the snapshot to
// check whether a diagnostic is still valid.
return false
}
action := protocol.CodeAction{
Title: fix.Title,
Kind: fix.ActionKind,
Command: fix.Command,
}
actions = append(actions, action)
}
fixes := quickFixesJSON{
Fixes: actions,
}
data, err := json.Marshal(fixes)
if err != nil {
bug.Reportf("marshalling quick fixes: %v", err)
return false
}
msg := json.RawMessage(data)
sd.BundledFixes = &msg
return true
}

// BundledQuickFixes extracts any bundled codeActions from the
// diag.Data field.
func BundledQuickFixes(diag protocol.Diagnostic) []protocol.CodeAction {
if diag.Data == nil {
return nil
}
var fix quickFixesJSON
if err := json.Unmarshal(*diag.Data, &fix); err != nil {
bug.Reportf("unmarshalling quick fix: %v", err)
return nil
}

var actions []protocol.CodeAction
for _, action := range fix.Fixes {
// See BundleQuickFixes: for now we only support bundling commands.
if action.Edit != nil {
bug.Reportf("bundled fix %q includes workspace edits", action.Title)
continue
}
// associate the action with the incoming diagnostic
// (Note that this does not mutate the fix.Fixes slice).
action.Diagnostics = []protocol.Diagnostic{diag}
actions = append(actions, action)
}

return actions
}
1 change: 0 additions & 1 deletion gopls/internal/lsp/cache/pkg.go
Expand Up @@ -74,7 +74,6 @@ var (
IsValidImport = source.IsValidImport
RemoveIntermediateTestVariants = source.RemoveIntermediateTestVariants
IsCommandLineArguments = source.IsCommandLineArguments
BundleQuickFixes = source.BundleQuickFixes
NewFilterer = source.NewFilterer
)

Expand Down
3 changes: 2 additions & 1 deletion gopls/internal/lsp/code_action.go
Expand Up @@ -17,6 +17,7 @@ import (
"golang.org/x/tools/gopls/internal/lsp/analysis/fillstruct"
"golang.org/x/tools/gopls/internal/lsp/analysis/infertypeargs"
"golang.org/x/tools/gopls/internal/lsp/analysis/stubmethods"
"golang.org/x/tools/gopls/internal/lsp/cache"
"golang.org/x/tools/gopls/internal/lsp/command"
"golang.org/x/tools/gopls/internal/lsp/mod"
"golang.org/x/tools/gopls/internal/lsp/protocol"
Expand Down Expand Up @@ -629,7 +630,7 @@ func (s *server) codeActionsMatchingDiagnostics(ctx context.Context, uri protoco
var actions []protocol.CodeAction
var unbundled []protocol.Diagnostic // diagnostics without bundled code actions in their Data field
for _, pd := range pds {
bundled := source.BundledQuickFixes(pd)
bundled := cache.BundledQuickFixes(pd)
if len(bundled) > 0 {
for _, fix := range bundled {
if want[fix.Kind] {
Expand Down
80 changes: 0 additions & 80 deletions gopls/internal/lsp/source/diagnostics.go
Expand Up @@ -6,9 +6,7 @@ package source

import (
"context"
"encoding/json"

"golang.org/x/tools/gopls/internal/bug"
"golang.org/x/tools/gopls/internal/lsp/progress"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/settings"
Expand Down Expand Up @@ -107,81 +105,3 @@ func CombineDiagnostics(tdiags []*Diagnostic, adiags []*Diagnostic, outT, outA *

*outT = append(*outT, tdiags...)
}

// quickFixesJSON is a JSON-serializable list of quick fixes
// to be saved in the protocol.Diagnostic.Data field.
type quickFixesJSON struct {
// TODO(rfindley): pack some sort of identifier here for later
// lookup/validation?
Fixes []protocol.CodeAction
}

// BundleQuickFixes attempts to bundle sd.SuggestedFixes into the
// sd.BundledFixes field, so that it can be round-tripped through the client.
// It returns false if the quick-fixes cannot be bundled.
func BundleQuickFixes(sd *Diagnostic) bool {
if len(sd.SuggestedFixes) == 0 {
return true
}
var actions []protocol.CodeAction
for _, fix := range sd.SuggestedFixes {
if fix.Edits != nil {
// For now, we only support bundled code actions that execute commands.
//
// In order to cleanly support bundled edits, we'd have to guarantee that
// the edits were generated on the current snapshot. But this naively
// implies that every fix would have to include a snapshot ID, which
// would require us to republish all diagnostics on each new snapshot.
//
// TODO(rfindley): in order to avoid this additional chatter, we'd need
// to build some sort of registry or other mechanism on the snapshot to
// check whether a diagnostic is still valid.
return false
}
action := protocol.CodeAction{
Title: fix.Title,
Kind: fix.ActionKind,
Command: fix.Command,
}
actions = append(actions, action)
}
fixes := quickFixesJSON{
Fixes: actions,
}
data, err := json.Marshal(fixes)
if err != nil {
bug.Reportf("marshalling quick fixes: %v", err)
return false
}
msg := json.RawMessage(data)
sd.BundledFixes = &msg
return true
}

// BundledQuickFixes extracts any bundled codeActions from the
// diag.Data field.
func BundledQuickFixes(diag protocol.Diagnostic) []protocol.CodeAction {
if diag.Data == nil {
return nil
}
var fix quickFixesJSON
if err := json.Unmarshal(*diag.Data, &fix); err != nil {
bug.Reportf("unmarshalling quick fix: %v", err)
return nil
}

var actions []protocol.CodeAction
for _, action := range fix.Fixes {
// See BundleQuickFixes: for now we only support bundling commands.
if action.Edit != nil {
bug.Reportf("bundled fix %q includes workspace edits", action.Title)
continue
}
// associate the action with the incoming diagnostic
// (Note that this does not mutate the fix.Fixes slice).
action.Diagnostics = []protocol.Diagnostic{diag}
actions = append(actions, action)
}

return actions
}

0 comments on commit d9b9452

Please sign in to comment.