1 // Copyright 2018 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.
10 "golang.org/x/tools/go/analysis"
11 "golang.org/x/tools/internal/event"
12 "golang.org/x/tools/internal/lsp/debug/tag"
13 "golang.org/x/tools/internal/lsp/protocol"
14 "golang.org/x/tools/internal/span"
17 type Diagnostic struct {
23 Severity protocol.DiagnosticSeverity
24 Tags []protocol.DiagnosticTag
26 Related []RelatedInformation
29 type SuggestedFix struct {
31 Edits map[span.URI][]protocol.TextEdit
32 Command *protocol.Command
35 type RelatedInformation struct {
41 func GetTypeCheckDiagnostics(ctx context.Context, snapshot Snapshot, pkg Package) TypeCheckDiagnostics {
42 onlyIgnoredFiles := true
43 for _, pgf := range pkg.CompiledGoFiles() {
44 onlyIgnoredFiles = onlyIgnoredFiles && snapshot.IgnoredFile(pgf.URI)
47 return TypeCheckDiagnostics{}
50 // Prepare any additional reports for the errors in this package.
51 for _, e := range pkg.GetErrors() {
52 // We only need to handle lower-level errors.
53 if e.Kind != ListError {
56 // If no file is associated with the error, pick an open file from the package.
57 if e.URI.Filename() == "" {
58 for _, pgf := range pkg.CompiledGoFiles() {
59 if snapshot.IsOpen(pgf.URI) {
65 return typeCheckDiagnostics(ctx, snapshot, pkg)
68 func Analyze(ctx context.Context, snapshot Snapshot, pkg Package, typeCheckResult TypeCheckDiagnostics) (map[span.URI][]*Diagnostic, error) {
69 // Exit early if the context has been canceled. This also protects us
70 // from a race on Options, see golang/go#36699.
74 // If we don't have any list or parse errors, run analyses.
75 analyzers := pickAnalyzers(snapshot, typeCheckResult.HasTypeErrors)
76 analysisErrors, err := snapshot.Analyze(ctx, pkg.ID(), analyzers...)
81 reports := emptyDiagnostics(pkg)
82 // Report diagnostics and errors from root analyzers.
83 for _, e := range analysisErrors {
84 // If the diagnostic comes from a "convenience" analyzer, it is not
85 // meant to provide diagnostics, but rather only suggested fixes.
86 // Skip these types of errors in diagnostics; we will use their
87 // suggested fixes when providing code actions.
88 if isConvenienceAnalyzer(e.Category) {
91 // This is a bit of a hack, but clients > 3.15 will be able to grey out unnecessary code.
92 // If we are deleting code as part of all of our suggested fixes, assume that this is dead code.
93 // TODO(golang/go#34508): Return these codes from the diagnostics themselves.
94 var tags []protocol.DiagnosticTag
95 if onlyDeletions(e.SuggestedFixes) {
96 tags = append(tags, protocol.Unnecessary)
98 // Type error analyzers only alter the tags for existing type errors.
99 if _, ok := snapshot.View().Options().TypeErrorAnalyzers[e.Category]; ok {
100 existingDiagnostics := typeCheckResult.Diagnostics[e.URI]
101 for _, d := range existingDiagnostics {
102 if r := protocol.CompareRange(e.Range, d.Range); r != 0 {
105 if e.Message != d.Message {
108 d.Tags = append(d.Tags, tags...)
111 reports[e.URI] = append(reports[e.URI], &Diagnostic{
115 Severity: protocol.SeverityWarning,
124 func pickAnalyzers(snapshot Snapshot, hadTypeErrors bool) []*analysis.Analyzer {
125 // Always run convenience analyzers.
126 categories := []map[string]Analyzer{snapshot.View().Options().ConvenienceAnalyzers}
127 // If we had type errors, only run type error analyzers.
129 categories = append(categories, snapshot.View().Options().TypeErrorAnalyzers)
131 categories = append(categories, snapshot.View().Options().DefaultAnalyzers, snapshot.View().Options().StaticcheckAnalyzers)
133 var analyzers []*analysis.Analyzer
134 for _, m := range categories {
135 for _, a := range m {
136 if a.IsEnabled(snapshot.View()) {
137 analyzers = append(analyzers, a.Analyzer)
144 func FileDiagnostics(ctx context.Context, snapshot Snapshot, uri span.URI) (VersionedFileIdentity, []*Diagnostic, error) {
145 fh, err := snapshot.GetVersionedFile(ctx, uri)
147 return VersionedFileIdentity{}, nil, err
149 pkg, _, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
151 return VersionedFileIdentity{}, nil, err
153 typeCheckResults := GetTypeCheckDiagnostics(ctx, snapshot, pkg)
154 diagnostics := typeCheckResults.Diagnostics[fh.URI()]
155 if !typeCheckResults.HasParseOrListErrors {
156 reports, err := Analyze(ctx, snapshot, pkg, typeCheckResults)
158 return VersionedFileIdentity{}, nil, err
160 diagnostics = append(diagnostics, reports[fh.URI()]...)
162 return fh.VersionedFileIdentity(), diagnostics, nil
165 type TypeCheckDiagnostics struct {
167 HasParseOrListErrors bool
168 Diagnostics map[span.URI][]*Diagnostic
171 type diagnosticSet struct {
172 listErrors, parseErrors, typeErrors []*Diagnostic
175 func typeCheckDiagnostics(ctx context.Context, snapshot Snapshot, pkg Package) TypeCheckDiagnostics {
176 ctx, done := event.Start(ctx, "source.diagnostics", tag.Package.Of(pkg.ID()))
177 _ = ctx // circumvent SA4006
180 diagSets := make(map[span.URI]*diagnosticSet)
181 for _, e := range pkg.GetErrors() {
185 Severity: protocol.SeverityError,
188 set, ok := diagSets[e.URI]
190 set = &diagnosticSet{}
191 diagSets[e.URI] = set
195 set.parseErrors = append(set.parseErrors, diag)
196 diag.Source = "syntax"
198 set.typeErrors = append(set.typeErrors, diag)
199 diag.Source = "compiler"
201 set.listErrors = append(set.listErrors, diag)
202 diag.Source = "go list"
205 typecheck := TypeCheckDiagnostics{
206 Diagnostics: emptyDiagnostics(pkg),
208 for uri, set := range diagSets {
209 // Don't report type errors if there are parse errors or list errors.
210 diags := set.typeErrors
212 case len(set.parseErrors) > 0:
213 typecheck.HasParseOrListErrors = true
214 diags = set.parseErrors
215 case len(set.listErrors) > 0:
216 typecheck.HasParseOrListErrors = true
217 if len(pkg.MissingDependencies()) > 0 {
218 diags = set.listErrors
220 case len(set.typeErrors) > 0:
221 typecheck.HasTypeErrors = true
223 typecheck.Diagnostics[uri] = diags
228 func emptyDiagnostics(pkg Package) map[span.URI][]*Diagnostic {
229 diags := map[span.URI][]*Diagnostic{}
230 for _, pgf := range pkg.CompiledGoFiles() {
231 if _, ok := diags[pgf.URI]; !ok {
238 // onlyDeletions returns true if all of the suggested fixes are deletions.
239 func onlyDeletions(fixes []SuggestedFix) bool {
240 for _, fix := range fixes {
241 for _, edits := range fix.Edits {
242 for _, edit := range edits {
243 if edit.NewText != "" {
246 if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 {
252 return len(fixes) > 0
255 func isConvenienceAnalyzer(category string) bool {
256 for _, a := range DefaultOptions().ConvenienceAnalyzers {
257 if category == a.Analyzer.Name {