File: diagnostics.go

package info (click to toggle)
golang-github-cue-lang-cue 0.12.0.-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 19,072 kB
  • sloc: sh: 57; makefile: 17
file content (190 lines) | stat: -rw-r--r-- 6,816 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cache

import (
	"encoding/json"
	"fmt"

	"cuelang.org/go/internal/golangorgx/gopls/protocol"
	"cuelang.org/go/internal/golangorgx/gopls/util/bug"
)

// A InitializationError is an error that causes snapshot initialization to fail.
// It is either the error returned from go/packages.Load, or an error parsing a
// workspace go.work or go.mod file.
//
// Such an error generally indicates that the View is malformed, and will never
// be usable.
type InitializationError struct {
	// MainError is the primary error. Must be non-nil.
	MainError error

	// Diagnostics contains any supplemental (structured) diagnostics extracted
	// from the load error.
	Diagnostics map[protocol.DocumentURI][]*Diagnostic
}

func byURI(d *Diagnostic) protocol.DocumentURI { return d.URI } // For use in maps.Group.

// An Diagnostic corresponds to an LSP Diagnostic.
// https://microsoft.github.io/language-server-protocol/specification#diagnostic
//
// It is (effectively) gob-serializable; see {encode,decode}Diagnostics.
type Diagnostic struct {
	URI      protocol.DocumentURI // of diagnosed file (not diagnostic documentation)
	Range    protocol.Range
	Severity protocol.DiagnosticSeverity
	Code     string // analysis.Diagnostic.Category (or "default" if empty) or hidden go/types error code
	CodeHref string

	// Source is a human-readable description of the source of the error.
	// Diagnostics generated by an analysis.Analyzer set it to Analyzer.Name.
	Source DiagnosticSource

	Message string

	Tags    []protocol.DiagnosticTag
	Related []protocol.DiagnosticRelatedInformation

	// Fields below are used internally to generate quick fixes. They aren't
	// part of the LSP spec and historically didn't leave the server.
	//
	// Update(2023-05): version 3.16 of the LSP spec included support for the
	// Diagnostic.data field, which holds arbitrary data preserved in the
	// diagnostic for codeAction requests. This field allows bundling additional
	// information for quick-fixes, and gopls can (and should) use this
	// information to avoid re-evaluating diagnostics in code-action handlers.
	//
	// In order to stage this transition incrementally, the 'BundledFixes' field
	// may store a 'bundled' (=json-serialized) form of the associated
	// SuggestedFixes. Not all diagnostics have their fixes bundled.
	BundledFixes   *json.RawMessage
	SuggestedFixes []SuggestedFix
}

func (d *Diagnostic) String() string {
	return fmt.Sprintf("%v: %s", d.Range, d.Message)
}

type DiagnosticSource string

const (
	UnknownError             DiagnosticSource = "<Unknown source>"
	ListError                DiagnosticSource = "go list"
	ParseError               DiagnosticSource = "syntax"
	TypeError                DiagnosticSource = "compiler"
	ModTidyError             DiagnosticSource = "go mod tidy"
	OptimizationDetailsError DiagnosticSource = "optimizer details"
	UpgradeNotification      DiagnosticSource = "upgrade available"
	Vulncheck                DiagnosticSource = "vulncheck imports"
	Govulncheck              DiagnosticSource = "govulncheck"
	TemplateError            DiagnosticSource = "template"
	WorkFileError            DiagnosticSource = "go.work file"
	ConsistencyInfo          DiagnosticSource = "consistency"
)

// A SuggestedFix represents a suggested fix (for a diagnostic)
// produced by analysis, in protocol form.
//
// The fixes are reported to the client as a set of code actions in
// response to a CodeAction query for a set of diagnostics. Multiple
// SuggestedFixes may be produced for the same logical fix, varying
// only in ActionKind. For example, a fix may be both a Refactor
// (which should appear on the refactoring menu) and a SourceFixAll (a
// clear fix that can be safely applied without explicit consent).
type SuggestedFix struct {
	Title      string
	Edits      map[protocol.DocumentURI][]protocol.TextEdit
	Command    *protocol.Command
	ActionKind protocol.CodeActionKind
}

// SuggestedFixFromCommand returns a suggested fix to run the given command.
func SuggestedFixFromCommand(cmd protocol.Command, kind protocol.CodeActionKind) SuggestedFix {
	return SuggestedFix{
		Title:      cmd.Title,
		Command:    &cmd,
		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 {
	var fix quickFixesJSON
	if diag.Data != nil {
		err := protocol.UnmarshalJSON(*diag.Data, &fix)
		if 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
}