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
|
// Copyright 2018 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 source
import (
"context"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/span"
)
type SuggestedFix struct {
Title string
Edits map[span.URI][]protocol.TextEdit
Command *protocol.Command
ActionKind protocol.CodeActionKind
}
type RelatedInformation struct {
URI span.URI
Range protocol.Range
Message string
}
// Analyze reports go/analysis-framework diagnostics in the specified package.
func Analyze(ctx context.Context, snapshot Snapshot, pkgid PackageID, includeConvenience bool) (map[span.URI][]*Diagnostic, error) {
// Exit early if the context has been canceled. This also protects us
// from a race on Options, see golang/go#36699.
if ctx.Err() != nil {
return nil, ctx.Err()
}
options := snapshot.View().Options()
categories := []map[string]*Analyzer{
options.DefaultAnalyzers,
options.StaticcheckAnalyzers,
options.TypeErrorAnalyzers,
}
if includeConvenience { // e.g. for codeAction
categories = append(categories, options.ConvenienceAnalyzers) // e.g. fillstruct
}
var analyzers []*Analyzer
for _, cat := range categories {
for _, a := range cat {
analyzers = append(analyzers, a)
}
}
analysisDiagnostics, err := snapshot.Analyze(ctx, pkgid, analyzers)
if err != nil {
return nil, err
}
// Report diagnostics and errors from root analyzers.
reports := make(map[span.URI][]*Diagnostic)
for _, diag := range analysisDiagnostics {
reports[diag.URI] = append(reports[diag.URI], diag)
}
return reports, nil
}
// FileDiagnostics reports diagnostics in the specified file,
// as used by the "gopls check" command.
//
// TODO(adonovan): factor in common with (*Server).codeAction, which
// executes { PackageForFile; Analyze } too?
//
// TODO(adonovan): opt: this function is called in a loop from the
// "gopls/diagnoseFiles" nonstandard request handler. It would be more
// efficient to compute the set of packages and TypeCheck and
// Analyze them all at once.
func FileDiagnostics(ctx context.Context, snapshot Snapshot, uri span.URI) (VersionedFileIdentity, []*Diagnostic, error) {
fh, err := snapshot.GetVersionedFile(ctx, uri)
if err != nil {
return VersionedFileIdentity{}, nil, err
}
pkg, _, err := PackageForFile(ctx, snapshot, uri, TypecheckFull, NarrowestPackage)
if err != nil {
return VersionedFileIdentity{}, nil, err
}
adiags, err := Analyze(ctx, snapshot, pkg.ID(), false)
if err != nil {
return VersionedFileIdentity{}, nil, err
}
var fileDiags []*Diagnostic // combine load/parse/type + analysis diagnostics
CombineDiagnostics(pkg, fh.URI(), adiags, &fileDiags, &fileDiags)
return fh.VersionedFileIdentity(), fileDiags, nil
}
// CombineDiagnostics combines and filters list/parse/type diagnostics
// from pkg.DiagnosticsForFile(uri) with analysisDiagnostics[uri], and
// appends the two lists to *outT and *outA, respectively.
//
// Type-error analyzers produce diagnostics that are redundant
// with type checker diagnostics, but more detailed (e.g. fixes).
// Rather than report two diagnostics for the same problem,
// we combine them by augmenting the type-checker diagnostic
// and discarding the analyzer diagnostic.
//
// If an analysis diagnostic has the same range and message as
// a list/parse/type diagnostic, the suggested fix information
// (et al) of the latter is merged into a copy of the former.
// This handles the case where a type-error analyzer suggests
// a fix to a type error, and avoids duplication.
//
// The use of out-slices, though irregular, allows the caller to
// easily choose whether to keep the results separate or combined.
//
// The arguments are not modified.
func CombineDiagnostics(pkg Package, uri span.URI, analysisDiagnostics map[span.URI][]*Diagnostic, outT, outA *[]*Diagnostic) {
// Build index of (list+parse+)type errors.
type key struct {
Range protocol.Range
message string
}
index := make(map[key]int) // maps (Range,Message) to index in tdiags slice
tdiags := pkg.DiagnosticsForFile(uri)
for i, diag := range tdiags {
index[key{diag.Range, diag.Message}] = i
}
// Filter out analysis diagnostics that match type errors,
// retaining their suggested fix (etc) fields.
for _, diag := range analysisDiagnostics[uri] {
if i, ok := index[key{diag.Range, diag.Message}]; ok {
copy := *tdiags[i]
copy.SuggestedFixes = diag.SuggestedFixes
copy.Tags = diag.Tags
tdiags[i] = ©
continue
}
*outA = append(*outA, diag)
}
*outT = append(*outT, tdiags...)
}
|