Skip to content

Commit f21a1dc

Browse files
committed
gopls: add initial support for pull diagnostics
Implement the scaffolding for pull diagnostics. For now, these are only supported for Go files, only return parse/type errors for the narrowest package in the default view, do not report related diagnostics, and do not run analysis. All of these limitations can be fixed, but this implementation should be sufficient for some end-to-end testing. Since the implementation is incomplete, guard the server capability behind a new internal "pullDiagnostics" setting. Wire in pull diagnostics to the marker tests: if the server supports it ("pullDiagnostics": true), use pull diagnostics rather than awaiting to collect the marker test diagnostics. For golang/go#53275 Change-Id: If6d1c0838d69e43f187863adeca6a3bd5d9bb45d Reviewed-on: https://go-review.googlesource.com/c/tools/+/616835 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent c19060b commit f21a1dc

File tree

18 files changed

+339
-115
lines changed

18 files changed

+339
-115
lines changed

gopls/doc/features/diagnostics.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ common mistakes.
1111
Diagnostics come from two main sources: compilation errors and analysis findings.
1212

1313
- **Compilation errors** are those that you would obtain from running `go
14-
build`. Gopls doesn't actually run the compiler; that would be too
14+
build`. Gopls doesn't actually run the compiler; that would be too
1515
slow. Instead it runs `go list` (when needed) to compute the
1616
metadata of the compilation, then processes those packages in a similar
1717
manner to the compiler front-end: reading, scanning, and parsing the
@@ -51,7 +51,7 @@ Diagnostics come from two main sources: compilation errors and analysis findings
5151

5252
## Recomputation of diagnostics
5353

54-
Diagnostics are automatically recomputed each time the source files
54+
By default, diagnostics are automatically recomputed each time the source files
5555
are edited.
5656

5757
Compilation errors in open files are updated after a very short delay
@@ -68,9 +68,12 @@ Alternatively, diagnostics may be triggered only after an edited file
6868
is saved, using the
6969
[`diagnosticsTrigger`](../settings.md#diagnosticsTrigger) setting.
7070

71-
Gopls does not currently support "pull-based" diagnostics, which are
72-
computed synchronously when requested by the client; see golang/go#53275.
73-
71+
When initialized with `"pullDiagnostics": true`, gopls also supports
72+
["pull diagnostics"](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics),
73+
an alternative mechanism for recomputing diagnostics in which the client
74+
requests diagnostics from gopls explicitly using the `textDocument/diagnostic`
75+
request. This feature is off by default until the performance of pull
76+
diagnostics is comparable to push diagnostics.
7477

7578
## Quick fixes
7679

@@ -91,6 +94,7 @@ Suggested fixes that are indisputably safe are [code
9194
actions](transformation.md#code-actions) whose kind is
9295
`"source.fixAll"`.
9396
Many client editors have a shortcut to apply all such fixes.
97+
9498
<!-- Note: each Code Action has exactly one kind, so a server
9599
must offer each "safe" action twice, once with its usual kind
96100
and once with kind "source.fixAll".
@@ -111,6 +115,7 @@ Settings:
111115
the base URI for Go package links in the Diagnostic.CodeDescription field.
112116

113117
Client support:
118+
114119
- **VS Code**: Each diagnostic appears as a squiggly underline.
115120
Hovering reveals the details, along with any suggested fixes.
116121
- **Emacs + eglot**: Each diagnostic appears as a squiggly underline.
@@ -119,7 +124,6 @@ Client support:
119124
- **Vim + coc.nvim**: ??
120125
- **CLI**: `gopls check file.go`
121126

122-
123127
<!-- Below we list any quick fixes (by their internal fix name)
124128
that aren't analyzers. -->
125129

gopls/doc/release/v0.17.0.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
2-
31
# Configuration Changes
42

53
The `fieldalignment` analyzer, previously disabled by default, has
@@ -30,13 +28,24 @@ or by selecting a whole declaration or multiple declrations.
3028
In order to avoid ambiguity and surprise about what to extract, some kinds
3129
of paritial selection of a declration cannot invoke this code action.
3230

31+
## Pull diagnostics
32+
33+
When initialized with the option `"pullDiagnostics": true`, gopls will advertise support for the
34+
`textDocument.diagnostic`
35+
[client capability](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics),
36+
which allows editors to request diagnostics directly from gopls using a
37+
`textDocument/diagnostic` request, rather than wait for a
38+
`textDocument/publishDiagnostics` notification. This feature is off by default
39+
until the performance of pull diagnostics is comparable to push diagnostics.
40+
3341
## Standard library version information in Hover
3442

3543
Hovering over a standard library symbol now displays information about the first
3644
Go release containing the symbol. For example, hovering over `errors.As` shows
3745
"Added in go1.13".
3846

3947
## Semantic token modifiers of top-level constructor of types
48+
4049
The semantic tokens response now includes additional modifiers for the top-level
4150
constructor of the type of each symbol:
4251
`interface`, `struct`, `signature`, `pointer`, `array`, `map`, `slice`, `chan`, `string`, `number`, `bool`, and `invalid`.

gopls/internal/cache/diagnostics.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
package cache
66

77
import (
8+
"crypto/sha256"
89
"encoding/json"
910
"fmt"
1011

12+
"golang.org/x/tools/gopls/internal/file"
1113
"golang.org/x/tools/gopls/internal/protocol"
1214
"golang.org/x/tools/gopls/internal/util/bug"
1315
)
@@ -69,6 +71,30 @@ func (d *Diagnostic) String() string {
6971
return fmt.Sprintf("%v: %s", d.Range, d.Message)
7072
}
7173

74+
// Hash computes a hash to identify the diagnostic.
75+
// The hash is for deduplicating within a file, so does not incorporate d.URI.
76+
func (d *Diagnostic) Hash() file.Hash {
77+
h := sha256.New()
78+
for _, t := range d.Tags {
79+
fmt.Fprintf(h, "tag: %s\n", t)
80+
}
81+
for _, r := range d.Related {
82+
fmt.Fprintf(h, "related: %s %s %s\n", r.Location.URI, r.Message, r.Location.Range)
83+
}
84+
fmt.Fprintf(h, "code: %s\n", d.Code)
85+
fmt.Fprintf(h, "codeHref: %s\n", d.CodeHref)
86+
fmt.Fprintf(h, "message: %s\n", d.Message)
87+
fmt.Fprintf(h, "range: %s\n", d.Range)
88+
fmt.Fprintf(h, "severity: %s\n", d.Severity)
89+
fmt.Fprintf(h, "source: %s\n", d.Source)
90+
if d.BundledFixes != nil {
91+
fmt.Fprintf(h, "fixes: %s\n", *d.BundledFixes)
92+
}
93+
var hash [sha256.Size]byte
94+
h.Sum(hash[:0])
95+
return hash
96+
}
97+
7298
type DiagnosticSource string
7399

74100
const (

gopls/internal/golang/diagnostics.go

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,36 @@ import (
1717
"golang.org/x/tools/gopls/internal/util/moremaps"
1818
)
1919

20+
// DiagnoseFile returns pull-based diagnostics for the given file.
21+
func DiagnoseFile(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) ([]*cache.Diagnostic, error) {
22+
mp, err := NarrowestMetadataForFile(ctx, snapshot, uri)
23+
if err != nil {
24+
return nil, err
25+
}
26+
27+
// TODO(rfindley): consider analysing the package concurrently to package
28+
// diagnostics.
29+
30+
// Get package (list/parse/type check) diagnostics.
31+
pkgDiags, err := snapshot.PackageDiagnostics(ctx, mp.ID)
32+
if err != nil {
33+
return nil, err
34+
}
35+
diags := pkgDiags[uri]
36+
37+
// Get analysis diagnostics.
38+
analyzers := analyzers(snapshot.Options().Staticcheck)
39+
pkgAnalysisDiags, err := snapshot.Analyze(ctx, map[PackageID]*metadata.Package{mp.ID: mp}, analyzers, nil)
40+
if err != nil {
41+
return nil, err
42+
}
43+
analysisDiags := moremaps.Group(pkgAnalysisDiags, byURI)[uri]
44+
45+
// Return the merged set of file diagnostics, combining type error analyses
46+
// with type error diagnostics.
47+
return CombineDiagnostics(diags, analysisDiags), nil
48+
}
49+
2050
// Analyze reports go/analysis-framework diagnostics in the specified package.
2151
//
2252
// If the provided tracker is non-nil, it may be used to provide notifications
@@ -30,15 +60,63 @@ func Analyze(ctx context.Context, snapshot *cache.Snapshot, pkgIDs map[PackageID
3060
return nil, ctx.Err()
3161
}
3262

33-
analyzers := slices.Collect(maps.Values(settings.DefaultAnalyzers))
34-
if snapshot.Options().Staticcheck {
35-
analyzers = slices.AppendSeq(analyzers, maps.Values(settings.StaticcheckAnalyzers))
36-
}
37-
63+
analyzers := analyzers(snapshot.Options().Staticcheck)
3864
analysisDiagnostics, err := snapshot.Analyze(ctx, pkgIDs, analyzers, tracker)
3965
if err != nil {
4066
return nil, err
4167
}
42-
byURI := func(d *cache.Diagnostic) protocol.DocumentURI { return d.URI }
4368
return moremaps.Group(analysisDiagnostics, byURI), nil
4469
}
70+
71+
// byURI is used for grouping diagnostics.
72+
func byURI(d *cache.Diagnostic) protocol.DocumentURI { return d.URI }
73+
74+
func analyzers(staticcheck bool) []*settings.Analyzer {
75+
analyzers := slices.Collect(maps.Values(settings.DefaultAnalyzers))
76+
if staticcheck {
77+
analyzers = slices.AppendSeq(analyzers, maps.Values(settings.StaticcheckAnalyzers))
78+
}
79+
return analyzers
80+
}
81+
82+
// CombineDiagnostics combines and filters list/parse/type diagnostics from
83+
// tdiags with the analysis adiags, returning the resulting combined set.
84+
//
85+
// Type-error analyzers produce diagnostics that are redundant with type
86+
// checker diagnostics, but more detailed (e.g. fixes). Rather than report two
87+
// diagnostics for the same problem, we combine them by augmenting the
88+
// type-checker diagnostic and discarding the analyzer diagnostic.
89+
//
90+
// If an analysis diagnostic has the same range and message as a
91+
// list/parse/type diagnostic, the suggested fix information (et al) of the
92+
// latter is merged into a copy of the former. This handles the case where a
93+
// type-error analyzer suggests a fix to a type error, and avoids duplication.
94+
//
95+
// The arguments are not modified.
96+
func CombineDiagnostics(tdiags []*cache.Diagnostic, adiags []*cache.Diagnostic) []*cache.Diagnostic {
97+
// Build index of (list+parse+)type errors.
98+
type key struct {
99+
Range protocol.Range
100+
message string
101+
}
102+
combined := make([]*cache.Diagnostic, len(tdiags))
103+
index := make(map[key]int) // maps (Range,Message) to index in tdiags slice
104+
for i, diag := range tdiags {
105+
index[key{diag.Range, diag.Message}] = i
106+
combined[i] = diag
107+
}
108+
109+
// Filter out analysis diagnostics that match type errors,
110+
// retaining their suggested fix (etc) fields.
111+
for _, diag := range adiags {
112+
if i, ok := index[key{diag.Range, diag.Message}]; ok {
113+
copy := *tdiags[i]
114+
copy.SuggestedFixes = diag.SuggestedFixes
115+
copy.Tags = diag.Tags
116+
combined[i] = &copy
117+
continue
118+
}
119+
combined = append(combined, diag)
120+
}
121+
return combined
122+
}

gopls/internal/protocol/generate/tables.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,6 @@ var usedDisambiguate = make(map[string]bool)
120120
var goplsType = map[string]string{
121121
"And_RegOpt_textDocument_colorPresentation": "WorkDoneProgressOptionsAndTextDocumentRegistrationOptions",
122122
"ConfigurationParams": "ParamConfiguration",
123-
"DocumentDiagnosticParams": "string",
124-
"DocumentDiagnosticReport": "string",
125123
"DocumentUri": "DocumentURI",
126124
"InitializeParams": "ParamInitialize",
127125
"LSPAny": "interface{}",

gopls/internal/protocol/tsprotocol.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gopls/internal/protocol/tsserver.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)