cuelang.org/go@v0.10.1/internal/golangorgx/gopls/cache/diagnostics.go (about)

     1  // Copyright 2023 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.
     4  
     5  package cache
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  
    11  	"cuelang.org/go/internal/golangorgx/gopls/protocol"
    12  	"cuelang.org/go/internal/golangorgx/gopls/util/bug"
    13  )
    14  
    15  // A InitializationError is an error that causes snapshot initialization to fail.
    16  // It is either the error returned from go/packages.Load, or an error parsing a
    17  // workspace go.work or go.mod file.
    18  //
    19  // Such an error generally indicates that the View is malformed, and will never
    20  // be usable.
    21  type InitializationError struct {
    22  	// MainError is the primary error. Must be non-nil.
    23  	MainError error
    24  
    25  	// Diagnostics contains any supplemental (structured) diagnostics extracted
    26  	// from the load error.
    27  	Diagnostics map[protocol.DocumentURI][]*Diagnostic
    28  }
    29  
    30  func byURI(d *Diagnostic) protocol.DocumentURI { return d.URI } // For use in maps.Group.
    31  
    32  // An Diagnostic corresponds to an LSP Diagnostic.
    33  // https://microsoft.github.io/language-server-protocol/specification#diagnostic
    34  //
    35  // It is (effectively) gob-serializable; see {encode,decode}Diagnostics.
    36  type Diagnostic struct {
    37  	URI      protocol.DocumentURI // of diagnosed file (not diagnostic documentation)
    38  	Range    protocol.Range
    39  	Severity protocol.DiagnosticSeverity
    40  	Code     string // analysis.Diagnostic.Category (or "default" if empty) or hidden go/types error code
    41  	CodeHref string
    42  
    43  	// Source is a human-readable description of the source of the error.
    44  	// Diagnostics generated by an analysis.Analyzer set it to Analyzer.Name.
    45  	Source DiagnosticSource
    46  
    47  	Message string
    48  
    49  	Tags    []protocol.DiagnosticTag
    50  	Related []protocol.DiagnosticRelatedInformation
    51  
    52  	// Fields below are used internally to generate quick fixes. They aren't
    53  	// part of the LSP spec and historically didn't leave the server.
    54  	//
    55  	// Update(2023-05): version 3.16 of the LSP spec included support for the
    56  	// Diagnostic.data field, which holds arbitrary data preserved in the
    57  	// diagnostic for codeAction requests. This field allows bundling additional
    58  	// information for quick-fixes, and gopls can (and should) use this
    59  	// information to avoid re-evaluating diagnostics in code-action handlers.
    60  	//
    61  	// In order to stage this transition incrementally, the 'BundledFixes' field
    62  	// may store a 'bundled' (=json-serialized) form of the associated
    63  	// SuggestedFixes. Not all diagnostics have their fixes bundled.
    64  	BundledFixes   *json.RawMessage
    65  	SuggestedFixes []SuggestedFix
    66  }
    67  
    68  func (d *Diagnostic) String() string {
    69  	return fmt.Sprintf("%v: %s", d.Range, d.Message)
    70  }
    71  
    72  type DiagnosticSource string
    73  
    74  const (
    75  	UnknownError             DiagnosticSource = "<Unknown source>"
    76  	ListError                DiagnosticSource = "go list"
    77  	ParseError               DiagnosticSource = "syntax"
    78  	TypeError                DiagnosticSource = "compiler"
    79  	ModTidyError             DiagnosticSource = "go mod tidy"
    80  	OptimizationDetailsError DiagnosticSource = "optimizer details"
    81  	UpgradeNotification      DiagnosticSource = "upgrade available"
    82  	Vulncheck                DiagnosticSource = "vulncheck imports"
    83  	Govulncheck              DiagnosticSource = "govulncheck"
    84  	TemplateError            DiagnosticSource = "template"
    85  	WorkFileError            DiagnosticSource = "go.work file"
    86  	ConsistencyInfo          DiagnosticSource = "consistency"
    87  )
    88  
    89  // A SuggestedFix represents a suggested fix (for a diagnostic)
    90  // produced by analysis, in protocol form.
    91  //
    92  // The fixes are reported to the client as a set of code actions in
    93  // response to a CodeAction query for a set of diagnostics. Multiple
    94  // SuggestedFixes may be produced for the same logical fix, varying
    95  // only in ActionKind. For example, a fix may be both a Refactor
    96  // (which should appear on the refactoring menu) and a SourceFixAll (a
    97  // clear fix that can be safely applied without explicit consent).
    98  type SuggestedFix struct {
    99  	Title      string
   100  	Edits      map[protocol.DocumentURI][]protocol.TextEdit
   101  	Command    *protocol.Command
   102  	ActionKind protocol.CodeActionKind
   103  }
   104  
   105  // SuggestedFixFromCommand returns a suggested fix to run the given command.
   106  func SuggestedFixFromCommand(cmd protocol.Command, kind protocol.CodeActionKind) SuggestedFix {
   107  	return SuggestedFix{
   108  		Title:      cmd.Title,
   109  		Command:    &cmd,
   110  		ActionKind: kind,
   111  	}
   112  }
   113  
   114  // quickFixesJSON is a JSON-serializable list of quick fixes
   115  // to be saved in the protocol.Diagnostic.Data field.
   116  type quickFixesJSON struct {
   117  	// TODO(rfindley): pack some sort of identifier here for later
   118  	// lookup/validation?
   119  	Fixes []protocol.CodeAction
   120  }
   121  
   122  // bundleQuickFixes attempts to bundle sd.SuggestedFixes into the
   123  // sd.BundledFixes field, so that it can be round-tripped through the client.
   124  // It returns false if the quick-fixes cannot be bundled.
   125  func bundleQuickFixes(sd *Diagnostic) bool {
   126  	if len(sd.SuggestedFixes) == 0 {
   127  		return true
   128  	}
   129  	var actions []protocol.CodeAction
   130  	for _, fix := range sd.SuggestedFixes {
   131  		if fix.Edits != nil {
   132  			// For now, we only support bundled code actions that execute commands.
   133  			//
   134  			// In order to cleanly support bundled edits, we'd have to guarantee that
   135  			// the edits were generated on the current snapshot. But this naively
   136  			// implies that every fix would have to include a snapshot ID, which
   137  			// would require us to republish all diagnostics on each new snapshot.
   138  			//
   139  			// TODO(rfindley): in order to avoid this additional chatter, we'd need
   140  			// to build some sort of registry or other mechanism on the snapshot to
   141  			// check whether a diagnostic is still valid.
   142  			return false
   143  		}
   144  		action := protocol.CodeAction{
   145  			Title:   fix.Title,
   146  			Kind:    fix.ActionKind,
   147  			Command: fix.Command,
   148  		}
   149  		actions = append(actions, action)
   150  	}
   151  	fixes := quickFixesJSON{
   152  		Fixes: actions,
   153  	}
   154  	data, err := json.Marshal(fixes)
   155  	if err != nil {
   156  		bug.Reportf("marshalling quick fixes: %v", err)
   157  		return false
   158  	}
   159  	msg := json.RawMessage(data)
   160  	sd.BundledFixes = &msg
   161  	return true
   162  }
   163  
   164  // BundledQuickFixes extracts any bundled codeActions from the
   165  // diag.Data field.
   166  func BundledQuickFixes(diag protocol.Diagnostic) []protocol.CodeAction {
   167  	var fix quickFixesJSON
   168  	if diag.Data != nil {
   169  		err := protocol.UnmarshalJSON(*diag.Data, &fix)
   170  		if err != nil {
   171  			bug.Reportf("unmarshalling quick fix: %v", err)
   172  			return nil
   173  		}
   174  	}
   175  
   176  	var actions []protocol.CodeAction
   177  	for _, action := range fix.Fixes {
   178  		// See BundleQuickFixes: for now we only support bundling commands.
   179  		if action.Edit != nil {
   180  			bug.Reportf("bundled fix %q includes workspace edits", action.Title)
   181  			continue
   182  		}
   183  		// associate the action with the incoming diagnostic
   184  		// (Note that this does not mutate the fix.Fixes slice).
   185  		action.Diagnostics = []protocol.Diagnostic{diag}
   186  		actions = append(actions, action)
   187  	}
   188  
   189  	return actions
   190  }